您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

TextLayoutManager.java 68KB

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