Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

TextLayoutManager.java 67KB

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