選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

TXTRenderer.java 16KB

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