/*****************************************************************************
* Copyright (C) The Apache Software Foundation. All rights reserved. *
* ------------------------------------------------------------------------- *
* This software is published under the terms of the Apache Software License *
* version 1.1, a copy of which has been included with this distribution in *
* the LICENSE file. *
*****************************************************************************/
package org.apache.batik.gvt.text;
import java.awt.font.FontRenderContext;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.text.AttributedCharacterIterator;
import org.apache.batik.gvt.font.AWTGVTFont;
import org.apache.batik.gvt.font.GVTFont;
import org.apache.batik.gvt.font.GVTGlyphVector;
import org.apache.batik.gvt.font.GVTLineMetrics;
public class GlyphIterator {
public static final AttributedCharacterIterator.Attribute PREFORMATTED
= GVTAttributedCharacterIterator.TextAttribute.PREFORMATTED;
public static final AttributedCharacterIterator.Attribute FLOW_LINE_BREAK
= GVTAttributedCharacterIterator.TextAttribute.FLOW_LINE_BREAK;
public static final AttributedCharacterIterator.Attribute
TEXT_COMPOUND_DELIMITER
= GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_DELIMITER;
public static final
AttributedCharacterIterator.Attribute GVT_FONT
= GVTAttributedCharacterIterator.TextAttribute.GVT_FONT;
public static final char SOFT_HYPHEN = 0x00AD;
public static final char ZERO_WIDTH_SPACE = 0x200B;
public static final char ZERO_WIDTH_JOINER = 0x200D;
// Glyph index of current glyph
int idx = -1;
// Glyph index of last 'printing' glyph.
int chIdx = -1;
int lineIdx = -1;
// The ACI index of current glyph.
int aciIdx = -1;
// The number of characters in ACI for current Glyph.
int charCount = -1;
// The total advance for line including last non-space glyph
float adv = 0;
// The total advance for line including spaces at end of line.
float adj = 0;
// The runLimit for current font
int runLimit = 0;
// The runLimit for current line element (need a line break at end).
int lineBreakRunLimit = 0;
int lineBreakCount = 0;
GVTFont font = null;
int fontStart = 0;
float maxAscent = 0;
float maxDescent = 0;
float maxFontSize = 0;
float width = 0;
// The current char (from ACI)
char ch = 0;
// The number of glyphs in gv.
int numGlyphs = 0;
// The AttributedCharacterIterator.
AttributedCharacterIterator aci;
// The GVTGlyphVector for this Text chunk.
GVTGlyphVector gv;
// The GlyphPositions for this GlyphVector (Shared)
float [] gp;
// The font render context for this GylphVector.
FontRenderContext frc;
// The Indexes of new leftShift amounts (soft-hyphens)
int [] leftShiftIdx = null;
// The amount of new leftShifts (soft-hyphen adv)
float [] leftShiftAmt = null;
// The current left shift (inherited from previous line).
int leftShift = 0;
Point2D gvBase = null;
public GlyphIterator(AttributedCharacterIterator aci,
GVTGlyphVector gv) {
this.aci = aci;
this.gv = gv;
this.idx = 0;
this.chIdx = 0;
this.lineIdx = 0;
this.aciIdx = aci.getBeginIndex();
this.charCount = gv.getCharacterCount(idx, idx);
this.ch = aci.first();
this.frc = gv.getFontRenderContext();
this.font = (GVTFont)aci.getAttribute(GVT_FONT);
if (font == null) {
font = new AWTGVTFont(aci.getAttributes());
}
fontStart = aciIdx;
this.maxFontSize = -Float.MAX_VALUE;
this.maxAscent = -Float.MAX_VALUE;
this.maxDescent = -Float.MAX_VALUE;
// Figure out where the font size might change again...
this.runLimit = aci.getRunLimit(TEXT_COMPOUND_DELIMITER);
this.lineBreakRunLimit = aci.getRunLimit(FLOW_LINE_BREAK);
Object o = (Object)aci.getAttribute(FLOW_LINE_BREAK);
this.lineBreakCount = (o == null)?0:1;
this.numGlyphs = gv.getNumGlyphs();
this.gp = gv.getGlyphPositions(0, this.numGlyphs+1, null);
this.gvBase = new Point2D.Float(gp[0], gp[1]);
this.adv = getCharWidth();
this.adj = getCharAdvance();
}
public GlyphIterator(GlyphIterator gi) {
gi.copy(this);
}
public GlyphIterator copy() {
return new GlyphIterator(this);
}
public GlyphIterator copy(GlyphIterator gi) {
if (gi == null)
return new GlyphIterator(this);
gi.idx = this.idx;
gi.chIdx = this.chIdx;
gi.aciIdx = this.aciIdx;
gi.charCount = this.charCount;
gi.adv = this.adv;
gi.adj = this.adj;
gi.runLimit = this.runLimit;
gi.ch = this.ch;
gi.numGlyphs = this.numGlyphs;
gi.gp = this.gp;
gi.gvBase = this.gvBase;
gi.lineBreakRunLimit = this.lineBreakRunLimit;
gi.lineBreakCount = this.lineBreakCount;
gi.frc = this.frc;
gi.font = this.font;
gi.fontStart = this.fontStart;
gi.maxAscent = this.maxAscent;
gi.maxDescent = this.maxDescent;
gi.maxFontSize = this.maxFontSize;
gi.leftShift = this.leftShift;
gi.leftShiftIdx = this.leftShiftIdx;
gi.leftShiftAmt = this.leftShiftAmt;
return gi;
}
/**
* @return The index into glyph vector for current character.
*/
public int getGlyphIndex() { return idx; }
/**
* @return the current character.
*/
public char getChar() { return ch; }
/**
* @return The index into Attributed Character iterator for
* current character.
*/
public int getACIIndex() { return aciIdx; }
/**
* @return The current advance for the line, this is the 'visual width'
* of the current line.
*/
public float getAdv() { return adv; }
/**
* @return The origin of the glyph vector (the point all glyphs are
* layed out with respect to).
*/
public Point2D getOrigin() { return gvBase; }
/**
* @return The current adjustment for the line. This is the ammount
* that needs to be subracted from the following line to get it back
* to the start of the next line.
*/
public float getAdj() { return adj; }
public float getMaxFontSize() {
if (aciIdx >= fontStart) {
int newFS = aciIdx + charCount;
updateLineMetrics(newFS);
fontStart = newFS;
}
return maxFontSize;
}
public float getMaxAscent() {
if (aciIdx >= fontStart) {
int newFS = aciIdx + charCount;
updateLineMetrics(newFS);
fontStart = newFS;
}
return maxAscent;
}
public float getMaxDescent() {
if (aciIdx >= fontStart) {
int newFS = aciIdx + charCount;
updateLineMetrics(newFS);
fontStart = newFS;
}
return maxDescent;
}
public boolean isLastChar() {
return (idx == (numGlyphs-1));
}
public boolean done() {
return (idx >= numGlyphs);
}
public boolean isBreakChar() {
switch (ch) {
case GlyphIterator.ZERO_WIDTH_SPACE: return true;
case GlyphIterator.ZERO_WIDTH_JOINER: return false;
case GlyphIterator.SOFT_HYPHEN: return true;
case ' ': case '\t': return true;
default: return false;
}
}
protected boolean isPrinting(char tstCH) {
switch (ch) {
case GlyphIterator.ZERO_WIDTH_SPACE: return false;
case GlyphIterator.ZERO_WIDTH_JOINER: return false;
case GlyphIterator.SOFT_HYPHEN: return true;
case ' ': case '\t': return false;
default: return true;
}
}
public int getLineBreaks() {
int ret = 0;
if (aciIdx+charCount >= lineBreakRunLimit) {
// Next char is outside this line element so break after
// the current char.
ret = lineBreakCount;
// Update the lineBreakRunLimit, this is a bit tricky since
// The attribute doesn't change until the next glyph...
aci.setIndex(aciIdx+charCount);
lineBreakRunLimit = aci.getRunLimit(FLOW_LINE_BREAK);
aci.setIndex(aciIdx); // Restore location...
Object o = (Object)aci.getAttribute(FLOW_LINE_BREAK);
lineBreakCount = (o == null)?0:1;
}
return ret;
}
/**
* Move iterator to the next char.
*/
public void nextChar() {
if ((ch == SOFT_HYPHEN) ||
(ch == ZERO_WIDTH_SPACE) ||
(ch == ZERO_WIDTH_JOINER)) {
// Special handling for soft hyphens and zero-width spaces
gv.setGlyphVisible(idx, false);
float chAdv = getCharAdvance();
adj -= chAdv;
addLeftShift(idx, chAdv);
}
aciIdx += charCount;
ch = aci.setIndex(aciIdx);
idx++;
charCount = gv.getCharacterCount(idx,idx);
if (idx == numGlyphs) return;
if (aciIdx >= runLimit) {
updateLineMetrics(aciIdx);
runLimit = aci.getRunLimit(TEXT_COMPOUND_DELIMITER);
font = (GVTFont)aci.getAttribute(GVT_FONT);
if (font == null) {
font = new AWTGVTFont(aci.getAttributes());
}
fontStart = aciIdx;
}
float chAdv = getCharAdvance();
adj += chAdv;
if (isPrinting()) {
chIdx = idx;
float chW = getCharWidth();
adv = adj-(chAdv-chW);
}
}
protected void addLeftShift(int idx, float chAdv) {
if (leftShiftIdx == null) {
leftShiftIdx = new int[1];
leftShiftIdx[0] = idx;
leftShiftAmt = new float[1];
leftShiftAmt[0] = chAdv;
} else {
int [] newLeftShiftIdx = new int[leftShiftIdx.length+1];
for (int i=0; i<leftShiftIdx.length; i++)
newLeftShiftIdx[i] = leftShiftIdx[i];
newLeftShiftIdx[leftShiftIdx.length] = idx;
leftShiftIdx = newLeftShiftIdx;
float [] newLeftShiftAmt = new float[leftShiftAmt.length+1];
for (int i=0; i<leftShiftAmt.length; i++)
newLeftShiftAmt[i] = leftShiftAmt[i];
newLeftShiftAmt[leftShiftAmt.length] = chAdv;
leftShiftAmt = newLeftShiftAmt;
}
}
protected void updateLineMetrics(int end) {
GVTLineMetrics glm = font.getLineMetrics
(aci, fontStart, end, frc);
float ascent = glm.getAscent();
float descent = glm.getDescent();
float fontSz = font.getSize();
if (ascent > maxAscent) maxAscent = ascent;
if (descent > maxDescent) maxDescent = descent;
if (fontSz > maxFontSize) maxFontSize = fontSz;
}
public LineInfo newLine(Point2D.Float loc,
float lineWidth,
boolean partial,
Point2D.Float verticalAlignOffset) {
if (ch == SOFT_HYPHEN) {
gv.setGlyphVisible(idx, true);
}
int lsi = 0;
int nextLSI;
if (leftShiftIdx != null) nextLSI = leftShiftIdx[lsi];
else nextLSI = idx+1;
for (int ci=lineIdx; ci<=idx; ci++) {
if (ci == nextLSI) {
leftShift += leftShiftAmt[lsi++];
if (lsi < leftShiftIdx.length)
nextLSI = leftShiftIdx[lsi];
}
gv.setGlyphPosition(ci, new Point2D.Float(gp[2*ci]-leftShift,
gp[2*ci+1]));
}
leftShiftIdx = null;
leftShiftAmt = null;
float lineInfoChW;
int hideIdx;
// System.out.println("ChIdx: " + chIdx);
if ((chIdx != 0) || (isPrinting())) {
lineInfoChW = getCharWidth(chIdx);
hideIdx = chIdx+1;
} else {
lineInfoChW = 0;
hideIdx = 0;
}
int lineInfoIdx = idx+1;
float lineInfoAdv = adv;
float lineInfoAdj = adj;
while (!done()) {
adv=0;
adj=0;
if ((ch == ZERO_WIDTH_SPACE) ||
(ch == ZERO_WIDTH_JOINER))
gv.setGlyphVisible(idx, false);
ch = 0; // Disable soft-hyphen/ZWS advance adjustment.
nextChar();
if (isPrinting()) break;
lineInfoIdx = idx+1;
lineInfoAdj += adj;
}
// hide trailing spaces if any
for (int i = hideIdx; i<lineInfoIdx; i++) {
gv.setGlyphVisible(i, false);
}
maxAscent = -Float.MAX_VALUE;
maxDescent = -Float.MAX_VALUE;
maxFontSize = -Float.MAX_VALUE;
LineInfo ret = new LineInfo(loc, aci, gv, lineIdx, lineInfoIdx,
lineInfoAdj, lineInfoAdv, lineInfoChW,
lineWidth, partial, verticalAlignOffset);
lineIdx = idx;
return ret;
}
public boolean isPrinting() {
if (aci.getAttribute(PREFORMATTED) == Boolean.TRUE)
return true;
return isPrinting(ch);
}
/**
* Get the advance associated with the current glyph
*/
public float getCharAdvance() {
return getCharAdvance(idx);
}
/**
* Get the visual advance associated with the current glyph.
* This is the distance from the location of the glyph to
* the rightmost part of the glyph.
*/
public float getCharWidth() {
return getCharWidth(idx);
}
/**
* Get the advance associated with any glyph
*/
protected float getCharAdvance(int gvIdx) {
return gp[2*gvIdx+2] - gp[2*gvIdx];
}
/**
* Get the visual advance associated with the current glyph.
* This is the distance from the location of the glyph to
* the rightmost part of the glyph.
*/
protected float getCharWidth(int gvIdx) {
Rectangle2D lcBound = gv.getGlyphVisualBounds(gvIdx).getBounds2D();
Point2D lcLoc = gv.getGlyphPosition(gvIdx);
return (float)(lcBound.getX()+lcBound.getWidth()- lcLoc.getX());
}
}