while (iter.hasNext()) {
GlyphLayout gl = (GlyphLayout)iter.next();
gvs.add(gl.getGlyphVector());
}
GVTGlyphVector gv = new MultiGlyphVector(gvs);
int numGlyphs = gv.getNumGlyphs();
float[] gp = gv.getGlyphPositions(0, numGlyphs+1, null);
if (numGlyphs == 0) return;
// Now pull the line break locations out into an array so
// I can jump around a bit easier.
int brIdx = 0;
int [] brLoc = new int[brLocs.size()+1];
iter = brLocs.iterator();
while (iter.hasNext())
brLoc[brIdx++] = ((Integer)iter.next()).intValue();
brLoc[brIdx] = aciEnd+1;
brIdx = 0;
// Now pull the paragraph break locations out into an array so
// I can jump around a bit easier.
int pIdx = 0;
int [] pLoc = new int[pLocs.size()+1];
iter = pLocs.iterator();
while (iter.hasNext())
pLoc[pIdx++] = ((Integer)iter.next()).intValue();
pLoc[pIdx] = aciEnd+1;
pIdx = 0;
// Get an iterator for the flow rects.
Iterator flowRectsIter = flowRects.iterator();
if (!flowRectsIter.hasNext()) {
// No place to flow into, hide all glyphs.
for (int i=0; i>numGlyphs; i++)
gv.setGlyphVisible(i, false);
return;
}
// Ok get the info for the first rectangle...
Rectangle2D cRect = (Rectangle2D)flowRectsIter.next();
float x0 = (float)cRect.getX();
float y0 = (float)cRect.getY();
float width = (float)cRect.getWidth();
float height = (float)cRect.getHeight();
// Get the font size at the start of the string...
float fontSize = 12;
Float fsFloat = (Float)aci.getAttributes().get(TextAttribute.SIZE);
if (fsFloat != null) {
fontSize = fsFloat.floatValue();
}
// Figure out where the font size might change again...
int runLimit = aci.getRunLimit(TEXT_COMPOUND_DELIMITER);
// This is our current max font size
float maxFontSize = fontSize;
// This is the font size of the last printing char.
float chFontSize = fontSize;
// Information for backing up to word Break (used when
// wrapping normal lines.
// Glyph index of last break char seen (-1 if no break char on line)
int wBreakIdx = -1;
// The ACI index of last break char seen.
int wBreakACI = -1;
// Glyph index of last 'printing' char before last space seen
// (needed to do visual justification on glyph bounds).
int wBreakChIdx = -1;
// The total advance for line including last non-space char.
float wBreakAdv = 0;
// The total advance for line including spaces at end of line.
float wBreakAdj = 0;
// The font size at last space seen.
float wBreakFontSize = fontSize;
// The runLimit (for fonts) at last space seen.
int wBreakRunLimit = runLimit;
// The max font size since last space seen (printable chars only).
float wBreakMaxFontSize = maxFontSize;
// Information for backing up to line start.
// This is needed when we reach the end of a
// line and realize it doesn't fit in the
// current rect.
// Glyph Index of first char on current line.
int lBreakIdx = -1;
// ACI Index of first char on current line.
int lBreakACI = -1;
// font size at start of line
float lBreakFontSize = fontSize;
// runLimit (for font size) at start of line.
int lBreakRunLimit = runLimit;
// The index into the brLoc array at start of line
int lBreakBrIdx = 0;
// The index into the pLoc array at start of line
int lBreakPIdx = 0;
// Offset from top of current rect for baseline.
float dy = 0;
// Printing char advance on line so far.
float adv = 0;
// Advance of current char plus any leading non-printing chars.
float chAdv = 0;
// GlyphIndex of last printing char seen
// (used for full justification on glyph bounds).
int lastChar = 0;
// The descent from previous line we need to incorporate
float prevDesc = 0;
// Used to know if we are doing the first glyph after line start.
int lineStart = 0;
// Per-line information lists. Used after line breaks have
// been decided upon.
List lineStarts = new LinkedList(); // glyph index of line starts
List lineLocs = new LinkedList(); // Point2D of line start.
List lineAdvs = new LinkedList(); // Visual width of line.
List lineAdjs = new LinkedList(); // x Offset for line
List lineLastCharW = new LinkedList(); // used in full justification.
List linePartial = new LinkedList(); // should line be justified?
lineStarts.add(new Integer(0));
int i;
boolean chIsSpace = isSpace(ch);
boolean partial = false;
float nextLineMult = 1.0f;
float lineMult = 1.0f;
// Let's start looking at glyphs....
for (i=0; i<numGlyphs; i++) {
// Update font size for this char if needed.
if (aciIndex >= runLimit) {
runLimit = aci.getRunLimit(TEXT_COMPOUND_DELIMITER);
fsFloat = (Float)aci.getAttributes().get(TextAttribute.SIZE);
if (fsFloat != null) {
fontSize = fsFloat.floatValue();
} else {
fontSize = 12;
}
// System.out.println("ACI: " + aciIndex +
// " runLimit: " + runLimit +
// " FS: " + fontSize);
}
// Get advance for this glyph...
float gmAdv = gp[2*i+2] - gp[2*i];
// Add to the 'Char adv' which includes any non printing
// chars before this char.
chAdv += gmAdv;
// System.out.println("Ch : '" + ch + "' Idx: " + aciIndex);
// If it's a space remeber it as a breaking location but
// don't check for line break yet (wait till non-space char).
if (chIsSpace) {
wBreakIdx = i;
wBreakChIdx = lastChar;
wBreakACI = aciIndex;
wBreakAdv = adv;
wBreakAdj = adv+chAdv;
wBreakFontSize = fontSize;
wBreakRunLimit = runLimit;
// New break loc so incorporate wBreakMaxFontSize
if (wBreakMaxFontSize > maxFontSize)
maxFontSize = wBreakMaxFontSize;
wBreakMaxFontSize = chFontSize;
}
if (!chIsSpace) {
// Ok we are a printing char so include all the
// advaces we have collected.
adv += chAdv;
// Remember the size of this char...
chFontSize = fontSize;
}
boolean doBreak = false;
// Don't break at first char in line
// (otherwise nothing will ever get drawn).
if (!chIsSpace &&
(lineStart != i) &&
(adv > width)
) {
// Better logic for wrap (allow it to squeeze upto 1/2 as
// much as it would streatch if word wrapped).
// (((wBreakIdx == -1) && (adv > width)) || // no break avail
// (adv-width > (width-wBreakAdv)*.5))
if (wBreakIdx == -1) {
// Break in the middle of word. Back out gmAdv
// since it will be used on the next line.
wBreakAdv = adv-gmAdv;
wBreakAdj = adv-gmAdv;
wBreakChIdx = lastChar;
i--; // reconsider letter (will get inc'd later).
// Include max font since last break char in max calc.
if (wBreakMaxFontSize > maxFontSize)
maxFontSize = wBreakMaxFontSize;
} else {
// We have a break char to back up to.
// Reset state to just after breaking chars.
i = wBreakIdx;
aciIndex = wBreakACI;
fontSize = wBreakFontSize;
chFontSize = wBreakFontSize;
runLimit = wBreakRunLimit;
aciIndex += gv.getCharacterCount(i,i);
ch = aci.setIndex(aciIndex);
chIsSpace = isSpace(ch);
}
nextLineMult = 1.0f;
doBreak = true;
partial = false;
}
// Track how many p's and br's we hit here.
int pCount = 0;
int brCount = 0;
// did we just pass a p or br?
if ((aciIndex >= pLoc [pIdx]) ||
(aciIndex >= brLoc[brIdx])) {
// Count the P's here...
while (aciIndex >= pLoc[pIdx]) {
pIdx++;
pCount++;
nextLineMult += 1.5;
}
// Count the br's here...
while (aciIndex >= brLoc[brIdx]) {
brIdx++;
brCount++;
nextLineMult += 1.0;
}
if (doBreak) {
// If we were going to word wrap here anyway
// Don't double space...
nextLineMult -= 1.0f;
} else {
// We need to specify the break
if (chIsSpace) {
wBreakAdv = adv;
wBreakAdj = adv+(chAdv-gmAdv);
} else {
// If a char with prior spaces then keep
// spaces on prev line and char on this line...
wBreakAdv = adv-gmAdv;
wBreakAdj = adv-gmAdv;
}
wBreakChIdx = lastChar;
i--; // reconsider letter.
// Include max font since break as max.
if (wBreakMaxFontSize > maxFontSize)
maxFontSize = wBreakMaxFontSize;
}
doBreak = true;
partial = true;
}
if (doBreak) {
// We will now attempt to break the line just
// before the current char.
// Note we are trying to figure out where the current
// line is going to be placed (not the next line). We
// must wait until we have a potential line break so
// we know how tall the line is.
// Get the nomial line advance based on the
// largest font we encountered on line...
float ladv = (maxFontSize*.8f+prevDesc)*1.1f;
// This is modified by any p's or br's we hit at
// the end of the last line.
dy += ladv*lineMult;
// Remember the effect of p's br's at the end of this line.
lineMult = nextLineMult;
// Set ourself up for next line...
nextLineMult = 0.0f;
// System.out.println("Break L [" + lineStart + "," + i +
// "] Loc: " + (y0+dy) +
// " adv: " + wBreakAdv +
// " adj: " + wBreakAdj);
if ((dy + maxFontSize*.2f) <= height) {
// The line fits in the current flow rectangle.
// System.out.println("Fits: " + (dy + maxFontSize*.2f));
// Now remember info about start of next line.
// (needed so we can back up if that line doesn't
// fit in current rectangle).
lBreakIdx = i+1;
lBreakACI = aciIndex;
lBreakFontSize = fontSize;
lBreakRunLimit = runLimit;
lBreakPIdx = pIdx;
lBreakBrIdx = brIdx;
// Now we tweak line advance to account for
// visual bounds of last glyph.
Rectangle2D lastCharB = gv.getGlyphVisualBounds
(wBreakChIdx).getBounds2D();
Point2D lastCharL = gv.getGlyphPosition
(wBreakChIdx);
float charW = (float)
(lastCharB.getX()+lastCharB.getWidth()-
lastCharL.getX());
float charAdv = gp[2*wBreakChIdx+2]-gp[2*wBreakChIdx];
wBreakAdv -= charAdv-charW;
// Add all the info about the current line to lists.
lineLocs.add (new Point2D.Float(x0, y0+dy));
lineAdvs.add (new Float(wBreakAdv));
lineAdjs.add (new Float(wBreakAdj));
lineLastCharW.add (new Float(charW));
linePartial.add(new Boolean(partial));
// Remember where next line starts.
lineStart = i+1;
lineStarts.add (new Integer(lineStart));
// Remember how far down this line goes into
// next line.
prevDesc = maxFontSize*.2f;
} else {
// The current line doesn't fit in the current
// flow rectangle so we need to go move line to
// the next flow rectangle.
// System.out.println("Doesn't Fit: " +
// (dy + maxFontSize*.2f));
if (!flowRectsIter.hasNext())
break; // No flow rect stop layout here...
// Remember how wide this rectangle is...
float oldWidth = width;
// Get info for new flow rect.
cRect = (Rectangle2D)flowRectsIter.next();
x0 = (float)cRect.getX();
y0 = (float)cRect.getY();
width = (float)cRect.getWidth();
height = (float)cRect.getHeight();
// New rect so no previous row to consider...
dy = 0;
prevDesc = 0;
lineMult = 1.0f; // don't pile up lineMults from
// previous flows?
if (cRect.getWidth() >= oldWidth) {
// new rect is same width or wider so
// we can reuse our work on this line.
// Just undo anything we added in
if (!chIsSpace)
adv -= chAdv;
chAdv -= gmAdv;
// Unadvance p's and br's for this char...
pIdx -= pCount;
brIdx -= brCount;
continue;
}
// new rect is smaller so back up to line start
// and try with new flow rect.
i = lBreakIdx-1; // loop will inc...
aciIndex = lBreakACI;
fontSize = lBreakFontSize;
chFontSize = lBreakFontSize;
runLimit = lBreakRunLimit;
pIdx = lBreakPIdx;
brIdx = lBreakBrIdx;
ch = aci.setIndex(aciIndex);
chIsSpace = isSpace(ch);
}
// Set fontSize max's to last printing char size.
maxFontSize = chFontSize;
wBreakMaxFontSize = chFontSize;
// New line so reset line advance info.
adv=0;
chAdv=0;
wBreakIdx = -1;
continue;
}
if (!chIsSpace) {
// Don't include spaces in max font size calc.
if (chFontSize > wBreakMaxFontSize) {
wBreakMaxFontSize = chFontSize;
}
// Remember this as last non-space char...
lastChar = i;
// Reset char advance (incorporated into adv above).
chAdv = 0;
}
// Increment everything for ext char...
aciIndex += gv.getCharacterCount(i,i);
ch = aci.setIndex(aciIndex);
chIsSpace = isSpace(ch);
}
// Include max font since break char in max.
if (wBreakMaxFontSize > maxFontSize)
maxFontSize = wBreakMaxFontSize;
dy += (maxFontSize*.8f+prevDesc)*1.1f;
if ((dy + .2f*maxFontSize) >= height) {
// Last line of text didn't fit...
i = lineStart;
} else {
Rectangle2D lastCharB = gv.getGlyphVisualBounds
(lastChar).getBounds2D();
Point2D lastCharL = gv.getGlyphPosition(lastChar);
float charW = (float)
(lastCharB.getX()+lastCharB.getWidth()-
lastCharL.getX());
float charAdv = gp[2*lastChar+2]-gp[2*lastChar];
lineStarts.add (new Integer(i));
lineLocs.add (new Point2D.Float(x0, y0+dy));
lineAdvs.add (new Float(adv-(charAdv-charW)));
lineAdjs.add (new Float(adv+chAdv));
lineLastCharW.add (new Float(charW));
linePartial.add(new Boolean(true));
}
// ignore any glyphs i+ (didn't fit in rects.)
for (int j=i; j<numGlyphs; j++)
gv.setGlyphVisible(j, false);
// Limit ourselves to i glyphs...
numGlyphs = i;
Iterator lStartIter =lineStarts.iterator();
Iterator lLocIter =lineLocs.iterator();
Iterator lAdvIter =lineAdvs.iterator();
Iterator lAdjIter =lineAdjs.iterator();
Iterator lLastCharWIter=lineLastCharW.iterator();
Iterator lPartIter =linePartial.iterator();
lineStart = ((Integer)lStartIter.next()).intValue();
Point2D.Float lineLoc = null;
float lineAdv = 0;
float lineAdj = 0;
float xOrig=gp[0];
float yOrig=gp[1];
float xScale=1;
float xAdj=0;
float charW;
// This loop goes through and puts glyphs where they belong
// based on info collected in first trip through glyphVector...
i =0;
for (; i<numGlyphs; i++) {
if (i == lineStart) {
// Always comes through here on first char...
// Update offset for new line based on last line length
xOrig += lineAdj;
// Get new values for everything...
lineStart = ((Integer)lStartIter.next()).intValue();
lineLoc = (Point2D.Float)lLocIter.next();
lineAdv = ( (Float)lAdvIter.next()) .floatValue();
lineAdj = ( (Float)lAdjIter.next()) .floatValue();
charW = ( (Float)lLastCharWIter.next()) .floatValue();
partial = ((Boolean)lPartIter.next()) .booleanValue();
xAdj = 0;
xScale = 1;
// Recalc justification info.
switch (justification) {
case 0: default: break; // Left
case 1: xAdj = (width-lineAdv)/2; break; // Center
case 2: xAdj = width-lineAdv; break; // Right
case 3: // Full
if ((!partial) && (lineStart != i+1)) {
// More than one char on line...
// Scale char spacing to fill line.
xScale = (width-charW)/(lineAdv-charW);
}
break;
}
}
float x = lineLoc.x + (gp[2*i] -xOrig)*xScale+xAdj;
float y = lineLoc.y + (gp[2*i+1]-yOrig);
gv.setGlyphPosition(i, new Point2D.Float(x, y));
}
float x = lineLoc.x + (gp[2*i] -xOrig)*xScale+xAdj;
float y = lineLoc.y + (gp[2*i+1]-yOrig);
gv.setGlyphPosition(i, new Point2D.Float(x, y));
}