| /* |
| * 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); |
| } |
| |
| } |