/* * 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$ */ 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. */ public class PCLGraphics2D extends AbstractGraphics2D { /** The PCL generator */ protected PCLGenerator gen; private static final boolean FAIL_ON_UNSUPPORTED_FEATURE = true; private boolean clippingDisabled; /** * Create a new PCLGraphics2D. * @param gen the PCL Generator to paint with */ public PCLGraphics2D(PCLGenerator gen) { super(true); this.gen = gen; } /** * Copy constructor * @param g parent PCLGraphics2D */ public PCLGraphics2D(PCLGraphics2D g) { super(true); this.gen = g.gen; } /** {@inheritDoc} */ public Graphics create() { PCLGraphics2D copy = new PCLGraphics2D(this); copy.setGraphicContext((GraphicContext)getGraphicContext().clone()); return copy; } /** {@inheritDoc} */ public void dispose() { this.gen = null; } /** * Sets the GraphicContext * @param c GraphicContext to use */ public void setGraphicContext(GraphicContext c) { this.gc = c; } /** * 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. * @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. * @param msg the error message to be displayed */ protected void handleUnsupportedFeature(String msg) { if (FAIL_ON_UNSUPPORTED_FEATURE) { throw new UnsupportedOperationException(msg); } } /** {@inheritDoc} */ public GraphicsConfiguration getDeviceConfiguration() { return new GraphicsConfigurationWithTransparency(); } /** * 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. * @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} */ 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} */ 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. * @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. * @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} */ 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} */ public void drawString(AttributedCharacterIterator iterator, float x, float y) { // TODO Auto-generated method stub handleUnsupportedFeature("drawString NYI"); } /** {@inheritDoc} */ public void drawRenderedImage(RenderedImage img, AffineTransform xform) { handleUnsupportedFeature("Bitmap images are not supported"); } /** {@inheritDoc} */ public void drawRenderableImage(RenderableImage img, AffineTransform xform) { handleUnsupportedFeature("Bitmap images are not supported"); } /** {@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} */ 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} */ public void copyArea(int x, int y, int width, int height, int dx, int dy) { // TODO Auto-generated method stub handleUnsupportedFeature("copyArea NYI"); } /** {@inheritDoc} */ public void setXORMode(Color c1) { // TODO Auto-generated method stub handleUnsupportedFeature("setXORMode NYI"); } /** * 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. * @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} */ public java.awt.FontMetrics getFontMetrics(java.awt.Font f) { return fmg.getFontMetrics(f); } }