You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TextLayoutManager.java 73KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.layoutmgr.inline;
  19. import java.util.ArrayList;
  20. import java.util.Arrays;
  21. import java.util.LinkedList;
  22. import java.util.List;
  23. import java.util.ListIterator;
  24. import org.apache.commons.logging.Log;
  25. import org.apache.commons.logging.LogFactory;
  26. import org.apache.fop.area.Trait;
  27. import org.apache.fop.area.inline.TextArea;
  28. import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
  29. import org.apache.fop.complexscripts.util.CharScript;
  30. import org.apache.fop.fo.Constants;
  31. import org.apache.fop.fo.FOText;
  32. import org.apache.fop.fonts.Font;
  33. import org.apache.fop.fonts.FontSelector;
  34. import org.apache.fop.layoutmgr.InlineKnuthSequence;
  35. import org.apache.fop.layoutmgr.KnuthBox;
  36. import org.apache.fop.layoutmgr.KnuthElement;
  37. import org.apache.fop.layoutmgr.KnuthGlue;
  38. import org.apache.fop.layoutmgr.KnuthPenalty;
  39. import org.apache.fop.layoutmgr.KnuthSequence;
  40. import org.apache.fop.layoutmgr.LayoutContext;
  41. import org.apache.fop.layoutmgr.LeafPosition;
  42. import org.apache.fop.layoutmgr.Position;
  43. import org.apache.fop.layoutmgr.PositionIterator;
  44. import org.apache.fop.layoutmgr.TraitSetter;
  45. import org.apache.fop.text.linebreak.LineBreakStatus;
  46. import org.apache.fop.traits.MinOptMax;
  47. import org.apache.fop.traits.SpaceVal;
  48. import org.apache.fop.util.CharUtilities;
  49. import org.apache.fop.util.ListUtil;
  50. /**
  51. * LayoutManager for text (a sequence of characters) which generates one
  52. * or more inline areas.
  53. */
  54. public class TextLayoutManager extends LeafNodeLayoutManager {
  55. //TODO: remove all final modifiers at local variables
  56. // static final int SOFT_HYPHEN_PENALTY = KnuthPenalty.FLAGGED_PENALTY / 10;
  57. private static final int SOFT_HYPHEN_PENALTY = 1;
  58. /**
  59. * Store information about each potential text area.
  60. * Index of character which ends the area, IPD of area, including
  61. * any word-space and letter-space.
  62. * Number of word-spaces?
  63. */
  64. private class AreaInfo {
  65. private final int startIndex;
  66. private final int breakIndex;
  67. private int wordCharLength;
  68. private final int wordSpaceCount;
  69. private int letterSpaceCount;
  70. private MinOptMax areaIPD;
  71. private final boolean isHyphenated;
  72. private final boolean isSpace;
  73. private boolean breakOppAfter;
  74. private final Font font;
  75. private final int level;
  76. private final int[][] gposAdjustments;
  77. AreaInfo // CSOK: ParameterNumber
  78. (int startIndex, int breakIndex, int wordSpaceCount, int letterSpaceCount,
  79. MinOptMax areaIPD, boolean isHyphenated, boolean isSpace, boolean breakOppAfter,
  80. Font font, int level, int[][] gposAdjustments) {
  81. assert startIndex <= breakIndex;
  82. this.startIndex = startIndex;
  83. this.breakIndex = breakIndex;
  84. this.wordCharLength = -1;
  85. this.wordSpaceCount = wordSpaceCount;
  86. this.letterSpaceCount = letterSpaceCount;
  87. this.areaIPD = areaIPD;
  88. this.isHyphenated = isHyphenated;
  89. this.isSpace = isSpace;
  90. this.breakOppAfter = breakOppAfter;
  91. this.font = font;
  92. this.level = level;
  93. this.gposAdjustments = gposAdjustments;
  94. }
  95. /**
  96. * Obtain number of 'characters' contained in word. If word
  97. * is mapped, then this number may be less than or greater than the
  98. * original length (breakIndex - startIndex). We compute and
  99. * memoize thius length upon first invocation of this method.
  100. */
  101. private int getWordLength() {
  102. if ( wordCharLength == -1 ) {
  103. if ( foText.hasMapping ( startIndex, breakIndex ) ) {
  104. wordCharLength = foText.getMapping ( startIndex, breakIndex ).length();
  105. } else {
  106. assert breakIndex >= startIndex;
  107. wordCharLength = breakIndex - startIndex;
  108. }
  109. }
  110. return wordCharLength;
  111. }
  112. private void addToAreaIPD(MinOptMax idp) {
  113. areaIPD = areaIPD.plus(idp);
  114. }
  115. public String toString() {
  116. return super.toString() + "{"
  117. + "interval = [" + startIndex + "," + breakIndex + "]"
  118. + ", isSpace = " + isSpace
  119. + ", level = " + level
  120. + ", areaIPD = " + areaIPD
  121. + ", letterSpaceCount = " + letterSpaceCount
  122. + ", wordSpaceCount = " + wordSpaceCount
  123. + ", isHyphenated = " + isHyphenated
  124. + ", font = " + font
  125. + "}";
  126. }
  127. }
  128. /**
  129. * this class stores information about changes in vecAreaInfo which are not yet applied
  130. */
  131. private final class PendingChange {
  132. private final AreaInfo areaInfo;
  133. private final int index;
  134. private PendingChange(final AreaInfo areaInfo, final int index) {
  135. this.areaInfo = areaInfo;
  136. this.index = index;
  137. }
  138. }
  139. /**
  140. * logging instance
  141. */
  142. private static final Log LOG = LogFactory.getLog(TextLayoutManager.class);
  143. // Hold all possible breaks for the text in this LM's FO.
  144. private final List areaInfos;
  145. /** Non-space characters on which we can end a line. */
  146. private static final String BREAK_CHARS = "-/";
  147. private final FOText foText;
  148. /**
  149. * Contains an array of widths to adjust for kerning. The first entry can
  150. * be used to influence the start position of the first letter. The entry i+1 defines the
  151. * cursor advancement after the character i. A null entry means no special advancement.
  152. */
  153. private final MinOptMax[] letterSpaceAdjustArray; //size = textArray.length + 1
  154. /** Font used for the space between words. */
  155. private Font spaceFont = null;
  156. /** Start index of next TextArea */
  157. private int nextStart = 0;
  158. /** size of a space character (U+0020) glyph in current font */
  159. private int spaceCharIPD;
  160. private MinOptMax wordSpaceIPD;
  161. private MinOptMax letterSpaceIPD;
  162. /** size of the hyphen character glyph in current font */
  163. private int hyphIPD;
  164. private boolean hasChanged = false;
  165. private int[] returnedIndices = {0, 0};
  166. private int changeOffset = 0;
  167. private int thisStart = 0;
  168. private int tempStart = 0;
  169. private List changeList = new LinkedList();
  170. private AlignmentContext alignmentContext = null;
  171. /**
  172. * The width to be reserved for border and padding at the start of the line.
  173. */
  174. private int lineStartBAP = 0;
  175. /**
  176. * The width to be reserved for border and padding at the end of the line.
  177. */
  178. private int lineEndBAP = 0;
  179. private boolean keepTogether;
  180. private final Position auxiliaryPosition = new LeafPosition(this, -1);
  181. /**
  182. * Create a Text layout manager.
  183. *
  184. * @param node The FOText object to be rendered
  185. */
  186. public TextLayoutManager(FOText node) {
  187. foText = node;
  188. letterSpaceAdjustArray = new MinOptMax[node.length() + 1];
  189. areaInfos = new ArrayList();
  190. }
  191. private KnuthPenalty makeZeroWidthPenalty(int penaltyValue) {
  192. return new KnuthPenalty(0, penaltyValue, false, auxiliaryPosition, true);
  193. }
  194. private KnuthBox makeAuxiliaryZeroWidthBox() {
  195. return new KnuthInlineBox(0, null, notifyPos(new LeafPosition(this, -1)), true);
  196. }
  197. /** {@inheritDoc} */
  198. public void initialize() {
  199. foText.resetBuffer();
  200. spaceFont = FontSelector.selectFontForCharacterInText(' ', foText, this);
  201. // With CID fonts, space isn't necessary currentFontState.width(32)
  202. spaceCharIPD = spaceFont.getCharWidth(' ');
  203. // Use hyphenationChar property
  204. // TODO: Use hyphen based on actual font used!
  205. hyphIPD = foText.getCommonHyphenation().getHyphIPD(spaceFont);
  206. SpaceVal letterSpacing = SpaceVal.makeLetterSpacing(foText.getLetterSpacing());
  207. SpaceVal wordSpacing = SpaceVal.makeWordSpacing(foText.getWordSpacing(), letterSpacing,
  208. spaceFont);
  209. // letter space applies only to consecutive non-space characters,
  210. // while word space applies to space characters;
  211. // i.e. the spaces in the string "A SIMPLE TEST" are:
  212. // A<<ws>>S<ls>I<ls>M<ls>P<ls>L<ls>E<<ws>>T<ls>E<ls>S<ls>T
  213. // there is no letter space after the last character of a word,
  214. // nor after a space character
  215. // NOTE: The above is not quite correct. Read on in XSL 1.0, 7.16.2, letter-spacing
  216. // set letter space and word space dimension;
  217. // the default value "normal" was converted into a MinOptMax value
  218. // in the SpaceVal.makeWordSpacing() method
  219. letterSpaceIPD = letterSpacing.getSpace();
  220. wordSpaceIPD = MinOptMax.getInstance(spaceCharIPD).plus(wordSpacing.getSpace());
  221. keepTogether = foText.getKeepTogether().getWithinLine().getEnum() == Constants.EN_ALWAYS;
  222. }
  223. /**
  224. * Generate and add areas to parent area.
  225. * This can either generate an area for each TextArea and each space, or
  226. * an area containing all text with a parameter controlling the size of
  227. * the word space. The latter is most efficient for PDF generation.
  228. * Set size of each area.
  229. * @param posIter Iterator over Position information returned
  230. * by this LayoutManager.
  231. * @param context LayoutContext for adjustments
  232. */
  233. public void addAreas(final PositionIterator posIter, final LayoutContext context) {
  234. // Add word areas
  235. AreaInfo areaInfo;
  236. int wordSpaceCount = 0;
  237. int letterSpaceCount = 0;
  238. int firstAreaInfoIndex = -1;
  239. int lastAreaInfoIndex = 0;
  240. MinOptMax realWidth = MinOptMax.ZERO;
  241. /* On first area created, add any leading space.
  242. * Calculate word-space stretch value.
  243. */
  244. AreaInfo lastAreaInfo = null;
  245. while (posIter.hasNext()) {
  246. final LeafPosition tbpNext = (LeafPosition) posIter.next();
  247. if (tbpNext == null) {
  248. continue; //Ignore elements without Positions
  249. }
  250. if (tbpNext.getLeafPos() != -1) {
  251. areaInfo = (AreaInfo) areaInfos.get(tbpNext.getLeafPos());
  252. if (lastAreaInfo == null
  253. || ( areaInfo.font != lastAreaInfo.font )
  254. || ( areaInfo.level != lastAreaInfo.level ) ) {
  255. if (lastAreaInfo != null) {
  256. addAreaInfoAreas(lastAreaInfo, wordSpaceCount,
  257. letterSpaceCount, firstAreaInfoIndex,
  258. lastAreaInfoIndex, realWidth, context);
  259. }
  260. firstAreaInfoIndex = tbpNext.getLeafPos();
  261. wordSpaceCount = 0;
  262. letterSpaceCount = 0;
  263. realWidth = MinOptMax.ZERO;
  264. }
  265. wordSpaceCount += areaInfo.wordSpaceCount;
  266. letterSpaceCount += areaInfo.letterSpaceCount;
  267. realWidth = realWidth.plus(areaInfo.areaIPD);
  268. lastAreaInfoIndex = tbpNext.getLeafPos();
  269. lastAreaInfo = areaInfo;
  270. }
  271. }
  272. if (lastAreaInfo != null) {
  273. addAreaInfoAreas(lastAreaInfo, wordSpaceCount, letterSpaceCount, firstAreaInfoIndex,
  274. lastAreaInfoIndex, realWidth, context);
  275. }
  276. }
  277. private void addAreaInfoAreas(AreaInfo areaInfo, int wordSpaceCount, int letterSpaceCount,
  278. int firstAreaInfoIndex, int lastAreaInfoIndex,
  279. MinOptMax realWidth, LayoutContext context) {
  280. // TODO: These two statements (if, for) were like this before my recent
  281. // changes. However, it seems as if they should use the AreaInfo from
  282. // firstAreaInfoIndex.. lastAreaInfoIndex rather than just the last areaInfo.
  283. // This needs to be checked.
  284. int textLength = areaInfo.getWordLength();
  285. if (areaInfo.letterSpaceCount == textLength && !areaInfo.isHyphenated
  286. && context.isLastArea()) {
  287. // the line ends at a character like "/" or "-";
  288. // remove the letter space after the last character
  289. realWidth = realWidth.minus(letterSpaceIPD);
  290. letterSpaceCount--;
  291. }
  292. for (int i = areaInfo.startIndex; i < areaInfo.breakIndex; i++) {
  293. MinOptMax letterSpaceAdjustment = letterSpaceAdjustArray[i + 1];
  294. if (letterSpaceAdjustment != null && letterSpaceAdjustment.isElastic()) {
  295. letterSpaceCount++;
  296. }
  297. }
  298. // add hyphenation character if the last word is hyphenated
  299. if (context.isLastArea() && areaInfo.isHyphenated) {
  300. realWidth = realWidth.plus(hyphIPD);
  301. }
  302. /* Calculate adjustments */
  303. double ipdAdjust = context.getIPDAdjust();
  304. // calculate total difference between real and available width
  305. int difference;
  306. if (ipdAdjust > 0.0) {
  307. difference = (int) (realWidth.getStretch() * ipdAdjust);
  308. } else {
  309. difference = (int) (realWidth.getShrink() * ipdAdjust);
  310. }
  311. // set letter space adjustment
  312. int letterSpaceDim = letterSpaceIPD.getOpt();
  313. if (ipdAdjust > 0.0) {
  314. letterSpaceDim += (int) (letterSpaceIPD.getStretch() * ipdAdjust);
  315. } else {
  316. letterSpaceDim += (int) (letterSpaceIPD.getShrink() * ipdAdjust);
  317. }
  318. int totalAdjust = (letterSpaceDim - letterSpaceIPD.getOpt()) * letterSpaceCount;
  319. // set word space adjustment
  320. int wordSpaceDim = wordSpaceIPD.getOpt();
  321. if (wordSpaceCount > 0) {
  322. wordSpaceDim += (difference - totalAdjust) / wordSpaceCount;
  323. }
  324. totalAdjust += (wordSpaceDim - wordSpaceIPD.getOpt()) * wordSpaceCount;
  325. if (totalAdjust != difference) {
  326. // the applied adjustment is greater or smaller than the needed one
  327. TextLayoutManager.LOG
  328. .trace("TextLM.addAreas: error in word / letter space adjustment = "
  329. + (totalAdjust - difference));
  330. // set totalAdjust = difference, so that the width of the TextArea
  331. // will counterbalance the error and the other inline areas will be
  332. // placed correctly
  333. totalAdjust = difference;
  334. }
  335. TextArea textArea = new TextAreaBuilder(realWidth, totalAdjust, context, firstAreaInfoIndex,
  336. lastAreaInfoIndex, context.isLastArea(), areaInfo.font).build();
  337. // wordSpaceDim is computed in relation to wordSpaceIPD.opt
  338. // but the renderer needs to know the adjustment in relation
  339. // to the size of the space character in the current font;
  340. // moreover, the pdf renderer adds the character spacing even to
  341. // the last character of a word and to space characters: in order
  342. // to avoid this, we must subtract the letter space width twice;
  343. // the renderer will compute the space width as:
  344. // space width =
  345. // = "normal" space width + letterSpaceAdjust + wordSpaceAdjust
  346. // = spaceCharIPD + letterSpaceAdjust +
  347. // + (wordSpaceDim - spaceCharIPD - 2 * letterSpaceAdjust)
  348. // = wordSpaceDim - letterSpaceAdjust
  349. textArea.setTextLetterSpaceAdjust(letterSpaceDim);
  350. textArea.setTextWordSpaceAdjust(wordSpaceDim - spaceCharIPD
  351. - 2 * textArea.getTextLetterSpaceAdjust());
  352. if (context.getIPDAdjust() != 0) {
  353. // add information about space width
  354. textArea.setSpaceDifference(wordSpaceIPD.getOpt() - spaceCharIPD
  355. - 2 * textArea.getTextLetterSpaceAdjust());
  356. }
  357. parentLayoutManager.addChildArea(textArea);
  358. }
  359. private final class TextAreaBuilder {
  360. // constructor initialized state
  361. private final MinOptMax width; // content ipd
  362. private final int adjust; // content ipd adjustment
  363. private final LayoutContext context; // layout context
  364. private final int firstIndex; // index of first AreaInfo
  365. private final int lastIndex; // index of last AreaInfo
  366. private final boolean isLastArea; // true if last inline area in line area
  367. private final Font font; // applicable font
  368. // other, non-constructor state
  369. private TextArea textArea; // text area being constructed
  370. private int blockProgressionDimension; // calculated bpd
  371. private AreaInfo areaInfo; // current area info when iterating over words
  372. private StringBuffer wordChars; // current word's character buffer
  373. private int[] letterSpaceAdjust; // current word's letter space adjustments
  374. private int letterSpaceAdjustIndex; // last written letter space adjustment index
  375. private int[] wordLevels; // current word's bidi levels
  376. private int wordLevelsIndex; // last written bidi level index
  377. private int wordIPD; // accumulated ipd of current word
  378. private int[][] gposAdjustments; // current word's glyph position adjustments
  379. private int gposAdjustmentsIndex; // last written glyph position adjustment index
  380. /**
  381. * Creates a new <code>TextAreaBuilder</code> which itself builds an inline word area. This
  382. * creates a TextArea and sets up the various attributes.
  383. *
  384. * @param width the MinOptMax width of the content
  385. * @param adjust the total ipd adjustment with respect to the optimal width
  386. * @param context the layout context
  387. * @param firstIndex the index of the first AreaInfo used for the TextArea
  388. * @param lastIndex the index of the last AreaInfo used for the TextArea
  389. * @param isLastArea is this TextArea the last in a line?
  390. * @param font Font to be used in this particular TextArea
  391. */
  392. private TextAreaBuilder(MinOptMax width, int adjust, LayoutContext context,
  393. int firstIndex, int lastIndex, boolean isLastArea, Font font) {
  394. this.width = width;
  395. this.adjust = adjust;
  396. this.context = context;
  397. this.firstIndex = firstIndex;
  398. this.lastIndex = lastIndex;
  399. this.isLastArea = isLastArea;
  400. this.font = font;
  401. }
  402. private TextArea build() {
  403. createTextArea();
  404. setInlineProgressionDimension();
  405. calcBlockProgressionDimension();
  406. setBlockProgressionDimension();
  407. setBaselineOffset();
  408. setBlockProgressionOffset();
  409. setText();
  410. TraitSetter.addFontTraits(textArea, font);
  411. textArea.addTrait(Trait.COLOR, foText.getColor());
  412. TraitSetter.addTextDecoration(textArea, foText.getTextDecoration());
  413. if (!context.treatAsArtifact()) {
  414. TraitSetter.addStructureTreeElement(textArea, foText.getStructureTreeElement());
  415. }
  416. return textArea;
  417. }
  418. /**
  419. * Creates an plain <code>TextArea</code> or a justified <code>TextArea</code> with
  420. * additional information.
  421. */
  422. private void createTextArea() {
  423. if (context.getIPDAdjust() == 0.0) {
  424. textArea = new TextArea();
  425. } else {
  426. textArea = new TextArea(width.getStretch(), width.getShrink(),
  427. adjust);
  428. }
  429. }
  430. private void setInlineProgressionDimension() {
  431. textArea.setIPD(width.getOpt() + adjust);
  432. }
  433. private void calcBlockProgressionDimension() {
  434. blockProgressionDimension = font.getAscender() - font.getDescender();
  435. }
  436. private void setBlockProgressionDimension() {
  437. textArea.setBPD(blockProgressionDimension);
  438. }
  439. private void setBaselineOffset() {
  440. textArea.setBaselineOffset(font.getAscender());
  441. }
  442. private void setBlockProgressionOffset() {
  443. if (blockProgressionDimension == alignmentContext.getHeight()) {
  444. textArea.setBlockProgressionOffset(0);
  445. } else {
  446. textArea.setBlockProgressionOffset(alignmentContext.getOffset());
  447. }
  448. }
  449. /**
  450. * Sets the text of the TextArea, split into words and spaces.
  451. */
  452. private void setText() {
  453. int areaInfoIndex = -1;
  454. int wordCharLength = 0;
  455. for (int wordIndex = firstIndex; wordIndex <= lastIndex; wordIndex++) {
  456. areaInfo = getAreaInfo(wordIndex);
  457. if (areaInfo.isSpace) {
  458. addSpaces();
  459. } else {
  460. // areaInfo stores information about a word fragment
  461. if (areaInfoIndex == -1) {
  462. // here starts a new word
  463. areaInfoIndex = wordIndex;
  464. wordCharLength = 0;
  465. }
  466. wordCharLength += areaInfo.getWordLength();
  467. if (isWordEnd(wordIndex)) {
  468. addWord(areaInfoIndex, wordIndex, wordCharLength);
  469. areaInfoIndex = -1;
  470. }
  471. }
  472. }
  473. }
  474. private boolean isWordEnd(int areaInfoIndex) {
  475. return areaInfoIndex == lastIndex || getAreaInfo(areaInfoIndex + 1).isSpace;
  476. }
  477. /**
  478. * Add word with fragments from STARTINDEX to ENDINDEX, where
  479. * total length of (possibly mapped) word is CHARLENGTH.
  480. * A word is composed from one or more word fragments, where each
  481. * fragment corresponds to distinct instance in a sequence of
  482. * area info instances starting at STARTINDEX continuing through (and
  483. * including) ENDINDEX.
  484. * @param startIndex index of first area info of word to add
  485. * @param endIndex index of last area info of word to add
  486. * @param wordLength number of (mapped) characters in word
  487. */
  488. private void addWord(int startIndex, int endIndex, int wordLength) {
  489. int blockProgressionOffset = 0;
  490. boolean gposAdjusted = false;
  491. if (isHyphenated(endIndex)) {
  492. // TODO may be problematic in some I18N contexts [GA]
  493. wordLength++;
  494. }
  495. initWord(wordLength);
  496. // iterate over word's fragments, adding word chars (with bidi
  497. // levels), letter space adjustments, and glyph position adjustments
  498. for (int i = startIndex; i <= endIndex; i++) {
  499. AreaInfo wordAreaInfo = getAreaInfo(i);
  500. addWordChars(wordAreaInfo);
  501. addLetterAdjust(wordAreaInfo);
  502. if ( addGlyphPositionAdjustments(wordAreaInfo) ) {
  503. gposAdjusted = true;
  504. }
  505. }
  506. if (isHyphenated(endIndex)) {
  507. // TODO may be problematic in some I18N contexts [GA]
  508. addHyphenationChar();
  509. }
  510. if ( !gposAdjusted ) {
  511. gposAdjustments = null;
  512. }
  513. textArea.addWord(wordChars.toString(), wordIPD, letterSpaceAdjust,
  514. getNonEmptyLevels(), gposAdjustments, blockProgressionOffset);
  515. }
  516. private int[] getNonEmptyLevels() {
  517. if ( wordLevels != null ) {
  518. assert wordLevelsIndex <= wordLevels.length;
  519. boolean empty = true;
  520. for ( int i = 0, n = wordLevelsIndex; i < n; i++ ) {
  521. if ( wordLevels [ i ] >= 0 ) {
  522. empty = false;
  523. break;
  524. }
  525. }
  526. return empty ? null : wordLevels;
  527. } else {
  528. return null;
  529. }
  530. }
  531. /**
  532. * Fully allocate word character buffer, letter space adjustments
  533. * array, bidi levels array, and glyph position adjustments array.
  534. * based on full word length, including all (possibly mapped) fragments.
  535. * @param wordLength length of word including all (possibly mapped) fragments
  536. */
  537. private void initWord(int wordLength) {
  538. wordChars = new StringBuffer(wordLength);
  539. letterSpaceAdjust = new int[wordLength];
  540. letterSpaceAdjustIndex = 0;
  541. wordLevels = new int[wordLength];
  542. wordLevelsIndex = 0;
  543. Arrays.fill ( wordLevels, -1 );
  544. gposAdjustments = new int[wordLength][4];
  545. gposAdjustmentsIndex = 0;
  546. wordIPD = 0;
  547. }
  548. private boolean isHyphenated(int endIndex) {
  549. return isLastArea && endIndex == lastIndex && areaInfo.isHyphenated;
  550. }
  551. private void addHyphenationChar() {
  552. wordChars.append(foText.getCommonHyphenation().getHyphChar(font));
  553. // [TBD] expand bidi word levels, letter space adjusts, gpos adjusts
  554. // [TBD] [GA] problematic in bidi context... what is level of hyphen?
  555. }
  556. /**
  557. * Given a word area info associated with a word fragment,
  558. * (1) concatenate (possibly mapped) word characters to word character buffer;
  559. * (2) concatenante (possibly mapped) word bidi levels to levels buffer;
  560. * (3) update word's IPD with optimal IPD of fragment.
  561. * @param wordAreaInfo fragment info
  562. */
  563. private void addWordChars(AreaInfo wordAreaInfo) {
  564. int s = wordAreaInfo.startIndex;
  565. int e = wordAreaInfo.breakIndex;
  566. if ( foText.hasMapping ( s, e ) ) {
  567. wordChars.append ( foText.getMapping ( s, e ) );
  568. addWordLevels ( foText.getMappingBidiLevels ( s, e ) );
  569. } else {
  570. for (int i = s; i < e; i++) {
  571. wordChars.append(foText.charAt(i));
  572. }
  573. addWordLevels ( foText.getBidiLevels ( s, e ) );
  574. }
  575. wordIPD += wordAreaInfo.areaIPD.getOpt();
  576. }
  577. /**
  578. * Given a (possibly null) bidi levels array associated with a word fragment,
  579. * concatenante (possibly mapped) word bidi levels to levels buffer.
  580. * @param levels bidi levels array or null
  581. */
  582. private void addWordLevels ( int[] levels ) {
  583. int numLevels = ( levels != null ) ? levels.length : 0;
  584. if ( numLevels > 0 ) {
  585. int need = wordLevelsIndex + numLevels;
  586. if ( need <= wordLevels.length ) {
  587. System.arraycopy ( levels, 0, wordLevels, wordLevelsIndex, numLevels );
  588. } else {
  589. throw new IllegalStateException
  590. ( "word levels array too short: expect at least "
  591. + need + " entries, but has only " + wordLevels.length + " entries" );
  592. }
  593. }
  594. wordLevelsIndex += numLevels;
  595. }
  596. /**
  597. * Given a word area info associated with a word fragment,
  598. * concatenate letter space adjustments for each (possibly mapped) character.
  599. * @param wordAreaInfo fragment info
  600. */
  601. private void addLetterAdjust(AreaInfo wordAreaInfo) {
  602. int letterSpaceCount = wordAreaInfo.letterSpaceCount;
  603. int wordLength = wordAreaInfo.getWordLength();
  604. int taAdjust = textArea.getTextLetterSpaceAdjust();
  605. for ( int i = 0, n = wordLength; i < n; i++ ) {
  606. int j = letterSpaceAdjustIndex + i;
  607. if ( j > 0 ) {
  608. int k = wordAreaInfo.startIndex + i;
  609. MinOptMax adj = ( k < letterSpaceAdjustArray.length )
  610. ? letterSpaceAdjustArray [ k ] : null;
  611. letterSpaceAdjust [ j ] = ( adj == null ) ? 0 : adj.getOpt();
  612. }
  613. if ( letterSpaceCount > 0 ) {
  614. letterSpaceAdjust [ j ] += taAdjust;
  615. letterSpaceCount--;
  616. }
  617. }
  618. letterSpaceAdjustIndex += wordLength;
  619. }
  620. /**
  621. * Given a word area info associated with a word fragment,
  622. * concatenate glyph position adjustments for each (possibly mapped) character.
  623. * @param wordAreaInfo fragment info
  624. * @return true if an adjustment was non-zero
  625. */
  626. private boolean addGlyphPositionAdjustments(AreaInfo wordAreaInfo) {
  627. boolean adjusted = false;
  628. int[][] gpa = wordAreaInfo.gposAdjustments;
  629. int numAdjusts = ( gpa != null ) ? gpa.length : 0;
  630. int wordLength = wordAreaInfo.getWordLength();
  631. if ( numAdjusts > 0 ) {
  632. int need = gposAdjustmentsIndex + numAdjusts;
  633. if ( need <= gposAdjustments.length ) {
  634. for ( int i = 0, n = wordLength, j = 0; i < n; i++ ) {
  635. if ( i < numAdjusts ) {
  636. int[] wpa1 = gposAdjustments [ gposAdjustmentsIndex + i ];
  637. int[] wpa2 = gpa [ j++ ];
  638. for ( int k = 0; k < 4; k++ ) {
  639. int a = wpa2 [ k ];
  640. if ( a != 0 ) {
  641. wpa1 [ k ] += a;
  642. adjusted = true;
  643. }
  644. }
  645. }
  646. }
  647. } else {
  648. throw new IllegalStateException
  649. ( "gpos adjustments array too short: expect at least "
  650. + need + " entries, but has only " + gposAdjustments.length
  651. + " entries" );
  652. }
  653. }
  654. gposAdjustmentsIndex += wordLength;
  655. return adjusted;
  656. }
  657. /**
  658. * The <code>AreaInfo</code> stores information about spaces.
  659. * <p/>
  660. * Add the spaces - except zero-width spaces - to the TextArea.
  661. */
  662. private void addSpaces() {
  663. int blockProgressionOffset = 0;
  664. // [TBD] need to better handling of spaceIPD assignment, for now,
  665. // divide the area info's allocated IPD evenly among the
  666. // non-zero-width space characters
  667. int numZeroWidthSpaces = 0;
  668. for (int i = areaInfo.startIndex; i < areaInfo.breakIndex; i++) {
  669. char spaceChar = foText.charAt(i);
  670. if (CharUtilities.isZeroWidthSpace(spaceChar)) {
  671. numZeroWidthSpaces++;
  672. }
  673. }
  674. int numSpaces = areaInfo.breakIndex - areaInfo.startIndex - numZeroWidthSpaces;
  675. int spaceIPD = areaInfo.areaIPD.getOpt() / ( ( numSpaces > 0 ) ? numSpaces : 1 );
  676. // add space area children, one for each non-zero-width space character
  677. for (int i = areaInfo.startIndex; i < areaInfo.breakIndex; i++) {
  678. char spaceChar = foText.charAt(i);
  679. int level = foText.bidiLevelAt(i);
  680. if (!CharUtilities.isZeroWidthSpace(spaceChar)) {
  681. textArea.addSpace
  682. ( spaceChar, spaceIPD,
  683. CharUtilities.isAdjustableSpace(spaceChar),
  684. blockProgressionOffset, level );
  685. }
  686. }
  687. }
  688. }
  689. private void addAreaInfo ( AreaInfo ai ) {
  690. addAreaInfo ( areaInfos.size(), ai );
  691. }
  692. private void addAreaInfo ( int index, AreaInfo ai ) {
  693. areaInfos.add ( index, ai );
  694. }
  695. private void removeAreaInfo ( int index ) {
  696. areaInfos.remove ( index );
  697. }
  698. private AreaInfo getAreaInfo(int index) {
  699. return (AreaInfo) areaInfos.get(index);
  700. }
  701. private void addToLetterAdjust(int index, int width) {
  702. if (letterSpaceAdjustArray[index] == null) {
  703. letterSpaceAdjustArray[index] = MinOptMax.getInstance(width);
  704. } else {
  705. letterSpaceAdjustArray[index] = letterSpaceAdjustArray[index].plus(width);
  706. }
  707. }
  708. /**
  709. * Indicates whether a character is a space in terms of this layout manager.
  710. * @param ch the character
  711. * @return true if it's a space
  712. */
  713. private static boolean isSpace(final char ch) {
  714. return ch == CharUtilities.SPACE
  715. || CharUtilities.isNonBreakableSpace(ch)
  716. || CharUtilities.isFixedWidthSpace(ch);
  717. }
  718. /** {@inheritDoc} */
  719. public List getNextKnuthElements(final LayoutContext context, final int alignment) {
  720. lineStartBAP = context.getLineStartBorderAndPaddingWidth();
  721. lineEndBAP = context.getLineEndBorderAndPaddingWidth();
  722. alignmentContext = context.getAlignmentContext();
  723. final List returnList = new LinkedList();
  724. KnuthSequence sequence = new InlineKnuthSequence();
  725. AreaInfo areaInfo = null;
  726. AreaInfo prevAreaInfo = null;
  727. returnList.add(sequence);
  728. if (LOG.isDebugEnabled()) {
  729. LOG.debug ( "GK: [" + nextStart + "," + foText.length() + "]" );
  730. }
  731. LineBreakStatus lineBreakStatus = new LineBreakStatus();
  732. thisStart = nextStart;
  733. boolean inWord = false;
  734. boolean inWhitespace = false;
  735. char ch = 0;
  736. int level = -1;
  737. int prevLevel = -1;
  738. while (nextStart < foText.length()) {
  739. ch = foText.charAt(nextStart);
  740. level = foText.bidiLevelAt(nextStart);
  741. boolean breakOpportunity = false;
  742. byte breakAction = keepTogether
  743. ? LineBreakStatus.PROHIBITED_BREAK
  744. : lineBreakStatus.nextChar(ch);
  745. switch (breakAction) {
  746. case LineBreakStatus.COMBINING_PROHIBITED_BREAK:
  747. case LineBreakStatus.PROHIBITED_BREAK:
  748. break;
  749. case LineBreakStatus.EXPLICIT_BREAK:
  750. break;
  751. case LineBreakStatus.COMBINING_INDIRECT_BREAK:
  752. case LineBreakStatus.DIRECT_BREAK:
  753. case LineBreakStatus.INDIRECT_BREAK:
  754. breakOpportunity = true;
  755. break;
  756. default:
  757. TextLayoutManager.LOG.error("Unexpected breakAction: " + breakAction);
  758. }
  759. if (LOG.isDebugEnabled()) {
  760. LOG.debug ( "GK: {"
  761. + " index = " + nextStart
  762. + ", char = " + CharUtilities.charToNCRef ( ch )
  763. + ", level = " + level
  764. + ", levelPrev = " + prevLevel
  765. + ", inWord = " + inWord
  766. + ", inSpace = " + inWhitespace
  767. + "}" );
  768. }
  769. if (inWord) {
  770. if ( breakOpportunity
  771. || TextLayoutManager.isSpace(ch)
  772. || CharUtilities.isExplicitBreak(ch)
  773. || ( ( prevLevel != -1 ) && ( level != prevLevel ) ) ) {
  774. // this.foText.charAt(lastIndex) == CharUtilities.SOFT_HYPHEN
  775. prevAreaInfo = processWord(alignment, sequence, prevAreaInfo, ch,
  776. breakOpportunity, true, prevLevel);
  777. }
  778. } else if (inWhitespace) {
  779. if (ch != CharUtilities.SPACE || breakOpportunity) {
  780. prevAreaInfo = processWhitespace(alignment, sequence,
  781. breakOpportunity, prevLevel);
  782. }
  783. } else {
  784. if (areaInfo != null) {
  785. prevAreaInfo = areaInfo;
  786. processLeftoverAreaInfo(alignment, sequence, areaInfo,
  787. ch == CharUtilities.SPACE || breakOpportunity);
  788. areaInfo = null;
  789. }
  790. if (breakAction == LineBreakStatus.EXPLICIT_BREAK) {
  791. sequence = processLinebreak(returnList, sequence);
  792. }
  793. }
  794. if (ch == CharUtilities.SPACE
  795. && foText.getWhitespaceTreatment() == Constants.EN_PRESERVE
  796. || ch == CharUtilities.NBSPACE) {
  797. // preserved space or non-breaking space:
  798. // create the AreaInfo object
  799. areaInfo = new AreaInfo(nextStart, nextStart + 1, 1, 0, wordSpaceIPD, false, true,
  800. breakOpportunity, spaceFont, level, null);
  801. thisStart = nextStart + 1;
  802. } else if (CharUtilities.isFixedWidthSpace(ch) || CharUtilities.isZeroWidthSpace(ch)) {
  803. // create the AreaInfo object
  804. Font font = FontSelector.selectFontForCharacterInText(ch, foText, this);
  805. MinOptMax ipd = MinOptMax.getInstance(font.getCharWidth(ch));
  806. areaInfo = new AreaInfo(nextStart, nextStart + 1, 0, 0, ipd, false, true,
  807. breakOpportunity, font, level, null);
  808. thisStart = nextStart + 1;
  809. } else if (CharUtilities.isExplicitBreak(ch)) {
  810. //mandatory break-character: only advance index
  811. thisStart = nextStart + 1;
  812. }
  813. inWord = !TextLayoutManager.isSpace(ch) && !CharUtilities.isExplicitBreak(ch);
  814. inWhitespace = ch == CharUtilities.SPACE
  815. && foText.getWhitespaceTreatment() != Constants.EN_PRESERVE;
  816. prevLevel = level;
  817. nextStart++;
  818. }
  819. // Process any last elements
  820. if (inWord) {
  821. processWord(alignment, sequence, prevAreaInfo, ch, false, false, prevLevel);
  822. } else if (inWhitespace) {
  823. processWhitespace(alignment, sequence, !keepTogether, prevLevel);
  824. } else if (areaInfo != null) {
  825. processLeftoverAreaInfo(alignment, sequence, areaInfo,
  826. ch == CharUtilities.ZERO_WIDTH_SPACE);
  827. } else if (CharUtilities.isExplicitBreak(ch)) {
  828. this.processLinebreak(returnList, sequence);
  829. }
  830. if (((List) ListUtil.getLast(returnList)).isEmpty()) {
  831. //Remove an empty sequence because of a trailing newline
  832. ListUtil.removeLast(returnList);
  833. }
  834. setFinished(true);
  835. if (returnList.isEmpty()) {
  836. return null;
  837. } else {
  838. return returnList;
  839. }
  840. }
  841. private KnuthSequence processLinebreak(List returnList, KnuthSequence sequence) {
  842. if (lineEndBAP != 0) {
  843. sequence.add(new KnuthGlue(lineEndBAP, 0, 0, auxiliaryPosition, true));
  844. }
  845. sequence.endSequence();
  846. sequence = new InlineKnuthSequence();
  847. returnList.add(sequence);
  848. return sequence;
  849. }
  850. private void processLeftoverAreaInfo(int alignment,
  851. KnuthSequence sequence, AreaInfo areaInfo,
  852. boolean breakOpportunityAfter) {
  853. addAreaInfo(areaInfo);
  854. areaInfo.breakOppAfter = breakOpportunityAfter;
  855. addElementsForASpace(sequence, alignment, areaInfo, areaInfos.size() - 1);
  856. }
  857. private AreaInfo processWhitespace(final int alignment,
  858. final KnuthSequence sequence, final boolean breakOpportunity, int level) {
  859. if (LOG.isDebugEnabled()) {
  860. LOG.debug ( "PS: [" + thisStart + "," + nextStart + "]" );
  861. }
  862. // End of whitespace
  863. // create the AreaInfo object
  864. assert nextStart >= thisStart;
  865. AreaInfo areaInfo = new AreaInfo
  866. ( thisStart, nextStart, nextStart - thisStart, 0,
  867. wordSpaceIPD.mult(nextStart - thisStart),
  868. false, true, breakOpportunity, spaceFont, level, null );
  869. addAreaInfo(areaInfo);
  870. // create the elements
  871. addElementsForASpace(sequence, alignment, areaInfo, areaInfos.size() - 1);
  872. thisStart = nextStart;
  873. return areaInfo;
  874. }
  875. private AreaInfo processWordMapping
  876. ( int lastIndex, final Font font, AreaInfo prevAreaInfo, final char breakOpportunityChar,
  877. final boolean endsWithHyphen, int level ) {
  878. int s = this.thisStart; // start index of word in FOText character buffer
  879. int e = lastIndex; // end index of word in FOText character buffer
  880. int nLS = 0; // # of letter spaces
  881. String script = foText.getScript();
  882. String language = foText.getLanguage();
  883. if (LOG.isDebugEnabled()) {
  884. LOG.debug ( "PW: [" + thisStart + "," + lastIndex + "]: {"
  885. + " +M"
  886. + ", level = " + level
  887. + " }" );
  888. }
  889. // 1. extract unmapped character sequence
  890. CharSequence ics = foText.subSequence ( s, e );
  891. // 2. if script is not specified (by FO property) or it is specified as 'auto',
  892. // then compute dominant script
  893. if ( ( script == null ) || "auto".equals(script) ) {
  894. script = CharScript.scriptTagFromCode ( CharScript.dominantScript ( ics ) );
  895. }
  896. if ( ( language == null ) || "none".equals(language) ) {
  897. language = "dflt";
  898. }
  899. // 3. perform mapping of chars to glyphs ... to glyphs ... to chars
  900. CharSequence mcs = font.performSubstitution ( ics, script, language );
  901. // 4. compute glyph position adjustments on (substituted) characters
  902. int[][] gpa;
  903. if ( font.performsPositioning() ) {
  904. // handle GPOS adjustments
  905. gpa = font.performPositioning ( mcs, script, language );
  906. } else if ( font.hasKerning() ) {
  907. // handle standard (non-GPOS) kerning adjustments
  908. gpa = getKerningAdjustments ( mcs, font );
  909. } else {
  910. gpa = null;
  911. }
  912. // 5. reorder combining marks so that they precede (within the mapped char sequence) the
  913. // base to which they are applied; N.B. position adjustments (gpa) are reordered in place
  914. mcs = font.reorderCombiningMarks ( mcs, gpa, script, language );
  915. // 6. if mapped sequence differs from input sequence, then memoize mapped sequence
  916. if ( !CharUtilities.isSameSequence ( mcs, ics ) ) {
  917. foText.addMapping ( s, e, mcs );
  918. }
  919. // 7. compute word ipd based on final position adjustments
  920. MinOptMax ipd = MinOptMax.ZERO;
  921. for ( int i = 0, n = mcs.length(); i < n; i++ ) {
  922. int c = mcs.charAt ( i );
  923. // TODO !BMP
  924. int w = font.getCharWidth ( c );
  925. if ( w < 0 ) {
  926. w = 0;
  927. }
  928. if ( gpa != null ) {
  929. w += gpa [ i ] [ GlyphPositioningTable.Value.IDX_X_ADVANCE ];
  930. }
  931. ipd = ipd.plus ( w );
  932. }
  933. // [TBD] - handle letter spacing
  934. return new AreaInfo
  935. ( s, e, 0, nLS, ipd, endsWithHyphen, false,
  936. breakOpportunityChar != 0, font, level, gpa );
  937. }
  938. /**
  939. * Given a mapped character sequence MCS, obtain glyph position adjustments
  940. * from the font's kerning data.
  941. * @param mcs mapped character sequence
  942. * @param font applicable font
  943. * @return glyph position adjustments (or null if no kerning)
  944. */
  945. private int[][] getKerningAdjustments ( CharSequence mcs, final Font font ) {
  946. int nc = mcs.length();
  947. // extract kerning array
  948. int[] ka = new int [ nc ]; // kerning array
  949. for ( int i = 0, n = nc, cPrev = -1; i < n; i++ ) {
  950. int c = mcs.charAt ( i );
  951. // TODO !BMP
  952. if ( cPrev >= 0 ) {
  953. ka[i] = font.getKernValue ( cPrev, c );
  954. }
  955. cPrev = c;
  956. }
  957. // was there a non-zero kerning?
  958. boolean hasKerning = false;
  959. for ( int i = 0, n = nc; i < n; i++ ) {
  960. if ( ka[i] != 0 ) {
  961. hasKerning = true;
  962. break;
  963. }
  964. }
  965. // if non-zero kerning, then create and return glyph position adjustment array
  966. if ( hasKerning ) {
  967. int[][] gpa = new int [ nc ] [ 4 ];
  968. for ( int i = 0, n = nc; i < n; i++ ) {
  969. if ( i > 0 ) {
  970. gpa [ i - 1 ] [ GlyphPositioningTable.Value.IDX_X_ADVANCE ] = ka [ i ];
  971. }
  972. }
  973. return gpa;
  974. } else {
  975. return null;
  976. }
  977. }
  978. private AreaInfo processWordNoMapping(int lastIndex, final Font font, AreaInfo prevAreaInfo,
  979. final char breakOpportunityChar, final boolean endsWithHyphen, int level) {
  980. boolean kerning = font.hasKerning();
  981. MinOptMax wordIPD = MinOptMax.ZERO;
  982. if (LOG.isDebugEnabled()) {
  983. LOG.debug ( "PW: [" + thisStart + "," + lastIndex + "]: {"
  984. + " -M"
  985. + ", level = " + level
  986. + " }" );
  987. }
  988. for (int i = thisStart; i < lastIndex; i++) {
  989. char currentChar = foText.charAt(i);
  990. //character width
  991. int charWidth = font.getCharWidth(currentChar);
  992. wordIPD = wordIPD.plus(charWidth);
  993. //kerning
  994. if (kerning) {
  995. int kern = 0;
  996. if (i > thisStart) {
  997. char previousChar = foText.charAt(i - 1);
  998. kern = font.getKernValue(previousChar, currentChar);
  999. } else if (prevAreaInfo != null
  1000. && !prevAreaInfo.isSpace && prevAreaInfo.breakIndex > 0) {
  1001. char previousChar = foText.charAt(prevAreaInfo.breakIndex - 1);
  1002. kern = font.getKernValue(previousChar, currentChar);
  1003. }
  1004. if (kern != 0) {
  1005. addToLetterAdjust(i, kern);
  1006. wordIPD = wordIPD.plus(kern);
  1007. }
  1008. }
  1009. }
  1010. if (kerning
  1011. && ( breakOpportunityChar != 0 )
  1012. && !TextLayoutManager.isSpace(breakOpportunityChar)
  1013. && lastIndex > 0
  1014. && endsWithHyphen) {
  1015. int kern = font.getKernValue(foText.charAt(lastIndex - 1), breakOpportunityChar);
  1016. if (kern != 0) {
  1017. addToLetterAdjust(lastIndex, kern);
  1018. //TODO: add kern to wordIPD?
  1019. }
  1020. }
  1021. // shy+chars at start of word: wordLength == 0 && breakOpportunity
  1022. // shy only characters in word: wordLength == 0 && !breakOpportunity
  1023. int wordLength = lastIndex - thisStart;
  1024. int letterSpaces = 0;
  1025. if (wordLength != 0) {
  1026. letterSpaces = wordLength - 1;
  1027. // if there is a break opportunity and the next one (break character)
  1028. // is not a space, it could be used as a line end;
  1029. // add one more letter space, in case other text follows
  1030. if (( breakOpportunityChar != 0 ) && !TextLayoutManager.isSpace(breakOpportunityChar)) {
  1031. letterSpaces++;
  1032. }
  1033. }
  1034. assert letterSpaces >= 0;
  1035. wordIPD = wordIPD.plus(letterSpaceIPD.mult(letterSpaces));
  1036. // create and return the AreaInfo object
  1037. return new AreaInfo(thisStart, lastIndex, 0,
  1038. letterSpaces, wordIPD,
  1039. endsWithHyphen,
  1040. false, breakOpportunityChar != 0, font, level, null);
  1041. }
  1042. private AreaInfo processWord(final int alignment, final KnuthSequence sequence,
  1043. AreaInfo prevAreaInfo, final char ch, final boolean breakOpportunity,
  1044. final boolean checkEndsWithHyphen, int level) {
  1045. //Word boundary found, process widths and kerning
  1046. int lastIndex = nextStart;
  1047. while (lastIndex > 0 && foText.charAt(lastIndex - 1) == CharUtilities.SOFT_HYPHEN) {
  1048. lastIndex--;
  1049. }
  1050. final boolean endsWithHyphen = checkEndsWithHyphen
  1051. && foText.charAt(lastIndex) == CharUtilities.SOFT_HYPHEN;
  1052. Font font = FontSelector.selectFontForCharactersInText
  1053. ( foText, thisStart, lastIndex, foText, this );
  1054. AreaInfo areaInfo;
  1055. if ( font.performsSubstitution() || font.performsPositioning() ) {
  1056. areaInfo = processWordMapping
  1057. ( lastIndex, font, prevAreaInfo, breakOpportunity ? ch : 0, endsWithHyphen, level );
  1058. } else {
  1059. areaInfo = processWordNoMapping
  1060. ( lastIndex, font, prevAreaInfo, breakOpportunity ? ch : 0, endsWithHyphen, level );
  1061. }
  1062. prevAreaInfo = areaInfo;
  1063. addAreaInfo(areaInfo);
  1064. tempStart = nextStart;
  1065. //add the elements
  1066. addElementsForAWordFragment(sequence, alignment, areaInfo, areaInfos.size() - 1);
  1067. thisStart = nextStart;
  1068. return prevAreaInfo;
  1069. }
  1070. /** {@inheritDoc} */
  1071. public List addALetterSpaceTo(List oldList) {
  1072. return addALetterSpaceTo(oldList, 0);
  1073. }
  1074. /** {@inheritDoc} */
  1075. public List addALetterSpaceTo(final List oldList, int depth) {
  1076. // old list contains only a box, or the sequence: box penalty glue box;
  1077. // look at the Position stored in the first element in oldList
  1078. // which is always a box
  1079. ListIterator oldListIterator = oldList.listIterator();
  1080. KnuthElement knuthElement = (KnuthElement) oldListIterator.next();
  1081. Position pos = knuthElement.getPosition();
  1082. LeafPosition leafPos = (LeafPosition) pos.getPosition(depth);
  1083. int index = leafPos.getLeafPos();
  1084. //element could refer to '-1' position, for non-collapsed spaces (?)
  1085. if (index > -1) {
  1086. AreaInfo areaInfo = getAreaInfo(index);
  1087. areaInfo.letterSpaceCount++;
  1088. areaInfo.addToAreaIPD(letterSpaceIPD);
  1089. if (TextLayoutManager.BREAK_CHARS.indexOf(foText.charAt(tempStart - 1)) >= 0) {
  1090. // the last character could be used as a line break
  1091. // append new elements to oldList
  1092. oldListIterator = oldList.listIterator(oldList.size());
  1093. oldListIterator.add(new KnuthPenalty(0, KnuthPenalty.FLAGGED_PENALTY, true,
  1094. auxiliaryPosition, false));
  1095. oldListIterator.add(new KnuthGlue(letterSpaceIPD, auxiliaryPosition, false));
  1096. } else if (letterSpaceIPD.isStiff()) {
  1097. // constant letter space: replace the box
  1098. // give it the unwrapped position of the replaced element
  1099. oldListIterator.set(new KnuthInlineBox(areaInfo.areaIPD.getOpt(),
  1100. alignmentContext, pos, false));
  1101. } else {
  1102. // adjustable letter space: replace the glue
  1103. oldListIterator.next(); // this would return the penalty element
  1104. oldListIterator.next(); // this would return the glue element
  1105. oldListIterator.set(new KnuthGlue(letterSpaceIPD.mult(areaInfo.letterSpaceCount),
  1106. auxiliaryPosition, true));
  1107. }
  1108. }
  1109. return oldList;
  1110. }
  1111. /** {@inheritDoc} */
  1112. public void hyphenate(Position pos, HyphContext hyphContext) {
  1113. AreaInfo areaInfo = getAreaInfo(((LeafPosition) pos).getLeafPos() + changeOffset);
  1114. int startIndex = areaInfo.startIndex;
  1115. int stopIndex;
  1116. boolean nothingChanged = true;
  1117. Font font = areaInfo.font;
  1118. while (startIndex < areaInfo.breakIndex) {
  1119. MinOptMax newIPD = MinOptMax.ZERO;
  1120. boolean hyphenFollows;
  1121. stopIndex = startIndex + hyphContext.getNextHyphPoint();
  1122. if (hyphContext.hasMoreHyphPoints() && stopIndex <= areaInfo.breakIndex) {
  1123. // stopIndex is the index of the first character
  1124. // after a hyphenation point
  1125. hyphenFollows = true;
  1126. } else {
  1127. // there are no more hyphenation points,
  1128. // or the next one is after areaInfo.breakIndex
  1129. hyphenFollows = false;
  1130. stopIndex = areaInfo.breakIndex;
  1131. }
  1132. hyphContext.updateOffset(stopIndex - startIndex);
  1133. //log.info("Word: " + new String(textArray, startIndex, stopIndex - startIndex));
  1134. for (int i = startIndex; i < stopIndex; i++) {
  1135. char ch = foText.charAt(i);
  1136. newIPD = newIPD.plus(font.getCharWidth(ch));
  1137. //if (i > startIndex) {
  1138. if (i < stopIndex) {
  1139. MinOptMax letterSpaceAdjust = letterSpaceAdjustArray[i + 1];
  1140. if (i == stopIndex - 1 && hyphenFollows) {
  1141. //the letter adjust here needs to be handled further down during
  1142. //element generation because it depends on hyph/no-hyph condition
  1143. letterSpaceAdjust = null;
  1144. }
  1145. if (letterSpaceAdjust != null) {
  1146. newIPD = newIPD.plus(letterSpaceAdjust);
  1147. }
  1148. }
  1149. }
  1150. // add letter spaces
  1151. boolean isWordEnd
  1152. = (stopIndex == areaInfo.breakIndex)
  1153. && (areaInfo.letterSpaceCount < areaInfo.getWordLength());
  1154. int letterSpaceCount = isWordEnd ? stopIndex - startIndex - 1 : stopIndex - startIndex;
  1155. assert letterSpaceCount >= 0;
  1156. newIPD = newIPD.plus(letterSpaceIPD.mult(letterSpaceCount));
  1157. if (!(nothingChanged && stopIndex == areaInfo.breakIndex && !hyphenFollows)) {
  1158. // the new AreaInfo object is not equal to the old one
  1159. changeList.add
  1160. ( new PendingChange
  1161. ( new AreaInfo(startIndex, stopIndex, 0,
  1162. letterSpaceCount, newIPD, hyphenFollows,
  1163. false, false, font, -1, null),
  1164. ((LeafPosition) pos).getLeafPos() + changeOffset));
  1165. nothingChanged = false;
  1166. }
  1167. startIndex = stopIndex;
  1168. }
  1169. hasChanged |= !nothingChanged;
  1170. }
  1171. /** {@inheritDoc} */
  1172. public boolean applyChanges(final List oldList) {
  1173. return applyChanges(oldList, 0);
  1174. }
  1175. /** {@inheritDoc} */
  1176. public boolean applyChanges(final List oldList, int depth) {
  1177. // make sure the LM appears unfinished in between this call
  1178. // and the next call to getChangedKnuthElements()
  1179. setFinished(false);
  1180. if (oldList.isEmpty()) {
  1181. return false;
  1182. }
  1183. // Find the first and last positions in oldList that point to an AreaInfo
  1184. // (i.e. getLeafPos() != -1)
  1185. LeafPosition startPos = null;
  1186. LeafPosition endPos = null;
  1187. ListIterator oldListIter;
  1188. for (oldListIter = oldList.listIterator(); oldListIter.hasNext();) {
  1189. Position pos = ((KnuthElement) oldListIter.next()).getPosition();
  1190. startPos = (LeafPosition) pos.getPosition(depth);
  1191. if (startPos != null && startPos.getLeafPos() != -1) {
  1192. break;
  1193. }
  1194. }
  1195. for (oldListIter = oldList.listIterator(oldList.size()); oldListIter.hasPrevious();) {
  1196. Position pos = ((KnuthElement) oldListIter.previous()).getPosition();
  1197. endPos = (LeafPosition) pos.getPosition(depth);
  1198. if (endPos != null && endPos.getLeafPos() != -1) {
  1199. break;
  1200. }
  1201. }
  1202. // set start/end index, taking into account any offset due to
  1203. // changes applied to previous paragraphs
  1204. returnedIndices[0] = (startPos != null ? startPos.getLeafPos() : -1) + changeOffset;
  1205. returnedIndices[1] = (endPos != null ? endPos.getLeafPos() : -1) + changeOffset;
  1206. int areaInfosAdded = 0;
  1207. int areaInfosRemoved = 0;
  1208. if (!changeList.isEmpty()) {
  1209. int oldIndex = -1;
  1210. int changeIndex;
  1211. PendingChange currChange;
  1212. ListIterator changeListIterator = changeList.listIterator();
  1213. while (changeListIterator.hasNext()) {
  1214. currChange = (PendingChange) changeListIterator.next();
  1215. if (currChange.index == oldIndex) {
  1216. areaInfosAdded++;
  1217. changeIndex = currChange.index + areaInfosAdded - areaInfosRemoved;
  1218. } else {
  1219. areaInfosRemoved++;
  1220. areaInfosAdded++;
  1221. oldIndex = currChange.index;
  1222. changeIndex = currChange.index + areaInfosAdded - areaInfosRemoved;
  1223. removeAreaInfo(changeIndex);
  1224. }
  1225. addAreaInfo(changeIndex, currChange.areaInfo);
  1226. }
  1227. changeList.clear();
  1228. }
  1229. // increase the end index for getChangedKnuthElements()
  1230. returnedIndices[1] += (areaInfosAdded - areaInfosRemoved);
  1231. // increase offset to use for subsequent paragraphs
  1232. changeOffset += (areaInfosAdded - areaInfosRemoved);
  1233. return hasChanged;
  1234. }
  1235. /** {@inheritDoc} */
  1236. public List getChangedKnuthElements(final List oldList, final int alignment) {
  1237. if (isFinished()) {
  1238. return null;
  1239. }
  1240. final LinkedList returnList = new LinkedList();
  1241. for (; returnedIndices[0] <= returnedIndices[1]; returnedIndices[0]++) {
  1242. AreaInfo areaInfo = getAreaInfo(returnedIndices[0]);
  1243. if (areaInfo.wordSpaceCount == 0) {
  1244. // areaInfo refers either to a word or a word fragment
  1245. addElementsForAWordFragment(returnList, alignment, areaInfo, returnedIndices[0]);
  1246. } else {
  1247. // areaInfo refers to a space
  1248. addElementsForASpace(returnList, alignment, areaInfo, returnedIndices[0]);
  1249. }
  1250. }
  1251. setFinished(returnedIndices[0] == areaInfos.size() - 1);
  1252. //ElementListObserver.observe(returnList, "text-changed", null);
  1253. return returnList;
  1254. }
  1255. /** {@inheritDoc} */
  1256. public String getWordChars(Position pos) {
  1257. int leafValue = ((LeafPosition) pos).getLeafPos() + changeOffset;
  1258. if (leafValue != -1) {
  1259. AreaInfo areaInfo = getAreaInfo(leafValue);
  1260. StringBuffer buffer = new StringBuffer(areaInfo.getWordLength());
  1261. for (int i = areaInfo.startIndex; i < areaInfo.breakIndex; i++) {
  1262. buffer.append(foText.charAt(i));
  1263. }
  1264. return buffer.toString();
  1265. } else {
  1266. return "";
  1267. }
  1268. }
  1269. private void addElementsForASpace(List baseList, int alignment, AreaInfo areaInfo,
  1270. int leafValue) {
  1271. LeafPosition mainPosition = new LeafPosition(this, leafValue);
  1272. if (!areaInfo.breakOppAfter) {
  1273. // a non-breaking space
  1274. if (alignment == Constants.EN_JUSTIFY) {
  1275. // the space can stretch and shrink, and must be preserved
  1276. // when starting a line
  1277. baseList.add(makeAuxiliaryZeroWidthBox());
  1278. baseList.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
  1279. baseList.add(new KnuthGlue(areaInfo.areaIPD, mainPosition, false));
  1280. } else {
  1281. // the space does not need to stretch or shrink, and must be
  1282. // preserved when starting a line
  1283. baseList.add(new KnuthInlineBox(areaInfo.areaIPD.getOpt(), null, mainPosition,
  1284. true));
  1285. }
  1286. } else {
  1287. if (foText.charAt(areaInfo.startIndex) != CharUtilities.SPACE
  1288. || foText.getWhitespaceTreatment() == Constants.EN_PRESERVE) {
  1289. // a breaking space that needs to be preserved
  1290. baseList
  1291. .addAll(getElementsForBreakingSpace(alignment, areaInfo, auxiliaryPosition, 0,
  1292. mainPosition, areaInfo.areaIPD.getOpt(), true));
  1293. } else {
  1294. // a (possible block) of breaking spaces
  1295. baseList
  1296. .addAll(getElementsForBreakingSpace(alignment, areaInfo, mainPosition,
  1297. areaInfo.areaIPD.getOpt(), auxiliaryPosition, 0, false));
  1298. }
  1299. }
  1300. }
  1301. private List getElementsForBreakingSpace(int alignment, AreaInfo areaInfo, Position pos2,
  1302. int p2WidthOffset, Position pos3,
  1303. int p3WidthOffset, boolean skipZeroCheck) {
  1304. List elements = new ArrayList();
  1305. switch (alignment) {
  1306. case EN_CENTER:
  1307. // centered text:
  1308. // if the second element is chosen as a line break these elements
  1309. // add a constant amount of stretch at the end of a line and at the
  1310. // beginning of the next one, otherwise they don't add any stretch
  1311. elements.add(new KnuthGlue(lineEndBAP, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
  1312. auxiliaryPosition, false));
  1313. elements.add(makeZeroWidthPenalty(0));
  1314. elements.add(new KnuthGlue(p2WidthOffset - (lineStartBAP + lineEndBAP), -6
  1315. * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, pos2, false));
  1316. elements.add(makeAuxiliaryZeroWidthBox());
  1317. elements.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
  1318. elements.add(new KnuthGlue(lineStartBAP + p3WidthOffset,
  1319. 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, pos3, false));
  1320. break;
  1321. case EN_START: // fall through
  1322. case EN_END:
  1323. // left- or right-aligned text:
  1324. // if the second element is chosen as a line break these elements
  1325. // add a constant amount of stretch at the end of a line, otherwise
  1326. // they don't add any stretch
  1327. KnuthGlue g;
  1328. if (skipZeroCheck || lineStartBAP != 0 || lineEndBAP != 0) {
  1329. g = new KnuthGlue
  1330. (lineEndBAP,
  1331. 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, auxiliaryPosition, false);
  1332. elements.add(g);
  1333. elements.add(makeZeroWidthPenalty(0));
  1334. g = new KnuthGlue
  1335. (p2WidthOffset - (lineStartBAP + lineEndBAP),
  1336. -3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, pos2, false);
  1337. elements.add(g);
  1338. elements.add(makeAuxiliaryZeroWidthBox());
  1339. elements.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
  1340. g = new KnuthGlue(lineStartBAP + p3WidthOffset, 0, 0, pos3, false);
  1341. elements.add(g);
  1342. } else {
  1343. g = new KnuthGlue
  1344. (0,
  1345. 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, auxiliaryPosition, false);
  1346. elements.add(g);
  1347. elements.add(makeZeroWidthPenalty(0));
  1348. g = new KnuthGlue
  1349. (areaInfo.areaIPD.getOpt(),
  1350. -3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, pos2, false);
  1351. elements.add(g);
  1352. }
  1353. break;
  1354. case EN_JUSTIFY:
  1355. // justified text:
  1356. // the stretch and shrink depends on the space width
  1357. elements.addAll(getElementsForJustifiedText(areaInfo, pos2, p2WidthOffset, pos3,
  1358. p3WidthOffset, skipZeroCheck, areaInfo.areaIPD.getShrink()));
  1359. break;
  1360. default:
  1361. // last line justified, the other lines unjustified:
  1362. // use only the space stretch
  1363. elements.addAll(getElementsForJustifiedText(areaInfo, pos2, p2WidthOffset, pos3,
  1364. p3WidthOffset, skipZeroCheck, 0));
  1365. }
  1366. return elements;
  1367. }
  1368. private List getElementsForJustifiedText
  1369. (AreaInfo areaInfo, Position pos2, int p2WidthOffset,
  1370. Position pos3, int p3WidthOffset, boolean skipZeroCheck,
  1371. int shrinkability) {
  1372. int stretchability = areaInfo.areaIPD.getStretch();
  1373. List elements = new ArrayList();
  1374. if (skipZeroCheck || lineStartBAP != 0 || lineEndBAP != 0) {
  1375. elements.add(new KnuthGlue(lineEndBAP, 0, 0, auxiliaryPosition, false));
  1376. elements.add(makeZeroWidthPenalty(0));
  1377. elements.add(new KnuthGlue(p2WidthOffset - (lineStartBAP + lineEndBAP),
  1378. stretchability, shrinkability, pos2, false));
  1379. elements.add(makeAuxiliaryZeroWidthBox());
  1380. elements.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
  1381. elements.add(new KnuthGlue(lineStartBAP + p3WidthOffset, 0, 0, pos3, false));
  1382. } else {
  1383. elements.add(new KnuthGlue(areaInfo.areaIPD.getOpt(), stretchability, shrinkability,
  1384. pos2, false));
  1385. }
  1386. return elements;
  1387. }
  1388. private void addElementsForAWordFragment(List baseList, int alignment, AreaInfo areaInfo,
  1389. int leafValue) {
  1390. LeafPosition mainPosition = new LeafPosition(this, leafValue);
  1391. // if the last character of the word fragment is '-' or '/',
  1392. // the fragment could end a line; in this case, it loses one
  1393. // of its letter spaces;
  1394. boolean suppressibleLetterSpace = areaInfo.breakOppAfter && !areaInfo.isHyphenated;
  1395. if (letterSpaceIPD.isStiff()) {
  1396. // constant letter spacing
  1397. baseList.add(new KnuthInlineBox(suppressibleLetterSpace
  1398. ? areaInfo.areaIPD.getOpt() - letterSpaceIPD.getOpt()
  1399. : areaInfo.areaIPD.getOpt(),
  1400. alignmentContext, notifyPos(mainPosition), false));
  1401. } else {
  1402. // adjustable letter spacing
  1403. int unsuppressibleLetterSpaces = suppressibleLetterSpace
  1404. ? areaInfo.letterSpaceCount - 1
  1405. : areaInfo.letterSpaceCount;
  1406. baseList.add(new KnuthInlineBox(areaInfo.areaIPD.getOpt()
  1407. - areaInfo.letterSpaceCount * letterSpaceIPD.getOpt(),
  1408. alignmentContext, notifyPos(mainPosition), false));
  1409. baseList.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
  1410. baseList.add(new KnuthGlue(letterSpaceIPD.mult(unsuppressibleLetterSpaces),
  1411. auxiliaryPosition, true));
  1412. baseList.add(makeAuxiliaryZeroWidthBox());
  1413. }
  1414. // extra-elements if the word fragment is the end of a syllable,
  1415. // or it ends with a character that can be used as a line break
  1416. if (areaInfo.isHyphenated) {
  1417. MinOptMax widthIfNoBreakOccurs = null;
  1418. if (areaInfo.breakIndex < foText.length()) {
  1419. //Add in kerning in no-break condition
  1420. widthIfNoBreakOccurs = letterSpaceAdjustArray[areaInfo.breakIndex];
  1421. }
  1422. //if (areaInfo.breakIndex)
  1423. // the word fragment ends at the end of a syllable:
  1424. // if a break occurs the content width increases,
  1425. // otherwise nothing happens
  1426. addElementsForAHyphen(baseList, alignment, hyphIPD, widthIfNoBreakOccurs,
  1427. areaInfo.breakOppAfter && areaInfo.isHyphenated);
  1428. } else if (suppressibleLetterSpace) {
  1429. // the word fragment ends with a character that acts as a hyphen
  1430. // if a break occurs the width does not increase,
  1431. // otherwise there is one more letter space
  1432. addElementsForAHyphen(baseList, alignment, 0, letterSpaceIPD, true);
  1433. }
  1434. }
  1435. private void addElementsForAHyphen(List baseList, int alignment, int widthIfBreakOccurs,
  1436. MinOptMax widthIfNoBreakOccurs, boolean unflagged) {
  1437. if (widthIfNoBreakOccurs == null) {
  1438. widthIfNoBreakOccurs = MinOptMax.ZERO;
  1439. }
  1440. switch (alignment) {
  1441. case EN_CENTER:
  1442. // centered text:
  1443. baseList.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
  1444. baseList.add(new KnuthGlue(lineEndBAP, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
  1445. auxiliaryPosition, true));
  1446. baseList.add(new KnuthPenalty(hyphIPD, unflagged
  1447. ? TextLayoutManager.SOFT_HYPHEN_PENALTY
  1448. : KnuthPenalty.FLAGGED_PENALTY, !unflagged, auxiliaryPosition, false));
  1449. baseList.add(new KnuthGlue(-(lineEndBAP + lineStartBAP),
  1450. -6 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
  1451. auxiliaryPosition, false));
  1452. baseList.add(makeAuxiliaryZeroWidthBox());
  1453. baseList.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
  1454. baseList.add(new KnuthGlue(lineStartBAP, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH,
  1455. 0, auxiliaryPosition, true));
  1456. break;
  1457. case EN_START: // fall through
  1458. case EN_END:
  1459. // left- or right-aligned text:
  1460. if (lineStartBAP != 0 || lineEndBAP != 0) {
  1461. baseList.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
  1462. baseList.add(new KnuthGlue(lineEndBAP,
  1463. 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
  1464. auxiliaryPosition, false));
  1465. baseList.add(new KnuthPenalty(widthIfBreakOccurs,
  1466. unflagged ? TextLayoutManager.SOFT_HYPHEN_PENALTY
  1467. : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
  1468. auxiliaryPosition, false));
  1469. baseList.add(new KnuthGlue(widthIfNoBreakOccurs.getOpt()
  1470. - (lineStartBAP + lineEndBAP),
  1471. -3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
  1472. auxiliaryPosition, false));
  1473. baseList.add(makeAuxiliaryZeroWidthBox());
  1474. baseList.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
  1475. baseList.add(new KnuthGlue(lineStartBAP, 0, 0, auxiliaryPosition, false));
  1476. } else {
  1477. baseList.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
  1478. baseList.add(new KnuthGlue(0, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
  1479. auxiliaryPosition, false));
  1480. baseList.add(new KnuthPenalty(widthIfBreakOccurs,
  1481. unflagged ? TextLayoutManager.SOFT_HYPHEN_PENALTY
  1482. : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
  1483. auxiliaryPosition, false));
  1484. baseList.add(new KnuthGlue(widthIfNoBreakOccurs.getOpt(),
  1485. -3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
  1486. auxiliaryPosition, false));
  1487. }
  1488. break;
  1489. default:
  1490. // justified text, or last line justified:
  1491. // just a flagged penalty
  1492. if (lineStartBAP != 0 || lineEndBAP != 0) {
  1493. baseList.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
  1494. baseList.add(new KnuthGlue(lineEndBAP, 0, 0, auxiliaryPosition, false));
  1495. baseList.add(new KnuthPenalty(widthIfBreakOccurs,
  1496. unflagged ? TextLayoutManager.SOFT_HYPHEN_PENALTY
  1497. : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
  1498. auxiliaryPosition, false));
  1499. // extra elements representing a letter space that is suppressed
  1500. // if a break occurs
  1501. if (widthIfNoBreakOccurs.isNonZero()) {
  1502. baseList.add(new KnuthGlue(widthIfNoBreakOccurs.getOpt()
  1503. - (lineStartBAP + lineEndBAP),
  1504. widthIfNoBreakOccurs.getStretch(),
  1505. widthIfNoBreakOccurs.getShrink(),
  1506. auxiliaryPosition, false));
  1507. } else {
  1508. baseList.add(new KnuthGlue(-(lineStartBAP + lineEndBAP), 0, 0,
  1509. auxiliaryPosition, false));
  1510. }
  1511. baseList.add(makeAuxiliaryZeroWidthBox());
  1512. baseList.add(makeZeroWidthPenalty(KnuthElement.INFINITE));
  1513. baseList.add(new KnuthGlue(lineStartBAP, 0, 0,
  1514. auxiliaryPosition, false));
  1515. } else {
  1516. baseList.add(new KnuthPenalty(widthIfBreakOccurs,
  1517. unflagged ? TextLayoutManager.SOFT_HYPHEN_PENALTY
  1518. : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
  1519. auxiliaryPosition, false));
  1520. // extra elements representing a letter space that is suppressed
  1521. // if a break occurs
  1522. if (widthIfNoBreakOccurs.isNonZero()) {
  1523. baseList.add(new KnuthGlue(widthIfNoBreakOccurs, auxiliaryPosition, false));
  1524. }
  1525. }
  1526. }
  1527. }
  1528. /** {@inheritDoc} */
  1529. public String toString() {
  1530. return super.toString() + "{"
  1531. + "chars = \'"
  1532. + CharUtilities.toNCRefs ( foText.getCharSequence().toString() )
  1533. + "\'"
  1534. + ", len = " + foText.length()
  1535. + "}";
  1536. }
  1537. }