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 58KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314
  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.LinkedList;
  20. import java.util.List;
  21. import java.util.ListIterator;
  22. import org.apache.commons.logging.Log;
  23. import org.apache.commons.logging.LogFactory;
  24. import org.apache.fop.area.Trait;
  25. import org.apache.fop.area.inline.TextArea;
  26. import org.apache.fop.fo.Constants;
  27. import org.apache.fop.fo.FOText;
  28. import org.apache.fop.fo.FObj;
  29. import org.apache.fop.fo.properties.StructurePointerPropertySet;
  30. import org.apache.fop.fonts.Font;
  31. import org.apache.fop.fonts.FontSelector;
  32. import org.apache.fop.layoutmgr.InlineKnuthSequence;
  33. import org.apache.fop.layoutmgr.KnuthBox;
  34. import org.apache.fop.layoutmgr.KnuthElement;
  35. import org.apache.fop.layoutmgr.KnuthGlue;
  36. import org.apache.fop.layoutmgr.KnuthPenalty;
  37. import org.apache.fop.layoutmgr.KnuthSequence;
  38. import org.apache.fop.layoutmgr.LayoutContext;
  39. import org.apache.fop.layoutmgr.LeafPosition;
  40. import org.apache.fop.layoutmgr.Position;
  41. import org.apache.fop.layoutmgr.PositionIterator;
  42. import org.apache.fop.layoutmgr.TraitSetter;
  43. import org.apache.fop.text.linebreak.LineBreakStatus;
  44. import org.apache.fop.traits.MinOptMax;
  45. import org.apache.fop.traits.SpaceVal;
  46. import org.apache.fop.util.CharUtilities;
  47. import org.apache.fop.util.ListUtil;
  48. /**
  49. * LayoutManager for text (a sequence of characters) which generates one
  50. * or more inline areas.
  51. */
  52. public class TextLayoutManager extends LeafNodeLayoutManager {
  53. //TODO: remove all final modifiers at local variables
  54. /**
  55. * Store information about each potential text area.
  56. * Index of character which ends the area, IPD of area, including
  57. * any word-space and letter-space.
  58. * Number of word-spaces?
  59. */
  60. private class AreaInfo {
  61. private final int startIndex;
  62. private final int breakIndex;
  63. private final int wordSpaceCount;
  64. private int letterSpaceCount;
  65. private final MinOptMax areaIPD;
  66. private final boolean isHyphenated;
  67. private final boolean isSpace;
  68. private boolean breakOppAfter;
  69. private final Font font;
  70. AreaInfo(final int startIndex,
  71. final int breakIndex,
  72. final int wordSpaceCount,
  73. final int letterSpaceCount,
  74. final MinOptMax areaIPD,
  75. final boolean isHyphenated,
  76. final boolean isSpace,
  77. final boolean breakOppAfter,
  78. final Font font) {
  79. this.startIndex = startIndex;
  80. this.breakIndex = breakIndex;
  81. this.wordSpaceCount = wordSpaceCount;
  82. this.letterSpaceCount = letterSpaceCount;
  83. this.areaIPD = areaIPD;
  84. this.isHyphenated = isHyphenated;
  85. this.isSpace = isSpace;
  86. this.breakOppAfter = breakOppAfter;
  87. this.font = font;
  88. }
  89. public String toString() {
  90. return "[ lscnt=" + this.letterSpaceCount
  91. + ", wscnt=" + this.wordSpaceCount
  92. + ", ipd=" + this.areaIPD.toString()
  93. + ", sidx=" + this.startIndex
  94. + ", bidx=" + this.breakIndex
  95. + ", hyph=" + this.isHyphenated
  96. + ", space=" + this.isSpace
  97. + ", font=" + this.font
  98. + "]";
  99. }
  100. }
  101. // this class stores information about changes in vecAreaInfo
  102. // which are not yet applied
  103. private final class PendingChange {
  104. private final AreaInfo ai;
  105. private final int index;
  106. private PendingChange(final AreaInfo ai, final int index) {
  107. this.ai = ai;
  108. this.index = index;
  109. }
  110. }
  111. /**
  112. * logging instance
  113. */
  114. private static final Log LOG = LogFactory.getLog(TextLayoutManager.class);
  115. // Hold all possible breaks for the text in this LM's FO.
  116. private final List vecAreaInfo;
  117. /** Non-space characters on which we can end a line. */
  118. private static final String BREAK_CHARS = "-/";
  119. /** Used to reduce instantiation of MinOptMax with zero length. Do not modify! */
  120. private static final MinOptMax ZERO_MINOPTMAX = new MinOptMax(0);
  121. private final FOText foText;
  122. /**
  123. * Contains an array of widths to adjust for kerning. The first entry can
  124. * be used to influence the start position of the first letter. The entry i+1 defines the
  125. * cursor advancement after the character i. A null entry means no special advancement.
  126. */
  127. private final MinOptMax[] letterAdjustArray; //size = textArray.length + 1
  128. /** Font used for the space between words. */
  129. private Font spaceFont = null;
  130. /** Start index of next TextArea */
  131. private int nextStart = 0;
  132. /** size of a space character (U+0020) glyph in current font */
  133. private int spaceCharIPD;
  134. private MinOptMax wordSpaceIPD;
  135. private MinOptMax letterSpaceIPD;
  136. /** size of the hyphen character glyph in current font */
  137. private int hyphIPD;
  138. /** 1/1 of word-spacing value */
  139. private SpaceVal ws;
  140. private boolean hasChanged = false;
  141. private int returnedIndex = 0;
  142. private int thisStart = 0;
  143. private int tempStart = 0;
  144. private List changeList = null;
  145. private AlignmentContext alignmentContext = null;
  146. private int lineStartBAP = 0;
  147. private int lineEndBAP = 0;
  148. private boolean keepTogether;
  149. private final Position auxiliaryPosition = new LeafPosition(this, -1);
  150. /**
  151. * Create a Text layout manager.
  152. *
  153. * @param node The FOText object to be rendered
  154. */
  155. public TextLayoutManager(final FOText node) {
  156. super();
  157. this.foText = node;
  158. this.letterAdjustArray = new MinOptMax[node.length() + 1];
  159. this.vecAreaInfo = new java.util.ArrayList();
  160. }
  161. private KnuthPenalty makeZeroWidthPenalty(final int penaltyValue) {
  162. return new KnuthPenalty(
  163. 0,
  164. penaltyValue,
  165. false,
  166. this.auxiliaryPosition,
  167. true);
  168. }
  169. private KnuthBox makeAuxiliaryZeroWidthBox() {
  170. return new KnuthInlineBox(
  171. 0,
  172. null,
  173. this.notifyPos(new LeafPosition(this, -1)),
  174. true);
  175. }
  176. /** {@inheritDoc} */
  177. public void initialize() {
  178. this.foText.resetBuffer();
  179. this.spaceFont = FontSelector.selectFontForCharacterInText(' ', this.foText, this);
  180. // With CID fonts, space isn't neccesary currentFontState.width(32)
  181. this.spaceCharIPD = this.spaceFont.getCharWidth(' ');
  182. // Use hyphenationChar property
  183. // TODO: Use hyphen based on actual font used!
  184. this.hyphIPD = this.foText.getCommonHyphenation().getHyphIPD(this.spaceFont);
  185. final SpaceVal ls = SpaceVal.makeLetterSpacing(this.foText.getLetterSpacing());
  186. this.ws = SpaceVal.makeWordSpacing(this.foText.getWordSpacing(), ls, this.spaceFont);
  187. // letter space applies only to consecutive non-space characters,
  188. // while word space applies to space characters;
  189. // i.e. the spaces in the string "A SIMPLE TEST" are:
  190. // A<<ws>>S<ls>I<ls>M<ls>P<ls>L<ls>E<<ws>>T<ls>E<ls>S<ls>T
  191. // there is no letter space after the last character of a word,
  192. // nor after a space character
  193. // NOTE: The above is not quite correct. Read on in XSL 1.0, 7.16.2, letter-spacing
  194. // set letter space and word space dimension;
  195. // the default value "normal" was converted into a MinOptMax value
  196. // in the SpaceVal.makeWordSpacing() method
  197. this.letterSpaceIPD = ls.getSpace();
  198. this.wordSpaceIPD = MinOptMax.add(new MinOptMax(this.spaceCharIPD), this.ws.getSpace());
  199. this.keepTogether = this.foText.getKeepTogether().getWithinLine()
  200. .getEnum() == Constants.EN_ALWAYS;
  201. }
  202. /**
  203. * Generate and add areas to parent area.
  204. * This can either generate an area for each TextArea and each space, or
  205. * an area containing all text with a parameter controlling the size of
  206. * the word space. The latter is most efficient for PDF generation.
  207. * Set size of each area.
  208. * @param posIter Iterator over Position information returned
  209. * by this LayoutManager.
  210. * @param context LayoutContext for adjustments
  211. */
  212. public void addAreas(final PositionIterator posIter, final LayoutContext context) {
  213. // Add word areas
  214. AreaInfo ai;
  215. int wordSpaceCount = 0;
  216. int letterSpaceCount = 0;
  217. int firstAreaInfoIndex = -1;
  218. int lastAreaInfoIndex = 0;
  219. MinOptMax realWidth = new MinOptMax(0);
  220. /* On first area created, add any leading space.
  221. * Calculate word-space stretch value.
  222. */
  223. AreaInfo lastAi = null;
  224. while (posIter.hasNext()) {
  225. final LeafPosition tbpNext = (LeafPosition) posIter.next();
  226. if (tbpNext == null) {
  227. continue; //Ignore elements without Positions
  228. }
  229. if (tbpNext.getLeafPos() != -1) {
  230. ai = (AreaInfo) this.vecAreaInfo.get(tbpNext.getLeafPos());
  231. if (lastAi == null || ai.font != lastAi.font) {
  232. if (lastAi != null) {
  233. this.addAreaInfoAreas(lastAi, wordSpaceCount,
  234. letterSpaceCount, firstAreaInfoIndex,
  235. lastAreaInfoIndex, realWidth, context);
  236. }
  237. firstAreaInfoIndex = tbpNext.getLeafPos();
  238. wordSpaceCount = 0;
  239. letterSpaceCount = 0;
  240. realWidth = new MinOptMax(0);
  241. }
  242. wordSpaceCount += ai.wordSpaceCount;
  243. letterSpaceCount += ai.letterSpaceCount;
  244. realWidth.add(ai.areaIPD);
  245. lastAreaInfoIndex = tbpNext.getLeafPos();
  246. lastAi = ai;
  247. }
  248. }
  249. if (lastAi != null) {
  250. this.addAreaInfoAreas(lastAi, wordSpaceCount, letterSpaceCount,
  251. firstAreaInfoIndex, lastAreaInfoIndex, realWidth, context);
  252. }
  253. }
  254. private void addAreaInfoAreas(final AreaInfo ai, final int wordSpaceCount,
  255. int letterSpaceCount, final int firstAreaInfoIndex,
  256. final int lastAreaInfoIndex, final MinOptMax realWidth, final LayoutContext context) {
  257. // TODO: These two statements (if, for) were like this before my recent
  258. // changes. However, it seems as if they should use the AreaInfo from
  259. // firstAreaInfoIndex.. lastAreaInfoIndex rather than just the last ai.
  260. // This needs to be checked.
  261. final int textLength = ai.breakIndex - ai.startIndex;
  262. if (ai.letterSpaceCount == textLength && !ai.isHyphenated
  263. && context.isLastArea()) {
  264. // the line ends at a character like "/" or "-";
  265. // remove the letter space after the last character
  266. realWidth.add(MinOptMax.multiply(this.letterSpaceIPD, -1));
  267. letterSpaceCount--;
  268. }
  269. for (int i = ai.startIndex; i < ai.breakIndex; i++) {
  270. final MinOptMax ladj = this.letterAdjustArray[i + 1];
  271. if (ladj != null && ladj.isElastic()) {
  272. letterSpaceCount++;
  273. }
  274. }
  275. // add hyphenation character if the last word is hyphenated
  276. if (context.isLastArea() && ai.isHyphenated) {
  277. realWidth.add(new MinOptMax(this.hyphIPD));
  278. }
  279. // Calculate adjustments
  280. int difference = 0;
  281. int totalAdjust = 0;
  282. int wordSpaceDim = this.wordSpaceIPD.opt;
  283. int letterSpaceDim = this.letterSpaceIPD.opt;
  284. final double ipdAdjust = context.getIPDAdjust();
  285. // calculate total difference between real and available width
  286. if (ipdAdjust > 0.0) {
  287. difference = (int) ((realWidth.max - realWidth.opt)
  288. * ipdAdjust);
  289. } else {
  290. difference = (int) ((realWidth.opt - realWidth.min)
  291. * ipdAdjust);
  292. }
  293. // set letter space adjustment
  294. if (ipdAdjust > 0.0) {
  295. letterSpaceDim
  296. += (int) ((this.letterSpaceIPD.max - this.letterSpaceIPD.opt)
  297. * ipdAdjust);
  298. } else {
  299. letterSpaceDim
  300. += (int) ((this.letterSpaceIPD.opt - this.letterSpaceIPD.min)
  301. * ipdAdjust);
  302. }
  303. totalAdjust += (letterSpaceDim - this.letterSpaceIPD.opt) * letterSpaceCount;
  304. // set word space adjustment
  305. //
  306. if (wordSpaceCount > 0) {
  307. wordSpaceDim += (difference - totalAdjust) / wordSpaceCount;
  308. }
  309. totalAdjust += (wordSpaceDim - this.wordSpaceIPD.opt) * wordSpaceCount;
  310. if (totalAdjust != difference) {
  311. // the applied adjustment is greater or smaller than the needed one
  312. TextLayoutManager.LOG
  313. .trace("TextLM.addAreas: error in word / letter space adjustment = "
  314. + (totalAdjust - difference));
  315. // set totalAdjust = difference, so that the width of the TextArea
  316. // will counterbalance the error and the other inline areas will be
  317. // placed correctly
  318. totalAdjust = difference;
  319. }
  320. final TextArea t = this.createTextArea(realWidth, totalAdjust, context,
  321. this.wordSpaceIPD.opt - this.spaceCharIPD, firstAreaInfoIndex,
  322. lastAreaInfoIndex, context.isLastArea(), ai.font);
  323. // wordSpaceDim is computed in relation to wordSpaceIPD.opt
  324. // but the renderer needs to know the adjustment in relation
  325. // to the size of the space character in the current font;
  326. // moreover, the pdf renderer adds the character spacing even to
  327. // the last character of a word and to space characters: in order
  328. // to avoid this, we must subtract the letter space width twice;
  329. // the renderer will compute the space width as:
  330. // space width =
  331. // = "normal" space width + letterSpaceAdjust + wordSpaceAdjust
  332. // = spaceCharIPD + letterSpaceAdjust +
  333. // + (wordSpaceDim - spaceCharIPD - 2 * letterSpaceAdjust)
  334. // = wordSpaceDim - letterSpaceAdjust
  335. t.setTextLetterSpaceAdjust(letterSpaceDim);
  336. t.setTextWordSpaceAdjust(wordSpaceDim - this.spaceCharIPD
  337. - 2 * t.getTextLetterSpaceAdjust());
  338. if (context.getIPDAdjust() != 0) {
  339. // add information about space width
  340. t.setSpaceDifference(this.wordSpaceIPD.opt - this.spaceCharIPD
  341. - 2 * t.getTextLetterSpaceAdjust());
  342. }
  343. this.parentLM.addChildArea(t);
  344. }
  345. /**
  346. * Create an inline word area.
  347. * This creates a TextArea and sets up the various attributes.
  348. *
  349. * @param width the MinOptMax width of the content
  350. * @param adjust the total ipd adjustment with respect to the optimal width
  351. * @param context the layout context
  352. * @param spaceDiff unused
  353. * @param firstIndex the index of the first AreaInfo used for the TextArea
  354. * @param lastIndex the index of the last AreaInfo used for the TextArea
  355. * @param isLastArea is this TextArea the last in a line?
  356. * @param font Font to be used in this particular TextArea
  357. * @return the new text area
  358. */
  359. protected TextArea createTextArea(final MinOptMax width, final int adjust,
  360. final LayoutContext context, final int spaceDiff, final int firstIndex,
  361. final int lastIndex, final boolean isLastArea, final Font font) {
  362. TextArea textArea;
  363. if (context.getIPDAdjust() == 0.0) {
  364. // create just a TextArea
  365. textArea = new TextArea();
  366. } else {
  367. // justified area: create a TextArea with extra info
  368. // about potential adjustments
  369. textArea = new TextArea(width.max - width.opt,
  370. width.opt - width.min,
  371. adjust);
  372. }
  373. textArea.setIPD(width.opt + adjust);
  374. textArea.setBPD(font.getAscender() - font.getDescender());
  375. textArea.setBaselineOffset(font.getAscender());
  376. if (textArea.getBPD() == this.alignmentContext.getHeight()) {
  377. textArea.setOffset(0);
  378. } else {
  379. textArea.setOffset(this.alignmentContext.getOffset());
  380. }
  381. // set the text of the TextArea, split into words and spaces
  382. int wordStartIndex = -1;
  383. AreaInfo areaInfo;
  384. int len = 0;
  385. for (int i = firstIndex; i <= lastIndex; i++) {
  386. areaInfo = (AreaInfo) this.vecAreaInfo.get(i);
  387. if (areaInfo.isSpace) {
  388. // areaInfo stores information about spaces
  389. // add the spaces - except zero-width spaces - to the TextArea
  390. for (int j = areaInfo.startIndex; j < areaInfo.breakIndex; j++) {
  391. final char spaceChar = this.foText.charAt(j);
  392. if (!CharUtilities.isZeroWidthSpace(spaceChar)) {
  393. textArea.addSpace(spaceChar, 0,
  394. CharUtilities.isAdjustableSpace(spaceChar));
  395. }
  396. }
  397. } else {
  398. // areaInfo stores information about a word fragment
  399. if (wordStartIndex == -1) {
  400. // here starts a new word
  401. wordStartIndex = i;
  402. len = 0;
  403. }
  404. len += areaInfo.breakIndex - areaInfo.startIndex;
  405. if (i == lastIndex || ((AreaInfo) this.vecAreaInfo.get(i + 1)).isSpace) {
  406. // here ends a new word
  407. // add a word to the TextArea
  408. if (isLastArea
  409. && i == lastIndex
  410. && areaInfo.isHyphenated) {
  411. len++;
  412. }
  413. final StringBuffer wordChars = new StringBuffer(len);
  414. final int[] letterAdjust = new int[len];
  415. int letter = 0;
  416. for (int j = wordStartIndex; j <= i; j++) {
  417. final AreaInfo ai = (AreaInfo) this.vecAreaInfo.get(j);
  418. int lsCount = ai.letterSpaceCount;
  419. /* TODO: in Java 5, StringBuffer has an append() variant
  420. * for CharSequence, so the below iteration can be replaced
  421. * by:
  422. * wordChars.append(this.foText, ai.startIndex,
  423. * ai.breakIndex - ai.startIndex);
  424. */
  425. for (int ci = ai.startIndex; ci < ai.breakIndex; ci++) {
  426. wordChars.append(this.foText.charAt(ci));
  427. }
  428. for (int k = 0; k < ai.breakIndex - ai.startIndex; k++) {
  429. final MinOptMax adj = this.letterAdjustArray[ai.startIndex + k];
  430. if (letter > 0) {
  431. letterAdjust[letter] = adj == null ? 0
  432. : adj.opt;
  433. }
  434. if (lsCount > 0) {
  435. letterAdjust[letter] += textArea.getTextLetterSpaceAdjust();
  436. lsCount--;
  437. }
  438. letter++;
  439. }
  440. }
  441. // String wordChars = new String(textArray, wordStartIndex, len);
  442. if (isLastArea
  443. && i == lastIndex
  444. && areaInfo.isHyphenated) {
  445. // add the hyphenation character
  446. wordChars.append(this.foText.getCommonHyphenation().getHyphChar(font));
  447. }
  448. textArea.addWord(wordChars.toString(), 0, letterAdjust);
  449. wordStartIndex = -1;
  450. }
  451. }
  452. }
  453. TraitSetter.addFontTraits(textArea, font);
  454. textArea.addTrait(Trait.COLOR, this.foText.getColor());
  455. TraitSetter.addPtr(textArea, getPtr()); // used for accessibility
  456. TraitSetter.addTextDecoration(textArea, this.foText.getTextDecoration());
  457. return textArea;
  458. }
  459. /**
  460. * used for accessibility
  461. * @return ptr of fobj
  462. */
  463. private String getPtr() {
  464. FObj fobj = this.parentLM.getFObj();
  465. if (fobj instanceof StructurePointerPropertySet) {
  466. return (((StructurePointerPropertySet) fobj).getPtr());
  467. } else {
  468. //No structure pointer applicable
  469. return null;
  470. }
  471. }
  472. private void addToLetterAdjust(final int index, final int width) {
  473. if (this.letterAdjustArray[index] == null) {
  474. this.letterAdjustArray[index] = new MinOptMax(width);
  475. } else {
  476. this.letterAdjustArray[index].add(width);
  477. }
  478. }
  479. /**
  480. * Indicates whether a character is a space in terms of this layout manager.
  481. * @param ch the character
  482. * @return true if it's a space
  483. */
  484. private static boolean isSpace(final char ch) {
  485. return ch == CharUtilities.SPACE
  486. || CharUtilities.isNonBreakableSpace(ch)
  487. || CharUtilities.isFixedWidthSpace(ch);
  488. }
  489. /** {@inheritDoc} */
  490. public List getNextKnuthElements(final LayoutContext context, final int alignment) {
  491. this.lineStartBAP = context.getLineStartBorderAndPaddingWidth();
  492. this.lineEndBAP = context.getLineEndBorderAndPaddingWidth();
  493. this.alignmentContext = context.getAlignmentContext();
  494. final List returnList = new LinkedList();
  495. KnuthSequence sequence = new InlineKnuthSequence();
  496. AreaInfo ai = null;
  497. AreaInfo prevAi = null;
  498. returnList.add(sequence);
  499. final LineBreakStatus lbs = new LineBreakStatus();
  500. this.thisStart = this.nextStart;
  501. boolean inWord = false;
  502. boolean inWhitespace = false;
  503. char ch = 0;
  504. while (this.nextStart < this.foText.length()) {
  505. ch = this.foText.charAt(this.nextStart);
  506. boolean breakOpportunity = false;
  507. final byte breakAction = this.keepTogether ? LineBreakStatus.PROHIBITED_BREAK
  508. : lbs.nextChar(ch);
  509. switch (breakAction) {
  510. case LineBreakStatus.COMBINING_PROHIBITED_BREAK:
  511. case LineBreakStatus.PROHIBITED_BREAK:
  512. break;
  513. case LineBreakStatus.EXPLICIT_BREAK:
  514. break;
  515. case LineBreakStatus.COMBINING_INDIRECT_BREAK:
  516. case LineBreakStatus.DIRECT_BREAK:
  517. case LineBreakStatus.INDIRECT_BREAK:
  518. breakOpportunity = true;
  519. break;
  520. default:
  521. TextLayoutManager.LOG.error("Unexpected breakAction: " + breakAction);
  522. }
  523. if (inWord) {
  524. if (breakOpportunity
  525. || TextLayoutManager.isSpace(ch)
  526. || CharUtilities.isExplicitBreak(ch)) {
  527. // this.foText.charAt(lastIndex) == CharUtilities.SOFT_HYPHEN
  528. prevAi = this.processWord(alignment, sequence, prevAi, ch,
  529. breakOpportunity, true);
  530. }
  531. } else if (inWhitespace) {
  532. if (ch != CharUtilities.SPACE || breakOpportunity) {
  533. prevAi = this.processWhitespace(alignment, sequence,
  534. breakOpportunity);
  535. }
  536. } else {
  537. if (ai != null) {
  538. prevAi = ai;
  539. ai = this.processLeftoverAi(alignment, sequence, ai, ch,
  540. ch == CharUtilities.SPACE || breakOpportunity);
  541. }
  542. if (breakAction == LineBreakStatus.EXPLICIT_BREAK) {
  543. sequence = this.processLinebreak(returnList, sequence);
  544. }
  545. }
  546. if (ch == CharUtilities.SPACE
  547. && this.foText.getWhitespaceTreatment() == Constants.EN_PRESERVE
  548. || ch == CharUtilities.NBSPACE) {
  549. // preserved space or non-breaking space:
  550. // create the AreaInfo object
  551. ai = new AreaInfo(this.nextStart, this.nextStart + 1,
  552. 1, 0, this.wordSpaceIPD, false, true,
  553. breakOpportunity, this.spaceFont);
  554. this.thisStart = this.nextStart + 1;
  555. } else if (CharUtilities.isFixedWidthSpace(ch) || CharUtilities.isZeroWidthSpace(ch)) {
  556. // create the AreaInfo object
  557. final Font font = FontSelector.selectFontForCharacterInText(ch,
  558. this.foText, this);
  559. final MinOptMax ipd = new MinOptMax(font.getCharWidth(ch));
  560. ai = new AreaInfo(this.nextStart, this.nextStart + 1,
  561. 0, 0, ipd, false, true,
  562. breakOpportunity, font);
  563. this.thisStart = this.nextStart + 1;
  564. } else if (CharUtilities.isExplicitBreak(ch)) {
  565. //mandatory break-character: only advance index
  566. this.thisStart = this.nextStart + 1;
  567. }
  568. inWord = !TextLayoutManager.isSpace(ch)
  569. && !CharUtilities.isExplicitBreak(ch);
  570. inWhitespace = ch == CharUtilities.SPACE
  571. && this.foText.getWhitespaceTreatment() != Constants.EN_PRESERVE;
  572. this.nextStart++;
  573. } // end of while
  574. // Process any last elements
  575. if (inWord) {
  576. this.processWord(alignment, sequence, prevAi, ch, false, false);
  577. } else if (inWhitespace) {
  578. this.processWhitespace(alignment, sequence, true);
  579. } else if (ai != null) {
  580. this.processLeftoverAi(alignment, sequence, ai, ch,
  581. ch == CharUtilities.ZERO_WIDTH_SPACE);
  582. } else if (CharUtilities.isExplicitBreak(ch)) {
  583. this.processLinebreak(returnList, sequence);
  584. }
  585. if (((List) ListUtil.getLast(returnList)).isEmpty()) {
  586. //Remove an empty sequence because of a trailing newline
  587. ListUtil.removeLast(returnList);
  588. }
  589. this.setFinished(true);
  590. if (returnList.isEmpty()) {
  591. return null;
  592. } else {
  593. return returnList;
  594. }
  595. }
  596. private KnuthSequence processLinebreak(final List returnList,
  597. KnuthSequence sequence) {
  598. if (this.lineEndBAP != 0) {
  599. sequence.add(
  600. new KnuthGlue(this.lineEndBAP, 0, 0,
  601. this.auxiliaryPosition, true));
  602. }
  603. sequence.endSequence();
  604. sequence = new InlineKnuthSequence();
  605. returnList.add(sequence);
  606. return sequence;
  607. }
  608. private AreaInfo processLeftoverAi(final int alignment,
  609. final KnuthSequence sequence, AreaInfo ai, final char ch,
  610. final boolean breakOpportunityAfter) {
  611. this.vecAreaInfo.add(ai);
  612. ai.breakOppAfter = breakOpportunityAfter;
  613. this.addElementsForASpace(sequence, alignment, ai, this.vecAreaInfo.size() - 1);
  614. ai = null;
  615. return ai;
  616. }
  617. private AreaInfo processWhitespace(final int alignment,
  618. final KnuthSequence sequence, final boolean breakOpportunity) {
  619. // End of whitespace
  620. // create the AreaInfo object
  621. AreaInfo ai = new AreaInfo(this.thisStart, this.nextStart,
  622. this.nextStart - this.thisStart, 0,
  623. MinOptMax.multiply(this.wordSpaceIPD, this.nextStart
  624. - this.thisStart), false, true,
  625. breakOpportunity, this.spaceFont);
  626. this.vecAreaInfo.add(ai);
  627. // create the elements
  628. this.addElementsForASpace(sequence, alignment, ai, this.vecAreaInfo.size() - 1);
  629. this.thisStart = this.nextStart;
  630. return ai;
  631. }
  632. private AreaInfo processWord(final int alignment, final KnuthSequence sequence,
  633. AreaInfo prevAreaInfo, final char ch, final boolean breakOpportunity,
  634. final boolean checkEndsWithHyphen) {
  635. //Word boundary found, process widths and kerning
  636. int lastIndex = this.nextStart;
  637. while (lastIndex > 0
  638. && foText.charAt(lastIndex - 1) == CharUtilities.SOFT_HYPHEN) {
  639. lastIndex--;
  640. }
  641. final boolean endsWithHyphen = checkEndsWithHyphen
  642. && foText.charAt(lastIndex) == CharUtilities.SOFT_HYPHEN;
  643. final Font font = FontSelector
  644. .selectFontForCharactersInText(foText,
  645. this.thisStart, lastIndex, foText, this);
  646. final int wordLength = lastIndex - this.thisStart;
  647. final boolean kerning = font.hasKerning();
  648. final MinOptMax wordIPD = new MinOptMax(0);
  649. for (int i = this.thisStart; i < lastIndex; i++) {
  650. final char currentChar = foText.charAt(i);
  651. //character width
  652. final int charWidth = font.getCharWidth(currentChar);
  653. wordIPD.add(charWidth);
  654. //kerning
  655. if (kerning) {
  656. int kern = 0;
  657. if (i > this.thisStart) {
  658. final char previousChar = foText.charAt(i - 1);
  659. kern = font.getKernValue(previousChar, currentChar);
  660. } else if (prevAreaInfo != null && !prevAreaInfo.isSpace && prevAreaInfo.breakIndex > 0) {
  661. final char previousChar = foText.charAt(prevAreaInfo.breakIndex - 1);
  662. kern = font.getKernValue(previousChar, currentChar);
  663. }
  664. if (kern != 0) {
  665. this.addToLetterAdjust(i, kern);
  666. wordIPD.add(kern);
  667. }
  668. }
  669. }
  670. if (kerning
  671. && breakOpportunity
  672. && !TextLayoutManager.isSpace(ch)
  673. && lastIndex > 0
  674. && endsWithHyphen) {
  675. final int kern = font.getKernValue(foText.charAt(lastIndex - 1), ch);
  676. if (kern != 0) {
  677. this.addToLetterAdjust(lastIndex, kern);
  678. //TODO: add kern to wordIPD?
  679. }
  680. }
  681. int iLetterSpaces = wordLength - 1;
  682. // if there is a break opportunity and the next one
  683. // is not a space, it could be used as a line end;
  684. // add one more letter space, in case other text follows
  685. if (breakOpportunity && !TextLayoutManager.isSpace(ch)) {
  686. iLetterSpaces++;
  687. }
  688. wordIPD.add(MinOptMax.multiply(this.letterSpaceIPD, iLetterSpaces));
  689. // create the AreaInfo object
  690. AreaInfo areaInfo = new AreaInfo(this.thisStart, lastIndex, 0,
  691. iLetterSpaces, wordIPD,
  692. endsWithHyphen,
  693. false, breakOpportunity, font);
  694. prevAreaInfo = areaInfo;
  695. this.vecAreaInfo.add(areaInfo);
  696. this.tempStart = this.nextStart;
  697. //add the elements
  698. this.addElementsForAWordFragment(sequence, alignment, areaInfo,
  699. this.vecAreaInfo.size() - 1, this.letterSpaceIPD);
  700. this.thisStart = this.nextStart;
  701. return prevAreaInfo;
  702. }
  703. /** {@inheritDoc} */
  704. public List addALetterSpaceTo(final List oldList) {
  705. // old list contains only a box, or the sequence: box penalty glue box;
  706. // look at the Position stored in the first element in oldList
  707. // which is always a box
  708. ListIterator oldListIterator = oldList.listIterator();
  709. final KnuthElement el = (KnuthElement)oldListIterator.next();
  710. final LeafPosition pos = (LeafPosition) ((KnuthBox) el).getPosition();
  711. final int idx = pos.getLeafPos();
  712. //element could refer to '-1' position, for non-collapsed spaces (?)
  713. if (idx > -1) {
  714. final AreaInfo ai = (AreaInfo) this.vecAreaInfo.get(idx);
  715. ai.letterSpaceCount++;
  716. ai.areaIPD.add(this.letterSpaceIPD);
  717. if (TextLayoutManager.BREAK_CHARS.indexOf(this.foText.charAt(this.tempStart - 1)) >= 0) {
  718. // the last character could be used as a line break
  719. // append new elements to oldList
  720. oldListIterator = oldList.listIterator(oldList.size());
  721. oldListIterator.add(new KnuthPenalty(0, KnuthPenalty.FLAGGED_PENALTY, true,
  722. this.auxiliaryPosition, false));
  723. oldListIterator.add(new KnuthGlue(this.letterSpaceIPD.opt,
  724. this.letterSpaceIPD.max - this.letterSpaceIPD.opt,
  725. this.letterSpaceIPD.opt - this.letterSpaceIPD.min,
  726. this.auxiliaryPosition, false));
  727. } else if (this.letterSpaceIPD.min == this.letterSpaceIPD.max) {
  728. // constant letter space: replace the box
  729. oldListIterator.set(new KnuthInlineBox(ai.areaIPD.opt,
  730. this.alignmentContext, pos, false));
  731. } else {
  732. // adjustable letter space: replace the glue
  733. oldListIterator.next(); // this would return the penalty element
  734. oldListIterator.next(); // this would return the glue element
  735. oldListIterator
  736. .set(new KnuthGlue(
  737. ai.letterSpaceCount * this.letterSpaceIPD.opt,
  738. ai.letterSpaceCount
  739. * (this.letterSpaceIPD.max - this.letterSpaceIPD.opt),
  740. ai.letterSpaceCount
  741. * (this.letterSpaceIPD.opt - this.letterSpaceIPD.min),
  742. this.auxiliaryPosition, true));
  743. }
  744. }
  745. return oldList;
  746. }
  747. /**
  748. * remove the AreaInfo object represented by the given elements,
  749. * so that it won't generate any element when getChangedKnuthElements
  750. * will be called
  751. *
  752. * @param oldList the elements representing the word space
  753. */
  754. public void removeWordSpace(final List oldList) {
  755. // find the element storing the Position whose value
  756. // points to the AreaInfo object
  757. final ListIterator oldListIterator = oldList.listIterator();
  758. if (((KnuthElement) ((LinkedList) oldList).getFirst()).isPenalty()) {
  759. // non breaking space: oldList starts with a penalty
  760. oldListIterator.next();
  761. }
  762. if (oldList.size() > 2) {
  763. // alignment is either center, start or end:
  764. // the first two elements does not store the needed Position
  765. oldListIterator.next();
  766. oldListIterator.next();
  767. }
  768. final int leafValue = ((LeafPosition) ((KnuthElement) oldListIterator
  769. .next()).getPosition()).getLeafPos();
  770. // only the last word space can be a trailing space!
  771. if (leafValue == this.vecAreaInfo.size() - 1) {
  772. this.vecAreaInfo.remove(leafValue);
  773. } else {
  774. TextLayoutManager.LOG.error("trying to remove a non-trailing word space");
  775. }
  776. }
  777. /** {@inheritDoc} */
  778. public void hyphenate(final Position pos, final HyphContext hc) {
  779. final AreaInfo ai
  780. = (AreaInfo) this.vecAreaInfo.get(((LeafPosition) pos).getLeafPos());
  781. int startIndex = ai.startIndex;
  782. int stopIndex;
  783. boolean nothingChanged = true;
  784. final Font font = ai.font;
  785. while (startIndex < ai.breakIndex) {
  786. final MinOptMax newIPD = new MinOptMax(0);
  787. boolean hyphenFollows;
  788. stopIndex = startIndex + hc.getNextHyphPoint();
  789. if (hc.hasMoreHyphPoints() && stopIndex <= ai.breakIndex) {
  790. // stopIndex is the index of the first character
  791. // after a hyphenation point
  792. hyphenFollows = true;
  793. } else {
  794. // there are no more hyphenation points,
  795. // or the next one is after ai.breakIndex
  796. hyphenFollows = false;
  797. stopIndex = ai.breakIndex;
  798. }
  799. hc.updateOffset(stopIndex - startIndex);
  800. //log.info("Word: " + new String(textArray, startIndex, stopIndex - startIndex));
  801. for (int i = startIndex; i < stopIndex; i++) {
  802. final char c = this.foText.charAt(i);
  803. newIPD.add(new MinOptMax(font.getCharWidth(c)));
  804. //if (i > startIndex) {
  805. if (i < stopIndex) {
  806. MinOptMax la = this.letterAdjustArray[i + 1];
  807. if (i == stopIndex - 1 && hyphenFollows) {
  808. //the letter adjust here needs to be handled further down during
  809. //element generation because it depends on hyph/no-hyph condition
  810. la = null;
  811. }
  812. if (la != null) {
  813. newIPD.add(la);
  814. }
  815. }
  816. }
  817. // add letter spaces
  818. final boolean isWordEnd
  819. = stopIndex == ai.breakIndex
  820. && ai.letterSpaceCount < ai.breakIndex - ai.startIndex;
  821. newIPD.add(MinOptMax.multiply(this.letterSpaceIPD,
  822. (isWordEnd
  823. ? stopIndex - startIndex - 1
  824. : stopIndex - startIndex)));
  825. if (!(nothingChanged
  826. && stopIndex == ai.breakIndex
  827. && !hyphenFollows)) {
  828. // the new AreaInfo object is not equal to the old one
  829. if (this.changeList == null) {
  830. this.changeList = new LinkedList();
  831. }
  832. this.changeList.add(new PendingChange(new AreaInfo(
  833. startIndex, stopIndex, 0,
  834. (isWordEnd ? stopIndex - startIndex - 1
  835. : stopIndex - startIndex), newIPD,
  836. hyphenFollows, false, false, font),
  837. ((LeafPosition) pos).getLeafPos()));
  838. nothingChanged = false;
  839. }
  840. startIndex = stopIndex;
  841. }
  842. this.hasChanged = (this.hasChanged || !nothingChanged);
  843. }
  844. /** {@inheritDoc} */
  845. public boolean applyChanges(final List oldList) {
  846. this.setFinished(false);
  847. if (this.changeList != null && !this.changeList.isEmpty()) {
  848. int areaInfosAdded = 0;
  849. int areaInfosRemoved = 0;
  850. int oldIndex = -1, changeIndex;
  851. PendingChange currChange;
  852. final ListIterator changeListIterator = this.changeList.listIterator();
  853. while (changeListIterator.hasNext()) {
  854. currChange = (PendingChange) changeListIterator.next();
  855. if (currChange.index == oldIndex) {
  856. areaInfosAdded++;
  857. changeIndex = currChange.index + areaInfosAdded - areaInfosRemoved;
  858. } else {
  859. areaInfosRemoved++;
  860. areaInfosAdded++;
  861. oldIndex = currChange.index;
  862. changeIndex = currChange.index + areaInfosAdded - areaInfosRemoved;
  863. this.vecAreaInfo.remove(changeIndex);
  864. }
  865. this.vecAreaInfo.add(changeIndex, currChange.ai);
  866. }
  867. this.changeList.clear();
  868. }
  869. this.returnedIndex = 0;
  870. return this.hasChanged;
  871. }
  872. /** {@inheritDoc} */
  873. public List getChangedKnuthElements(final List oldList,
  874. final int alignment) {
  875. if (this.isFinished()) {
  876. return null;
  877. }
  878. final LinkedList returnList = new LinkedList();
  879. while (this.returnedIndex < this.vecAreaInfo.size()) {
  880. final AreaInfo ai = (AreaInfo) this.vecAreaInfo.get(this.returnedIndex);
  881. if (ai.wordSpaceCount == 0) {
  882. // ai refers either to a word or a word fragment
  883. this.addElementsForAWordFragment(returnList, alignment, ai,
  884. this.returnedIndex, this.letterSpaceIPD);
  885. } else {
  886. // ai refers to a space
  887. this.addElementsForASpace(returnList, alignment, ai, this.returnedIndex);
  888. }
  889. this.returnedIndex++;
  890. } // end of while
  891. this.setFinished(true);
  892. //ElementListObserver.observe(returnList, "text-changed", null);
  893. return returnList;
  894. }
  895. /** {@inheritDoc} */
  896. public void getWordChars(final StringBuffer sbChars, final Position pos) {
  897. final int leafValue = ((LeafPosition) pos).getLeafPos();
  898. if (leafValue != -1) {
  899. final AreaInfo ai = (AreaInfo) this.vecAreaInfo.get(leafValue);
  900. for (int i = ai.startIndex; i < ai.breakIndex; ++i) {
  901. sbChars.append(this.foText.charAt(i));
  902. }
  903. }
  904. }
  905. private void addElementsForASpace(final List baseList,
  906. final int alignment,
  907. final AreaInfo ai,
  908. final int leafValue) {
  909. final LeafPosition mainPosition = new LeafPosition(this, leafValue);
  910. if (!ai.breakOppAfter) {
  911. // a non-breaking space
  912. if (alignment == Constants.EN_JUSTIFY) {
  913. // the space can stretch and shrink, and must be preserved
  914. // when starting a line
  915. baseList.add(this.makeAuxiliaryZeroWidthBox());
  916. baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
  917. baseList.add(new KnuthGlue(ai.areaIPD.opt, ai.areaIPD.max - ai.areaIPD.opt,
  918. ai.areaIPD.opt - ai.areaIPD.min, mainPosition, false));
  919. } else {
  920. // the space does not need to stretch or shrink, and must be
  921. // preserved when starting a line
  922. baseList.add(new KnuthInlineBox(ai.areaIPD.opt, null,
  923. mainPosition, true));
  924. }
  925. } else {
  926. if (this.foText.charAt(ai.startIndex) != CharUtilities.SPACE
  927. || this.foText.getWhitespaceTreatment() == Constants.EN_PRESERVE) {
  928. // a breaking space that needs to be preserved
  929. this.addElementsForBreakingSpace(baseList, alignment, ai,
  930. this.auxiliaryPosition, 0, mainPosition,
  931. ai.areaIPD.opt, true);
  932. } else {
  933. // a (possible block) of breaking spaces
  934. this.addElementsForBreakingSpace(baseList, alignment, ai,
  935. mainPosition, ai.areaIPD.opt,
  936. this.auxiliaryPosition, 0, false);
  937. }
  938. }
  939. }
  940. private void addElementsForBreakingSpace(final List baseList,
  941. final int alignment, final AreaInfo ai, final Position pos2,
  942. final int p2WidthOffset, final Position pos3,
  943. final int p3WidthOffset, final boolean skipZeroCheck) {
  944. switch (alignment) {
  945. case EN_CENTER:
  946. // centered text:
  947. // if the second element is chosen as a line break these elements
  948. // add a constant amount of stretch at the end of a line and at the
  949. // beginning of the next one, otherwise they don't add any stretch
  950. baseList.add(new KnuthGlue(this.lineEndBAP,
  951. 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
  952. this.auxiliaryPosition, false));
  953. baseList.add(this.makeZeroWidthPenalty(0));
  954. baseList.add(new KnuthGlue(p2WidthOffset
  955. - (this.lineStartBAP + this.lineEndBAP), -6
  956. * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, pos2, false));
  957. baseList.add(this.makeAuxiliaryZeroWidthBox());
  958. baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
  959. baseList.add(new KnuthGlue(this.lineStartBAP + p3WidthOffset,
  960. 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0, pos3, false));
  961. break;
  962. case EN_START: // fall through
  963. case EN_END:
  964. // left- or right-aligned text:
  965. // if the second element is chosen as a line break these elements
  966. // add a constant amount of stretch at the end of a line, otherwise
  967. // they don't add any stretch
  968. if (skipZeroCheck || this.lineStartBAP != 0 || this.lineEndBAP != 0) {
  969. baseList.add(new KnuthGlue(this.lineEndBAP,
  970. 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
  971. this.auxiliaryPosition, false));
  972. baseList.add(this.makeZeroWidthPenalty(0));
  973. baseList.add(new KnuthGlue(p2WidthOffset
  974. - (this.lineStartBAP + this.lineEndBAP), -3
  975. * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
  976. pos2, false));
  977. baseList.add(this.makeAuxiliaryZeroWidthBox());
  978. baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
  979. baseList.add(new KnuthGlue(this.lineStartBAP + p3WidthOffset,
  980. 0, 0, pos3, false));
  981. } else {
  982. baseList.add(new KnuthGlue(0,
  983. 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
  984. this.auxiliaryPosition, false));
  985. baseList.add(this.makeZeroWidthPenalty(0));
  986. baseList.add(new KnuthGlue(ai.areaIPD.opt, -3
  987. * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
  988. pos2, false));
  989. }
  990. break;
  991. case EN_JUSTIFY:
  992. // justified text:
  993. // the stretch and shrink depends on the space width
  994. if (skipZeroCheck || this.lineStartBAP != 0 || this.lineEndBAP != 0) {
  995. baseList.add(new KnuthGlue(this.lineEndBAP, 0, 0,
  996. this.auxiliaryPosition, false));
  997. baseList.add(this.makeZeroWidthPenalty(0));
  998. baseList.add(new KnuthGlue(p2WidthOffset
  999. - (this.lineStartBAP + this.lineEndBAP), ai.areaIPD.max
  1000. - ai.areaIPD.opt, ai.areaIPD.opt - ai.areaIPD.min,
  1001. pos2, false));
  1002. baseList.add(this.makeAuxiliaryZeroWidthBox());
  1003. baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
  1004. baseList.add(new KnuthGlue(this.lineStartBAP + p3WidthOffset,
  1005. 0, 0, pos3, false));
  1006. } else {
  1007. baseList.add(new KnuthGlue(ai.areaIPD.opt, ai.areaIPD.max
  1008. - ai.areaIPD.opt, ai.areaIPD.opt - ai.areaIPD.min,
  1009. pos2, false));
  1010. }
  1011. break;
  1012. default:
  1013. // last line justified, the other lines unjustified:
  1014. // use only the space stretch
  1015. if (skipZeroCheck || this.lineStartBAP != 0 || this.lineEndBAP != 0) {
  1016. baseList.add(new KnuthGlue(this.lineEndBAP, 0, 0,
  1017. this.auxiliaryPosition, false));
  1018. baseList.add(this.makeZeroWidthPenalty(0));
  1019. baseList.add(new KnuthGlue(p2WidthOffset
  1020. - (this.lineStartBAP + this.lineEndBAP), ai.areaIPD.max
  1021. - ai.areaIPD.opt, 0, pos2, false));
  1022. baseList.add(this.makeAuxiliaryZeroWidthBox());
  1023. baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
  1024. baseList.add(new KnuthGlue(this.lineStartBAP + p3WidthOffset,
  1025. 0, 0, pos3, false));
  1026. } else {
  1027. baseList.add(new KnuthGlue(ai.areaIPD.opt, ai.areaIPD.max
  1028. - ai.areaIPD.opt, 0, pos2, false));
  1029. }
  1030. }
  1031. }
  1032. private void addElementsForAWordFragment(final List baseList,
  1033. final int alignment,
  1034. final AreaInfo ai,
  1035. final int leafValue,
  1036. final MinOptMax letterSpaceWidth) {
  1037. final LeafPosition mainPosition = new LeafPosition(this, leafValue);
  1038. // if the last character of the word fragment is '-' or '/',
  1039. // the fragment could end a line; in this case, it loses one
  1040. // of its letter spaces;
  1041. final boolean suppressibleLetterSpace = ai.breakOppAfter && !ai.isHyphenated;
  1042. if (letterSpaceWidth.min == letterSpaceWidth.max) {
  1043. // constant letter spacing
  1044. baseList.add(new KnuthInlineBox(
  1045. suppressibleLetterSpace
  1046. ? ai.areaIPD.opt - letterSpaceWidth.opt
  1047. : ai.areaIPD.opt,
  1048. this.alignmentContext,
  1049. this.notifyPos(mainPosition), false));
  1050. } else {
  1051. // adjustable letter spacing
  1052. final int unsuppressibleLetterSpaces
  1053. = suppressibleLetterSpace ? ai.letterSpaceCount - 1 : ai.letterSpaceCount;
  1054. baseList.add
  1055. (new KnuthInlineBox(ai.areaIPD.opt
  1056. - ai.letterSpaceCount * letterSpaceWidth.opt,
  1057. this.alignmentContext,
  1058. this.notifyPos(mainPosition), false));
  1059. baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
  1060. baseList.add
  1061. (new KnuthGlue(unsuppressibleLetterSpaces * letterSpaceWidth.opt,
  1062. unsuppressibleLetterSpaces * (letterSpaceWidth.max - letterSpaceWidth.opt),
  1063. unsuppressibleLetterSpaces * (letterSpaceWidth.opt - letterSpaceWidth.min),
  1064. this.auxiliaryPosition, true));
  1065. baseList.add(this.makeAuxiliaryZeroWidthBox());
  1066. }
  1067. // extra-elements if the word fragment is the end of a syllable,
  1068. // or it ends with a character that can be used as a line break
  1069. if (ai.isHyphenated) {
  1070. MinOptMax widthIfNoBreakOccurs = null;
  1071. if (ai.breakIndex < this.foText.length()) {
  1072. //Add in kerning in no-break condition
  1073. widthIfNoBreakOccurs = this.letterAdjustArray[ai.breakIndex];
  1074. }
  1075. //if (ai.breakIndex)
  1076. // the word fragment ends at the end of a syllable:
  1077. // if a break occurs the content width increases,
  1078. // otherwise nothing happens
  1079. this.addElementsForAHyphen(baseList, alignment, this.hyphIPD,
  1080. widthIfNoBreakOccurs, ai.breakOppAfter && ai.isHyphenated);
  1081. } else if (suppressibleLetterSpace) {
  1082. // the word fragment ends with a character that acts as a hyphen
  1083. // if a break occurs the width does not increase,
  1084. // otherwise there is one more letter space
  1085. this.addElementsForAHyphen(baseList, alignment, 0, letterSpaceWidth, true);
  1086. }
  1087. }
  1088. // static final int SOFT_HYPHEN_PENALTY = KnuthPenalty.FLAGGED_PENALTY / 10;
  1089. private static final int SOFT_HYPHEN_PENALTY = 1;
  1090. private void addElementsForAHyphen(final List baseList,
  1091. final int alignment,
  1092. final int widthIfBreakOccurs,
  1093. MinOptMax widthIfNoBreakOccurs,
  1094. final boolean unflagged) {
  1095. if (widthIfNoBreakOccurs == null) {
  1096. widthIfNoBreakOccurs = TextLayoutManager.ZERO_MINOPTMAX;
  1097. }
  1098. switch (alignment) {
  1099. case EN_CENTER :
  1100. // centered text:
  1101. baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
  1102. baseList.add(new KnuthGlue(this.lineEndBAP,
  1103. 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
  1104. this.auxiliaryPosition, true));
  1105. baseList.add(new KnuthPenalty(this.hyphIPD,
  1106. unflagged ? TextLayoutManager.SOFT_HYPHEN_PENALTY
  1107. : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
  1108. this.auxiliaryPosition, false));
  1109. baseList.add(new KnuthGlue(-(this.lineEndBAP + this.lineStartBAP),
  1110. -6 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
  1111. this.auxiliaryPosition, false));
  1112. baseList.add(this.makeAuxiliaryZeroWidthBox());
  1113. baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
  1114. baseList.add(new KnuthGlue(this.lineStartBAP,
  1115. 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
  1116. this.auxiliaryPosition, true));
  1117. break;
  1118. case EN_START : // fall through
  1119. case EN_END :
  1120. // left- or right-aligned text:
  1121. if (this.lineStartBAP != 0 || this.lineEndBAP != 0) {
  1122. baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
  1123. baseList.add(new KnuthGlue(this.lineEndBAP,
  1124. 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
  1125. this.auxiliaryPosition, false));
  1126. baseList.add(new KnuthPenalty(widthIfBreakOccurs,
  1127. unflagged ? TextLayoutManager.SOFT_HYPHEN_PENALTY
  1128. : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
  1129. this.auxiliaryPosition, false));
  1130. baseList.add(new KnuthGlue(widthIfNoBreakOccurs.opt
  1131. - (this.lineStartBAP + this.lineEndBAP), -3
  1132. * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
  1133. this.auxiliaryPosition, false));
  1134. baseList.add(this.makeAuxiliaryZeroWidthBox());
  1135. baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
  1136. baseList.add(new KnuthGlue(this.lineStartBAP, 0, 0,
  1137. this.auxiliaryPosition, false));
  1138. } else {
  1139. baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
  1140. baseList.add(new KnuthGlue(0, 3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
  1141. this.auxiliaryPosition, false));
  1142. baseList.add(new KnuthPenalty(widthIfBreakOccurs,
  1143. unflagged ? TextLayoutManager.SOFT_HYPHEN_PENALTY
  1144. : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
  1145. this.auxiliaryPosition, false));
  1146. baseList.add(new KnuthGlue(widthIfNoBreakOccurs.opt,
  1147. -3 * LineLayoutManager.DEFAULT_SPACE_WIDTH, 0,
  1148. this.auxiliaryPosition, false));
  1149. }
  1150. break;
  1151. default:
  1152. // justified text, or last line justified:
  1153. // just a flagged penalty
  1154. if (this.lineStartBAP != 0 || this.lineEndBAP != 0) {
  1155. baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
  1156. baseList.add(new KnuthGlue(this.lineEndBAP, 0, 0,
  1157. this.auxiliaryPosition, false));
  1158. baseList.add(new KnuthPenalty(widthIfBreakOccurs,
  1159. unflagged ? TextLayoutManager.SOFT_HYPHEN_PENALTY
  1160. : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
  1161. this.auxiliaryPosition, false));
  1162. // extra elements representing a letter space that is suppressed
  1163. // if a break occurs
  1164. if (widthIfNoBreakOccurs.min != 0
  1165. || widthIfNoBreakOccurs.max != 0) {
  1166. baseList
  1167. .add(new KnuthGlue(widthIfNoBreakOccurs.opt
  1168. - (this.lineStartBAP + this.lineEndBAP),
  1169. widthIfNoBreakOccurs.max
  1170. - widthIfNoBreakOccurs.opt,
  1171. widthIfNoBreakOccurs.opt
  1172. - widthIfNoBreakOccurs.min,
  1173. this.auxiliaryPosition, false));
  1174. } else {
  1175. baseList.add(new KnuthGlue(-(this.lineStartBAP + this.lineEndBAP), 0, 0,
  1176. this.auxiliaryPosition, false));
  1177. }
  1178. baseList.add(this.makeAuxiliaryZeroWidthBox());
  1179. baseList.add(this.makeZeroWidthPenalty(KnuthElement.INFINITE));
  1180. baseList.add(new KnuthGlue(this.lineStartBAP, 0, 0,
  1181. this.auxiliaryPosition, false));
  1182. } else {
  1183. baseList.add(new KnuthPenalty(widthIfBreakOccurs,
  1184. unflagged ? TextLayoutManager.SOFT_HYPHEN_PENALTY
  1185. : KnuthPenalty.FLAGGED_PENALTY, !unflagged,
  1186. this.auxiliaryPosition, false));
  1187. // extra elements representing a letter space that is suppressed
  1188. // if a break occurs
  1189. if (widthIfNoBreakOccurs.min != 0
  1190. || widthIfNoBreakOccurs.max != 0) {
  1191. baseList.add(new KnuthGlue(widthIfNoBreakOccurs.opt,
  1192. widthIfNoBreakOccurs.max - widthIfNoBreakOccurs.opt,
  1193. widthIfNoBreakOccurs.opt - widthIfNoBreakOccurs.min,
  1194. this.auxiliaryPosition, false));
  1195. }
  1196. }
  1197. }
  1198. }
  1199. }