/*
 * 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: PCLGraphics2D.java 1618496 2014-08-17 18:56:01Z gadams $ */

package org.apache.fop.render.pcl;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.RenderableImage;
import java.io.IOException;
import java.text.AttributedCharacterIterator;

import org.apache.xmlgraphics.java2d.AbstractGraphics2D;
import org.apache.xmlgraphics.java2d.GraphicContext;
import org.apache.xmlgraphics.java2d.GraphicsConfigurationWithTransparency;
import org.apache.xmlgraphics.util.UnitConv;

Graphics2D implementation implementing PCL and HP GL/2. Note: This class cannot be used stand-alone to create full PCL documents.
/** * Graphics2D implementation implementing PCL and HP GL/2. * Note: This class cannot be used stand-alone to create full PCL documents. */
public class PCLGraphics2D extends AbstractGraphics2D {
The PCL generator
/** The PCL generator */
protected PCLGenerator gen; private static final boolean FAIL_ON_UNSUPPORTED_FEATURE = true; private boolean clippingDisabled;
Create a new PCLGraphics2D.
Params:
  • gen – the PCL Generator to paint with
/** * Create a new PCLGraphics2D. * @param gen the PCL Generator to paint with */
public PCLGraphics2D(PCLGenerator gen) { super(true); this.gen = gen; }
Copy constructor
Params:
  • g – parent PCLGraphics2D
/** * Copy constructor * @param g parent PCLGraphics2D */
public PCLGraphics2D(PCLGraphics2D g) { super(true); this.gen = g.gen; }
{@inheritDoc}
/** {@inheritDoc} */
public Graphics create() { PCLGraphics2D copy = new PCLGraphics2D(this); copy.setGraphicContext((GraphicContext)getGraphicContext().clone()); return copy; }
{@inheritDoc}
/** {@inheritDoc} */
public void dispose() { this.gen = null; }
Sets the GraphicContext
Params:
  • c – GraphicContext to use
/** * Sets the GraphicContext * @param c GraphicContext to use */
public void setGraphicContext(GraphicContext c) { this.gc = c; }
Allows to disable all clipping operations.
Params:
  • value – true if clipping should be disabled.
/** * Allows to disable all clipping operations. * @param value true if clipping should be disabled. */
public void setClippingDisabled(boolean value) { this.clippingDisabled = value; }
Central handler for IOExceptions for this class.
Params:
  • ioe – IOException to handle
/** * Central handler for IOExceptions for this class. * @param ioe IOException to handle */
public void handleIOException(IOException ioe) { //TODO Surely, there's a better way to do this. ioe.printStackTrace(); }
Raises an UnsupportedOperationException if this instance is configured to do so and an unsupported feature has been requested. Clients can make use of this to fall back to a more compatible way of painting a PCL graphic.
Params:
  • msg – the error message to be displayed
/** * Raises an UnsupportedOperationException if this instance is configured to do so and an * unsupported feature has been requested. Clients can make use of this to fall back to * a more compatible way of painting a PCL graphic. * @param msg the error message to be displayed */
protected void handleUnsupportedFeature(String msg) { if (FAIL_ON_UNSUPPORTED_FEATURE) { throw new UnsupportedOperationException(msg); } }
{@inheritDoc}
/** {@inheritDoc} */
public GraphicsConfiguration getDeviceConfiguration() { return new GraphicsConfigurationWithTransparency(); }
Applies a new Stroke object.
Params:
  • stroke – Stroke object to use
Throws:
/** * Applies a new Stroke object. * @param stroke Stroke object to use * @throws IOException In case of an I/O problem */
protected void applyStroke(Stroke stroke) throws IOException { if (stroke instanceof BasicStroke) { BasicStroke bs = (BasicStroke)stroke; float[] da = bs.getDashArray(); if (da != null) { gen.writeText("UL1,"); int len = Math.min(20, da.length); float patternLen = 0.0f; for (int idx = 0; idx < len; idx++) { patternLen += da[idx]; } if (len == 1) { patternLen *= 2; } for (int idx = 0; idx < len; idx++) { float perc = da[idx] * 100 / patternLen; gen.writeText(gen.formatDouble2(perc)); if (idx < da.length - 1) { gen.writeText(","); } } if (len == 1) { gen.writeText("," + gen.formatDouble2(da[0] * 100 / patternLen)); } gen.writeText(";"); /* TODO Dash phase NYI float offset = bs.getDashPhase(); gen.writeln(gen.formatDouble4(offset) + " setdash"); */ Point2D ptLen = new Point2D.Double(patternLen, 0); //interpret as absolute length getTransform().deltaTransform(ptLen, ptLen); double transLen = UnitConv.pt2mm(ptLen.distance(0, 0)); gen.writeText("LT1," + gen.formatDouble4(transLen) + ",1;"); } else { gen.writeText("LT;"); } gen.writeText("LA1"); //line cap int ec = bs.getEndCap(); switch (ec) { case BasicStroke.CAP_BUTT: gen.writeText(",1"); break; case BasicStroke.CAP_ROUND: gen.writeText(",4"); break; case BasicStroke.CAP_SQUARE: gen.writeText(",2"); break; default: System.err.println("Unsupported line cap: " + ec); } gen.writeText(",2"); //line join int lj = bs.getLineJoin(); switch (lj) { case BasicStroke.JOIN_MITER: gen.writeText(",1"); break; case BasicStroke.JOIN_ROUND: gen.writeText(",4"); break; case BasicStroke.JOIN_BEVEL: gen.writeText(",5"); break; default: System.err.println("Unsupported line join: " + lj); } float ml = bs.getMiterLimit(); gen.writeText(",3" + gen.formatDouble4(ml)); float lw = bs.getLineWidth(); Point2D ptSrc = new Point2D.Double(lw, 0); //Pen widths are set as absolute metric values (WU0;) Point2D ptDest = getTransform().deltaTransform(ptSrc, null); double transDist = UnitConv.pt2mm(ptDest.distance(0, 0)); //System.out.println("--" + ptDest.distance(0, 0) + " " + transDist); gen.writeText(";PW" + gen.formatDouble4(transDist) + ";"); } else { handleUnsupportedFeature("Unsupported Stroke: " + stroke.getClass().getName()); } }
Applies a new Paint object.
Params:
  • paint – Paint object to use
Throws:
/** * Applies a new Paint object. * @param paint Paint object to use * @throws IOException In case of an I/O problem */
protected void applyPaint(Paint paint) throws IOException { if (paint instanceof Color) { Color col = (Color)paint; int shade = gen.convertToPCLShade(col); gen.writeText("TR0;FT10," + shade + ";"); } else { handleUnsupportedFeature("Unsupported Paint: " + paint.getClass().getName()); } } private void writeClip(Shape imclip) throws IOException { if (clippingDisabled) { return; } if (imclip == null) { //gen.writeText("IW;"); } else { handleUnsupportedFeature("Clipping is not supported. Shape: " + imclip); /* This is an attempt to clip using the "InputWindow" (IW) but this only allows to * clip a rectangular area. Force falling back to bitmap mode for now. Rectangle2D bounds = imclip.getBounds2D(); Point2D p1 = new Point2D.Double(bounds.getX(), bounds.getY()); Point2D p2 = new Point2D.Double( bounds.getX() + bounds.getWidth(), bounds.getY() + bounds.getHeight()); getTransform().transform(p1, p1); getTransform().transform(p2, p2); gen.writeText("IW" + gen.formatDouble4(p1.getX()) + "," + gen.formatDouble4(p2.getY()) + "," + gen.formatDouble4(p2.getX()) + "," + gen.formatDouble4(p1.getY()) + ";"); */ } }
{@inheritDoc}
/** {@inheritDoc} */
public void draw(Shape s) { try { AffineTransform trans = getTransform(); Shape imclip = getClip(); writeClip(imclip); if (!Color.black.equals(getColor())) { //TODO PCL 5 doesn't support colored pens, PCL5c has a pen color (PC) command handleUnsupportedFeature("Only black is supported as stroke color: " + getColor()); } applyStroke(getStroke()); PathIterator iter = s.getPathIterator(trans); processPathIteratorStroke(iter); writeClip(null); } catch (IOException ioe) { handleIOException(ioe); } }
{@inheritDoc}
/** {@inheritDoc} */
public void fill(Shape s) { try { AffineTransform trans = getTransform(); Shape imclip = getClip(); writeClip(imclip); applyPaint(getPaint()); PathIterator iter = s.getPathIterator(trans); processPathIteratorFill(iter); writeClip(null); } catch (IOException ioe) { handleIOException(ioe); } }
Processes a path iterator generating the nexessary painting operations.
Params:
  • iter – PathIterator to process
Throws:
/** * Processes a path iterator generating the nexessary painting operations. * @param iter PathIterator to process * @throws IOException In case of an I/O problem. */
public void processPathIteratorStroke(PathIterator iter) throws IOException { gen.writeText("\n"); double[] vals = new double[6]; boolean penDown = false; double x = 0; double y = 0; StringBuffer sb = new StringBuffer(256); penUp(sb); while (!iter.isDone()) { int type = iter.currentSegment(vals); if (type == PathIterator.SEG_CLOSE) { gen.writeText("PM;"); gen.writeText(sb.toString()); gen.writeText("PM2;EP;"); sb.setLength(0); iter.next(); continue; } else if (type == PathIterator.SEG_MOVETO) { gen.writeText(sb.toString()); sb.setLength(0); if (penDown) { penUp(sb); penDown = false; } } else { if (!penDown) { penDown(sb); penDown = true; } } switch (type) { case PathIterator.SEG_CLOSE: break; case PathIterator.SEG_MOVETO: x = vals[0]; y = vals[1]; plotAbsolute(x, y, sb); gen.writeText(sb.toString()); sb.setLength(0); break; case PathIterator.SEG_LINETO: x = vals[0]; y = vals[1]; plotAbsolute(x, y, sb); break; case PathIterator.SEG_CUBICTO: x = vals[4]; y = vals[5]; bezierAbsolute(vals[0], vals[1], vals[2], vals[3], x, y, sb); break; case PathIterator.SEG_QUADTO: double originX = x; double originY = y; x = vals[2]; y = vals[3]; quadraticBezierAbsolute(originX, originY, vals[0], vals[1], x, y, sb); break; default: break; } iter.next(); } sb.append("\n"); gen.writeText(sb.toString()); }
Processes a path iterator generating the nexessary painting operations.
Params:
  • iter – PathIterator to process
Throws:
/** * Processes a path iterator generating the nexessary painting operations. * @param iter PathIterator to process * @throws IOException In case of an I/O problem. */
public void processPathIteratorFill(PathIterator iter) throws IOException { gen.writeText("\n"); double[] vals = new double[6]; boolean penDown = false; double x = 0; double y = 0; boolean pendingPM0 = true; StringBuffer sb = new StringBuffer(256); penUp(sb); while (!iter.isDone()) { int type = iter.currentSegment(vals); if (type == PathIterator.SEG_CLOSE) { sb.append("PM1;"); iter.next(); continue; } else if (type == PathIterator.SEG_MOVETO) { if (penDown) { penUp(sb); penDown = false; } } else { if (!penDown) { penDown(sb); penDown = true; } } switch (type) { case PathIterator.SEG_MOVETO: x = vals[0]; y = vals[1]; plotAbsolute(x, y, sb); break; case PathIterator.SEG_LINETO: x = vals[0]; y = vals[1]; plotAbsolute(x, y, sb); break; case PathIterator.SEG_CUBICTO: x = vals[4]; y = vals[5]; bezierAbsolute(vals[0], vals[1], vals[2], vals[3], x, y, sb); break; case PathIterator.SEG_QUADTO: double originX = x; double originY = y; x = vals[2]; y = vals[3]; quadraticBezierAbsolute(originX, originY, vals[0], vals[1], x, y, sb); break; default: throw new IllegalStateException("Must not get here"); } if (pendingPM0) { pendingPM0 = false; sb.append("PM;"); } iter.next(); } sb.append("PM2;"); fillPolygon(iter.getWindingRule(), sb); sb.append("\n"); gen.writeText(sb.toString()); } private void fillPolygon(int windingRule, StringBuffer sb) { int fillMethod = (windingRule == PathIterator.WIND_EVEN_ODD ? 0 : 1); sb.append("FP").append(fillMethod).append(";"); } private void plotAbsolute(double x, double y, StringBuffer sb) { sb.append("PA").append(gen.formatDouble4(x)); sb.append(",").append(gen.formatDouble4(y)).append(";"); } private void bezierAbsolute(double x1, double y1, double x2, double y2, double x3, double y3, StringBuffer sb) { sb.append("BZ").append(gen.formatDouble4(x1)); sb.append(",").append(gen.formatDouble4(y1)); sb.append(",").append(gen.formatDouble4(x2)); sb.append(",").append(gen.formatDouble4(y2)); sb.append(",").append(gen.formatDouble4(x3)); sb.append(",").append(gen.formatDouble4(y3)).append(";"); } private void quadraticBezierAbsolute(double originX, double originY, double x1, double y1, double x2, double y2, StringBuffer sb) { //Quadratic Bezier curve can be mapped to a normal bezier curve //See http://pfaedit.sourceforge.net/bezier.html double nx1 = originX + (2.0 / 3.0) * (x1 - originX); double ny1 = originY + (2.0 / 3.0) * (y1 - originY); double nx2 = nx1 + (1.0 / 3.0) * (x2 - originX); double ny2 = ny1 + (1.0 / 3.0) * (y2 - originY); bezierAbsolute(nx1, ny1, nx2, ny2, x2, y2, sb); } private void penDown(StringBuffer sb) { sb.append("PD;"); } private void penUp(StringBuffer sb) { sb.append("PU;"); }
{@inheritDoc}
/** {@inheritDoc} */
public void drawString(String s, float x, float y) { java.awt.Font awtFont = getFont(); FontRenderContext frc = getFontRenderContext(); GlyphVector gv = awtFont.createGlyphVector(frc, s); Shape glyphOutline = gv.getOutline(x, y); fill(glyphOutline); }
{@inheritDoc}
/** {@inheritDoc} */
public void drawString(AttributedCharacterIterator iterator, float x, float y) { // TODO Auto-generated method stub handleUnsupportedFeature("drawString NYI"); }
{@inheritDoc}
/** {@inheritDoc} */
public void drawRenderedImage(RenderedImage img, AffineTransform xform) { handleUnsupportedFeature("Bitmap images are not supported"); }
{@inheritDoc}
/** {@inheritDoc} */
public void drawRenderableImage(RenderableImage img, AffineTransform xform) { handleUnsupportedFeature("Bitmap images are not supported"); }
{@inheritDoc}
/** {@inheritDoc} */
public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) { handleUnsupportedFeature("Bitmap images are not supported"); return false; }
{@inheritDoc}
/** {@inheritDoc} */
public boolean drawImage(Image img, int x, int y, ImageObserver observer) { handleUnsupportedFeature("Bitmap images are not supported"); return false; /* * First attempt disabled. * Reasons: Lack of transparency control, positioning and rotation issues final int width = img.getWidth(observer); final int height = img.getHeight(observer); if (width == -1 || height == -1) { return false; } Dimension size = new Dimension(width, height); BufferedImage buf = buildBufferedImage(size); java.awt.Graphics2D g = buf.createGraphics(); try { g.setComposite(AlphaComposite.SrcOver); g.setBackground(new Color(255, 255, 255)); g.setPaint(new Color(255, 255, 255)); g.fillRect(0, 0, width, height); g.clip(new Rectangle(0, 0, buf.getWidth(), buf.getHeight())); if (!g.drawImage(img, 0, 0, observer)) { return false; } } finally { g.dispose(); } try { AffineTransform at = getTransform(); gen.enterPCLMode(false); //Shape imclip = getClip(); Clipping is not available in PCL Point2D p1 = new Point2D.Double(x, y); at.transform(p1, p1); pclContext.getTransform().transform(p1, p1); gen.setCursorPos(p1.getX(), p1.getY()); gen.paintBitmap(buf, 72); gen.enterHPGL2Mode(false); } catch (IOException ioe) { handleIOException(ioe); } return true;*/ }
{@inheritDoc}
/** {@inheritDoc} */
public void copyArea(int x, int y, int width, int height, int dx, int dy) { // TODO Auto-generated method stub handleUnsupportedFeature("copyArea NYI"); }
{@inheritDoc}
/** {@inheritDoc} */
public void setXORMode(Color c1) { // TODO Auto-generated method stub handleUnsupportedFeature("setXORMode NYI"); }
Used to create proper font metrics
/** * Used to create proper font metrics */
private Graphics2D fmg; { BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); fmg = bi.createGraphics(); }
Creates a buffered image.
Params:
  • size – dimensions of the image to be created
Returns:the buffered image
/** * Creates a buffered image. * @param size dimensions of the image to be created * @return the buffered image */
protected BufferedImage buildBufferedImage(Dimension size) { return new BufferedImage(size.width, size.height, BufferedImage.TYPE_BYTE_GRAY); }
{@inheritDoc}
/** {@inheritDoc} */
public java.awt.FontMetrics getFontMetrics(java.awt.Font f) { return fmg.getFontMetrics(f); } }