/*
 * 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: TXTRenderer.java 1761019 2016-09-16 10:43:45Z ssteiner $ */

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;

Renderer that renders areas to plain text.

This work was authored by Art Welch and Mark Lillywhite (mark-fop@inomial.com) [to use the new Renderer interface].

/** * <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
/**The stream for output */
private OutputStream outputStream;
The current stream to add Text commands to.
/** The current stream to add Text commands to. */
private TXTStream currentStream;
Buffer for text.
/** Buffer for text. */
private StringBuffer[] charData;
Buffer for background and images.
/** Buffer for background and images. */
private StringBuffer[] decoData;
Leading of line containing Courier font size of 10pt.
/** 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.
/** 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.
/** Width of one symbol in Courier font size of 10pt. */
public static final int CHAR_WIDTH = 6000;
Current processing page width.
/** Current processing page width. */
private int pageWidth;
Current processing page height.
/** 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.
/** * 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.
/** 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.
/** Equals true, if current page is first. */
private boolean firstPage;
Manager for storing border's information.
/** Manager for storing border's information. */
private BorderManager bm;
Char for current filling.
/** Char for current filling. */
private char fillChar;
Saves current coordinate transformation.
/** Saves current coordinate transformation. */
private final TXTState currentState = new TXTState(); private String encoding;
Constructs a newly allocated TXTRenderer object.
Params:
  • userAgent – the user agent that contains configuration details. This cannot be null.
/** * 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}
/** {@inheritDoc} */
public String getMimeType() { return "text/plain"; }
Sets the encoding of the target file.
Params:
  • encoding – the encoding, null to select the default encoding (UTF-8)
/** * 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.
Params:
  • x – x coordinate
  • y – y coordinate
Returns:true if point lay inside page
/** * 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.
Params:
  • x – x coordinate
  • y – y coordinate
  • ch – char to add
  • ischar – boolean, repersenting is character adding to text buffer
/** * 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.
Params:
  • x – x coordinate
  • y – x coordinate
  • ch – char to add
  • ischar – indicates if it char or background
/** * 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 (charData).

Chars of string map in turn.

Params:
  • row – x coordinate
  • col – y coordinate
  • s – string to add
/** * 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.
Params:
  • area – inline area to render
/** * 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}
/** * {@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.bm) to buffer for background and images (i.e.decoData).
/** * 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.
/** * 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}
/** * {@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}
/** * {@inheritDoc} */
public void stopRenderer() throws IOException { log.info("writing out TEXT"); outputStream.flush(); super.stopRenderer(); }
Does nothing. {@inheritDoc}
/** * Does nothing. * {@inheritDoc} */
protected void restoreStateStackAfterBreakOut(List breakOutList) { }
Does nothing.
Returns:null {@inheritDoc}
/** * Does nothing. * @return null * {@inheritDoc} */
protected List breakOutOfStateStack() { return null; }
Does nothing. {@inheritDoc}
/** * Does nothing. * {@inheritDoc} */
protected void saveGraphicsState() { currentState.push(new CTM()); }
Does nothing. {@inheritDoc}
/** * Does nothing. * {@inheritDoc} */
protected void restoreGraphicsState() { currentState.pop(); }
Does nothing. {@inheritDoc}
/** * Does nothing. * {@inheritDoc} */
protected void beginTextObject() { }
Does nothing. {@inheritDoc}
/** * Does nothing. * {@inheritDoc} */
protected void endTextObject() { }
Does nothing. {@inheritDoc}
/** * Does nothing. * {@inheritDoc} */
protected void clip() { }
Does nothing. {@inheritDoc}
/** * Does nothing. * {@inheritDoc} */
protected void clipRect(float x, float y, float width, float height) { }
Does nothing. {@inheritDoc}
/** * Does nothing. * {@inheritDoc} */
protected void moveTo(float x, float y) { }
Does nothing. {@inheritDoc}
/** * Does nothing. * {@inheritDoc} */
protected void lineTo(float x, float y) { }
Does nothing. {@inheritDoc}
/** * Does nothing. * {@inheritDoc} */
protected void closePath() { }
Fills rectangle startX, startY, width, height with char charToFill.
Params:
  • startX – x-coordinate of upper left point
  • startY – y-coordinate of upper left point
  • width – width of rectangle
  • height – height of rectangle
  • charToFill – filling char
/** * 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}
/** * 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}
/** * 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}
/** {@inheritDoc} */
protected void drawImage(String url, Rectangle2D pos, Map foreignAttributes) { //No images are painted here }
Fills image rectangle with a IMAGE_CHAR.
Params:
  • image – the base image
  • pos – the position of the image
/** * 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.
Params:
  • x – the value of the argument, multiplied by 1000 and rounded
Returns:the value of the argument multiplied by 1000 and rounded to the nearest integer
/** * 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.
Params:
  • x – x coordinate
  • y – y coordinate
  • style – integer, representing border style
  • type – integer, representing border element type
/** * 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}
/** * {@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}
/** * {@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}
/** * {@inheritDoc} */
protected void startVParea(CTM ctm, Rectangle clippingRect) { currentState.push(ctm); }
{@inheritDoc}
/** * {@inheritDoc} */
protected void endVParea() { currentState.pop(); }
{@inheritDoc}
/** {@inheritDoc} */
protected void startLayer(String layer) { }
{@inheritDoc}
/** {@inheritDoc} */
protected void endLayer() { }
{@inheritDoc}
/** {@inheritDoc} */
protected void concatenateTransformationMatrix(AffineTransform at) { currentState.push(new CTM(UnitConv.ptToMpt(at))); } }