12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709 |
- /*
- * 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.ArrayList;
- import java.util.Arrays;
- 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.complexscripts.fonts.GlyphPositioningTable;
- import org.apache.fop.complexscripts.util.CharScript;
- import org.apache.fop.fo.Constants;
- import org.apache.fop.fo.FOText;
- 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
-
- // static final int SOFT_HYPHEN_PENALTY = KnuthPenalty.FLAGGED_PENALTY / 10;
- private static final int SOFT_HYPHEN_PENALTY = 1;
-
- /**
- * 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 int wordCharLength;
- private final int wordSpaceCount;
- private int letterSpaceCount;
- private MinOptMax areaIPD;
- private final boolean isHyphenated;
- private final boolean isSpace;
- private boolean breakOppAfter;
- private final Font font;
- private final int level;
- private final int[][] gposAdjustments;
-
- AreaInfo // CSOK: ParameterNumber
- (int startIndex, int breakIndex, int wordSpaceCount, int letterSpaceCount,
- MinOptMax areaIPD, boolean isHyphenated, boolean isSpace, boolean breakOppAfter,
- Font font, int level, int[][] gposAdjustments) {
- assert startIndex <= breakIndex;
- this.startIndex = startIndex;
- this.breakIndex = breakIndex;
- this.wordCharLength = -1;
- this.wordSpaceCount = wordSpaceCount;
- this.letterSpaceCount = letterSpaceCount;
- this.areaIPD = areaIPD;
- this.isHyphenated = isHyphenated;
- this.isSpace = isSpace;
- this.breakOppAfter = breakOppAfter;
- this.font = font;
- this.level = level;
- this.gposAdjustments = gposAdjustments;
- }
-
- /**
- * Obtain number of 'characters' contained in word. If word
- * is mapped, then this number may be less than or greater than the
- * original length (breakIndex - startIndex). We compute and
- * memoize thius length upon first invocation of this method.
- */
- private int getWordLength() {
- if ( wordCharLength == -1 ) {
- if ( foText.hasMapping ( startIndex, breakIndex ) ) {
- wordCharLength = foText.getMapping ( startIndex, breakIndex ).length();
- } else {
- assert breakIndex >= startIndex;
- wordCharLength = breakIndex - startIndex;
- }
- }
- return wordCharLength;
- }
-
- private void addToAreaIPD(MinOptMax idp) {
- areaIPD = areaIPD.plus(idp);
- }
-
- public String toString() {
- return super.toString() + "{"
- + "interval = [" + startIndex + "," + breakIndex + "]"
- + ", isSpace = " + isSpace
- + ", level = " + level
- + ", areaIPD = " + areaIPD
- + ", letterSpaceCount = " + letterSpaceCount
- + ", wordSpaceCount = " + wordSpaceCount
- + ", isHyphenated = " + isHyphenated
- + ", font = " + font
- + "}";
- }
- }
-
- /**
- * this class stores information about changes in vecAreaInfo which are not yet applied
- */
- private final class PendingChange {
-
- private final AreaInfo areaInfo;
- private final int index;
-
- private PendingChange(final AreaInfo areaInfo, final int index) {
- this.areaInfo = areaInfo;
- 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 areaInfos;
-
- /** Non-space characters on which we can end a line. */
- private static final String BREAK_CHARS = "-/";
-
- 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[] letterSpaceAdjustArray; //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;
-
- private boolean hasChanged = false;
- private int[] returnedIndices = {0, 0};
- private int changeOffset = 0;
- private int thisStart = 0;
- private int tempStart = 0;
- private List changeList = new LinkedList();
-
- private AlignmentContext alignmentContext = null;
-
- /**
- * The width to be reserved for border and padding at the start of the line.
- */
- private int lineStartBAP = 0;
-
- /**
- * The width to be reserved for border and padding at the end of the line.
- */
- 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(FOText node) {
- foText = node;
- letterSpaceAdjustArray = new MinOptMax[node.length() + 1];
- areaInfos = new ArrayList();
- }
-
- private KnuthPenalty makeZeroWidthPenalty(int penaltyValue) {
- return new KnuthPenalty(0, penaltyValue, false, auxiliaryPosition, true);
- }
-
- private KnuthBox makeAuxiliaryZeroWidthBox() {
- return new KnuthInlineBox(0, null, notifyPos(new LeafPosition(this, -1)), true);
- }
-
- /** {@inheritDoc} */
- public void initialize() {
-
- foText.resetBuffer();
-
- spaceFont = FontSelector.selectFontForCharacterInText(' ', foText, this);
-
- // With CID fonts, space isn't necessary currentFontState.width(32)
- spaceCharIPD = spaceFont.getCharWidth(' ');
-
- // Use hyphenationChar property
- // TODO: Use hyphen based on actual font used!
- hyphIPD = foText.getCommonHyphenation().getHyphIPD(spaceFont);
-
- SpaceVal letterSpacing = SpaceVal.makeLetterSpacing(foText.getLetterSpacing());
- SpaceVal wordSpacing = SpaceVal.makeWordSpacing(foText.getWordSpacing(), letterSpacing,
- 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
- letterSpaceIPD = letterSpacing.getSpace();
- wordSpaceIPD = MinOptMax.getInstance(spaceCharIPD).plus(wordSpacing.getSpace());
- keepTogether = 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 areaInfo;
- int wordSpaceCount = 0;
- int letterSpaceCount = 0;
- int firstAreaInfoIndex = -1;
- int lastAreaInfoIndex = 0;
- MinOptMax realWidth = MinOptMax.ZERO;
-
- /* On first area created, add any leading space.
- * Calculate word-space stretch value.
- */
- AreaInfo lastAreaInfo = null;
- while (posIter.hasNext()) {
- final LeafPosition tbpNext = (LeafPosition) posIter.next();
- if (tbpNext == null) {
- continue; //Ignore elements without Positions
- }
- if (tbpNext.getLeafPos() != -1) {
- areaInfo = (AreaInfo) areaInfos.get(tbpNext.getLeafPos());
- if (lastAreaInfo == null
- || ( areaInfo.font != lastAreaInfo.font )
- || ( areaInfo.level != lastAreaInfo.level ) ) {
- if (lastAreaInfo != null) {
- addAreaInfoAreas(lastAreaInfo, wordSpaceCount,
- letterSpaceCount, firstAreaInfoIndex,
- lastAreaInfoIndex, realWidth, context);
- }
- firstAreaInfoIndex = tbpNext.getLeafPos();
- wordSpaceCount = 0;
- letterSpaceCount = 0;
- realWidth = MinOptMax.ZERO;
- }
- wordSpaceCount += areaInfo.wordSpaceCount;
- letterSpaceCount += areaInfo.letterSpaceCount;
- realWidth = realWidth.plus(areaInfo.areaIPD);
- lastAreaInfoIndex = tbpNext.getLeafPos();
- lastAreaInfo = areaInfo;
- }
- }
- if (lastAreaInfo != null) {
- addAreaInfoAreas(lastAreaInfo, wordSpaceCount, letterSpaceCount, firstAreaInfoIndex,
- lastAreaInfoIndex, realWidth, context);
- }
-
- }
-
- private void addAreaInfoAreas(AreaInfo areaInfo, int wordSpaceCount, int letterSpaceCount,
- int firstAreaInfoIndex, int lastAreaInfoIndex,
- MinOptMax realWidth, 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 areaInfo.
- // This needs to be checked.
- int textLength = areaInfo.getWordLength();
- if (areaInfo.letterSpaceCount == textLength && !areaInfo.isHyphenated
- && context.isLastArea()) {
- // the line ends at a character like "/" or "-";
- // remove the letter space after the last character
- realWidth = realWidth.minus(letterSpaceIPD);
- letterSpaceCount--;
- }
-
- for (int i = areaInfo.startIndex; i < areaInfo.breakIndex; i++) {
- MinOptMax letterSpaceAdjustment = letterSpaceAdjustArray[i + 1];
- if (letterSpaceAdjustment != null && letterSpaceAdjustment.isElastic()) {
- letterSpaceCount++;
- }
- }
-
- // add hyphenation character if the last word is hyphenated
- if (context.isLastArea() && areaInfo.isHyphenated) {
- realWidth = realWidth.plus(hyphIPD);
- }
-
- /* Calculate adjustments */
- double ipdAdjust = context.getIPDAdjust();
-
- // calculate total difference between real and available width
- int difference;
- if (ipdAdjust > 0.0) {
- difference = (int) (realWidth.getStretch() * ipdAdjust);
- } else {
- difference = (int) (realWidth.getShrink() * ipdAdjust);
- }
-
- // set letter space adjustment
- int letterSpaceDim = letterSpaceIPD.getOpt();
- if (ipdAdjust > 0.0) {
- letterSpaceDim += (int) (letterSpaceIPD.getStretch() * ipdAdjust);
- } else {
- letterSpaceDim += (int) (letterSpaceIPD.getShrink() * ipdAdjust);
- }
- int totalAdjust = (letterSpaceDim - letterSpaceIPD.getOpt()) * letterSpaceCount;
-
- // set word space adjustment
- int wordSpaceDim = wordSpaceIPD.getOpt();
- if (wordSpaceCount > 0) {
- wordSpaceDim += (difference - totalAdjust) / wordSpaceCount;
- }
- totalAdjust += (wordSpaceDim - wordSpaceIPD.getOpt()) * 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;
- }
-
- TextArea textArea = new TextAreaBuilder(realWidth, totalAdjust, context, firstAreaInfoIndex,
- lastAreaInfoIndex, context.isLastArea(), areaInfo.font).build();
-
- // 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
- textArea.setTextLetterSpaceAdjust(letterSpaceDim);
- textArea.setTextWordSpaceAdjust(wordSpaceDim - spaceCharIPD
- - 2 * textArea.getTextLetterSpaceAdjust());
- if (context.getIPDAdjust() != 0) {
- // add information about space width
- textArea.setSpaceDifference(wordSpaceIPD.getOpt() - spaceCharIPD
- - 2 * textArea.getTextLetterSpaceAdjust());
- }
- parentLayoutManager.addChildArea(textArea);
- }
-
- private final class TextAreaBuilder {
-
- // constructor initialized state
- private final MinOptMax width; // content ipd
- private final int adjust; // content ipd adjustment
- private final LayoutContext context; // layout context
- private final int firstIndex; // index of first AreaInfo
- private final int lastIndex; // index of last AreaInfo
- private final boolean isLastArea; // true if last inline area in line area
- private final Font font; // applicable font
-
- // other, non-constructor state
- private TextArea textArea; // text area being constructed
- private int blockProgressionDimension; // calculated bpd
- private AreaInfo areaInfo; // current area info when iterating over words
- private StringBuffer wordChars; // current word's character buffer
- private int[] letterSpaceAdjust; // current word's letter space adjustments
- private int letterSpaceAdjustIndex; // last written letter space adjustment index
- private int[] wordLevels; // current word's bidi levels
- private int wordLevelsIndex; // last written bidi level index
- private int wordIPD; // accumulated ipd of current word
- private int[][] gposAdjustments; // current word's glyph position adjustments
- private int gposAdjustmentsIndex; // last written glyph position adjustment index
-
- /**
- * Creates a new <code>TextAreaBuilder</code> which itself builds 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 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
- */
- private TextAreaBuilder(MinOptMax width, int adjust, LayoutContext context,
- int firstIndex, int lastIndex, boolean isLastArea, Font font) {
- this.width = width;
- this.adjust = adjust;
- this.context = context;
- this.firstIndex = firstIndex;
- this.lastIndex = lastIndex;
- this.isLastArea = isLastArea;
- this.font = font;
- }
-
- private TextArea build() {
- createTextArea();
- setInlineProgressionDimension();
- calcBlockProgressionDimension();
- setBlockProgressionDimension();
- setBaselineOffset();
- setBlockProgressionOffset();
- setText();
- TraitSetter.addFontTraits(textArea, font);
- textArea.addTrait(Trait.COLOR, foText.getColor());
- TraitSetter.addTextDecoration(textArea, foText.getTextDecoration());
- if (!context.treatAsArtifact()) {
- TraitSetter.addStructureTreeElement(textArea, foText.getStructureTreeElement());
- }
- return textArea;
- }
-
- /**
- * Creates an plain <code>TextArea</code> or a justified <code>TextArea</code> with
- * additional information.
- */
- private void createTextArea() {
- if (context.getIPDAdjust() == 0.0) {
- textArea = new TextArea();
- } else {
- textArea = new TextArea(width.getStretch(), width.getShrink(),
- adjust);
- }
- }
-
- private void setInlineProgressionDimension() {
- textArea.setIPD(width.getOpt() + adjust);
- }
-
- private void calcBlockProgressionDimension() {
- blockProgressionDimension = font.getAscender() - font.getDescender();
- }
-
- private void setBlockProgressionDimension() {
- textArea.setBPD(blockProgressionDimension);
- }
-
- private void setBaselineOffset() {
- textArea.setBaselineOffset(font.getAscender());
- }
-
- private void setBlockProgressionOffset() {
- if (blockProgressionDimension == alignmentContext.getHeight()) {
- textArea.setBlockProgressionOffset(0);
- } else {
- textArea.setBlockProgressionOffset(alignmentContext.getOffset());
- }
- }
-
- /**
- * Sets the text of the TextArea, split into words and spaces.
- */
- private void setText() {
- int areaInfoIndex = -1;
- int wordCharLength = 0;
- for (int wordIndex = firstIndex; wordIndex <= lastIndex; wordIndex++) {
- areaInfo = getAreaInfo(wordIndex);
- if (areaInfo.isSpace) {
- addSpaces();
- } else {
- // areaInfo stores information about a word fragment
- if (areaInfoIndex == -1) {
- // here starts a new word
- areaInfoIndex = wordIndex;
- wordCharLength = 0;
- }
- wordCharLength += areaInfo.getWordLength();
- if (isWordEnd(wordIndex)) {
- addWord(areaInfoIndex, wordIndex, wordCharLength);
- areaInfoIndex = -1;
- }
- }
- }
- }
-
- private boolean isWordEnd(int areaInfoIndex) {
- return areaInfoIndex == lastIndex || getAreaInfo(areaInfoIndex + 1).isSpace;
- }
-
- /**
- * Add word with fragments from STARTINDEX to ENDINDEX, where
- * total length of (possibly mapped) word is CHARLENGTH.
- * A word is composed from one or more word fragments, where each
- * fragment corresponds to distinct instance in a sequence of
- * area info instances starting at STARTINDEX continuing through (and
- * including) ENDINDEX.
- * @param startIndex index of first area info of word to add
- * @param endIndex index of last area info of word to add
- * @param wordLength number of (mapped) characters in word
- */
- private void addWord(int startIndex, int endIndex, int wordLength) {
- int blockProgressionOffset = 0;
- boolean gposAdjusted = false;
- if (isHyphenated(endIndex)) {
- // TODO may be problematic in some I18N contexts [GA]
- wordLength++;
- }
- initWord(wordLength);
- // iterate over word's fragments, adding word chars (with bidi
- // levels), letter space adjustments, and glyph position adjustments
- for (int i = startIndex; i <= endIndex; i++) {
- AreaInfo wordAreaInfo = getAreaInfo(i);
- addWordChars(wordAreaInfo);
- addLetterAdjust(wordAreaInfo);
- if ( addGlyphPositionAdjustments(wordAreaInfo) ) {
- gposAdjusted = true;
- }
- }
- if (isHyphenated(endIndex)) {
- // TODO may be problematic in some I18N contexts [GA]
- addHyphenationChar();
- }
- if ( !gposAdjusted ) {
- gposAdjustments = null;
- }
- textArea.addWord(wordChars.toString(), wordIPD, letterSpaceAdjust,
- getNonEmptyLevels(), gposAdjustments, blockProgressionOffset);
- }
-
- private int[] getNonEmptyLevels() {
- if ( wordLevels != null ) {
- assert wordLevelsIndex <= wordLevels.length;
- boolean empty = true;
- for ( int i = 0, n = wordLevelsIndex; i < n; i++ ) {
- if ( wordLevels [ i ] >= 0 ) {
- empty = false;
- break;
- }
- }
- return empty ? null : wordLevels;
- } else {
- return null;
- }
- }
-
- /**
- * Fully allocate word character buffer, letter space adjustments
- * array, bidi levels array, and glyph position adjustments array.
- * based on full word length, including all (possibly mapped) fragments.
- * @param wordLength length of word including all (possibly mapped) fragments
- */
- private void initWord(int wordLength) {
- wordChars = new StringBuffer(wordLength);
- letterSpaceAdjust = new int[wordLength];
- letterSpaceAdjustIndex = 0;
- wordLevels = new int[wordLength];
- wordLevelsIndex = 0;
- Arrays.fill ( wordLevels, -1 );
- gposAdjustments = new int[wordLength][4];
- gposAdjustmentsIndex = 0;
- wordIPD = 0;
- }
-
- private boolean isHyphenated(int endIndex) {
- return isLastArea && endIndex == lastIndex && areaInfo.isHyphenated;
- }
-
- private void addHyphenationChar() {
- wordChars.append(foText.getCommonHyphenation().getHyphChar(font));
- // [TBD] expand bidi word levels, letter space adjusts, gpos adjusts
- // [TBD] [GA] problematic in bidi context... what is level of hyphen?
- }
-
- /**
- * Given a word area info associated with a word fragment,
- * (1) concatenate (possibly mapped) word characters to word character buffer;
- * (2) concatenante (possibly mapped) word bidi levels to levels buffer;
- * (3) update word's IPD with optimal IPD of fragment.
- * @param wordAreaInfo fragment info
- */
- private void addWordChars(AreaInfo wordAreaInfo) {
- int s = wordAreaInfo.startIndex;
- int e = wordAreaInfo.breakIndex;
- if ( foText.hasMapping ( s, e ) ) {
- wordChars.append ( foText.getMapping ( s, e ) );
- addWordLevels ( foText.getMappingBidiLevels ( s, e ) );
- } else {
- for (int i = s; i < e; i++) {
- wordChars.append(foText.charAt(i));
- }
- addWordLevels ( foText.getBidiLevels ( s, e ) );
- }
- wordIPD += wordAreaInfo.areaIPD.getOpt();
- }
-
- /**
- * Given a (possibly null) bidi levels array associated with a word fragment,
- * concatenante (possibly mapped) word bidi levels to levels buffer.
- * @param levels bidi levels array or null
- */
- private void addWordLevels ( int[] levels ) {
- int numLevels = ( levels != null ) ? levels.length : 0;
- if ( numLevels > 0 ) {
- int need = wordLevelsIndex + numLevels;
- if ( need <= wordLevels.length ) {
- System.arraycopy ( levels, 0, wordLevels, wordLevelsIndex, numLevels );
- } else {
- throw new IllegalStateException
- ( "word levels array too short: expect at least "
- + need + " entries, but has only " + wordLevels.length + " entries" );
- }
- }
- wordLevelsIndex += numLevels;
- }
-
- /**
- * Given a word area info associated with a word fragment,
- * concatenate letter space adjustments for each (possibly mapped) character.
- * @param wordAreaInfo fragment info
- */
- private void addLetterAdjust(AreaInfo wordAreaInfo) {
- int letterSpaceCount = wordAreaInfo.letterSpaceCount;
- int wordLength = wordAreaInfo.getWordLength();
- int taAdjust = textArea.getTextLetterSpaceAdjust();
- for ( int i = 0, n = wordLength; i < n; i++ ) {
- int j = letterSpaceAdjustIndex + i;
- if ( j > 0 ) {
- int k = wordAreaInfo.startIndex + i;
- MinOptMax adj = ( k < letterSpaceAdjustArray.length )
- ? letterSpaceAdjustArray [ k ] : null;
- letterSpaceAdjust [ j ] = ( adj == null ) ? 0 : adj.getOpt();
- }
- if ( letterSpaceCount > 0 ) {
- letterSpaceAdjust [ j ] += taAdjust;
- letterSpaceCount--;
- }
- }
- letterSpaceAdjustIndex += wordLength;
- }
-
- /**
- * Given a word area info associated with a word fragment,
- * concatenate glyph position adjustments for each (possibly mapped) character.
- * @param wordAreaInfo fragment info
- * @return true if an adjustment was non-zero
- */
- private boolean addGlyphPositionAdjustments(AreaInfo wordAreaInfo) {
- boolean adjusted = false;
- int[][] gpa = wordAreaInfo.gposAdjustments;
- int numAdjusts = ( gpa != null ) ? gpa.length : 0;
- int wordLength = wordAreaInfo.getWordLength();
- if ( numAdjusts > 0 ) {
- int need = gposAdjustmentsIndex + numAdjusts;
- if ( need <= gposAdjustments.length ) {
- for ( int i = 0, n = wordLength, j = 0; i < n; i++ ) {
- if ( i < numAdjusts ) {
- int[] wpa1 = gposAdjustments [ gposAdjustmentsIndex + i ];
- int[] wpa2 = gpa [ j++ ];
- for ( int k = 0; k < 4; k++ ) {
- int a = wpa2 [ k ];
- if ( a != 0 ) {
- wpa1 [ k ] += a;
- adjusted = true;
- }
- }
- }
- }
- } else {
- throw new IllegalStateException
- ( "gpos adjustments array too short: expect at least "
- + need + " entries, but has only " + gposAdjustments.length
- + " entries" );
- }
- }
- gposAdjustmentsIndex += wordLength;
- return adjusted;
- }
-
- /**
- * The <code>AreaInfo</code> stores information about spaces.
- * <p/>
- * Add the spaces - except zero-width spaces - to the TextArea.
- */
- private void addSpaces() {
- int blockProgressionOffset = 0;
- // [TBD] need to better handling of spaceIPD assignment, for now,
- // divide the area info's allocated IPD evenly among the
- // non-zero-width space characters
- int numZeroWidthSpaces = 0;
- for (int i = areaInfo.startIndex; i < areaInfo.breakIndex; i++) {
- char spaceChar = foText.charAt(i);
- if (CharUtilities.isZeroWidthSpace(spaceChar)) {
- numZeroWidthSpaces++;
- }
- }
- int numSpaces = areaInfo.breakIndex - areaInfo.startIndex - numZeroWidthSpaces;
- int spaceIPD = areaInfo.areaIPD.getOpt() / ( ( numSpaces > 0 ) ? numSpaces : 1 );
- // add space area children, one for each non-zero-width space character
- for (int i = areaInfo.startIndex; i < areaInfo.breakIndex; i++) {
- char spaceChar = foText.charAt(i);
- int level = foText.bidiLevelAt(i);
- if (!CharUtilities.isZeroWidthSpace(spaceChar)) {
- textArea.addSpace
- ( spaceChar, spaceIPD,
- CharUtilities.isAdjustableSpace(spaceChar),
- blockProgressionOffset, level );
- }
- }
- }
-
- }
-
- private void addAreaInfo ( AreaInfo ai ) {
- addAreaInfo ( areaInfos.size(), ai );
- }
-
- private void addAreaInfo ( int index, AreaInfo ai ) {
- areaInfos.add ( index, ai );
- }
-
- private void removeAreaInfo ( int index ) {
- areaInfos.remove ( index );
- }
-
- private AreaInfo getAreaInfo(int index) {
- return (AreaInfo) areaInfos.get(index);
- }
-
- private void addToLetterAdjust(int index, int width) {
- if (letterSpaceAdjustArray[index] == null) {
- letterSpaceAdjustArray[index] = MinOptMax.getInstance(width);
- } else {
- letterSpaceAdjustArray[index] = letterSpaceAdjustArray[index].plus(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) {
-
- lineStartBAP = context.getLineStartBorderAndPaddingWidth();
- lineEndBAP = context.getLineEndBorderAndPaddingWidth();
- alignmentContext = context.getAlignmentContext();
-
- final List returnList = new LinkedList();
- KnuthSequence sequence = new InlineKnuthSequence();
- AreaInfo areaInfo = null;
- AreaInfo prevAreaInfo = null;
- returnList.add(sequence);
-
- if (LOG.isDebugEnabled()) {
- LOG.debug ( "GK: [" + nextStart + "," + foText.length() + "]" );
- }
- LineBreakStatus lineBreakStatus = new LineBreakStatus();
- thisStart = nextStart;
- boolean inWord = false;
- boolean inWhitespace = false;
- char ch = 0;
- int level = -1;
- int prevLevel = -1;
- while (nextStart < foText.length()) {
- ch = foText.charAt(nextStart);
- level = foText.bidiLevelAt(nextStart);
- boolean breakOpportunity = false;
- byte breakAction = keepTogether
- ? LineBreakStatus.PROHIBITED_BREAK
- : lineBreakStatus.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 (LOG.isDebugEnabled()) {
- LOG.debug ( "GK: {"
- + " index = " + nextStart
- + ", char = " + CharUtilities.charToNCRef ( ch )
- + ", level = " + level
- + ", levelPrev = " + prevLevel
- + ", inWord = " + inWord
- + ", inSpace = " + inWhitespace
- + "}" );
- }
- if (inWord) {
- if ( breakOpportunity
- || TextLayoutManager.isSpace(ch)
- || CharUtilities.isExplicitBreak(ch)
- || ( ( prevLevel != -1 ) && ( level != prevLevel ) ) ) {
- // this.foText.charAt(lastIndex) == CharUtilities.SOFT_HYPHEN
- prevAreaInfo = processWord(alignment, sequence, prevAreaInfo, ch,
- breakOpportunity, true, prevLevel);
- }
- } else if (inWhitespace) {
- if (ch != CharUtilities.SPACE || breakOpportunity) {
- prevAreaInfo = processWhitespace(alignment, sequence,
- breakOpportunity, prevLevel);
- }
- } else {
- if (areaInfo != null) {
- prevAreaInfo = areaInfo;
- processLeftoverAreaInfo(alignment, sequence, areaInfo,
- ch == CharUtilities.SPACE || breakOpportunity);
- areaInfo = null;
- }
- if (breakAction == LineBreakStatus.EXPLICIT_BREAK) {
- sequence = processLinebreak(returnList, sequence);
- }
- }
-
- if (ch == CharUtilities.SPACE
- && foText.getWhitespaceTreatment() == Constants.EN_PRESERVE
- || ch == CharUtilities.NBSPACE) {
- // preserved space or non-breaking space:
- // create the AreaInfo object
- areaInfo = new AreaInfo(nextStart, nextStart + 1, 1, 0, wordSpaceIPD, false, true,
- breakOpportunity, spaceFont, level, null);
- thisStart = nextStart + 1;
- } else if (CharUtilities.isFixedWidthSpace(ch) || CharUtilities.isZeroWidthSpace(ch)) {
- // create the AreaInfo object
- Font font = FontSelector.selectFontForCharacterInText(ch, foText, this);
- MinOptMax ipd = MinOptMax.getInstance(font.getCharWidth(ch));
- areaInfo = new AreaInfo(nextStart, nextStart + 1, 0, 0, ipd, false, true,
- breakOpportunity, font, level, null);
- thisStart = nextStart + 1;
- } else if (CharUtilities.isExplicitBreak(ch)) {
- //mandatory break-character: only advance index
- thisStart = nextStart + 1;
- }
-
- inWord = !TextLayoutManager.isSpace(ch) && !CharUtilities.isExplicitBreak(ch);
- inWhitespace = ch == CharUtilities.SPACE
- && foText.getWhitespaceTreatment() != Constants.EN_PRESERVE;
- prevLevel = level;
- nextStart++;
- }
-
- // Process any last elements
- if (inWord) {
- processWord(alignment, sequence, prevAreaInfo, ch, false, false, prevLevel);
- } else if (inWhitespace) {
- processWhitespace(alignment, sequence, !keepTogether, prevLevel);
- } else if (areaInfo != null) {
- processLeftoverAreaInfo(alignment, sequence, areaInfo,
- 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);
- }
-
- setFinished(true);
- if (returnList.isEmpty()) {
- return null;
- } else {
- return returnList;
- }
-
-
- }
-
- private KnuthSequence processLinebreak(List returnList, KnuthSequence sequence) {
- if (lineEndBAP != 0) {
- sequence.add(new KnuthGlue(lineEndBAP, 0, 0, auxiliaryPosition, true));
- }
- sequence.endSequence();
- sequence = new InlineKnuthSequence();
- returnList.add(sequence);
- return sequence;
- }
-
- private void processLeftoverAreaInfo(int alignment,
- KnuthSequence sequence, AreaInfo areaInfo,
- boolean breakOpportunityAfter) {
- addAreaInfo(areaInfo);
- areaInfo.breakOppAfter = breakOpportunityAfter;
- addElementsForASpace(sequence, alignment, areaInfo, areaInfos.size() - 1);
- }
-
- private AreaInfo processWhitespace(final int alignment,
- final KnuthSequence sequence, final boolean breakOpportunity, int level) {
-
- if (LOG.isDebugEnabled()) {
- LOG.debug ( "PS: [" + thisStart + "," + nextStart + "]" );
- }
-
- // End of whitespace
- // create the AreaInfo object
- assert nextStart >= thisStart;
- AreaInfo areaInfo = new AreaInfo
- ( thisStart, nextStart, nextStart - thisStart, 0,
- wordSpaceIPD.mult(nextStart - thisStart),
- false, true, breakOpportunity, spaceFont, level, null );
-
- addAreaInfo(areaInfo);
-
- // create the elements
- addElementsForASpace(sequence, alignment, areaInfo, areaInfos.size() - 1);
-
- thisStart = nextStart;
- return areaInfo;
- }
-
- private AreaInfo processWordMapping
- ( int lastIndex, final Font font, AreaInfo prevAreaInfo, final char breakOpportunityChar,
- final boolean endsWithHyphen, int level ) {
- int s = this.thisStart; // start index of word in FOText character buffer
- int e = lastIndex; // end index of word in FOText character buffer
- int nLS = 0; // # of letter spaces
- String script = foText.getScript();
- String language = foText.getLanguage();
-
- if (LOG.isDebugEnabled()) {
- LOG.debug ( "PW: [" + thisStart + "," + lastIndex + "]: {"
- + " +M"
- + ", level = " + level
- + " }" );
- }
-
- // 1. extract unmapped character sequence
- CharSequence ics = foText.subSequence ( s, e );
-
- // 2. if script is not specified (by FO property) or it is specified as 'auto',
- // then compute dominant script
- if ( ( script == null ) || "auto".equals(script) ) {
- script = CharScript.scriptTagFromCode ( CharScript.dominantScript ( ics ) );
- }
- if ( ( language == null ) || "none".equals(language) ) {
- language = "dflt";
- }
-
- // 3. perform mapping of chars to glyphs ... to glyphs ... to chars
- CharSequence mcs = font.performSubstitution ( ics, script, language );
-
- // 4. compute glyph position adjustments on (substituted) characters
- int[][] gpa;
- if ( font.performsPositioning() ) {
- // handle GPOS adjustments
- gpa = font.performPositioning ( mcs, script, language );
- } else if ( font.hasKerning() ) {
- // handle standard (non-GPOS) kerning adjustments
- gpa = getKerningAdjustments ( mcs, font );
- } else {
- gpa = null;
- }
-
- // 5. reorder combining marks so that they precede (within the mapped char sequence) the
- // base to which they are applied; N.B. position adjustments (gpa) are reordered in place
- mcs = font.reorderCombiningMarks ( mcs, gpa, script, language );
-
- // 6. if mapped sequence differs from input sequence, then memoize mapped sequence
- if ( !CharUtilities.isSameSequence ( mcs, ics ) ) {
- foText.addMapping ( s, e, mcs );
- }
-
- // 7. compute word ipd based on final position adjustments
- MinOptMax ipd = MinOptMax.ZERO;
- for ( int i = 0, n = mcs.length(); i < n; i++ ) {
- int c = mcs.charAt ( i );
- // TODO !BMP
- int w = font.getCharWidth ( c );
- if ( w < 0 ) {
- w = 0;
- }
- if ( gpa != null ) {
- w += gpa [ i ] [ GlyphPositioningTable.Value.IDX_X_ADVANCE ];
- }
- ipd = ipd.plus ( w );
- }
-
- // [TBD] - handle letter spacing
-
- return new AreaInfo
- ( s, e, 0, nLS, ipd, endsWithHyphen, false,
- breakOpportunityChar != 0, font, level, gpa );
- }
-
- /**
- * Given a mapped character sequence MCS, obtain glyph position adjustments
- * from the font's kerning data.
- * @param mcs mapped character sequence
- * @param font applicable font
- * @return glyph position adjustments (or null if no kerning)
- */
- private int[][] getKerningAdjustments ( CharSequence mcs, final Font font ) {
- int nc = mcs.length();
- // extract kerning array
- int[] ka = new int [ nc ]; // kerning array
- for ( int i = 0, n = nc, cPrev = -1; i < n; i++ ) {
- int c = mcs.charAt ( i );
- // TODO !BMP
- if ( cPrev >= 0 ) {
- ka[i] = font.getKernValue ( cPrev, c );
- }
- cPrev = c;
- }
- // was there a non-zero kerning?
- boolean hasKerning = false;
- for ( int i = 0, n = nc; i < n; i++ ) {
- if ( ka[i] != 0 ) {
- hasKerning = true;
- break;
- }
- }
- // if non-zero kerning, then create and return glyph position adjustment array
- if ( hasKerning ) {
- int[][] gpa = new int [ nc ] [ 4 ];
- for ( int i = 0, n = nc; i < n; i++ ) {
- if ( i > 0 ) {
- gpa [ i - 1 ] [ GlyphPositioningTable.Value.IDX_X_ADVANCE ] = ka [ i ];
- }
- }
- return gpa;
- } else {
- return null;
- }
- }
-
- private AreaInfo processWordNoMapping(int lastIndex, final Font font, AreaInfo prevAreaInfo,
- final char breakOpportunityChar, final boolean endsWithHyphen, int level) {
- boolean kerning = font.hasKerning();
- MinOptMax wordIPD = MinOptMax.ZERO;
-
- if (LOG.isDebugEnabled()) {
- LOG.debug ( "PW: [" + thisStart + "," + lastIndex + "]: {"
- + " -M"
- + ", level = " + level
- + " }" );
- }
-
- for (int i = thisStart; i < lastIndex; i++) {
- char currentChar = foText.charAt(i);
-
- //character width
- int charWidth = font.getCharWidth(currentChar);
- wordIPD = wordIPD.plus(charWidth);
-
- //kerning
- if (kerning) {
- int kern = 0;
- if (i > thisStart) {
- char previousChar = foText.charAt(i - 1);
- kern = font.getKernValue(previousChar, currentChar);
- } else if (prevAreaInfo != null
- && !prevAreaInfo.isSpace && prevAreaInfo.breakIndex > 0) {
- char previousChar = foText.charAt(prevAreaInfo.breakIndex - 1);
- kern = font.getKernValue(previousChar, currentChar);
- }
- if (kern != 0) {
- addToLetterAdjust(i, kern);
- wordIPD = wordIPD.plus(kern);
- }
- }
- }
- if (kerning
- && ( breakOpportunityChar != 0 )
- && !TextLayoutManager.isSpace(breakOpportunityChar)
- && lastIndex > 0
- && endsWithHyphen) {
- int kern = font.getKernValue(foText.charAt(lastIndex - 1), breakOpportunityChar);
- if (kern != 0) {
- addToLetterAdjust(lastIndex, kern);
- //TODO: add kern to wordIPD?
- }
- }
- // shy+chars at start of word: wordLength == 0 && breakOpportunity
- // shy only characters in word: wordLength == 0 && !breakOpportunity
- int wordLength = lastIndex - thisStart;
- int letterSpaces = 0;
- if (wordLength != 0) {
- letterSpaces = wordLength - 1;
- // if there is a break opportunity and the next one (break character)
- // is not a space, it could be used as a line end;
- // add one more letter space, in case other text follows
- if (( breakOpportunityChar != 0 ) && !TextLayoutManager.isSpace(breakOpportunityChar)) {
- letterSpaces++;
- }
- }
- assert letterSpaces >= 0;
- wordIPD = wordIPD.plus(letterSpaceIPD.mult(letterSpaces));
-
- // create and return the AreaInfo object
- return new AreaInfo(thisStart, lastIndex, 0,
- letterSpaces, wordIPD,
- endsWithHyphen,
- false, breakOpportunityChar != 0, font, level, null);
- }
-
- private AreaInfo processWord(final int alignment, final KnuthSequence sequence,
- AreaInfo prevAreaInfo, final char ch, final boolean breakOpportunity,
- final boolean checkEndsWithHyphen, int level) {
-
- //Word boundary found, process widths and kerning
- int lastIndex = nextStart;
- while (lastIndex > 0 && foText.charAt(lastIndex - 1) == CharUtilities.SOFT_HYPHEN) {
- lastIndex--;
- }
- final boolean endsWithHyphen = checkEndsWithHyphen
- && foText.charAt(lastIndex) == CharUtilities.SOFT_HYPHEN;
- Font font = FontSelector.selectFontForCharactersInText
- ( foText, thisStart, lastIndex, foText, this );
- AreaInfo areaInfo;
- if ( font.performsSubstitution() || font.performsPositioning() ) {
- areaInfo = processWordMapping
- ( lastIndex, font, prevAreaInfo, breakOpportunity ? ch : 0, endsWithHyphen, level );
- } else {
- areaInfo = processWordNoMapping
- ( lastIndex, font, prevAreaInfo, breakOpportunity ? ch : 0, endsWithHyphen, level );
- }
- prevAreaInfo = areaInfo;
- addAreaInfo(areaInfo);
- tempStart = nextStart;
-
- //add the elements
- addElementsForAWordFragment(sequence, alignment, areaInfo, areaInfos.size() - 1);
- thisStart = nextStart;
-
- return prevAreaInfo;
- }
-
- /** {@inheritDoc} */
- public List addALetterSpaceTo(List oldList) {
- return addALetterSpaceTo(oldList, 0);
- }
-
- /** {@inheritDoc} */
- public List addALetterSpaceTo(final List oldList, int depth) {
- // 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();
- KnuthElement knuthElement = (KnuthElement) oldListIterator.next();
- Position pos = knuthElement.getPosition();
- LeafPosition leafPos = (LeafPosition) pos.getPosition(depth);
- int index = leafPos.getLeafPos();
- //element could refer to '-1' position, for non-collapsed spaces (?)
- if (index > -1) {
- AreaInfo areaInfo = getAreaInfo(index);
- areaInfo.letterSpaceCount++;
- areaInfo.addToAreaIPD(letterSpaceIPD);
- if (TextLayoutManager.BREAK_CHARS.indexOf(foText.charAt(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,
- auxiliaryPosition, false));
- oldListIterator.add(new KnuthGlue(letterSpaceIPD, auxiliaryPosition, false));
- } else if (letterSpaceIPD.isStiff()) {
- // constant letter space: replace the box
- // give it the unwrapped position of the replaced element
- oldListIterator.set(new KnuthInlineBox(areaInfo.areaIPD.getOpt(),
- 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(letterSpaceIPD.mult(areaInfo.letterSpaceCount),
- auxiliaryPosition, true));
- }
- }
- return oldList;
- }
-
- /** {@inheritDoc} */
- public void hyphenate(Position pos, HyphContext hyphContext) {
- AreaInfo areaInfo = getAreaInfo(((LeafPosition) pos).getLeafPos() + changeOffset);
- int startIndex = areaInfo.startIndex;
- int stopIndex;
- boolean nothingChanged = true;
- Font font = areaInfo.font;
-
- while (startIndex < areaInfo.breakIndex) {
- MinOptMax newIPD = MinOptMax.ZERO;
- boolean hyphenFollows;
-
- stopIndex = startIndex + hyphContext.getNextHyphPoint();
- if (hyphContext.hasMoreHyphPoints() && stopIndex <= areaInfo.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 areaInfo.breakIndex
- hyphenFollows = false;
- stopIndex = areaInfo.breakIndex;
- }
-
- hyphContext.updateOffset(stopIndex - startIndex);
-
- //log.info("Word: " + new String(textArray, startIndex, stopIndex - startIndex));
- for (int i = startIndex; i < stopIndex; i++) {
- char ch = foText.charAt(i);
- newIPD = newIPD.plus(font.getCharWidth(ch));
- //if (i > startIndex) {
- if (i < stopIndex) {
- MinOptMax letterSpaceAdjust = letterSpaceAdjustArray[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
- letterSpaceAdjust = null;
- }
- if (letterSpaceAdjust != null) {
- newIPD = newIPD.plus(letterSpaceAdjust);
- }
- }
- }
-
- // add letter spaces
- boolean isWordEnd
- = (stopIndex == areaInfo.breakIndex)
- && (areaInfo.letterSpaceCount < areaInfo.getWordLength());
- int letterSpaceCount = isWordEnd ? stopIndex - startIndex - 1 : stopIndex - startIndex;
-
- assert letterSpaceCount >= 0;
- newIPD = newIPD.plus(letterSpaceIPD.mult(letterSpaceCount));
-
- if (!(nothingChanged && stopIndex == areaInfo.breakIndex && !hyphenFollows)) {
- // the new AreaInfo object is not equal to the old one
- changeList.add
- ( new PendingChange
- ( new AreaInfo(startIndex, stopIndex, 0,
- letterSpaceCount, newIPD, hyphenFollows,
- false, false, font, -1, null),
- ((LeafPosition) pos).getLeafPos() + changeOffset));
- nothingChanged = false;
- }
- startIndex = stopIndex;
- }
- hasChanged |= !nothingChanged;
- }
-
- /** {@inheritDoc} */
- public boolean applyChanges(final List oldList) {
- return applyChanges(oldList, 0);
- }
-
- /** {@inheritDoc} */
- public boolean applyChanges(final List oldList, int depth) {
-
- // make sure the LM appears unfinished in between this call
- // and the next call to getChangedKnuthElements()
- setFinished(false);
-
- if (oldList.isEmpty()) {
- return false;
- }
-
- // Find the first and last positions in oldList that point to an AreaInfo
- // (i.e. getLeafPos() != -1)
- LeafPosition startPos = null;
- LeafPosition endPos = null;
- ListIterator oldListIter;
- for (oldListIter = oldList.listIterator(); oldListIter.hasNext();) {
- Position pos = ((KnuthElement) oldListIter.next()).getPosition();
- startPos = (LeafPosition) pos.getPosition(depth);
- if (startPos != null && startPos.getLeafPos() != -1) {
- break;
- }
- }
- for (oldListIter = oldList.listIterator(oldList.size()); oldListIter.hasPrevious();) {
- Position pos = ((KnuthElement) oldListIter.previous()).getPosition();
- endPos = (LeafPosition) pos.getPosition(depth);
- if (endPos != null && endPos.getLeafPos() != -1) {
- break;
- }
- }
-
- // set start/end index, taking into account any offset due to
- // changes applied to previous paragraphs
- returnedIndices[0] = (startPos != null ? startPos.getLeafPos() : -1) + changeOffset;
- returnedIndices[1] = (endPos != null ? endPos.getLeafPos() : -1) + changeOffset;
-
- int areaInfosAdded = 0;
- int areaInfosRemoved = 0;
-
- if (!changeList.isEmpty()) {
- int oldIndex = -1;
- int changeIndex;
- PendingChange currChange;
- ListIterator changeListIterator = 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;
- removeAreaInfo(changeIndex);
- }
- addAreaInfo(changeIndex, currChange.areaInfo);
- }
- changeList.clear();
- }
-
- // increase the end index for getChangedKnuthElements()
- returnedIndices[1] += (areaInfosAdded - areaInfosRemoved);
- // increase offset to use for subsequent paragraphs
- changeOffset += (areaInfosAdded - areaInfosRemoved);
-
- return hasChanged;
- }
-
- /** {@inheritDoc} */
- public List getChangedKnuthElements(final List oldList, final int alignment) {
- if (isFinished()) {
- return null;
- }
-
- final LinkedList returnList = new LinkedList();
-
- for (; returnedIndices[0] <= returnedIndices[1]; returnedIndices[0]++) {
- AreaInfo areaInfo = getAreaInfo(returnedIndices[0]);
- if (areaInfo.wordSpaceCount == 0) {
- // areaInfo refers either to a word or a word fragment
- addElementsForAWordFragment(returnList, alignment, areaInfo, returnedIndices[0]);
- } else {
- // areaInfo refers to a space
- addElementsForASpace(returnList, alignment, areaInfo, returnedIndices[0]);
- }
- }
- setFinished(returnedIndices[0] == areaInfos.size() - 1);
- //ElementListObserver.observe(returnList, "text-changed", null);
- return returnList;
- }
-
- /** {@inheritDoc} */
- public String getWordChars(Position pos) {
- int leafValue = ((LeafPosition) pos).getLeafPos() + changeOffset;
- if (leafValue != -1) {
- AreaInfo areaInfo = getAreaInfo(leafValue);
- StringBuffer buffer = new StringBuffer(areaInfo.getWordLength());
- for (int i = areaInfo.startIndex; i < areaInfo.breakIndex; i++) {
- buffer.append(foText.charAt(i));
- }
- return buffer.toString();
- } else {
- return "";
- }
- }
-
- private void addElementsForASpace(List baseList, int alignment, AreaInfo areaInfo,
- int leafValue) {
- LeafPosition mainPosition = new LeafPosition(this, leafValue);
-
- if (!areaInfo.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(makeAuxiliaryZeroWidthBox());
- baseList.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(areaInfo.areaIPD, mainPosition, false));
- } else {
- // the space does not need to stretch or shrink, and must be
- // preserved when starting a line
- baseList.add(new KnuthInlineBox(areaInfo.areaIPD.getOpt(), null, mainPosition,
- true));
- }
- } else {
- if (foText.charAt(areaInfo.startIndex) != CharUtilities.SPACE
- || foText.getWhitespaceTreatment() == Constants.EN_PRESERVE) {
- // a breaking space that needs to be preserved
- baseList
- .addAll(getElementsForBreakingSpace(alignment, areaInfo, auxiliaryPosition, 0,
- mainPosition, areaInfo.areaIPD.getOpt(), true));
- } else {
- // a (possible block) of breaking spaces
- baseList
- .addAll(getElementsForBreakingSpace(alignment, areaInfo, mainPosition,
- areaInfo.areaIPD.getOpt(), auxiliaryPosition, 0, false));
- }
- }
- }
-
- private List getElementsForBreakingSpace(int alignment, AreaInfo areaInfo, Position pos2,
- int p2WidthOffset, Position pos3,
- int p3WidthOffset, boolean skipZeroCheck) {
- List elements = new ArrayList();
-
- 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
- elements.add(new KnuthGlue(lineEndBAP, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
- auxiliaryPosition, false));
- elements.add(makeZeroWidthPenalty(0));
- elements.add(new KnuthGlue(p2WidthOffset - (lineStartBAP + lineEndBAP), -6
- * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, pos2, false));
- elements.add(makeAuxiliaryZeroWidthBox());
- elements.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
- elements.add(new KnuthGlue(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
- KnuthGlue g;
- if (skipZeroCheck || lineStartBAP != 0 || lineEndBAP != 0) {
- g = new KnuthGlue
- (lineEndBAP,
- 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, auxiliaryPosition, false);
- elements.add(g);
- elements.add(makeZeroWidthPenalty(0));
- g = new KnuthGlue
- (p2WidthOffset - (lineStartBAP + lineEndBAP),
- -3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, pos2, false);
- elements.add(g);
- elements.add(makeAuxiliaryZeroWidthBox());
- elements.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
- g = new KnuthGlue(lineStartBAP + p3WidthOffset, 0, 0, pos3, false);
- elements.add(g);
- } else {
- g = new KnuthGlue
- (0,
- 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, auxiliaryPosition, false);
- elements.add(g);
- elements.add(makeZeroWidthPenalty(0));
- g = new KnuthGlue
- (areaInfo.areaIPD.getOpt(),
- -3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, pos2, false);
- elements.add(g);
- }
- break;
-
- case EN_JUSTIFY:
- // justified text:
- // the stretch and shrink depends on the space width
- elements.addAll(getElementsForJustifiedText(areaInfo, pos2, p2WidthOffset, pos3,
- p3WidthOffset, skipZeroCheck, areaInfo.areaIPD.getShrink()));
- break;
-
- default:
- // last line justified, the other lines unjustified:
- // use only the space stretch
- elements.addAll(getElementsForJustifiedText(areaInfo, pos2, p2WidthOffset, pos3,
- p3WidthOffset, skipZeroCheck, 0));
- }
- return elements;
- }
-
- private List getElementsForJustifiedText
- (AreaInfo areaInfo, Position pos2, int p2WidthOffset,
- Position pos3, int p3WidthOffset, boolean skipZeroCheck,
- int shrinkability) {
-
- int stretchability = areaInfo.areaIPD.getStretch();
-
- List elements = new ArrayList();
- if (skipZeroCheck || lineStartBAP != 0 || lineEndBAP != 0) {
- elements.add(new KnuthGlue(lineEndBAP, 0, 0, auxiliaryPosition, false));
- elements.add(makeZeroWidthPenalty(0));
- elements.add(new KnuthGlue(p2WidthOffset - (lineStartBAP + lineEndBAP),
- stretchability, shrinkability, pos2, false));
- elements.add(makeAuxiliaryZeroWidthBox());
- elements.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
- elements.add(new KnuthGlue(lineStartBAP + p3WidthOffset, 0, 0, pos3, false));
- } else {
- elements.add(new KnuthGlue(areaInfo.areaIPD.getOpt(), stretchability, shrinkability,
- pos2, false));
- }
- return elements;
- }
-
- private void addElementsForAWordFragment(List baseList, int alignment, AreaInfo areaInfo,
- int leafValue) {
- 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;
- boolean suppressibleLetterSpace = areaInfo.breakOppAfter && !areaInfo.isHyphenated;
-
- if (letterSpaceIPD.isStiff()) {
- // constant letter spacing
- baseList.add(new KnuthInlineBox(suppressibleLetterSpace
- ? areaInfo.areaIPD.getOpt() - letterSpaceIPD.getOpt()
- : areaInfo.areaIPD.getOpt(),
- alignmentContext, notifyPos(mainPosition), false));
- } else {
- // adjustable letter spacing
- int unsuppressibleLetterSpaces = suppressibleLetterSpace
- ? areaInfo.letterSpaceCount - 1
- : areaInfo.letterSpaceCount;
- baseList.add(new KnuthInlineBox(areaInfo.areaIPD.getOpt()
- - areaInfo.letterSpaceCount * letterSpaceIPD.getOpt(),
- alignmentContext, notifyPos(mainPosition), false));
- baseList.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(letterSpaceIPD.mult(unsuppressibleLetterSpaces),
- auxiliaryPosition, true));
- baseList.add(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 (areaInfo.isHyphenated) {
- MinOptMax widthIfNoBreakOccurs = null;
- if (areaInfo.breakIndex < foText.length()) {
- //Add in kerning in no-break condition
- widthIfNoBreakOccurs = letterSpaceAdjustArray[areaInfo.breakIndex];
- }
- //if (areaInfo.breakIndex)
-
- // the word fragment ends at the end of a syllable:
- // if a break occurs the content width increases,
- // otherwise nothing happens
- addElementsForAHyphen(baseList, alignment, hyphIPD, widthIfNoBreakOccurs,
- areaInfo.breakOppAfter && areaInfo.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
- addElementsForAHyphen(baseList, alignment, 0, letterSpaceIPD, true);
- }
- }
-
- private void addElementsForAHyphen(List baseList, int alignment, int widthIfBreakOccurs,
- MinOptMax widthIfNoBreakOccurs, boolean unflagged) {
-
- if (widthIfNoBreakOccurs == null) {
- widthIfNoBreakOccurs = MinOptMax.ZERO;
- }
-
- switch (alignment) {
- case EN_CENTER:
- // centered text:
- baseList.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(lineEndBAP, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
- auxiliaryPosition, true));
- baseList.add(new KnuthPenalty(hyphIPD, unflagged
- ? TextLayoutManager.SOFT_HYPHEN_PENALTY
- : KnuthPenalty.FLAGGED_PENALTY, !unflagged, auxiliaryPosition, false));
- baseList.add(new KnuthGlue(-(lineEndBAP + lineStartBAP),
- -6 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
- auxiliaryPosition, false));
- baseList.add(makeAuxiliaryZeroWidthBox());
- baseList.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(lineStartBAP, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH,
- 0, auxiliaryPosition, true));
- break;
-
- case EN_START: // fall through
- case EN_END:
- // left- or right-aligned text:
- if (lineStartBAP != 0 || lineEndBAP != 0) {
- baseList.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(lineEndBAP,
- 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
- auxiliaryPosition, false));
- baseList.add(new KnuthPenalty(widthIfBreakOccurs,
- unflagged ? TextLayoutManager.SOFT_HYPHEN_PENALTY
- : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
- auxiliaryPosition, false));
- baseList.add(new KnuthGlue(widthIfNoBreakOccurs.getOpt()
- - (lineStartBAP + lineEndBAP),
- -3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
- auxiliaryPosition, false));
- baseList.add(makeAuxiliaryZeroWidthBox());
- baseList.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(lineStartBAP, 0, 0, auxiliaryPosition, false));
- } else {
- baseList.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(0, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
- auxiliaryPosition, false));
- baseList.add(new KnuthPenalty(widthIfBreakOccurs,
- unflagged ? TextLayoutManager.SOFT_HYPHEN_PENALTY
- : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
- auxiliaryPosition, false));
- baseList.add(new KnuthGlue(widthIfNoBreakOccurs.getOpt(),
- -3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
- auxiliaryPosition, false));
- }
- break;
-
- default:
- // justified text, or last line justified:
- // just a flagged penalty
- if (lineStartBAP != 0 || lineEndBAP != 0) {
- baseList.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(lineEndBAP, 0, 0, auxiliaryPosition, false));
- baseList.add(new KnuthPenalty(widthIfBreakOccurs,
- unflagged ? TextLayoutManager.SOFT_HYPHEN_PENALTY
- : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
- auxiliaryPosition, false));
- // extra elements representing a letter space that is suppressed
- // if a break occurs
- if (widthIfNoBreakOccurs.isNonZero()) {
- baseList.add(new KnuthGlue(widthIfNoBreakOccurs.getOpt()
- - (lineStartBAP + lineEndBAP),
- widthIfNoBreakOccurs.getStretch(),
- widthIfNoBreakOccurs.getShrink(),
- auxiliaryPosition, false));
- } else {
- baseList.add(new KnuthGlue(-(lineStartBAP + lineEndBAP), 0, 0,
- auxiliaryPosition, false));
- }
- baseList.add(makeAuxiliaryZeroWidthBox());
- baseList.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
- baseList.add(new KnuthGlue(lineStartBAP, 0, 0,
- auxiliaryPosition, false));
- } else {
- baseList.add(new KnuthPenalty(widthIfBreakOccurs,
- unflagged ? TextLayoutManager.SOFT_HYPHEN_PENALTY
- : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
- auxiliaryPosition, false));
- // extra elements representing a letter space that is suppressed
- // if a break occurs
- if (widthIfNoBreakOccurs.isNonZero()) {
- baseList.add(new KnuthGlue(widthIfNoBreakOccurs, auxiliaryPosition, false));
- }
- }
- }
-
- }
-
- /** {@inheritDoc} */
- public String toString() {
- return super.toString() + "{"
- + "chars = \'"
- + CharUtilities.toNCRefs ( foText.getCharSequence().toString() )
- + "\'"
- + ", len = " + foText.length()
- + "}";
- }
-
- }
|