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

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