]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Bugzilla #37253:
authorJeremias Maerki <jeremias@apache.org>
Wed, 9 Nov 2005 21:00:23 +0000 (21:00 +0000)
committerJeremias Maerki <jeremias@apache.org>
Wed, 9 Nov 2005 21:00:23 +0000 (21:00 +0000)
TXT Renderer resurrected with additional features.
Submitted by: Sergey Simonchik <Sergey.Simonchik.at.borland.com>

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@332141 13f79535-47bb-0310-9956-ffa450edef68

12 files changed:
src/java/org/apache/fop/fo/flow/TableColumn.java
src/java/org/apache/fop/render/RendererFactory.java
src/java/org/apache/fop/render/txt/Helper.java [new file with mode: 0644]
src/java/org/apache/fop/render/txt/TXTHandler.java [new file with mode: 0644]
src/java/org/apache/fop/render/txt/TXTRenderer.java
src/java/org/apache/fop/render/txt/TXTState.java [new file with mode: 0644]
src/java/org/apache/fop/render/txt/TXTStream.java
src/java/org/apache/fop/render/txt/border/AbstractBorderElement.java [new file with mode: 0644]
src/java/org/apache/fop/render/txt/border/BorderManager.java [new file with mode: 0644]
src/java/org/apache/fop/render/txt/border/DashedBorderElement.java [new file with mode: 0644]
src/java/org/apache/fop/render/txt/border/DottedBorderElement.java [new file with mode: 0644]
src/java/org/apache/fop/render/txt/border/SolidAndDoubleBorderElement.java [new file with mode: 0644]

index 94cacf4e663777079d7494b30daf23c2cc6cae1a..df3cd7663f94ca5e56f381b202d30ddcf02b2d91 100644 (file)
@@ -25,7 +25,6 @@ import org.apache.fop.apps.FOPException;
 import org.apache.fop.datatypes.Length;
 import org.apache.fop.datatypes.Numeric;
 import org.apache.fop.fo.FONode;
-import org.apache.fop.fo.FObj;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
 import org.apache.fop.fo.expr.PropertyException;
@@ -126,6 +125,14 @@ public class TableColumn extends TableFObj {
         return columnWidth;
     }
 
+    /**
+     * Sets the column width.
+     * @param columnWidth the column width
+     */
+    public void setColumnWidth(Length columnWidth) {
+        this.columnWidth = columnWidth;
+    }
+
     /**
      * @return the "column-number" property.
      */
@@ -176,5 +183,6 @@ public class TableColumn extends TableFObj {
         sb.append(" column-width=").append(getColumnWidth());
         return sb.toString();
     }
+
 }
 
index dbd94119d2278754be5605dad4daed1eaa40f816..d4d1460bd9136599a70c7737b441f2dcaaa7db8d 100644 (file)
@@ -33,6 +33,7 @@ import org.apache.fop.fo.Constants;
 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 @@ public class RendererFactory {
                 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) {
diff --git a/src/java/org/apache/fop/render/txt/Helper.java b/src/java/org/apache/fop/render/txt/Helper.java
new file mode 100644 (file)
index 0000000..fe44e4e
--- /dev/null
@@ -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 <code>x</code>, divisible by 
+     * <code>quantum</code>. 
+     * 
+     * @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 
+     * <code>x</code>, divisible by <code>quantum</code>.
+     *         
+     * @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
+     * <code>oldValue</code>, divisible by <code>quantum</code>.
+     * 
+     * @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 <code>x/y</code> 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 
+     * <code>x/y</code> 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 
+     *         <code>x/y</code> 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 <code>x/y</code> 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 <code>x/y</code> fraction
+     * @see java.lang.Math#ceil(double)
+     */
+    public static int floorPosition(int x, int y) {
+        return floor(x, y) / y;
+    }
+}
diff --git a/src/java/org/apache/fop/render/txt/TXTHandler.java b/src/java/org/apache/fop/render/txt/TXTHandler.java
new file mode 100644 (file)
index 0000000..d75e65f
--- /dev/null
@@ -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 <code>TXTHandler</code> 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 <code>CP_LENGTH</code> of <code>cd</code> to
+     * <code>value</code>.
+
+     * @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 <code>CP_MINIMUM, CP_OPTIMUM, CP_MAXIMUM</code> of
+     * <code>cd</code> to <code>p</code>.
+     * 
+     * @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. <p> 
+     * Difference between values of new border-width and old border-width is 
+     * saved in <code>lastOverPatching</code>.
+     * 
+     * @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. <p>
+     * Difference between values of new padding and old padding is saved 
+     * in <code>lastOverPatching</code>.
+     * 
+     * @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 <code>bpb</code>.
+     * 
+     * @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 <code>space</code> to nearest integer,
+     * divisible by <code>q</code>.
+     * 
+     * @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
+     *         <code>length</code>, and divisible by <code>q</code>.
+     */
+    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
+     *         <code>length</code>, and divisible by <code>q</code>.
+     */
+    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:
+     * <ul>
+     * <li>margin-top, margin-left, margin-bottom, margin-right
+     * <li>start-indent, end-indent
+     * <li>space-before, space-after.
+     * </ul>
+     * 
+     * @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:
+     * <ul>
+     * <li>Common Margin Properties Block
+     * <li>Common Border, Padding, and Background Properties
+     * <li>columns.
+     * </ul>
+     * 
+     * @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:
+     * <ul>
+     * <li>width.
+     * </ul>
+     * 
+     * @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:
+     * <ul>
+     * <li>Common Border, Padding, and Background Properties.
+     * </ul>
+     * 
+     * @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:
+     * <ul>
+     * <li>left
+     * <li>top.
+     * </ul>
+     * 
+     * @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:
+     * <ul>
+     * <li>font-family = Courier;
+     * <li>font-size = MODIFIED_FONT_SIZE;
+     * <li>font-stretch = EN_NORMAL;
+     * <li>font-weight = EN_NORMAL.
+     * </ul>
+     * 
+     * @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:
+     * <ul>
+     * <li>Common Border, Padding, and Background Properties
+     * <li>Common Margin Properties-Block
+     * <li>Common Font Properties
+     * <li>line-height.
+     * </ul>
+     * 
+     * @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:
+     * <ul>
+     * <li>Common Border, Padding, and Background Properties
+     * <li>Common Margin Properties-Block
+     * <li>Common Absolute Position Properties.
+     * </ul>
+     * 
+     * @param bc BlockContainer to modify.
+     */
+    private void modifyBlockContainer(BlockContainer bc) {
+        modifyBPB(bc.getCommonBorderPaddingBackground());
+        modifyCommonMarginBlock(bc.getCommonMarginBlock());
+        modifyCommonAbsolutePosition(bc.getCommonAbsolutePosition());
+    }
+
+    /**
+     * Modifies fo:inline:
+     * <ul>
+     * <li>Common Font Properties
+     * </ul>
+     * 
+     * @param inline Inline to modify.
+     */
+    private void modifyInline(Inline inline) {
+        modifyCommonFont(inline.getCommonFont());
+    }
+
+    /**
+     * Modifies FOText:
+     * <ul>
+     * <li>Common Font Properties
+     * </ul>
+     * 
+     * @param text FOText to modify.
+     */
+    private void modifyFOText(FOText text) {
+        modifyCommonFont(text.getCommonFont());
+    }
+
+    /**
+     * Modifies fo:external-graphic:
+     * <ul>
+     * <li>Common Border, Padding, and Background Properties
+     * <li>line-height.
+     * </ul>
+     * 
+     * @param eg ExternalGraphic to modify.
+     */
+    private void modifyExternalGraphic(ExternalGraphic eg) {
+        modifyBPB(eg.getCommonBorderPaddingBackground());
+        modifyLineHeight(eg.getLineHeight());
+    }
+
+    /**
+     * Modifies fo:list-block:
+     * <ul>
+     * <li>Common Border, Padding, and Background Properties
+     * <li>Common Margin Properties-Block.
+     * </ul>
+     * 
+     * @param lb ListBlock to modify.
+     */
+    private void modifyListBlock(ListBlock lb) {
+        modifyBPB(lb.getCommonBorderPaddingBackground());
+        modifyCommonMarginBlock(lb.getCommonMarginBlock());
+    }
+
+    /**
+     * Modifies fo:list-item:
+     * <ul>
+     * <li>Common Border, Padding, and Background Properties
+     * <li>Common Margin Properties-Block.
+     * </ul>
+     * <p>
+     * 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:
+     * <ul>
+     * <li>mainflow (xsl-region-body)
+     * <li>staticflow (xsl-region-before, xsl-region-after, xsl-region-start,
+     * xsl-region-end).
+     * </ul>
+     * 
+     * @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 0f9dda7fa097bc8a1e3a372fd9a07606bda1b5ef..a06c815eaa018dbe70536a60a6fe5d21e5b0bcc8 100644 (file)
  
 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 <a href="mailto:mark-fop@inomial.com">Mark Lillywhite</a> (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 <code>TXTRenderer</code> 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 + ")");
-        }
-        if (suppressGraphics && !ischar) {
-            return;
+    /**
+     * 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);
         }
-        StringBuffer sb;
-        if (row < 0) {
-            row = 0;
+    }
+
+    /**
+     * 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);
         }
-        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.<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.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 
+     * <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.
+     * @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;
         }
-        if (debug) {
-            log.debug("TXTRenderer.addStr() sb.length()="
-                               + sb.length());
+        // 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 = ' ';
         }
-        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));
-            }
+    }
+
+    /** 
+     * Does nothing. 
+     * @param url  String
+     * @param pos  Rectangle2D
+     */
+    protected void drawImage(String url, Rectangle2D pos) {
+    }
+    
+    /**
+     * Fills image rectangle with a <code>IMAGE_CHAR</code>.
+     * @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) {
 
-        if (ischar) {
-            charData[row] = sb;
+        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();
+    }
 }
diff --git a/src/java/org/apache/fop/render/txt/TXTState.java b/src/java/org/apache/fop/render/txt/TXTState.java
new file mode 100644 (file)
index 0000000..b63f049
--- /dev/null
@@ -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 <code>stackCTM</code>.
+     */
+    private CTM resultCTM = new CTM();
+
+    /**
+     * Constructs a newly allocated <code>TXTState</code> object.
+     */
+    public TXTState() {
+    }
+
+    /**
+     * Updates result coordinate transformation matrix 
+     * (i.e. <code>resultCTM</code>), 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 <code>resultCTM</code>.
+     * 
+     * @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
+     * <code>resultCTM</code>.
+     */
+    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 <code>point</code> using <code>ctm</code>.
+     * 
+     * @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 <code>resultCTM</code>.
+     * 
+     * @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 df38cd10e3d5e828ab02346c3439532d5efb2165..f3dc9e194a4aad755f646ad85f7e011c6a1becc9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 1999-2004 The Apache Software Foundation.
+ * Copyright 1999-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.
@@ -47,7 +47,7 @@ public class TXTStream {
         }
 
         try {
-            byte buff[] = str.getBytes("UTF-8");
+            byte[] buff = str.getBytes("UTF-8");
             out.write(buff);
         } catch (IOException e) {
             throw new RuntimeException(e.toString());
diff --git a/src/java/org/apache/fop/render/txt/border/AbstractBorderElement.java b/src/java/org/apache/fop/render/txt/border/AbstractBorderElement.java
new file mode 100644 (file)
index 0000000..d02a8da
--- /dev/null
@@ -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 <code>AbstractBorderElement</code> object 
+     * so that it represents an empty border element.
+     */
+    public AbstractBorderElement() {
+    }
+
+    /**
+     * Constructs a newly allocated <code>AbstractBorderElement</code> object.
+     * Fills array <code>data</code> using binary representation of 
+     * <code>type</code>.
+     * 
+     * @param type binary representation of type gives <code>data</code>
+     */
+    public AbstractBorderElement(int type) {
+        for (int i = 0; i < 4; i++) {
+            data[i] = (type >> i) & 1;
+        }
+    }
+
+    /**
+     * Returns value of side's element of <code>data</code>.
+     * 
+     * @param side integer, representing side
+     * @return value of side's element
+     */
+    public int getData(int side) {
+        return data[side];
+    }
+
+    /**
+     * Sets a value for <code>data[side]</code>.
+     * 
+     * @param side integer, representing side
+     * @param value a new value for <code>data[side]</code>
+     */
+    public void setData(int side, int value) {
+        data[side] = value;
+    }
+
+    /**
+     * Transform border element in according with <code>state</code>.
+     * @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();
+}
diff --git a/src/java/org/apache/fop/render/txt/border/BorderManager.java b/src/java/org/apache/fop/render/txt/border/BorderManager.java
new file mode 100644 (file)
index 0000000..2e20d0c
--- /dev/null
@@ -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 <code>pageWidth</code> and 
+     * <code>pageHeight</code> for creating <code>borderInfo</code>.
+     *  
+     * @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 <code>borderInfo</code>.
+     * 
+     * @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;
+    }
+}
diff --git a/src/java/org/apache/fop/render/txt/border/DashedBorderElement.java b/src/java/org/apache/fop/render/txt/border/DashedBorderElement.java
new file mode 100644 (file)
index 0000000..7e7aa7a
--- /dev/null
@@ -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 <code>DashedBorderElement</code> object.
+     * Fills <code>data</code> using superclass constructor.
+     * 
+     * @param type binary representation of type gives <code>data</code>
+     */
+    public DashedBorderElement(int type) {
+        super(type);
+    }
+
+    /**
+     * Merges dashed border element with instance of solid and double border
+     * element, returns instance of <code>SolidAndDoubleBorderElement</code>. 
+     * 
+     * @param sdb instance of <code>SolidAndDoubleBorderElement</code> 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 <code>DashedBorderElement</code>. 
+     * 
+     * @param dbe instance of <code>DashedBorderElement</code> 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 
+     * <code>SolidAndDoubleBorderElement</code>.
+     * 
+     * @return converted instance of <code>SolidAndDoubleBorderElement</code>
+     */
+    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;
+    }
+}
diff --git a/src/java/org/apache/fop/render/txt/border/DottedBorderElement.java b/src/java/org/apache/fop/render/txt/border/DottedBorderElement.java
new file mode 100644 (file)
index 0000000..8e69397
--- /dev/null
@@ -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 <code>this</code> 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;
+    }
+}
diff --git a/src/java/org/apache/fop/render/txt/border/SolidAndDoubleBorderElement.java b/src/java/org/apache/fop/render/txt/border/SolidAndDoubleBorderElement.java
new file mode 100644 (file)
index 0000000..72ca9d2
--- /dev/null
@@ -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 <code>SolidAndDoubleBorderElement</code> 
+     * object so that it represents an empty border element.
+     */
+    public SolidAndDoubleBorderElement() {
+    }
+
+    /**
+     * Constructs a newly allocated <code>SolidAndDoubleBorderElement</code> 
+     * object. Fills <code>data</code> using binary representation of 
+     * <code>type</code>. If border style is EN_DOUBLE, multiplies 
+     * <code>data[side]</code> 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 <code>data</code>
+     */
+    public SolidAndDoubleBorderElement(int style, int type) {
+        super(type);
+        if (style == EN_DOUBLE) {
+            for (int i = 0; i < 4; i++) {
+                data[i] *= 2;
+            }
+        }
+    }
+    
+    /**
+     * Merges with <code>sde</code>.
+     * @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;
+    }
+}