| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* $Id$ */ |
| |
| package org.apache.fop.render.txt; |
| |
| import java.awt.Color; |
| import java.awt.Point; |
| import java.awt.Rectangle; |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.Rectangle2D; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.xmlgraphics.util.UnitConv; |
| |
| import org.apache.fop.apps.FOPException; |
| import org.apache.fop.apps.FOUserAgent; |
| import org.apache.fop.area.Area; |
| import org.apache.fop.area.CTM; |
| import org.apache.fop.area.PageViewport; |
| import org.apache.fop.area.inline.Image; |
| import org.apache.fop.area.inline.TextArea; |
| import org.apache.fop.render.AbstractPathOrientedRenderer; |
| import org.apache.fop.render.txt.border.AbstractBorderElement; |
| import org.apache.fop.render.txt.border.BorderManager; |
| |
| /** |
| * <p>Renderer that renders areas to plain text.</p> |
| * |
| * <p>This work was authored by Art Welch and |
| * Mark Lillywhite (mark-fop@inomial.com) [to use the new Renderer interface].</p> |
| */ |
| public class TXTRenderer extends AbstractPathOrientedRenderer { |
| |
| private static final char LIGHT_SHADE = '\u2591'; |
| |
| private static final char MEDIUM_SHADE = '\u2592'; |
| |
| private static final char DARK_SHADE = '\u2593'; |
| |
| private static final char FULL_BLOCK = '\u2588'; |
| |
| private static final char IMAGE_CHAR = '#'; |
| |
| /**The stream for output */ |
| private OutputStream outputStream; |
| |
| /** The current stream to add Text commands to. */ |
| private TXTStream currentStream; |
| |
| /** Buffer for text. */ |
| private StringBuffer[] charData; |
| |
| /** Buffer for background and images. */ |
| private StringBuffer[] decoData; |
| |
| /** Leading of line containing Courier font size of 10pt. */ |
| public static final int LINE_LEADING = 1070; |
| |
| /** Height of one symbol in Courier font size of 10pt. */ |
| public static final int CHAR_HEIGHT = 7860; |
| |
| /** Width of one symbol in Courier font size of 10pt. */ |
| public static final int CHAR_WIDTH = 6000; |
| |
| /** Current processing page width. */ |
| private int pageWidth; |
| |
| /** Current processing page height. */ |
| private int pageHeight; |
| |
| /** |
| * Every line except the last line on a page (which will end with |
| * pageEnding) will be terminated with this string. |
| */ |
| private static final String LINE_ENDING = "\r\n"; |
| |
| /** Every page except the last one will end with this string. */ |
| private static final String PAGE_ENDING = "\f"; |
| |
| /** Equals true, if current page is first. */ |
| private boolean firstPage; |
| |
| /** Manager for storing border's information. */ |
| private BorderManager bm; |
| |
| /** Char for current filling. */ |
| private char fillChar; |
| |
| /** Saves current coordinate transformation. */ |
| private final TXTState currentState = new TXTState(); |
| |
| private String encoding; |
| |
| /** |
| * Constructs a newly allocated <code>TXTRenderer</code> object. |
| * |
| * @param userAgent the user agent that contains configuration details. This cannot be null. |
| */ |
| public TXTRenderer(FOUserAgent userAgent) { |
| super(userAgent); |
| } |
| |
| /** {@inheritDoc} */ |
| public String getMimeType() { |
| return "text/plain"; |
| } |
| |
| /** |
| * Sets the encoding of the target file. |
| * @param encoding the encoding, null to select the default encoding (UTF-8) |
| */ |
| public void setEncoding(String encoding) { |
| this.encoding = encoding; |
| } |
| |
| /** |
| * Indicates if point (x, y) lay inside currentPage. |
| * |
| * @param x x coordinate |
| * @param y y coordinate |
| * @return <b>true</b> if point lay inside page |
| */ |
| public boolean isLayInside(int x, int y) { |
| return (x >= 0) && (x < pageWidth) && (y >= 0) && (y < pageHeight); |
| } |
| |
| /** |
| * Add char to text buffer. |
| * |
| * @param x x coordinate |
| * @param y y coordinate |
| * @param ch char to add |
| * @param ischar boolean, repersenting is character adding to text buffer |
| */ |
| protected void addChar(int x, int y, char ch, boolean ischar) { |
| Point point = currentState.transformPoint(x, y); |
| putChar(point.x, point.y, ch, ischar); |
| } |
| |
| /** |
| * Add char to text or background buffer. |
| * |
| * @param x x coordinate |
| * @param y x coordinate |
| * @param ch char to add |
| * @param ischar indicates if it char or background |
| */ |
| protected void putChar(int x, int y, char ch, boolean ischar) { |
| if (isLayInside(x, y)) { |
| StringBuffer sb = ischar ? charData[y] : decoData[y]; |
| while (sb.length() <= x) { |
| sb.append(' '); |
| } |
| sb.setCharAt(x, ch); |
| } |
| } |
| |
| /** |
| * Adds string to text buffer (<code>charData</code>). <p> |
| * Chars of string map in turn. |
| * |
| * @param row x coordinate |
| * @param col y coordinate |
| * @param s string to add |
| */ |
| protected void addString(int row, int col, String s) { |
| for (int l = 0; l < s.length(); l++) { |
| addChar(col + l, row, s.charAt(l), true); |
| } |
| } |
| |
| /** |
| * Render TextArea to Text. |
| * |
| * @param area inline area to render |
| */ |
| protected void renderText(TextArea area) { |
| int col = Helper.ceilPosition(this.currentIPPosition, CHAR_WIDTH); |
| int row = Helper.ceilPosition(this.currentBPPosition - LINE_LEADING, CHAR_HEIGHT + 2 * LINE_LEADING); |
| |
| String s = area.getText(); |
| |
| addString(row, col, s); |
| |
| super.renderText(area); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void renderPage(PageViewport page) throws IOException, FOPException { |
| if (firstPage) { |
| firstPage = false; |
| } else { |
| currentStream.add(PAGE_ENDING); |
| } |
| |
| Rectangle2D bounds = page.getViewArea(); |
| double width = bounds.getWidth(); |
| double height = bounds.getHeight(); |
| |
| pageWidth = Helper.ceilPosition((int) width, CHAR_WIDTH); |
| pageHeight = Helper.ceilPosition((int) height, CHAR_HEIGHT + 2 * LINE_LEADING); |
| |
| // init buffers |
| charData = new StringBuffer[pageHeight]; |
| decoData = new StringBuffer[pageHeight]; |
| for (int i = 0; i < pageHeight; i++) { |
| charData[i] = new StringBuffer(); |
| decoData[i] = new StringBuffer(); |
| } |
| |
| bm = new BorderManager(pageWidth, pageHeight, currentState); |
| |
| super.renderPage(page); |
| |
| flushBorderToBuffer(); |
| flushBuffer(); |
| } |
| |
| /** |
| * Projects current page borders (i.e.<code>bm</code>) to buffer for |
| * background and images (i.e.<code>decoData</code>). |
| */ |
| private void flushBorderToBuffer() { |
| for (int x = 0; x < pageWidth; x++) { |
| for (int y = 0; y < pageHeight; y++) { |
| Character c = bm.getCharacter(x, y); |
| if (c != null) { |
| putChar(x, y, c, false); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Write out the buffer to output stream. |
| */ |
| private void flushBuffer() { |
| for (int row = 0; row < pageHeight; row++) { |
| StringBuffer cr = charData[row]; |
| StringBuffer dr = decoData[row]; |
| StringBuffer outr = null; |
| |
| if (cr != null && dr == null) { |
| outr = cr; |
| } else if (dr != null && cr == null) { |
| outr = dr; |
| } else if (cr != null && dr != null) { |
| int len = dr.length(); |
| if (cr.length() > len) { |
| len = cr.length(); |
| } |
| outr = new StringBuffer(); |
| for (int countr = 0; countr < len; countr++) { |
| if (countr < cr.length() && cr.charAt(countr) != ' ') { |
| outr.append(cr.charAt(countr)); |
| } else if (countr < dr.length()) { |
| outr.append(dr.charAt(countr)); |
| } else { |
| outr.append(' '); |
| } |
| } |
| } |
| |
| if (outr != null) { |
| currentStream.add(outr.toString()); |
| } |
| if (row < pageHeight) { |
| currentStream.add(LINE_ENDING); |
| } |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void startRenderer(OutputStream os) throws IOException { |
| log.info("Rendering areas to TEXT."); |
| this.outputStream = os; |
| currentStream = new TXTStream(os); |
| currentStream.setEncoding(this.encoding); |
| firstPage = true; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void stopRenderer() throws IOException { |
| log.info("writing out TEXT"); |
| outputStream.flush(); |
| super.stopRenderer(); |
| } |
| |
| /** |
| * Does nothing. |
| * {@inheritDoc} |
| */ |
| protected void restoreStateStackAfterBreakOut(List breakOutList) { |
| } |
| |
| /** |
| * Does nothing. |
| * @return null |
| * {@inheritDoc} |
| */ |
| protected List breakOutOfStateStack() { |
| return null; |
| } |
| |
| /** |
| * Does nothing. |
| * {@inheritDoc} |
| */ |
| protected void saveGraphicsState() { |
| currentState.push(new CTM()); |
| } |
| |
| /** |
| * Does nothing. |
| * {@inheritDoc} |
| */ |
| protected void restoreGraphicsState() { |
| currentState.pop(); |
| } |
| |
| /** |
| * Does nothing. |
| * {@inheritDoc} |
| */ |
| protected void beginTextObject() { |
| } |
| |
| /** |
| * Does nothing. |
| * {@inheritDoc} |
| */ |
| protected void endTextObject() { |
| } |
| |
| /** |
| * Does nothing. |
| * {@inheritDoc} |
| */ |
| protected void clip() { |
| } |
| |
| /** |
| * Does nothing. |
| * {@inheritDoc} |
| */ |
| protected void clipRect(float x, float y, float width, float height) { |
| } |
| |
| /** |
| * Does nothing. |
| * {@inheritDoc} |
| */ |
| protected void moveTo(float x, float y) { |
| } |
| |
| /** |
| * Does nothing. |
| * {@inheritDoc} |
| */ |
| protected void lineTo(float x, float y) { |
| } |
| |
| /** |
| * Does nothing. |
| * {@inheritDoc} |
| */ |
| protected void closePath() { |
| } |
| |
| /** |
| * Fills rectangle startX, startY, width, height with char |
| * <code>charToFill</code>. |
| * |
| * @param startX x-coordinate of upper left point |
| * @param startY y-coordinate of upper left point |
| * @param width width of rectangle |
| * @param height height of rectangle |
| * @param charToFill filling char |
| */ |
| private void fillRect(int startX, int startY, int width, int height, |
| char charToFill) { |
| for (int x = startX; x < startX + width; x++) { |
| for (int y = startY; y < startY + height; y++) { |
| addChar(x, y, charToFill, false); |
| } |
| } |
| } |
| |
| /** |
| * Fills a rectangular area with the current filling char. |
| * {@inheritDoc} |
| */ |
| protected void fillRect(float x, float y, float width, float height) { |
| fillRect(bm.getStartX(), bm.getStartY(), bm.getWidth(), bm.getHeight(), |
| fillChar); |
| } |
| |
| /** |
| * Changes current filling char. |
| * {@inheritDoc} |
| */ |
| protected void updateColor(Color col, boolean fill) { |
| if (col == null) { |
| return; |
| } |
| // fillShade evaluation was taken from fop-0.20.5 |
| // TODO: This fillShase is catually the luminance component of the color |
| // transformed to the YUV (YPrBb) Colorspace. It should use standard |
| // Java methods for its conversion instead of the formula given here. |
| double fillShade = 0.30f / 255f * col.getRed() |
| + 0.59f / 255f * col.getGreen() |
| + 0.11f / 255f * col.getBlue(); |
| fillShade = 1 - fillShade; |
| |
| if (fillShade > 0.8f) { |
| fillChar = FULL_BLOCK; |
| } else if (fillShade > 0.6f) { |
| fillChar = DARK_SHADE; |
| } else if (fillShade > 0.4f) { |
| fillChar = MEDIUM_SHADE; |
| } else if (fillShade > 0.2f) { |
| fillChar = LIGHT_SHADE; |
| } else { |
| fillChar = ' '; |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| protected void drawImage(String url, Rectangle2D pos, Map foreignAttributes) { |
| //No images are painted here |
| } |
| |
| /** |
| * Fills image rectangle with a <code>IMAGE_CHAR</code>. |
| * |
| * @param image the base image |
| * @param pos the position of the image |
| */ |
| public void renderImage(Image image, Rectangle2D pos) { |
| int x1 = Helper.ceilPosition(currentIPPosition, CHAR_WIDTH); |
| int y1 = Helper.ceilPosition(currentBPPosition - LINE_LEADING, CHAR_HEIGHT + 2 * LINE_LEADING); |
| int width = Helper.ceilPosition((int) pos.getWidth(), CHAR_WIDTH); |
| int height = Helper.ceilPosition((int) pos.getHeight(), CHAR_HEIGHT + 2 * LINE_LEADING); |
| |
| fillRect(x1, y1, width, height, IMAGE_CHAR); |
| } |
| |
| |
| /** |
| * Returns the closest integer to the multiplication of a number and 1000. |
| * |
| * @param x the value of the argument, multiplied by |
| * 1000 and rounded |
| * @return the value of the argument multiplied by |
| * 1000 and rounded to the nearest integer |
| */ |
| protected int toMilli(float x) { |
| return Math.round(x * 1000f); |
| } |
| |
| /** |
| * Adds one element of border. |
| * |
| * @param x x coordinate |
| * @param y y coordinate |
| * @param style integer, representing border style |
| * @param type integer, representing border element type |
| */ |
| private void addBitOfBorder(int x, int y, int style, int type) { |
| Point point = currentState.transformPoint(x, y); |
| if (isLayInside(point.x, point.y)) { |
| bm.addBorderElement(point.x, point.y, style, type); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| protected void drawBorderLine(float x1, float y1, float x2, float y2, |
| boolean horz, boolean startOrBefore, int style, Color col) { |
| |
| int borderHeight = bm.getHeight(); |
| int borderWidth = bm.getWidth(); |
| int borderStartX = bm.getStartX(); |
| int borderStartY = bm.getStartY(); |
| |
| int x; |
| int y; |
| if (horz && startOrBefore) { // BEFORE |
| x = borderStartX; |
| y = borderStartY; |
| } else if (horz && !startOrBefore) { // AFTER |
| x = borderStartX; |
| y = borderStartY + borderHeight - 1; |
| } else if (!horz && startOrBefore) { // START |
| x = borderStartX; |
| y = borderStartY; |
| } else { // END |
| x = borderStartX + borderWidth - 1; |
| y = borderStartY; |
| } |
| |
| int dx; |
| int dy; |
| int length; |
| int startType; |
| int endType; |
| if (horz) { |
| length = borderWidth; |
| dx = 1; |
| dy = 0; |
| startType = 1 << AbstractBorderElement.RIGHT; |
| endType = 1 << AbstractBorderElement.LEFT; |
| } else { |
| length = borderHeight; |
| dx = 0; |
| dy = 1; |
| startType = 1 << AbstractBorderElement.DOWN; |
| endType = 1 << AbstractBorderElement.UP; |
| } |
| |
| addBitOfBorder(x, y, style, startType); |
| for (int i = 0; i < length - 2; i++) { |
| x += dx; |
| y += dy; |
| addBitOfBorder(x, y, style, startType + endType); |
| } |
| x += dx; |
| y += dy; |
| addBitOfBorder(x, y, style, endType); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| protected void drawBackAndBorders(Area area, float startx, float starty, |
| float width, float height) { |
| bm.setWidth(Helper.ceilPosition(toMilli(width), CHAR_WIDTH)); |
| bm.setHeight(Helper.ceilPosition(toMilli(height), CHAR_HEIGHT + 2 * LINE_LEADING)); |
| bm.setStartX(Helper.ceilPosition(toMilli(startx), CHAR_WIDTH)); |
| bm.setStartY(Helper.ceilPosition(toMilli(starty), CHAR_HEIGHT + 2 * LINE_LEADING)); |
| |
| super.drawBackAndBorders(area, startx, starty, width, height); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| protected void startVParea(CTM ctm, Rectangle clippingRect) { |
| currentState.push(ctm); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| protected void endVParea() { |
| currentState.pop(); |
| } |
| |
| /** {@inheritDoc} */ |
| protected void startLayer(String layer) { |
| } |
| |
| /** {@inheritDoc} */ |
| protected void endLayer() { |
| } |
| |
| /** {@inheritDoc} */ |
| protected void concatenateTransformationMatrix(AffineTransform at) { |
| currentState.push(new CTM(UnitConv.ptToMpt(at))); |
| } |
| |
| } |