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.

TXTHandler.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. /*
  2. * Copyright 2005 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. /* $Id$ */
  17. package org.apache.fop.render.txt;
  18. import java.io.OutputStream;
  19. import java.util.Arrays;
  20. import java.util.Iterator;
  21. import java.util.List;
  22. import org.apache.fop.apps.FOPException;
  23. import org.apache.fop.apps.FOUserAgent;
  24. import org.apache.fop.apps.MimeConstants;
  25. import org.apache.fop.area.AreaTreeHandler;
  26. import org.apache.fop.datatypes.CompoundDatatype;
  27. import org.apache.fop.datatypes.Length;
  28. import org.apache.fop.datatypes.PercentBaseContext;
  29. import org.apache.fop.fo.Constants;
  30. import org.apache.fop.fo.FONode;
  31. import org.apache.fop.fo.FOText;
  32. import org.apache.fop.fo.expr.NumericProperty;
  33. import org.apache.fop.fo.expr.RelativeNumericProperty;
  34. import org.apache.fop.fo.flow.Block;
  35. import org.apache.fop.fo.flow.BlockContainer;
  36. import org.apache.fop.fo.flow.ExternalGraphic;
  37. import org.apache.fop.fo.flow.Inline;
  38. import org.apache.fop.fo.flow.ListBlock;
  39. import org.apache.fop.fo.flow.ListItem;
  40. import org.apache.fop.fo.flow.PageNumber;
  41. import org.apache.fop.fo.flow.Table;
  42. import org.apache.fop.fo.flow.TableCell;
  43. import org.apache.fop.fo.flow.TableColumn;
  44. import org.apache.fop.fo.pagination.PageSequence;
  45. import org.apache.fop.fo.properties.CommonAbsolutePosition;
  46. import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
  47. import org.apache.fop.fo.properties.CommonFont;
  48. import org.apache.fop.fo.properties.CommonMarginBlock;
  49. import org.apache.fop.fo.properties.FixedLength;
  50. import org.apache.fop.fo.properties.Property;
  51. import org.apache.fop.fo.properties.SpaceProperty;
  52. import org.apache.fop.layoutmgr.BlockLayoutManager;
  53. /**
  54. * Handler for formatting objects in case of rendering to txt.
  55. *
  56. * This handler gets page-sequence, modifies formatting objects and return them
  57. * to superclass. So areas are generated from modified FO. Idea of modifying is
  58. * to quantize FO properties, making them divisible by width of char or height
  59. * of char.
  60. */
  61. public class TXTHandler extends AreaTreeHandler {
  62. /** Percent base context. Needed for line-height. */
  63. private static final PercentBaseContext CONTEXT
  64. = new BlockLayoutManager(new Block(null));
  65. /** Modified font size in millipoints. */
  66. private static final int MODIFIED_FONT_SIZE = 10000;
  67. /** Quantum for each side (BEFORE, AFTER, START, END). */
  68. private final int[] quantum = {TXTRenderer.CHAR_HEIGHT,
  69. TXTRenderer.CHAR_HEIGHT, TXTRenderer.CHAR_WIDTH,
  70. TXTRenderer.CHAR_WIDTH};
  71. /** Keeps overpatching for each side. */
  72. private int[] overPatching = new int[4];
  73. /**
  74. * Keeps last overpatching for each side. Needed for selective modifying of
  75. * start-indent and end-indent.
  76. */
  77. private int[] lastOverPatching = new int[4];
  78. /**
  79. * Constructs a newly allocated <code>TXTHandler</code> object.
  80. *
  81. * @param userAgent FOUserAgent
  82. * @param stream OutputStream
  83. * @throws FOPException if the RenderPagesModel cannot be created
  84. */
  85. public TXTHandler(FOUserAgent userAgent, OutputStream stream)
  86. throws FOPException {
  87. super(userAgent, MimeConstants.MIME_PLAIN_TEXT, stream);
  88. }
  89. /**
  90. * Sets a component <code>CP_LENGTH</code> of <code>cd</code> to
  91. * <code>value</code>.
  92. * @param cd CompoundDatatype
  93. * @param value new integer value
  94. */
  95. private static void setLength(CompoundDatatype cd, int value) {
  96. cd.setComponent(Constants.CP_LENGTH, new FixedLength(value), true);
  97. }
  98. /**
  99. * Sets components <code>CP_MINIMUM, CP_OPTIMUM, CP_MAXIMUM</code> of
  100. * <code>cd</code> to <code>p</code>.
  101. *
  102. * @param cd instance of CompoundDatatype for modifying.
  103. * @param p property for setting.
  104. */
  105. private static void setMinOptMax(CompoundDatatype cd, Property p) {
  106. cd.setComponent(Constants.CP_MINIMUM, p, true);
  107. cd.setComponent(Constants.CP_OPTIMUM, p, true);
  108. cd.setComponent(Constants.CP_MAXIMUM, p, true);
  109. }
  110. /**
  111. * Modifies border of side. If there is no border of given side, does
  112. * nothing, otherwise sets border-width to half of char width or char height
  113. * depending on side. <p>
  114. * Difference between values of new border-width and old border-width is
  115. * saved in <code>lastOverPatching</code>.
  116. *
  117. * @param side side to modify.
  118. * @param bpb instance of CommonBorderPaddingBackground for modifying.
  119. */
  120. private void modifyBorder(int side, CommonBorderPaddingBackground bpb) {
  121. CommonBorderPaddingBackground.BorderInfo bi = bpb.getBorderInfo(side);
  122. if (bi != null) {
  123. int width = bpb.getBorderWidth(side, false);
  124. setLength(bi.getWidth(), quantum[side] / 2);
  125. lastOverPatching[side] += bpb.getBorderWidth(side, false) - width;
  126. }
  127. }
  128. /**
  129. * Modifies padding of side. First rounds padding to nearest integer,
  130. * divisible by char width or char height depending on side. If border of
  131. * given side is available, modifies padding in such a way, so sum of border
  132. * width and padding will be divisible by char width or char height,
  133. * depending on side. <p>
  134. * Difference between values of new padding and old padding is saved
  135. * in <code>lastOverPatching</code>.
  136. *
  137. * @param side side to modify.
  138. * @param bpb instance of CommonBorderPaddingBackground for modifying.
  139. */
  140. private void modifyPadding(int side, CommonBorderPaddingBackground bpb) {
  141. int oldPadding = bpb.getPadding(side, false, null);
  142. int newPadding = Helper.round(oldPadding, quantum[side]);
  143. if (bpb.getBorderInfo(side) != null) {
  144. newPadding = Math.max(newPadding, quantum[side])
  145. - bpb.getBorderWidth(side, false);
  146. }
  147. setLength(bpb.getPaddingLengthProperty(side), newPadding);
  148. lastOverPatching[side] += newPadding - oldPadding;
  149. }
  150. /**
  151. * Modifies borders and paddings of <code>bpb</code>.
  152. *
  153. * @param bpb instance of CommonBorderPaddingBackground for modifying.
  154. */
  155. private void modifyBPB(CommonBorderPaddingBackground bpb) {
  156. modifyBorder(CommonBorderPaddingBackground.BEFORE, bpb);
  157. modifyBorder(CommonBorderPaddingBackground.AFTER, bpb);
  158. modifyBorder(CommonBorderPaddingBackground.START, bpb);
  159. modifyBorder(CommonBorderPaddingBackground.END, bpb);
  160. modifyPadding(CommonBorderPaddingBackground.BEFORE, bpb);
  161. modifyPadding(CommonBorderPaddingBackground.AFTER, bpb);
  162. modifyPadding(CommonBorderPaddingBackground.START, bpb);
  163. modifyPadding(CommonBorderPaddingBackground.END, bpb);
  164. }
  165. /**
  166. * Rounds optimum value of <code>space</code> to nearest integer,
  167. * divisible by <code>q</code>.
  168. *
  169. * @param space instance of SpaceProperty.
  170. * @param q integer.
  171. */
  172. private void modifySpace(SpaceProperty space, int q) {
  173. int value = space.getOptimum(null).getLength().getValue();
  174. setMinOptMax(space, new FixedLength(Helper.round(value, q)));
  175. }
  176. /**
  177. * @param length instance of Length.
  178. * @param q integer.
  179. * @return instance of Length, having value nearest to value of
  180. * <code>length</code>, and divisible by <code>q</code>.
  181. */
  182. private Length roundLength(Length length, int q) {
  183. int x = Helper.round(length.getValue(), q);
  184. return new FixedLength(x);
  185. }
  186. /**
  187. * @param length instance of Length.
  188. * @param q integer.
  189. * @return instance of Length, having minimal value, greater value of
  190. * <code>length</code>, and divisible by <code>q</code>.
  191. */
  192. private Length ceilLength(Length length, int q) {
  193. int x = Helper.ceil(length.getValue(), q);
  194. return new FixedLength(x);
  195. }
  196. /**
  197. * Modifies indent for given side. Summarizes value of indent and modifing
  198. * error (i.e. overPatching). Rounds result to nearest integer, divisible by
  199. * quantum.
  200. *
  201. * @param indent Length to modify.
  202. * @param side an integer, representing side.
  203. * @return modified Length.
  204. */
  205. private Length modifyIndent(Length indent, int side) {
  206. if (indent instanceof NumericProperty) {
  207. overPatching[side] += lastOverPatching[side];
  208. }
  209. int newValue = indent.getValue() + overPatching[side];
  210. newValue = Helper.round(newValue, quantum[side]);
  211. return new FixedLength(newValue);
  212. }
  213. /**
  214. * Modifies Common Margin Properties-Block:
  215. * <ul>
  216. * <li>margin-top, margin-left, margin-bottom, margin-right
  217. * <li>start-indent, end-indent
  218. * <li>space-before, space-after.
  219. * </ul>
  220. *
  221. * @param cmb instance of CommonMarginBlock to modify.
  222. */
  223. private void modifyCommonMarginBlock(CommonMarginBlock cmb) {
  224. cmb.marginTop = roundLength(cmb.marginTop, TXTRenderer.CHAR_HEIGHT);
  225. cmb.marginBottom = roundLength(cmb.marginBottom,
  226. TXTRenderer.CHAR_HEIGHT);
  227. cmb.marginLeft = roundLength(cmb.marginLeft, TXTRenderer.CHAR_WIDTH);
  228. cmb.marginRight = roundLength(cmb.marginRight, TXTRenderer.CHAR_WIDTH);
  229. modifySpace(cmb.spaceBefore, TXTRenderer.CHAR_HEIGHT);
  230. modifySpace(cmb.spaceAfter, TXTRenderer.CHAR_HEIGHT);
  231. if (!(cmb.startIndent instanceof RelativeNumericProperty)) {
  232. cmb.startIndent = modifyIndent(cmb.startIndent,
  233. CommonBorderPaddingBackground.START);
  234. }
  235. if (!(cmb.endIndent instanceof RelativeNumericProperty)) {
  236. cmb.endIndent = modifyIndent(cmb.endIndent,
  237. CommonBorderPaddingBackground.END);
  238. }
  239. }
  240. /**
  241. * Modifies fo:table attributes:
  242. * <ul>
  243. * <li>Common Margin Properties Block
  244. * <li>Common Border, Padding, and Background Properties
  245. * <li>columns.
  246. * </ul>
  247. *
  248. * @param table Table to modify.
  249. */
  250. private void modifyTable(Table table) {
  251. CommonMarginBlock cmb = table.getCommonMarginBlock();
  252. if (table.getBorderCollapse() == Constants.EN_COLLAPSE) {
  253. // If border-collapse == "collapse", add space-after in order to
  254. // impove interaction with other FO.
  255. int value = cmb.spaceAfter.getOptimum(null).getLength().getValue();
  256. value += TXTRenderer.CHAR_HEIGHT;
  257. setMinOptMax(cmb.spaceAfter, new FixedLength(value));
  258. }
  259. modifyCommonMarginBlock(cmb);
  260. modifyBPB(table.getCommonBorderPaddingBackground());
  261. // modify all table-columns
  262. List columnList = table.getColumns();
  263. Iterator iter = columnList.iterator();
  264. while (iter.hasNext()) {
  265. modifyTableColumn((TableColumn) iter.next());
  266. }
  267. }
  268. /**
  269. * Modifies fo:table-column attributes:
  270. * <ul>
  271. * <li>width.
  272. * </ul>
  273. *
  274. * @param column TableColumn to modify.
  275. */
  276. private void modifyTableColumn(TableColumn column) {
  277. column.setColumnWidth(ceilLength(column.getColumnWidth(),
  278. TXTRenderer.CHAR_WIDTH));
  279. }
  280. /**
  281. * Modifies padding of fo:table-cell.
  282. *
  283. * @param side side.
  284. * @param bpb instance of CommonBorderPaddingBackground to modify.
  285. */
  286. private void modifyCellPadding(int side, CommonBorderPaddingBackground bpb) {
  287. if (bpb.getBorderInfo(side) == null) {
  288. int oldPadding = bpb.getPadding(side, false, null);
  289. int newPadding = oldPadding + quantum[side] / 2;
  290. setLength(bpb.getPaddingLengthProperty(side), newPadding);
  291. }
  292. }
  293. /**
  294. * Modifies table-cell properties:
  295. * <ul>
  296. * <li>Common Border, Padding, and Background Properties.
  297. * </ul>
  298. *
  299. * @param c TableCell to modify.
  300. */
  301. private void modifyTableCell(TableCell c) {
  302. CommonBorderPaddingBackground bpb = c
  303. .getCommonBorderPaddingBackground();
  304. modifyBPB(bpb);
  305. modifyCellPadding(CommonBorderPaddingBackground.BEFORE, bpb);
  306. modifyCellPadding(CommonBorderPaddingBackground.AFTER, bpb);
  307. modifyCellPadding(CommonBorderPaddingBackground.START, bpb);
  308. modifyCellPadding(CommonBorderPaddingBackground.END, bpb);
  309. }
  310. /**
  311. * Modifies Common Absolute Position Properties:
  312. * <ul>
  313. * <li>left
  314. * <li>top.
  315. * </ul>
  316. *
  317. * @param cap CommonAbsolutePosition to modify.
  318. */
  319. private void modifyCommonAbsolutePosition(CommonAbsolutePosition cap) {
  320. if (cap.absolutePosition == Constants.EN_ABSOLUTE) {
  321. cap.left = roundLength(cap.left, TXTRenderer.CHAR_WIDTH);
  322. cap.top = roundLength(cap.top, TXTRenderer.CHAR_HEIGHT);
  323. }
  324. }
  325. /**
  326. * Modifies line-height property. Sets a value of line-height to max(char
  327. * height; lowest integer, divisible by char height).
  328. *
  329. * @param lineHeight SpaceProperty to modify.
  330. */
  331. private void modifyLineHeight(SpaceProperty lineHeight) {
  332. Property p = lineHeight.getOptimum(null);
  333. int value = p.getLength().getValue(CONTEXT);
  334. int height = TXTRenderer.CHAR_HEIGHT;
  335. int newValue = Math.max(Helper.floor(value, height), height);
  336. setMinOptMax(lineHeight, new FixedLength(newValue));
  337. }
  338. /**
  339. * Modifies Common Font Properties:
  340. * <ul>
  341. * <li>font-family = Courier;
  342. * <li>font-size = MODIFIED_FONT_SIZE;
  343. * <li>font-stretch = EN_NORMAL;
  344. * <li>font-weight = EN_NORMAL.
  345. * </ul>
  346. *
  347. * @param cf the font to modify.
  348. */
  349. private void modifyCommonFont(CommonFont cf) {
  350. if (cf != null) {
  351. cf.fontFamily = "Courier";
  352. cf.fontSize = new FixedLength(MODIFIED_FONT_SIZE);
  353. cf.fontStretch = Constants.EN_NORMAL;
  354. cf.fontWeight = Constants.EN_NORMAL;
  355. }
  356. }
  357. /**
  358. * Modifies fo:block:
  359. * <ul>
  360. * <li>Common Border, Padding, and Background Properties
  361. * <li>Common Margin Properties-Block
  362. * <li>Common Font Properties
  363. * <li>line-height.
  364. * </ul>
  365. *
  366. * @param block Block to modify.
  367. */
  368. private void modifyBlock(Block block) {
  369. modifyBPB(block.getCommonBorderPaddingBackground());
  370. modifyCommonMarginBlock(block.getCommonMarginBlock());
  371. modifyCommonFont(block.getCommonFont());
  372. modifyLineHeight(block.getLineHeight());
  373. }
  374. /**
  375. * Modifies fo:block-container:
  376. * <ul>
  377. * <li>Common Border, Padding, and Background Properties
  378. * <li>Common Margin Properties-Block
  379. * <li>Common Absolute Position Properties.
  380. * </ul>
  381. *
  382. * @param bc BlockContainer to modify.
  383. */
  384. private void modifyBlockContainer(BlockContainer bc) {
  385. modifyBPB(bc.getCommonBorderPaddingBackground());
  386. modifyCommonMarginBlock(bc.getCommonMarginBlock());
  387. modifyCommonAbsolutePosition(bc.getCommonAbsolutePosition());
  388. }
  389. /**
  390. * Modifies fo:inline:
  391. * <ul>
  392. * <li>Common Font Properties
  393. * </ul>
  394. *
  395. * @param inline Inline to modify.
  396. */
  397. private void modifyInline(Inline inline) {
  398. modifyCommonFont(inline.getCommonFont());
  399. }
  400. /**
  401. * Modifies FOText:
  402. * <ul>
  403. * <li>Common Font Properties
  404. * </ul>
  405. *
  406. * @param text FOText to modify.
  407. */
  408. private void modifyFOText(FOText text) {
  409. modifyCommonFont(text.getCommonFont());
  410. }
  411. /**
  412. * Modifies fo:external-graphic:
  413. * <ul>
  414. * <li>Common Border, Padding, and Background Properties
  415. * <li>line-height.
  416. * </ul>
  417. *
  418. * @param eg ExternalGraphic to modify.
  419. */
  420. private void modifyExternalGraphic(ExternalGraphic eg) {
  421. modifyBPB(eg.getCommonBorderPaddingBackground());
  422. modifyLineHeight(eg.getLineHeight());
  423. }
  424. /**
  425. * Modifies fo:list-block:
  426. * <ul>
  427. * <li>Common Border, Padding, and Background Properties
  428. * <li>Common Margin Properties-Block.
  429. * </ul>
  430. *
  431. * @param lb ListBlock to modify.
  432. */
  433. private void modifyListBlock(ListBlock lb) {
  434. modifyBPB(lb.getCommonBorderPaddingBackground());
  435. modifyCommonMarginBlock(lb.getCommonMarginBlock());
  436. }
  437. /**
  438. * Modifies fo:list-item:
  439. * <ul>
  440. * <li>Common Border, Padding, and Background Properties
  441. * <li>Common Margin Properties-Block.
  442. * </ul>
  443. * <p>
  444. * Make refinement for fo:list-item-label and fo:list-item-body.
  445. *
  446. * @param li ListItem to modify.
  447. */
  448. private void modifyListItem(ListItem li) {
  449. modifyBPB(li.getCommonBorderPaddingBackground());
  450. modifyCommonMarginBlock(li.getCommonMarginBlock());
  451. refinement(li.getLabel());
  452. refinement(li.getBody());
  453. }
  454. /**
  455. * Does refinement for particular node. Modifies node's properties and
  456. * refines its children recursively.
  457. *
  458. * @param node the node to refine.
  459. */
  460. private void refinement(FONode node) {
  461. int[] saveOverPatching = (int[]) overPatching.clone();
  462. Arrays.fill(lastOverPatching, 0);
  463. if (node instanceof Block) {
  464. modifyBlock((Block) node);
  465. } else if (node instanceof BlockContainer) {
  466. modifyBlockContainer((BlockContainer) node);
  467. } else if (node instanceof Inline) {
  468. modifyInline((Inline) node);
  469. } else if (node instanceof FOText) {
  470. modifyFOText((FOText) node);
  471. } else if (node instanceof Table) {
  472. modifyTable((Table) node);
  473. Arrays.fill(overPatching, 0);
  474. } else if (node instanceof TableCell) {
  475. modifyTableCell((TableCell) node);
  476. } else if (node instanceof ExternalGraphic) {
  477. modifyExternalGraphic((ExternalGraphic) node);
  478. } else if (node instanceof ListBlock) {
  479. modifyListBlock((ListBlock) node);
  480. } else if (node instanceof ListItem) {
  481. modifyListItem((ListItem) node);
  482. } else if (node instanceof PageNumber) {
  483. modifyCommonFont(((PageNumber) node).getCommonFont());
  484. }
  485. Iterator it = node.getChildNodes();
  486. if (it != null) {
  487. while (it.hasNext()) {
  488. refinement((FONode) it.next());
  489. }
  490. }
  491. overPatching = saveOverPatching;
  492. }
  493. /**
  494. * Run refinement for:
  495. * <ul>
  496. * <li>mainflow (xsl-region-body)
  497. * <li>staticflow (xsl-region-before, xsl-region-after, xsl-region-start,
  498. * xsl-region-end).
  499. * </ul>
  500. *
  501. * @param pageSequence PageSequence to refine.
  502. */
  503. public void endPageSequence(PageSequence pageSequence) {
  504. Arrays.fill(overPatching, 0);
  505. refinement(pageSequence.getMainFlow());
  506. if (pageSequence.getStaticContent("xsl-region-before") != null) {
  507. refinement(pageSequence.getStaticContent("xsl-region-before"));
  508. }
  509. if (pageSequence.getStaticContent("xsl-region-after") != null) {
  510. refinement(pageSequence.getStaticContent("xsl-region-after"));
  511. }
  512. if (pageSequence.getStaticContent("xsl-region-start") != null) {
  513. refinement(pageSequence.getStaticContent("xsl-region-start"));
  514. }
  515. if (pageSequence.getStaticContent("xsl-region-end") != null) {
  516. refinement(pageSequence.getStaticContent("xsl-region-end"));
  517. }
  518. super.endPageSequence(pageSequence);
  519. }
  520. }