Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

LineLayoutManager.java 60KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505
  1. /*
  2. * Copyright 1999-2004 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. /* $Id: LineLayoutManager.java,v 1.17 2004/04/02 10:38:29 cbowditch Exp $ */
  17. package org.apache.fop.layoutmgr;
  18. import org.apache.fop.datatypes.Length;
  19. import org.apache.fop.fo.Constants;
  20. import org.apache.fop.fo.flow.Block;
  21. import org.apache.fop.fo.properties.CommonHyphenation;
  22. import org.apache.fop.hyphenation.Hyphenation;
  23. import org.apache.fop.hyphenation.Hyphenator;
  24. import org.apache.fop.area.LineArea;
  25. import org.apache.fop.area.Resolvable;
  26. import java.util.ListIterator;
  27. import java.util.Iterator;
  28. import java.util.List;
  29. import java.util.ArrayList;
  30. import java.util.LinkedList;
  31. import org.apache.fop.traits.MinOptMax;
  32. /**
  33. * LayoutManager for lines. It builds one or more lines containing
  34. * inline areas generated by its sub layout managers.
  35. * A break is found for each line which may contain one of more
  36. * breaks from the child layout managers.
  37. * Once a break is found then it is return for the parent layout
  38. * manager to handle.
  39. * When the areas are being added to the page this manager
  40. * creates a line area to contain the inline areas added by the
  41. * child layout managers.
  42. */
  43. public class LineLayoutManager extends InlineStackingLayoutManager {
  44. private Block fobj;
  45. /**
  46. * @see org.apache.fop.layoutmgr.AbstractLayoutManager#initProperties()
  47. */
  48. protected void initProperties() {
  49. bTextAlignment = fobj.getTextAlign();
  50. bTextAlignmentLast = fobj.getTextAlignLast();
  51. textIndent = fobj.getTextIndent();
  52. hyphProps = fobj.getCommonHyphenation();
  53. //
  54. if (bTextAlignment != JUSTIFY && bTextAlignmentLast == JUSTIFY) {
  55. effectiveAlignment = 0;
  56. } else {
  57. effectiveAlignment = bTextAlignment;
  58. }
  59. }
  60. /**
  61. * Private class to store information about inline breaks.
  62. * Each value holds the start and end indexes into a List of
  63. * inline break positions.
  64. */
  65. private static class LineBreakPosition extends LeafPosition {
  66. // int iPos;
  67. double dAdjust; // Percentage to adjust (stretch or shrink)
  68. double ipdAdjust; // Percentage to adjust (stretch or shrink)
  69. int startIndent;
  70. int lineHeight;
  71. int baseline;
  72. LineBreakPosition(LayoutManager lm, int iBreakIndex,
  73. double ipdA, double adjust, int ind, int lh, int bl) {
  74. super(lm, iBreakIndex);
  75. // iPos = iBreakIndex;
  76. ipdAdjust = ipdA;
  77. dAdjust = adjust;
  78. startIndent = ind;
  79. lineHeight = lh;
  80. baseline = bl;
  81. }
  82. }
  83. /** Break positions returned by inline content. */
  84. private List vecInlineBreaks = new ArrayList();
  85. private BreakPoss prevBP = null; // Last confirmed break position
  86. private int bTextAlignment = TextAlign.JUSTIFY;
  87. private int bTextAlignmentLast;
  88. private int effectiveAlignment;
  89. private Length textIndent;
  90. private CommonHyphenation hyphProps;
  91. private int lineHeight;
  92. private int lead;
  93. private int follow;
  94. // inline start pos when adding areas
  95. private int iStartPos = 0;
  96. private ArrayList knuthParagraphs = null;
  97. private LinkedList activeList = null;
  98. private LinkedList inactiveList = null;
  99. private ArrayList breakpoints = null;
  100. private int iReturnedLBP = 0;
  101. private int iStartElement = 0;
  102. private int iEndElement = 0;
  103. private int iCurrParIndex = 0;
  104. private KnuthNode lastDeactivatedNode = null;
  105. // parameters of Knuth's algorithm:
  106. // penalty value for flagged penalties
  107. private int flaggedPenalty = 50;
  108. // demerit for consecutive lines ending at flagged penalties
  109. private int repeatedFlaggedDemerit = 50;
  110. // demerit for consecutive lines belonging to incompatible fitness classes
  111. private int incompatibleFitnessDemerit = 50;
  112. // suggested modification to the "optimum" number of lines
  113. private int looseness = 0;
  114. private static final int INFINITE_RATIO = 1000;
  115. // this class represent a feasible breaking point
  116. private class KnuthNode {
  117. // index of the breakpoint represented by this node
  118. public int position;
  119. // number of the line ending at this breakpoint
  120. public int line;
  121. // fitness class of the line ending at his breakpoint
  122. public int fitness;
  123. // accumulated width of the KnuthElements
  124. public int totalWidth;
  125. // accumulated stretchability of the KnuthElements
  126. public int totalStretch;
  127. // accumulated shrinkability of the KnuthElements
  128. public int totalShrink;
  129. // adjustment ratio if the line ends at this breakpoint
  130. public double adjustRatio;
  131. // difference between target and actual line width
  132. public int difference;
  133. // minimum total demerits up to this breakpoint
  134. public double totalDemerits;
  135. // best node for the preceding breakpoint
  136. public KnuthNode previous;
  137. public KnuthNode(int position, int line, int fitness,
  138. int totalWidth, int totalStretch, int totalShrink,
  139. double adjustRatio, int difference,
  140. double totalDemerits, KnuthNode previous) {
  141. this.position = position;
  142. this.line = line;
  143. this.fitness = fitness;
  144. this.totalWidth = totalWidth;
  145. this.totalStretch = totalStretch;
  146. this.totalShrink = totalShrink;
  147. this.adjustRatio = adjustRatio;
  148. this.difference = difference;
  149. this.totalDemerits = totalDemerits;
  150. this.previous = previous;
  151. }
  152. }
  153. // this class stores information about how the nodes
  154. // which could start a line
  155. // ending at the current element
  156. private class BestRecords {
  157. private static final double INFINITE_DEMERITS = 1E11;
  158. private double bestDemerits[] = {
  159. INFINITE_DEMERITS, INFINITE_DEMERITS,
  160. INFINITE_DEMERITS, INFINITE_DEMERITS
  161. };
  162. private KnuthNode bestNode[] = {null, null, null, null};
  163. private double bestAdjust[] = {0.0, 0.0, 0.0, 0.0};
  164. private int bestDifference[] = {0, 0, 0, 0};
  165. private int bestIndex = -1;
  166. public BestRecords() {
  167. }
  168. public void addRecord(double demerits, KnuthNode node, double adjust,
  169. int difference, int fitness) {
  170. if (demerits > bestDemerits[fitness]) {
  171. log.error("New demerits value greter than the old one");
  172. }
  173. bestDemerits[fitness] = demerits;
  174. bestNode[fitness] = node;
  175. bestAdjust[fitness] = adjust;
  176. bestDifference[fitness] = difference;
  177. if (bestIndex == -1 || demerits < bestDemerits[bestIndex]) {
  178. bestIndex = fitness;
  179. }
  180. }
  181. public boolean hasRecords() {
  182. return (bestIndex != -1);
  183. }
  184. public boolean notInfiniteDemerits(int fitness) {
  185. return (bestDemerits[fitness] != INFINITE_DEMERITS);
  186. }
  187. public double getDemerits(int fitness) {
  188. return bestDemerits[fitness];
  189. }
  190. public KnuthNode getNode(int fitness) {
  191. return bestNode[fitness];
  192. }
  193. public double getAdjust(int fitness) {
  194. return bestAdjust[fitness];
  195. }
  196. public int getDifference(int fitness) {
  197. return bestDifference[fitness];
  198. }
  199. public double getMinDemerits() {
  200. if (bestIndex != -1) {
  201. return getDemerits(bestIndex);
  202. } else {
  203. // anyway, this should never happen
  204. return INFINITE_DEMERITS;
  205. }
  206. }
  207. }
  208. // this class is used to remember
  209. // which was the first element in the paragraph
  210. // returned by each LM
  211. private class Update {
  212. private LayoutManager inlineLM;
  213. private int iFirstIndex;
  214. public Update(LayoutManager lm, int index) {
  215. inlineLM = lm;
  216. iFirstIndex = index;
  217. }
  218. }
  219. // this class represents a paragraph
  220. private class Paragraph extends LinkedList {
  221. // number of KnuthElements added by the LineLayoutManager
  222. public int ignoreAtStart = 0;
  223. public int ignoreAtEnd = 0;
  224. // minimum space at the end of the last line (in millipoints)
  225. public int lineFillerWidth;
  226. // word space dimension (in millipoints)
  227. private int wordSpaceIPD;
  228. public void startParagraph(int lineWidth) {
  229. // get the word space dimension, which needs to be known
  230. // in order to center text
  231. LayoutManager lm;
  232. if ((lm = getChildLM()) != null) {
  233. wordSpaceIPD = lm.getWordSpaceIPD();
  234. }
  235. // set the minimum amount of empty space at the end of the
  236. // last line
  237. if (bTextAlignment == CENTER) {
  238. lineFillerWidth = 0;
  239. } else {
  240. lineFillerWidth = (int)(lineWidth / 6);
  241. }
  242. // add auxiliary elements at the beginning of the paragraph
  243. if (bTextAlignment == CENTER && bTextAlignmentLast != JUSTIFY) {
  244. this.add(new KnuthGlue(0, 3 * wordSpaceIPD, 0,
  245. null, false));
  246. ignoreAtStart ++;
  247. }
  248. // add the element representing text indentation
  249. // at the beginning of the first paragraph
  250. if (knuthParagraphs.size() == 0
  251. && textIndent.getValue() != 0) {
  252. this.add(new KnuthBox(textIndent.getValue(), 0, 0, 0,
  253. null, false));
  254. ignoreAtStart ++;
  255. }
  256. }
  257. public void endParagraph() {
  258. // remove glue and penalty item at the end of the paragraph
  259. while (this.size() > ignoreAtStart
  260. && !((KnuthElement) this.get(this.size() - 1)).isBox()) {
  261. this.remove(this.size() - 1);
  262. }
  263. if (this.size() > ignoreAtStart) {
  264. if (bTextAlignment == CENTER
  265. && bTextAlignmentLast != JUSTIFY) {
  266. this.add(new KnuthGlue(0, 3 * wordSpaceIPD, 0,
  267. null, false));
  268. this.add(new KnuthPenalty(0, -KnuthElement.INFINITE,
  269. false, null, false));
  270. ignoreAtEnd = 2;
  271. } else if (bTextAlignmentLast != JUSTIFY) {
  272. // add the elements representing the space
  273. // at the end of the last line
  274. // and the forced break
  275. this.add(new KnuthPenalty(0, KnuthElement.INFINITE,
  276. false, null, false));
  277. this.add(new KnuthGlue(lineFillerWidth, 10000000, 0,
  278. null, false));
  279. this.add(new KnuthPenalty(0, -KnuthElement.INFINITE,
  280. false, null, false));
  281. ignoreAtEnd = 3;
  282. } else {
  283. // add only the element representing the forced break
  284. this.add(new KnuthPenalty(0, -KnuthElement.INFINITE,
  285. false, null, false));
  286. ignoreAtEnd = 1;
  287. }
  288. knuthParagraphs.add(this);
  289. }
  290. }
  291. }
  292. /**
  293. * Create a new Line Layout Manager.
  294. * This is used by the block layout manager to create
  295. * line managers for handling inline areas flowing into line areas.
  296. *
  297. * @param lh the default line height
  298. * @param l the default lead, from top to baseline
  299. * @param f the default follow, from baseline to bottom
  300. */
  301. public LineLayoutManager(Block node, int lh, int l, int f) {
  302. super(node);
  303. fobj = node;
  304. // the child FObj are owned by the parent BlockLM
  305. // this LM has all its childLMs preloaded
  306. fobjIter = null;
  307. lineHeight = lh;
  308. lead = l;
  309. follow = f;
  310. initialize(); // Normally done when started by parent!
  311. }
  312. /**
  313. * Call child layout managers to generate content.
  314. * This gets the next break which is a full line.
  315. *
  316. * @param context the layout context for finding breaks
  317. * @return the next break position
  318. */
  319. public BreakPoss getNextBreakPoss(LayoutContext context) {
  320. // Get a break from currently active child LM
  321. // Set up constraints for inline level managers
  322. LayoutManager curLM ; // currently active LM
  323. BreakPoss prev = null;
  324. BreakPoss bp = null; // proposed BreakPoss
  325. ArrayList vecPossEnd = new ArrayList();
  326. // IPD remaining in line
  327. MinOptMax availIPD = context.getStackLimit();
  328. LayoutContext inlineLC = new LayoutContext(context);
  329. clearPrevIPD();
  330. int iPrevLineEnd = vecInlineBreaks.size();
  331. if (iPrevLineEnd == 0 && bTextAlignment == TextAlign.START) {
  332. availIPD.subtract(new MinOptMax(textIndent.getValue()));
  333. }
  334. prevBP = null;
  335. // here starts Knuth's algorithm
  336. KnuthElement thisElement = null;
  337. LinkedList returnedList = null;
  338. LineBreakPosition lbp = null;
  339. if (knuthParagraphs == null) {
  340. // it's the first time this method is called
  341. knuthParagraphs = new ArrayList();
  342. breakpoints = new ArrayList();
  343. // convert all the text in a sequence of paragraphs made
  344. // of KnuthBox, KnuthGlue and KnuthPenalty objects
  345. boolean bPrevWasKnuthBox = false;
  346. KnuthBox prevBox = null;
  347. Paragraph knuthPar = new Paragraph();
  348. knuthPar.startParagraph(availIPD.opt);
  349. while ((curLM = getChildLM()) != null) {
  350. if ((returnedList
  351. = curLM.getNextKnuthElements(inlineLC,
  352. effectiveAlignment))
  353. != null) {
  354. // if there are two consecutive KnuthBox, the first one
  355. // does not represent a whole word, so it must be given
  356. // one more letter space
  357. thisElement = (KnuthElement) returnedList.getFirst();
  358. if (returnedList.size() > 1
  359. || !(thisElement.isPenalty()
  360. && ((KnuthPenalty) thisElement).getP()
  361. == -KnuthElement.INFINITE)) {
  362. if (thisElement.isBox() && !thisElement.isAuxiliary()
  363. && bPrevWasKnuthBox) {
  364. prevBox = (KnuthBox) knuthPar.removeLast();
  365. if (!prevBox.isAuxiliary()) {
  366. // if letter spacing is constant,
  367. // only prevBox needs to be replaced;
  368. knuthPar.addLast(prevBox.getLayoutManager()
  369. .addALetterSpaceTo(prevBox));
  370. } else {
  371. // prevBox is the last element
  372. // in the sub-sequence
  373. // <box> <aux penalty> <aux glue> <aux box>
  374. // the letter space is added to <aux glue>,
  375. // while the other elements are not changed
  376. KnuthBox auxBox = prevBox;
  377. KnuthGlue auxGlue
  378. = (KnuthGlue) knuthPar.removeLast();
  379. KnuthPenalty auxPenalty
  380. = (KnuthPenalty) knuthPar.removeLast();
  381. prevBox = (KnuthBox) knuthPar.getLast();
  382. knuthPar.addLast(auxPenalty);
  383. knuthPar.addLast(prevBox.getLayoutManager()
  384. .addALetterSpaceTo(prevBox));
  385. knuthPar.addLast(auxBox);
  386. }
  387. }
  388. if (((KnuthElement) returnedList.getLast()).isBox()) {
  389. bPrevWasKnuthBox = true;
  390. } else {
  391. bPrevWasKnuthBox = false;
  392. }
  393. // add the new elements to the paragraph
  394. knuthPar.addAll(returnedList);
  395. } else {
  396. // a list with a single penalty item
  397. // whose value is -inf
  398. // represents a preserved linefeed,
  399. // wich forces a line break
  400. knuthPar.endParagraph();
  401. knuthPar = new Paragraph();
  402. knuthPar.startParagraph(availIPD.opt);
  403. bPrevWasKnuthBox = false;
  404. }
  405. } else {
  406. // curLM returned null; this can happen
  407. // if it has nothing more to layout,
  408. // so just iterate once more to see
  409. // if there are other chilren
  410. }
  411. }
  412. knuthPar.endParagraph();
  413. // find the optimal line breaking points for each paragraph
  414. ListIterator paragraphsIterator
  415. = knuthParagraphs.listIterator(knuthParagraphs.size());
  416. Paragraph currPar = null;
  417. while (paragraphsIterator.hasPrevious()) {
  418. currPar = (Paragraph) paragraphsIterator.previous();
  419. double maxAdjustment = 1;
  420. int iBPcount = 0;
  421. // first try
  422. if ((iBPcount
  423. = findBreakingPoints(currPar,
  424. context.getStackLimit().opt,
  425. maxAdjustment, false)) == 0) {
  426. // the first try failed, now try something different
  427. log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment);
  428. if (hyphProps.hyphenate == Constants.TRUE) {
  429. // consider every hyphenation point as a legal break
  430. findHyphenationPoints(currPar);
  431. } else {
  432. // try with a higher threshold
  433. maxAdjustment = 5;
  434. }
  435. if ((iBPcount
  436. = findBreakingPoints(currPar,
  437. context.getStackLimit().opt,
  438. maxAdjustment, false)) == 0) {
  439. // the second try failed too, try with a huge threshold
  440. // and force the algorithm to find
  441. // a set of breaking points
  442. log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment
  443. + (hyphProps.hyphenate == Constants.TRUE ? " and hyphenation" : ""));
  444. maxAdjustment = 20;
  445. iBPcount
  446. = findBreakingPoints(currPar,
  447. context.getStackLimit().opt,
  448. maxAdjustment, true);
  449. }
  450. }
  451. }
  452. } else {
  453. // this method has been called before
  454. // all line breaks are already calculated
  455. }
  456. // get a break point from the list
  457. lbp = (LineBreakPosition) breakpoints.get(iReturnedLBP ++);
  458. if (iReturnedLBP == breakpoints.size()) {
  459. setFinished(true);
  460. }
  461. BreakPoss curLineBP = new BreakPoss(lbp);
  462. curLineBP.setFlag(BreakPoss.ISLAST, isFinished());
  463. curLineBP.setStackingSize(new MinOptMax(lbp.lineHeight));
  464. return curLineBP;
  465. }
  466. private int findBreakingPoints(Paragraph par, int lineWidth,
  467. double threshold, boolean force) {
  468. int totalWidth = 0;
  469. int totalStretch = 0;
  470. int totalShrink = 0;
  471. boolean bForced = false;
  472. // current element in the paragraph
  473. KnuthElement thisElement = null;
  474. // previous element in the paragraph is a KnuthBox
  475. boolean previousIsBox = false;
  476. // create an active node representing the starting point
  477. activeList = new LinkedList();
  478. activeList.add(new KnuthNode(0, 0, 1, 0, 0, 0, 0, 0, 0, null));
  479. inactiveList = new LinkedList();
  480. // main loop
  481. ListIterator paragraphIterator = par.listIterator();
  482. while (paragraphIterator.hasNext()) {
  483. thisElement = (KnuthElement) paragraphIterator.next();
  484. if (thisElement.isBox()) {
  485. // a KnuthBox object is not a legal line break
  486. totalWidth += thisElement.getW();
  487. previousIsBox = true;
  488. } else if (thisElement.isGlue()) {
  489. // a KnuthGlue object is a legal line break
  490. // only if the previous object is a KnuthBox
  491. if (previousIsBox) {
  492. considerLegalBreak(par, lineWidth, thisElement,
  493. totalWidth, totalStretch, totalShrink,
  494. threshold);
  495. }
  496. totalWidth += thisElement.getW();
  497. totalStretch += ((KnuthGlue) thisElement).getY();
  498. totalShrink += ((KnuthGlue) thisElement).getZ();
  499. previousIsBox = false;
  500. } else {
  501. // a KnuthPenalty is a legal line break
  502. // only if its penalty is not infinite
  503. if (((KnuthPenalty) thisElement).getP()
  504. < KnuthElement.INFINITE) {
  505. considerLegalBreak(par, lineWidth, thisElement,
  506. totalWidth, totalStretch, totalShrink,
  507. threshold);
  508. }
  509. previousIsBox = false;
  510. }
  511. }
  512. if (activeList.size() == 0) {
  513. if (force) {
  514. activeList.add(lastDeactivatedNode);
  515. bForced = true;
  516. log.error("Could not find a set of breaking points");
  517. } else {
  518. inactiveList.clear();
  519. return 0;
  520. }
  521. }
  522. // there is at least one set of breaking points
  523. // choose the active node with fewest total demerits
  524. ListIterator activeListIterator = activeList.listIterator();
  525. KnuthNode tempNode = null;
  526. KnuthNode bestActiveNode = null;
  527. double bestDemerits = BestRecords.INFINITE_DEMERITS;
  528. int line = 0;
  529. while (activeListIterator.hasNext()) {
  530. tempNode = (KnuthNode) activeListIterator.next();
  531. if (tempNode.totalDemerits < bestDemerits) {
  532. bestActiveNode = tempNode;
  533. bestDemerits = bestActiveNode.totalDemerits;
  534. }
  535. }
  536. line = bestActiveNode.line;
  537. if (looseness != 0) {
  538. // choose the appropriate active node
  539. activeListIterator = activeList.listIterator();
  540. int s = 0;
  541. while (activeListIterator.hasNext()) {
  542. tempNode = (KnuthNode) activeListIterator.next();
  543. int delta = tempNode.line - line;
  544. if (looseness <= delta && delta < s
  545. || s < delta && delta <= looseness) {
  546. s = delta;
  547. bestActiveNode = tempNode;
  548. bestDemerits = tempNode.totalDemerits;
  549. } else if (delta == s
  550. && tempNode.totalDemerits < bestDemerits) {
  551. bestActiveNode = tempNode;
  552. bestDemerits = tempNode.totalDemerits;
  553. }
  554. }
  555. line = bestActiveNode.line;
  556. }
  557. // use the chosen node to determine the optimum breakpoints
  558. for (int i = line; i > 0; i--) {
  559. // compute indent and adjustment ratio, according to
  560. // the value of text-align and text-align-last
  561. int indent = 0;
  562. int difference = (bestActiveNode.line < line || bForced)
  563. ? bestActiveNode.difference
  564. : bestActiveNode.difference + par.lineFillerWidth;
  565. int textAlign = (bestActiveNode.line < line || bForced)
  566. ? bTextAlignment : bTextAlignmentLast;
  567. indent += (textAlign == CENTER)
  568. ? difference / 2
  569. : (textAlign == END) ? difference : 0;
  570. indent += (bestActiveNode.line == 1
  571. && knuthParagraphs.indexOf(par) == 0)
  572. ? textIndent.getValue() : 0;
  573. double ratio = (textAlign == JUSTIFY)
  574. ? bestActiveNode.adjustRatio : 0;
  575. // lead to baseline is
  576. // max of: baseline fixed alignment and middle/2
  577. // after baseline is
  578. // max: top height-lead, middle/2 and bottom height-lead
  579. int halfLeading = (lineHeight - lead - follow) / 2;
  580. // height before baseline
  581. int lineLead = lead + halfLeading;
  582. // maximum size of top and bottom alignment
  583. int maxtb = follow + halfLeading;
  584. // max size of middle alignment below baseline
  585. int middlefollow = maxtb;
  586. // index of the first KnuthElement in this line
  587. int firstElementIndex = 0;
  588. if (line > 1) {
  589. firstElementIndex = bestActiveNode.previous.position + 1;
  590. }
  591. ListIterator inlineIterator = par.listIterator(firstElementIndex);
  592. for (int j = 0;
  593. j < (bestActiveNode.position - firstElementIndex + 1);
  594. j++) {
  595. KnuthElement element = (KnuthElement) inlineIterator.next();
  596. if (element.isBox()) {
  597. if (((KnuthBox) element).getLead() > lineLead) {
  598. lineLead = ((KnuthBox) element).getLead();
  599. }
  600. if (((KnuthBox) element).getTotal() > maxtb) {
  601. maxtb = ((KnuthBox) element).getTotal();
  602. }
  603. if (((KnuthBox) element).getMiddle() > middlefollow) {
  604. middlefollow = ((KnuthBox) element).getMiddle();
  605. }
  606. }
  607. }
  608. if (maxtb - lineLead > middlefollow) {
  609. middlefollow = maxtb - lineLead;
  610. }
  611. // add nodes at the beginning of the list, as they are found
  612. // backwards, from the last one to the first one
  613. breakpoints.add(0,
  614. new LineBreakPosition(this,
  615. bestActiveNode.position,
  616. ratio, 0, indent,
  617. lineLead + middlefollow,
  618. lineLead));
  619. bestActiveNode = bestActiveNode.previous;
  620. }
  621. if (bForced) {
  622. fallback(par, line);
  623. }
  624. activeList.clear();
  625. inactiveList.clear();
  626. return line;
  627. }
  628. private void fallback(Paragraph par, int line) {
  629. // lead to baseline is
  630. // max of: baseline fixed alignment and middle/2
  631. // after baseline is
  632. // max: top height-lead, middle/2 and bottom height-lead
  633. int halfLeading = (lineHeight - lead - follow) / 2;
  634. // height before baseline
  635. int lineLead = lead + halfLeading;
  636. // maximum size of top and bottom alignment
  637. int maxtb = follow + halfLeading;
  638. // max size of middle alignment below baseline
  639. int middlefollow = maxtb;
  640. ListIterator inlineIterator
  641. = par.listIterator(lastDeactivatedNode.position);
  642. for (int j = lastDeactivatedNode.position;
  643. j < (par.size());
  644. j++) {
  645. KnuthElement element = (KnuthElement) inlineIterator.next();
  646. if (element.isBox()) {
  647. if (((KnuthBox) element).getLead() > lineLead) {
  648. lineLead = ((KnuthBox) element).getLead();
  649. }
  650. if (((KnuthBox) element).getTotal() > maxtb) {
  651. maxtb = ((KnuthBox) element).getTotal();
  652. }
  653. if (((KnuthBox) element).getMiddle() > middlefollow) {
  654. middlefollow = ((KnuthBox) element).getMiddle();
  655. }
  656. }
  657. }
  658. if (maxtb - lineLead > middlefollow) {
  659. middlefollow = maxtb - lineLead;
  660. }
  661. breakpoints.add(line,
  662. new LineBreakPosition(this, par.size() - 1,
  663. 0, 0, 0,
  664. lineLead + middlefollow,
  665. lineLead));
  666. }
  667. private void considerLegalBreak(LinkedList par, int lineWidth,
  668. KnuthElement element,
  669. int totalWidth, int totalStretch,
  670. int totalShrink, double threshold) {
  671. KnuthNode activeNode = null;
  672. ListIterator activeListIterator = activeList.listIterator();
  673. if (activeListIterator.hasNext()) {
  674. activeNode = (KnuthNode) activeListIterator.next();
  675. } else {
  676. activeNode = null;
  677. }
  678. while (activeNode != null) {
  679. BestRecords best = new BestRecords();
  680. // these are the new values that must be computed
  681. // in order to define a new active node
  682. int newLine = 0;
  683. int newFitnessClass = 0;
  684. int newWidth = 0;
  685. int newStretch = 0;
  686. int newShrink = 0;
  687. double newIPDAdjust = 0;
  688. double newDemerits = 0;
  689. while (activeNode != null) {
  690. // compute the line number
  691. newLine = activeNode.line + 1;
  692. // compute the adjustment ratio
  693. int actualWidth = totalWidth - activeNode.totalWidth;
  694. if (element.isPenalty()) {
  695. actualWidth += element.getW();
  696. }
  697. int neededAdjustment = lineWidth - actualWidth;
  698. int maxAdjustment = 0;
  699. if (neededAdjustment > 0) {
  700. maxAdjustment = totalStretch - activeNode.totalStretch;
  701. if (maxAdjustment > 0) {
  702. newIPDAdjust
  703. = (double) neededAdjustment / maxAdjustment;
  704. } else {
  705. newIPDAdjust = INFINITE_RATIO;
  706. }
  707. } else if (neededAdjustment < 0) {
  708. maxAdjustment = totalShrink - activeNode.totalShrink;
  709. if (maxAdjustment > 0) {
  710. newIPDAdjust
  711. = (double) neededAdjustment / maxAdjustment;
  712. } else {
  713. newIPDAdjust = INFINITE_RATIO;
  714. }
  715. } else {
  716. // neededAdjustment == 0
  717. newIPDAdjust = 0;
  718. }
  719. if (newIPDAdjust < -1
  720. || (element.isPenalty()
  721. && ((KnuthPenalty) element).getP()
  722. == -KnuthElement.INFINITE)
  723. && !(activeNode.position == par.indexOf(element))) {
  724. // deactivate activeNode
  725. KnuthNode tempNode
  726. = (KnuthNode) activeListIterator.previous();
  727. int iCallNext = 0;
  728. while (tempNode != activeNode) {
  729. // this is not the node we meant to remove!
  730. tempNode = (KnuthNode) activeListIterator.previous();
  731. iCallNext ++;
  732. }
  733. lastDeactivatedNode = tempNode;
  734. inactiveList.add(tempNode);
  735. activeListIterator.remove();
  736. for (int i = 0; i < iCallNext; i++) {
  737. activeListIterator.next();
  738. }
  739. }
  740. if ((-1 <= newIPDAdjust) && (newIPDAdjust <= threshold)) {
  741. // compute demerits and fitness class
  742. if (element.isPenalty()
  743. && ((KnuthPenalty) element).getP() >= 0) {
  744. newDemerits
  745. = Math.pow((1
  746. + 100 * Math.pow(Math.abs(newIPDAdjust), 3)
  747. + ((KnuthPenalty) element).getP()), 2);
  748. } else if (element.isPenalty()
  749. && ((KnuthPenalty) element).getP()
  750. > -INFINITE_RATIO) {
  751. newDemerits
  752. = Math.pow((1
  753. + 100 * Math.pow(Math.abs(newIPDAdjust), 3)), 2)
  754. - Math.pow(((KnuthPenalty) element).getP(), 2);
  755. } else {
  756. newDemerits
  757. = Math.pow((1
  758. + 100 * Math.pow(Math.abs(newIPDAdjust), 3)), 2);
  759. }
  760. if (element.isPenalty()
  761. && ((KnuthPenalty) element).isFlagged()
  762. && ((KnuthElement) par.get(activeNode.position)).isPenalty()
  763. && ((KnuthPenalty) par.get(activeNode.position)).isFlagged()) {
  764. // add demerit for consecutive breaks at flagged penalties
  765. newDemerits += repeatedFlaggedDemerit;
  766. }
  767. if (newIPDAdjust < -0.5) {
  768. newFitnessClass = 0;
  769. } else if (newIPDAdjust <= 0.5) {
  770. newFitnessClass = 1;
  771. } else if (newIPDAdjust <= 1) {
  772. newFitnessClass = 2;
  773. } else {
  774. newFitnessClass = 3;
  775. }
  776. if (Math.abs(newFitnessClass - activeNode.fitness) > 1) {
  777. // add demerit for consecutive breaks
  778. // with very different fitness classes
  779. newDemerits += incompatibleFitnessDemerit;
  780. }
  781. newDemerits += activeNode.totalDemerits;
  782. if (newDemerits < best.getDemerits(newFitnessClass)) {
  783. // updates best demerits data
  784. best.addRecord(newDemerits, activeNode, newIPDAdjust,
  785. neededAdjustment, newFitnessClass);
  786. }
  787. }
  788. if (activeListIterator.hasNext()) {
  789. activeNode = (KnuthNode) activeListIterator.next();
  790. } else {
  791. activeNode = null;
  792. break;
  793. }
  794. if (activeNode.line >= newLine) {
  795. break;
  796. }
  797. } // end of the inner while
  798. if (best.hasRecords()) {
  799. // compute width, stratchability and shrinkability
  800. newWidth = totalWidth;
  801. newStretch = totalStretch;
  802. newShrink = totalShrink;
  803. ListIterator tempIterator
  804. = par.listIterator(par.indexOf(element));
  805. while (tempIterator.hasNext()) {
  806. KnuthElement tempElement
  807. = (KnuthElement) tempIterator.next();
  808. if (tempElement.isBox()) {
  809. break;
  810. } else if (tempElement.isGlue()) {
  811. newWidth += ((KnuthGlue) tempElement).getW();
  812. newStretch += ((KnuthGlue) tempElement).getY();
  813. newShrink += ((KnuthGlue) tempElement).getZ();
  814. } else if (((KnuthPenalty) tempElement).getP()
  815. == -KnuthElement.INFINITE
  816. && tempElement != element) {
  817. break;
  818. }
  819. }
  820. // add nodes to the active nodes list
  821. for (int i = 0; i <= 3; i++) {
  822. if (best.notInfiniteDemerits(i)
  823. && best.getDemerits(i)
  824. <= (best.getMinDemerits()
  825. + incompatibleFitnessDemerit)) {
  826. // the nodes in activeList must be ordered
  827. // by line number and position;
  828. // so:
  829. // 1) advance in the list until the end,
  830. // or a node with a higher line number, is reached
  831. int iStepsForward = 0;
  832. KnuthNode tempNode;
  833. while (activeListIterator.hasNext()) {
  834. iStepsForward ++;
  835. tempNode = (KnuthNode) activeListIterator.next();
  836. if (tempNode.line > (best.getNode(i).line + 1)) {
  837. activeListIterator.previous();
  838. iStepsForward --;
  839. break;
  840. }
  841. }
  842. // 2) add the new node
  843. activeListIterator.add
  844. (new KnuthNode(par.indexOf(element),
  845. best.getNode(i).line + 1, i,
  846. newWidth, newStretch, newShrink,
  847. best.getAdjust(i),
  848. best.getDifference(i),
  849. best.getDemerits(i),
  850. best.getNode(i)));
  851. // 3) go back
  852. for (int j = 0;
  853. j <= iStepsForward;
  854. j ++) {
  855. activeListIterator.previous();
  856. }
  857. }
  858. }
  859. }
  860. if (activeNode == null) {
  861. break;
  862. }
  863. } // end of the outer while
  864. }
  865. /**
  866. * find hyphenation points for every word int the current paragraph
  867. * @ param currPar the paragraph whose words will be hyphenated
  868. */
  869. private void findHyphenationPoints(Paragraph currPar){
  870. // hyphenate every word
  871. ListIterator currParIterator
  872. = currPar.listIterator(currPar.ignoreAtStart);
  873. // list of TLM involved in hyphenation
  874. LinkedList updateList = new LinkedList();
  875. KnuthElement firstElement = null;
  876. KnuthElement nextElement = null;
  877. // current TextLayoutManager
  878. LayoutManager currLM = null;
  879. // number of KnuthBox elements containing word fragments
  880. int boxCount;
  881. // number of auxiliary KnuthElements between KnuthBoxes
  882. int auxCount;
  883. StringBuffer sbChars = null;
  884. // find all hyphenation points
  885. while (currParIterator.hasNext()) {
  886. firstElement = (KnuthElement) currParIterator.next();
  887. //
  888. if (firstElement.getLayoutManager() != currLM) {
  889. currLM = firstElement.getLayoutManager();
  890. if (currLM != null) {
  891. updateList.add(new Update(currLM, currParIterator.previousIndex()));
  892. } else {
  893. break;
  894. }
  895. }
  896. // collect word fragments, ignoring auxiliary elements;
  897. // each word fragment was created by a different TextLM
  898. if (firstElement.isBox() && !firstElement.isAuxiliary()) {
  899. boxCount = 1;
  900. auxCount = 0;
  901. sbChars = new StringBuffer();
  902. currLM.getWordChars(sbChars, firstElement.getPosition());
  903. // look if next elements are boxes too
  904. while (currParIterator.hasNext()) {
  905. nextElement = (KnuthElement) currParIterator.next();
  906. if (nextElement.isBox() && !nextElement.isAuxiliary()) {
  907. // a non-auxiliary KnuthBox: append word chars
  908. if (currLM != nextElement.getLayoutManager()) {
  909. currLM = nextElement.getLayoutManager();
  910. updateList.add(new Update(currLM, currParIterator.previousIndex()));
  911. }
  912. // append text to recreate the whole word
  913. boxCount ++;
  914. currLM.getWordChars(sbChars, nextElement.getPosition());
  915. } else if (!nextElement.isAuxiliary()) {
  916. // a non-auxiliary non-box KnuthElement: stop
  917. // go back to the last box or auxiliary element
  918. currParIterator.previous();
  919. break;
  920. } else {
  921. // an auxiliary KnuthElement: simply ignore it
  922. auxCount ++;
  923. }
  924. }
  925. log.trace(" Word to hyphenate: " + sbChars.toString());
  926. // find hyphenation points
  927. HyphContext hc = getHyphenContext(sbChars);
  928. // ask each LM to hyphenate its word fragment
  929. if (hc != null) {
  930. KnuthElement element = null;
  931. for (int i = 0; i < (boxCount + auxCount); i++) {
  932. currParIterator.previous();
  933. }
  934. for (int i = 0; i < (boxCount + auxCount); i++) {
  935. element = (KnuthElement) currParIterator.next();
  936. if (element.isBox() && !element.isAuxiliary()) {
  937. element.getLayoutManager().hyphenate(element.getPosition(), hc);
  938. } else {
  939. // nothing to do, element is an auxiliary KnuthElement
  940. }
  941. }
  942. }
  943. }
  944. }
  945. // create iterator for the updateList
  946. ListIterator updateListIterator = updateList.listIterator();
  947. Update currUpdate = null;
  948. int iPreservedElements = 0;
  949. int iAddedElements = 0;
  950. int iRemovedElements = 0;
  951. while (updateListIterator.hasNext()) {
  952. // ask the LMs to apply the changes and return
  953. // the new KnuthElements to replace the old ones
  954. currUpdate = (Update) updateListIterator.next();
  955. int fromIndex = currUpdate.iFirstIndex;
  956. int toIndex;
  957. if (updateListIterator.hasNext()) {
  958. Update nextUpdate = (Update) updateListIterator.next();
  959. toIndex = nextUpdate.iFirstIndex;
  960. updateListIterator.previous();
  961. } else {
  962. // maybe this is not always correct!
  963. toIndex = currPar.size() - currPar.ignoreAtEnd
  964. - iAddedElements;
  965. }
  966. // applyChanges() returns true if the LM modifies its data,
  967. // so it must return new KnuthElements to replace the old ones
  968. if (currUpdate.inlineLM
  969. .applyChanges(currPar.subList(fromIndex + iAddedElements,
  970. toIndex + iAddedElements))) {
  971. // insert the new KnuthElements
  972. LinkedList newElements = null;
  973. newElements
  974. = currUpdate.inlineLM.getChangedKnuthElements
  975. (currPar.subList(fromIndex + iAddedElements,
  976. toIndex + iAddedElements),
  977. flaggedPenalty, effectiveAlignment);
  978. // remove the old elements
  979. currPar.subList(fromIndex + iAddedElements,
  980. toIndex + iAddedElements).clear();
  981. // insert the new elements
  982. currPar.addAll(fromIndex + iAddedElements, newElements);
  983. iAddedElements += newElements.size() - (toIndex - fromIndex);
  984. }
  985. }
  986. updateListIterator = null;
  987. updateList.clear();
  988. }
  989. private void resetBP(BreakPoss resetBP) {
  990. if (resetBP == null) {
  991. reset((Position) null);
  992. } else {
  993. while (vecInlineBreaks.get(vecInlineBreaks.size() - 1) != resetBP) {
  994. vecInlineBreaks.remove(vecInlineBreaks.size() - 1);
  995. }
  996. reset(resetBP.getPosition());
  997. }
  998. }
  999. private void reset() {
  1000. resetBP(prevBP);
  1001. }
  1002. protected boolean couldEndLine(BreakPoss bp) {
  1003. if (bp.canBreakAfter()) {
  1004. return true; // no keep, ends on break char
  1005. } else if (bp.isSuppressible()) {
  1006. // NOTE: except at end of content for this LM!!
  1007. // Never break after only space chars or any other sequence
  1008. // of areas which would be suppressed at the end of the line.
  1009. return false;
  1010. } else {
  1011. // See if could break before next area
  1012. // TODO: do we need to set anything on the layout context?
  1013. LayoutContext lc = new LayoutContext(0);
  1014. LayoutManager nextLM = getChildLM();
  1015. return (nextLM == null || nextLM.canBreakBefore(lc));
  1016. }
  1017. }
  1018. private BreakPoss getBestBP(ArrayList vecPossEnd) {
  1019. if (vecPossEnd.size() == 1) {
  1020. return ((BreakCost) vecPossEnd.get(0)).getBP();
  1021. }
  1022. // Choose the best break (use a sort on cost!)
  1023. Iterator iter = vecPossEnd.iterator();
  1024. int minCost = Integer.MAX_VALUE;
  1025. BreakPoss bestBP = null;
  1026. while (iter.hasNext()) {
  1027. BreakCost bc = (BreakCost) iter.next();
  1028. if (bc.getCost() < minCost) {
  1029. minCost = bc.getCost();
  1030. bestBP = bc.getBP();
  1031. }
  1032. }
  1033. return bestBP;
  1034. }
  1035. /** Line area is always considered to act as a fence. */
  1036. protected boolean hasLeadingFence(boolean bNotFirst) {
  1037. return true;
  1038. }
  1039. /** Line area is always considered to act as a fence. */
  1040. protected boolean hasTrailingFence(boolean bNotLast) {
  1041. return true;
  1042. }
  1043. /** Return true if we are at the end of this LM,
  1044. and BPs after prev have been added to vecInlineBreaks
  1045. and all breakposs in vecInlineBreaks
  1046. back to and excluding prev are suppressible */
  1047. private boolean condAllAreSuppressible(BreakPoss prev) {
  1048. if (!isFinished()) {
  1049. return false;
  1050. }
  1051. if (vecInlineBreaks.get(vecInlineBreaks.size() - 1) == prev) {
  1052. return false;
  1053. }
  1054. return allAreSuppressible(prev);
  1055. }
  1056. /** Test whether all breakposs in vecInlineBreaks
  1057. back to and excluding prev are suppressible */
  1058. private boolean allAreSuppressible(BreakPoss prev) {
  1059. ListIterator bpIter =
  1060. vecInlineBreaks.listIterator(vecInlineBreaks.size());
  1061. boolean allAreSuppressible = true;
  1062. BreakPoss bp;
  1063. while (bpIter.hasPrevious()
  1064. && (bp = (BreakPoss) bpIter.previous()) != prev
  1065. && (allAreSuppressible = bp.isSuppressible())) {
  1066. }
  1067. return allAreSuppressible;
  1068. }
  1069. /** Remove all BPs from the end back to and excluding prev
  1070. from vecInlineBreaks*/
  1071. private void removeAllBP(BreakPoss prev) {
  1072. int iPrev;
  1073. if (prev == null) {
  1074. vecInlineBreaks.clear();
  1075. } else if ((iPrev = vecInlineBreaks.indexOf(prev)) != -1) {
  1076. for (int i = vecInlineBreaks.size()-1; iPrev < i; --i) {
  1077. vecInlineBreaks.remove(i);
  1078. }
  1079. }
  1080. }
  1081. private HyphContext getHyphenContext(StringBuffer sbChars) {
  1082. // Find all hyphenation points in this word
  1083. // (get in an array of offsets)
  1084. // hyphProps are from the block level?.
  1085. // Note that according to the spec,
  1086. // they also "apply to" fo:character.
  1087. // I don't know what that means, since
  1088. // if we change language in the middle of a "word",
  1089. // the effect would seem quite strange!
  1090. // Or perhaps in that case, we say that it's several words.
  1091. // We probably should bring the hyphenation props up from the actual
  1092. // TextLM which generate the hyphenation buffer,
  1093. // since these properties inherit and could be specified
  1094. // on an inline or wrapper below the block level.
  1095. Hyphenation hyph
  1096. = Hyphenator.hyphenate(hyphProps.language,
  1097. hyphProps.country, sbChars.toString(),
  1098. hyphProps.hyphenationRemainCharacterCount,
  1099. hyphProps.hyphenationPushCharacterCount);
  1100. // They hyph structure contains the information we need
  1101. // Now start from prev: reset to that position, ask that LM to get
  1102. // a Position for the first hyphenation offset. If the offset isn't in
  1103. // its characters, it returns null,
  1104. // but must tell how many chars it had.
  1105. // Keep looking at currentBP using next hyphenation point until the
  1106. // returned size is greater than the available size
  1107. // or no more hyphenation points remain. Choose the best break.
  1108. if (hyph != null) {
  1109. return new HyphContext(hyph.getHyphenationPoints());
  1110. } else {
  1111. return null;
  1112. }
  1113. }
  1114. /**
  1115. * Make a line break for returning as the next break.
  1116. * This makes the line break and calculates the height and
  1117. * ipd adjustment factors.
  1118. *
  1119. * @param prevLineEnd previous line break index
  1120. * @param target the target ipd value
  1121. * @param textalign the text align in operation for this line
  1122. * @return the line break position
  1123. */
  1124. private BreakPoss makeLineBreak(int prevLineEnd, MinOptMax target,
  1125. int textalign) {
  1126. // make a new BP
  1127. // Store information needed to make areas in the LineBreakPosition!
  1128. // lead to baseline is
  1129. // max of: baseline fixed alignment and middle/2
  1130. // after baseline is
  1131. // max: top height-lead, middle/2 and bottom height-lead
  1132. int halfLeading = (lineHeight - lead - follow) / 2;
  1133. // height before baseline
  1134. int lineLead = lead + halfLeading;
  1135. // maximum size of top and bottom alignment
  1136. int maxtb = follow + halfLeading;
  1137. // max size of middle alignment below baseline
  1138. int middlefollow = maxtb;
  1139. // calculate actual ipd
  1140. MinOptMax actual = new MinOptMax();
  1141. BreakPoss lastBP = null;
  1142. LayoutManager lastLM = null;
  1143. for (Iterator iter = vecInlineBreaks.listIterator(prevLineEnd);
  1144. iter.hasNext();) {
  1145. BreakPoss bp = (BreakPoss) iter.next();
  1146. if (bp.getLead() > lineLead) {
  1147. lineLead = bp.getLead();
  1148. }
  1149. if (bp.getTotal() > maxtb) {
  1150. maxtb = bp.getTotal();
  1151. }
  1152. if (bp.getMiddle() > middlefollow) {
  1153. middlefollow = bp.getMiddle();
  1154. }
  1155. // the stacking size of textLM accumulate for each break
  1156. // so the ipd is only added at the end of each LM
  1157. if (bp.getLayoutManager() != lastLM) {
  1158. if (lastLM != null) {
  1159. actual.add(lastBP.getStackingSize());
  1160. }
  1161. lastLM = bp.getLayoutManager();
  1162. }
  1163. lastBP = bp;
  1164. }
  1165. if (lastBP != null) {
  1166. // add final ipd
  1167. actual.add(lastBP.getStackingSize());
  1168. // ATTENTION: make sure this hasn't gotten start space for next
  1169. // LM added onto it!
  1170. actual.add(lastBP.resolveTrailingSpace(true));
  1171. }
  1172. if (maxtb - lineLead > middlefollow) {
  1173. middlefollow = maxtb - lineLead;
  1174. }
  1175. // in 7.21.4 the spec suggests that the leader and other
  1176. // similar min/opt/max areas should be adjusted before
  1177. // adjusting word spacing
  1178. // Calculate stretch or shrink factor
  1179. double ipdAdjust = 0;
  1180. int targetWith = target.opt;
  1181. int realWidth = actual.opt;
  1182. if (actual.opt > targetWith) {
  1183. if (actual.opt - targetWith < (actual.opt - actual.min)) {
  1184. ipdAdjust = -(actual.opt - targetWith)
  1185. / (float) (actual.opt - actual.min);
  1186. realWidth = targetWith;
  1187. } else {
  1188. ipdAdjust = -1;
  1189. realWidth = actual.min;
  1190. }
  1191. } else {
  1192. if (targetWith - actual.opt < actual.max - actual.opt) {
  1193. ipdAdjust = (targetWith - actual.opt)
  1194. / (float) (actual.max - actual.opt);
  1195. realWidth = targetWith;
  1196. } else {
  1197. ipdAdjust = 1;
  1198. realWidth = actual.max;
  1199. }
  1200. }
  1201. // if justifying then set the space adjustment
  1202. // after the normal ipd adjustment
  1203. double dAdjust = 0.0;
  1204. int indent = 0;
  1205. switch (textalign) {
  1206. case TextAlign.JUSTIFY:
  1207. if (realWidth != 0) {
  1208. dAdjust = (double) (targetWith - realWidth) / realWidth;
  1209. }
  1210. break;
  1211. case TextAlign.START:
  1212. if (prevLineEnd == 0) {
  1213. indent = textIndent.getValue();
  1214. }
  1215. break;
  1216. case TextAlign.CENTER:
  1217. indent = (targetWith - realWidth) / 2;
  1218. break;
  1219. case TextAlign.END:
  1220. indent = targetWith - realWidth;
  1221. break;
  1222. }
  1223. LineBreakPosition lbp;
  1224. lbp = new LineBreakPosition(this,
  1225. vecInlineBreaks.size() - 1,
  1226. ipdAdjust, dAdjust, indent,
  1227. lineLead + middlefollow, lineLead);
  1228. BreakPoss curLineBP = new BreakPoss(lbp);
  1229. curLineBP.setFlag(BreakPoss.ISLAST, isFinished());
  1230. curLineBP.setStackingSize(new MinOptMax(lineLead + middlefollow));
  1231. return curLineBP;
  1232. }
  1233. /**
  1234. * Reset the positions to the given position.
  1235. *
  1236. * @param resetPos the position to reset to
  1237. */
  1238. public void resetPosition(Position resetPos) {
  1239. if (resetPos == null) {
  1240. setFinished(false);
  1241. iReturnedLBP = 0;
  1242. } else {
  1243. if (isFinished()) {
  1244. // if isFinished is true, iReturned LBP == breakpoints.size()
  1245. // and breakpoints.get(iReturnedLBP) would generate
  1246. // an IndexOutOfBoundException
  1247. setFinished(false);
  1248. iReturnedLBP--;
  1249. }
  1250. while ((LineBreakPosition) breakpoints.get(iReturnedLBP)
  1251. != (LineBreakPosition) resetPos) {
  1252. iReturnedLBP --;
  1253. }
  1254. iReturnedLBP ++;
  1255. }
  1256. }
  1257. /**
  1258. * Add the areas with the break points.
  1259. *
  1260. * @param parentIter the iterator of break positions
  1261. * @param context the context for adding areas
  1262. */
  1263. public void addAreas(PositionIterator parentIter,
  1264. LayoutContext context) {
  1265. addAreas(parentIter, 0.0);
  1266. //vecInlineBreaks.clear();
  1267. prevBP = null;
  1268. }
  1269. // Generate and add areas to parent area
  1270. // Set size etc
  1271. // dSpaceAdjust should reference extra space in the BPD
  1272. /**
  1273. * Add the areas with the associated space adjustment.
  1274. *
  1275. * @param parentIter the iterator of breaks positions
  1276. * @param dSpaceAdjust the space adjustment
  1277. */
  1278. public void addAreas(PositionIterator parentIter, double dSpaceAdjust) {
  1279. LayoutManager childLM;
  1280. LayoutContext lc = new LayoutContext(0);
  1281. iCurrParIndex = 0;
  1282. while (parentIter.hasNext()) {
  1283. ListIterator paragraphIterator = null;
  1284. KnuthElement tempElement = null;
  1285. // the TLM which created the last KnuthElement in this line
  1286. LayoutManager lastLM = null;
  1287. LineBreakPosition lbp = (LineBreakPosition) parentIter.next();
  1288. LineArea lineArea = new LineArea();
  1289. lineArea.setStartIndent(lbp.startIndent);
  1290. lineArea.setBPD(lbp.lineHeight);
  1291. lc.setBaseline(lbp.baseline);
  1292. lc.setLineHeight(lbp.lineHeight);
  1293. setCurrentArea(lineArea);
  1294. Paragraph currPar = (Paragraph) knuthParagraphs.get(iCurrParIndex);
  1295. iEndElement = lbp.getLeafPos();
  1296. // ignore the first elements added by the LineLayoutManager
  1297. iStartElement += (iStartElement == 0) ? currPar.ignoreAtStart : 0;
  1298. // ignore the last elements added by the LineLayoutManager
  1299. iEndElement -= (iEndElement == (currPar.size() - 1))
  1300. ? currPar.ignoreAtEnd : 0;
  1301. // ignore the last element in the line if it is a KnuthGlue object
  1302. paragraphIterator = currPar.listIterator(iEndElement);
  1303. tempElement = (KnuthElement) paragraphIterator.next();
  1304. if (tempElement.isGlue()) {
  1305. iEndElement --;
  1306. // this returns the same KnuthElement
  1307. paragraphIterator.previous();
  1308. tempElement = (KnuthElement) paragraphIterator.previous();
  1309. }
  1310. lastLM = tempElement.getLayoutManager();
  1311. // ignore KnuthGlue and KnuthPenalty objects
  1312. // at the beginning of the line
  1313. paragraphIterator = currPar.listIterator(iStartElement);
  1314. tempElement = (KnuthElement) paragraphIterator.next();
  1315. while (!tempElement.isBox() && paragraphIterator.hasNext()) {
  1316. tempElement = (KnuthElement) paragraphIterator.next();
  1317. iStartElement ++;
  1318. }
  1319. // Add the inline areas to lineArea
  1320. PositionIterator inlinePosIter
  1321. = new KnuthPossPosIter(currPar, iStartElement,
  1322. iEndElement + 1);
  1323. iStartElement = lbp.getLeafPos() + 1;
  1324. if (iStartElement == currPar.size()) {
  1325. // advance to next paragraph
  1326. iCurrParIndex++;
  1327. iStartElement = 0;
  1328. }
  1329. lc.setSpaceAdjust(lbp.dAdjust);
  1330. lc.setIPDAdjust(lbp.ipdAdjust);
  1331. lc.setLeadingSpace(new SpaceSpecifier(true));
  1332. lc.setTrailingSpace(new SpaceSpecifier(false));
  1333. lc.setFlags(LayoutContext.RESOLVE_LEADING_SPACE, true);
  1334. setChildContext(lc);
  1335. while ((childLM = inlinePosIter.getNextChildLM()) != null) {
  1336. lc.setFlags(LayoutContext.LAST_AREA, (childLM == lastLM));
  1337. childLM.addAreas(inlinePosIter, lc);
  1338. lc.setLeadingSpace(lc.getTrailingSpace());
  1339. lc.setTrailingSpace(new SpaceSpecifier(false));
  1340. }
  1341. // when can this be null?
  1342. if (lc.getTrailingSpace() != null) {
  1343. addSpace(lineArea, lc.getTrailingSpace().resolve(true),
  1344. lc.getSpaceAdjust());
  1345. }
  1346. parentLM.addChild(lineArea);
  1347. }
  1348. setCurrentArea(null); // ?? necessary
  1349. }
  1350. /**
  1351. * Add an unresolved area.
  1352. * If a child layout manager needs to add an unresolved area
  1353. * for page reference or linking then this intercepts it for
  1354. * line area handling.
  1355. * A line area may need to have the inline areas adjusted
  1356. * to properly fill the line area. This adds a resolver that
  1357. * resolves the inline area and can do the necessary
  1358. * adjustments to the line and inline areas.
  1359. *
  1360. * @param id the id reference of the resolvable
  1361. * @param res the resolvable object
  1362. */
  1363. public void addUnresolvedArea(String id, Resolvable res) {
  1364. // create a resolvable class that handles ipd
  1365. // adjustment for the current line
  1366. parentLM.addUnresolvedArea(id, res);
  1367. }
  1368. }