org.apache.fop.render.pcl.PCLPainter.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.fop.render.pcl.PCLPainter.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id: PCLPainter.java 820672 2009-10-01 14:48:27Z jeremias $ */

package org.apache.fop.render.pcl;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.util.Map;
import java.util.Stack;

import org.w3c.dom.Document;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.xmlgraphics.image.loader.ImageException;
import org.apache.xmlgraphics.image.loader.ImageInfo;
import org.apache.xmlgraphics.image.loader.ImageProcessingHints;
import org.apache.xmlgraphics.image.loader.ImageSize;
import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D;
import org.apache.xmlgraphics.java2d.GraphicContext;
import org.apache.xmlgraphics.java2d.Graphics2DImagePainter;

import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontTriplet;
import org.apache.fop.render.ImageHandlerUtil;
import org.apache.fop.render.RenderingContext;
import org.apache.fop.render.intermediate.AbstractIFPainter;
import org.apache.fop.render.intermediate.IFContext;
import org.apache.fop.render.intermediate.IFException;
import org.apache.fop.render.intermediate.IFPainter;
import org.apache.fop.render.intermediate.IFState;
import org.apache.fop.render.java2d.FontMetricsMapper;
import org.apache.fop.render.java2d.Java2DPainter;
import org.apache.fop.traits.BorderProps;
import org.apache.fop.traits.RuleStyle;
import org.apache.fop.util.CharUtilities;

/**
 * {@link IFPainter} implementation that produces PCL 5.
 */
public class PCLPainter extends AbstractIFPainter implements PCLConstants {

    /** logging instance */
    private static Log log = LogFactory.getLog(PCLPainter.class);

    private static final boolean DEBUG = false;

    private PCLDocumentHandler parent;

    /** The PCL generator */
    private PCLGenerator gen;

    private PCLPageDefinition currentPageDefinition;
    private int currentPrintDirection = 0;
    //private GeneralPath currentPath = null;

    private Stack graphicContextStack = new Stack();
    private GraphicContext graphicContext = new GraphicContext();

    /**
     * Main constructor.
     * @param parent the parent document handler
     * @param pageDefinition the page definition describing the page to be rendered
     */
    public PCLPainter(PCLDocumentHandler parent, PCLPageDefinition pageDefinition) {
        this.parent = parent;
        this.gen = parent.getPCLGenerator();
        this.state = IFState.create();
        this.currentPageDefinition = pageDefinition;
    }

    /** {@inheritDoc} */
    public IFContext getContext() {
        return this.parent.getContext();
    }

    PCLRenderingUtil getPCLUtil() {
        return this.parent.getPCLUtil();
    }

    /** @return the target resolution */
    protected int getResolution() {
        int resolution = (int) Math.round(getUserAgent().getTargetResolution());
        if (resolution <= 300) {
            return 300;
        } else {
            return 600;
        }
    }

    private boolean isSpeedOptimized() {
        return getPCLUtil().getRenderingMode() == PCLRenderingMode.SPEED;
    }

    //----------------------------------------------------------------------------------------------

    /** {@inheritDoc} */
    public void startViewport(AffineTransform transform, Dimension size, Rectangle clipRect) throws IFException {
        saveGraphicsState();
        try {
            concatenateTransformationMatrix(transform);
            /* PCL cannot clip!
            if (clipRect != null) {
            clipRect(clipRect);
            }*/
        } catch (IOException ioe) {
            throw new IFException("I/O error in startViewport()", ioe);
        }
    }

    /** {@inheritDoc} */
    public void endViewport() throws IFException {
        restoreGraphicsState();
    }

    /** {@inheritDoc} */
    public void startGroup(AffineTransform transform) throws IFException {
        saveGraphicsState();
        try {
            concatenateTransformationMatrix(transform);
        } catch (IOException ioe) {
            throw new IFException("I/O error in startGroup()", ioe);
        }
    }

    /** {@inheritDoc} */
    public void endGroup() throws IFException {
        restoreGraphicsState();
    }

    /** {@inheritDoc} */
    public void drawImage(String uri, Rectangle rect) throws IFException {
        drawImageUsingURI(uri, rect);
    }

    /** {@inheritDoc} */
    protected RenderingContext createRenderingContext() {
        PCLRenderingContext pdfContext = new PCLRenderingContext(getUserAgent(), this.gen, getPCLUtil()) {

            public Point2D transformedPoint(int x, int y) {
                return PCLPainter.this.transformedPoint(x, y);
            }

            public GraphicContext getGraphicContext() {
                return PCLPainter.this.graphicContext;
            }

        };
        return pdfContext;
    }

    /** {@inheritDoc} */
    public void drawImage(Document doc, Rectangle rect) throws IFException {
        drawImageUsingDocument(doc, rect);
    }

    /** {@inheritDoc} */
    public void clipRect(Rectangle rect) throws IFException {
        //PCL cannot clip (only HP GL/2 can)
        //If you need clipping support, switch to RenderingMode.BITMAP.
    }

    /** {@inheritDoc} */
    public void fillRect(Rectangle rect, Paint fill) throws IFException {
        if (fill == null) {
            return;
        }
        if (rect.width != 0 && rect.height != 0) {
            Color fillColor = null;
            if (fill != null) {
                if (fill instanceof Color) {
                    fillColor = (Color) fill;
                } else {
                    throw new UnsupportedOperationException("Non-Color paints NYI");
                }
                try {
                    setCursorPos(rect.x, rect.y);
                    gen.fillRect(rect.width, rect.height, fillColor);
                } catch (IOException ioe) {
                    throw new IFException("I/O error in fillRect()", ioe);
                }
            }
        }
    }

    /** {@inheritDoc} */
    public void drawBorderRect(final Rectangle rect, final BorderProps before, final BorderProps after,
            final BorderProps start, final BorderProps end) throws IFException {
        if (isSpeedOptimized()) {
            super.drawBorderRect(rect, before, after, start, end);
            return;
        }
        if (before != null || after != null || start != null || end != null) {
            final Rectangle boundingBox = rect;
            final Dimension dim = boundingBox.getSize();

            Graphics2DImagePainter painter = new Graphics2DImagePainter() {

                public void paint(Graphics2D g2d, Rectangle2D area) {
                    g2d.translate(-rect.x, -rect.y);

                    Java2DPainter painter = new Java2DPainter(g2d, getContext(), parent.getFontInfo(), state);
                    try {
                        painter.drawBorderRect(rect, before, after, start, end);
                    } catch (IFException e) {
                        //This should never happen with the Java2DPainter
                        throw new RuntimeException("Unexpected error while painting borders", e);
                    }
                }

                public Dimension getImageSize() {
                    return dim.getSize();
                }

            };
            paintMarksAsBitmap(painter, boundingBox);
        }
    }

    /** {@inheritDoc} */
    public void drawLine(final Point start, final Point end, final int width, final Color color,
            final RuleStyle style) throws IFException {
        if (isSpeedOptimized()) {
            super.drawLine(start, end, width, color, style);
            return;
        }
        final Rectangle boundingBox = getLineBoundingBox(start, end, width);
        final Dimension dim = boundingBox.getSize();

        Graphics2DImagePainter painter = new Graphics2DImagePainter() {

            public void paint(Graphics2D g2d, Rectangle2D area) {
                g2d.translate(-boundingBox.x, -boundingBox.y);

                Java2DPainter painter = new Java2DPainter(g2d, getContext(), parent.getFontInfo(), state);
                try {
                    painter.drawLine(start, end, width, color, style);
                } catch (IFException e) {
                    //This should never happen with the Java2DPainter
                    throw new RuntimeException("Unexpected error while painting a line", e);
                }
            }

            public Dimension getImageSize() {
                return dim.getSize();
            }

        };
        paintMarksAsBitmap(painter, boundingBox);
    }

    private void paintMarksAsBitmap(Graphics2DImagePainter painter, Rectangle boundingBox) throws IFException {
        ImageInfo info = new ImageInfo(null, null);
        ImageSize size = new ImageSize();
        size.setSizeInMillipoints(boundingBox.width, boundingBox.height);
        info.setSize(size);
        ImageGraphics2D img = new ImageGraphics2D(info, painter);

        Map hints = new java.util.HashMap();
        if (isSpeedOptimized()) {
            //Gray text may not be painted in this case! We don't get dithering in Sun JREs.
            //But this approach is about twice as fast as the grayscale image.
            hints.put(ImageProcessingHints.BITMAP_TYPE_INTENT, ImageProcessingHints.BITMAP_TYPE_INTENT_MONO);
        } else {
            hints.put(ImageProcessingHints.BITMAP_TYPE_INTENT, ImageProcessingHints.BITMAP_TYPE_INTENT_GRAY);
        }
        hints.put(ImageHandlerUtil.CONVERSION_MODE, ImageHandlerUtil.CONVERSION_MODE_BITMAP);
        PCLRenderingContext context = (PCLRenderingContext) createRenderingContext();
        context.setSourceTransparencyEnabled(true);
        try {
            drawImage(img, boundingBox, context, true, hints);
        } catch (IOException ioe) {
            throw new IFException("I/O error while painting marks using a bitmap", ioe);
        } catch (ImageException ie) {
            throw new IFException("Error while painting marks using a bitmap", ie);
        }
    }

    /** {@inheritDoc} */
    public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[] dx, String text)
            throws IFException {
        try {
            FontTriplet triplet = new FontTriplet(state.getFontFamily(), state.getFontStyle(),
                    state.getFontWeight());
            //TODO Ignored: state.getFontVariant()
            //TODO Opportunity for font caching if font state is more heavily used
            String fontKey = parent.getFontInfo().getInternalFontKey(triplet);
            boolean pclFont = getPCLUtil().isAllTextAsBitmaps() ? false
                    : HardcodedFonts.setFont(gen, fontKey, state.getFontSize(), text);
            if (pclFont) {
                drawTextNative(x, y, letterSpacing, wordSpacing, dx, text, triplet);
            } else {
                drawTextAsBitmap(x, y, letterSpacing, wordSpacing, dx, text, triplet);
                if (DEBUG) {
                    state.setTextColor(Color.GRAY);
                    HardcodedFonts.setFont(gen, "F1", state.getFontSize(), text);
                    drawTextNative(x, y, letterSpacing, wordSpacing, dx, text, triplet);
                }
            }
        } catch (IOException ioe) {
            throw new IFException("I/O error in drawText()", ioe);
        }
    }

    private void drawTextNative(int x, int y, int letterSpacing, int wordSpacing, int[] dx, String text,
            FontTriplet triplet) throws IOException {
        Color textColor = state.getTextColor();
        if (textColor != null) {
            gen.setTransparencyMode(true, false);
            gen.selectGrayscale(textColor);
        }

        gen.setTransparencyMode(true, true);
        setCursorPos(x, y);

        float fontSize = state.getFontSize() / 1000f;
        Font font = parent.getFontInfo().getFontInstance(triplet, state.getFontSize());
        int l = text.length();
        int dxl = (dx != null ? dx.length : 0);

        StringBuffer sb = new StringBuffer(Math.max(16, l));
        if (dx != null && dxl > 0 && dx[0] != 0) {
            sb.append("\u001B&a+").append(gen.formatDouble2(dx[0] / 100.0)).append('H');
        }
        for (int i = 0; i < l; i++) {
            char orgChar = text.charAt(i);
            char ch;
            float glyphAdjust = 0;
            if (font.hasChar(orgChar)) {
                ch = font.mapChar(orgChar);
            } else {
                if (CharUtilities.isFixedWidthSpace(orgChar)) {
                    //Fixed width space are rendered as spaces so copy/paste works in a reader
                    ch = font.mapChar(CharUtilities.SPACE);
                    int spaceDiff = font.getCharWidth(ch) - font.getCharWidth(orgChar);
                    glyphAdjust = -(10 * spaceDiff / fontSize);
                } else {
                    ch = font.mapChar(orgChar);
                }
            }
            sb.append(ch);

            if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
                glyphAdjust += wordSpacing;
            }
            glyphAdjust += letterSpacing;
            if (dx != null && i < dxl - 1) {
                glyphAdjust += dx[i + 1];
            }

            if (glyphAdjust != 0) {
                sb.append("\u001B&a+").append(gen.formatDouble2(glyphAdjust / 100.0)).append('H');
            }

        }
        gen.getOutputStream().write(sb.toString().getBytes(gen.getTextEncoding()));

    }

    private static final double SAFETY_MARGIN_FACTOR = 0.05;

    private Rectangle getTextBoundingBox(int x, int y, int letterSpacing, int wordSpacing, int[] dx, String text,
            Font font, FontMetricsMapper metrics) {
        int maxAscent = metrics.getMaxAscent(font.getFontSize()) / 1000;
        int descent = metrics.getDescender(font.getFontSize()) / 1000; //is negative
        int safetyMargin = (int) (SAFETY_MARGIN_FACTOR * font.getFontSize());
        Rectangle boundingRect = new Rectangle(x, y - maxAscent - safetyMargin, 0,
                maxAscent - descent + 2 * safetyMargin);

        int l = text.length();
        int dxl = (dx != null ? dx.length : 0);

        if (dx != null && dxl > 0 && dx[0] != 0) {
            boundingRect.setLocation(boundingRect.x - (int) Math.ceil(dx[0] / 10f), boundingRect.y);
        }
        float width = 0.0f;
        for (int i = 0; i < l; i++) {
            char orgChar = text.charAt(i);
            float glyphAdjust = 0;
            int cw = font.getCharWidth(orgChar);

            if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
                glyphAdjust += wordSpacing;
            }
            glyphAdjust += letterSpacing;
            if (dx != null && i < dxl - 1) {
                glyphAdjust += dx[i + 1];
            }

            width += cw + glyphAdjust;
        }
        int extraWidth = font.getFontSize() / 3;
        boundingRect.setSize((int) Math.ceil(width) + extraWidth, boundingRect.height);
        return boundingRect;
    }

    private void drawTextAsBitmap(final int x, final int y, final int letterSpacing, final int wordSpacing,
            final int[] dx, final String text, FontTriplet triplet) throws IFException {
        //Use Java2D to paint different fonts via bitmap
        final Font font = parent.getFontInfo().getFontInstance(triplet, state.getFontSize());

        //for cursive fonts, so the text isn't clipped
        final FontMetricsMapper mapper = (FontMetricsMapper) parent.getFontInfo().getMetricsFor(font.getFontName());
        final int maxAscent = mapper.getMaxAscent(font.getFontSize()) / 1000;
        final int ascent = mapper.getAscender(font.getFontSize()) / 1000;
        final int descent = mapper.getDescender(font.getFontSize()) / 1000;
        int safetyMargin = (int) (SAFETY_MARGIN_FACTOR * font.getFontSize());
        final int baselineOffset = maxAscent + safetyMargin;

        final Rectangle boundingBox = getTextBoundingBox(x, y, letterSpacing, wordSpacing, dx, text, font, mapper);
        final Dimension dim = boundingBox.getSize();

        Graphics2DImagePainter painter = new Graphics2DImagePainter() {

            public void paint(Graphics2D g2d, Rectangle2D area) {
                if (DEBUG) {
                    g2d.setBackground(Color.LIGHT_GRAY);
                    g2d.clearRect(0, 0, (int) area.getWidth(), (int) area.getHeight());
                }
                g2d.translate(-x, -y + baselineOffset);

                if (DEBUG) {
                    Rectangle rect = new Rectangle(x, y - maxAscent, 3000, maxAscent);
                    g2d.draw(rect);
                    rect = new Rectangle(x, y - ascent, 2000, ascent);
                    g2d.draw(rect);
                    rect = new Rectangle(x, y, 1000, -descent);
                    g2d.draw(rect);
                }
                Java2DPainter painter = new Java2DPainter(g2d, getContext(), parent.getFontInfo(), state);
                try {
                    painter.drawText(x, y, letterSpacing, wordSpacing, dx, text);
                } catch (IFException e) {
                    //This should never happen with the Java2DPainter
                    throw new RuntimeException("Unexpected error while painting text", e);
                }
            }

            public Dimension getImageSize() {
                return dim.getSize();
            }

        };
        paintMarksAsBitmap(painter, boundingBox);
    }

    /** Saves the current graphics state on the stack. */
    private void saveGraphicsState() {
        graphicContextStack.push(graphicContext);
        graphicContext = (GraphicContext) graphicContext.clone();
    }

    /** Restores the last graphics state from the stack. */
    private void restoreGraphicsState() {
        graphicContext = (GraphicContext) graphicContextStack.pop();
    }

    private void concatenateTransformationMatrix(AffineTransform transform) throws IOException {
        if (!transform.isIdentity()) {
            graphicContext.transform(transform);
            changePrintDirection();
        }
    }

    private Point2D transformedPoint(int x, int y) {
        return PCLRenderingUtil.transformedPoint(x, y, graphicContext.getTransform(), currentPageDefinition,
                currentPrintDirection);
    }

    private void changePrintDirection() throws IOException {
        AffineTransform at = graphicContext.getTransform();
        int newDir;
        newDir = PCLRenderingUtil.determinePrintDirection(at);
        if (newDir != this.currentPrintDirection) {
            this.currentPrintDirection = newDir;
            gen.changePrintDirection(this.currentPrintDirection);
        }
    }

    /**
     * Sets the current cursor position. The coordinates are transformed to the absolute position
     * on the logical PCL page and then passed on to the PCLGenerator.
     * @param x the x coordinate (in millipoints)
     * @param y the y coordinate (in millipoints)
     */
    void setCursorPos(int x, int y) throws IOException {
        Point2D transPoint = transformedPoint(x, y);
        gen.setCursorPos(transPoint.getX(), transPoint.getY());
    }

}