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.

TXTRenderer.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.render.txt;
  19. import java.awt.Color;
  20. import java.awt.Point;
  21. import java.awt.geom.AffineTransform;
  22. import java.awt.geom.Rectangle2D;
  23. import java.io.IOException;
  24. import java.io.OutputStream;
  25. import java.util.List;
  26. import java.util.Map;
  27. import org.apache.xmlgraphics.util.UnitConv;
  28. import org.apache.fop.apps.FOPException;
  29. import org.apache.fop.area.Area;
  30. import org.apache.fop.area.CTM;
  31. import org.apache.fop.area.PageViewport;
  32. import org.apache.fop.area.inline.Image;
  33. import org.apache.fop.area.inline.TextArea;
  34. import org.apache.fop.render.AbstractPathOrientedRenderer;
  35. import org.apache.fop.render.txt.border.AbstractBorderElement;
  36. import org.apache.fop.render.txt.border.BorderManager;
  37. /**
  38. * Renderer that renders areas to plain text.
  39. *
  40. * @author Art Welch
  41. * @author <a href="mailto:mark-fop@inomial.com">Mark Lillywhite</a> (to use
  42. * the new Renderer interface)
  43. */
  44. public class TXTRenderer extends AbstractPathOrientedRenderer {
  45. private static final char LIGHT_SHADE = '\u2591';
  46. private static final char MEDIUM_SHADE = '\u2592';
  47. private static final char DARK_SHADE = '\u2593';
  48. private static final char FULL_BLOCK = '\u2588';
  49. private static final char IMAGE_CHAR = '#';
  50. /**The stream for output */
  51. private OutputStream outputStream;
  52. /** The current stream to add Text commands to. */
  53. private TXTStream currentStream;
  54. /** Buffer for text. */
  55. private StringBuffer[] charData;
  56. /** Buffer for background and images. */
  57. private StringBuffer[] decoData;
  58. /** Height of one symbol in Courier font size of 10pt. */
  59. public static final int CHAR_HEIGHT = 7860;
  60. /** Width of one symbol in Courier font size of 10pt. */
  61. public static final int CHAR_WIDTH = 6000;
  62. /** Current processing page width. */
  63. private int pageWidth;
  64. /** Current processing page height. */
  65. private int pageHeight;
  66. /**
  67. * Every line except the last line on a page (which will end with
  68. * pageEnding) will be terminated with this string.
  69. */
  70. private final String lineEnding = "\r\n";
  71. /** Every page except the last one will end with this string. */
  72. private final String pageEnding = "\f";
  73. /** Equals true, if current page is first. */
  74. private boolean firstPage = false;
  75. /** Manager for storing border's information. */
  76. private BorderManager bm;
  77. /** Char for current filling. */
  78. private char fillChar;
  79. /** Saves current coordinate transformation. */
  80. private final TXTState currentState = new TXTState();
  81. private String encoding;
  82. /**
  83. * Constructs a newly allocated <code>TXTRenderer</code> object.
  84. */
  85. public TXTRenderer() {
  86. }
  87. /** {@inheritDoc} */
  88. public String getMimeType() {
  89. return "text/plain";
  90. }
  91. /**
  92. * Sets the encoding of the target file.
  93. * @param encoding the encoding, null to select the default encoding (UTF-8)
  94. */
  95. public void setEncoding(String encoding) {
  96. this.encoding = encoding;
  97. }
  98. /**
  99. * Indicates if point (x, y) lay inside currentPage.
  100. *
  101. * @param x x coordinate
  102. * @param y y coordinate
  103. * @return <b>true</b> if point lay inside page
  104. */
  105. public boolean isLayInside(int x, int y) {
  106. return (x >= 0) && (x < pageWidth) && (y >= 0) && (y < pageHeight);
  107. }
  108. /**
  109. * Add char to text buffer.
  110. *
  111. * @param x x coordinate
  112. * @param y y coordinate
  113. * @param ch char to add
  114. * @param ischar boolean, repersenting is character adding to text buffer
  115. */
  116. protected void addChar(int x, int y, char ch, boolean ischar) {
  117. Point point = currentState.transformPoint(x, y);
  118. putChar(point.x, point.y, ch, ischar);
  119. }
  120. /**
  121. * Add char to text or background buffer.
  122. *
  123. * @param x x coordinate
  124. * @param y x coordinate
  125. * @param ch char to add
  126. * @param ischar indicates if it char or background
  127. */
  128. protected void putChar(int x, int y, char ch, boolean ischar) {
  129. if (isLayInside(x, y)) {
  130. StringBuffer sb = ischar ? charData[y] : decoData[y];
  131. while (sb.length() <= x) {
  132. sb.append(' ');
  133. }
  134. sb.setCharAt(x, ch);
  135. }
  136. }
  137. /**
  138. * Adds string to text buffer (<code>charData</code>). <p>
  139. * Chars of string map in turn.
  140. *
  141. * @param row x coordinate
  142. * @param col y coordinate
  143. * @param s string to add
  144. */
  145. protected void addString(int row, int col, String s) {
  146. for (int l = 0; l < s.length(); l++) {
  147. addChar(col + l, row, s.charAt(l), true);
  148. }
  149. }
  150. /**
  151. * Render TextArea to Text.
  152. *
  153. * @param area inline area to render
  154. */
  155. protected void renderText(TextArea area) {
  156. int col = Helper.ceilPosition(this.currentIPPosition, CHAR_WIDTH);
  157. int row = Helper.ceilPosition(this.currentBPPosition, CHAR_HEIGHT);
  158. String s = area.getText();
  159. addString(row, col, s);
  160. super.renderText(area);
  161. }
  162. /**
  163. * {@inheritDoc}
  164. */
  165. public void renderPage(PageViewport page) throws IOException, FOPException {
  166. if (firstPage) {
  167. firstPage = false;
  168. } else {
  169. currentStream.add(pageEnding);
  170. }
  171. Rectangle2D bounds = page.getViewArea();
  172. double width = bounds.getWidth();
  173. double height = bounds.getHeight();
  174. pageWidth = Helper.ceilPosition((int) width, CHAR_WIDTH);
  175. pageHeight = Helper.ceilPosition((int) height, CHAR_HEIGHT);
  176. // init buffers
  177. charData = new StringBuffer[pageHeight];
  178. decoData = new StringBuffer[pageHeight];
  179. for (int i = 0; i < pageHeight; i++) {
  180. charData[i] = new StringBuffer();
  181. decoData[i] = new StringBuffer();
  182. }
  183. bm = new BorderManager(pageWidth, pageHeight, currentState);
  184. super.renderPage(page);
  185. flushBorderToBuffer();
  186. flushBuffer();
  187. }
  188. /**
  189. * Projects current page borders (i.e.<code>bm</code>) to buffer for
  190. * background and images (i.e.<code>decoData</code>).
  191. */
  192. private void flushBorderToBuffer() {
  193. for (int x = 0; x < pageWidth; x++) {
  194. for (int y = 0; y < pageHeight; y++) {
  195. Character c = bm.getCharacter(x, y);
  196. if (c != null) {
  197. putChar(x, y, c.charValue(), false);
  198. }
  199. }
  200. }
  201. }
  202. /**
  203. * Write out the buffer to output stream.
  204. */
  205. private void flushBuffer() {
  206. for (int row = 0; row < pageHeight; row++) {
  207. StringBuffer cr = charData[row];
  208. StringBuffer dr = decoData[row];
  209. StringBuffer outr = null;
  210. if (cr != null && dr == null) {
  211. outr = cr;
  212. } else if (dr != null && cr == null) {
  213. outr = dr;
  214. } else if (cr != null && dr != null) {
  215. int len = dr.length();
  216. if (cr.length() > len) {
  217. len = cr.length();
  218. }
  219. outr = new StringBuffer();
  220. for (int countr = 0; countr < len; countr++) {
  221. if (countr < cr.length() && cr.charAt(countr) != ' ') {
  222. outr.append(cr.charAt(countr));
  223. } else if (countr < dr.length()) {
  224. outr.append(dr.charAt(countr));
  225. } else {
  226. outr.append(' ');
  227. }
  228. }
  229. }
  230. if (outr != null) {
  231. currentStream.add(outr.toString());
  232. }
  233. if (row < pageHeight) {
  234. currentStream.add(lineEnding);
  235. }
  236. }
  237. }
  238. /**
  239. * {@inheritDoc}
  240. */
  241. public void startRenderer(OutputStream os) throws IOException {
  242. log.info("Rendering areas to TEXT.");
  243. this.outputStream = os;
  244. currentStream = new TXTStream(os);
  245. currentStream.setEncoding(this.encoding);
  246. firstPage = true;
  247. }
  248. /**
  249. * {@inheritDoc}
  250. */
  251. public void stopRenderer() throws IOException {
  252. log.info("writing out TEXT");
  253. outputStream.flush();
  254. super.stopRenderer();
  255. }
  256. /**
  257. * Does nothing.
  258. * {@inheritDoc}
  259. */
  260. protected void restoreStateStackAfterBreakOut(List breakOutList) {
  261. }
  262. /**
  263. * Does nothing.
  264. * @return null
  265. * {@inheritDoc}
  266. */
  267. protected List breakOutOfStateStack() {
  268. return null;
  269. }
  270. /**
  271. * Does nothing.
  272. * {@inheritDoc}
  273. */
  274. protected void saveGraphicsState() {
  275. currentState.push(new CTM());
  276. }
  277. /**
  278. * Does nothing.
  279. * {@inheritDoc}
  280. */
  281. protected void restoreGraphicsState() {
  282. currentState.pop();
  283. }
  284. /**
  285. * Does nothing.
  286. * {@inheritDoc}
  287. */
  288. protected void beginTextObject() {
  289. }
  290. /**
  291. * Does nothing.
  292. * {@inheritDoc}
  293. */
  294. protected void endTextObject() {
  295. }
  296. /**
  297. * Does nothing.
  298. * {@inheritDoc}
  299. */
  300. protected void clip() {
  301. }
  302. /**
  303. * Does nothing.
  304. * {@inheritDoc}
  305. */
  306. protected void clipRect(float x, float y, float width, float height) {
  307. }
  308. /**
  309. * Does nothing.
  310. * {@inheritDoc}
  311. */
  312. protected void moveTo(float x, float y) {
  313. }
  314. /**
  315. * Does nothing.
  316. * {@inheritDoc}
  317. */
  318. protected void lineTo(float x, float y) {
  319. }
  320. /**
  321. * Does nothing.
  322. * {@inheritDoc}
  323. */
  324. protected void closePath() {
  325. }
  326. /**
  327. * Fills rectangle startX, startY, width, height with char
  328. * <code>charToFill</code>.
  329. *
  330. * @param startX x-coordinate of upper left point
  331. * @param startY y-coordinate of upper left point
  332. * @param width width of rectangle
  333. * @param height height of rectangle
  334. * @param charToFill filling char
  335. */
  336. private void fillRect(int startX, int startY, int width, int height,
  337. char charToFill) {
  338. for (int x = startX; x < startX + width; x++) {
  339. for (int y = startY; y < startY + height; y++) {
  340. addChar(x, y, charToFill, false);
  341. }
  342. }
  343. }
  344. /**
  345. * Fills a rectangular area with the current filling char.
  346. * {@inheritDoc}
  347. */
  348. protected void fillRect(float x, float y, float width, float height) {
  349. fillRect(bm.getStartX(), bm.getStartY(), bm.getWidth(), bm.getHeight(),
  350. fillChar);
  351. }
  352. /**
  353. * Changes current filling char.
  354. * {@inheritDoc}
  355. */
  356. protected void updateColor(Color col, boolean fill) {
  357. if (col == null) {
  358. return;
  359. }
  360. // fillShade evaluation was taken from fop-0.20.5
  361. // TODO: This fillShase is catually the luminance component of the color
  362. // transformed to the YUV (YPrBb) Colorspace. It should use standard
  363. // Java methods for its conversion instead of the formula given here.
  364. double fillShade = 0.30f / 255f * col.getRed()
  365. + 0.59f / 255f * col.getGreen()
  366. + 0.11f / 255f * col.getBlue();
  367. fillShade = 1 - fillShade;
  368. if (fillShade > 0.8f) {
  369. fillChar = FULL_BLOCK;
  370. } else if (fillShade > 0.6f) {
  371. fillChar = DARK_SHADE;
  372. } else if (fillShade > 0.4f) {
  373. fillChar = MEDIUM_SHADE;
  374. } else if (fillShade > 0.2f) {
  375. fillChar = LIGHT_SHADE;
  376. } else {
  377. fillChar = ' ';
  378. }
  379. }
  380. /** {@inheritDoc} */
  381. protected void drawImage(String url, Rectangle2D pos, Map foreignAttributes) {
  382. //No images are painted here
  383. }
  384. /**
  385. * Fills image rectangle with a <code>IMAGE_CHAR</code>.
  386. *
  387. * @param image the base image
  388. * @param pos the position of the image
  389. */
  390. public void renderImage(Image image, Rectangle2D pos) {
  391. int x1 = Helper.ceilPosition(currentIPPosition, CHAR_WIDTH);
  392. int y1 = Helper.ceilPosition(currentBPPosition, CHAR_HEIGHT);
  393. int width = Helper.ceilPosition((int) pos.getWidth(), CHAR_WIDTH);
  394. int height = Helper.ceilPosition((int) pos.getHeight(), CHAR_HEIGHT);
  395. fillRect(x1, y1, width, height, IMAGE_CHAR);
  396. }
  397. /**
  398. * Returns the closest integer to the multiplication of a number and 1000.
  399. *
  400. * @param x the value of the argument, multiplied by
  401. * 1000 and rounded
  402. * @return the value of the argument multiplied by
  403. * 1000 and rounded to the nearest integer
  404. */
  405. protected int toMilli(float x) {
  406. return Math.round(x * 1000f);
  407. }
  408. /**
  409. * Adds one element of border.
  410. *
  411. * @param x x coordinate
  412. * @param y y coordinate
  413. * @param style integer, representing border style
  414. * @param type integer, representing border element type
  415. */
  416. private void addBitOfBorder(int x, int y, int style, int type) {
  417. Point point = currentState.transformPoint(x, y);
  418. if (isLayInside(point.x, point.y)) {
  419. bm.addBorderElement(point.x, point.y, style, type);
  420. }
  421. }
  422. /**
  423. * {@inheritDoc}
  424. */
  425. protected void drawBorderLine(float x1, float y1, float x2, float y2,
  426. boolean horz, boolean startOrBefore, int style, Color col) {
  427. int borderHeight = bm.getHeight();
  428. int borderWidth = bm.getWidth();
  429. int borderStartX = bm.getStartX();
  430. int borderStartY = bm.getStartY();
  431. int x, y;
  432. if (horz && startOrBefore) { // BEFORE
  433. x = borderStartX;
  434. y = borderStartY;
  435. } else if (horz && !startOrBefore) { // AFTER
  436. x = borderStartX;
  437. y = borderStartY + borderHeight - 1;
  438. } else if (!horz && startOrBefore) { // START
  439. x = borderStartX;
  440. y = borderStartY;
  441. } else { // END
  442. x = borderStartX + borderWidth - 1;
  443. y = borderStartY;
  444. }
  445. int dx, dy, length, startType, endType;
  446. if (horz) {
  447. length = borderWidth;
  448. dx = 1;
  449. dy = 0;
  450. startType = 1 << AbstractBorderElement.RIGHT;
  451. endType = 1 << AbstractBorderElement.LEFT;
  452. } else {
  453. length = borderHeight;
  454. dx = 0;
  455. dy = 1;
  456. startType = 1 << AbstractBorderElement.DOWN;
  457. endType = 1 << AbstractBorderElement.UP;
  458. }
  459. addBitOfBorder(x, y, style, startType);
  460. for (int i = 0; i < length - 2; i++) {
  461. x += dx;
  462. y += dy;
  463. addBitOfBorder(x, y, style, startType + endType);
  464. }
  465. x += dx;
  466. y += dy;
  467. addBitOfBorder(x, y, style, endType);
  468. }
  469. /**
  470. * {@inheritDoc}
  471. */
  472. protected void drawBackAndBorders(Area area, float startx, float starty,
  473. float width, float height) {
  474. bm.setWidth(Helper.ceilPosition(toMilli(width), CHAR_WIDTH));
  475. bm.setHeight(Helper.ceilPosition(toMilli(height), CHAR_HEIGHT));
  476. bm.setStartX(Helper.ceilPosition(toMilli(startx), CHAR_WIDTH));
  477. bm.setStartY(Helper.ceilPosition(toMilli(starty), CHAR_HEIGHT));
  478. super.drawBackAndBorders(area, startx, starty, width, height);
  479. }
  480. /**
  481. * {@inheritDoc}
  482. */
  483. protected void startVParea(CTM ctm, Rectangle2D clippingRect) {
  484. currentState.push(ctm);
  485. }
  486. /**
  487. * {@inheritDoc}
  488. */
  489. protected void endVParea() {
  490. currentState.pop();
  491. }
  492. /** {@inheritDoc} */
  493. protected void concatenateTransformationMatrix(AffineTransform at) {
  494. currentState.push(new CTM(UnitConv.ptToMpt(at)));
  495. }
  496. }