Index: src/java/org/apache/fop/fo/flow/TableColumn.java =================================================================== --- src/java/org/apache/fop/fo/flow/TableColumn.java (revision 332010) +++ src/java/org/apache/fop/fo/flow/TableColumn.java (working copy) @@ -30,6 +30,7 @@ import org.apache.fop.fo.ValidationException; import org.apache.fop.fo.expr.PropertyException; import org.apache.fop.fo.properties.CommonBorderPaddingBackground; +import org.apache.fop.fo.properties.FixedLength; /** * Class modelling the fo:table-column object. @@ -176,5 +177,13 @@ sb.append(" column-width=").append(getColumnWidth()); return sb.toString(); } + + /** + * Sets a column width. + * @param length + */ + public void setColumnWidth(Length columnWidth) { + this.columnWidth = columnWidth; + } } Index: src/java/org/apache/fop/render/RendererFactory.java =================================================================== --- src/java/org/apache/fop/render/RendererFactory.java (revision 332010) +++ src/java/org/apache/fop/render/RendererFactory.java (working copy) @@ -33,6 +33,7 @@ import org.apache.fop.fo.FOEventHandler; import org.apache.fop.render.mif.MIFHandler; import org.apache.fop.render.rtf.RTFHandler; +import org.apache.fop.render.txt.TXTHandler; /** * Factory for FOEventHandlers and Renderers. @@ -132,6 +133,8 @@ return new MIFHandler(userAgent, out); } else if (renderType == Constants.RENDER_RTF) { return new RTFHandler(userAgent, out); + } else if (renderType == Constants.RENDER_TXT) { + return new TXTHandler(userAgent, out); } else { if (renderType < Constants.RENDER_MIN_CONST || renderType > Constants.RENDER_MAX_CONST) { Index: src/java/org/apache/fop/render/txt/border/DashedBorderElement.java =================================================================== --- src/java/org/apache/fop/render/txt/border/DashedBorderElement.java (revision 0) +++ src/java/org/apache/fop/render/txt/border/DashedBorderElement.java (revision 0) @@ -0,0 +1,141 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.border; + +import java.util.Arrays; + +/** + * This class is responsible for managing of dashed border elements. + */ +public class DashedBorderElement extends AbstractBorderElement { + + private static final char DASH_HORIZONTAL = '-'; + + private static final char DASH_VERTICAL = '|'; + + private static final char UNDEFINED = '?'; + + private static final int UP2 = 1; + + private static final int RIGHT2 = 2; + + private static final int DOWN2 = 4; + + private static final int LEFT2 = 8; + + private static char[] map = new char[20]; + + static { + Arrays.fill(map, UNDEFINED); + map[0] = ' '; + map[UP2] = DASH_VERTICAL; + map[DOWN2] = DASH_VERTICAL; + map[UP2 + DOWN2] = DASH_VERTICAL; + + map[LEFT2] = DASH_HORIZONTAL; + map[RIGHT2] = DASH_HORIZONTAL; + map[LEFT2 + RIGHT2] = DASH_HORIZONTAL; + } + + /** + * Constructs a newly allocated DashedBorderElement object. + * Fills data using superclass constructor. + * + * @param type binary representation of type gives data + */ + public DashedBorderElement(int type) { + super(type); + } + + /** + * Merges dashed border element with instance of solid and double border + * element, returns instance of SolidAndDoubleBorderElement. + * + * @param sdb instance of SolidAndDoubleBorderElement to merge + * @return merged border element + */ + private AbstractBorderElement mergeSolid(SolidAndDoubleBorderElement sdb) { + AbstractBorderElement e = new SolidAndDoubleBorderElement(EN_SOLID, 0); + for (int i = 0; i < 4; i++) { + e.setData(i, Math.max(data[i], sdb.getData(i))); + } + return e; + } + + /** + * Merges dashed border element with dashed border element and returns + * instance of DashedBorderElement. + * + * @param dbe instance of DashedBorderElement to merge + * @return merged border element + */ + private AbstractBorderElement mergeDashed(DashedBorderElement dbe) { + for (int i = 0; i < 4; i++) { + data[i] = Math.max(data[i], dbe.getData(i)); + } + return this; + } + + /** + * Converts dashed border element to + * SolidAndDoubleBorderElement. + * + * @return converted instance of SolidAndDoubleBorderElement + */ + private AbstractBorderElement toSolidAndDouble() { + AbstractBorderElement e = new SolidAndDoubleBorderElement(EN_SOLID, 0); + for (int i = 0; i < 4; i++) { + e.setData(i, data[i]); + } + return e; + } + + /** + * Merges with border element. + * @param e instance of AbstractBorderElement + * @return instance of AbstractBorderElement + */ + public AbstractBorderElement merge(AbstractBorderElement e) { + AbstractBorderElement abe = this; + if (e instanceof SolidAndDoubleBorderElement) { + abe = mergeSolid((SolidAndDoubleBorderElement) e); + } else if (e instanceof DashedBorderElement) { + abe = mergeDashed((DashedBorderElement) e); + } else { + abe = e; + } + return abe; + } + + /** + * @see org.apache.fop.render.txt.border.AbstractBorderElement#convert2Char() + */ + public char convert2Char() { + int key = 0; + key += data[UP] * UP2; + key += data[DOWN] * DOWN2; + key += data[LEFT] * LEFT2; + key += data[RIGHT] * RIGHT2; + char ch = map[key]; + if (ch == UNDEFINED) { + ch = toSolidAndDouble().convert2Char(); + } + return ch; + } +} Index: src/java/org/apache/fop/render/txt/border/SolidAndDoubleBorderElement.java =================================================================== --- src/java/org/apache/fop/render/txt/border/SolidAndDoubleBorderElement.java (revision 0) +++ src/java/org/apache/fop/render/txt/border/SolidAndDoubleBorderElement.java (revision 0) @@ -0,0 +1,276 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.border; + +import java.util.Arrays; + +/** + * This class is responsible for solid and double border elements managing. + */ +public class SolidAndDoubleBorderElement extends AbstractBorderElement { + + private static final char LIGHT_HORIZONTAL = '\u2500'; + + private static final char LIGHT_VERTICAL = '\u2502'; + + private static final char LIGHT_DOWN_AND_RIGHT = '\u250C'; + + private static final char LIGHT_DOWN_AND_LEFT = '\u2510'; + + private static final char LIGHT_UP_AND_RIGHT = '\u2514'; + + private static final char LIGHT_UP_AND_LEFT = '\u2518'; + + private static final char LIGHT_VERTICAL_AND_RIGHT = '\u251C'; + + private static final char LIGHT_VERTICAL_AND_LEFT = '\u2524'; + + private static final char LIGHT_DOWN_AND_HORIZONTAL = '\u252C'; + + private static final char LIGHT_UP_AND_HORIZONTAL = '\u2534'; + + private static final char LIGHT_VERTICAL_AND_HORIZONTAL = '\u253C'; + + private static final char DOUBLE_HORIZONTAL = '\u2550'; + + private static final char DOUBLE_VERTICAL = '\u2551'; + + private static final char DOUBLE_DOWN_AND_RIGHT = '\u2554'; + + private static final char DOUBLE_DOWN_AND_LEFT = '\u2557'; + + private static final char DOUBLE_UP_AND_RIGHT = '\u255A'; + + private static final char DOUBLE_UP_AND_LEFT = '\u255D'; + + private static final char DOUBLE_VERTICAL_AND_RIGHT = '\u2560'; + + private static final char DOUBLE_VERTICAL_AND_LEFT = '\u2563'; + + private static final char DOUBLE_DOWN_AND_HORIZONTAL = '\u2566'; + + private static final char DOUBLE_UP_AND_HORIZONTAL = '\u2569'; + + private static final char DOUBLE_VERTICAL_AND_HORIZONTAL = '\u256C'; + + private static final char DOWN_SINGLE_AND_RIGHT_DOUBLE = '\u2552'; + + private static final char DOWN_DOUBLE_AND_RIGHT_SINGLE = '\u2553'; + + private static final char DOWN_SINGLE_AND_LEFT_DOUBLE = '\u2555'; + + private static final char DOWN_DOUBLE_AND_LEFT_SINGLE = '\u2556'; + + private static final char UP_SINGLE_AND_RIGHT_DOUBLE = '\u2558'; + + private static final char UP_DOUBLE_AND_RIGHT_SINGLE = '\u2559'; + + private static final char UP_SINGLE_AND_LEFT_DOUBLE = '\u255B'; + + private static final char UP_DOUBLE_AND_LEFT_SINGLE = '\u255C'; + + private static final char VERTICAL_SINGLE_AND_RIGHT_DOUBLE = '\u255E'; + + private static final char VERTICAL_DOUBLE_AND_RIGHT_SINGLE = '\u255F'; + + private static final char VERTICAL_SINGLE_AND_LEFT_DOUBLE = '\u2561'; + + private static final char VERTICAL_DOUBLE_AND_LEFT_SINGLE = '\u2562'; + + private static final char DOWN_SINGLE_AND_HORIZONTAL_DOUBLE = '\u2564'; + + private static final char DOWN_DOUBLE_AND_HORIZONTAL_SINGLE = '\u2565'; + + private static final char UP_SINGLE_AND_HORIZONTAL_DOUBLE = '\u2567'; + + private static final char UP_DOUBLE_AND_HORIZONTAL_SINGLE = '\u2568'; + + private static final char VERTICAL_SINGLE_AND_HORIZONTAL_DOUBLE = '\u256A'; + + private static final char VERTICAL_DOUBLE_AND_HORIZONTAL_SINGLE = '\u256B'; + + private static final char UNDEFINED = '?'; + + private static final int UP3 = 1; + + private static final int DOWN3 = 3; + + private static final int LEFT3 = 9; + + private static final int RIGHT3 = 27; + + private static final char[] MAP = new char[100]; + + static { + Arrays.fill(MAP, UNDEFINED); + MAP[0] = ' '; + MAP[UP3] = LIGHT_VERTICAL; + MAP[DOWN3] = LIGHT_VERTICAL; + MAP[RIGHT3] = LIGHT_HORIZONTAL; + MAP[LEFT3] = LIGHT_HORIZONTAL; + MAP[UP3 + DOWN3] = LIGHT_VERTICAL; + MAP[LEFT3 + RIGHT3] = LIGHT_HORIZONTAL; + MAP[UP3 + LEFT3] = LIGHT_UP_AND_LEFT; + MAP[LEFT3 + DOWN3] = LIGHT_DOWN_AND_LEFT; + MAP[DOWN3 + RIGHT3] = LIGHT_DOWN_AND_RIGHT; + MAP[UP3 + RIGHT3] = LIGHT_UP_AND_RIGHT; + MAP[UP3 + DOWN3 + RIGHT3] = LIGHT_VERTICAL_AND_RIGHT; + MAP[UP3 + LEFT3 + DOWN3] = LIGHT_VERTICAL_AND_LEFT; + MAP[LEFT3 + DOWN3 + RIGHT3] = LIGHT_DOWN_AND_HORIZONTAL; + MAP[UP3 + LEFT3 + RIGHT3] = LIGHT_UP_AND_HORIZONTAL; + MAP[UP3 + LEFT3 + DOWN3 + RIGHT3] = LIGHT_VERTICAL_AND_HORIZONTAL; + //DOUBLE + MAP[2 * UP3] = DOUBLE_VERTICAL; + MAP[2 * DOWN3] = DOUBLE_VERTICAL; + MAP[2 * RIGHT3] = DOUBLE_HORIZONTAL; + MAP[2 * LEFT3] = DOUBLE_HORIZONTAL; + MAP[2 * UP3 + 2 * DOWN3] = DOUBLE_VERTICAL; + MAP[2 * LEFT3 + 2 * RIGHT3] = DOUBLE_HORIZONTAL; + MAP[2 * UP3 + 2 * LEFT3] = DOUBLE_UP_AND_LEFT; + MAP[2 * LEFT3 + 2 * DOWN3] = DOUBLE_DOWN_AND_LEFT; + MAP[2 * DOWN3 + 2 * RIGHT3] = DOUBLE_DOWN_AND_RIGHT; + MAP[2 * UP3 + 2 * RIGHT3] = DOUBLE_UP_AND_RIGHT; + MAP[2 * UP3 + 2 * DOWN3 + 2 * RIGHT3] = DOUBLE_VERTICAL_AND_RIGHT; + MAP[2 * UP3 + 2 * DOWN3 + 2 * LEFT3] = DOUBLE_VERTICAL_AND_LEFT; + MAP[2 * DOWN3 + 2 * RIGHT3 + 2 * LEFT3] = DOUBLE_DOWN_AND_HORIZONTAL; + MAP[2 * UP3 + 2 * RIGHT3 + 2 * LEFT3] = DOUBLE_UP_AND_HORIZONTAL; + MAP[2 * UP3 + 2 * DOWN3 + 2 * RIGHT3 + 2 * LEFT3] = DOUBLE_VERTICAL_AND_HORIZONTAL; + //DOUBLE&SINGLE + MAP[DOWN3 + 2 * RIGHT3] = DOWN_SINGLE_AND_RIGHT_DOUBLE; + MAP[2 * DOWN3 + RIGHT3] = DOWN_DOUBLE_AND_RIGHT_SINGLE; + MAP[DOWN3 + 2 * LEFT3] = DOWN_SINGLE_AND_LEFT_DOUBLE; + MAP[2 * DOWN3 + LEFT3] = DOWN_DOUBLE_AND_LEFT_SINGLE; + MAP[UP3 + 2 * RIGHT3] = UP_SINGLE_AND_RIGHT_DOUBLE; + MAP[2 * UP3 + RIGHT3] = UP_DOUBLE_AND_RIGHT_SINGLE; + MAP[UP3 + 2 * LEFT3] = UP_SINGLE_AND_LEFT_DOUBLE; + MAP[2 * UP3 + LEFT3] = UP_DOUBLE_AND_LEFT_SINGLE; + MAP[UP3 + DOWN3 + 2 * RIGHT3] = VERTICAL_SINGLE_AND_RIGHT_DOUBLE; + MAP[2 * UP3 + 2 * DOWN3 + RIGHT3] = VERTICAL_DOUBLE_AND_RIGHT_SINGLE; + MAP[UP3 + DOWN3 + 2 * LEFT3] = VERTICAL_SINGLE_AND_LEFT_DOUBLE; + MAP[2 * UP3 + 2 * DOWN3 + LEFT3] = VERTICAL_DOUBLE_AND_LEFT_SINGLE; + MAP[DOWN3 + 2 * LEFT3 + 2 * RIGHT3] = DOWN_SINGLE_AND_HORIZONTAL_DOUBLE; + MAP[2 * DOWN3 + LEFT3 + RIGHT3] = DOWN_DOUBLE_AND_HORIZONTAL_SINGLE; + MAP[UP3 + 2 * LEFT3 + 2 * RIGHT3] = UP_SINGLE_AND_HORIZONTAL_DOUBLE; + MAP[2 * UP3 + LEFT3 + RIGHT3] = UP_DOUBLE_AND_HORIZONTAL_SINGLE; + MAP[UP3 + DOWN3 + 2 * LEFT3 + 2 * RIGHT3] = VERTICAL_SINGLE_AND_HORIZONTAL_DOUBLE; + MAP[2 * UP3 + 2 * DOWN3 + LEFT3 + RIGHT3] = VERTICAL_DOUBLE_AND_HORIZONTAL_SINGLE; + } + + /** + * Initializes a newly created SolidAndDoubleBorderElement + * object so that it represents an empty border element. + */ + public SolidAndDoubleBorderElement() { + } + + /** + * Constructs a newly allocated SolidAndDoubleBorderElement + * object. Fills data using binary representation of + * type. If border style is EN_DOUBLE, multiplies + * data[side] by 2 for every side to distinguish EN_SOLID and + * EN_DOUBLE. + * + * @param style integer, representing border style. + * @param type binary representation of type gives data + */ + public SolidAndDoubleBorderElement(int style, int type) { + super(type); + if (style == EN_DOUBLE) { + for (int i = 0; i < 4; i++) { + data[i] *= 2; + } + } + } + + /** + * Merges with sde. + * @param sde instance of SolidAndDoubleBorderElement + * @return instance of AbstractBorderElement + */ + public AbstractBorderElement mergeSolid(SolidAndDoubleBorderElement sde) { + AbstractBorderElement e = new SolidAndDoubleBorderElement(EN_SOLID, 0); + for (int i = 0; i < 4; i++) { + if (sde.getData(i) != 0) { + e.setData(i, sde.getData(i)); + } else { + e.setData(i, data[i]); + } + } + return e; + } + + /** + * Merges with e. + * @param e instance of AbstractBorderElement + * @return instance of AbstractBorderElement + */ + public AbstractBorderElement merge(AbstractBorderElement e) { + AbstractBorderElement abe = this; + if (e instanceof SolidAndDoubleBorderElement) { + abe = mergeSolid((SolidAndDoubleBorderElement) e); + } else if (e instanceof DottedBorderElement) { + abe = e; + } else if (e instanceof DashedBorderElement) { + abe = e.merge(this); + } + return abe; + } + + /** + * Maps to char. + * @return resulting mapping char + */ + private char map2Char() { + int key = 0; + key += data[UP] * UP3; + key += data[LEFT] * LEFT3; + key += data[DOWN] * DOWN3; + key += data[RIGHT] * RIGHT3; + return MAP[key]; + } + + /** + * Modifies data to nearest normal internal representation. + */ + private void modifyData() { + int c1 = 0; + int c2 = 0; + for (int i = 0; i < 4; i++) { + c1 += (data[i] == 1) ? 1 : 0; + c2 += (data[i] == 2) ? 1 : 0; + } + int m = c1 > c2 ? 1 : 0; + int[] p = {0, m, 2 * (1 - m)}; + for (int i = 0; i < 4; i++) { + data[i] = p[data[i]]; + } + } + + /** + * @see org.apache.fop.render.txt.border.AbstractBorderElement#convert2Char() + */ + public char convert2Char() { + char ch = map2Char(); + if (ch == UNDEFINED) { + modifyData(); + ch = map2Char(); + } + return ch; + } +} Index: src/java/org/apache/fop/render/txt/border/BorderManager.java =================================================================== --- src/java/org/apache/fop/render/txt/border/BorderManager.java (revision 0) +++ src/java/org/apache/fop/render/txt/border/BorderManager.java (revision 0) @@ -0,0 +1,164 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.border; + +import org.apache.fop.fo.Constants; +import org.apache.fop.render.txt.TXTState; + +/** + * This keeps all information about borders for current processed page. + */ +public class BorderManager { + + /** Matrix for storing information about one border element. */ + private AbstractBorderElement[][] borderInfo; + + /** Width of current processed border. */ + private int width; + + /** Height of current processed border. */ + private int height; + + /** x-coordinate of upper left point of current processed border. */ + private int startX; + + /** y-coordinate of upper left point of current processed border. */ + private int startY; + + /** Stores TXTState for transforming border elements. */ + private TXTState state; + + /** + * Constructs BorderManger, using pageWidth and + * pageHeight for creating borderInfo. + * + * @param pageWidth page width + * @param pageHeight page height + * @param state TXTState + */ + public BorderManager(int pageWidth, int pageHeight, TXTState state) { + this.state = state; + borderInfo = new AbstractBorderElement[pageHeight][pageWidth]; + } + + /** + * Adds border element to borderInfo. + * + * @param x x-coordinate + * @param y y-coordinate + * @param style border-style + * @param type border element type, binary representation of wich gives + * information about availability or absence of corresponding side. + */ + public void addBorderElement(int x, int y, int style, int type) { + AbstractBorderElement be = null; + + if (style == Constants.EN_SOLID || style == Constants.EN_DOUBLE) { + be = new SolidAndDoubleBorderElement(style, type); + } else if (style == Constants.EN_DOTTED) { + be = new DottedBorderElement(); + } else if (style == Constants.EN_DASHED) { + be = new DashedBorderElement(type); + } else { + return; + } + be.transformElement(state); + + if (borderInfo[y][x] != null) { + borderInfo[y][x] = borderInfo[y][x].merge(be); + } else { + borderInfo[y][x] = be; + } + } + + /** + * @param x x-coordinate + * @param y y-coordinate + * @return if border element at point (x,y) is available, returns instance + * of Character, created on char, given by corresponding border element, + * otherwise returns null. + */ + public Character getCharacter(int x, int y) { + Character c = null; + if (borderInfo[y][x] != null) { + c = new Character(borderInfo[y][x].convert2Char()); + } + return c; + } + + /** + * @return width of current processed border. + */ + public int getWidth() { + return width; + } + + /** + * Sets width of current processed border. + * @param width width of border + */ + public void setWidth(int width) { + this.width = width; + } + + /** + * @return height of current processed border. + */ + public int getHeight() { + return height; + } + + /** + * Sets height of current processed border. + * @param height height of border + */ + public void setHeight(int height) { + this.height = height; + } + + /** + * @return x-coordinate of upper left point of current processed border. + */ + public int getStartX() { + return startX; + } + + /** + * Sets x-coordinate of upper left point of current processed border. + * @param startX x-coordinate of upper left border's point. + */ + public void setStartX(int startX) { + this.startX = startX; + } + + /** + * @return y-coordinate of upper left point of current processed border. + */ + public int getStartY() { + return startY; + } + + /** + * Sets y-coordinate of upper left point of current processed border. + * @param startY y-coordinate of upper left border's point. + */ + public void setStartY(int startY) { + this.startY = startY; + } +} Index: src/java/org/apache/fop/render/txt/border/AbstractBorderElement.java =================================================================== --- src/java/org/apache/fop/render/txt/border/AbstractBorderElement.java (revision 0) +++ src/java/org/apache/fop/render/txt/border/AbstractBorderElement.java (revision 0) @@ -0,0 +1,148 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.border; + +import java.awt.Point; +import java.util.Arrays; + +import org.apache.fop.area.CTM; +import org.apache.fop.fo.Constants; +import org.apache.fop.render.txt.TXTState; + +/** + * This class keeps information about abstract border element, i.e. specifies + * border element for one text position. + */ +public abstract class AbstractBorderElement implements Constants { + + /** + * Constant for a line segment, directing from a center of symbol up + * the the symbol boundary. + */ + public static final int UP = 0; + + /** + * Constant for a line segment, directing from a center of symbol right + * the the symbol boundary. + */ + public static final int RIGHT = 1; + + /** + * Constant for a line segment, directing from a center of symbol down + * the the symbol boundary. + */ + public static final int DOWN = 2; + + /** + * Constant for a line segment, directing from a center of symbol left + * the the symbol boundary. + */ + public static final int LEFT = 3; + + /** + * I-th element of this array specify, if there line from center of symbol + * to corresponding side (UP, RIGHT, DOWN, LEFT). + */ + protected int[] data = {0, 0, 0, 0}; + + /** + * Initializes a newly created AbstractBorderElement object + * so that it represents an empty border element. + */ + public AbstractBorderElement() { + } + + /** + * Constructs a newly allocated AbstractBorderElement object. + * Fills array data using binary representation of + * type. + * + * @param type binary representation of type gives data + */ + public AbstractBorderElement(int type) { + for (int i = 0; i < 4; i++) { + data[i] = (type >> i) & 1; + } + } + + /** + * Returns value of side's element of data. + * + * @param side integer, representing side + * @return value of side's element + */ + public int getData(int side) { + return data[side]; + } + + /** + * Sets a value for data[side]. + * + * @param side integer, representing side + * @param value a new value for data[side] + */ + public void setData(int side, int value) { + data[side] = value; + } + + /** + * Transform border element in according with state. + * @param state instance of TXTState + */ + public void transformElement(TXTState state) { + // here we'll get CTM^-1 without shift + double[] da = state.getResultCTM().toArray(); + CTM ctm = new CTM(da[0], -da[1], -da[2], da[3], 0, 0); + + Point[] pa = new Point[4]; + pa[0] = new Point(0, data[UP]); + pa[1] = new Point(data[RIGHT], 0); + pa[2] = new Point(0, -data[DOWN]); + pa[3] = new Point(-data[LEFT], 0); + + Arrays.fill(data, 0); + for (int i = 0; i < 4; i++) { + Point p = state.transformPoint(pa[i], ctm); + + int length = (int) p.distance(0, 0); + if (p.x == 0 && p.y > 0) { + data[UP] = length; + } else if (p.x == 0 && p.y < 0) { + data[DOWN] = length; + } else if (p.x > 0 && p.y == 0) { + data[RIGHT] = length; + } else if (p.x < 0 && p.y == 0) { + data[LEFT] = length; + } + } + } + + /** + * Merges with border element. + * @param e instance of AbstractBorderElement + * @return instance of AbstractBorderElement + */ + public abstract AbstractBorderElement merge(AbstractBorderElement e); + + /** + * Convert internal representation of border element to char. + * @return corresponding char + */ + public abstract char convert2Char(); +} Index: src/java/org/apache/fop/render/txt/border/DottedBorderElement.java =================================================================== --- src/java/org/apache/fop/render/txt/border/DottedBorderElement.java (revision 0) +++ src/java/org/apache/fop/render/txt/border/DottedBorderElement.java (revision 0) @@ -0,0 +1,45 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.border; + +/** + * This class is responsible for managing of dotted border elements. + */ +public class DottedBorderElement extends AbstractBorderElement { + + private static final char MIDDLE_DOT = '\u00B7'; + + /** + * Merges dotted border element with another border element. Here merging + * is quite simple: returning this without any comparing. + * + * @param e instance of AbstractBorderElement + * @return instance of DottedBorderElement + */ + public AbstractBorderElement merge(AbstractBorderElement e) { + return this; + } + + /** + * @see org.apache.fop.render.txt.border.AbstractBorderElement#convert2Char() + */ + public char convert2Char() { + return MIDDLE_DOT; + } +} Index: src/java/org/apache/fop/render/txt/Helper.java =================================================================== --- src/java/org/apache/fop/render/txt/Helper.java (revision 0) +++ src/java/org/apache/fop/render/txt/Helper.java (revision 0) @@ -0,0 +1,117 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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; + +/** + * This class has a few convenient static methods for number quantization. + */ +public final class Helper { + + /** + * Don't let anyone instantiate this class. + */ + private Helper() { } + + /** + * Returns nearest integer to x, divisible by + * quantum. + * + * @param x integer for quantization + * @param quantum integer, representing quantization + * @return computed nearest integer + */ + public static int round(int x, int quantum) { + int ceil = ceil(x, quantum); + int floor = floor(x, quantum); + return (ceil - x < x - floor) ? ceil : floor; + } + + /** + * Returns minimal possible integer, greater or equal than + * x, divisible by quantum. + * + * @param x integer for quantization + * @param quantum integer, representing quantization + * @return computed nearest integer + */ + public static int ceil(int x, int quantum) { + int dx = (x < 0) || (x % quantum == 0) ? 0 : 1; + return (x / quantum + dx) * quantum; + } + + /** + * Returns maximum possible integer, less or equal than + * oldValue, divisible by quantum. + * + * @param x integer for quantization + * @param quantum integer, representing quantization + * @return computed nearest integer + */ + public static int floor(int x, int quantum) { + int dx = (x > 0) || (x % quantum == 0) ? 0 : -1; + return (x / quantum + dx) * quantum; + } + + /** + * Returns the closest integer to x/y fraction. + * It's possible to consider this methos as a analog of Math.round(x/y), + * without having deal with non-integer. + * + * @param x integer, fraction numerator + * @param y integer, fraction denominator + * @return the value of the fraction rounded to the nearest + * @see java.lang.Math#round(double) + */ + public static int roundPosition(int x, int y) { + return round(x, y) / y; + } + + /** + * Returns the smallest integer that is greater than or equal to the + * x/y fraction. + * It's possible to consider this function as a analog of Math.ceil(x/y), + * without having deal with non-integer. + * + * @param x integer, fraction numerator + * @param y integer, fraction denominator + * @return the smallest integer that is greater than or equal to + * x/y fraction + * @see java.lang.Math#ceil(double) + */ + public static int ceilPosition(int x, int y) { + return ceil(x, y) / y; + } + + + /** + * Returns the largest integer that is less than or equal to the + * argument and is equal to x/y fraction. + * It's possible to consider this function as a analog of Math.floor(x/y), + * without having deal with non-integer. + * + * @param x integer, fraction numerator + * @param y integer, fraction denominator + * @return the largest integer that is less than or equal to + * the argument and is equal to x/y fraction + * @see java.lang.Math#ceil(double) + */ + public static int floorPosition(int x, int y) { + return floor(x, y) / y; + } +} Index: src/java/org/apache/fop/render/txt/TXTHandler.java =================================================================== --- src/java/org/apache/fop/render/txt/TXTHandler.java (revision 0) +++ src/java/org/apache/fop/render/txt/TXTHandler.java (revision 0) @@ -0,0 +1,570 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.io.OutputStream; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.area.AreaTreeHandler; +import org.apache.fop.datatypes.CompoundDatatype; +import org.apache.fop.datatypes.Length; +import org.apache.fop.datatypes.PercentBaseContext; +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.FOText; +import org.apache.fop.fo.expr.NumericProperty; +import org.apache.fop.fo.expr.RelativeNumericProperty; +import org.apache.fop.fo.flow.Block; +import org.apache.fop.fo.flow.BlockContainer; +import org.apache.fop.fo.flow.ExternalGraphic; +import org.apache.fop.fo.flow.Inline; +import org.apache.fop.fo.flow.ListBlock; +import org.apache.fop.fo.flow.ListItem; +import org.apache.fop.fo.flow.PageNumber; +import org.apache.fop.fo.flow.Table; +import org.apache.fop.fo.flow.TableCell; +import org.apache.fop.fo.flow.TableColumn; +import org.apache.fop.fo.pagination.PageSequence; +import org.apache.fop.fo.properties.CommonAbsolutePosition; +import org.apache.fop.fo.properties.CommonBorderPaddingBackground; +import org.apache.fop.fo.properties.CommonFont; +import org.apache.fop.fo.properties.CommonMarginBlock; +import org.apache.fop.fo.properties.FixedLength; +import org.apache.fop.fo.properties.Property; +import org.apache.fop.fo.properties.SpaceProperty; +import org.apache.fop.layoutmgr.BlockLayoutManager; + +/** + * Handler for formatting objects in case of rendering to txt. + * + * This handler gets page-sequence, modifies formatting objects and return them + * to superclass. So areas are generated from modified FO. Idea of modifying is + * to quantize FO properties, making them divisible by width of char or height + * of char. + */ +public class TXTHandler extends AreaTreeHandler { + + /** Percent base context. Needed for line-height. */ + private static final PercentBaseContext CONTEXT + = new BlockLayoutManager(new Block(null)); + + /** Modified font size in millipoints. */ + private static final int MODIFIED_FONT_SIZE = 10000; + + /** Quantum for each side (BEFORE, AFTER, START, END). */ + private final int[] quantum = {TXTRenderer.CHAR_HEIGHT, + TXTRenderer.CHAR_HEIGHT, TXTRenderer.CHAR_WIDTH, + TXTRenderer.CHAR_WIDTH}; + + /** Keeps overpatching for each side. */ + private int[] overPatching = new int[4]; + + /** + * Keeps last overpatching for each side. Needed for selective modifying of + * start-indent and end-indent. + */ + private int[] lastOverPatching = new int[4]; + + /** + * Constructs a newly allocated TXTHandler object. + * + * @param userAgent FOUserAgent + * @param stream OutputStream + * @throws FOPException if the RenderPagesModel cannot be created + */ + public TXTHandler(FOUserAgent userAgent, OutputStream stream) + throws FOPException { + super(userAgent, Constants.RENDER_TXT, stream); + } + + /** + * Sets a component CP_LENGTH of cd to + * value. + + * @param cd CompoundDatatype + * @param value new integer value + */ + private static void setLength(CompoundDatatype cd, int value) { + cd.setComponent(Constants.CP_LENGTH, new FixedLength(value), true); + } + + /** + * Sets components CP_MINIMUM, CP_OPTIMUM, CP_MAXIMUM of + * cd to p. + * + * @param cd instance of CompoundDatatype for modifying. + * @param p property for setting. + */ + private static void setMinOptMax(CompoundDatatype cd, Property p) { + cd.setComponent(Constants.CP_MINIMUM, p, true); + cd.setComponent(Constants.CP_OPTIMUM, p, true); + cd.setComponent(Constants.CP_MAXIMUM, p, true); + } + + /** + * Modifies border of side. If there is no border of given side, does + * nothing, otherwise sets border-width to half of char width or char height + * depending on side.

+ * Difference between values of new border-width and old border-width is + * saved in lastOverPatching. + * + * @param side side to modify. + * @param bpb instance of CommonBorderPaddingBackground for modifying. + */ + private void modifyBorder(int side, CommonBorderPaddingBackground bpb) { + CommonBorderPaddingBackground.BorderInfo bi = bpb.getBorderInfo(side); + + if (bi != null) { + int width = bpb.getBorderWidth(side, false); + setLength(bi.getWidth(), quantum[side] / 2); + lastOverPatching[side] += bpb.getBorderWidth(side, false) - width; + } + } + + /** + * Modifies padding of side. First rounds padding to nearest integer, + * divisible by char width or char height depending on side. If border of + * given side is available, modifies padding in such a way, so sum of border + * width and padding will be divisible by char width or char height, + * depending on side.

+ * Difference between values of new padding and old padding is saved + * in lastOverPatching. + * + * @param side side to modify. + * @param bpb instance of CommonBorderPaddingBackground for modifying. + */ + private void modifyPadding(int side, CommonBorderPaddingBackground bpb) { + int oldPadding = bpb.getPadding(side, false, null); + int newPadding = Helper.round(oldPadding, quantum[side]); + if (bpb.getBorderInfo(side) != null) { + newPadding = Math.max(newPadding, quantum[side]) + - bpb.getBorderWidth(side, false); + } + + setLength(bpb.getPaddingLengthProperty(side), newPadding); + lastOverPatching[side] += newPadding - oldPadding; + } + + /** + * Modifies borders and paddings of bpb. + * + * @param bpb instance of CommonBorderPaddingBackground for modifying. + */ + private void modifyBPB(CommonBorderPaddingBackground bpb) { + modifyBorder(CommonBorderPaddingBackground.BEFORE, bpb); + modifyBorder(CommonBorderPaddingBackground.AFTER, bpb); + modifyBorder(CommonBorderPaddingBackground.START, bpb); + modifyBorder(CommonBorderPaddingBackground.END, bpb); + + modifyPadding(CommonBorderPaddingBackground.BEFORE, bpb); + modifyPadding(CommonBorderPaddingBackground.AFTER, bpb); + modifyPadding(CommonBorderPaddingBackground.START, bpb); + modifyPadding(CommonBorderPaddingBackground.END, bpb); + } + + /** + * Rounds optimum value of space to nearest integer, + * divisible by q. + * + * @param space instance of SpaceProperty. + * @param q integer. + */ + private void modifySpace(SpaceProperty space, int q) { + int value = space.getOptimum(null).getLength().getValue(); + setMinOptMax(space, new FixedLength(Helper.round(value, q))); + } + + /** + * @param length instance of Length. + * @param q integer. + * @return instance of Length, having value nearest to value of + * length, and divisible by q. + */ + private Length roundLength(Length length, int q) { + int x = Helper.round(length.getValue(), q); + return new FixedLength(x); + } + + /** + * @param length instance of Length. + * @param q integer. + * @return instance of Length, having minimal value, greater value of + * length, and divisible by q. + */ + private Length ceilLength(Length length, int q) { + int x = Helper.ceil(length.getValue(), q); + return new FixedLength(x); + } + + /** + * Modifies indent for given side. Summarizes value of indent and modifing + * error (i.e. overPatching). Rounds result to nearest integer, divisible by + * quantum. + * + * @param indent Length to modify. + * @param side an integer, representing side. + * @return modified Length. + */ + private Length modifyIndent(Length indent, int side) { + if (indent instanceof NumericProperty) { + overPatching[side] += lastOverPatching[side]; + } + int newValue = indent.getValue() + overPatching[side]; + newValue = Helper.round(newValue, quantum[side]); + return new FixedLength(newValue); + } + + /** + * Modifies Common Margin Properties-Block: + *

+ * + * @param cmb instance of CommonMarginBlock to modify. + */ + private void modifyCommonMarginBlock(CommonMarginBlock cmb) { + cmb.marginTop = roundLength(cmb.marginTop, TXTRenderer.CHAR_HEIGHT); + cmb.marginBottom = roundLength(cmb.marginBottom, + TXTRenderer.CHAR_HEIGHT); + cmb.marginLeft = roundLength(cmb.marginLeft, TXTRenderer.CHAR_WIDTH); + cmb.marginRight = roundLength(cmb.marginRight, TXTRenderer.CHAR_WIDTH); + + modifySpace(cmb.spaceBefore, TXTRenderer.CHAR_HEIGHT); + modifySpace(cmb.spaceAfter, TXTRenderer.CHAR_HEIGHT); + + if (!(cmb.startIndent instanceof RelativeNumericProperty)) { + cmb.startIndent = modifyIndent(cmb.startIndent, + CommonBorderPaddingBackground.START); + } + if (!(cmb.endIndent instanceof RelativeNumericProperty)) { + cmb.endIndent = modifyIndent(cmb.endIndent, + CommonBorderPaddingBackground.END); + } + } + + /** + * Modifies fo:table attributes: + * + * + * @param table Table to modify. + */ + private void modifyTable(Table table) { + CommonMarginBlock cmb = table.getCommonMarginBlock(); + if (table.getBorderCollapse() == Constants.EN_COLLAPSE) { + // If border-collapse == "collapse", add space-after in order to + // impove interaction with other FO. + int value = cmb.spaceAfter.getOptimum(null).getLength().getValue(); + value += TXTRenderer.CHAR_HEIGHT; + setMinOptMax(cmb.spaceAfter, new FixedLength(value)); + } + modifyCommonMarginBlock(cmb); + + modifyBPB(table.getCommonBorderPaddingBackground()); + + // modify all table-columns + List columnList = table.getColumns(); + Iterator iter = columnList.iterator(); + while (iter.hasNext()) { + modifyTableColumn((TableColumn) iter.next()); + } + } + + /** + * Modifies fo:table-column attributes: + * + * + * @param column TableColumn to modify. + */ + private void modifyTableColumn(TableColumn column) { + column.setColumnWidth(ceilLength(column.getColumnWidth(), + TXTRenderer.CHAR_WIDTH)); + } + + /** + * Modifies padding of fo:table-cell. + * + * @param side side. + * @param bpb instance of CommonBorderPaddingBackground to modify. + */ + private void modifyCellPadding(int side, CommonBorderPaddingBackground bpb) { + if (bpb.getBorderInfo(side) == null) { + int oldPadding = bpb.getPadding(side, false, null); + int newPadding = oldPadding + quantum[side] / 2; + setLength(bpb.getPaddingLengthProperty(side), newPadding); + } + } + + /** + * Modifies table-cell properties: + * + * + * @param c TableCell to modify. + */ + private void modifyTableCell(TableCell c) { + CommonBorderPaddingBackground bpb = c + .getCommonBorderPaddingBackground(); + modifyBPB(bpb); + modifyCellPadding(CommonBorderPaddingBackground.BEFORE, bpb); + modifyCellPadding(CommonBorderPaddingBackground.AFTER, bpb); + modifyCellPadding(CommonBorderPaddingBackground.START, bpb); + modifyCellPadding(CommonBorderPaddingBackground.END, bpb); + } + + /** + * Modifies Common Absolute Position Properties: + * + * + * @param cap CommonAbsolutePosition to modify. + */ + private void modifyCommonAbsolutePosition(CommonAbsolutePosition cap) { + if (cap.absolutePosition == Constants.EN_ABSOLUTE) { + cap.left = roundLength(cap.left, TXTRenderer.CHAR_WIDTH); + cap.top = roundLength(cap.top, TXTRenderer.CHAR_HEIGHT); + } + } + + /** + * Modifies line-height property. Sets a value of line-height to max(char + * height; lowest integer, divisible by char height). + * + * @param lineHeight SpaceProperty to modify. + */ + private void modifyLineHeight(SpaceProperty lineHeight) { + Property p = lineHeight.getOptimum(null); + int value = p.getLength().getValue(CONTEXT); + + int height = TXTRenderer.CHAR_HEIGHT; + int newValue = Math.max(Helper.floor(value, height), height); + setMinOptMax(lineHeight, new FixedLength(newValue)); + } + + /** + * Modifies Common Font Properties: + * + * + * @param cf the font to modify. + */ + private void modifyCommonFont(CommonFont cf) { + if (cf != null) { + cf.fontFamily = "Courier"; + cf.fontSize = new FixedLength(MODIFIED_FONT_SIZE); + cf.fontStretch = Constants.EN_NORMAL; + cf.fontWeight = Constants.EN_NORMAL; + } + } + + /** + * Modifies fo:block: + * + * + * @param block Block to modify. + */ + private void modifyBlock(Block block) { + modifyBPB(block.getCommonBorderPaddingBackground()); + modifyCommonMarginBlock(block.getCommonMarginBlock()); + modifyCommonFont(block.getCommonFont()); + modifyLineHeight(block.getLineHeight()); + } + + /** + * Modifies fo:block-container: + * + * + * @param bc BlockContainer to modify. + */ + private void modifyBlockContainer(BlockContainer bc) { + modifyBPB(bc.getCommonBorderPaddingBackground()); + modifyCommonMarginBlock(bc.getCommonMarginBlock()); + modifyCommonAbsolutePosition(bc.getCommonAbsolutePosition()); + } + + /** + * Modifies fo:inline: + * + * + * @param inline Inline to modify. + */ + private void modifyInline(Inline inline) { + modifyCommonFont(inline.getCommonFont()); + } + + /** + * Modifies FOText: + * + * + * @param text FOText to modify. + */ + private void modifyFOText(FOText text) { + modifyCommonFont(text.getCommonFont()); + } + + /** + * Modifies fo:external-graphic: + * + * + * @param eg ExternalGraphic to modify. + */ + private void modifyExternalGraphic(ExternalGraphic eg) { + modifyBPB(eg.getCommonBorderPaddingBackground()); + modifyLineHeight(eg.getLineHeight()); + } + + /** + * Modifies fo:list-block: + * + * + * @param lb ListBlock to modify. + */ + private void modifyListBlock(ListBlock lb) { + modifyBPB(lb.getCommonBorderPaddingBackground()); + modifyCommonMarginBlock(lb.getCommonMarginBlock()); + } + + /** + * Modifies fo:list-item: + * + *

+ * Make refinement for fo:list-item-label and fo:list-item-body. + * + * @param li ListItem to modify. + */ + private void modifyListItem(ListItem li) { + modifyBPB(li.getCommonBorderPaddingBackground()); + modifyCommonMarginBlock(li.getCommonMarginBlock()); + refinement(li.getLabel()); + refinement(li.getBody()); + } + + /** + * Does refinement for particular node. Modifies node's properties and + * refines its children recursively. + * + * @param node the node to refine. + */ + private void refinement(FONode node) { + int[] saveOverPatching = (int[]) overPatching.clone(); + Arrays.fill(lastOverPatching, 0); + + if (node instanceof Block) { + modifyBlock((Block) node); + } else if (node instanceof BlockContainer) { + modifyBlockContainer((BlockContainer) node); + } else if (node instanceof Inline) { + modifyInline((Inline) node); + } else if (node instanceof FOText) { + modifyFOText((FOText) node); + } else if (node instanceof Table) { + modifyTable((Table) node); + Arrays.fill(overPatching, 0); + } else if (node instanceof TableCell) { + modifyTableCell((TableCell) node); + } else if (node instanceof ExternalGraphic) { + modifyExternalGraphic((ExternalGraphic) node); + } else if (node instanceof ListBlock) { + modifyListBlock((ListBlock) node); + } else if (node instanceof ListItem) { + modifyListItem((ListItem) node); + } else if (node instanceof PageNumber) { + modifyCommonFont(((PageNumber) node).getCommonFont()); + } + + Iterator it = node.getChildNodes(); + if (it != null) { + while (it.hasNext()) { + refinement((FONode) it.next()); + } + } + overPatching = saveOverPatching; + } + + /** + * Run refinement for: + *

+ * + * @param pageSequence PageSequence to refine. + */ + public void endPageSequence(PageSequence pageSequence) { + Arrays.fill(overPatching, 0); + + refinement(pageSequence.getMainFlow()); + + if (pageSequence.getStaticContent("xsl-region-before") != null) { + refinement(pageSequence.getStaticContent("xsl-region-before")); + } + if (pageSequence.getStaticContent("xsl-region-after") != null) { + refinement(pageSequence.getStaticContent("xsl-region-after")); + } + if (pageSequence.getStaticContent("xsl-region-start") != null) { + refinement(pageSequence.getStaticContent("xsl-region-start")); + } + if (pageSequence.getStaticContent("xsl-region-end") != null) { + refinement(pageSequence.getStaticContent("xsl-region-end")); + } + + super.endPageSequence(pageSequence); + } +} Index: src/java/org/apache/fop/render/txt/TXTRenderer.java =================================================================== --- src/java/org/apache/fop/render/txt/TXTRenderer.java (revision 332010) +++ src/java/org/apache/fop/render/txt/TXTRenderer.java (working copy) @@ -18,122 +18,530 @@ package org.apache.fop.render.txt; -// FOP -import org.apache.fop.render.PrintRenderer; -import org.apache.fop.render.pcl.PCLStream; +import java.awt.Point; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; +import org.apache.fop.apps.FOPException; +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.datatypes.ColorType; +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 - * - * Created by Arthur E Welch III while at M&I EastPoint Technology - * Modified by Mark Lillywhite mark-fop@inomial.com to use the new - * Renderer interface. + * Renderer that renders areas to plain text. + * + * @author Art Welch + * @author Mark Lillywhite (to use + * the new Renderer interface) */ -public class TXTRenderer extends PrintRenderer { +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'; - /** The MIME type for PostScript */ - public static final String MIME_TYPE = "text/plain"; + private static final char FULL_BLOCK = '\u2588'; - /** - * the current stream to add Text commands to - */ - private PCLStream currentStream; + private static final char IMAGE_CHAR = '#'; - private int pageHeight = 7920; + /**The stream for output */ + private OutputStream outputStream; - // These variables control the virtual paggination functionality. - private int curdiv = 0; - private int divisions = -1; - private int paperheight = -1; // Paper height in decipoints? - private int orientation = -1; // -1=default/unknown, 0=portrait, 1=landscape. - private int topmargin = -1; // Top margin in decipoints? - private int leftmargin = -1; // Left margin in decipoints? - private int fullmargin = 0; - private final boolean debug = false; + /** The current stream to add Text commands to. */ + private TXTStream currentStream; - // Variables for rendering text. - private StringBuffer charData[]; - private StringBuffer decoData[]; - private float textCPI = 16.67f; - private float textLPI = 8; - private int maxX = (int)(8.5f * textCPI + 1); - private int maxY = (int)(11f * textLPI + 1); - private float xFactor; - private float yFactor; - /** - * Every line except the last line on a page (which will end with + /** Buffer for text. */ + private StringBuffer[] charData; + + /** Buffer for background and images. */ + private StringBuffer[] decoData; + + /** 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 String lineEnding = "\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 String pageEnding = "\f"; + + /** Equals true, if current page is first. */ + private boolean firstPage = false; + + /** Manager for storing border's information. */ + private BorderManager bm; + + /** Char for current filling. */ + private char fillChar; + + /** Saves current coordinate transformation. */ + private TXTState currentState = new TXTState(); + /** - * If true then graphics/decorations will not be rendered - text only. + * Constructs a newly allocated TXTRenderer object. */ - private boolean suppressGraphics = false; - private boolean firstPage = false; + public TXTRenderer() { + } - private void addStr(int row, int col, String str, boolean ischar) { - if (debug) { - log.debug("TXTRenderer.addStr(" + row + ", " + col - + ", \"" + str + "\", " + ischar + ")"); + /** + * Indicates if point (x, y) lay inside currentPage. + * + * @param x x coordinate + * @param y y coordinate + * @return true 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); } - if (suppressGraphics && !ischar) { - return; + } + + /** + * Adds string to text buffer (charData).

+ * 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); } - StringBuffer sb; - if (row < 0) { - row = 0; - } - if (ischar) { - sb = charData[row]; + } + + /** + * 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, CHAR_HEIGHT); + + String s = area.getText(); + + addString(row, col, s); + + super.renderText(area); + } + + /** + * @see org.apache.fop.render.Renderer#renderPage(PageViewport) + */ + public void renderPage(PageViewport page) throws IOException, FOPException { + if (firstPage) { + firstPage = false; } else { - sb = decoData[row]; + currentStream.add(pageEnding); } - if (sb == null) { - sb = new StringBuffer(); + + 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); + + // 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(); } - if ((col + str.length()) > maxX) { - col = maxX - str.length(); + + 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). + */ + 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.charValue(), false); + } + } } - if (col < 0) { - col = 0; - if (str.length() > maxX) { - str = str.substring(0, maxX); + } + + /** + * 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(lineEnding); + } } - // Pad to col - for (int countr = sb.length(); countr < col; countr++) { - sb.append(' '); + } + + /** + * @see org.apache.fop.render.Renderer#startRenderer(java.io.OutputStream) + */ + public void startRenderer(OutputStream os) throws IOException { + log.info("Rendering areas to TEXT."); + this.outputStream = os; + currentStream = new TXTStream(os); + firstPage = true; + } + + /** + * @see org.apache.fop.render.Renderer#stopRenderer() + */ + public void stopRenderer() throws IOException { + log.info("writing out TEXT"); + outputStream.flush(); + super.stopRenderer(); + } + + /** + * Does nothing. + * @see org.apache.fop.render.AbstractPathOrientedRenderer + */ + protected void restoreStateStackAfterBreakOut(List breakOutList) { + } + + /** + * Does nothing. + * @return null + * @see org.apache.fop.render.AbstractPathOrientedRenderer + */ + protected List breakOutOfStateStack() { + return null; + } + + /** + * Does nothing. + * @see org.apache.fop.render.AbstractPathOrientedRenderer + */ + protected void saveGraphicsState() { + } + + /** + * Does nothing. + * @see org.apache.fop.render.AbstractPathOrientedRenderer + */ + protected void restoreGraphicsState() { + } + + /** + * Does nothing. + * @see org.apache.fop.render.AbstractPathOrientedRenderer + */ + protected void beginTextObject() { + } + + /** + * Does nothing. + * @see org.apache.fop.render.AbstractPathOrientedRenderer + */ + protected void endTextObject() { + } + + /** + * Does nothing. + * @see org.apache.fop.render.AbstractPathOrientedRenderer + */ + protected void clip() { + } + + /** + * Does nothing. + * @see org.apache.fop.render.AbstractPathOrientedRenderer + */ + protected void clipRect(float x, float y, float width, float height) { + } + + /** + * Does nothing. + * @see org.apache.fop.render.AbstractPathOrientedRenderer + */ + protected void moveTo(float x, float y) { + } + + /** + * Does nothing. + * @see org.apache.fop.render.AbstractPathOrientedRenderer + */ + protected void lineTo(float x, float y) { + } + + /** + * Does nothing. + * @see org.apache.fop.render.AbstractPathOrientedRenderer + */ + protected void closePath() { + } + + /** + * Fills rectangle startX, startY, width, height with char + * charToFill. + * + * @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); + } } - if (debug) { - log.debug("TXTRenderer.addStr() sb.length()=" - + sb.length()); + } + + /** + * Fills a rectangular area with the current filling char. + * @see org.apache.fop.render.AbstractPathOrientedRenderer + */ + 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. + * @see org.apache.fop.render.AbstractPathOrientedRenderer + */ + protected void updateColor(ColorType col, boolean fill) { + if (col == null) { + return; } - for (int countr = col; countr < (col + str.length()); countr++) { - if (countr >= sb.length()) { - sb.append(str.charAt(countr - col)); - } else { - if (debug) { - log.debug("TXTRenderer.addStr() sb.length()=" - + sb.length() + " countr=" + countr); - } - sb.setCharAt(countr, str.charAt(countr - col)); - } + // fillShade evaluation was taken from fop-0.20.5 + double fillShade = 0.30f * col.getRed() + + 0.59f * col.getGreen() + + 0.11f * 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 = ' '; } + } - if (ischar) { - charData[row] = sb; + /** + * Does nothing. + * @param url String + * @param pos Rectangle2D + */ + protected void drawImage(String url, Rectangle2D pos) { + } + + /** + * Fills image rectangle with a IMAGE_CHAR. + * @see org.apache.fop.render.AbstractPathOrientedRenderer + */ + public void renderImage(Image image, Rectangle2D pos) { + int x1 = Helper.ceilPosition(currentIPPosition, CHAR_WIDTH); + int y1 = Helper.ceilPosition(currentBPPosition, CHAR_HEIGHT); + int width = Helper.ceilPosition((int) pos.getWidth(), CHAR_WIDTH); + int height = Helper.ceilPosition((int) pos.getHeight(), CHAR_HEIGHT); + + 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); + } + } + + /** + * @see org.apache.fop.render.AbstractPathOrientedRenderer + */ + protected void drawBorderLine(float x1, float y1, float x2, float y2, + boolean horz, boolean startOrBefore, int style, ColorType col) { + + int borderHeight = bm.getHeight(); + int borderWidth = bm.getWidth(); + int borderStartX = bm.getStartX(); + int borderStartY = bm.getStartY(); + + int x, 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, dy, length, startType, endType; + if (horz) { + length = borderWidth; + dx = 1; + dy = 0; + startType = 1 << AbstractBorderElement.RIGHT; + endType = 1 << AbstractBorderElement.LEFT; } else { - decoData[row] = sb; + 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); } - /** @see org.apache.fop.render.AbstractRenderer */ - public String getMimeType() { - return MIME_TYPE; + /** + * @see org.apache.fop.render.AbstractPathOrientedRenderer + */ + 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)); + bm.setStartX(Helper.ceilPosition(toMilli(startx), CHAR_WIDTH)); + bm.setStartY(Helper.ceilPosition(toMilli(starty), CHAR_HEIGHT)); + + super.drawBackAndBorders(area, startx, starty, width, height); } + /** + * @see org.apache.fop.render.AbstractRenderer#startVParea(CTM) + */ + protected void startVParea(CTM ctm) { + currentState.push(ctm); + } + + /** + * @see org.apache.fop.render.AbstractRenderer#endVParea() + */ + protected void endVParea() { + currentState.pop(); + } } Index: src/java/org/apache/fop/render/txt/TXTState.java =================================================================== --- src/java/org/apache/fop/render/txt/TXTState.java (revision 0) +++ src/java/org/apache/fop/render/txt/TXTState.java (revision 0) @@ -0,0 +1,137 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.Point; +import java.awt.geom.Rectangle2D; +import java.util.Iterator; +import java.util.LinkedList; + +import org.apache.fop.area.CTM; + +/** + * This keeps information about the current state when writing to txt, i.e. + * manages coordinate transformation matrices for getting absolute coordinates. + */ +public class TXTState { + + /** Keeps all coordinate transformation matrices during rendering. */ + private LinkedList stackCTM = new LinkedList(); + + /** + * Current result coordinate transformation matrix. It's product of + * all matrices in order, saved in stackCTM. + */ + private CTM resultCTM = new CTM(); + + /** + * Constructs a newly allocated TXTState object. + */ + public TXTState() { + } + + /** + * Updates result coordinate transformation matrix + * (i.e. resultCTM), multipliing it by given matrix. + * + * @param ctm CTM + */ + private void updateResultCTM(CTM ctm) { + resultCTM = resultCTM.multiply(ctm); + } + + /** + * Recalculate current result coordinate transformation matrix. + */ + private void calcResultCTM() { + resultCTM = new CTM(); + for (Iterator i = stackCTM.iterator(); i.hasNext();) { + updateResultCTM((CTM) i.next()); + } + } + + /** + * Push the current coordinate transformation matrix onto the stack and + * reevaluate resultCTM. + * + * @param ctm instance of CTM + */ + public void push(CTM ctm) { + stackCTM.addLast(ctm); + updateResultCTM(ctm); + } + + /** + * Pop the coordinate transformation matrix from the stack and reevaluate + * resultCTM. + */ + public void pop() { + stackCTM.removeLast(); + calcResultCTM(); + } + + /** + * Modifies coordinate transformation matrix in such a way, so + * x-shift and y-shift will be transformed in text positions. + * + * @param ctm CTM to modify + * @return instance of CTM + */ + public CTM refineCTM(CTM ctm) { + double[] da = ctm.toArray(); + // refine x-shift + da[4] = Helper.roundPosition((int) da[4], TXTRenderer.CHAR_WIDTH); + // refine y-shift + da[5] = Helper.roundPosition((int) da[5], TXTRenderer.CHAR_HEIGHT); + + return new CTM(da[0], da[1], da[2], da[3], da[4], da[5]); + } + + /** + * Transforms point using ctm. + * + * @param p Point + * @param ctm CTM + * @return transformed Point + */ + public Point transformPoint(Point p, CTM ctm) { + Rectangle2D r = new Rectangle2D.Double(p.x, p.y, 0, 0); + CTM nctm = refineCTM(ctm); + r = nctm.transform(r); + return new Point((int) r.getX(), (int) r.getY()); + } + + /** + * Transforms point (x, y) using resultCTM. + * + * @param x x-coordinate + * @param y y-coordinate + * @return transformed Point + */ + public Point transformPoint(int x, int y) { + return transformPoint(new Point(x, y), resultCTM); + } + + /** + * @return current result coordinate transformation matrix + */ + public CTM getResultCTM() { + return resultCTM; + } +} Index: src/java/org/apache/fop/render/txt/TXTStream.java =================================================================== --- src/java/org/apache/fop/render/txt/TXTStream.java (revision 332010) +++ src/java/org/apache/fop/render/txt/TXTStream.java (working copy) @@ -47,7 +47,7 @@ } try { - byte buff[] = str.getBytes("UTF-8"); + byte[] buff = str.getBytes("UTF-8"); out.write(buff); } catch (IOException e) { throw new RuntimeException(e.toString());