* In theory you could use setClip(), except that java.awt.Graphics only * supports Rectangle with that method, so we will have an extra method. * * @param p Polygon to clip to */ public void clipPolygon(Polygon p) { closeBlock(); // finish off any existing path polygon(p.xpoints, p.ypoints, p.npoints); closeBlock("W"); // clip to current path clipRectangle = p.getBounds(); } /** * Clips to a set of coordinates * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void clipRect(int x, int y, int w, int h) { setClip(x, y, w, h); } /** * All functions should call this to close any existing optimized blocks. */ void closeBlock() { closeBlock("S"); } /** *
* This is used by code that use the path in any way other than Stroke (like * Fill, close path & Stroke etc). Usually this is used internally.
* * @param code PDF operators that will close the path */ void closeBlock(String code) { if (inText) { pw.println("ET Q"); // setOrientation(); // fixes Orientation matrix } if (inStroke) { pw.println(code); } inStroke = inText = false; } /** * This is unsupported - how do you do this with Vector graphics? * * @param x coordinate * @param y coordinate * @param w width * @param h height * @param dx coordinate * @param dy coordinate */ @Override public void copyArea(int x, int y, int w, int h, int dx, int dy) { // Hmm... Probably need to keep track of everything // that has been drawn so far to get the contents of an area } //============ Line operations ======================= /** ** This returns a child instance of this Graphics object. As with AWT, the * affects of using the parent instance while the child exists, is not * determined.
* ** Once complete, the child should be released with it's dispose() method * which will restore the graphics state to it's parent.
* * @return Graphics object to render onto the page */ @Override public Graphics create() { closeBlock(); PDFGraphics g = createGraphic(page, pw); // The new instance inherits a few items g.clipRectangle = new Rectangle(clipRectangle); return (Graphics) g; } // end create() /** * This method creates a new instance of the class based on the page and a * print writer. * * @param page the page to attach to * @param pw thePrintWriter to attach to.
*/
protected PDFGraphics createGraphic(PDFPage page,
RawPrintWriter pw) {
PDFGraphics g = new PDFGraphics();
g.init(page, pw);
return g;
}
/**
* This extension appends a Bezier curve to the path. The curve extends from
* the current point to (x2,y2) using the current point and (x1,y1) as the
* Bezier control points.
* * The new current point is (x2,y2) * * @param x1 Second control point * @param y1 Second control point * @param x2 Destination point * @param y2 Destination point */ public void curveto(double x1, double y1, double x2, double y2) { newPath(); pw.println(cxy(x1, y1) + cxy(x2, y2) + "v"); lx = (float) x2; ly = (float) y2; } /** * This extension appends a Bezier curve to the path. The curve extends from * the current point to (x3,y3) using (x1,y1) and (x2,y2) as the Bezier * control points. *
* The new current point is (x3,y3) * * @param x1 First control point * @param y1 First control point * @param x2 Second control point * @param y2 Second control point * @param x3 Destination point * @param y3 Destination point */ public void curveto(double x1, double y1, double x2, double y2, double x3, double y3) { newPath(); pw.println(cxy(x1, y1) + cxy(x2, y2) + cxy(x3, y3) + "c"); lx = (float) x3; ly = (float) y3; } /** * This extension appends a Bezier curve to the path. The curve extends from * the current point to (x2,y2) using the current point and (x1,y1) as the * Bezier control points. *
* The new current point is (x2,y2) * * @param x1 Second control point * @param y1 Second control point * @param x2 Destination point * @param y2 Destination point */ public void curveto(int x1, int y1, int x2, int y2) { newPath(); pw.println(cxy(x1, y1) + cxy(x2, y2) + "v"); lx = x2; ly = y2; } /** * This extension appends a Bezier curve to the path. The curve extends from * the current point to (x3,y3) using (x1,y1) and (x2,y2) as the Bezier * control points. *
* The new current point is (x3,y3) * * @param x1 First control point * @param y1 First control point * @param x2 Second control point * @param y2 Second control point * @param x3 Destination point * @param y3 Destination point */ public void curveto(int x1, int y1, int x2, int y2, int x3, int y3) { newPath(); pw.println(cxy(x1, y1) + cxy(x2, y2) + cxy(x3, y3) + "c"); lx = x3; ly = y3; } /** * This extension appends a Bezier curve to the path. The curve extends from * the current point to (x2,y2) using (x1,y1) and the end point as the * Bezier control points. *
* The new current point is (x2,y2) * * @param x1 Second control point * @param y1 Second control point * @param x2 Destination point * @param y2 Destination point */ public void curveto2(double x1, double y1, double x2, double y2) { newPath(); pw.println(cxy(x1, y1) + cxy(x2, y2) + "y"); lx = (float) x2; ly = (float) y2; } // Arcs are horrible and complex. They are at the end of the // file, because they are the largest. This is because, unlike // Postscript, PDF doesn't have any arc operators, so we must // implement them by converting into one or more Bezier curves // (which is how Postscript does them internally). /** * This extension appends a Bezier curve to the path. The curve extends from * the current point to (x2,y2) using (x1,y1) and the end point as the * Bezier control points. *
* The new current point is (x2,y2) * * @param x1 Second control point * @param y1 Second control point * @param x2 Destination point * @param y2 Destination point */ public void curveto2(int x1, int y1, int x2, int y2) { newPath(); pw.println(cxy(x1, y1) + cxy(x2, y2) + "y"); lx = x2; ly = y2; } /** * Converts the Java space dimension into pdf. * * @param w width * @param h height * @return String containing the coordinates in PDF space */ private String cwh(double w, double h) { return "" + df.format(w) + " " + df.format(h) + " "; } /** * Converts the Java space dimension into pdf. * * @param w width * @param h height * @return String containing the coordinates in PDF space */ private String cwh(int w, int h) { return cwh((double) w, (double) h); } /** * Converts the Java space coordinates into pdf. * * @param x coordinate * @param y coordinate * @return String containing the coordinates in PDF space */ private String cxy(double x, double y) { return "" + df.format(x) + " " + df.format(y) + " "; } /** * Converts the Java space coordinates into pdf. * * @param x coordinate * @param y coordinate * @return String containing the coordinates in PDF space */ private String cxy(int x, int y) { return cxy((double) x, (double) y); } /** *
* This releases any resources used by this Graphics object. You must use * this method once finished with it. Leaving it open will leave the PDF * stream in an inconsistent state, and will produce errors.
* ** If this was created with Graphics.create() then the parent instance can * be used again. If not, then this closes the graphics operations for this * page when used with PDFJob.
* ** When using PDFPage, you can create another fresh Graphics instance, which * will draw over this one.
* */ @Override public void dispose() { closeBlock(); if (clip != null) { restoreState(); } if (child) { pw.println("Q"); // restore graphics context } else { pw.close(); // close the stream if were the parent } } // ********************************************* // **** Implementation of java.awt.Graphics **** // ********************************************* //============ Rectangle operations ======================= /** * @see Graphics2D#draw(Shape) */ @Override public void draw(Shape s) { followPath(s, STROKE); } /** ** Not implemented
* ** Draws a 3-D highlighted outline of the specified rectangle. The edges of * the rectangle are highlighted so that they appear to be beveled and lit * from the upper left corner. The colors used for the highlighting effect * are determined based on the current color. The resulting rectangle covers * an area that is width + 1 pixels wide by height + 1 pixels tall. *
* * @param x anint value
* @param y an int value
* @param width an int value
* @param height an int value
* @param raised a boolean value
*/
@Override
public void draw3DRect(int x, int y,
int width, int height, boolean raised) {
// Not implemented
}
/**
* Draws an arc
*
* @param x coordinate
* @param y coordinate
* @param w width
* @param h height
* @param sa Start angle
* @param aa End angle
*/
@Override
public void drawArc(int x, int y, int w, int h, int sa, int aa) {
w = w >> 1;
h = h >> 1;
x += w;
y += h;
arc((double) x, (double) y,
(double) w, (double) h,
(double) -sa, (double) (-sa - aa),
false);
}
/**
* * Not implemented
* * @param data abyte[] value
* @param offset an int value
* @param length an int value
* @param x an int value
* @param y an int value
*/
@Override
public void drawBytes(byte[] data, int offset, int length, int x, int y) {
}
//============ Optimizers =======================
/**
* @see Graphics2D#drawGlyphVector(GlyphVector, float, float)
*/
@Override
public void drawGlyphVector(GlyphVector g, float x, float y) {
Shape s = g.getOutline(x, y);
fill(s);
}
/**
* @see Graphics2D#drawImage(BufferedImage, BufferedImageOp, int, int)
*/
@Override
public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) {
BufferedImage result = img;
if (op != null) {
result = op.createCompatibleDestImage(img, img.getColorModel());
result = op.filter(img, result);
}
drawImage(result, x, y, null);
}
/**
* @see Graphics2D#drawImage(Image, AffineTransform, ImageObserver)
*/
@Override
public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) {
// return drawImage(img, null, xform, null, obs);
return true;
}
/**
* * Draw's an image onto the page, with a backing colour.
* * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param bgcolor Background colour * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver obs) { return drawImage(img, x, y, img.getWidth(obs), img.getHeight(obs), bgcolor, obs); } /** * Draw's an image onto the page * * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int x, int y, ImageObserver obs) { return drawImage(img, x, y, img.getWidth(obs), img.getHeight(obs), obs); } /** ** Draw's an image onto the page, with a backing colour.
* * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param w Width on page * @param h height on page * @param bgcolor Background colour * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int x, int y, int w, int h, Color bgcolor, ImageObserver obs) { closeBlock(); pw.print("q "); // save state Color c = getColor(); // save current colour setColor(bgcolor); // change the colour drawRect(x, y, w, h); closeBlock("B Q"); // fill stroke, restore state paint = c; // restore original colour return drawImage(img, x, y, img.getWidth(obs), img.getHeight(obs), obs); } /** ** Draws an image onto the page.
* ** This method is implemented with ASCIIbase85 encoding and the zip stream * deflater. It results in a stream that is anywhere from 3 to 10 times as * big as the image. This obviously needs some improvement, but it works * well for small images
* * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param w Width on page * @param h height on page * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int x, int y, int w, int h, ImageObserver obs) { closeBlock(); PDFImage image; if (usedImages.containsKey(img)) { image = usedImages.get(img); } else { PDFMask mask = new PDFMask(img); page.getPDFDocument().add(mask); image = new PDFImage(img, x, y, w, h, obs, "" + mask.getSerialID() + " 0 R"); // The image needs to be registered in several places page.getPDFDocument().setImageName(image); page.getPDFDocument().add(image); usedImages.put(img, image); } page.addToProcset("/ImageC"); page.addImageResource(image.getName() + " " + image.getSerialID() + " 0 R"); // JM /*page.addResource("/XObject << " + image.getName() + " " + image.getSerialID() + " 0 R >>");*/ // q w 0 0 h x y cm % the coordinate matrix AffineTransform newTransform = new AffineTransform(w, 0, 0, -h, x, y + h); AffineTransform transformToSet = newTransform; pw.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); return false; } /** * Draw's an image onto the page, with scaling ** This is not yet supported. * * @param img The java.awt.Image * @param dx1 coordinate on page * @param dy1 coordinate on page * @param dx2 coordinate on page * @param dy2 coordinate on page * @param sx1 coordinate on image * @param sy1 coordinate on image * @param sx2 coordinate on image * @param sy2 coordinate on image * @param bgcolor Background colour * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver obs) { return false; } //============ Clipping operations ======================= /** * Draw's an image onto the page, with scaling *
* This is not yet supported. * * @param img The java.awt.Image * @param dx1 coordinate on page * @param dy1 coordinate on page * @param dx2 coordinate on page * @param dy2 coordinate on page * @param sx1 coordinate on image * @param sy1 coordinate on image * @param sx2 coordinate on image * @param sy2 coordinate on image * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver obs) { // This shouldn't be too bad, just change the coordinate matrix return false; } /** * Draws a line between two coordinates. * * If the first coordinate is the same as the last one drawn (i.e. a * previous drawLine, moveto, etc) it is ignored. * * @param x1 coordinate * @param y1 coordinate * @param x2 coordinate * @param y2 coordinate */ @Override public void drawLine(int x1, int y1, int x2, int y2) { moveto(x1, y1); lineto(x2, y2); } //============ Arcs operations ============================== // These are the standard Graphics operators. They use the // arc extension operators to achieve the affect. /** *
* Draws an oval
* * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void drawOval(int x, int y, int w, int h) { drawArc(x, y, w, h, 0, 360); } /** * Draws a polygon, linking the first and last coordinates. * * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polygon */ @Override public void drawPolygon(int[] xp, int[] yp, int np) { polygon(xp, yp, np); closeBlock("s"); // close path and stroke } /** * Draws a polyline. The first and last coordinates are not linked. * * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polyline */ @Override public void drawPolyline(int[] xp, int[] yp, int np) { polygon(xp, yp, np); // no stroke, as we keep the optimizer in stroke state } /** * We override Graphics.drawRect as it doesn't join the 4 lines. Also, PDF * provides us with a Rectangle operator, so we will use that. * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void drawRect(int x, int y, int w, int h) { draw(new Rectangle(x, y, w, h)); /*newPath(); pw.print(cxy(x, y) + cwh(w, h) + "re "); // rectangle lx = x; // I don't know if this is correct, but lets see if PDF ends ly = y; // the rectangle at it's start. // stroke (optimized)*/ } /** * @see Graphics2D#drawRenderableImage(RenderableImage, AffineTransform) */ @Override public void drawRenderableImage(RenderableImage img, AffineTransform xform) { drawRenderedImage(img.createDefaultRendering(), xform); } /** * @see Graphics2D#drawRenderedImage(RenderedImage, AffineTransform) */ @Override public void drawRenderedImage(RenderedImage img, AffineTransform xform) { BufferedImage image = null; if (img instanceof BufferedImage) { image = (BufferedImage) img; } else { ColorModel cm = img.getColorModel(); int width = img.getWidth(); int height = img.getHeight(); WritableRaster raster = cm.createCompatibleWritableRaster(width, height); boolean isAlphaPremultiplied = cm.isAlphaPremultiplied(); Hashtable* This is not supported yet, as I have no idea what an * AttributedCharacterIterator is. *
* This method is new to the Java2 API. */ @Override public void drawString(java.text.AttributedCharacterIterator aci, float x, float y) { } /** * Draws a string using a AttributedCharacterIterator. *
* This is not supported yet, as I have no idea what an * AttributedCharacterIterator is. *
* This method is new to the Java2 API. */ @Override public void drawString(java.text.AttributedCharacterIterator aci, int x, int y) { } public void drawStringWithMode(String s, float x, float y, int mode) { newTextBlock(x, y); if (mode > -1) { pw.println("" + mode + " Tr"); } if (pdffont instanceof PDFEmbeddedFont) { pw.print("[("); pw.printRaw(PDFStringHelper.makeRawPDFString(s)); pw.println(")] TJ"); } else { pw.print(PDFStringHelper.makePDFString(s)); pw.println(" Tj"); } closeBlock(); } @Override public void drawString(String s, float x, float y) { drawStringWithMode(s, x, y, -1); } /** * This draws a string. * * @param s * @oaran s String to draw * @param x coordinate * @param y coordinate */ @Override public void drawString(String s, int x, int y) { drawString(s, (float) x, (float) y); } public void drawTransparentString(String s, float x, float y) { drawStringWithMode(s, x, y, 3); } /** * This draws a transparent string. * * @oaran s String to draw * @param x coordinate * @param y coordinate */ public void drawTransparentString(String s, int x, int y) { drawTransparentString(s, (float) x, (float) y); } /** * @see Graphics2D#fill(Shape) */ @Override public void fill(Shape s) { followPath(s, FILL); } /** *
* Not implemented
* * @param x anint value
* @param y an int value
* @param width an int value
* @param height an int value
* @param raised a boolean value
*/
@Override
public void fill3DRect(int x, int y,
int width, int height, boolean raised) {
// Not implemented
}
/**
* Fills an arc, joining the start and end coordinates
*
* @param x coordinate
* @param y coordinate
* @param w width
* @param h height
* @param sa Start angle
* @param aa End angle
*/
@Override
public void fillArc(int x, int y, int w, int h, int sa, int aa) {
// here we fool the optimizer. We force any open path to be closed,
// then draw the arc. Finally, as the optimizer hasn't stroke'd the
// path, we close and fill it, and mark the Stroke as closed.
//
// Note: The lineto to the centre of the object is required, because
// the fill only fills the arc. Skipping this includes an extra
// chord, which isn't correct. Peter May 31 2000
closeBlock();
patternFill();
drawArc(x, y, w, h, sa, aa);
lineto(x + (w >> 1), y + (h >> 1));
if (shadingFill()) {
return;
}
closeBlock("b"); // closepath and fill
}
//============ Extension operations ==============================
// These are extensions, and provide access to PDF Specific
// operators.
/**
* * Draws a filled oval
* * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void fillOval(int x, int y, int w, int h) { fillArc(x, y, w, h, 0, 360); } //============ Polygon operations ======================= /** * Fills a polygon. * * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polygon */ @Override public void fillPolygon(int[] xp, int[] yp, int np) { closeBlock(); // finish off any previous paths patternFill(); polygon(xp, yp, np); if (shadingFill()) { return; } closeBlock("b"); // closepath, fill and stroke } //============ Image operations ======================= /** * Fills a rectangle with the current colour * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void fillRect(int x, int y, int w, int h) { fill(new Rectangle(x, y, w, h)); /* // end any path & stroke. This ensures the fill is on this // rectangle, and not on any previous graphics closeBlock(); patternFill(); drawRect(x, y, w, h); if (shadingFill()) { return; } closeBlock("B"); // rectangle, fill stroke*/ } private void patternFill() { if (pattern != null) { if (paint instanceof TexturePaint) { if (pattern.equals("texture") || !paintTransform.equals(transform)) { initTexturePaint((TexturePaint) paint); paintTransform = transform; } } if (paint instanceof MultipleGradientPaint) { if (pattern.equals("gradient") || !paintTransform.equals(transform)) { initGradientPaint((MultipleGradientPaint) paint); paintTransform = transform; } } pw.println("/Pattern cs"); pw.println(pattern + " scn"); } } private boolean shadingFill() { if (pattern == null && shading != null) { saveState(); pw.println("W n"); if (paint instanceof MultipleGradientPaint) { if (shading.equals("gradient") || !paintTransform.equals(transform)) { initGradientPaint((MultipleGradientPaint) paint); paintTransform = transform; } } pw.println(shading + " sh"); restoreState(); return true; } return false; } //============ Round Rectangle operations ======================= /** * This is not yet implemented * * @param x coordinate * @param y coordinate * @param w width * @param h height * @param aw a-width * @param ah a-height */ @Override public void fillRoundRect(int x, int y, int w, int h, int aw, int ah) { } /////////////////////////////////////////////// // // // implementation specific methods // // private void followPath(Shape s, int drawType) { PathIterator points; if (s == null) { return; } if (drawType == FILL) { patternFill(); } if (drawType == STROKE) { if (!(stroke instanceof BasicStroke)) { s = stroke.createStrokedShape(s); followPath(s, FILL); return; } } // if (drawType==STROKE) { // setStrokeDiff(stroke, oldStroke); // oldStroke = stroke; // setStrokePaint(); // } // else if (drawType==FILL) // setFillPaint(); points = s.getPathIterator(IDENTITY); int segments = 0; float[] coords = new float[6]; while (!points.isDone()) { segments++; int segtype = points.currentSegment(coords); switch (segtype) { case PathIterator.SEG_CLOSE: pw.print("h "); break; case PathIterator.SEG_CUBICTO: curveto(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); break; case PathIterator.SEG_LINETO: lineto(coords[0], coords[1]); break; case PathIterator.SEG_MOVETO: moveto(coords[0], coords[1]); break; case PathIterator.SEG_QUADTO: curveto(coords[0], coords[1], coords[2], coords[3]); break; } points.next(); } switch (drawType) { case FILL: if (segments > 0) { if (pattern == null && shading != null) { saveState(); if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD) { closeBlock("W*"); } else { closeBlock("W"); } pw.println("n"); if (paint instanceof MultipleGradientPaint) { if (shading.equals("gradient") || !paintTransform.equals(transform)) { initGradientPaint((MultipleGradientPaint) paint); paintTransform = transform; } } pw.println(shading + " sh"); restoreState(); return; } if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD) { closeBlock("f*"); } else { closeBlock("f"); } } break; case STROKE: if (segments > 0) { closeBlock("S"); } break; case CLIP: default: //drawType==CLIP if (segments == 0) { drawRect(0, 0, 0, 0); } if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD) { closeBlock("W*"); } else { closeBlock("W"); } } } /** * @see Graphics2D#getBackground() */ @Override public Color getBackground() { return background; } /** * Returns the Shape of the clipping region As my JDK docs say, this may * break with Java 2D. * * @return Shape of the clipping region */ @Override public Shape getClip() { return null; } /** * Returns the Rectangle that fits the current clipping region * * @return the Rectangle that fits the current clipping region */ @Override public Rectangle getClipBounds() { return clipRectangle; } //============ Color operations ======================= /** * Returns the current pen Colour * * @return the current pen Colour */ @Override public Color getColor() { return (paint instanceof Color) ? (Color) paint : Color.black; } /** * @see Graphics2D#getComposite() */ @Override public Composite getComposite() { return composite; } /** * @see Graphics2D#getDeviceConfiguration() */ @Override public GraphicsConfiguration getDeviceConfiguration() { return dg2.getDeviceConfiguration(); } /** * Return's the current font. * * @return the current font. */ @Override public Font getFont() { if (font == null) { setFont(new Font("SansSerif", Font.PLAIN, 12)); } return font; } /** * Returns the FontMetrics for a font. ** This doesn't work correctly. Perhaps having some way of mapping the base * 14 fonts to our own FontMetrics implementation? * * @param font The java.awt.Font to return the metrics for * @return FontMetrics for a font */ @Override public FontMetrics getFontMetrics(Font font) { Frame dummy = new Frame(); dummy.addNotify(); Image image = dummy.createImage(100, 100); if (image == null) { System.err.println("getFontMetrics: image is null"); } Graphics graphics = image.getGraphics(); return graphics.getFontMetrics(font); } /** * @see Graphics2D#getFontRenderContext() */ @Override public FontRenderContext getFontRenderContext() { boolean antialias = RenderingHints.VALUE_TEXT_ANTIALIAS_ON.equals(getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING)); boolean fractions = RenderingHints.VALUE_FRACTIONALMETRICS_ON.equals(getRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS)); return new FontRenderContext(new AffineTransform(), antialias, fractions); } /** * Returns the associated PDFPage for this graphic * * @return the associated PDFPage for this graphic */ public PDFPage getPage() { return page; } /** * Returns the current pen Colour * * @return the current pen Colour */ @Override public Paint getPaint() { return paint; } /** * @param arg0 a key * @return the rendering hint */ @Override public Object getRenderingHint(Key arg0) { return rhints.get(arg0); } /** * @see Graphics2D#getRenderingHints() */ @Override public RenderingHints getRenderingHints() { return rhints; } /** * @see Graphics2D#getStroke() */ @Override public Stroke getStroke() { return stroke; } /** * @see Graphics2D#getTransform() */ @Override public AffineTransform getTransform() { return new AffineTransform(transform); } /** * Returns the PrintWriter handling the underlying stream * * @return the PrintWriter handling the underlying stream */ public RawPrintWriter getWriter() { return pw; } /** * @see Graphics2D#hit(Rectangle, Shape, boolean) */ @Override public boolean hit(Rectangle rect, Shape s, boolean onStroke) { if (onStroke) { s = stroke.createStrokedShape(s); } Area area = new Area(s); if (clip != null) { area.intersect(clip); } return area.intersects(rect.x, rect.y, rect.width, rect.height); } /** * This initialises the stream by saving the current graphics state, and * setting up the default line width (for us). * * It also sets up the instance ready for graphic operations and any * optimisations. * *
* For child instances, the stream is already open, so this should keep * things happy. */ private void init() { PageFormat pf = page.getPageFormat(); // save graphics state (restored by dispose) if (child) { pw.print("q "); } // now initialise the instance //setColor(Color.black); paint = Color.black; // possible: if parent.color is not black, then force black? // must check to see what AWT does? // Original User Space Transform (identity) // Transform from Java Space to PDF Space pTransform = new AffineTransform(); pTransform.translate(0, pf.getHeight()); pTransform.scale(1d, -1d); // Combined Transform User->Java->PDF setNewTranform(new AffineTransform()); // Set the line width setStroke(DEF_STROKE); } /** * This is called by PDFPage when creating a Graphcis instance. * * @param page The PDFPage to draw onto. */ protected void init(PDFPage page) { this.page = page; // We are the parent instance child = false; // Now create a stream to store the graphics in PDFStream stream = new PDFStream(); // To view detail in uncompressed format comment out the next line stream.setDeflate(true); page.getPDFDocument().add(stream); page.add(stream); pw = new RawPrintWriter(stream.getOutputStream()); // initially, we are limited to the page size clipRectangle = page.getImageableArea(); // finally initialise the stream init(); } /** * This method is used internally by create() and by the PDFJob class * * @param page PDFPage to draw into * @param pw PrintWriter to use */ protected void init(PDFPage page, RawPrintWriter pw) { this.page = page; this.pw = pw; // In this case, we didn't create the stream (our parent did) // so child is true (see dispose) child = true; // finally initialise the stream init(); } /** * This adds a line segment to the current path * * @param x coordinate * @param y coordinate */ public void lineto(double x, double y) { newPath(); // no optimisation here as it may introduce errors on decimal coordinates. pw.print(cxy(x, y) + "l "); lx = (float) x; ly = (float) y; } /** * This adds a line segment to the current path * * @param x coordinate * @param y coordinate */ public void lineto(int x, int y) { newPath(); if (lx != x && ly != y) { pw.print(cxy(x, y) + "l "); } lx = x; ly = y; } /** * This moves the current drawing point. * * @param x coordinate * @param y coordinate */ public void moveto(double x, double y) { newPath(); // no optimisation here as it may introduce errors on decimal coordinates. pw.print(cxy(x, y) + "m "); lx = (float) x; ly = (float) y; } /** * This moves the current drawing point. * * @param x coordinate * @param y coordinate */ public void moveto(int x, int y) { newPath(); if (lx != x || ly != y) { pw.print(cxy(x, y) + "m "); } lx = x; ly = y; } /** * Functions that draw lines should start by calling this. It starts a new * path unless inStroke is set, in that case it uses the existing path */ void newPath() { if (inText) { closeBlock(); } if (!inStroke) { if (pre_np != null) { pw.print(pre_np); // this is the prefix set by setOrientation() pre_np = null; } pw.print("n "); } inText = false; inStroke = true; // an unlikely coordinate to fool the moveto() optimizer lx = ly = -9999; } /** *
* Functions that draw text should start by calling this. It starts a text * block (accounting for media orientation) unless we are already in a Text * block.
* ** It also handles if the font has been changed since the current text block * was started, so your function will be current.
* * @param x x coordinate in java space * @param y y coordinate in java space */ void newTextBlock(float x, float y) { // close the current path if there is one if (inStroke) { closeBlock(); } // create the text block if one is not current. If we are, the newFont // condition at the end catches font changes if (!inText) { // This ensures that there is a font available getFont(); pw.print("q BT "); tx = ty = 0; AffineTransform tm = new AffineTransform(pTransform); pw.println("" + df.format(tm.getScaleX()) + " " + "" + df.format(tm.getShearY()) + " " + "" + df.format(tm.getShearX()) + " " + "" + df.format(tm.getScaleY()) + " " + "" + df.format(tm.getTranslateX()) + " " + "" + df.format(tm.getTranslateY()) + " Tm" ); // produce the text matrix for the media // switch(mediaRot) { // case PageFormat.PORTRAIT: // Portrait // //pw.println("1 0 0 1 0 0 Tm"); // break; // // case PageFormat.LANDSCAPE: // Landscape // pw.println("0 1 -1 0 0 0 Tm"); // rotate // break; // // case 180: // Inverted Portrait // pw.println("1 0 0 -1 0 0 Tm"); // break; // // case PageFormat.REVERSE_LANDSCAPE: // Seascape // pw.println("0 -1 1 0 0 0 Tm"); // rotate // break; // } // move the text cursor by an absolute amount pw.print(txy(x, y) + "Td "); } else { // move the text cursor by a relative amount pw.print(twh(x, y, tx, ty) + "Td "); //pw.print(txy(x,y)+"Td "); } // preserve the coordinates for the next time tx = x; ty = y; if (newFont || !inText) { pw.print(pdffont.getName() + " " + font.getSize() + " Tf "); } // later add colour changes here (if required) inStroke = newFont = false; inText = true; } /** * This is used to add a polygon to the current path. Used by drawPolygon(), * drawPolyline() and fillPolygon() etal * * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polygon * @see #drawPolygon * @see #drawPolyline * @see #fillPolygon */ public void polygon(int[] xp, int[] yp, int np) { // newPath() not needed here as moveto does it ;-) moveto(xp[0], yp[0]); for (int i = 1; i < np; i++) { lineto(xp[i], yp[i]); } } /** * @see Graphics2D#rotate(double) */ @Override public void rotate(double theta) { AffineTransform newTransform = new AffineTransform(transform); newTransform.rotate(theta); setNewTranform(newTransform); } /** * @see Graphics2D#rotate(double, double, double) */ @Override public void rotate(double theta, double x, double y) { AffineTransform newTransform = new AffineTransform(transform); newTransform.rotate(theta, x, y); setNewTranform(newTransform); } /** * @see Graphics2D#scale(double, double) */ @Override public void scale(double sx, double sy) { AffineTransform newTransform = new AffineTransform(transform); newTransform.scale(sx, sy); setNewTranform(newTransform); } /** * @see Graphics2D#setBackground(Color) */ @Override public void setBackground(Color color) { background = color; } /** * Clips to a set of coordinates * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void setClip(int x, int y, int w, int h) { /*clipRectangle = new Rectangle(x, y, w, h); closeBlock(); // finish off any existing paths drawRect(x, y, w, h); closeBlock("W n"); // clip to current path*/ setClip(new Rectangle(x, y, w, h)); } /** * As my JDK docs say, this may break with Java 2D. *
* Sets the clipping region to that of a Shape.
*
* @param s Shape to clip to.
*/
@Override
public void setClip(Shape s) {
closeBlock();
if (clip != null) {
restoreState();
transform = clipTransform;
}
if (s == null) {
clip = null;
return;
}
clipTransform = transform;
clip = new Area(s);
clipRectangle = s.getBounds();
saveState();
followPath(s, CLIP);
pw.println("n");
//setClip(r.x, r.y, r.width, r.height);
}
/**
* Sets the color for drawing
*
* @param c Color to use
*/
@Override
public void setColor(Color c) {
setPaint(c);
}
/**
* @see Graphics2D#setComposite(Composite)
*/
@Override
public void setComposite(Composite comp) {
this.composite = comp;
}
/**
* This extension sets the line width to the default of 1mm which is what
* Java uses when drawing to a PrintJob.
*/
public void setDefaultLineWidth() {
closeBlock(); // draw any path before we change the line width
pw.println("1 w");
}
/**
* This sets the font.
*
* @param f java.awt.Font to set to.
*/
@Override
public void setFont(Font f) {
// optimize: Save some space if the font is already the current one.
if (font != f) {
font = f;
pdffont = page.getFont("/Type1", f.getName(), f.getStyle());
// mark the font as changed
newFont = true;
}
}
public void setExistingTtfFont(Font f) {
if (font != f) {
font = f;
pdffont = page.getFont("/TrueType", f.getName(), f.getStyle());
// mark the font as changed
newFont = true;
}
}
public void setTtfFont(Font f, File file) throws IOException {
if (font != f) {
font = f;
pdffont = page.getEmbeddedFont(f.getName(), f.getStyle(), file);
// mark the font as changed
newFont = true;
}
}
private void setLineCap(int cap) {
int lineCap = 0;
switch (cap) {
case BasicStroke.JOIN_MITER:
lineCap = 0;
break;
case BasicStroke.JOIN_ROUND:
lineCap = 1;
break;
case BasicStroke.JOIN_BEVEL:
lineCap = 2;
break;
}
if (this.lineCap != lineCap) {
closeBlock(); // draw any path before we change the line width
this.lineCap = lineCap;
pw.println("" + lineCap + " J");
}
}
private void setLineJoin(int join) {
int lineJoin = 0;
switch (join) {
case BasicStroke.JOIN_MITER:
lineJoin = 0;
break;
case BasicStroke.JOIN_ROUND:
lineJoin = 1;
break;
case BasicStroke.JOIN_BEVEL:
lineJoin = 2;
break;
}
if (this.lineJoin != lineJoin) {
closeBlock(); // draw any path before we change the line width
this.lineJoin = lineJoin;
pw.println("" + lineJoin + " j");
}
}
/**
* This extension allows the width of the drawn line to be set
*
* @param width Line width in pdf graphic units (points)
*/
public void setLineWidth(float width) {
if (width != this.lineWidth) {
closeBlock(); // draw any path before we change the line width
this.lineWidth = width;
pw.println("" + width + " w");
}
}
private void setMiterLimit(float limit) {
if (limit != this.miterLimit) {
closeBlock(); // draw any path before we change the line width
this.miterLimit = limit;
pw.println("" + limit + " M");
}
}
/**
* Sets the paint for drawing
*
* @param paint Paint to use
*/
@Override
public void setPaint(Paint paint) {
this.paint = paint;
this.shading = null;
this.pattern = null;
this.paintTransform = null;
if (paint instanceof Color) {
Color c = (Color) paint;
double r = ((double) c.getRed()) / 255.0;
double g = ((double) c.getGreen()) / 255.0;
double b = ((double) c.getBlue()) / 255.0;
closeBlock(); // This ensures any paths are drawn in the previous
if (currentAlpha != c.getAlpha()) {
String gsId = "/GSAlpha" + c.getAlpha();
currentAlpha = c.getAlpha();
if (!usedAlphas.contains(c.getAlpha())) {
page.addExtGStateResource(gsId + " <>");
usedAlphas.add(currentAlpha);
}
pw.println(gsId + " gs");
}
// colours
pw.println("" + r + " " + g + " " + b + " rg "
+ r + " " + g + " " + b + " RG");
}
if (paint instanceof MultipleGradientPaint) {
closeBlock();
if ((paint instanceof RadialGradientPaint) || ((paint instanceof LinearGradientPaint) && (((LinearGradientPaint) paint).getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE))) {
shading = "gradient";
} else {
pattern = "gradient";
}
}
if (paint instanceof TexturePaint) {
closeBlock();
pattern = "texture";
}
}
/*private boolean isPaintShading(Paint p) {
return (p instanceof RadialGradientPaint) || ((p instanceof LinearGradientPaint) && (((LinearGradientPaint) p).getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE));
}*/
private void initGradientPaint(MultipleGradientPaint grad) {
if ((grad instanceof LinearGradientPaint) && (((LinearGradientPaint) grad).getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT)) {
LinearGradientPaint linGrad = (LinearGradientPaint) grad;
Point2D start = linGrad.getStartPoint();
Point2D end = linGrad.getEndPoint();
double deltaX = end.getX() - start.getX();
double deltaY = end.getY() - start.getY();
Point2D newEnd = new Point2D.Double(end.getX() + deltaX, end.getY() + deltaY);
int colorCount = grad.getFractions().length;
float fractions2[] = new float[colorCount * 2 - 1];
Color colors2[] = new Color[colorCount * 2 - 1];
float fractionsrev[] = new float[linGrad.getFractions().length];
Color colorsrev[] = new Color[linGrad.getColors().length];
for (int i = 0; i < fractionsrev.length; i++) {
colorsrev[i] = linGrad.getColors()[i];
fractionsrev[i] = linGrad.getFractions()[i];
}
for (int i = 0; i < colorCount; i++) {
colors2[i] = colorsrev[i];
fractions2[i] = fractionsrev[i] / 2;
}
for (int i = 0; i < colorCount; i++) {
colors2[colors2.length - i - 1] = colorsrev[i];
fractions2[colors2.length - i - 1] = 1f - fractionsrev[i] / 2;
}
LinearGradientPaint linGrad2 = new LinearGradientPaint(start, newEnd, fractions2, colors2, MultipleGradientPaint.CycleMethod.REPEAT);
grad = linGrad2;
}
List
* In theory you could use setClip(), except that java.awt.Graphics only
* supports Rectangle with that method, so we will have an extra method.
*
* @param p Polygon to clip to
*/
public void clipPolygon(Polygon p) {
closeBlock(); // finish off any existing path
polygon(p.xpoints, p.ypoints, p.npoints);
closeBlock("W"); // clip to current path
clipRectangle = p.getBounds();
}
/**
* Clips to a set of coordinates
*
* @param x coordinate
* @param y coordinate
* @param w width
* @param h height
*/
@Override
public void clipRect(int x, int y, int w, int h) {
setClip(x, y, w, h);
}
/**
* All functions should call this to close any existing optimized blocks.
*/
void closeBlock() {
closeBlock("S");
}
/**
*
* This is used by code that use the path in any way other than Stroke (like
* Fill, close path & Stroke etc). Usually this is used internally.
* This returns a child instance of this Graphics object. As with AWT, the
* affects of using the parent instance while the child exists, is not
* determined.
* Once complete, the child should be released with it's dispose() method
* which will restore the graphics state to it's parent.
* The new current point is (x2,y2)
*
* @param x1 Second control point
* @param y1 Second control point
* @param x2 Destination point
* @param y2 Destination point
*/
public void curveto(double x1, double y1, double x2, double y2) {
newPath();
pw.println(cxy(x1, y1) + cxy(x2, y2) + "v");
lx = (float) x2;
ly = (float) y2;
}
/**
* This extension appends a Bezier curve to the path. The curve extends from
* the current point to (x3,y3) using (x1,y1) and (x2,y2) as the Bezier
* control points.
*
* The new current point is (x3,y3)
*
* @param x1 First control point
* @param y1 First control point
* @param x2 Second control point
* @param y2 Second control point
* @param x3 Destination point
* @param y3 Destination point
*/
public void curveto(double x1, double y1, double x2, double y2, double x3, double y3) {
newPath();
pw.println(cxy(x1, y1) + cxy(x2, y2) + cxy(x3, y3) + "c");
lx = (float) x3;
ly = (float) y3;
}
/**
* This extension appends a Bezier curve to the path. The curve extends from
* the current point to (x2,y2) using the current point and (x1,y1) as the
* Bezier control points.
*
* The new current point is (x2,y2)
*
* @param x1 Second control point
* @param y1 Second control point
* @param x2 Destination point
* @param y2 Destination point
*/
public void curveto(int x1, int y1, int x2, int y2) {
newPath();
pw.println(cxy(x1, y1) + cxy(x2, y2) + "v");
lx = x2;
ly = y2;
}
/**
* This extension appends a Bezier curve to the path. The curve extends from
* the current point to (x3,y3) using (x1,y1) and (x2,y2) as the Bezier
* control points.
*
* The new current point is (x3,y3)
*
* @param x1 First control point
* @param y1 First control point
* @param x2 Second control point
* @param y2 Second control point
* @param x3 Destination point
* @param y3 Destination point
*/
public void curveto(int x1, int y1, int x2, int y2, int x3, int y3) {
newPath();
pw.println(cxy(x1, y1) + cxy(x2, y2) + cxy(x3, y3) + "c");
lx = x3;
ly = y3;
}
/**
* This extension appends a Bezier curve to the path. The curve extends from
* the current point to (x2,y2) using (x1,y1) and the end point as the
* Bezier control points.
*
* The new current point is (x2,y2)
*
* @param x1 Second control point
* @param y1 Second control point
* @param x2 Destination point
* @param y2 Destination point
*/
public void curveto2(double x1, double y1, double x2, double y2) {
newPath();
pw.println(cxy(x1, y1) + cxy(x2, y2) + "y");
lx = (float) x2;
ly = (float) y2;
}
// Arcs are horrible and complex. They are at the end of the
// file, because they are the largest. This is because, unlike
// Postscript, PDF doesn't have any arc operators, so we must
// implement them by converting into one or more Bezier curves
// (which is how Postscript does them internally).
/**
* This extension appends a Bezier curve to the path. The curve extends from
* the current point to (x2,y2) using (x1,y1) and the end point as the
* Bezier control points.
*
* The new current point is (x2,y2)
*
* @param x1 Second control point
* @param y1 Second control point
* @param x2 Destination point
* @param y2 Destination point
*/
public void curveto2(int x1, int y1, int x2, int y2) {
newPath();
pw.println(cxy(x1, y1) + cxy(x2, y2) + "y");
lx = x2;
ly = y2;
}
/**
* Converts the Java space dimension into pdf.
*
* @param w width
* @param h height
* @return String containing the coordinates in PDF space
*/
private String cwh(double w, double h) {
return "" + df.format(w) + " " + df.format(h) + " ";
}
/**
* Converts the Java space dimension into pdf.
*
* @param w width
* @param h height
* @return String containing the coordinates in PDF space
*/
private String cwh(int w, int h) {
return cwh((double) w, (double) h);
}
/**
* Converts the Java space coordinates into pdf.
*
* @param x coordinate
* @param y coordinate
* @return String containing the coordinates in PDF space
*/
private String cxy(double x, double y) {
return "" + df.format(x) + " " + df.format(y) + " ";
}
/**
* Converts the Java space coordinates into pdf.
*
* @param x coordinate
* @param y coordinate
* @return String containing the coordinates in PDF space
*/
private String cxy(int x, int y) {
return cxy((double) x, (double) y);
}
/**
*
* This releases any resources used by this Graphics object. You must use
* this method once finished with it. Leaving it open will leave the PDF
* stream in an inconsistent state, and will produce errors.
* If this was created with Graphics.create() then the parent instance can
* be used again. If not, then this closes the graphics operations for this
* page when used with PDFJob.
* When using PDFPage, you can create another fresh Graphics instance, which
* will draw over this one.
* Not implemented
* Draws a 3-D highlighted outline of the specified rectangle. The edges of
* the rectangle are highlighted so that they appear to be beveled and lit
* from the upper left corner. The colors used for the highlighting effect
* are determined based on the current color. The resulting rectangle covers
* an area that is width + 1 pixels wide by height + 1 pixels tall.
*
* Not implemented
* Draw's an image onto the page, with a backing colour.
* Draw's an image onto the page, with a backing colour.
* Draws an image onto the page.
* This method is implemented with ASCIIbase85 encoding and the zip stream
* deflater. It results in a stream that is anywhere from 3 to 10 times as
* big as the image. This obviously needs some improvement, but it works
* well for small images
* This is not yet supported.
*
* @param img The java.awt.Image
* @param dx1 coordinate on page
* @param dy1 coordinate on page
* @param dx2 coordinate on page
* @param dy2 coordinate on page
* @param sx1 coordinate on image
* @param sy1 coordinate on image
* @param sx2 coordinate on image
* @param sy2 coordinate on image
* @param bgcolor Background colour
* @param obs ImageObserver
* @return true if drawn
*/
@Override
public boolean drawImage(Image img, int dx1, int dy1, int dx2,
int dy2, int sx1, int sy1, int sx2, int sy2,
Color bgcolor, ImageObserver obs) {
return false;
}
//============ Clipping operations =======================
/**
* Draw's an image onto the page, with scaling
*
* This is not yet supported.
*
* @param img The java.awt.Image
* @param dx1 coordinate on page
* @param dy1 coordinate on page
* @param dx2 coordinate on page
* @param dy2 coordinate on page
* @param sx1 coordinate on image
* @param sy1 coordinate on image
* @param sx2 coordinate on image
* @param sy2 coordinate on image
* @param obs ImageObserver
* @return true if drawn
*/
@Override
public boolean drawImage(Image img, int dx1, int dy1, int dx2,
int dy2, int sx1, int sy1, int sx2, int sy2,
ImageObserver obs) {
// This shouldn't be too bad, just change the coordinate matrix
return false;
}
/**
* Draws a line between two coordinates.
*
* If the first coordinate is the same as the last one drawn (i.e. a
* previous drawLine, moveto, etc) it is ignored.
*
* @param x1 coordinate
* @param y1 coordinate
* @param x2 coordinate
* @param y2 coordinate
*/
@Override
public void drawLine(int x1, int y1, int x2, int y2) {
moveto(x1, y1);
lineto(x2, y2);
}
//============ Arcs operations ==============================
// These are the standard Graphics operators. They use the
// arc extension operators to achieve the affect.
/**
*
* Draws an oval
* This is not supported yet, as I have no idea what an
* AttributedCharacterIterator is.
*
* This method is new to the Java2 API.
*/
@Override
public void drawString(java.text.AttributedCharacterIterator aci,
float x, float y) {
}
/**
* Draws a string using a AttributedCharacterIterator.
*
* This is not supported yet, as I have no idea what an
* AttributedCharacterIterator is.
*
* This method is new to the Java2 API.
*/
@Override
public void drawString(java.text.AttributedCharacterIterator aci,
int x, int y) {
}
public void drawStringWithMode(String s, float x, float y, int mode) {
newTextBlock(x, y);
if (mode > -1) {
pw.println("" + mode + " Tr");
}
if (pdffont instanceof PDFEmbeddedFont) {
pw.print("[(");
pw.printRaw(PDFStringHelper.makeRawPDFString(s));
pw.println(")] TJ");
} else {
pw.print(PDFStringHelper.makePDFString(s));
pw.println(" Tj");
}
closeBlock();
}
@Override
public void drawString(String s, float x, float y) {
drawStringWithMode(s, x, y, -1);
}
/**
* This draws a string.
*
* @param s
* @oaran s String to draw
* @param x coordinate
* @param y coordinate
*/
@Override
public void drawString(String s, int x, int y) {
drawString(s, (float) x, (float) y);
}
public void drawTransparentString(String s, float x, float y) {
drawStringWithMode(s, x, y, 3);
}
/**
* This draws a transparent string.
*
* @oaran s String to draw
* @param x coordinate
* @param y coordinate
*/
public void drawTransparentString(String s, int x, int y) {
drawTransparentString(s, (float) x, (float) y);
}
/**
* @see Graphics2D#fill(Shape)
*/
@Override
public void fill(Shape s) {
followPath(s, FILL);
}
/**
*
* Not implemented
* Draws a filled oval
* This doesn't work correctly. Perhaps having some way of mapping the base
* 14 fonts to our own FontMetrics implementation?
*
* @param font The java.awt.Font to return the metrics for
* @return FontMetrics for a font
*/
@Override
public FontMetrics getFontMetrics(Font font) {
Frame dummy = new Frame();
dummy.addNotify();
Image image = dummy.createImage(100, 100);
if (image == null) {
System.err.println("getFontMetrics: image is null");
}
Graphics graphics = image.getGraphics();
return graphics.getFontMetrics(font);
}
/**
* @see Graphics2D#getFontRenderContext()
*/
@Override
public FontRenderContext getFontRenderContext() {
boolean antialias = RenderingHints.VALUE_TEXT_ANTIALIAS_ON.equals(getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING));
boolean fractions = RenderingHints.VALUE_FRACTIONALMETRICS_ON.equals(getRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS));
return new FontRenderContext(new AffineTransform(), antialias, fractions);
}
/**
* Returns the associated PDFPage for this graphic
*
* @return the associated PDFPage for this graphic
*/
public PDFPage getPage() {
return page;
}
/**
* Returns the current pen Colour
*
* @return the current pen Colour
*/
@Override
public Paint getPaint() {
return paint;
}
/**
* @param arg0 a key
* @return the rendering hint
*/
@Override
public Object getRenderingHint(Key arg0) {
return rhints.get(arg0);
}
/**
* @see Graphics2D#getRenderingHints()
*/
@Override
public RenderingHints getRenderingHints() {
return rhints;
}
/**
* @see Graphics2D#getStroke()
*/
@Override
public Stroke getStroke() {
return stroke;
}
/**
* @see Graphics2D#getTransform()
*/
@Override
public AffineTransform getTransform() {
return new AffineTransform(transform);
}
/**
* Returns the PrintWriter handling the underlying stream
*
* @return the PrintWriter handling the underlying stream
*/
public RawPrintWriter getWriter() {
return pw;
}
/**
* @see Graphics2D#hit(Rectangle, Shape, boolean)
*/
@Override
public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
if (onStroke) {
s = stroke.createStrokedShape(s);
}
Area area = new Area(s);
if (clip != null) {
area.intersect(clip);
}
return area.intersects(rect.x, rect.y, rect.width, rect.height);
}
/**
* This initialises the stream by saving the current graphics state, and
* setting up the default line width (for us).
*
* It also sets up the instance ready for graphic operations and any
* optimisations.
*
*
* For child instances, the stream is already open, so this should keep
* things happy.
*/
private void init() {
PageFormat pf = page.getPageFormat();
// save graphics state (restored by dispose)
if (child) {
pw.print("q ");
}
// now initialise the instance
//setColor(Color.black);
paint = Color.black;
// possible: if parent.color is not black, then force black?
// must check to see what AWT does?
// Original User Space Transform (identity)
// Transform from Java Space to PDF Space
pTransform = new AffineTransform();
pTransform.translate(0, pf.getHeight());
pTransform.scale(1d, -1d);
// Combined Transform User->Java->PDF
setNewTranform(new AffineTransform());
// Set the line width
setStroke(DEF_STROKE);
}
/**
* This is called by PDFPage when creating a Graphcis instance.
*
* @param page The PDFPage to draw onto.
*/
protected void init(PDFPage page) {
this.page = page;
// We are the parent instance
child = false;
// Now create a stream to store the graphics in
PDFStream stream = new PDFStream();
// To view detail in uncompressed format comment out the next line
//stream.setDeflate(true);
page.getPDFDocument().add(stream);
page.add(stream);
pw = new RawPrintWriter(stream.getOutputStream());
// initially, we are limited to the page size
clipRectangle = page.getImageableArea();
// finally initialise the stream
init();
}
/**
* This method is used internally by create() and by the PDFJob class
*
* @param page PDFPage to draw into
* @param pw PrintWriter to use
*/
protected void init(PDFPage page, RawPrintWriter pw) {
this.page = page;
this.pw = pw;
// In this case, we didn't create the stream (our parent did)
// so child is true (see dispose)
child = true;
// finally initialise the stream
init();
}
/**
* This adds a line segment to the current path
*
* @param x coordinate
* @param y coordinate
*/
public void lineto(double x, double y) {
newPath();
// no optimisation here as it may introduce errors on decimal coordinates.
pw.print(cxy(x, y) + "l ");
lx = (float) x;
ly = (float) y;
}
/**
* This adds a line segment to the current path
*
* @param x coordinate
* @param y coordinate
*/
public void lineto(int x, int y) {
newPath();
if (lx != x && ly != y) {
pw.print(cxy(x, y) + "l ");
}
lx = x;
ly = y;
}
/**
* This moves the current drawing point.
*
* @param x coordinate
* @param y coordinate
*/
public void moveto(double x, double y) {
newPath();
// no optimisation here as it may introduce errors on decimal coordinates.
pw.print(cxy(x, y) + "m ");
lx = (float) x;
ly = (float) y;
}
/**
* This moves the current drawing point.
*
* @param x coordinate
* @param y coordinate
*/
public void moveto(int x, int y) {
newPath();
if (lx != x || ly != y) {
pw.print(cxy(x, y) + "m ");
}
lx = x;
ly = y;
}
/**
* Functions that draw lines should start by calling this. It starts a new
* path unless inStroke is set, in that case it uses the existing path
*/
void newPath() {
if (inText) {
closeBlock();
}
if (!inStroke) {
if (pre_np != null) {
pw.print(pre_np); // this is the prefix set by setOrientation()
pre_np = null;
}
pw.print("n ");
}
inText = false;
inStroke = true;
// an unlikely coordinate to fool the moveto() optimizer
lx = ly = -9999;
}
/**
*
* Functions that draw text should start by calling this. It starts a text
* block (accounting for media orientation) unless we are already in a Text
* block.
* It also handles if the font has been changed since the current text block
* was started, so your function will be current.
* Sets the clipping region to that of a Shape.
*
* @param s Shape to clip to.
*/
@Override
public void setClip(Shape s) {
closeBlock();
if (clip != null) {
restoreState();
transform = clipTransform;
}
if (s == null) {
clip = null;
return;
}
clipTransform = transform;
clip = new Area(s);
clipRectangle = s.getBounds();
saveState();
followPath(s, CLIP);
pw.println("n");
//setClip(r.x, r.y, r.width, r.height);
}
/**
* Sets the color for drawing
*
* @param c Color to use
*/
@Override
public void setColor(Color c) {
setPaint(c);
}
/**
* @see Graphics2D#setComposite(Composite)
*/
@Override
public void setComposite(Composite comp) {
this.composite = comp;
}
/**
* This extension sets the line width to the default of 1mm which is what
* Java uses when drawing to a PrintJob.
*/
public void setDefaultLineWidth() {
closeBlock(); // draw any path before we change the line width
pw.println("1 w");
}
/**
* This sets the font.
*
* @param f java.awt.Font to set to.
*/
@Override
public void setFont(Font f) {
// optimize: Save some space if the font is already the current one.
if (font != f) {
font = f;
pdffont = page.getFont("/Type1", f.getName(), f.getStyle());
// mark the font as changed
newFont = true;
}
}
public void setExistingTtfFont(Font f) {
if (font != f) {
font = f;
pdffont = page.getFont("/TrueType", f.getName(), f.getStyle());
// mark the font as changed
newFont = true;
}
}
public void setTtfFont(Font f, File file) throws IOException {
if (font != f) {
font = f;
pdffont = page.getEmbeddedFont(f.getName(), f.getStyle(), file);
// mark the font as changed
newFont = true;
}
}
private void setLineCap(int cap) {
int lineCap = 0;
switch (cap) {
case BasicStroke.JOIN_MITER:
lineCap = 0;
break;
case BasicStroke.JOIN_ROUND:
lineCap = 1;
break;
case BasicStroke.JOIN_BEVEL:
lineCap = 2;
break;
}
if (this.lineCap != lineCap) {
closeBlock(); // draw any path before we change the line width
this.lineCap = lineCap;
pw.println("" + lineCap + " J");
}
}
private void setLineJoin(int join) {
int lineJoin = 0;
switch (join) {
case BasicStroke.JOIN_MITER:
lineJoin = 0;
break;
case BasicStroke.JOIN_ROUND:
lineJoin = 1;
break;
case BasicStroke.JOIN_BEVEL:
lineJoin = 2;
break;
}
if (this.lineJoin != lineJoin) {
closeBlock(); // draw any path before we change the line width
this.lineJoin = lineJoin;
pw.println("" + lineJoin + " j");
}
}
/**
* This extension allows the width of the drawn line to be set
*
* @param width Line width in pdf graphic units (points)
*/
public void setLineWidth(float width) {
if (width != this.lineWidth) {
closeBlock(); // draw any path before we change the line width
this.lineWidth = width;
pw.println("" + width + " w");
}
}
private void setMiterLimit(float limit) {
if (limit != this.miterLimit) {
closeBlock(); // draw any path before we change the line width
this.miterLimit = limit;
pw.println("" + limit + " M");
}
}
/**
* Sets the paint for drawing
*
* @param paint Paint to use
*/
@Override
public void setPaint(Paint paint) {
this.paint = paint;
this.shading = null;
this.pattern = null;
this.paintTransform = null;
if (paint instanceof Color) {
Color c = (Color) paint;
double r = ((double) c.getRed()) / 255.0;
double g = ((double) c.getGreen()) / 255.0;
double b = ((double) c.getBlue()) / 255.0;
closeBlock(); // This ensures any paths are drawn in the previous
if (currentAlpha != c.getAlpha()) {
String gsId = "/GSAlpha" + c.getAlpha();
currentAlpha = c.getAlpha();
if (!usedAlphas.contains(c.getAlpha())) {
page.addExtGStateResource(gsId + " <>");
usedAlphas.add(currentAlpha);
}
pw.println(gsId + " gs");
}
// colours
pw.println("" + r + " " + g + " " + b + " rg "
+ r + " " + g + " " + b + " RG");
}
if (paint instanceof MultipleGradientPaint) {
closeBlock();
if ((paint instanceof RadialGradientPaint) || ((paint instanceof LinearGradientPaint) && (((LinearGradientPaint) paint).getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE))) {
shading = "gradient";
} else {
pattern = "gradient";
}
}
if (paint instanceof TexturePaint) {
closeBlock();
pattern = "texture";
}
}
private boolean useFunctionShading(MultipleGradientPaint fgrad) {
return ((fgrad instanceof RadialGradientPaint)
&& fgrad.getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE);
}
private Color[] convertColorSpace(Color[] colors, MultipleGradientPaint.ColorSpaceType colorSpaceType) {
/*if (colorSpaceType == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) {
Color[] ret = new Color[colors.length];
for (int i = 0; i < colors.length; i++) {
int argb = colors[i].getRGB();
int a = argb >>> 24;
int r = srgbToLinear[(argb >> 16) & 0xff];
int g = srgbToLinear[(argb >> 8) & 0xff];
int b = srgbToLinear[(argb) & 0xff];
ret[i] = new Color(r, g, b, a);
}
return ret;
}*/
//return colors;
return colors;
}
/*private boolean isPaintShading(Paint p) {
return (p instanceof RadialGradientPaint) || ((p instanceof LinearGradientPaint) && (((LinearGradientPaint) p).getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE));
}*/
private void initGradientPaint(MultipleGradientPaint grad, Shape fillShape) {
if ((grad instanceof LinearGradientPaint) && (((LinearGradientPaint) grad).getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT)) {
LinearGradientPaint linGrad = (LinearGradientPaint) grad;
Point2D start = linGrad.getStartPoint();
Point2D end = linGrad.getEndPoint();
double deltaX = end.getX() - start.getX();
double deltaY = end.getY() - start.getY();
Point2D newEnd = new Point2D.Double(end.getX() + deltaX, end.getY() + deltaY);
int colorCount = grad.getFractions().length;
float fractions2[] = new float[colorCount * 2 - 1];
Color colors2[] = new Color[colorCount * 2 - 1];
float fractionsrev[] = new float[linGrad.getFractions().length];
Color colorsrev[] = new Color[linGrad.getColors().length];
for (int i = 0; i < fractionsrev.length; i++) {
colorsrev[i] = linGrad.getColors()[i];
fractionsrev[i] = linGrad.getFractions()[i];
}
for (int i = 0; i < colorCount; i++) {
colors2[i] = colorsrev[i];
fractions2[i] = fractionsrev[i] / 2;
}
for (int i = 0; i < colorCount; i++) {
colors2[colors2.length - i - 1] = colorsrev[i];
fractions2[colors2.length - i - 1] = 1f - fractionsrev[i] / 2;
}
LinearGradientPaint linGrad2 = new LinearGradientPaint(start, newEnd, fractions2, colors2, MultipleGradientPaint.CycleMethod.REPEAT);
grad = linGrad2;
}
ListPrintWriter to attach to.
*/
protected PDFGraphics createGraphic(PDFPage page,
RawPrintWriter pw) {
PDFGraphics g = new PDFGraphics();
g.init(page, pw);
return g;
}
/**
* This extension appends a Bezier curve to the path. The curve extends from
* the current point to (x2,y2) using the current point and (x1,y1) as the
* Bezier control points.
* int value
* @param y an int value
* @param width an int value
* @param height an int value
* @param raised a boolean value
*/
@Override
public void draw3DRect(int x, int y,
int width, int height, boolean raised) {
// Not implemented
}
/**
* Draws an arc
*
* @param x coordinate
* @param y coordinate
* @param w width
* @param h height
* @param sa Start angle
* @param aa End angle
*/
@Override
public void drawArc(int x, int y, int w, int h, int sa, int aa) {
w = w >> 1;
h = h >> 1;
x += w;
y += h;
arc((double) x, (double) y,
(double) w, (double) h,
(double) -sa, (double) (-sa - aa),
false);
}
/**
* byte[] value
* @param offset an int value
* @param length an int value
* @param x an int value
* @param y an int value
*/
@Override
public void drawBytes(byte[] data, int offset, int length, int x, int y) {
}
//============ Optimizers =======================
/**
* @see Graphics2D#drawGlyphVector(GlyphVector, float, float)
*/
@Override
public void drawGlyphVector(GlyphVector g, float x, float y) {
Shape s = g.getOutline(x, y);
fill(s);
}
/**
* @see Graphics2D#drawImage(BufferedImage, BufferedImageOp, int, int)
*/
@Override
public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) {
BufferedImage result = img;
if (op != null) {
result = op.createCompatibleDestImage(img, img.getColorModel());
result = op.filter(img, result);
}
drawImage(result, x, y, null);
}
/**
* @see Graphics2D#drawImage(Image, AffineTransform, ImageObserver)
*/
@Override
public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) {
// return drawImage(img, null, xform, null, obs);
return true;
}
/**
* int value
* @param y an int value
* @param width an int value
* @param height an int value
* @param raised a boolean value
*/
@Override
public void fill3DRect(int x, int y,
int width, int height, boolean raised) {
// Not implemented
}
/**
* Fills an arc, joining the start and end coordinates
*
* @param x coordinate
* @param y coordinate
* @param w width
* @param h height
* @param sa Start angle
* @param aa End angle
*/
@Override
public void fillArc(int x, int y, int w, int h, int sa, int aa) {
// here we fool the optimizer. We force any open path to be closed,
// then draw the arc. Finally, as the optimizer hasn't stroke'd the
// path, we close and fill it, and mark the Stroke as closed.
//
// Note: The lineto to the centre of the object is required, because
// the fill only fills the arc. Skipping this includes an extra
// chord, which isn't correct. Peter May 31 2000
closeBlock();
patternFill(null/*FIXME!!!*/);
drawArc(x, y, w, h, sa, aa);
lineto(x + (w >> 1), y + (h >> 1));
if (shadingFill(null)) {
return;
}
closeBlock("b"); // closepath and fill
}
//============ Extension operations ==============================
// These are extensions, and provide access to PDF Specific
// operators.
/**
*