You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

LineLayoutManager.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. /*
  2. * $Id$
  3. * Copyright (C) 2001 The Apache Software Foundation. All rights reserved.
  4. * For details on use and redistribution please refer to the
  5. * LICENSE file included with these sources.
  6. */
  7. package org.apache.fop.layoutmgr;
  8. import org.apache.fop.fo.FObj;
  9. import org.apache.fop.area.Area;
  10. import org.apache.fop.area.LineArea;
  11. import org.apache.fop.area.MinOptMax;
  12. import org.apache.fop.area.inline.InlineArea;
  13. import org.apache.fop.fo.properties.VerticalAlign;
  14. import org.apache.fop.area.inline.Word;
  15. import org.apache.fop.area.inline.Space;
  16. import org.apache.fop.area.inline.Character;
  17. import java.util.ListIterator;
  18. import java.util.List;
  19. import java.util.ArrayList;
  20. import java.util.Iterator;
  21. /**
  22. * LayoutManager for lines. It builds one or more lines containing
  23. * inline areas generated by its sub layout managers.
  24. *
  25. * The line layout manager does the following things:
  26. * receives a list of inline creating layout managers
  27. * adds the inline areas retrieved from the child layout managers
  28. * finds the best line break position
  29. * adds complete line to parent
  30. * stores the starting position for each line in case of recreation
  31. * if ipd not changed but line contains resolved values (eg. page number), redoes from that line
  32. * when freeing memory, release all layout managers and inline areas before current position
  33. * As each child layout manager is used it gets the start, end and normal references for id area, footnotes, floats, links, colour-back properties
  34. * first line properties are set and used by the child when retrieving the inline area(s)
  35. *
  36. * Hyphenation is handled by asking the child to split the words then this
  37. * adds the hyph char. If redone then exra char ignored.
  38. *
  39. * How do we handle Unicode BIDI?
  40. */
  41. public class LineLayoutManager extends AbstractBPLayoutManager {
  42. private LineInfo currentLine = null;
  43. private boolean bFirstLine = true;
  44. private MinOptMax totalIPD;
  45. // the following values must be set by the block
  46. // these are the dominant basline and lineheight values
  47. private int lineHeight;
  48. private int lead;
  49. private int follow;
  50. List lmList;
  51. List lines = new ArrayList();
  52. private LayoutPos bestPos = null;
  53. private MinOptMax bestIPD = null;
  54. static class LineInfo {
  55. LayoutPos startPos;
  56. LineArea area;
  57. boolean hasResolved = false;
  58. boolean noJustify = false;
  59. // footnotes, floats?
  60. }
  61. public LineLayoutManager(FObj fobjBlock, List lms, int lh, int l, int f) {
  62. super(fobjBlock);
  63. lmList = lms;
  64. lineHeight = lh;
  65. lead = l;
  66. follow = f;
  67. }
  68. public int getContentIPD() {
  69. return parentLM.getContentIPD();
  70. }
  71. /**
  72. * Call child layout managers to generate content as long as they
  73. * generate inline areas. If a block-level generating LM is found,
  74. * finish any line being filled and return to the parent LM.
  75. */
  76. public boolean generateAreas() {
  77. // if a side float is added and the line contains content
  78. // where the ipd depends on the line width then restart
  79. // the line with the adjusted length
  80. while (curPos.lmIndex < lmList.size()) {
  81. LeafNodeLayoutManager curLM =
  82. (LeafNodeLayoutManager) lmList.get(curPos.lmIndex);
  83. curLM.setParentLM(this);
  84. LeafNodeLayoutManager nextLM = null;
  85. if (curPos.lmIndex + 1 < lmList.size()) {
  86. nextLM = (LeafNodeLayoutManager) lmList.get(
  87. curPos.lmIndex + 1);
  88. while (nextLM.size() == 0) {
  89. lmList.remove(curPos.lmIndex + 1);
  90. if (curPos.lmIndex + 1 == lmList.size()) {
  91. nextLM = null;
  92. break;
  93. }
  94. nextLM = (LeafNodeLayoutManager) lmList.get(
  95. curPos.lmIndex + 1);
  96. }
  97. }
  98. if (nextLM != null) {
  99. nextLM.setParentLM(this);
  100. }
  101. if (curLM.resolved()) {
  102. currentLine.hasResolved = true;
  103. }
  104. while (curPos.subIndex < curLM.size()) {
  105. InlineArea ia = curLM.get(curPos.subIndex);
  106. InlineArea next = null;
  107. if (curPos.subIndex + 1 < curLM.size()) {
  108. next = curLM.get(curPos.subIndex + 1);
  109. } else if (curPos.lmIndex + 1 < lmList.size()) {
  110. if (nextLM != null) {
  111. next = nextLM.get(0);
  112. }
  113. }
  114. if (currentLine != null && !currentLine.noJustify &&
  115. (curPos.subIndex + 1 == curLM.size() &&
  116. curPos.lmIndex + 1 == lmList.size())) {
  117. currentLine.noJustify = true;
  118. }
  119. if (addChild(ia, next)) {
  120. if (flush()) {
  121. return true;
  122. }
  123. }
  124. // flush final line in same context as other lines
  125. // handle last line concepts
  126. if (curPos.subIndex + 1 == curLM.size() &&
  127. curPos.lmIndex + 1 == lmList.size()) {
  128. if (flush()) {
  129. return true;
  130. }
  131. if (curPos.subIndex + 1 == curLM.size() &&
  132. curPos.lmIndex + 1 == lmList.size()) {
  133. return false;
  134. }
  135. }
  136. curPos.subIndex++;
  137. }
  138. curPos.lmIndex++;
  139. curPos.subIndex = 0;
  140. }
  141. return false;
  142. }
  143. /**
  144. * Align and position curLine and add it to parentContainer.
  145. * Set curLine to null.
  146. */
  147. public boolean flush() {
  148. if (currentLine != null) {
  149. // Adjust spacing as necessary
  150. adjustSpacing();
  151. verticalAlign(currentLine.area);
  152. boolean res = parentLM.addChild(currentLine.area);
  153. lines.add(currentLine);
  154. currentLine = null;
  155. bestPos = null;
  156. bestIPD = null;
  157. return res;
  158. }
  159. return false;
  160. }
  161. /**
  162. * Do the ipd adjustment for stretch areas etc.
  163. * Consecutive spaces need to be collapsed if possible.
  164. * should this be on the line area so it can finish resolved areas?
  165. */
  166. private void adjustSpacing() {
  167. List inlineAreas = currentLine.area.getInlineAreas();
  168. // group text elements to split at hyphen if available
  169. // remove collapsable spaces at start or end on line
  170. // backtrack to best position
  171. while (true) {
  172. if (curPos.lmIndex == bestPos.lmIndex &&
  173. curPos.subIndex == bestPos.subIndex) {
  174. break;
  175. }
  176. InlineArea inline =
  177. (InlineArea) inlineAreas.get(inlineAreas.size() - 1);
  178. MinOptMax ipd = inline.getAllocationIPD();
  179. totalIPD.subtract(ipd);
  180. inlineAreas.remove(inlineAreas.size() - 1);
  181. currentLine.noJustify = false;
  182. curPos.subIndex--;
  183. if (curPos.subIndex == -1) {
  184. curPos.lmIndex--;
  185. LeafNodeLayoutManager curLM =
  186. (LeafNodeLayoutManager) lmList.get( curPos.lmIndex);
  187. curPos.subIndex = curLM.size() - 1;
  188. }
  189. }
  190. // for justify also stretch spaces to fill
  191. // stretch to best match
  192. float percentAdjust = 0;
  193. boolean maxSide = false;
  194. int realWidth = bestIPD.opt;
  195. if (bestIPD.opt > parentLM.getContentIPD()) {
  196. if (bestIPD.opt - parentLM.getContentIPD() <
  197. (bestIPD.max - bestIPD.opt)) {
  198. percentAdjust = (bestIPD.opt - parentLM.getContentIPD()) /
  199. (float)(bestIPD.max - bestIPD.opt);
  200. realWidth = parentLM.getContentIPD();
  201. } else {
  202. percentAdjust = 1;
  203. realWidth = bestIPD.max;
  204. }
  205. maxSide = true;
  206. } else {
  207. if (parentLM.getContentIPD() - bestIPD.opt <
  208. bestIPD.opt - bestIPD.min) {
  209. percentAdjust = (parentLM.getContentIPD() - bestIPD.opt) /
  210. (float)(bestIPD.opt - bestIPD.min);
  211. realWidth = parentLM.getContentIPD();
  212. } else {
  213. percentAdjust = 1;
  214. realWidth = bestIPD.min;
  215. }
  216. }
  217. if (percentAdjust > 0) {
  218. for (Iterator iter = inlineAreas.iterator(); iter.hasNext();) {
  219. InlineArea inline = (InlineArea) iter.next();
  220. int width;
  221. MinOptMax iipd = inline.getAllocationIPD();
  222. if (!maxSide) {
  223. width = iipd.opt +
  224. (int)((iipd.max - iipd.opt) * percentAdjust);
  225. } else {
  226. width = iipd.opt -
  227. (int)((iipd.opt - iipd.min) * percentAdjust);
  228. }
  229. inline.setWidth(width);
  230. }
  231. }
  232. // don't justify lines ending with U+000A or last line
  233. if (/*justify && */!currentLine.noJustify &&
  234. realWidth != parentLM.getContentIPD()) {
  235. ArrayList spaces = new ArrayList();
  236. for (Iterator iter = inlineAreas.iterator(); iter.hasNext();) {
  237. InlineArea inline = (InlineArea) iter.next();
  238. if (inline instanceof Space /* && !((Space)inline).fixed*/) {
  239. spaces.add(inline);
  240. }
  241. }
  242. for (Iterator iter = spaces.iterator(); iter.hasNext();) {
  243. Space space = (Space) iter.next();
  244. space.setWidth(space.getWidth() +
  245. (parentLM.getContentIPD() - realWidth) /
  246. spaces.size());
  247. }
  248. }
  249. }
  250. protected void verticalAlign(LineArea lineArea) {
  251. int maxHeight = lineHeight;
  252. List inlineAreas = lineArea.getInlineAreas();
  253. // get smallest possible offset to before edge
  254. // this depends on the height of no and middle alignments
  255. int before = lead;
  256. int after = follow;
  257. int halfLeading = (lineHeight - lead - follow) / 2;
  258. before += halfLeading;
  259. for (Iterator iter = inlineAreas.iterator(); iter.hasNext();) {
  260. InlineArea inline = (InlineArea) iter.next();
  261. LayoutInfo info = inline.info;
  262. int al;
  263. int ld = inline.getHeight();
  264. if (info != null) {
  265. al = info.alignment;
  266. ld = info.lead;
  267. } else {
  268. al = VerticalAlign.BASELINE;
  269. }
  270. if (al == VerticalAlign.BASELINE) {
  271. if (ld > before) {
  272. before = ld;
  273. }
  274. if (inline.getHeight() > before) {
  275. before = inline.getHeight();
  276. }
  277. } else if (al == VerticalAlign.MIDDLE) {
  278. if (inline.getHeight() / 2 + lead / 2 > before) {
  279. before = inline.getHeight() / 2 + lead / 2;
  280. }
  281. if (inline.getHeight() / 2 - lead / 2 > after) {
  282. after = inline.getHeight() / 2 - lead / 2;
  283. }
  284. } else if (al == VerticalAlign.TOP) {
  285. } else if (al == VerticalAlign.BOTTOM) {
  286. }
  287. }
  288. // then align all before, no and middle alignment
  289. for (Iterator iter = inlineAreas.iterator(); iter.hasNext();) {
  290. InlineArea inline = (InlineArea) iter.next();
  291. LayoutInfo info = inline.info;
  292. int al;
  293. int ld = inline.getHeight();
  294. boolean bloffset = false;
  295. if (info != null) {
  296. al = info.alignment;
  297. ld = info.lead;
  298. bloffset = info.blOffset;
  299. } else {
  300. al = VerticalAlign.BASELINE;
  301. }
  302. if (al == VerticalAlign.BASELINE) {
  303. // the offset position for text is the baseline
  304. if (bloffset) {
  305. inline.setOffset(before);
  306. } else {
  307. inline.setOffset(before - ld);
  308. }
  309. if (inline.getHeight() - ld > after) {
  310. after = inline.getHeight() - ld;
  311. }
  312. } else if (al == VerticalAlign.MIDDLE) {
  313. inline.setOffset(before - inline.getHeight() / 2 -
  314. lead / 2);
  315. } else if (al == VerticalAlign.TOP) {
  316. inline.setOffset(0);
  317. if (inline.getHeight() - before > after) {
  318. after = inline.getHeight() - before;
  319. }
  320. } else if (al == VerticalAlign.BOTTOM) {
  321. if (inline.getHeight() - before > after) {
  322. after = inline.getHeight() - before;
  323. }
  324. }
  325. }
  326. // after alignment depends on maximum height of before
  327. // and middle alignments
  328. for (Iterator iter = inlineAreas.iterator(); iter.hasNext();) {
  329. InlineArea inline = (InlineArea) iter.next();
  330. LayoutInfo info = inline.info;
  331. int al;
  332. if (info != null) {
  333. al = info.alignment;
  334. } else {
  335. al = VerticalAlign.BASELINE;
  336. }
  337. if (al == VerticalAlign.BASELINE) {
  338. } else if (al == VerticalAlign.MIDDLE) {
  339. } else if (al == VerticalAlign.TOP) {
  340. } else if (al == VerticalAlign.BOTTOM) {
  341. inline.setOffset(before + after - inline.getHeight());
  342. }
  343. }
  344. if (before + after > maxHeight) {
  345. lineArea.setHeight(before + after);
  346. } else {
  347. lineArea.setHeight(maxHeight);
  348. }
  349. }
  350. /**
  351. * Return current lineArea or generate a new one if necessary.
  352. */
  353. public Area getParentArea(Area childArea) {
  354. if (currentLine.area == null) {
  355. createLine();
  356. }
  357. return currentLine.area;
  358. }
  359. protected void createLine() {
  360. currentLine = new LineInfo();
  361. currentLine.startPos = curPos;
  362. currentLine.area = new LineArea();
  363. /* Set line IPD from parentArea
  364. * This accounts for indents. What about first line indent?
  365. * Should we set an "isFirst" flag on the lineArea to signal
  366. * that to the parent (Block) LM? That's where indent property
  367. * information will be managed.
  368. */
  369. Area parent = parentLM.getParentArea(currentLine.area);
  370. // currentLine.area.setContentIPD(parent.getContentIPD());
  371. // totalIPD = new MinOptMax();
  372. // OR???
  373. totalIPD = new MinOptMax();
  374. this.bFirstLine = false;
  375. }
  376. /**
  377. * Called by child LayoutManager when it has filled one of its areas.
  378. * See if the area will fit in the current container.
  379. * If so, add it.
  380. * This should also handle floats if childArea is an anchor.
  381. * @param childArea the area to add: should be an InlineArea subclass!
  382. */
  383. public boolean addChild(InlineArea inlineArea, InlineArea nextArea) {
  384. if (currentLine == null) {
  385. createLine();
  386. }
  387. // add side floats first
  388. int pIPD = parentLM.getContentIPD();
  389. currentLine.area.addInlineArea(inlineArea);
  390. totalIPD.add(inlineArea.getAllocationIPD());
  391. LayoutInfo info = inlineArea.info;
  392. if (info == null) {
  393. info = new LayoutInfo();
  394. }
  395. LayoutInfo ninfo;
  396. if (nextArea != null && nextArea.info != null) {
  397. ninfo = nextArea.info;
  398. } else {
  399. ninfo = new LayoutInfo();
  400. }
  401. // the best pos cannot be before the first area
  402. if (bestPos == null || bestIPD == null) {
  403. bestPos = new LayoutPos();
  404. bestPos.lmIndex = curPos.lmIndex;
  405. bestPos.subIndex = curPos.subIndex;
  406. MinOptMax imop = inlineArea.getAllocationIPD();
  407. bestIPD = new MinOptMax(imop.min, imop.opt, imop.max);
  408. } else {
  409. // bestPos changed only when it can break
  410. // before/after a space or other atomic inlines
  411. // check keep-with on this and next
  412. // since chars are optimized as words we cannot assume a
  413. // word is complete and therefore hyphenate or break after
  414. // side floats effect the available ipd but do not add to line
  415. if (!ninfo.keepPrev && !info.keepNext &&
  416. !(info.isText && ninfo.isText)) {
  417. if (Math.abs(bestIPD.opt - pIPD) >
  418. Math.abs(totalIPD.opt - pIPD) &&
  419. (totalIPD.min <= pIPD)) {
  420. bestPos.lmIndex = curPos.lmIndex;
  421. bestPos.subIndex = curPos.subIndex;
  422. bestIPD = new MinOptMax(totalIPD.min, totalIPD.opt,
  423. totalIPD.max);
  424. }
  425. }
  426. }
  427. // Forced line break after this area (ex. ends with LF in nowrap)
  428. if (info.breakAfter) {
  429. currentLine.noJustify = true;
  430. return true;
  431. }
  432. if (totalIPD.min > pIPD) {
  433. return true;
  434. }
  435. return false;
  436. }
  437. public boolean addChild(Area childArea) {
  438. return false;
  439. }
  440. }