123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314 |
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
- /* $Id$ */
-
- package org.apache.fop.layoutmgr.inline;
-
- import java.util.LinkedList;
- import java.util.List;
- import java.util.ListIterator;
-
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
-
- import org.apache.fop.area.Trait;
- import org.apache.fop.area.inline.TextArea;
- import org.apache.fop.fo.Constants;
- import org.apache.fop.fo.FOText;
- import org.apache.fop.fo.FObj;
- import org.apache.fop.fo.properties.StructurePointerPropertySet;
- import org.apache.fop.fonts.Font;
- import org.apache.fop.fonts.FontSelector;
- import org.apache.fop.layoutmgr.InlineKnuthSequence;
- import org.apache.fop.layoutmgr.KnuthBox;
- import org.apache.fop.layoutmgr.KnuthElement;
- import org.apache.fop.layoutmgr.KnuthGlue;
- import org.apache.fop.layoutmgr.KnuthPenalty;
- import org.apache.fop.layoutmgr.KnuthSequence;
- import org.apache.fop.layoutmgr.LayoutContext;
- import org.apache.fop.layoutmgr.LeafPosition;
- import org.apache.fop.layoutmgr.Position;
- import org.apache.fop.layoutmgr.PositionIterator;
- import org.apache.fop.layoutmgr.TraitSetter;
- import org.apache.fop.text.linebreak.LineBreakStatus;
- import org.apache.fop.traits.MinOptMax;
- import org.apache.fop.traits.SpaceVal;
- import org.apache.fop.util.CharUtilities;
- import org.apache.fop.util.ListUtil;
-
- /**
- * LayoutManager for text (a sequence of characters) which generates one
- * or more inline areas.
- */
- public class TextLayoutManager extends LeafNodeLayoutManager {
-
- //TODO: remove all final modifiers at local variables
-
- /**
- * Store information about each potential text area.
- * Index of character which ends the area, IPD of area, including
- * any word-space and letter-space.
- * Number of word-spaces?
- */
- private class AreaInfo {
- private final int startIndex;
- private final int breakIndex;
- private final int wordSpaceCount;
- private int letterSpaceCount;
- private final MinOptMax areaIPD;
- private final boolean isHyphenated;
- private final boolean isSpace;
- private boolean breakOppAfter;
- private final Font font;
-
- AreaInfo(final int startIndex,
- final int breakIndex,
- final int wordSpaceCount,
- final int letterSpaceCount,
- final MinOptMax areaIPD,
- final boolean isHyphenated,
- final boolean isSpace,
- final boolean breakOppAfter,
- final Font font) {
- this.startIndex = startIndex;
- this.breakIndex = breakIndex;
- this.wordSpaceCount = wordSpaceCount;
- this.letterSpaceCount = letterSpaceCount;
- this.areaIPD = areaIPD;
- this.isHyphenated = isHyphenated;
- this.isSpace = isSpace;
- this.breakOppAfter = breakOppAfter;
- this.font = font;
- }
-
- public String toString() {
- return "[ lscnt=" + this.letterSpaceCount
- + ", wscnt=" + this.wordSpaceCount
- + ", ipd=" + this.areaIPD.toString()
- + ", sidx=" + this.startIndex
- + ", bidx=" + this.breakIndex
- + ", hyph=" + this.isHyphenated
- + ", space=" + this.isSpace
- + ", font=" + this.font
- + "]";
- }
-
- }
-
- // this class stores information about changes in vecAreaInfo
- // which are not yet applied
- private final class PendingChange {
- private final AreaInfo ai;
- private final int index;
-
- private PendingChange(final AreaInfo ai, final int index) {
- this.ai = ai;
- this.index = index;
- }
- }
-
- /**
- * logging instance
- */
- private static final Log LOG = LogFactory.getLog(TextLayoutManager.class);
-
- // Hold all possible breaks for the text in this LM's FO.
- private final List vecAreaInfo;
-
- /** Non-space characters on which we can end a line. */
- private static final String BREAK_CHARS = "-/";
-
- /** Used to reduce instantiation of MinOptMax with zero length. Do not modify! */
- private static final MinOptMax ZERO_MINOPTMAX = new MinOptMax(0);
-
- private final FOText foText;
-
- /**
- * Contains an array of widths to adjust for kerning. The first entry can
- * be used to influence the start position of the first letter. The entry i+1 defines the
- * cursor advancement after the character i. A null entry means no special advancement.
- */
- private final MinOptMax[] letterAdjustArray; //size = textArray.length + 1
-
- /** Font used for the space between words. */
- private Font spaceFont = null;
- /** Start index of next TextArea */
- private int nextStart = 0;
- /** size of a space character (U+0020) glyph in current font */
- private int spaceCharIPD;
- private MinOptMax wordSpaceIPD;
- private MinOptMax letterSpaceIPD;
- /** size of the hyphen character glyph in current font */
- private int hyphIPD;
- /** 1/1 of word-spacing value */
- private SpaceVal ws;
-
- private boolean hasChanged = false;
- private int returnedIndex = 0;
- private int thisStart = 0;
- private int tempStart = 0;
- private List changeList = null;
-
- private AlignmentContext alignmentContext = null;
-
- private int lineStartBAP = 0;
- private int lineEndBAP = 0;
-
- private boolean keepTogether;
-
- private final Position auxiliaryPosition = new LeafPosition(this, -1);
-
- /**
- * Create a Text layout manager.
- *
- * @param node The FOText object to be rendered
- */
- public TextLayoutManager(final FOText node) {
- super();
- this.foText = node;
-
- this.letterAdjustArray = new MinOptMax[node.length() + 1];
-
- this.vecAreaInfo = new java.util.ArrayList();
- }
-
- private KnuthPenalty makeZeroWidthPenalty(final int penaltyValue) {
- return new KnuthPenalty(
- 0,
- penaltyValue,
- false,
- this.auxiliaryPosition,
- true);
- }
-
- private KnuthBox makeAuxiliaryZeroWidthBox() {
- return new KnuthInlineBox(
- 0,
- null,
- this.notifyPos(new LeafPosition(this, -1)),
- true);
- }
-
- /** {@inheritDoc} */
- public void initialize() {
-
- this.foText.resetBuffer();
-
- this.spaceFont = FontSelector.selectFontForCharacterInText(' ', this.foText, this);
-
- // With CID fonts, space isn't neccesary currentFontState.width(32)
- this.spaceCharIPD = this.spaceFont.getCharWidth(' ');
- // Use hyphenationChar property
-
- // TODO: Use hyphen based on actual font used!
- this.hyphIPD = this.foText.getCommonHyphenation().getHyphIPD(this.spaceFont);
-
- final SpaceVal ls = SpaceVal.makeLetterSpacing(this.foText.getLetterSpacing());
-
- this.ws = SpaceVal.makeWordSpacing(this.foText.getWordSpacing(), ls, this.spaceFont);
-
- // letter space applies only to consecutive non-space characters,
- // while word space applies to space characters;
- // i.e. the spaces in the string "A SIMPLE TEST" are:
- // A<<ws>>S<ls>I<ls>M<ls>P<ls>L<ls>E<<ws>>T<ls>E<ls>S<ls>T
- // there is no letter space after the last character of a word,
- // nor after a space character
- // NOTE: The above is not quite correct. Read on in XSL 1.0, 7.16.2, letter-spacing
-
- // set letter space and word space dimension;
- // the default value "normal" was converted into a MinOptMax value
- // in the SpaceVal.makeWordSpacing() method
- this.letterSpaceIPD = ls.getSpace();
- this.wordSpaceIPD = MinOptMax.add(new MinOptMax(this.spaceCharIPD), this.ws.getSpace());
-
- this.keepTogether = this.foText.getKeepTogether().getWithinLine()
- .getEnum() == Constants.EN_ALWAYS;
-
- }
-
- /**
- * Generate and add areas to parent area.
- * This can either generate an area for each TextArea and each space, or
- * an area containing all text with a parameter controlling the size of
- * the word space. The latter is most efficient for PDF generation.
- * Set size of each area.
- * @param posIter Iterator over Position information returned
- * by this LayoutManager.
- * @param context LayoutContext for adjustments
- */
- public void addAreas(final PositionIterator posIter, final LayoutContext context) {
-
- // Add word areas
- AreaInfo ai;
- int wordSpaceCount = 0;
- int letterSpaceCount = 0;
- int firstAreaInfoIndex = -1;
- int lastAreaInfoIndex = 0;
- MinOptMax realWidth = new MinOptMax(0);
-
- /* On first area created, add any leading space.
- * Calculate word-space stretch value.
- */
- AreaInfo lastAi = null;
- while (posIter.hasNext()) {
- final LeafPosition tbpNext = (LeafPosition) posIter.next();
- if (tbpNext == null) {
- continue; //Ignore elements without Positions
- }
- if (tbpNext.getLeafPos() != -1) {
- ai = (AreaInfo) this.vecAreaInfo.get(tbpNext.getLeafPos());
- if (lastAi == null || ai.font != lastAi.font) {
- if (lastAi != null) {
- this.addAreaInfoAreas(lastAi, wordSpaceCount,
- letterSpaceCount, firstAreaInfoIndex,
- lastAreaInfoIndex, realWidth, context);
- }
- firstAreaInfoIndex = tbpNext.getLeafPos();
- wordSpaceCount = 0;
- letterSpaceCount = 0;
- realWidth = new MinOptMax(0);
- }
- wordSpaceCount += ai.wordSpaceCount;
- letterSpaceCount += ai.letterSpaceCount;
- realWidth.add(ai.areaIPD);
- lastAreaInfoIndex = tbpNext.getLeafPos();
- lastAi = ai;
- }
- }
- if (lastAi != null) {
- this.addAreaInfoAreas(lastAi, wordSpaceCount, letterSpaceCount,
- firstAreaInfoIndex, lastAreaInfoIndex, realWidth, context);
- }
- }
-
- private void addAreaInfoAreas(final AreaInfo ai, final int wordSpaceCount,
- int letterSpaceCount, final int firstAreaInfoIndex,
- final int lastAreaInfoIndex, final MinOptMax realWidth, final LayoutContext context) {
-
- // TODO: These two statements (if, for) were like this before my recent
- // changes. However, it seems as if they should use the AreaInfo from
- // firstAreaInfoIndex.. lastAreaInfoIndex rather than just the last ai.
- // This needs to be checked.
- final int textLength = ai.breakIndex - ai.startIndex;
- if (ai.letterSpaceCount == textLength && !ai.isHyphenated
- && context.isLastArea()) {
- // the line ends at a character like "/" or "-";
- // remove the letter space after the last character
- realWidth.add(MinOptMax.multiply(this.letterSpaceIPD, -1));
- letterSpaceCount--;
- }
-
- for (int i = ai.startIndex; i < ai.breakIndex; i++) {
- final MinOptMax ladj = this.letterAdjustArray[i + 1];
- if (ladj != null && ladj.isElastic()) {
- letterSpaceCount++;
- }
- }
-
- // add hyphenation character if the last word is hyphenated
- if (context.isLastArea() && ai.isHyphenated) {
- realWidth.add(new MinOptMax(this.hyphIPD));
- }
-
- // Calculate adjustments
- int difference = 0;
- int totalAdjust = 0;
- int wordSpaceDim = this.wordSpaceIPD.opt;
- int letterSpaceDim = this.letterSpaceIPD.opt;
- final double ipdAdjust = context.getIPDAdjust();
-
- // calculate total difference between real and available width
- if (ipdAdjust > 0.0) {
- difference = (int) ((realWidth.max - realWidth.opt)
- * ipdAdjust);
- } else {
- difference = (int) ((realWidth.opt - realWidth.min)
- * ipdAdjust);
- }
-
- // set letter space adjustment
- if (ipdAdjust > 0.0) {
- letterSpaceDim
- += (int) ((this.letterSpaceIPD.max - this.letterSpaceIPD.opt)
- * ipdAdjust);
- } else {
- letterSpaceDim
- += (int) ((this.letterSpaceIPD.opt - this.letterSpaceIPD.min)
- * ipdAdjust);
- }
- totalAdjust += (letterSpaceDim - this.letterSpaceIPD.opt) * letterSpaceCount;
-
- // set word space adjustment
- //
- if (wordSpaceCount > 0) {
- wordSpaceDim += (difference - totalAdjust) / wordSpaceCount;
- }
- totalAdjust += (wordSpaceDim - this.wordSpaceIPD.opt) * wordSpaceCount;
- if (totalAdjust != difference) {
- // the applied adjustment is greater or smaller than the needed one
- TextLayoutManager.LOG
- .trace("TextLM.addAreas: error in word / letter space adjustment = "
- + (totalAdjust - difference));
- // set totalAdjust = difference, so that the width of the TextArea
- // will counterbalance the error and the other inline areas will be
- // placed correctly
- totalAdjust = difference;
- }
-
- final TextArea t = this.createTextArea(realWidth, totalAdjust, context,
- this.wordSpaceIPD.opt - this.spaceCharIPD, firstAreaInfoIndex,
- lastAreaInfoIndex, context.isLastArea(), ai.font);
-
- // wordSpaceDim is computed in relation to wordSpaceIPD.opt
- // but the renderer needs to know the adjustment in relation
- // to the size of the space character in the current font;
- // moreover, the pdf renderer adds the character spacing even to
- // the last character of a word and to space characters: in order
- // to avoid this, we must subtract the letter space width twice;
- // the renderer will compute the space width as:
- // space width =
- // = "normal" space width + letterSpaceAdjust + wordSpaceAdjust
- // = spaceCharIPD + letterSpaceAdjust +
- // + (wordSpaceDim - spaceCharIPD - 2 * letterSpaceAdjust)
- // = wordSpaceDim - letterSpaceAdjust
- t.setTextLetterSpaceAdjust(letterSpaceDim);
- t.setTextWordSpaceAdjust(wordSpaceDim - this.spaceCharIPD
- - 2 * t.getTextLetterSpaceAdjust());
- if (context.getIPDAdjust() != 0) {
- // add information about space width
- t.setSpaceDifference(this.wordSpaceIPD.opt - this.spaceCharIPD
- - 2 * t.getTextLetterSpaceAdjust());
- }
- this.parentLM.addChildArea(t);
- }
-
- /**
- * Create an inline word area.
- * This creates a TextArea and sets up the various attributes.
- *
- * @param width the MinOptMax width of the content
- * @param adjust the total ipd adjustment with respect to the optimal width
- * @param context the layout context
- * @param spaceDiff unused
- * @param firstIndex the index of the first AreaInfo used for the TextArea
- * @param lastIndex the index of the last AreaInfo used for the TextArea
- * @param isLastArea is this TextArea the last in a line?
- * @param font Font to be used in this particular TextArea
- * @return the new text area
- */
- protected TextArea createTextArea(final MinOptMax width, final int adjust,
- final LayoutContext context, final int spaceDiff, final int firstIndex,
- final int lastIndex, final boolean isLastArea, final Font font) {
- TextArea textArea;
- if (context.getIPDAdjust() == 0.0) {
- // create just a TextArea
- textArea = new TextArea();
- } else {
- // justified area: create a TextArea with extra info
- // about potential adjustments
- textArea = new TextArea(width.max - width.opt,
- width.opt - width.min,
- adjust);
- }
- textArea.setIPD(width.opt + adjust);
- textArea.setBPD(font.getAscender() - font.getDescender());
-
- textArea.setBaselineOffset(font.getAscender());
- if (textArea.getBPD() == this.alignmentContext.getHeight()) {
- textArea.setOffset(0);
- } else {
- textArea.setOffset(this.alignmentContext.getOffset());
- }
-
- // set the text of the TextArea, split into words and spaces
- int wordStartIndex = -1;
- AreaInfo areaInfo;
- int len = 0;
- for (int i = firstIndex; i <= lastIndex; i++) {
- areaInfo = (AreaInfo) this.vecAreaInfo.get(i);
- if (areaInfo.isSpace) {
- // areaInfo stores information about spaces
- // add the spaces - except zero-width spaces - to the TextArea
- for (int j = areaInfo.startIndex; j < areaInfo.breakIndex; j++) {
- final char spaceChar = this.foText.charAt(j);
- if (!CharUtilities.isZeroWidthSpace(spaceChar)) {
- textArea.addSpace(spaceChar, 0,
- CharUtilities.isAdjustableSpace(spaceChar));
- }
- }
- } else {
- // areaInfo stores information about a word fragment
- if (wordStartIndex == -1) {
- // here starts a new word
- wordStartIndex = i;
- len = 0;
- }
- len += areaInfo.breakIndex - areaInfo.startIndex;
- if (i == lastIndex || ((AreaInfo) this.vecAreaInfo.get(i + 1)).isSpace) {
- // here ends a new word
- // add a word to the TextArea
- if (isLastArea
- && i == lastIndex
- && areaInfo.isHyphenated) {
- len++;
- }
- final StringBuffer wordChars = new StringBuffer(len);
- final int[] letterAdjust = new int[len];
- int letter = 0;
- for (int j = wordStartIndex; j <= i; j++) {
- final AreaInfo ai = (AreaInfo) this.vecAreaInfo.get(j);
- int lsCount = ai.letterSpaceCount;
- /* TODO: in Java 5, StringBuffer has an append() variant
- * for CharSequence, so the below iteration can be replaced
- * by:
- * wordChars.append(this.foText, ai.startIndex,
- * ai.breakIndex - ai.startIndex);
- */
- for (int ci = ai.startIndex; ci < ai.breakIndex; ci++) {
- wordChars.append(this.foText.charAt(ci));
- }
- for (int k = 0; k < ai.breakIndex - ai.startIndex; k++) {
- final MinOptMax adj = this.letterAdjustArray[ai.startIndex + k];
- if (letter > 0) {
- letterAdjust[letter] = adj == null ? 0
- : adj.opt;
- }
- if (lsCount > 0) {
- letterAdjust[letter] += textArea.getTextLetterSpaceAdjust();
- lsCount--;
- }
- letter++;
- }
- }
- // String wordChars = new String(textArray, wordStartIndex, len);
- if (isLastArea
- && i == lastIndex
- && areaInfo.isHyphenated) {
- // add the hyphenation character
- wordChars.append(this.foText.getCommonHyphenation().getHyphChar(font));
- }
- textArea.addWord(wordChars.toString(), 0, letterAdjust);
- wordStartIndex = -1;
- }
- }
- }
- TraitSetter.addFontTraits(textArea, font);
- textArea.addTrait(Trait.COLOR, this.foText.getColor());
- TraitSetter.addPtr(textArea, getPtr()); // used for accessibility
- TraitSetter.addTextDecoration(textArea, this.foText.getTextDecoration());
-
- return textArea;
- }
-
- /**
- * used for accessibility
- * @return ptr of fobj
- */
- private String getPtr() {
- FObj fobj = this.parentLM.getFObj();
- if (fobj instanceof StructurePointerPropertySet) {
- return (((StructurePointerPropertySet) fobj).getPtr());
- } else {
- //No structure pointer applicable
- return null;
- }
- }
-
- private void addToLetterAdjust(final int index, final int width) {
- if (this.letterAdjustArray[index] == null) {
- this.letterAdjustArray[index] = new MinOptMax(width);
- } else {
- this.letterAdjustArray[index].add(width);
- }
- }
-
- /**
- * Indicates whether a character is a space in terms of this layout manager.
- * @param ch the character
- * @return true if it's a space
- */
- private static boolean isSpace(final char ch) {
- return ch == CharUtilities.SPACE
- || CharUtilities.isNonBreakableSpace(ch)
- || CharUtilities.isFixedWidthSpace(ch);
- }
-
- /** {@inheritDoc} */
- public List getNextKnuthElements(final LayoutContext context, final int alignment) {
- this.lineStartBAP = context.getLineStartBorderAndPaddingWidth();
- this.lineEndBAP = context.getLineEndBorderAndPaddingWidth();
- this.alignmentContext = context.getAlignmentContext();
-
- final List returnList = new LinkedList();
- KnuthSequence sequence = new InlineKnuthSequence();
- AreaInfo ai = null;
- AreaInfo prevAi = null;
- returnList.add(sequence);
-
- final LineBreakStatus lbs = new LineBreakStatus();
- this.thisStart = this.nextStart;
- boolean inWord = false;
- boolean inWhitespace = false;
- char ch = 0;
- while (this.nextStart < this.foText.length()) {
- ch = this.foText.charAt(this.nextStart);
- boolean breakOpportunity = false;
- final byte breakAction = this.keepTogether ? LineBreakStatus.PROHIBITED_BREAK
- : lbs.nextChar(ch);
- switch (breakAction) {
- case LineBreakStatus.COMBINING_PROHIBITED_BREAK:
- case LineBreakStatus.PROHIBITED_BREAK:
- break;
- case LineBreakStatus.EXPLICIT_BREAK:
- break;
- case LineBreakStatus.COMBINING_INDIRECT_BREAK:
- case LineBreakStatus.DIRECT_BREAK:
- case LineBreakStatus.INDIRECT_BREAK:
- breakOpportunity = true;
- break;
- default:
- TextLayoutManager.LOG.error("Unexpected breakAction: " + breakAction);
- }
- if (inWord) {
- if (breakOpportunity
- || TextLayoutManager.isSpace(ch)
- || CharUtilities.isExplicitBreak(ch)) {
- // this.foText.charAt(lastIndex) == CharUtilities.SOFT_HYPHEN
- prevAi = this.processWord(alignment, sequence, prevAi, ch,
- breakOpportunity, true);
- }
- } else if (inWhitespace) {
- if (ch != CharUtilities.SPACE || breakOpportunity) {
- prevAi = this.processWhitespace(alignment, sequence,
- breakOpportunity);
- }
- } else {
- if (ai != null) {
- prevAi = ai;
- ai = this.processLeftoverAi(alignment, sequence, ai, ch,
- ch == CharUtilities.SPACE || breakOpportunity);
- }
- if (breakAction == LineBreakStatus.EXPLICIT_BREAK) {
- sequence = this.processLinebreak(returnList, sequence);
- }
- }
-
- if (ch == CharUtilities.SPACE
- && this.foText.getWhitespaceTreatment() == Constants.EN_PRESERVE
- || ch == CharUtilities.NBSPACE) {
- // preserved space or non-breaking space:
- // create the AreaInfo object
- ai = new AreaInfo(this.nextStart, this.nextStart + 1,
- 1, 0, this.wordSpaceIPD, false, true,
- breakOpportunity, this.spaceFont);
- this.thisStart = this.nextStart + 1;
- } else if (CharUtilities.isFixedWidthSpace(ch) || CharUtilities.isZeroWidthSpace(ch)) {
- // create the AreaInfo object
- final Font font = FontSelector.selectFontForCharacterInText(ch,
- this.foText, this);
- final MinOptMax ipd = new MinOptMax(font.getCharWidth(ch));
- ai = new AreaInfo(this.nextStart, this.nextStart + 1,
- 0, 0, ipd, false, true,
- breakOpportunity, font);
- this.thisStart = this.nextStart + 1;
- } else if (CharUtilities.isExplicitBreak(ch)) {
- //mandatory break-character: only advance index
- this.thisStart = this.nextStart + 1;
- }
-
- inWord = !TextLayoutManager.isSpace(ch)
- && !CharUtilities.isExplicitBreak(ch);
- inWhitespace = ch == CharUtilities.SPACE
- && this.foText.getWhitespaceTreatment() != Constants.EN_PRESERVE;
- this.nextStart++;
- } // end of while
-
- // Process any last elements
- if (inWord) {
- this.processWord(alignment, sequence, prevAi, ch, false, false);
- } else if (inWhitespace) {
- this.processWhitespace(alignment, sequence, true);
- } else if (ai != null) {
- this.processLeftoverAi(alignment, sequence, ai, ch,
- ch == CharUtilities.ZERO_WIDTH_SPACE);
- } else if (CharUtilities.isExplicitBreak(ch)) {
- this.processLinebreak(returnList, sequence);
- }
-
- if (((List) ListUtil.getLast(returnList)).isEmpty()) {
- //Remove an empty sequence because of a trailing newline
- ListUtil.removeLast(returnList);
- }
-
- this.setFinished(true);
- if (returnList.isEmpty()) {
- return null;
- } else {
- return returnList;
- }
- }
-
- private KnuthSequence processLinebreak(final List returnList,
- KnuthSequence sequence) {
- if (this.lineEndBAP != 0) {
- sequence.add(
- new KnuthGlue(this.lineEndBAP, 0, 0,
- this.auxiliaryPosition, true));
- }
- sequence.endSequence();
- sequence = new InlineKnuthSequence();
- returnList.add(sequence);
- return sequence;
- }
-
- private AreaInfo processLeftoverAi(final int alignment,
- final KnuthSequence sequence, AreaInfo ai, final char ch,
- final boolean breakOpportunityAfter) {
- this.vecAreaInfo.add(ai);
- ai.breakOppAfter = breakOpportunityAfter;
- this.addElementsForASpace(sequence, alignment, ai, this.vecAreaInfo.size() - 1);
- ai = null;
- return ai;
- }
-
- private AreaInfo processWhitespace(final int alignment,
- final KnuthSequence sequence, final boolean breakOpportunity) {
- // End of whitespace
- // create the AreaInfo object
- AreaInfo ai = new AreaInfo(this.thisStart, this.nextStart,
- this.nextStart - this.thisStart, 0,
- MinOptMax.multiply(this.wordSpaceIPD, this.nextStart
- - this.thisStart), false, true,
- breakOpportunity, this.spaceFont);
- this.vecAreaInfo.add(ai);
-
- // create the elements
- this.addElementsForASpace(sequence, alignment, ai, this.vecAreaInfo.size() - 1);
-
- this.thisStart = this.nextStart;
- return ai;
- }
-
- private AreaInfo processWord(final int alignment, final KnuthSequence sequence,
- AreaInfo prevAreaInfo, final char ch, final boolean breakOpportunity,
- final boolean checkEndsWithHyphen) {
-
- //Word boundary found, process widths and kerning
- int lastIndex = this.nextStart;
- while (lastIndex > 0
- && foText.charAt(lastIndex - 1) == CharUtilities.SOFT_HYPHEN) {
- lastIndex--;
- }
- final boolean endsWithHyphen = checkEndsWithHyphen
- && foText.charAt(lastIndex) == CharUtilities.SOFT_HYPHEN;
- final Font font = FontSelector
- .selectFontForCharactersInText(foText,
- this.thisStart, lastIndex, foText, this);
- final int wordLength = lastIndex - this.thisStart;
- final boolean kerning = font.hasKerning();
- final MinOptMax wordIPD = new MinOptMax(0);
- for (int i = this.thisStart; i < lastIndex; i++) {
- final char currentChar = foText.charAt(i);
-
- //character width
- final int charWidth = font.getCharWidth(currentChar);
- wordIPD.add(charWidth);
-
- //kerning
- if (kerning) {
- int kern = 0;
- if (i > this.thisStart) {
- final char previousChar = foText.charAt(i - 1);
- kern = font.getKernValue(previousChar, currentChar);
- } else if (prevAreaInfo != null && !prevAreaInfo.isSpace && prevAreaInfo.breakIndex > 0) {
- final char previousChar = foText.charAt(prevAreaInfo.breakIndex - 1);
- kern = font.getKernValue(previousChar, currentChar);
- }
- if (kern != 0) {
- this.addToLetterAdjust(i, kern);
- wordIPD.add(kern);
- }
- }
- }
- if (kerning
- && breakOpportunity
- && !TextLayoutManager.isSpace(ch)
- && lastIndex > 0
- && endsWithHyphen) {
- final int kern = font.getKernValue(foText.charAt(lastIndex - 1), ch);
- if (kern != 0) {
- this.addToLetterAdjust(lastIndex, kern);
- //TODO: add kern to wordIPD?
- }
- }
- int iLetterSpaces = wordLength - 1;
- // if there is a break opportunity and the next one
- // is not a space, it could be used as a line end;
- // add one more letter space, in case other text follows
- if (breakOpportunity && !TextLayoutManager.isSpace(ch)) {
- iLetterSpaces++;
- }
- wordIPD.add(MinOptMax.multiply(this.letterSpaceIPD, iLetterSpaces));
-
- // create the AreaInfo object
- AreaInfo areaInfo = new AreaInfo(this.thisStart, lastIndex, 0,
- iLetterSpaces, wordIPD,
- endsWithHyphen,
- false, breakOpportunity, font);
- prevAreaInfo = areaInfo;
- this.vecAreaInfo.add(areaInfo);
- this.tempStart = this.nextStart;
-
- //add the elements
- this.addElementsForAWordFragment(sequence, alignment, areaInfo,
- this.vecAreaInfo.size() - 1, this.letterSpaceIPD);
- this.thisStart = this.nextStart;
-
- return prevAreaInfo;
- }
-
- /** {@inheritDoc} */
- public List addALetterSpaceTo(final List oldList) {
- // old list contains only a box, or the sequence: box penalty glue box;
- // look at the Position stored in the first element in oldList
- // which is always a box
- ListIterator oldListIterator = oldList.listIterator();
- final KnuthElement el = (KnuthElement)oldListIterator.next();
- final LeafPosition pos = (LeafPosition) ((KnuthBox) el).getPosition();
- final int idx = pos.getLeafPos();
- //element could refer to '-1' position, for non-collapsed spaces (?)
- if (idx > -1) {
- final AreaInfo ai = (AreaInfo) this.vecAreaInfo.get(idx);
- ai.letterSpaceCount++;
- ai.areaIPD.add(this.letterSpaceIPD);
- if (TextLayoutManager.BREAK_CHARS.indexOf(this.foText.charAt(this.tempStart - 1)) >= 0) {
- // the last character could be used as a line break
- // append new elements to oldList
- oldListIterator = oldList.listIterator(oldList.size());
- oldListIterator.add(new KnuthPenalty(0, KnuthPenalty.FLAGGED_PENALTY, true,
- this.auxiliaryPosition, false));
- oldListIterator.add(new KnuthGlue(this.letterSpaceIPD.opt,
- this.letterSpaceIPD.max - this.letterSpaceIPD.opt,
- this.letterSpaceIPD.opt - this.letterSpaceIPD.min,
- this.auxiliaryPosition, false));
- } else if (this.letterSpaceIPD.min == this.letterSpaceIPD.max) {
- // constant letter space: replace the box
- oldListIterator.set(new KnuthInlineBox(ai.areaIPD.opt,
- this.alignmentContext, pos, false));
- } else {
- // adjustable letter space: replace the glue
- oldListIterator.next(); // this would return the penalty element
- oldListIterator.next(); // this would return the glue element
- oldListIterator
- .set(new KnuthGlue(
- ai.letterSpaceCount * this.letterSpaceIPD.opt,
- ai.letterSpaceCount
- * (this.letterSpaceIPD.max - this.letterSpaceIPD.opt),
- ai.letterSpaceCount
- * (this.letterSpaceIPD.opt - this.letterSpaceIPD.min),
- this.auxiliaryPosition, true));
- }
- }
- return oldList;
- }
-
- /**
- * remove the AreaInfo object represented by the given elements,
- * so that it won't generate any element when getChangedKnuthElements
- * will be called
- *
- * @param oldList the elements representing the word space
- */
- public void removeWordSpace(final List oldList) {
- // find the element storing the Position whose value
- // points to the AreaInfo object
- final ListIterator oldListIterator = oldList.listIterator();
- if (((KnuthElement) ((LinkedList) oldList).getFirst()).isPenalty()) {
- // non breaking space: oldList starts with a penalty
- oldListIterator.next();
- }
- if (oldList.size() > 2) {
- // alignment is either center, start or end:
- // the first two elements does not store the needed Position
- oldListIterator.next();
- oldListIterator.next();
- }
- final int leafValue = ((LeafPosition) ((KnuthElement) oldListIterator
- .next()).getPosition()).getLeafPos();
- // only the last word space can be a trailing space!
- if (leafValue == this.vecAreaInfo.size() - 1) {
- this.vecAreaInfo.remove(leafValue);
- } else {
- TextLayoutManager.LOG.error("trying to remove a non-trailing word space");
- }
- }
-
- /** {@inheritDoc} */
- public void hyphenate(final Position pos, final HyphContext hc) {
- final AreaInfo ai
- = (AreaInfo) this.vecAreaInfo.get(((LeafPosition) pos).getLeafPos());
- int startIndex = ai.startIndex;
- int stopIndex;
- boolean nothingChanged = true;
- final Font font = ai.font;
-
- while (startIndex < ai.breakIndex) {
- final MinOptMax newIPD = new MinOptMax(0);
- boolean hyphenFollows;
-
- stopIndex = startIndex + hc.getNextHyphPoint();
- if (hc.hasMoreHyphPoints() && stopIndex <= ai.breakIndex) {
- // stopIndex is the index of the first character
- // after a hyphenation point
- hyphenFollows = true;
- } else {
- // there are no more hyphenation points,
- // or the next one is after ai.breakIndex
- hyphenFollows = false;
- stopIndex = ai.breakIndex;
- }
-
- hc.updateOffset(stopIndex - startIndex);
-
- //log.info("Word: " + new String(textArray, startIndex, stopIndex - startIndex));
- for (int i = startIndex; i < stopIndex; i++) {
- final char c = this.foText.charAt(i);
- newIPD.add(new MinOptMax(font.getCharWidth(c)));
- //if (i > startIndex) {
- if (i < stopIndex) {
- MinOptMax la = this.letterAdjustArray[i + 1];
- if (i == stopIndex - 1 && hyphenFollows) {
- //the letter adjust here needs to be handled further down during
- //element generation because it depends on hyph/no-hyph condition
- la = null;
- }
- if (la != null) {
- newIPD.add(la);
- }
- }
- }
- // add letter spaces
- final boolean isWordEnd
- = stopIndex == ai.breakIndex
- && ai.letterSpaceCount < ai.breakIndex - ai.startIndex;
- newIPD.add(MinOptMax.multiply(this.letterSpaceIPD,
- (isWordEnd
- ? stopIndex - startIndex - 1
- : stopIndex - startIndex)));
-
- if (!(nothingChanged
- && stopIndex == ai.breakIndex
- && !hyphenFollows)) {
- // the new AreaInfo object is not equal to the old one
- if (this.changeList == null) {
- this.changeList = new LinkedList();
- }
- this.changeList.add(new PendingChange(new AreaInfo(
- startIndex, stopIndex, 0,
- (isWordEnd ? stopIndex - startIndex - 1
- : stopIndex - startIndex), newIPD,
- hyphenFollows, false, false, font),
- ((LeafPosition) pos).getLeafPos()));
- nothingChanged = false;
- }
- startIndex = stopIndex;
- }
- this.hasChanged = (this.hasChanged || !nothingChanged);
- }
-
- /** {@inheritDoc} */
- public boolean applyChanges(final List oldList) {
- this.setFinished(false);
-
- if (this.changeList != null && !this.changeList.isEmpty()) {
- int areaInfosAdded = 0;
- int areaInfosRemoved = 0;
- int oldIndex = -1, changeIndex;
- PendingChange currChange;
- final ListIterator changeListIterator = this.changeList.listIterator();
- while (changeListIterator.hasNext()) {
- currChange = (PendingChange) changeListIterator.next();
- if (currChange.index == oldIndex) {
- areaInfosAdded++;
- changeIndex = currChange.index + areaInfosAdded - areaInfosRemoved;
- } else {
- areaInfosRemoved++;
- areaInfosAdded++;
- oldIndex = currChange.index;
- changeIndex = currChange.index + areaInfosAdded - areaInfosRemoved;
- this.vecAreaInfo.remove(changeIndex);
- }
- this.vecAreaInfo.add(changeIndex, currChange.ai);
- }
- this.changeList.clear();
- }
-
- this.returnedIndex = 0;
- return this.hasChanged;
- }
-
- /** {@inheritDoc} */
- public List getChangedKnuthElements(final List oldList,
- final int alignment) {
- if (this.isFinished()) {
- return null;
- }
-
- final LinkedList returnList = new LinkedList();
-
- while (this.returnedIndex < this.vecAreaInfo.size()) {
- final AreaInfo ai = (AreaInfo) this.vecAreaInfo.get(this.returnedIndex);
- if (ai.wordSpaceCount == 0) {
- // ai refers either to a word or a word fragment
- this.addElementsForAWordFragment(returnList, alignment, ai,
- this.returnedIndex, this.letterSpaceIPD);
- } else {
- // ai refers to a space
- this.addElementsForASpace(returnList, alignment, ai, this.returnedIndex);
- }
- this.returnedIndex++;
- } // end of while
- this.setFinished(true);
- //ElementListObserver.observe(returnList, "text-changed", null);
- return returnList;
- }
-
- /** {@inheritDoc} */
- public void getWordChars(final StringBuffer sbChars, final Position pos) {
- final int leafValue = ((LeafPosition) pos).getLeafPos();
- if (leafValue != -1) {
- final AreaInfo ai = (AreaInfo) this.vecAreaInfo.get(leafValue);
- for (int i = ai.startIndex; i < ai.breakIndex; ++i) {
- sbChars.append(this.foText.charAt(i));
- }
- }
- }
-
- private void addElementsForASpace(final List baseList,
- final int alignment,
- final AreaInfo ai,
- final int leafValue) {
- final LeafPosition mainPosition = new LeafPosition(this, leafValue);
-
- if (!ai.breakOppAfter) {
- // a non-breaking space
- if (alignment == Constants.EN_JUSTIFY) {
- // the space can stretch and shrink, and must be preserved
- // when starting a line
- baseList.add(this.makeAuxiliaryZeroWidthBox());
- baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(ai.areaIPD.opt, ai.areaIPD.max - ai.areaIPD.opt,
- ai.areaIPD.opt - ai.areaIPD.min, mainPosition, false));
- } else {
- // the space does not need to stretch or shrink, and must be
- // preserved when starting a line
- baseList.add(new KnuthInlineBox(ai.areaIPD.opt, null,
- mainPosition, true));
- }
- } else {
- if (this.foText.charAt(ai.startIndex) != CharUtilities.SPACE
- || this.foText.getWhitespaceTreatment() == Constants.EN_PRESERVE) {
- // a breaking space that needs to be preserved
- this.addElementsForBreakingSpace(baseList, alignment, ai,
- this.auxiliaryPosition, 0, mainPosition,
- ai.areaIPD.opt, true);
- } else {
- // a (possible block) of breaking spaces
- this.addElementsForBreakingSpace(baseList, alignment, ai,
- mainPosition, ai.areaIPD.opt,
- this.auxiliaryPosition, 0, false);
- }
- }
- }
-
- private void addElementsForBreakingSpace(final List baseList,
- final int alignment, final AreaInfo ai, final Position pos2,
- final int p2WidthOffset, final Position pos3,
- final int p3WidthOffset, final boolean skipZeroCheck) {
- switch (alignment) {
- case EN_CENTER:
- // centered text:
- // if the second element is chosen as a line break these elements
- // add a constant amount of stretch at the end of a line and at the
- // beginning of the next one, otherwise they don't add any stretch
- baseList.add(new KnuthGlue(this.lineEndBAP,
- 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
- this.auxiliaryPosition, false));
- baseList.add(this.makeZeroWidthPenalty(0));
- baseList.add(new KnuthGlue(p2WidthOffset
- - (this.lineStartBAP + this.lineEndBAP), -6
- * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, pos2, false));
- baseList.add(this.makeAuxiliaryZeroWidthBox());
- baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(this.lineStartBAP + p3WidthOffset,
- 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, pos3, false));
- break;
-
- case EN_START: // fall through
- case EN_END:
- // left- or right-aligned text:
- // if the second element is chosen as a line break these elements
- // add a constant amount of stretch at the end of a line, otherwise
- // they don't add any stretch
- if (skipZeroCheck || this.lineStartBAP != 0 || this.lineEndBAP != 0) {
- baseList.add(new KnuthGlue(this.lineEndBAP,
- 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
- this.auxiliaryPosition, false));
- baseList.add(this.makeZeroWidthPenalty(0));
- baseList.add(new KnuthGlue(p2WidthOffset
- - (this.lineStartBAP + this.lineEndBAP), -3
- * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
- pos2, false));
- baseList.add(this.makeAuxiliaryZeroWidthBox());
- baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(this.lineStartBAP + p3WidthOffset,
- 0, 0, pos3, false));
- } else {
- baseList.add(new KnuthGlue(0,
- 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
- this.auxiliaryPosition, false));
- baseList.add(this.makeZeroWidthPenalty(0));
- baseList.add(new KnuthGlue(ai.areaIPD.opt, -3
- * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
- pos2, false));
- }
- break;
-
- case EN_JUSTIFY:
- // justified text:
- // the stretch and shrink depends on the space width
- if (skipZeroCheck || this.lineStartBAP != 0 || this.lineEndBAP != 0) {
- baseList.add(new KnuthGlue(this.lineEndBAP, 0, 0,
- this.auxiliaryPosition, false));
- baseList.add(this.makeZeroWidthPenalty(0));
- baseList.add(new KnuthGlue(p2WidthOffset
- - (this.lineStartBAP + this.lineEndBAP), ai.areaIPD.max
- - ai.areaIPD.opt, ai.areaIPD.opt - ai.areaIPD.min,
- pos2, false));
- baseList.add(this.makeAuxiliaryZeroWidthBox());
- baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(this.lineStartBAP + p3WidthOffset,
- 0, 0, pos3, false));
- } else {
- baseList.add(new KnuthGlue(ai.areaIPD.opt, ai.areaIPD.max
- - ai.areaIPD.opt, ai.areaIPD.opt - ai.areaIPD.min,
- pos2, false));
- }
- break;
-
- default:
- // last line justified, the other lines unjustified:
- // use only the space stretch
- if (skipZeroCheck || this.lineStartBAP != 0 || this.lineEndBAP != 0) {
- baseList.add(new KnuthGlue(this.lineEndBAP, 0, 0,
- this.auxiliaryPosition, false));
- baseList.add(this.makeZeroWidthPenalty(0));
- baseList.add(new KnuthGlue(p2WidthOffset
- - (this.lineStartBAP + this.lineEndBAP), ai.areaIPD.max
- - ai.areaIPD.opt, 0, pos2, false));
- baseList.add(this.makeAuxiliaryZeroWidthBox());
- baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(this.lineStartBAP + p3WidthOffset,
- 0, 0, pos3, false));
- } else {
- baseList.add(new KnuthGlue(ai.areaIPD.opt, ai.areaIPD.max
- - ai.areaIPD.opt, 0, pos2, false));
- }
- }
- }
-
- private void addElementsForAWordFragment(final List baseList,
- final int alignment,
- final AreaInfo ai,
- final int leafValue,
- final MinOptMax letterSpaceWidth) {
-
- final LeafPosition mainPosition = new LeafPosition(this, leafValue);
-
- // if the last character of the word fragment is '-' or '/',
- // the fragment could end a line; in this case, it loses one
- // of its letter spaces;
- final boolean suppressibleLetterSpace = ai.breakOppAfter && !ai.isHyphenated;
-
- if (letterSpaceWidth.min == letterSpaceWidth.max) {
- // constant letter spacing
- baseList.add(new KnuthInlineBox(
- suppressibleLetterSpace
- ? ai.areaIPD.opt - letterSpaceWidth.opt
- : ai.areaIPD.opt,
- this.alignmentContext,
- this.notifyPos(mainPosition), false));
- } else {
- // adjustable letter spacing
- final int unsuppressibleLetterSpaces
- = suppressibleLetterSpace ? ai.letterSpaceCount - 1 : ai.letterSpaceCount;
- baseList.add
- (new KnuthInlineBox(ai.areaIPD.opt
- - ai.letterSpaceCount * letterSpaceWidth.opt,
- this.alignmentContext,
- this.notifyPos(mainPosition), false));
- baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add
- (new KnuthGlue(unsuppressibleLetterSpaces * letterSpaceWidth.opt,
- unsuppressibleLetterSpaces * (letterSpaceWidth.max - letterSpaceWidth.opt),
- unsuppressibleLetterSpaces * (letterSpaceWidth.opt - letterSpaceWidth.min),
- this.auxiliaryPosition, true));
- baseList.add(this.makeAuxiliaryZeroWidthBox());
- }
-
- // extra-elements if the word fragment is the end of a syllable,
- // or it ends with a character that can be used as a line break
- if (ai.isHyphenated) {
- MinOptMax widthIfNoBreakOccurs = null;
- if (ai.breakIndex < this.foText.length()) {
- //Add in kerning in no-break condition
- widthIfNoBreakOccurs = this.letterAdjustArray[ai.breakIndex];
- }
- //if (ai.breakIndex)
-
- // the word fragment ends at the end of a syllable:
- // if a break occurs the content width increases,
- // otherwise nothing happens
- this.addElementsForAHyphen(baseList, alignment, this.hyphIPD,
- widthIfNoBreakOccurs, ai.breakOppAfter && ai.isHyphenated);
- } else if (suppressibleLetterSpace) {
- // the word fragment ends with a character that acts as a hyphen
- // if a break occurs the width does not increase,
- // otherwise there is one more letter space
- this.addElementsForAHyphen(baseList, alignment, 0, letterSpaceWidth, true);
- }
- }
-
- // static final int SOFT_HYPHEN_PENALTY = KnuthPenalty.FLAGGED_PENALTY / 10;
- private static final int SOFT_HYPHEN_PENALTY = 1;
-
- private void addElementsForAHyphen(final List baseList,
- final int alignment,
- final int widthIfBreakOccurs,
- MinOptMax widthIfNoBreakOccurs,
- final boolean unflagged) {
- if (widthIfNoBreakOccurs == null) {
- widthIfNoBreakOccurs = TextLayoutManager.ZERO_MINOPTMAX;
- }
-
- switch (alignment) {
- case EN_CENTER :
- // centered text:
- baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(this.lineEndBAP,
- 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
- this.auxiliaryPosition, true));
- baseList.add(new KnuthPenalty(this.hyphIPD,
- unflagged ? TextLayoutManager.SOFT_HYPHEN_PENALTY
- : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
- this.auxiliaryPosition, false));
- baseList.add(new KnuthGlue(-(this.lineEndBAP + this.lineStartBAP),
- -6 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
- this.auxiliaryPosition, false));
- baseList.add(this.makeAuxiliaryZeroWidthBox());
- baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(this.lineStartBAP,
- 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
- this.auxiliaryPosition, true));
- break;
-
- case EN_START : // fall through
- case EN_END :
- // left- or right-aligned text:
- if (this.lineStartBAP != 0 || this.lineEndBAP != 0) {
- baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(this.lineEndBAP,
- 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
- this.auxiliaryPosition, false));
- baseList.add(new KnuthPenalty(widthIfBreakOccurs,
- unflagged ? TextLayoutManager.SOFT_HYPHEN_PENALTY
- : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
- this.auxiliaryPosition, false));
- baseList.add(new KnuthGlue(widthIfNoBreakOccurs.opt
- - (this.lineStartBAP + this.lineEndBAP), -3
- * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
- this.auxiliaryPosition, false));
- baseList.add(this.makeAuxiliaryZeroWidthBox());
- baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(this.lineStartBAP, 0, 0,
- this.auxiliaryPosition, false));
- } else {
- baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(0, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
- this.auxiliaryPosition, false));
- baseList.add(new KnuthPenalty(widthIfBreakOccurs,
- unflagged ? TextLayoutManager.SOFT_HYPHEN_PENALTY
- : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
- this.auxiliaryPosition, false));
- baseList.add(new KnuthGlue(widthIfNoBreakOccurs.opt,
- -3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
- this.auxiliaryPosition, false));
- }
- break;
-
- default:
- // justified text, or last line justified:
- // just a flagged penalty
- if (this.lineStartBAP != 0 || this.lineEndBAP != 0) {
- baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(this.lineEndBAP, 0, 0,
- this.auxiliaryPosition, false));
- baseList.add(new KnuthPenalty(widthIfBreakOccurs,
- unflagged ? TextLayoutManager.SOFT_HYPHEN_PENALTY
- : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
- this.auxiliaryPosition, false));
- // extra elements representing a letter space that is suppressed
- // if a break occurs
- if (widthIfNoBreakOccurs.min != 0
- || widthIfNoBreakOccurs.max != 0) {
- baseList
- .add(new KnuthGlue(widthIfNoBreakOccurs.opt
- - (this.lineStartBAP + this.lineEndBAP),
- widthIfNoBreakOccurs.max
- - widthIfNoBreakOccurs.opt,
- widthIfNoBreakOccurs.opt
- - widthIfNoBreakOccurs.min,
- this.auxiliaryPosition, false));
- } else {
- baseList.add(new KnuthGlue(-(this.lineStartBAP + this.lineEndBAP), 0, 0,
- this.auxiliaryPosition, false));
- }
- baseList.add(this.makeAuxiliaryZeroWidthBox());
- baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(this.lineStartBAP, 0, 0,
- this.auxiliaryPosition, false));
- } else {
- baseList.add(new KnuthPenalty(widthIfBreakOccurs,
- unflagged ? TextLayoutManager.SOFT_HYPHEN_PENALTY
- : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
- this.auxiliaryPosition, false));
- // extra elements representing a letter space that is suppressed
- // if a break occurs
- if (widthIfNoBreakOccurs.min != 0
- || widthIfNoBreakOccurs.max != 0) {
- baseList.add(new KnuthGlue(widthIfNoBreakOccurs.opt,
- widthIfNoBreakOccurs.max - widthIfNoBreakOccurs.opt,
- widthIfNoBreakOccurs.opt - widthIfNoBreakOccurs.min,
- this.auxiliaryPosition, false));
- }
- }
- }
-
- }
-
- }
|