0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017:
0018: /* $Id: TextLayoutManager.java 563951 2007-08-08 17:37:33Z vhennebert $ */
0019:
0020: package org.apache.fop.layoutmgr.inline;
0021:
0022: import java.util.ArrayList;
0023: import java.util.List;
0024: import java.util.LinkedList;
0025: import java.util.ListIterator;
0026:
0027: import org.apache.commons.logging.Log;
0028: import org.apache.commons.logging.LogFactory;
0029: import org.apache.fop.area.Trait;
0030: import org.apache.fop.area.inline.TextArea;
0031: import org.apache.fop.fo.Constants;
0032: import org.apache.fop.fo.FOText;
0033: import org.apache.fop.fonts.Font;
0034: import org.apache.fop.layoutmgr.InlineKnuthSequence;
0035: import org.apache.fop.layoutmgr.KnuthBox;
0036: import org.apache.fop.layoutmgr.KnuthElement;
0037: import org.apache.fop.layoutmgr.KnuthGlue;
0038: import org.apache.fop.layoutmgr.KnuthPenalty;
0039: import org.apache.fop.layoutmgr.KnuthSequence;
0040: import org.apache.fop.layoutmgr.LayoutContext;
0041: import org.apache.fop.layoutmgr.LeafPosition;
0042: import org.apache.fop.layoutmgr.Position;
0043: import org.apache.fop.layoutmgr.PositionIterator;
0044: import org.apache.fop.layoutmgr.TraitSetter;
0045: import org.apache.fop.text.linebreak.LineBreakStatus;
0046: import org.apache.fop.traits.MinOptMax;
0047: import org.apache.fop.traits.SpaceVal;
0048: import org.apache.fop.util.CharUtilities;
0049:
0050: /**
0051: * LayoutManager for text (a sequence of characters) which generates one
0052: * or more inline areas.
0053: */
0054: public class TextLayoutManager extends LeafNodeLayoutManager {
0055:
0056: /**
0057: * Store information about each potential text area.
0058: * Index of character which ends the area, IPD of area, including
0059: * any word-space and letter-space.
0060: * Number of word-spaces?
0061: */
0062: private class AreaInfo {
0063: private short iStartIndex;
0064: private short iBreakIndex;
0065: private short iWScount;
0066: private short iLScount;
0067: private MinOptMax ipdArea;
0068: private boolean bHyphenated;
0069: private boolean isSpace;
0070: private boolean breakOppAfter;
0071:
0072: public AreaInfo(short iSIndex, short iBIndex, short iWS,
0073: short iLS, MinOptMax ipd, boolean bHyph,
0074: boolean isSpace, boolean breakOppAfter) {
0075: iStartIndex = iSIndex;
0076: iBreakIndex = iBIndex;
0077: iWScount = iWS;
0078: iLScount = iLS;
0079: ipdArea = ipd;
0080: bHyphenated = bHyph;
0081: this .isSpace = isSpace;
0082: this .breakOppAfter = breakOppAfter;
0083: }
0084:
0085: public String toString() {
0086: return "[ lscnt=" + iLScount + ", wscnt=" + iWScount
0087: + ", ipd=" + ipdArea.toString() + ", sidx="
0088: + iStartIndex + ", bidx=" + iBreakIndex + ", hyph="
0089: + bHyphenated + ", space=" + isSpace + "]";
0090: }
0091:
0092: }
0093:
0094: // this class stores information about changes in vecAreaInfo
0095: // which are not yet applied
0096: private class PendingChange {
0097: public AreaInfo ai;
0098: public int index;
0099:
0100: public PendingChange(AreaInfo ai, int index) {
0101: this .ai = ai;
0102: this .index = index;
0103: }
0104: }
0105:
0106: /**
0107: * logging instance
0108: */
0109: private static Log log = LogFactory.getLog(TextLayoutManager.class);
0110:
0111: // Hold all possible breaks for the text in this LM's FO.
0112: private ArrayList vecAreaInfo;
0113:
0114: /** Non-space characters on which we can end a line. */
0115: private static final String BREAK_CHARS = "-/";
0116:
0117: /** Used to reduce instantiation of MinOptMax with zero length. Do not modify! */
0118: private static final MinOptMax ZERO_MINOPTMAX = new MinOptMax(0);
0119:
0120: private FOText foText;
0121: private char[] textArray;
0122: /**
0123: * Contains an array of widths to adjust for kerning. The first entry can
0124: * be used to influence the start position of the first letter. The entry i+1 defines the
0125: * cursor advancement after the character i. A null entry means no special advancement.
0126: */
0127: private MinOptMax[] letterAdjustArray; //size = textArray.length + 1
0128:
0129: private static final char NEWLINE = '\n';
0130:
0131: private Font font = null;
0132: /** Start index of first character in this parent Area */
0133: private short iAreaStart = 0;
0134: /** Start index of next TextArea */
0135: private short iNextStart = 0;
0136: /** Size since last makeArea call, except for last break */
0137: private MinOptMax ipdTotal;
0138: /** Size including last break possibility returned */
0139: // private MinOptMax nextIPD = new MinOptMax(0);
0140: /** size of a space character (U+0020) glyph in current font */
0141: private int spaceCharIPD;
0142: private MinOptMax wordSpaceIPD;
0143: private MinOptMax letterSpaceIPD;
0144: /** size of the hyphen character glyph in current font */
0145: private int hyphIPD;
0146: /** 1/1 of word-spacing value */
0147: private SpaceVal ws;
0148: /** 1/2 of word-spacing value */
0149: private SpaceVal halfWS;
0150: /** 1/2 of letter-spacing value */
0151: private SpaceVal halfLS;
0152: /** Number of space characters after previous possible break position. */
0153: private int iNbSpacesPending;
0154:
0155: private boolean bChanged = false;
0156: private int iReturnedIndex = 0;
0157: private short iThisStart = 0;
0158: private short iTempStart = 0;
0159: private LinkedList changeList = null;
0160:
0161: private AlignmentContext alignmentContext = null;
0162:
0163: private int lineStartBAP = 0;
0164: private int lineEndBAP = 0;
0165:
0166: private boolean keepTogether;
0167:
0168: /**
0169: * Create a Text layout manager.
0170: *
0171: * @param node The FOText object to be rendered
0172: */
0173: public TextLayoutManager(FOText node) {
0174: super ();
0175: foText = node;
0176:
0177: textArray = new char[node.endIndex - node.startIndex];
0178: System.arraycopy(node.ca, node.startIndex, textArray, 0,
0179: node.endIndex - node.startIndex);
0180: letterAdjustArray = new MinOptMax[textArray.length + 1];
0181:
0182: vecAreaInfo = new java.util.ArrayList();
0183: }
0184:
0185: /** @see org.apache.fop.layoutmgr.LayoutManager#initialize */
0186: public void initialize() {
0187: font = foText.getCommonFont().getFontState(
0188: foText.getFOEventHandler().getFontInfo(), this );
0189:
0190: // With CID fonts, space isn't neccesary currentFontState.width(32)
0191: spaceCharIPD = font.getCharWidth(' ');
0192: // Use hyphenationChar property
0193: hyphIPD = font
0194: .getCharWidth(foText.getCommonHyphenation().hyphenationCharacter);
0195:
0196: SpaceVal ls = SpaceVal.makeLetterSpacing(foText
0197: .getLetterSpacing());
0198: halfLS = new SpaceVal(MinOptMax.multiply(ls.getSpace(), 0.5),
0199: ls.isConditional(), ls.isForcing(), ls.getPrecedence());
0200:
0201: ws = SpaceVal
0202: .makeWordSpacing(foText.getWordSpacing(), ls, font);
0203: // Make half-space: <space> on either side of a word-space)
0204: halfWS = new SpaceVal(MinOptMax.multiply(ws.getSpace(), 0.5),
0205: ws.isConditional(), ws.isForcing(), ws.getPrecedence());
0206:
0207: // letter space applies only to consecutive non-space characters,
0208: // while word space applies to space characters;
0209: // i.e. the spaces in the string "A SIMPLE TEST" are:
0210: // A<<ws>>S<ls>I<ls>M<ls>P<ls>L<ls>E<<ws>>T<ls>E<ls>S<ls>T
0211: // there is no letter space after the last character of a word,
0212: // nor after a space character
0213: // NOTE: The above is not quite correct. Read on in XSL 1.0, 7.16.2, letter-spacing
0214:
0215: // set letter space and word space dimension;
0216: // the default value "normal" was converted into a MinOptMax value
0217: // in the SpaceVal.makeWordSpacing() method
0218: letterSpaceIPD = ls.getSpace();
0219: wordSpaceIPD = MinOptMax.add(new MinOptMax(spaceCharIPD), ws
0220: .getSpace());
0221:
0222: keepTogether = foText.getKeepTogether().getWithinLine()
0223: .getEnum() == Constants.EN_ALWAYS;
0224:
0225: }
0226:
0227: /**
0228: * Reset position for returning next BreakPossibility.
0229: *
0230: * @param prevPos the position to reset to
0231: */
0232: public void resetPosition(Position prevPos) {
0233: if (prevPos != null) {
0234: // ASSERT (prevPos.getLM() == this)
0235: if (prevPos.getLM() != this ) {
0236: log.error("TextLayoutManager.resetPosition: "
0237: + "LM mismatch!!!");
0238: }
0239: LeafPosition tbp = (LeafPosition) prevPos;
0240: AreaInfo ai = (AreaInfo) vecAreaInfo.get(tbp.getLeafPos());
0241: if (ai.iBreakIndex != iNextStart) {
0242: iNextStart = ai.iBreakIndex;
0243: vecAreaInfo.ensureCapacity(tbp.getLeafPos() + 1);
0244: // TODO: reset or recalculate total IPD = sum of all word IPD
0245: // up to the break position
0246: ipdTotal = ai.ipdArea;
0247: setFinished(false);
0248: }
0249: } else {
0250: // Reset to beginning!
0251: vecAreaInfo.clear();
0252: iNextStart = 0;
0253: setFinished(false);
0254: }
0255: }
0256:
0257: // TODO: see if we can use normal getNextBreakPoss for this with
0258: // extra hyphenation information in LayoutContext
0259: private boolean getHyphenIPD(HyphContext hc, MinOptMax hyphIPD) {
0260: // Skip leading word-space before calculating count?
0261: boolean bCanHyphenate = true;
0262: int iStopIndex = iNextStart + hc.getNextHyphPoint();
0263:
0264: if (textArray.length < iStopIndex) {
0265: iStopIndex = textArray.length;
0266: bCanHyphenate = false;
0267: }
0268: hc.updateOffset(iStopIndex - iNextStart);
0269:
0270: for (; iNextStart < iStopIndex; iNextStart++) {
0271: char c = textArray[iNextStart];
0272: hyphIPD.opt += font.getCharWidth(c);
0273: // letter-space?
0274: }
0275: // Need to include hyphen size too, but don't count it in the
0276: // stored running total, since it would be double counted
0277: // with later hyphenation points
0278: return bCanHyphenate;
0279: }
0280:
0281: /**
0282: * Generate and add areas to parent area.
0283: * This can either generate an area for each TextArea and each space, or
0284: * an area containing all text with a parameter controlling the size of
0285: * the word space. The latter is most efficient for PDF generation.
0286: * Set size of each area.
0287: * @param posIter Iterator over Position information returned
0288: * by this LayoutManager.
0289: * @param context LayoutContext for adjustments
0290: */
0291: public void addAreas(PositionIterator posIter, LayoutContext context) {
0292:
0293: // Add word areas
0294: AreaInfo ai = null;
0295: int iWScount = 0;
0296: int iLScount = 0;
0297: int firstAreaInfoIndex = -1;
0298: int lastAreaInfoIndex = 0;
0299: MinOptMax realWidth = new MinOptMax(0);
0300:
0301: /* On first area created, add any leading space.
0302: * Calculate word-space stretch value.
0303: */
0304: while (posIter.hasNext()) {
0305: LeafPosition tbpNext = (LeafPosition) posIter.next();
0306: if (tbpNext == null) {
0307: continue; //Ignore elements without Positions
0308: }
0309: if (tbpNext.getLeafPos() != -1) {
0310: ai = (AreaInfo) vecAreaInfo.get(tbpNext.getLeafPos());
0311: if (firstAreaInfoIndex == -1) {
0312: firstAreaInfoIndex = tbpNext.getLeafPos();
0313: }
0314: iWScount += ai.iWScount;
0315: iLScount += ai.iLScount;
0316: realWidth.add(ai.ipdArea);
0317: lastAreaInfoIndex = tbpNext.getLeafPos();
0318: }
0319: }
0320: if (ai == null) {
0321: return;
0322: }
0323: int textLength = ai.iBreakIndex - ai.iStartIndex;
0324: if (ai.iLScount == textLength && !ai.bHyphenated
0325: && context.isLastArea()) {
0326: // the line ends at a character like "/" or "-";
0327: // remove the letter space after the last character
0328: realWidth.add(MinOptMax.multiply(letterSpaceIPD, -1));
0329: iLScount--;
0330: }
0331:
0332: for (int i = ai.iStartIndex; i < ai.iBreakIndex; i++) {
0333: MinOptMax ladj = letterAdjustArray[i + 1];
0334: if (ladj != null && ladj.isElastic()) {
0335: iLScount++;
0336: }
0337: }
0338:
0339: // add hyphenation character if the last word is hyphenated
0340: if (context.isLastArea() && ai.bHyphenated) {
0341: realWidth.add(new MinOptMax(hyphIPD));
0342: }
0343:
0344: // Calculate adjustments
0345: int iDifference = 0;
0346: int iTotalAdjust = 0;
0347: int iWordSpaceDim = wordSpaceIPD.opt;
0348: int iLetterSpaceDim = letterSpaceIPD.opt;
0349: double dIPDAdjust = context.getIPDAdjust();
0350: double dSpaceAdjust = context.getSpaceAdjust(); // not used
0351:
0352: // calculate total difference between real and available width
0353: if (dIPDAdjust > 0.0) {
0354: iDifference = (int) ((double) (realWidth.max - realWidth.opt) * dIPDAdjust);
0355: } else {
0356: iDifference = (int) ((double) (realWidth.opt - realWidth.min) * dIPDAdjust);
0357: }
0358:
0359: // set letter space adjustment
0360: if (dIPDAdjust > 0.0) {
0361: iLetterSpaceDim += (int) ((double) (letterSpaceIPD.max - letterSpaceIPD.opt) * dIPDAdjust);
0362: } else {
0363: iLetterSpaceDim += (int) ((double) (letterSpaceIPD.opt - letterSpaceIPD.min) * dIPDAdjust);
0364: }
0365: iTotalAdjust += (iLetterSpaceDim - letterSpaceIPD.opt)
0366: * iLScount;
0367:
0368: // set word space adjustment
0369: //
0370: if (iWScount > 0) {
0371: iWordSpaceDim += (int) ((iDifference - iTotalAdjust) / iWScount);
0372: } else {
0373: // there are no word spaces in this area
0374: }
0375: iTotalAdjust += (iWordSpaceDim - wordSpaceIPD.opt) * iWScount;
0376: if (iTotalAdjust != iDifference) {
0377: // the applied adjustment is greater or smaller than the needed one
0378: log
0379: .trace("TextLM.addAreas: error in word / letter space adjustment = "
0380: + (iTotalAdjust - iDifference));
0381: // set iTotalAdjust = iDifference, so that the width of the TextArea
0382: // will counterbalance the error and the other inline areas will be
0383: // placed correctly
0384: iTotalAdjust = iDifference;
0385: }
0386:
0387: TextArea t = createTextArea(realWidth, iTotalAdjust, context,
0388: wordSpaceIPD.opt - spaceCharIPD, firstAreaInfoIndex,
0389: lastAreaInfoIndex, context.isLastArea());
0390:
0391: // iWordSpaceDim is computed in relation to wordSpaceIPD.opt
0392: // but the renderer needs to know the adjustment in relation
0393: // to the size of the space character in the current font;
0394: // moreover, the pdf renderer adds the character spacing even to
0395: // the last character of a word and to space characters: in order
0396: // to avoid this, we must subtract the letter space width twice;
0397: // the renderer will compute the space width as:
0398: // space width =
0399: // = "normal" space width + letterSpaceAdjust + wordSpaceAdjust
0400: // = spaceCharIPD + letterSpaceAdjust +
0401: // + (iWordSpaceDim - spaceCharIPD - 2 * letterSpaceAdjust)
0402: // = iWordSpaceDim - letterSpaceAdjust
0403: t.setTextLetterSpaceAdjust(iLetterSpaceDim);
0404: t.setTextWordSpaceAdjust(iWordSpaceDim - spaceCharIPD - 2
0405: * t.getTextLetterSpaceAdjust());
0406: if (context.getIPDAdjust() != 0) {
0407: // add information about space width
0408: t.setSpaceDifference(wordSpaceIPD.opt - spaceCharIPD - 2
0409: * t.getTextLetterSpaceAdjust());
0410: }
0411: parentLM.addChildArea(t);
0412: }
0413:
0414: /**
0415: * Create an inline word area.
0416: * This creates a TextArea and sets up the various attributes.
0417: *
0418: * @param width the MinOptMax width of the content
0419: * @param adjust the total ipd adjustment with respect to the optimal width
0420: * @param context the layout context
0421: * @param spaceDiff unused
0422: * @param firstIndex the index of the first AreaInfo used for the TextArea
0423: * @param lastIndex the index of the last AreaInfo used for the TextArea
0424: * @param isLastArea is this TextArea the last in a line?
0425: * @return the new text area
0426: */
0427: protected TextArea createTextArea(MinOptMax width, int adjust,
0428: LayoutContext context, int spaceDiff, int firstIndex,
0429: int lastIndex, boolean isLastArea) {
0430: TextArea textArea;
0431: if (context.getIPDAdjust() == 0.0) {
0432: // create just a TextArea
0433: textArea = new TextArea();
0434: } else {
0435: // justified area: create a TextArea with extra info
0436: // about potential adjustments
0437: textArea = new TextArea(width.max - width.opt, width.opt
0438: - width.min, adjust);
0439: }
0440: textArea.setIPD(width.opt + adjust);
0441: textArea.setBPD(font.getAscender() - font.getDescender());
0442: textArea.setBaselineOffset(font.getAscender());
0443: if (textArea.getBPD() == alignmentContext.getHeight()) {
0444: textArea.setOffset(0);
0445: } else {
0446: textArea.setOffset(alignmentContext.getOffset());
0447: }
0448:
0449: // set the text of the TextArea, split into words and spaces
0450: int wordStartIndex = -1;
0451: AreaInfo areaInfo;
0452: int len = 0;
0453: for (int i = firstIndex; i <= lastIndex; i++) {
0454: areaInfo = (AreaInfo) vecAreaInfo.get(i);
0455: if (areaInfo.isSpace) {
0456: // areaInfo stores information about spaces
0457: // add the spaces - except zero-width spaces - to the TextArea
0458: for (int j = areaInfo.iStartIndex; j < areaInfo.iBreakIndex; j++) {
0459: char spaceChar = textArray[j];
0460: if (!CharUtilities.isZeroWidthSpace(spaceChar)) {
0461: textArea.addSpace(spaceChar, 0, CharUtilities
0462: .isAdjustableSpace(spaceChar));
0463: }
0464: }
0465: } else {
0466: // areaInfo stores information about a word fragment
0467: if (wordStartIndex == -1) {
0468: // here starts a new word
0469: wordStartIndex = i;
0470: len = 0;
0471: }
0472: len += areaInfo.iBreakIndex - areaInfo.iStartIndex;
0473: if (i == lastIndex
0474: || ((AreaInfo) vecAreaInfo.get(i + 1)).isSpace) {
0475: // here ends a new word
0476: // add a word to the TextArea
0477: if (isLastArea && i == lastIndex
0478: && areaInfo.bHyphenated) {
0479: len++;
0480: }
0481: StringBuffer wordChars = new StringBuffer(len);
0482: int[] letterAdjust = new int[len];
0483: int letter = 0;
0484: for (int j = wordStartIndex; j <= i; j++) {
0485: AreaInfo ai = (AreaInfo) vecAreaInfo.get(j);
0486: int lsCount = ai.iLScount;
0487: wordChars.append(textArray, ai.iStartIndex,
0488: ai.iBreakIndex - ai.iStartIndex);
0489: for (int k = 0; k < ai.iBreakIndex
0490: - ai.iStartIndex; k++) {
0491: MinOptMax adj = letterAdjustArray[ai.iStartIndex
0492: + k];
0493: if (letter > 0) {
0494: letterAdjust[letter] = (adj != null ? adj.opt
0495: : 0);
0496: }
0497: if (lsCount > 0) {
0498: letterAdjust[letter] += textArea
0499: .getTextLetterSpaceAdjust();
0500: lsCount--;
0501: }
0502: letter++;
0503: }
0504: }
0505: // String wordChars = new String(textArray, wordStartIndex, len);
0506: if (isLastArea && i == lastIndex
0507: && areaInfo.bHyphenated) {
0508: // add the hyphenation character
0509: wordChars
0510: .append(foText.getCommonHyphenation().hyphenationCharacter);
0511: }
0512: textArea.addWord(wordChars.toString(), 0,
0513: letterAdjust);
0514: wordStartIndex = -1;
0515: }
0516: }
0517: }
0518: TraitSetter.addFontTraits(textArea, font);
0519: textArea.addTrait(Trait.COLOR, foText.getColor());
0520:
0521: TraitSetter.addTextDecoration(textArea, foText
0522: .getTextDecoration());
0523:
0524: return textArea;
0525: }
0526:
0527: private void addToLetterAdjust(int index, int width) {
0528: if (letterAdjustArray[index] == null) {
0529: letterAdjustArray[index] = new MinOptMax(width);
0530: } else {
0531: letterAdjustArray[index].add(width);
0532: }
0533: }
0534:
0535: private void addToLetterAdjust(int index, MinOptMax width) {
0536: if (letterAdjustArray[index] == null) {
0537: letterAdjustArray[index] = new MinOptMax(width);
0538: } else {
0539: letterAdjustArray[index].add(width);
0540: }
0541: }
0542:
0543: /**
0544: * Indicates whether a character is a space in terms of this layout manager.
0545: * @param ch the character
0546: * @return true if it's a space
0547: */
0548: private static boolean isSpace(final char ch) {
0549: return ch == CharUtilities.SPACE
0550: || CharUtilities.isNonBreakableSpace(ch)
0551: || CharUtilities.isFixedWidthSpace(ch);
0552: }
0553:
0554: /** @see org.apache.fop.layoutmgr.LayoutManager#getNextKnuthElements(LayoutContext, int) */
0555: public LinkedList getNextKnuthElements(LayoutContext context,
0556: int alignment) {
0557: lineStartBAP = context.getLineStartBorderAndPaddingWidth();
0558: lineEndBAP = context.getLineEndBorderAndPaddingWidth();
0559: alignmentContext = context.getAlignmentContext();
0560:
0561: LinkedList returnList = new LinkedList();
0562: KnuthSequence sequence = new InlineKnuthSequence();
0563: AreaInfo ai = null;
0564: AreaInfo prevAi = null;
0565: returnList.add(sequence);
0566:
0567: LineBreakStatus lbs = new LineBreakStatus();
0568: iThisStart = iNextStart;
0569: boolean inWord = false;
0570: boolean inWhitespace = false;
0571: char ch = 0;
0572: while (iNextStart < textArray.length) {
0573: ch = textArray[iNextStart];
0574: boolean breakOpportunity = false;
0575: byte breakAction = keepTogether ? LineBreakStatus.PROHIBITED_BREAK
0576: : lbs.nextChar(ch);
0577: switch (breakAction) {
0578: case LineBreakStatus.COMBINING_PROHIBITED_BREAK:
0579: case LineBreakStatus.PROHIBITED_BREAK:
0580: break;
0581: case LineBreakStatus.EXPLICIT_BREAK:
0582: break;
0583: case LineBreakStatus.COMBINING_INDIRECT_BREAK:
0584: case LineBreakStatus.DIRECT_BREAK:
0585: case LineBreakStatus.INDIRECT_BREAK:
0586: breakOpportunity = true;
0587: break;
0588: default:
0589: log.error("Unexpected breakAction: " + breakAction);
0590: }
0591: if (inWord) {
0592: if (breakOpportunity || isSpace(ch) || ch == NEWLINE) {
0593: //Word boundary found, process widths and kerning
0594: int lastIndex = iNextStart;
0595: while (lastIndex > 0
0596: && textArray[lastIndex - 1] == CharUtilities.SOFT_HYPHEN) {
0597: lastIndex--;
0598: }
0599: int wordLength = lastIndex - iThisStart;
0600: boolean kerning = font.hasKerning();
0601: MinOptMax wordIPD = new MinOptMax(0);
0602: for (int i = iThisStart; i < lastIndex; i++) {
0603: char c = textArray[i];
0604:
0605: //character width
0606: int charWidth = font.getCharWidth(c);
0607: wordIPD.add(charWidth);
0608:
0609: //kerning
0610: if (kerning) {
0611: int kern = 0;
0612: if (i > iThisStart) {
0613: char previous = textArray[i - 1];
0614: kern = font.getKernValue(previous, c)
0615: * font.getFontSize() / 1000;
0616: } else if (prevAi != null
0617: && !prevAi.isSpace
0618: && prevAi.iBreakIndex > 0) {
0619: char previous = textArray[prevAi.iBreakIndex - 1];
0620: kern = font.getKernValue(previous, c)
0621: * font.getFontSize() / 1000;
0622: }
0623: if (kern != 0) {
0624: //log.info("Kerning between " + previous + " and " + c + ": " + kern);
0625: addToLetterAdjust(i, kern);
0626: wordIPD.add(kern);
0627: }
0628: }
0629: }
0630: if (kerning
0631: && breakOpportunity
0632: && !isSpace(ch)
0633: && lastIndex > 0
0634: && textArray[lastIndex] == CharUtilities.SOFT_HYPHEN) {
0635: int kern = font.getKernValue(
0636: textArray[lastIndex - 1], ch)
0637: * font.getFontSize() / 1000;
0638: if (kern != 0) {
0639: addToLetterAdjust(lastIndex, kern);
0640: }
0641: }
0642: int iLetterSpaces = wordLength - 1;
0643: // if there is a break opportunity and the next one
0644: // is not a space, it could be used as a line end;
0645: // add one more letter space, in case other text follows
0646: if (breakOpportunity && !isSpace(ch)) {
0647: iLetterSpaces++;
0648: }
0649: wordIPD.add(MinOptMax.multiply(letterSpaceIPD,
0650: iLetterSpaces));
0651:
0652: // create the AreaInfo object
0653: ai = new AreaInfo(
0654: iThisStart,
0655: (short) lastIndex,
0656: (short) 0,
0657: (short) iLetterSpaces,
0658: wordIPD,
0659: textArray[lastIndex] == CharUtilities.SOFT_HYPHEN,
0660: false, breakOpportunity);
0661: vecAreaInfo.add(ai);
0662: prevAi = ai;
0663: iTempStart = iNextStart;
0664:
0665: // create the elements
0666: sequence.addAll(createElementsForAWordFragment(
0667: alignment, ai, vecAreaInfo.size() - 1,
0668: letterSpaceIPD));
0669: ai = null;
0670:
0671: iThisStart = iNextStart;
0672: }
0673: } else if (inWhitespace) {
0674: if (ch != CharUtilities.SPACE || breakOpportunity) {
0675: // End of whitespace
0676: // create the AreaInfo object
0677: ai = new AreaInfo(iThisStart, (short) (iNextStart),
0678: (short) (iNextStart - iThisStart),
0679: (short) 0, MinOptMax.multiply(wordSpaceIPD,
0680: iNextStart - iThisStart), false,
0681: true, breakOpportunity);
0682: vecAreaInfo.add(ai);
0683: prevAi = ai;
0684:
0685: // create the elements
0686: sequence.addAll(createElementsForASpace(alignment,
0687: ai, vecAreaInfo.size() - 1));
0688: ai = null;
0689:
0690: iThisStart = iNextStart;
0691: }
0692: } else {
0693: if (ai != null) {
0694: vecAreaInfo.add(ai);
0695: prevAi = ai;
0696: ai.breakOppAfter = ch == CharUtilities.SPACE
0697: || breakOpportunity;
0698: sequence.addAll(createElementsForASpace(alignment,
0699: ai, vecAreaInfo.size() - 1));
0700: ai = null;
0701: }
0702: if (breakAction == LineBreakStatus.EXPLICIT_BREAK) {
0703: if (lineEndBAP != 0) {
0704: sequence.add(new KnuthGlue(lineEndBAP, 0, 0,
0705: new LeafPosition(this , -1), true));
0706: }
0707: sequence.endSequence();
0708: sequence = new InlineKnuthSequence();
0709: returnList.add(sequence);
0710: }
0711: }
0712:
0713: if ((ch == CharUtilities.SPACE && foText
0714: .getWhitespaceTreatment() == Constants.EN_PRESERVE)
0715: || ch == CharUtilities.NBSPACE) {
0716: // preserved space or non-breaking space:
0717: // create the AreaInfo object
0718: ai = new AreaInfo(iNextStart, (short) (iNextStart + 1),
0719: (short) 1, (short) 0, wordSpaceIPD, false,
0720: true, breakOpportunity);
0721: iThisStart = (short) (iNextStart + 1);
0722: } else if (CharUtilities.isFixedWidthSpace(ch)
0723: || CharUtilities.isZeroWidthSpace(ch)) {
0724: // create the AreaInfo object
0725: MinOptMax ipd = new MinOptMax(font.getCharWidth(ch));
0726: ai = new AreaInfo(iNextStart, (short) (iNextStart + 1),
0727: (short) 0, (short) 0, ipd, false, true,
0728: breakOpportunity);
0729: iThisStart = (short) (iNextStart + 1);
0730: } else if (ch == NEWLINE) {
0731: // linefeed; this can happen when linefeed-treatment="preserve"
0732: iThisStart = (short) (iNextStart + 1);
0733: }
0734: inWord = !isSpace(ch) && ch != NEWLINE;
0735: inWhitespace = ch == CharUtilities.SPACE
0736: && foText.getWhitespaceTreatment() != Constants.EN_PRESERVE;
0737: iNextStart++;
0738: } // end of while
0739:
0740: // Process any last elements
0741: if (inWord) {
0742: int lastIndex = iNextStart;
0743: if (textArray[iNextStart - 1] == CharUtilities.SOFT_HYPHEN) {
0744: lastIndex--;
0745: }
0746: int wordLength = lastIndex - iThisStart;
0747: boolean kerning = font.hasKerning();
0748: MinOptMax wordIPD = new MinOptMax(0);
0749: for (int i = iThisStart; i < lastIndex; i++) {
0750: char c = textArray[i];
0751:
0752: //character width
0753: int charWidth = font.getCharWidth(c);
0754: wordIPD.add(charWidth);
0755:
0756: //kerning
0757: if (kerning) {
0758: int kern = 0;
0759: if (i > iThisStart) {
0760: char previous = textArray[i - 1];
0761: kern = font.getKernValue(previous, c)
0762: * font.getFontSize() / 1000;
0763: } else if (prevAi != null && !prevAi.isSpace) {
0764: char previous = textArray[prevAi.iBreakIndex - 1];
0765: kern = font.getKernValue(previous, c)
0766: * font.getFontSize() / 1000;
0767: }
0768: if (kern != 0) {
0769: //log.info("Kerning between " + previous + " and " + c + ": " + kern);
0770: addToLetterAdjust(i, kern);
0771: wordIPD.add(kern);
0772: }
0773: }
0774: }
0775: int iLetterSpaces = wordLength - 1;
0776: wordIPD.add(MinOptMax.multiply(letterSpaceIPD,
0777: iLetterSpaces));
0778:
0779: // create the AreaInfo object
0780: ai = new AreaInfo(iThisStart, (short) lastIndex, (short) 0,
0781: (short) iLetterSpaces, wordIPD, false, false, false);
0782: vecAreaInfo.add(ai);
0783: iTempStart = iNextStart;
0784:
0785: // create the elements
0786: sequence.addAll(createElementsForAWordFragment(alignment,
0787: ai, vecAreaInfo.size() - 1, letterSpaceIPD));
0788: ai = null;
0789: } else if (inWhitespace) {
0790: ai = new AreaInfo(iThisStart, (short) (iNextStart),
0791: (short) (iNextStart - iThisStart), (short) 0,
0792: MinOptMax.multiply(wordSpaceIPD, iNextStart
0793: - iThisStart), false, true, true);
0794: vecAreaInfo.add(ai);
0795:
0796: // create the elements
0797: sequence.addAll(createElementsForASpace(alignment, ai,
0798: vecAreaInfo.size() - 1));
0799: ai = null;
0800: } else if (ai != null) {
0801: vecAreaInfo.add(ai);
0802: ai.breakOppAfter = ch == CharUtilities.ZERO_WIDTH_SPACE;
0803: sequence.addAll(createElementsForASpace(alignment, ai,
0804: vecAreaInfo.size() - 1));
0805: ai = null;
0806: } else if (ch == NEWLINE) {
0807: if (lineEndBAP != 0) {
0808: sequence.add(new KnuthGlue(lineEndBAP, 0, 0,
0809: new LeafPosition(this , -1), true));
0810: }
0811: sequence.endSequence();
0812: sequence = new InlineKnuthSequence();
0813: returnList.add(sequence);
0814: }
0815:
0816: if (((List) returnList.getLast()).size() == 0) {
0817: //Remove an empty sequence because of a trailing newline
0818: returnList.removeLast();
0819: }
0820: setFinished(true);
0821: if (returnList.size() > 0) {
0822: return returnList;
0823: } else {
0824: return null;
0825: }
0826: }
0827:
0828: /** @see InlineLevelLayoutManager#addALetterSpaceTo(List) */
0829: public List addALetterSpaceTo(List oldList) {
0830: // old list contains only a box, or the sequence: box penalty glue box;
0831: // look at the Position stored in the first element in oldList
0832: // which is always a box
0833: ListIterator oldListIterator = oldList.listIterator();
0834: KnuthElement el = (KnuthElement) oldListIterator.next();
0835: LeafPosition pos = (LeafPosition) ((KnuthBox) el).getPosition();
0836: AreaInfo ai = (AreaInfo) vecAreaInfo.get(pos.getLeafPos());
0837: ai.iLScount++;
0838: ai.ipdArea.add(letterSpaceIPD);
0839: if (BREAK_CHARS.indexOf(textArray[iTempStart - 1]) >= 0) {
0840: // the last character could be used as a line break
0841: // append new elements to oldList
0842: oldListIterator = oldList.listIterator(oldList.size());
0843: oldListIterator.add(new KnuthPenalty(0,
0844: KnuthPenalty.FLAGGED_PENALTY, true,
0845: new LeafPosition(this , -1), false));
0846: oldListIterator.add(new KnuthGlue(letterSpaceIPD.opt,
0847: letterSpaceIPD.max - letterSpaceIPD.opt,
0848: letterSpaceIPD.opt - letterSpaceIPD.min,
0849: new LeafPosition(this , -1), false));
0850: } else if (letterSpaceIPD.min == letterSpaceIPD.max) {
0851: // constant letter space: replace the box
0852: oldListIterator.set(new KnuthInlineBox(ai.ipdArea.opt,
0853: alignmentContext, pos, false));
0854: } else {
0855: // adjustable letter space: replace the glue
0856: oldListIterator.next(); // this would return the penalty element
0857: oldListIterator.next(); // this would return the glue element
0858: oldListIterator
0859: .set(new KnuthGlue(
0860: ai.iLScount * letterSpaceIPD.opt,
0861: ai.iLScount
0862: * (letterSpaceIPD.max - letterSpaceIPD.opt),
0863: ai.iLScount
0864: * (letterSpaceIPD.opt - letterSpaceIPD.min),
0865: new LeafPosition(this , -1), true));
0866: }
0867: return oldList;
0868: }
0869:
0870: /**
0871: * remove the AreaInfo object represented by the given elements,
0872: * so that it won't generate any element when getChangedKnuthElements
0873: * will be called
0874: *
0875: * @param oldList the elements representing the word space
0876: */
0877: public void removeWordSpace(List oldList) {
0878: // find the element storing the Position whose value
0879: // points to the AreaInfo object
0880: ListIterator oldListIterator = oldList.listIterator();
0881: if (((KnuthElement) ((LinkedList) oldList).getFirst())
0882: .isPenalty()) {
0883: // non breaking space: oldList starts with a penalty
0884: oldListIterator.next();
0885: }
0886: if (oldList.size() > 2) {
0887: // alignment is either center, start or end:
0888: // the first two elements does not store the needed Position
0889: oldListIterator.next();
0890: oldListIterator.next();
0891: }
0892: int leafValue = ((LeafPosition) ((KnuthElement) oldListIterator
0893: .next()).getPosition()).getLeafPos();
0894: // only the last word space can be a trailing space!
0895: if (leafValue == vecAreaInfo.size() - 1) {
0896: vecAreaInfo.remove(leafValue);
0897: } else {
0898: log.error("trying to remove a non-trailing word space");
0899: }
0900: }
0901:
0902: /** @see InlineLevelLayoutManager#hyphenate(Position, HyphContext) */
0903: public void hyphenate(Position pos, HyphContext hc) {
0904: AreaInfo ai = (AreaInfo) vecAreaInfo.get(((LeafPosition) pos)
0905: .getLeafPos());
0906: int iStartIndex = ai.iStartIndex;
0907: int iStopIndex;
0908: boolean bNothingChanged = true;
0909:
0910: while (iStartIndex < ai.iBreakIndex) {
0911: MinOptMax newIPD = new MinOptMax(0);
0912: boolean bHyphenFollows;
0913:
0914: if (hc.hasMoreHyphPoints()
0915: && (iStopIndex = iStartIndex
0916: + hc.getNextHyphPoint()) <= ai.iBreakIndex) {
0917: // iStopIndex is the index of the first character
0918: // after a hyphenation point
0919: bHyphenFollows = true;
0920: } else {
0921: // there are no more hyphenation points,
0922: // or the next one is after ai.iBreakIndex
0923: bHyphenFollows = false;
0924: iStopIndex = ai.iBreakIndex;
0925: }
0926:
0927: hc.updateOffset(iStopIndex - iStartIndex);
0928:
0929: //log.info("Word: " + new String(textArray, iStartIndex, iStopIndex - iStartIndex));
0930: for (int i = iStartIndex; i < iStopIndex; i++) {
0931: char c = textArray[i];
0932: newIPD.add(new MinOptMax(font.getCharWidth(c)));
0933: //if (i > iStartIndex) {
0934: if (i < iStopIndex) {
0935: MinOptMax la = this .letterAdjustArray[i + 1];
0936: if ((i == iStopIndex - 1) && bHyphenFollows) {
0937: //the letter adjust here needs to be handled further down during
0938: //element generation because it depends on hyph/no-hyph condition
0939: la = null;
0940: }
0941: if (la != null) {
0942: newIPD.add(la);
0943: }
0944: }
0945: }
0946: // add letter spaces
0947: boolean bIsWordEnd = iStopIndex == ai.iBreakIndex
0948: && ai.iLScount < (ai.iBreakIndex - ai.iStartIndex);
0949: newIPD.add(MinOptMax.multiply(letterSpaceIPD,
0950: (bIsWordEnd ? (iStopIndex - iStartIndex - 1)
0951: : (iStopIndex - iStartIndex))));
0952:
0953: if (!(bNothingChanged && iStopIndex == ai.iBreakIndex && bHyphenFollows == false)) {
0954: // the new AreaInfo object is not equal to the old one
0955: if (changeList == null) {
0956: changeList = new LinkedList();
0957: }
0958: changeList.add(new PendingChange(new AreaInfo(
0959: (short) iStartIndex, (short) iStopIndex,
0960: (short) 0, (short) (bIsWordEnd ? (iStopIndex
0961: - iStartIndex - 1)
0962: : (iStopIndex - iStartIndex)), newIPD,
0963: bHyphenFollows, false, false),
0964: ((LeafPosition) pos).getLeafPos()));
0965: bNothingChanged = false;
0966: }
0967: iStartIndex = iStopIndex;
0968: }
0969: if (!bChanged && !bNothingChanged) {
0970: bChanged = true;
0971: }
0972: }
0973:
0974: /** @see InlineLevelLayoutManager#applyChanges(List) */
0975: public boolean applyChanges(List oldList) {
0976: setFinished(false);
0977:
0978: if (changeList != null) {
0979: int iAddedAI = 0;
0980: int iRemovedAI = 0;
0981: int iOldIndex = -1;
0982: PendingChange currChange = null;
0983: ListIterator changeListIterator = changeList.listIterator();
0984: while (changeListIterator.hasNext()) {
0985: currChange = (PendingChange) changeListIterator.next();
0986: if (currChange.index != iOldIndex) {
0987: iRemovedAI++;
0988: iAddedAI++;
0989: iOldIndex = currChange.index;
0990: vecAreaInfo.remove(currChange.index + iAddedAI
0991: - iRemovedAI);
0992: vecAreaInfo.add(currChange.index + iAddedAI
0993: - iRemovedAI, currChange.ai);
0994: } else {
0995: iAddedAI++;
0996: vecAreaInfo.add(currChange.index + iAddedAI
0997: - iRemovedAI, currChange.ai);
0998: }
0999: }
1000: changeList.clear();
1001: }
1002:
1003: iReturnedIndex = 0;
1004: return bChanged;
1005: }
1006:
1007: /** @see org.apache.fop.layoutmgr.LayoutManager#getChangedKnuthElements(List, int) */
1008: public LinkedList getChangedKnuthElements(List oldList,
1009: int alignment) {
1010: if (isFinished()) {
1011: return null;
1012: }
1013:
1014: LinkedList returnList = new LinkedList();
1015:
1016: while (iReturnedIndex < vecAreaInfo.size()) {
1017: AreaInfo ai = (AreaInfo) vecAreaInfo.get(iReturnedIndex);
1018: if (ai.iWScount == 0) {
1019: // ai refers either to a word or a word fragment
1020: returnList.addAll(createElementsForAWordFragment(
1021: alignment, ai, iReturnedIndex, letterSpaceIPD));
1022: } else {
1023: // ai refers to a space
1024: returnList.addAll(createElementsForASpace(alignment,
1025: ai, iReturnedIndex));
1026: }
1027: iReturnedIndex++;
1028: } // end of while
1029: setFinished(true);
1030: //ElementListObserver.observe(returnList, "text-changed", null);
1031: return returnList;
1032: }
1033:
1034: /** @see InlineLevelLayoutManager#getWordChars(StringBuffer, Position) */
1035: public void getWordChars(StringBuffer sbChars, Position pos) {
1036: int iLeafValue = ((LeafPosition) pos).getLeafPos();
1037: if (iLeafValue != -1) {
1038: AreaInfo ai = (AreaInfo) vecAreaInfo.get(iLeafValue);
1039: sbChars.append(new String(textArray, ai.iStartIndex,
1040: ai.iBreakIndex - ai.iStartIndex));
1041: }
1042: }
1043:
1044: private LinkedList createElementsForASpace(int alignment,
1045: AreaInfo ai, int leafValue) {
1046: LinkedList spaceElements = new LinkedList();
1047: LeafPosition mainPosition = new LeafPosition(this , leafValue);
1048:
1049: if (!ai.breakOppAfter) {
1050: // a non-breaking space
1051: if (alignment == EN_JUSTIFY) {
1052: // the space can stretch and shrink, and must be preserved
1053: // when starting a line
1054: spaceElements.add(new KnuthInlineBox(0, null,
1055: notifyPos(new LeafPosition(this , -1)), true));
1056: spaceElements.add(new KnuthPenalty(0,
1057: KnuthElement.INFINITE, false, new LeafPosition(
1058: this , -1), false));
1059: spaceElements.add(new KnuthGlue(ai.ipdArea.opt,
1060: ai.ipdArea.max - ai.ipdArea.opt, ai.ipdArea.opt
1061: - ai.ipdArea.min, mainPosition, false));
1062: } else {
1063: // the space does not need to stretch or shrink, and must be
1064: // preserved when starting a line
1065: spaceElements.add(new KnuthInlineBox(ai.ipdArea.opt,
1066: null, mainPosition, true));
1067: }
1068: } else {
1069: if (textArray[ai.iStartIndex] != CharUtilities.SPACE
1070: || foText.getWhitespaceTreatment() == Constants.EN_PRESERVE) {
1071: // a breaking space that needs to be preserved
1072: switch (alignment) {
1073: case EN_CENTER:
1074: // centered text:
1075: // if the second element is chosen as a line break these elements
1076: // add a constant amount of stretch at the end of a line and at the
1077: // beginning of the next one, otherwise they don't add any stretch
1078: spaceElements.add(new KnuthGlue(lineEndBAP,
1079: 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH,
1080: 0, new LeafPosition(this , -1), false));
1081: spaceElements.add(new KnuthPenalty(0, 0, false,
1082: new LeafPosition(this , -1), false));
1083: spaceElements.add(new KnuthGlue(
1084: -(lineStartBAP + lineEndBAP),
1085: -6 * LineLayoutManager.DEFAULT_SPACE_WIDTH,
1086: 0, new LeafPosition(this , -1), false));
1087: spaceElements.add(new KnuthInlineBox(0, null,
1088: notifyPos(new LeafPosition(this , -1)),
1089: false));
1090: spaceElements.add(new KnuthPenalty(0,
1091: KnuthElement.INFINITE, false,
1092: new LeafPosition(this , -1), false));
1093: spaceElements.add(new KnuthGlue(ai.ipdArea.opt
1094: + lineStartBAP,
1095: 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH,
1096: 0, mainPosition, false));
1097: break;
1098:
1099: case EN_START: // fall through
1100: case EN_END:
1101: // left- or right-aligned text:
1102: // if the second element is chosen as a line break these elements
1103: // add a constant amount of stretch at the end of a line, otherwise
1104: // they don't add any stretch
1105: spaceElements.add(new KnuthGlue(lineEndBAP,
1106: 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH,
1107: 0, new LeafPosition(this , -1), false));
1108: spaceElements.add(new KnuthPenalty(0, 0, false,
1109: new LeafPosition(this , -1), false));
1110: spaceElements.add(new KnuthGlue(
1111: -(lineStartBAP + lineEndBAP),
1112: -3 * LineLayoutManager.DEFAULT_SPACE_WIDTH,
1113: 0, new LeafPosition(this , -1), false));
1114: spaceElements.add(new KnuthInlineBox(0, null,
1115: notifyPos(new LeafPosition(this , -1)),
1116: false));
1117: spaceElements.add(new KnuthPenalty(0,
1118: KnuthElement.INFINITE, false,
1119: new LeafPosition(this , -1), false));
1120: spaceElements.add(new KnuthGlue(ai.ipdArea.opt
1121: + lineStartBAP, 0, 0, mainPosition, false));
1122: break;
1123:
1124: case EN_JUSTIFY:
1125: // justified text:
1126: // the stretch and shrink depends on the space width
1127: spaceElements.add(new KnuthGlue(lineEndBAP, 0, 0,
1128: new LeafPosition(this , -1), false));
1129: spaceElements.add(new KnuthPenalty(0, 0, false,
1130: new LeafPosition(this , -1), false));
1131: spaceElements.add(new KnuthGlue(
1132: -(lineStartBAP + lineEndBAP),
1133: ai.ipdArea.max - ai.ipdArea.opt,
1134: ai.ipdArea.opt - ai.ipdArea.min,
1135: new LeafPosition(this , -1), false));
1136: spaceElements.add(new KnuthInlineBox(0, null,
1137: notifyPos(new LeafPosition(this , -1)),
1138: false));
1139: spaceElements.add(new KnuthPenalty(0,
1140: KnuthElement.INFINITE, false,
1141: new LeafPosition(this , -1), false));
1142: spaceElements
1143: .add(new KnuthGlue(lineStartBAP
1144: + ai.ipdArea.opt, 0, 0,
1145: mainPosition, false));
1146: break;
1147:
1148: default:
1149: // last line justified, the other lines unjustified:
1150: // use only the space stretch
1151: spaceElements.add(new KnuthGlue(lineEndBAP, 0, 0,
1152: new LeafPosition(this , -1), false));
1153: spaceElements.add(new KnuthPenalty(0, 0, false,
1154: new LeafPosition(this , -1), false));
1155: spaceElements.add(new KnuthGlue(
1156: -(lineStartBAP + lineEndBAP),
1157: ai.ipdArea.max - ai.ipdArea.opt, 0,
1158: new LeafPosition(this , -1), false));
1159: spaceElements.add(new KnuthInlineBox(0, null,
1160: notifyPos(new LeafPosition(this , -1)),
1161: false));
1162: spaceElements.add(new KnuthPenalty(0,
1163: KnuthElement.INFINITE, false,
1164: new LeafPosition(this , -1), false));
1165: spaceElements
1166: .add(new KnuthGlue(lineStartBAP
1167: + ai.ipdArea.opt, 0, 0,
1168: mainPosition, false));
1169: }
1170: } else {
1171: // a (possible block) of breaking spaces
1172: switch (alignment) {
1173: case EN_CENTER:
1174: // centered text:
1175: // if the second element is chosen as a line break these elements
1176: // add a constant amount of stretch at the end of a line and at the
1177: // beginning of the next one, otherwise they don't add any stretch
1178: spaceElements.add(new KnuthGlue(lineEndBAP,
1179: 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH,
1180: 0, new LeafPosition(this , -1), false));
1181: spaceElements.add(new KnuthPenalty(0, 0, false,
1182: new LeafPosition(this , -1), false));
1183: spaceElements.add(new KnuthGlue(ai.ipdArea.opt
1184: - (lineStartBAP + lineEndBAP), -6
1185: * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
1186: mainPosition, false));
1187: spaceElements.add(new KnuthInlineBox(0, null,
1188: notifyPos(new LeafPosition(this , -1)),
1189: false));
1190: spaceElements.add(new KnuthPenalty(0,
1191: KnuthElement.INFINITE, false,
1192: new LeafPosition(this , -1), false));
1193: spaceElements.add(new KnuthGlue(lineStartBAP,
1194: 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH,
1195: 0, new LeafPosition(this , -1), false));
1196: break;
1197:
1198: case EN_START: // fall through
1199: case EN_END:
1200: // left- or right-aligned text:
1201: // if the second element is chosen as a line break these elements
1202: // add a constant amount of stretch at the end of a line, otherwise
1203: // they don't add any stretch
1204: if (lineStartBAP != 0 || lineEndBAP != 0) {
1205: spaceElements
1206: .add(new KnuthGlue(
1207: lineEndBAP,
1208: 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH,
1209: 0, new LeafPosition(this , -1),
1210: false));
1211: spaceElements.add(new KnuthPenalty(0, 0, false,
1212: new LeafPosition(this , -1), false));
1213: spaceElements
1214: .add(new KnuthGlue(
1215: ai.ipdArea.opt
1216: - (lineStartBAP + lineEndBAP),
1217: -3
1218: * LineLayoutManager.DEFAULT_SPACE_WIDTH,
1219: 0, mainPosition, false));
1220: spaceElements.add(new KnuthInlineBox(0, null,
1221: notifyPos(new LeafPosition(this , -1)),
1222: false));
1223: spaceElements.add(new KnuthPenalty(0,
1224: KnuthElement.INFINITE, false,
1225: new LeafPosition(this , -1), false));
1226: spaceElements
1227: .add(new KnuthGlue(lineStartBAP, 0, 0,
1228: new LeafPosition(this , -1),
1229: false));
1230: } else {
1231: spaceElements
1232: .add(new KnuthGlue(
1233: 0,
1234: 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH,
1235: 0, new LeafPosition(this , -1),
1236: false));
1237: spaceElements.add(new KnuthPenalty(0, 0, false,
1238: new LeafPosition(this , -1), false));
1239: spaceElements
1240: .add(new KnuthGlue(
1241: ai.ipdArea.opt,
1242: -3
1243: * LineLayoutManager.DEFAULT_SPACE_WIDTH,
1244: 0, mainPosition, false));
1245: }
1246: break;
1247:
1248: case EN_JUSTIFY:
1249: // justified text:
1250: // the stretch and shrink depends on the space width
1251: if (lineStartBAP != 0 || lineEndBAP != 0) {
1252: spaceElements.add(new KnuthGlue(lineEndBAP, 0,
1253: 0, new LeafPosition(this , -1), false));
1254: spaceElements.add(new KnuthPenalty(0, 0, false,
1255: new LeafPosition(this , -1), false));
1256: spaceElements.add(new KnuthGlue(ai.ipdArea.opt
1257: - (lineStartBAP + lineEndBAP),
1258: ai.ipdArea.max - ai.ipdArea.opt,
1259: ai.ipdArea.opt - ai.ipdArea.min,
1260: mainPosition, false));
1261: spaceElements.add(new KnuthInlineBox(0, null,
1262: notifyPos(new LeafPosition(this , -1)),
1263: false));
1264: spaceElements.add(new KnuthPenalty(0,
1265: KnuthElement.INFINITE, false,
1266: new LeafPosition(this , -1), false));
1267: spaceElements
1268: .add(new KnuthGlue(lineStartBAP, 0, 0,
1269: new LeafPosition(this , -1),
1270: false));
1271: } else {
1272: spaceElements.add(new KnuthGlue(ai.ipdArea.opt,
1273: ai.ipdArea.max - ai.ipdArea.opt,
1274: ai.ipdArea.opt - ai.ipdArea.min,
1275: mainPosition, false));
1276: }
1277: break;
1278:
1279: default:
1280: // last line justified, the other lines unjustified:
1281: // use only the space stretch
1282: if (lineStartBAP != 0 || lineEndBAP != 0) {
1283: spaceElements.add(new KnuthGlue(lineEndBAP, 0,
1284: 0, new LeafPosition(this , -1), false));
1285: spaceElements.add(new KnuthPenalty(0, 0, false,
1286: new LeafPosition(this , -1), false));
1287: spaceElements.add(new KnuthGlue(ai.ipdArea.opt
1288: - (lineStartBAP + lineEndBAP),
1289: ai.ipdArea.max - ai.ipdArea.opt, 0,
1290: mainPosition, false));
1291: spaceElements.add(new KnuthInlineBox(0, null,
1292: notifyPos(new LeafPosition(this , -1)),
1293: false));
1294: spaceElements.add(new KnuthPenalty(0,
1295: KnuthElement.INFINITE, false,
1296: new LeafPosition(this , -1), false));
1297: spaceElements
1298: .add(new KnuthGlue(lineStartBAP, 0, 0,
1299: new LeafPosition(this , -1),
1300: false));
1301: } else {
1302: spaceElements.add(new KnuthGlue(ai.ipdArea.opt,
1303: ai.ipdArea.max - ai.ipdArea.opt, 0,
1304: mainPosition, false));
1305: }
1306: }
1307: }
1308: }
1309:
1310: return spaceElements;
1311: }
1312:
1313: private LinkedList createElementsForAWordFragment(int alignment,
1314: AreaInfo ai, int leafValue, MinOptMax letterSpaceWidth) {
1315: LinkedList wordElements = new LinkedList();
1316: LeafPosition mainPosition = new LeafPosition(this , leafValue);
1317:
1318: // if the last character of the word fragment is '-' or '/',
1319: // the fragment could end a line; in this case, it loses one
1320: // of its letter spaces;
1321: boolean bSuppressibleLetterSpace = ai.breakOppAfter
1322: && !ai.bHyphenated;
1323:
1324: if (letterSpaceWidth.min == letterSpaceWidth.max) {
1325: // constant letter spacing
1326: wordElements.add(new KnuthInlineBox(
1327: bSuppressibleLetterSpace ? ai.ipdArea.opt
1328: - letterSpaceWidth.opt : ai.ipdArea.opt,
1329: alignmentContext, notifyPos(mainPosition), false));
1330: } else {
1331: // adjustable letter spacing
1332: int unsuppressibleLetterSpaces = bSuppressibleLetterSpace ? ai.iLScount - 1
1333: : ai.iLScount;
1334: wordElements.add(new KnuthInlineBox(ai.ipdArea.opt
1335: - ai.iLScount * letterSpaceWidth.opt,
1336: alignmentContext, notifyPos(mainPosition), false));
1337: wordElements.add(new KnuthPenalty(0, KnuthElement.INFINITE,
1338: false, new LeafPosition(this , -1), true));
1339: wordElements
1340: .add(new KnuthGlue(
1341: unsuppressibleLetterSpaces
1342: * letterSpaceWidth.opt,
1343: unsuppressibleLetterSpaces
1344: * (letterSpaceWidth.max - letterSpaceWidth.opt),
1345: unsuppressibleLetterSpaces
1346: * (letterSpaceWidth.opt - letterSpaceWidth.min),
1347: new LeafPosition(this , -1), true));
1348: wordElements.add(new KnuthInlineBox(0, null,
1349: notifyPos(new LeafPosition(this , -1)), true));
1350: }
1351:
1352: // extra-elements if the word fragment is the end of a syllable,
1353: // or it ends with a character that can be used as a line break
1354: if (ai.bHyphenated) {
1355: MinOptMax widthIfNoBreakOccurs = null;
1356: if (ai.iBreakIndex < textArray.length) {
1357: //Add in kerning in no-break condition
1358: widthIfNoBreakOccurs = letterAdjustArray[ai.iBreakIndex];
1359: }
1360: //if (ai.iBreakIndex)
1361:
1362: // the word fragment ends at the end of a syllable:
1363: // if a break occurs the content width increases,
1364: // otherwise nothing happens
1365: wordElements.addAll(createElementsForAHyphen(alignment,
1366: hyphIPD, widthIfNoBreakOccurs, ai.breakOppAfter
1367: && ai.bHyphenated));
1368: } else if (bSuppressibleLetterSpace) {
1369: // the word fragment ends with a character that acts as a hyphen
1370: // if a break occurs the width does not increase,
1371: // otherwise there is one more letter space
1372: wordElements.addAll(createElementsForAHyphen(alignment, 0,
1373: letterSpaceWidth, true));
1374: }
1375: return wordElements;
1376: }
1377:
1378: // static final int SOFT_HYPHEN_PENALTY = KnuthPenalty.FLAGGED_PENALTY / 10;
1379: static final int SOFT_HYPHEN_PENALTY = 1;
1380:
1381: private LinkedList createElementsForAHyphen(int alignment,
1382: int widthIfBreakOccurs, MinOptMax widthIfNoBreakOccurs,
1383: boolean unflagged) {
1384: if (widthIfNoBreakOccurs == null) {
1385: widthIfNoBreakOccurs = ZERO_MINOPTMAX;
1386: }
1387: LinkedList hyphenElements = new LinkedList();
1388:
1389: switch (alignment) {
1390: case EN_CENTER:
1391: // centered text:
1392: /*
1393: hyphenElements.add
1394: (new KnuthGlue(0, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
1395: new LeafPosition(this, -1), false));
1396: hyphenElements.add
1397: (new KnuthPenalty(hyphIPD,
1398: KnuthPenalty.FLAGGED_PENALTY, true,
1399: new LeafPosition(this, -1), false));
1400: hyphenElements.add
1401: (new KnuthGlue(0,
1402: - 6 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
1403: new LeafPosition(this, -1), false));
1404: hyphenElements.add
1405: (new KnuthInlineBox(0, 0, 0, 0,
1406: new LeafPosition(this, -1), false));
1407: hyphenElements.add
1408: (new KnuthPenalty(0, KnuthElement.INFINITE, true,
1409: new LeafPosition(this, -1), false));
1410: hyphenElements.add
1411: (new KnuthGlue(0, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
1412: new LeafPosition(this, -1), false));
1413: */
1414: hyphenElements.add(new KnuthPenalty(0,
1415: KnuthElement.INFINITE, false, new LeafPosition(
1416: this , -1), true));
1417: hyphenElements.add(new KnuthGlue(lineEndBAP,
1418: 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
1419: new LeafPosition(this , -1), true));
1420: hyphenElements.add(new KnuthPenalty(hyphIPD,
1421: unflagged ? SOFT_HYPHEN_PENALTY
1422: : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
1423: new LeafPosition(this , -1), false));
1424: hyphenElements.add(new KnuthGlue(
1425: -(lineEndBAP + lineStartBAP), -6
1426: * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
1427: new LeafPosition(this , -1), false));
1428: hyphenElements.add(new KnuthInlineBox(0, null,
1429: notifyPos(new LeafPosition(this , -1)), true));
1430: hyphenElements.add(new KnuthPenalty(0,
1431: KnuthElement.INFINITE, false, new LeafPosition(
1432: this , -1), true));
1433: hyphenElements.add(new KnuthGlue(lineStartBAP,
1434: 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
1435: new LeafPosition(this , -1), true));
1436: break;
1437:
1438: case EN_START: // fall through
1439: case EN_END:
1440: // left- or right-aligned text:
1441: /*
1442: hyphenElements.add
1443: (new KnuthGlue(0, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
1444: new LeafPosition(this, -1), false));
1445: hyphenElements.add
1446: (new KnuthPenalty(widthIfBreakOccurs,
1447: KnuthPenalty.FLAGGED_PENALTY, true,
1448: new LeafPosition(this, -1), false));
1449: hyphenElements.add
1450: (new KnuthGlue(widthIfNoBreakOccurs.opt,
1451: - 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
1452: new LeafPosition(this, -1), false));
1453: */
1454: if (lineStartBAP != 0 || lineEndBAP != 0) {
1455: hyphenElements.add(new KnuthPenalty(0,
1456: KnuthElement.INFINITE, false, new LeafPosition(
1457: this , -1), true));
1458: hyphenElements.add(new KnuthGlue(lineEndBAP,
1459: 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
1460: new LeafPosition(this , -1), false));
1461: hyphenElements.add(new KnuthPenalty(widthIfBreakOccurs,
1462: unflagged ? SOFT_HYPHEN_PENALTY
1463: : KnuthPenalty.FLAGGED_PENALTY,
1464: !unflagged, new LeafPosition(this , -1), false));
1465: hyphenElements.add(new KnuthGlue(
1466: widthIfNoBreakOccurs.opt
1467: - (lineStartBAP + lineEndBAP),
1468: -3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
1469: new LeafPosition(this , -1), false));
1470: hyphenElements.add(new KnuthInlineBox(0, null,
1471: notifyPos(new LeafPosition(this , -1)), false));
1472: hyphenElements.add(new KnuthPenalty(0,
1473: KnuthElement.INFINITE, false, new LeafPosition(
1474: this , -1), false));
1475: hyphenElements.add(new KnuthGlue(lineStartBAP, 0, 0,
1476: new LeafPosition(this , -1), false));
1477: } else {
1478: hyphenElements.add(new KnuthPenalty(0,
1479: KnuthElement.INFINITE, false, new LeafPosition(
1480: this , -1), true));
1481: hyphenElements.add(new KnuthGlue(0,
1482: 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
1483: new LeafPosition(this , -1), false));
1484: hyphenElements.add(new KnuthPenalty(widthIfBreakOccurs,
1485: unflagged ? SOFT_HYPHEN_PENALTY
1486: : KnuthPenalty.FLAGGED_PENALTY,
1487: !unflagged, new LeafPosition(this , -1), false));
1488: hyphenElements.add(new KnuthGlue(
1489: widthIfNoBreakOccurs.opt,
1490: -3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
1491: new LeafPosition(this , -1), false));
1492: }
1493: break;
1494:
1495: default:
1496: // justified text, or last line justified:
1497: // just a flagged penalty
1498: /*
1499: hyphenElements.add
1500: (new KnuthPenalty(widthIfBreakOccurs,
1501: KnuthPenalty.FLAGGED_PENALTY, true,
1502: new LeafPosition(this, -1), false));
1503: */
1504: if (lineStartBAP != 0 || lineEndBAP != 0) {
1505: hyphenElements.add(new KnuthPenalty(0,
1506: KnuthElement.INFINITE, false, new LeafPosition(
1507: this , -1), true));
1508:
1509: hyphenElements.add(new KnuthGlue(lineEndBAP, 0, 0,
1510: new LeafPosition(this , -1), false));
1511: hyphenElements.add(new KnuthPenalty(widthIfBreakOccurs,
1512: unflagged ? SOFT_HYPHEN_PENALTY
1513: : KnuthPenalty.FLAGGED_PENALTY,
1514: !unflagged, new LeafPosition(this , -1), false));
1515: // extra elements representing a letter space that is suppressed
1516: // if a break occurs
1517: if (widthIfNoBreakOccurs.min != 0
1518: || widthIfNoBreakOccurs.max != 0) {
1519: hyphenElements.add(new KnuthGlue(
1520: widthIfNoBreakOccurs.opt
1521: - (lineStartBAP + lineEndBAP),
1522: widthIfNoBreakOccurs.max
1523: - widthIfNoBreakOccurs.opt,
1524: widthIfNoBreakOccurs.opt
1525: - widthIfNoBreakOccurs.min,
1526: new LeafPosition(this , -1), false));
1527: } else {
1528: hyphenElements.add(new KnuthGlue(
1529: -(lineStartBAP + lineEndBAP), 0, 0,
1530: new LeafPosition(this , -1), false));
1531: }
1532: hyphenElements.add(new KnuthInlineBox(0, null,
1533: notifyPos(new LeafPosition(this , -1)), false));
1534: hyphenElements.add(new KnuthPenalty(0,
1535: KnuthElement.INFINITE, false, new LeafPosition(
1536: this , -1), false));
1537: hyphenElements.add(new KnuthGlue(lineStartBAP, 0, 0,
1538: new LeafPosition(this , -1), false));
1539: } else {
1540: hyphenElements.add(new KnuthPenalty(widthIfBreakOccurs,
1541: unflagged ? SOFT_HYPHEN_PENALTY
1542: : KnuthPenalty.FLAGGED_PENALTY,
1543: !unflagged, new LeafPosition(this , -1), false));
1544: // extra elements representing a letter space that is suppressed
1545: // if a break occurs
1546: if (widthIfNoBreakOccurs.min != 0
1547: || widthIfNoBreakOccurs.max != 0) {
1548: hyphenElements.add(new KnuthGlue(
1549: widthIfNoBreakOccurs.opt,
1550: widthIfNoBreakOccurs.max
1551: - widthIfNoBreakOccurs.opt,
1552: widthIfNoBreakOccurs.opt
1553: - widthIfNoBreakOccurs.min,
1554: new LeafPosition(this , -1), false));
1555: }
1556: }
1557: }
1558:
1559: return hyphenElements;
1560: }
1561:
1562: }
|