From 22c5e67a74d1c986008f44e31bd2a191d0a50602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Wed, 2 Nov 2022 08:43:52 +0100 Subject: [PATCH] Fixed #1817 PDF export - now storing JPEG images without recompression to PNG --- CHANGELOG.md | 8 +- lib/gnujpdf.jar | Bin 205035 -> 207773 bytes .../exporters/ImageTagBufferedImage.java | 328 ++++++++++++++++++ .../flash/exporters/shape/BitmapExporter.java | 12 +- libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java | 2 +- libsrc/gnujpdf/src/gnu/jpdf/PDFImage.java | 115 +++--- 6 files changed, 420 insertions(+), 45 deletions(-) create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ImageTagBufferedImage.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 196f8abe6..760bdf7be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Fixed +- [#1817] PDF export - now storing JPEG images without recompression to PNG ## [16.0.2] - 2022-11-01 ### Added @@ -2321,7 +2323,10 @@ All notable changes to this project will be documented in this file. ### Added - Initial public release -[Unreleased]: https://github.com/jindrapetrik/jpexs-decompiler/compare/version15.1.1...dev +[Unreleased]: https://github.com/jindrapetrik/jpexs-decompiler/compare/version16.0.2...dev +[16.0.2]: https://github.com/jindrapetrik/jpexs-decompiler/compare/version16.0.1...version16.0.2 +[16.0.1]: https://github.com/jindrapetrik/jpexs-decompiler/compare/version16.0.0...version16.0.1 +[16.0.0]: https://github.com/jindrapetrik/jpexs-decompiler/compare/version15.1.1...version16.0.0 [15.1.1]: https://github.com/jindrapetrik/jpexs-decompiler/compare/version15.1.0...version15.1.1 [15.1.0]: https://github.com/jindrapetrik/jpexs-decompiler/compare/version15.0.0...version15.1.0 [15.0.0]: https://github.com/jindrapetrik/jpexs-decompiler/compare/version14.6.0...version15.0.0 @@ -2442,6 +2447,7 @@ All notable changes to this project will be documented in this file. [alpha 9]: https://github.com/jindrapetrik/jpexs-decompiler/compare/alpha8...alpha9 [alpha 8]: https://github.com/jindrapetrik/jpexs-decompiler/compare/alpha7...alpha8 [alpha 7]: https://github.com/jindrapetrik/jpexs-decompiler/releases/tag/alpha7 +[#1817]: https://www.free-decompiler.com/flash/issues/1817 [#1731]: https://www.free-decompiler.com/flash/issues/1731 [#1825]: https://www.free-decompiler.com/flash/issues/1825 [#1737]: https://www.free-decompiler.com/flash/issues/1737 diff --git a/lib/gnujpdf.jar b/lib/gnujpdf.jar index 48178b77ee81f31773a3563f030a2001ed14bbe9..72ac4a7cf1ec2d65c0e761ef52ededf183d87ec3 100644 GIT binary patch delta 41139 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 G{QEE81DkgM delta 39109 zcmd442YgjU^FKVhd-^?Tgp^AXNC+(nAv7tWcM(Dt2-N_gNC}W2g5`=RU@uXRU;`V9 zjUow%ide8KSSgCwv0;B~DDQXo+9y`l1RN~2D zM|G8WWb`wdO3WXBO+Y3#&sbusj0@-AaE3}G-rvTr)c4_HSLH0*R%ux%d-kBukG{3# z*fV{(cVi|GRtF9gZdvi1&e+oGtDi56yNGoAVoJ0_lww~}{pa(el4PAv=eB=suppBE zvZ{N2n(k#c7FKoW1-p9wUy0u8zgV1iT`%TTKlVcImPv0+X1sM5?{H66Q2p7PZ}Py~ z)m!(BHUjTaAn<;5(%!Qa%s<|mSpO1@Nd8kWtHm2h-W7kbEFV;tH*&Xpw6{IyZMszN zD#@+`tk-tYXt0|%?yzgyKHaESE4N0b;iOzDA-DL3#5wF-Pn?E;@wKl|<|MQO$S zcgJjL_})80QMx5?poFp2)ujgy=iLq}@1RusPRg_IqOSH@8fvejDfZp8$i9cJ#=Gn6 zd+8?ozUnC-_^`fQHDU6Wr$2baho!i->2Nk3Zi3y4mSaJ@%(j@v`5K zu1nB|Lr{lX)!jbL*z)dAA8XY`Q<`i!@Y`g@%C{8%F^u(_3COd_bmmZ;Gmp}p3d(in zQ!8fyjdT{$X#AEsm1v=gZg&>ZcIP~L*Eyf|A$`zULdTt@Omi-%e)G?ns?YLDny%W? z-CbS#cOQ0N^0JHMUU$o(7glN2 zLl391&ea7|&e-z!D;8rrwmkLfD%R_1VE7DZ-v!#A1?|sKKj(RxjNd8F3v{)!n`)dF zX_xa7z2>|^e><-+%Xxz(J9}A2=PlONdAoYW>;5=6ch8ocpT0yS|24okNb`m@6^8T5 zKTkYOMD0zp_It+qCfe+BcE8Kkvj@ES^;o*G!DTvos5)_Wb~1Z}k(S*$dxXQ5v&URY zVjI2TZ?VBPyMb+X*%tP=H!+!YGiqE`%eHxIlUbk86O7EFfpg1dl=pGiRqRQZJw^91 za?7So8&xrEVa3$S@+t{#r^}vUE(*^muNqP|w_N7kFKayKvgc)uMAT4lMwC}pELu3V z+#!wacG-*UB}VqFd9$i|&6z*53>dZ;m^a#FuQ19wx6GSTpJjz!X0J(XUq`_f{ndYY zqaF4#d(&lm*k0tK(UDWj=9G_RBwFP4mG9nm**np9Gt0{smdf1sT=xD6xnpJSewQ7H z=1MrK><2FUQ0B&k0DjcMvU!zr%Bsp$g^yfzSiOpUQ);nK80pzl=Vm(=`;3v1-M7LS z?npwvaOnp2CEm6e2^pW8Je;<$Y;muJQzf|r@n1Vx z$6`M)VwDbk&wc_k=s{WI7nl7?o4_+!Bfx%Rznkn2m;K59@;0Wh*(UqjW&g0_UgK0& zYA`myxpOzRdd&*S-!r+MXzN0pyn)q76G)Y+0v{riJt-V*(pJ!+v% z8gJ$DBHr3746+>7)*BjRS;_4g@$oGj-hp>Cc_){5=EYtp$Oai*UEYm%_x1!?N^(y? z%bS=z>MU8Vx6AwRzTVCPUR;m&SHRB&ANiRsAIQ)0`Zi$gjlnK2;X}NO8?XXnn9GMF z{a6E**>I%GM@e1>=9g7fl`ovvf7YBivhHY?kKtpzuNtssnd8t!Q)K1@mrqn(IUS|Y zl_sC;6=bjm=@4+U3WP3NSnlwte45G2T|S*%T)pX$6z|aG+;k+dGhIH5pX)8pU>$Sj zxO^@@7qU<#MR0n>!nqJxK25eV-{lMVLhoP(Ys;#<_)M0YdLAQd#=^2`v&!dH$s*^w zdhjfmjdy((D-Ybx?=bnDF29Q>-qMh@ux{t;Tz)sd$7|h?WrUzH z_{?mH*Ln;Tx>2Scbm?Zk0Ss)>7yspr4)ELg!!CbBVy(>sQ)R|uF5k!=U=)v`IcC

h&4A}boopCWv^!=)_cgS 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 bQ;H|!;@fJD*UX6N0e<{D;IPUqlj45>Ylo`I diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ImageTagBufferedImage.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ImageTagBufferedImage.java new file mode 100644 index 000000000..cbdc9c534 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ImageTagBufferedImage.java @@ -0,0 +1,328 @@ +package com.jpexs.decompiler.flash.exporters; + +import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.tags.enums.ImageFormat; +import com.jpexs.helpers.Helper; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.Image; +import java.awt.ImageCapabilities; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ImageObserver; +import java.awt.image.ImageProducer; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.SampleModel; +import java.awt.image.TileObserver; +import java.awt.image.WritableRaster; +import java.util.Vector; + +/** + * BufferedImage that has link to ImageTag. This is mainly used for PDF export coupling. + * @author JPEXS + */ +public class ImageTagBufferedImage extends BufferedImage { + private byte[] imageData; + private ImageTag tag; + private final BufferedImage image; + + public ImageTagBufferedImage(ImageTag tag, BufferedImage image) { + super(1, 1, TYPE_INT_RGB); + this.image = image; + this.tag = tag; + } + + public int getImageId() { + return tag.getCharacterId(); + } + + public boolean isJpeg () { + return tag.getImageFormat() == ImageFormat.JPEG; + } + + public byte[] getImageData() { + if (imageData != null) { + return imageData; + } + imageData = Helper.readStream(tag.getOriginalImageData()); + return imageData; + } + + @Override + public int getTransparency() { + return image.getTransparency(); + } + + @Override + public void releaseWritableTile(int tileX, int tileY) { + image.releaseWritableTile(tileX, tileY); + } + + @Override + public WritableRaster getWritableTile(int tileX, int tileY) { + return image.getWritableTile(tileX, tileY); + } + + @Override + public boolean hasTileWriters() { + return image.hasTileWriters(); + } + + @Override + public Point[] getWritableTileIndices() { + return image.getWritableTileIndices(); + } + + @Override + public boolean isTileWritable(int tileX, int tileY) { + return image.isTileWritable(tileX, tileY); + } + + @Override + public void removeTileObserver(TileObserver to) { + image.removeTileObserver(to); + } + + @Override + public void addTileObserver(TileObserver to) { + image.addTileObserver(to); + } + + @Override + public void setData(Raster r) { + image.setData(r); + } + + @Override + public WritableRaster copyData(WritableRaster outRaster) { + return image.copyData(outRaster); + } + + @Override + public Raster getData(Rectangle rect) { + return image.getData(rect); + } + + @Override + public Raster getData() { + return image.getData(); + } + + @Override + public Raster getTile(int tileX, int tileY) { + return image.getTile(tileX, tileY); + } + + @Override + public int getTileGridYOffset() { + return image.getTileGridXOffset(); + } + + @Override + public int getTileGridXOffset() { + return image.getTileGridXOffset(); + } + + @Override + public int getTileHeight() { + return image.getTileHeight(); + } + + @Override + public int getTileWidth() { + return image.getTileWidth(); + } + + @Override + public int getMinTileY() { + return image.getMinTileY(); + } + + @Override + public int getMinTileX() { + return image.getMinTileX(); + } + + @Override + public int getNumYTiles() { + return image.getNumYTiles(); + } + + @Override + public int getNumXTiles() { + return image.getNumXTiles(); + } + + @Override + public SampleModel getSampleModel() { + return image.getSampleModel(); + } + + @Override + public int getMinY() { + return image.getMinY(); + } + + @Override + public int getMinX() { + return image.getMinX(); + } + + @Override + public String[] getPropertyNames() { + return image.getPropertyNames(); + } + + @Override + public Vector getSources() { + return image.getSources(); + } + + @Override + public String toString() { + return image.toString(); + } + + @Override + public void coerceData(boolean isAlphaPremultiplied) { + image.coerceData(isAlphaPremultiplied); + } + + @Override + public boolean isAlphaPremultiplied() { + return image.isAlphaPremultiplied(); + } + + @Override + public BufferedImage getSubimage(int x, int y, int w, int h) { + //?? FIXME ?? + return image.getSubimage(x, y, w, h); + } + + @Override + public Graphics2D createGraphics() { + return image.createGraphics(); + } + + @Override + public Graphics getGraphics() { + return image.getGraphics(); + } + + @Override + public Object getProperty(String name) { + return image.getProperty(name); + } + + @Override + public Object getProperty(String name, ImageObserver observer) { + return image.getProperty(name, observer); + } + + @Override + public ImageProducer getSource() { + return image.getSource(); + } + + @Override + public int getHeight(ImageObserver observer) { + return image.getHeight(observer); + } + + @Override + public int getWidth(ImageObserver observer) { + return image.getWidth(observer); + } + + @Override + public int getHeight() { + return image.getHeight(); + } + + @Override + public int getWidth() { + return image.getWidth(); + } + + @Override + public void setRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize) { + image.setRGB(startX, startY, w, h, rgbArray, offset, scansize); + } + + @Override + public void setRGB(int x, int y, int rgb) { + image.setRGB(x, y, rgb); + } + + @Override + public int[] getRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize) { + return image.getRGB(startX, startY, w, h, rgbArray, offset, scansize); + } + + @Override + public int getRGB(int x, int y) { + return image.getRGB(x, y); + } + + @Override + public WritableRaster getAlphaRaster() { + return image.getAlphaRaster(); + } + + @Override + public WritableRaster getRaster() { + return image.getRaster(); + } + + @Override + public ColorModel getColorModel() { + return image.getColorModel(); + } + + @Override + public int getType() { + return image.getType(); + } + + @Override + public float getAccelerationPriority() { + return image.getAccelerationPriority(); + } + + @Override + public void setAccelerationPriority(float priority) { + image.setAccelerationPriority(priority); + } + + @Override + public ImageCapabilities getCapabilities(GraphicsConfiguration gc) { + return image.getCapabilities(gc); + } + + @Override + public void flush() { + image.flush(); + } + + @Override + public Image getScaledInstance(int width, int height, int hints) { + return image.getScaledInstance(width, height, hints); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ImageTagBufferedImage) { + return tag.getCharacterId() == ((ImageTagBufferedImage) obj).tag.getCharacterId(); + } + return image.equals(obj); + } + + @Override + public int hashCode() { + return tag.getCharacterId(); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/BitmapExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/BitmapExporter.java index 546fd7cce..4b0a705ea 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/BitmapExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/BitmapExporter.java @@ -17,6 +17,7 @@ package com.jpexs.decompiler.flash.exporters.shape; import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.exporters.ImageTagBufferedImage; import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle; import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; import com.jpexs.decompiler.flash.tags.base.ImageTag; @@ -46,6 +47,7 @@ import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; +import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; @@ -363,8 +365,12 @@ public class BitmapExporter extends ShapeExporterBase { if (colorTransform != null) { img = colorTransform.apply(img); } - - fillPaint = new TexturePaint(img.getBufferedImage(), new java.awt.Rectangle(img.getWidth(), img.getHeight())); + BufferedImage bufImg = img.getBufferedImage(); + if (colorTransform == null) { + bufImg = new ImageTagBufferedImage(imageTag, bufImg); + } + fillPaint = new TexturePaint(bufImg, new java.awt.Rectangle(img.getWidth(), img.getHeight())); + fillTransform = matrix.toTransform(); fillRepeat = repeat; fillSmooth = smooth; @@ -670,7 +676,7 @@ public class BitmapExporter extends ShapeExporterBase { double minY = rect.yMin; graphics.fill(new Rectangle((int) minX, (int) minY, (int) (rect.xMax - minX), (int) (rect.yMax - minY))); } - } else { + } else { graphics.drawImage(((TexturePaint) fillPaint).getImage(), 0, 0, null); } graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, interpolationBefore); diff --git a/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java b/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java index 9098bac54..a2b322f21 100644 --- a/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java +++ b/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java @@ -1 +1 @@ -/* * $Id: PDFGraphics.java,v 1.6 2007/09/22 12:58:40 gil1 Exp $ * * $Date: 2007/09/22 12:58:40 $ * * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package gnu.jpdf; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Composite; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Frame; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.Image; import java.awt.LinearGradientPaint; import java.awt.MultipleGradientPaint; import java.awt.Paint; import java.awt.Polygon; import java.awt.RadialGradientPaint; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.RenderingHints.Key; import java.awt.Shape; import java.awt.Stroke; import java.awt.TexturePaint; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.Ellipse2D; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ColorModel; import java.awt.image.ImageObserver; import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import java.awt.image.renderable.RenderableImage; import java.awt.print.PageFormat; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.io.Serializable; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.ArrayList; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; /** * This class is our implementation of AWT's Graphics class. It provides a Java * standard way of rendering into a PDF Document's Page. * * @author Peter T Mount, http://www.retep.org.uk/pdf/ * @author Eric Z. Beard, ericzbeard@hotmail.com * @author Gilbert DeLeeuw, gil1@users.sourceforge.net * @version $Revision: 1.6 $, $Date: 2007/09/22 12:58:40 $ * @see gnu.jpdf.PDFGraphics */ public class PDFGraphics extends Graphics2D implements Serializable { /** * One degree in radians */ private static final double degrees_to_radians = Math.PI / 180.0; private static final int FILL = 1; private static final int STROKE = 2; private static final int CLIP = 3; private static final AffineTransform IDENTITY = new AffineTransform(); private static final Stroke DEF_STROKE = new BasicStroke(); /* * NOTE: The original class is the work of Peter T. Mount, who released it * in the uk.org.retep.pdf package. It was modified by Eric Z. Beard as * follows: * The package name was changed to gnu.pdf. * The formatting was changed a little bit. * This used to subclass an abstract class in a different package with * the same name (confusing). Now it's one concrete class. * drawImage() was implemented * It is still licensed under the LGPL. */ // Implementation notes: // // Pages 333-335 of the PDF Reference Manual // // Unless absolutely required, use the moveto, lineto and rectangle // operators to perform those actions. // They contain some extra optimizations // which will reduce the output size by up to half in some cases. // // About fill operators: For correct operation, any fill operation should // start with closeBlock(), which will ensure any previous path is completed, // otherwise you may find the fill will include previous items private static final DecimalFormat df = new DecimalFormat("#.###", new DecimalFormatSymbols(Locale.ENGLISH)); private static final DecimalFormat matDf = new DecimalFormat("0", new DecimalFormatSymbols(Locale.ENGLISH)); static { matDf.setMaximumFractionDigits(340); } //JPEXS: cache for already used images private static Map usedImages = new WeakHashMap(); private Color background; /** * This is true for any Graphics instance that didn't create the stream. * * @see #create */ private boolean child; private Area clip; private AffineTransform clipTransform; /** * This holds the current clipRectangle */ protected Rectangle clipRectangle; private Composite composite; private Graphics2D dg2 = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB).createGraphics(); /** * This is the current font (in Java format) */ private Font font; /** * Part of the optimizer: When true, we are drawing a path. */ private boolean inStroke; /** * Part of the optimizer: When true, we are within a Text Block. */ private boolean inText; // true if within a Text Block - see newTextBlock() /** * The stroke line cap code; */ private int lineCap = 0; /** * The stroke line join code */ private int lineJoin = 0; /** * The stroke line width */ private float lineWidth = 1.0f; /** * Part of the optimizer: The last known moveto/lineto x coordinate * * @see #moveto * @see #lineto */ private float lx; // last known moveto/lineto coordinates /** * Part of the optimizer: The last known moveto/lineto y coordinate * * @see #moveto * @see #lineto */ private float ly; // last known moveto/lineto coordinates private float miterLimit = 10.0f; /** * Part of the optimizer: When true, the font has changed. */ private boolean newFont; // true if the font changes - see newTextBlock() /** * This is a reference to the PDFPage we are rendering to. */ private PDFPage page; /** * This is the current pen/fill color */ private Paint paint; private AffineTransform paintTransform; /** * This is the current font (in PDF format) */ private PDFFont pdffont; /** * Part of the optimizer: This is written to the stream when the newPath() * is called. np then clears this value. */ private String pre_np; // PDF space transform private AffineTransform pTransform; /** * This is the PrintWriter used to write PDF drawing commands to the Stream */ private RawPrintWriter pw; /** * RenderingHints */ private RenderingHints rhints = new RenderingHints(null); private Stroke stroke; // Start of Graphics2D properties private AffineTransform transform; /** * Part of the optimizer: The last x coordinate when rendering text */ private float tx; // the last coordinate for text rendering /** * Part of the optimizer: The last y coordinate when rendering text */ private float ty; // the last coordinate for text rendering private String shading = null; private String pattern = null; private Set usedAlphas = new HashSet<>(); private Set gsBlendModes = new HashSet<>(); private int currentAlpha = 255; private String blendMode; private int shadingCount = 0; private int objId = 0; private boolean usePTransform = true; private static int[] srgbToLinear = new int[256]; private static int[] linearToSrgb = new int[256]; static { for (int i = 0; i < 256; i++) { srgbToLinear[i] = convertSRGBtoLinearRGB(i); linearToSrgb[i] = convertLinearRGBtoSRGB(i); } } private static int convertSRGBtoLinearRGB(int color) { float input, output; input = color / 255.0f; if (input <= 0.04045f) { output = input / 12.92f; } else { output = (float) Math.pow((input + 0.055) / 1.055, 2.4); } return Math.round(output * 255.0f); } private static int convertLinearRGBtoSRGB(int color) { float input, output; input = color / 255.0f; if (input <= 0.0031308) { output = input * 12.92f; } else { output = (1.055f * ((float) Math.pow(input, (1.0 / 2.4)))) - 0.055f; } return Math.round(output * 255.0f); } /** * @see Graphics2D#addRenderingHints(Map) */ @Override public void addRenderingHints(Map hints) { rhints.putAll(hints); } /** * This produces an arc by breaking it down into one or more Bezier curves. * It is used internally to implement the drawArc and fillArc methods. * * @param axc X coordinate of arc centre * @param ayc Y coordinate of arc centre * @param width of bounding rectangle * @param height of bounding rectangle * @param ang1 Start angle * @param ang2 End angle * @param clockwise true to draw clockwise, false anti-clockwise */ public void arc(double axc, double ayc, double width, double height, double ang1, double ang2, boolean clockwise) { double adiff; double x0, y0; double x3r, y3r; boolean first = true; // may not need this //if( ar < 0 ) { //ang1 += fixed_180; //ang2 += fixed_180; //ar = - ar; //} double ang1r = (ang1 % 360.0) * degrees_to_radians; double sin0 = Math.sin(ang1r); double cos0 = Math.cos(ang1r); x0 = axc + width * cos0; y0 = ayc + height * sin0; // NB: !clockwise here as Java Space is inverted to User Space if (!clockwise) { // Quadrant reduction while (ang1 < ang2) { ang2 -= 360.0; } while ((adiff = ang2 - ang1) < -90.0) { double w = sin0; sin0 = -cos0; cos0 = w; x3r = axc + width * cos0; y3r = ayc + height * sin0; arc_add(first, width, height, x0, y0, x3r, y3r, (x0 + width * cos0), (y0 + height * sin0) ); x0 = x3r; y0 = y3r; ang1 -= 90.0; first = false; } } else { // Quadrant reduction while (ang2 < ang1) { ang2 += 360.0; } while ((adiff = ang2 - ang1) > 90.0) { double w = cos0; cos0 = -sin0; sin0 = w; x3r = axc + width * cos0; y3r = ayc + height * sin0; arc_add(first, width, height, x0, y0, x3r, y3r, (x0 + width * cos0), (y0 + height * sin0) ); x0 = x3r; y0 = y3r; ang1 += 90.0; first = false; } } // Compute the intersection of the tangents. // We know that -fixed_90 <= adiff <= fixed_90. double trad = Math.tan(adiff * (degrees_to_radians / 2)); double ang2r = ang2 * degrees_to_radians; double xt = x0 - trad * width * sin0; double yt = y0 + trad * height * cos0; arc_add(first, width, height, x0, y0, (axc + width * Math.cos(ang2r)), (ayc + height * Math.sin(ang2r)), xt, yt); } /** * Used by the arc method to actually add an arc to the path Important: We * write directly to the stream here, because this method operates in User * space, rather than Java space. * * @param first true if the first arc * @param w width * @param h height * @param x0 coordinate * @param y0 coordinate * @param x3 coordinate * @param y3 coordinate * @param xt coordinate * @param yt coordinate */ private void arc_add(boolean first, double w, double h, double x0, double y0, double x3, double y3, double xt, double yt) { double dx = xt - x0, dy = yt - y0; double dist = dx * dx + dy * dy; double w2 = w * w, h2 = h * h; double r2 = w2 + h2; double fw = 0.0, fh = 0.0; if (dist < (r2 * 1.0e8)) { // JM fw = (w2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / w2))) : 0.0; fh = (h2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / h2))) : 0.0; } // The path must have a starting point if (first) { moveto(x0, y0); } double x = x0 + ((xt - x0) * fw); double y = y0 + ((yt - y0) * fh); x0 = x3 + ((xt - x3) * fw); y0 = y3 + ((yt - y3) * fh); // Finally the actual curve. curveto(x, y, x0, y0, x3, y3); } /** * This simply draws a White Rectangle to clear the area * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void clearRect(int x, int y, int w, int h) { closeBlock(); pw.print("q 1 1 1 RG ");// save state, set colour to White drawRect(x, y, w, h); closeBlock("B Q"); // close fill & stroke, then restore state } /** * @see Graphics2D#clip(Shape) */ @Override public void clip(Shape s) { if (s == null) { setClip(null); return; } Area newClip; if (clip == null) { newClip = new Area(s); } else { newClip = (Area) clip.clone(); newClip.intersect(new Area(s)); } setClip(newClip); } /** * This extra method allows PDF users to clip to a Polygon. * *

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

* Not implemented

* * @param data a 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; } /** *

* 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); Object interpolationHint = getRenderingHint(RenderingHints.KEY_INTERPOLATION); boolean interpolate = interpolationHint == RenderingHints.VALUE_INTERPOLATION_BILINEAR || interpolationHint == RenderingHints.VALUE_INTERPOLATION_BICUBIC; image = new PDFImage(img, x, y, w, h, obs, "" + mask.getSerialID() + " 0 R", interpolate); // 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"); initAlpha(255); // 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 properties = new Hashtable(); String[] keys = img.getPropertyNames(); if (keys != null) { for (int i = 0; i < keys.length; i++) { properties.put(keys[i], img.getProperty(keys[i])); } } BufferedImage result = new BufferedImage(cm, raster, isAlphaPremultiplied, properties); img.copyData(raster); image = result; } drawImage(image, xform, null); } /** * 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 drawRoundRect(int x, int y, int w, int h, int aw, int ah) { } //============ Oval operations ======================= /** * 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, 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 an 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. /** *

* 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(null /*FIXME!!!*/); polygon(xp, yp, np); if (shadingFill(null)) { 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(Shape s) { 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, s); paintTransform = transform; } } pw.println("/Pattern cs"); pw.println(pattern + " scn"); } } private boolean shadingFill(Shape s) { if (pattern == null && shading != null) { saveState(); pw.println("W n"); if (paint instanceof MultipleGradientPaint) { if (shading.equals("gradient") || !paintTransform.equals(transform)) { initGradientPaint((MultipleGradientPaint) paint, s); 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(s); } 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, s); 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() { if (clip == null) { return null; } return clip; } /** * 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 uncompressesd 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 = usePTransform ? new AffineTransform(pTransform) : new AffineTransform(); 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(); AffineTransform currentTransform = transform; transform = clipTransform; setTransform(currentTransform); } 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"); } } private void initAlpha(int alpha) { if (currentAlpha != alpha || blendMode != null) { String gsId = "/GSAlpha" + alpha; currentAlpha = alpha; if (!usedAlphas.contains(alpha)) { page.addExtGStateResource(gsId + " <>"); usedAlphas.add(currentAlpha); } pw.println(gsId + " gs"); } } public void setBlendMode(String mode) { if (currentAlpha < 0) { currentAlpha = 255; } String gsName = "/GSBlend" + mode + "Alpha" + currentAlpha; if (!gsBlendModes.contains(gsName)) { page.addExtGStateResource(gsName + " <>"); } pw.println(gsName + " gs"); this.blendMode = mode; } /** * 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 initAlpha(c.getAlpha()); // 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 String generateRadialFunctionBody(RadialGradientPaint radGrad, boolean alpha) { double a = ((radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) * (radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) + (radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) * (radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) - radGrad.getRadius() * radGrad.getRadius()); String functionBody = "{\n" + matDf.format(radGrad.getFocusPoint().getX()) + " 2 index sub\n" + matDf.format(radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) + " mul 2 mul\n" //stack size: 3 + matDf.format(radGrad.getFocusPoint().getY()) + " 2 index sub\n" + matDf.format(radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) + " mul 2 mul\n" + "add\n" //b, stack size: 3 + matDf.format(radGrad.getFocusPoint().getX()) + " 3 index sub\n" + "dup mul\n" + matDf.format(radGrad.getFocusPoint().getY()) + " 3 index sub\n" + "dup mul\n" + "add\n" //c, stack size: 4 + "1 index dup mul 4 " + matDf.format(a) + " mul 2 index mul sub\n" //D, stack size: 4 + "0 index 0 lt\n" + "{\n" + "1\n" + "}" + "{" + "0 index 0 gt\n" + "{\n" + "2 index neg 1 index sqrt add 2 " + matDf.format(a) + " mul div\n" // x1, stack size: 5 + "3 index neg 2 index sqrt sub 2 " + matDf.format(a) + " mul div\n" // x2, stack size: 6 + "0 index 2 index gt{0 index} {1 index} ifelse\n" + "exch pop exch pop\n" //x, stack size 5 + "}" + "{\n" + "2 index neg 2 " + matDf.format(a) + " mul div\n" // x, stack size 5 + "} ifelse\n" + "} ifelse\n" + "exch pop exch pop exch pop exch pop exch pop\n"; //remove index0,1,2,3,4 if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT) { functionBody += "dup\n"; } if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE) { functionBody += "dup 1 gt {pop 1} if\n"; } else { functionBody += "dup 1 gt {dup floor sub} if\n"; } if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT) { functionBody += "exch floor 2 mod 1 eq {" + "neg 1 add" + "}\n" + "if\n"; } int num = radGrad.getFractions().length; Color[] rcolors = convertColorSpace(radGrad.getColors(), radGrad.getColorSpace()); for (int i = 0; i < num - 1; i++) { functionBody += "dup " + radGrad.getFractions()[i] + " lt not 1 index " + radGrad.getFractions()[i + 1] + " gt not and\n{\n" + "0 index " + radGrad.getFractions()[i] + " sub " + (radGrad.getFractions()[i + 1] - radGrad.getFractions()[i]) + " div\n"; if (alpha) { functionBody += "0 index " + ((rcolors[i + 1].getAlpha() - rcolors[i].getAlpha()) / 255.0) + " mul " + (rcolors[i].getAlpha() / 255.0) + " add\n"; functionBody += "dup dup\n"; } else { if (radGrad.getColorSpace() == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) { functionBody += "0 index " + ((srgbToLinear[rcolors[i + 1].getRed()] - srgbToLinear[rcolors[i].getRed()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getRed()] / 255.0) + " add\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "1 index " + ((srgbToLinear[rcolors[i + 1].getGreen()] - srgbToLinear[rcolors[i].getGreen()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getGreen()] / 255.0) + " add\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "2 index " + ((srgbToLinear[rcolors[i + 1].getBlue()] - srgbToLinear[rcolors[i].getBlue()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getBlue()] / 255.0) + " add\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n"; } else { functionBody += "0 index " + ((rcolors[i + 1].getRed() - rcolors[i].getRed()) / 255.0) + " mul " + (rcolors[i].getRed() / 255.0) + " add\n" + "1 index " + ((rcolors[i + 1].getGreen() - rcolors[i].getGreen()) / 255.0) + " mul " + (rcolors[i].getGreen() / 255.0) + " add\n" + "2 index " + ((rcolors[i + 1].getBlue() - rcolors[i].getBlue()) / 255.0) + " mul " + (rcolors[i].getBlue() / 255.0) + " add\n"; } } if (i < num - 2) { functionBody += "}\n{\n"; } } functionBody += "}if\n"; for (int i = 0; i < num - 2; i++) { functionBody += "}ifelse\n"; } functionBody += "}\n"; return functionBody; } 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; } List functions2Refs = new ArrayList<>(); Color[] colors = convertColorSpace(grad.getColors(), grad.getColorSpace()); for (int i = 1; i < grad.getColors().length; i++) { final Color color1 = colors[i - 1]; final Color color2 = colors[i]; final MultipleGradientPaint.ColorSpaceType colorSpace = grad.getColorSpace(); if (colorSpace == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) { PDFStream function4 = new PDFStream(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4 /Domain [0 1] /Range [0 1 0 1 0 1]".getBytes("UTF-8")); writeStream(os); } }; OutputStream f4Os = function4.getOutputStream(); int redDelta = srgbToLinear[color2.getRed()] - srgbToLinear[color1.getRed()]; int greenDelta = srgbToLinear[color2.getGreen()] - srgbToLinear[color1.getGreen()]; int blueDelta = srgbToLinear[color2.getBlue()] - srgbToLinear[color1.getBlue()]; String functionBody = "{" + "0 index " + redDelta + " mul " + srgbToLinear[color1.getRed()] + " add 255 div\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "1 index " + greenDelta + " mul " + srgbToLinear[color1.getGreen()] + " add 255 div\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "2 index " + blueDelta + " mul " + srgbToLinear[color1.getBlue()] + " add 255 div\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "}"; try { f4Os.write(functionBody.getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(function4); functions2Refs.add(function4.getSerialID() + " 0 R"); } else { // PDFObject function2 = new PDFObject(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write(("/FunctionType 2 /Domain [0 1] /C0 [" + (((float) color1.getRed()) / 255.0f) + " " + (((float) color1.getGreen()) / 255.0f) + " " + (((float) color1.getBlue()) / 255.0f) + "] /C1 [" + (((float) color2.getRed()) / 255.0f) + " " + (((float) color2.getGreen()) / 255.0f) + " " + (((float) color2.getBlue()) / 255.0f) + "] /N 1\n").getBytes("UTF-8")); writeEnd(os); } }; page.getPDFDocument().add(function2); functions2Refs.add(function2.getSerialID() + " 0 R"); } } List functions2AlphaRefs = new ArrayList<>(); Color[] alphaColors = grad.getColors(); for (int i = 1; i < grad.getColors().length; i++) { final Color color1 = alphaColors[i - 1]; final Color color2 = alphaColors[i]; PDFObject function2 = new PDFObject(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write(("/FunctionType 2 /Domain [0 1] /C0 [" + (((float) color1.getAlpha()) / 255.0f) + " " + (((float) color1.getAlpha()) / 255.0f) + " " + (((float) color1.getAlpha()) / 255.0f) + "] /C1 [" + (((float) color2.getAlpha()) / 255.0f) + " " + (((float) color2.getAlpha()) / 255.0f) + " " + (((float) color2.getAlpha()) / 255.0f) + "] /N 1\n").getBytes("UTF-8")); writeEnd(os); } }; page.getPDFDocument().add(function2); functions2AlphaRefs.add(function2.getSerialID() + " 0 R"); } final MultipleGradientPaint fgrad = grad; PDFObject function3 = new PDFGradientFunction3(fgrad, functions2Refs); page.getPDFDocument().add(function3); PDFObject function3Alpha = new PDFGradientFunction3(fgrad, functions2AlphaRefs); page.getPDFDocument().add(function3Alpha); double glen = 0; double divisor = 1; double maxlen = 256; if ((fgrad instanceof LinearGradientPaint) && (fgrad.getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE)) { LinearGradientPaint linGrad = (LinearGradientPaint) fgrad; Point2D startPoint = new Point2D.Double(); Point2D endPoint = new Point2D.Double(); startPoint = linGrad.getStartPoint(); endPoint = linGrad.getEndPoint(); double deltaX = endPoint.getX() - startPoint.getX(); double deltaY = endPoint.getY() - startPoint.getY(); glen = Math.sqrt(deltaX * deltaX + deltaY * deltaY); if (glen > maxlen) { divisor = glen / maxlen; glen = maxlen; } } final double flen = glen; PDFStream radialFunction; PDFStream radialAlphaFunction; if (useFunctionShading(fgrad)) { RadialGradientPaint radGrad = (RadialGradientPaint) fgrad; radialFunction = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4\n".getBytes("UTF-8")); //pdf reference, page 168 os.write("/Domain [-1000000 1000000 -1000000 1000000]\n".getBytes("UTF-8")); os.write("/Range [0 1 0 1 0 1]\n".getBytes("UTF-8")); //3 - R,G,B writeStream(os); } }; radialAlphaFunction = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4\n".getBytes("UTF-8")); //pdf reference, page 168 os.write("/Domain [-1000000 1000000 -1000000 1000000]\n".getBytes("UTF-8")); os.write("/Range [0 1 0 1 0 1]\n".getBytes("UTF-8")); //3 - R,G,B writeStream(os); } }; //PDF reference, page 176 /*double b = 2 * (focalX - x) * (centerX - focalX) + 2 * (focalY - y) * (centerY - focalY); double c = (focalX - x) * (focalX - x) + (focalY - y) * (focalY - y); double D = b * b - 4 * a * c;*/ //(-b + Math.sqrt(D)) / (2 * a) //D = b * b - 4 * a * c; String functionBody = generateRadialFunctionBody(radGrad, false); OutputStream funOs = radialFunction.getOutputStream(); try { funOs.write(functionBody.getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } String alphaFunctionBody = generateRadialFunctionBody(radGrad, true); funOs = radialAlphaFunction.getOutputStream(); try { funOs.write(alphaFunctionBody.getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(radialFunction); page.getPDFDocument().add(radialAlphaFunction); } else { radialFunction = null; radialAlphaFunction = null; } PDFObject shadingObj = new PdfGradientShading(fgrad, flen, useFunctionShading(fgrad), function3, radialFunction); page.getPDFDocument().add(shadingObj); PDFObject shadingAlphaObj = new PdfGradientShading(fgrad, flen, useFunctionShading(fgrad), function3Alpha, radialAlphaFunction); page.getPDFDocument().add(shadingAlphaObj); shadingCount++; final int fCurrentShadingCount = shadingCount; PDFStream alphaObject = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/Group << /CS /DeviceGray /S /Transparency >>\n".getBytes("UTF-8")); os.write("/Type /XObject\n".getBytes("UTF-8")); os.write("/Resources <<\n".getBytes("UTF-8")); os.write("/Shading <<".getBytes("UTF-8")); os.write(("/ShA" + fCurrentShadingCount + " " + shadingAlphaObj.getSerialID() + " 0 R").getBytes("UTF-8")); os.write(">>\n".getBytes("UTF-8")); //shading os.write(">>\n".getBytes("UTF-8")); //resources os.write("/Subtype /Form\n".getBytes("UTF-8")); os.write("/BBox [-1000000 -1000000 2000000 2000000]\n".getBytes("UTF-8")); //fixme writeStream(os); } }; OutputStream alphaOs = alphaObject.getOutputStream(); try { alphaOs.write(("/ShA" + fCurrentShadingCount + " sh\n").getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(alphaObject); String alphaExtGState = "/GradAlpha" + fCurrentShadingCount + " <<" + "/SMask <<" + "/Type /Mask\n" + "/S /Luminosity\n" + "/G " + alphaObject.getSerialID() + " 0 R" + ">>" + ">>"; if ((grad instanceof LinearGradientPaint) && ((LinearGradientPaint) grad).getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE) { LinearGradientPaint linGrad = (LinearGradientPaint) grad; Point2D startPoint = linGrad.getStartPoint(); Point2D endPoint = linGrad.getEndPoint(); Point2D startPointTrans = new Point2D.Double(); Point2D endPointTrans = new Point2D.Double(); transform.transform(linGrad.getStartPoint(), startPointTrans); transform.transform(linGrad.getEndPoint(), endPointTrans); double deltaX = endPoint.getX() - startPoint.getX(); double deltaY = endPoint.getY() - startPoint.getY(); double tana = deltaX / deltaY; double alfa = Math.atan(tana); AffineTransform m = new AffineTransform(); if (usePTransform) { m.concatenate(pTransform); } m.concatenate(transform); m.concatenate(AffineTransform.getTranslateInstance(startPoint.getX(), endPoint.getY())); m.concatenate(AffineTransform.getScaleInstance(divisor, divisor)); m.concatenate(AffineTransform.getRotateInstance(-alfa)); String matrixStr = "" + matDf.format(m.getScaleX()) + " " + matDf.format(m.getShearY()) + " " + matDf.format(m.getShearX()) + " " + matDf.format(m.getScaleY()) + " " + matDf.format(m.getTranslateX()) + " " + matDf.format(m.getTranslateY()); PDFStream innerPattern = new PDFStream("/Pattern") { @Override public void write(OutputStream os) throws IOException { writeStart(os); double w; double h; w = 1; h = flen; os.write("/PatternType 1\n".getBytes("UTF-8")); os.write("/PaintType 1\n".getBytes("UTF-8")); os.write("/TilingType 2\n".getBytes("UTF-8")); os.write(("/BBox [0 0 " + w + " " + h + "]\n").getBytes("UTF-8")); os.write(("/XStep " + w + "\n").getBytes("UTF-8")); os.write(("/YStep " + h + "\n").getBytes("UTF-8")); os.write(("/Resources << " + "/Shading << /Shin" + fCurrentShadingCount + " " + shadingObj.getSerialID() + " 0 R >>" + "/ExtGState <<" + alphaExtGState + ">>" + ">>\n").getBytes("UTF-8")); os.write(("/Matrix [" + matrixStr + "]\n").getBytes("UTF-8")); writeStream(os); } }; OutputStream patOs = innerPattern.getOutputStream(); try { patOs.write(("/GradAlpha" + fCurrentShadingCount + " gs").getBytes("UTF-8")); patOs.write(("/Shin" + shadingCount + " sh").getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(innerPattern); page.addPatternResource("/p" + shadingCount + " " + innerPattern.getSerialID() + " 0 R"); this.pattern = "/p" + shadingCount; return; } currentAlpha = -1; page.addExtGStateResource(alphaExtGState); pw.println("/GradAlpha" + fCurrentShadingCount + " gs"); page.addShadingResource("/Sh" + shadingCount + " " + shadingObj.getSerialID() + " 0 R "); this.shading = "/Sh" + shadingCount; } private void initTexturePaint(TexturePaint texturePaint) { BufferedImage img = texturePaint.getImage(); PDFMask mask = new PDFMask(img); page.getPDFDocument().add(mask); Rectangle2D anchorRect = texturePaint.getAnchorRect(); final double w = anchorRect.getWidth(); final double h = anchorRect.getHeight(); Object interpolationHint = getRenderingHint(RenderingHints.KEY_INTERPOLATION); boolean interpolate = interpolationHint == RenderingHints.VALUE_INTERPOLATION_BILINEAR || interpolationHint == RenderingHints.VALUE_INTERPOLATION_BICUBIC; PDFImage image = new PDFImage(img, 0, 0, img.getWidth(), img.getHeight(), new ImageObserver() { @Override public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { return true; } }, "" + mask.getSerialID() + " 0 R", interpolate); // The image needs to be registered in several places page.getPDFDocument().setImageName(image); page.getPDFDocument().add(image); usedImages.put(img, image); AffineTransform m = new AffineTransform(); AffineTransform ptt = new AffineTransform(); if (usePTransform) { ptt.concatenate(pTransform); } ptt.concatenate(transform); m.concatenate(ptt); String matrixStr = "" + matDf.format(m.getScaleX()) + " " + matDf.format(m.getShearY()) + " " + matDf.format(m.getShearX()) + " " + matDf.format(m.getScaleY()) + " " + matDf.format(m.getTranslateX()) + " " + matDf.format(m.getTranslateY()); PDFStream innerPattern = new PDFStream("/Pattern") { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/PatternType 1\n".getBytes("UTF-8")); os.write("/PaintType 1\n".getBytes("UTF-8")); os.write("/TilingType 2\n".getBytes("UTF-8")); os.write(("/BBox [0 0 " + matDf.format(w) + " " + matDf.format(h) + "]\n").getBytes("UTF-8")); os.write(("/XStep " + matDf.format(w) + "\n").getBytes("UTF-8")); os.write(("/YStep " + matDf.format(h) + "\n").getBytes("UTF-8")); os.write(("/Resources << ").getBytes("UTF-8")); os.write("/XObject << ".getBytes("UTF-8")); os.write((image.getName() + " " + image.getSerialID() + " 0 R").getBytes("UTF-8")); os.write(" >> ".getBytes("UTF-8")); os.write((">>\n").getBytes("UTF-8")); //"1 0 0 1 0 0" os.write(("/Matrix [" + matrixStr + "]\n").getBytes("UTF-8")); writeStream(os); } }; OutputStream patOs = innerPattern.getOutputStream(); PrintWriter patwriter = new PrintWriter(patOs); AffineTransform transformToSet; transformToSet = new AffineTransform(w, 0, 0, -h, 0 - anchorRect.getX(), h - anchorRect.getY()); patwriter.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"); transformToSet = new AffineTransform(w, 0, 0, -h, w - anchorRect.getX(), h - anchorRect.getY()); patwriter.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"); transformToSet = new AffineTransform(w, 0, 0, -h, 0 - anchorRect.getX(), 2 * h - anchorRect.getY()); patwriter.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"); transformToSet = new AffineTransform(w, 0, 0, -h, w - anchorRect.getX(), 2 * h - anchorRect.getY()); patwriter.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"); patwriter.flush(); page.getPDFDocument().add(innerPattern); shadingCount++; page.addPatternResource("/p" + shadingCount + " " + innerPattern.getSerialID() + " 0 R"); this.pattern = "/p" + shadingCount; } /** * Not implemented, as this is not supported in the PDF specification. */ @Override public void setPaintMode() { } /** * Sets a rendering hint * * @param arg0 * @param arg1 */ @Override public void setRenderingHint(Key arg0, Object arg1) { if (arg1 != null) { rhints.put(arg0, arg1); } else { rhints.remove(arg0); } } // Add Graphics2D methods. /** * @see Graphics2D#setRenderingHints(Map) */ @Override public void setRenderingHints(Map hints) { rhints.clear(); rhints.putAll(hints); } /** * @see Graphics2D#setStroke(Stroke) */ @Override public void setStroke(Stroke s) { this.stroke = s; if (stroke instanceof BasicStroke) { BasicStroke bs = (BasicStroke) stroke; setLineCap(bs.getEndCap()); setLineJoin(bs.getLineJoin()); setLineWidth(bs.getLineWidth()); setMiterLimit(bs.getMiterLimit()); // TODO: Line dash pattern } } /** * @see Graphics2D#setTransform(AffineTransform) */ @Override public void setTransform(AffineTransform t) { setNewTranform(new AffineTransform(t)); } /** * Not implemented, as this is not supported in the PDF specification. * * @param c1 Color to xor with */ @Override public void setXORMode(Color c1) { } //============ Text operations ======================= /** * @see Graphics2D#shear(double, double) */ @Override public void shear(double shx, double shy) { AffineTransform newTransform = new AffineTransform(transform); newTransform.shear(shx, shy); setNewTranform(newTransform); } /** * @see Graphics2D#transform(AffineTransform) */ @Override public void transform(AffineTransform tx) { AffineTransform newTransform = new AffineTransform(transform); newTransform.concatenate(tx); setNewTranform(newTransform); } /** * @see Graphics2D#translate(double, double) */ @Override public void translate(double tx, double ty) { AffineTransform newTransform = new AffineTransform(transform); newTransform.translate(tx, ty); setNewTranform(newTransform); } /** * @see Graphics#translate(int, int) */ @Override public void translate(int x, int y) { translate((double) x, (double) y); } /** * Converts the Java space coordinates into pdf text space. * * @param x coordinate * @param y coordinate * @param tx coordinate * @param ty coordinate * @return String containing the coordinates in PDF text space */ private String twh(float x, float y, float tx, float ty) { float nx = x, ny = y; float ntx = tx, nty = ty; nx = (float) (x - tx); ny = (float) (y - ty); return "" + df.format(nx) + " " + df.format(ny) + " "; } /** * Converts the Java space coordinates into pdf text space. * * @param x coordinate * @param y coordinate * @return String containing the coordinates in PDF text space */ private String txy(float x, float y) { Point2D ptSrc = new Point2D.Float(x, y); Point2D ptDst = new Point2D.Float(); if (usePTransform) { pTransform.transform(ptSrc, ptDst); } return "" + df.format(ptDst.getX()) + " " + df.format(ptDst.getY()) + " "; } private void setNewTranform(AffineTransform t) { closeBlock(); if (true) { //return; } AffineTransform newTransform = new AffineTransform(t); AffineTransform transformToSet = new AffineTransform(newTransform); if (usePTransform) { if (transform != null) { AffineTransform aInv = new AffineTransform(transform); try { aInv.invert(); } catch (NoninvertibleTransformException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } AffineTransform pInv = new AffineTransform(pTransform); try { pInv.invert(); } catch (NoninvertibleTransformException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } transformToSet = new AffineTransform(); transformToSet.concatenate(aInv); transformToSet.concatenate(pInv); transformToSet.concatenate(pTransform); transformToSet.concatenate(newTransform); } else { transformToSet.preConcatenate(pTransform); } } if (clip != null) { AffineTransform inv = new AffineTransform(newTransform); try { inv.invert(); } catch (NoninvertibleTransformException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } clip = new Area(transform.createTransformedShape(inv.createTransformedShape(clip))); clipRectangle = clip.getBounds(); } transform = newTransform; pw.println("" + 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" ); } private void saveState() { pw.println("q"); } private void restoreState() { pw.println("Q"); } public void drawXObject(Graphics g) { if (g instanceof PDFGraphics) { int objId = ((PDFGraphics) g).objId; pw.println("/MyObj" + objId + " Do"); } } public Graphics2D createXObject() { final PDFPage newPage = new PDFPage(page.pageFormat) { @Override public void addToProcset(String proc) { page.addToProcset(proc); } }; newPage.pdfDocument = page.pdfDocument; PDFStream retObject = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/Type /XObject\n".getBytes("UTF-8")); newPage.writeResources(os); os.write("/Subtype /Form\n".getBytes("UTF-8")); os.write("/BBox [-1000000 -1000000 2000000 2000000]\n".getBytes("UTF-8")); //fixme writeStream(os); } }; page.getPDFDocument().add(retObject); OutputStream os = retObject.getOutputStream(); RawPrintWriter pw2 = new RawPrintWriter(os); page.addXObject("/MyObj" + retObject.getSerialID() + " " + retObject.getSerialID() + " 0 R"); PDFGraphics g = new PDFGraphics(); g.usePTransform = false; g.init(newPage, pw2); g.objId = retObject.getSerialID(); //g.setTransform(new AffineTransform()); return g; } } \ No newline at end of file +/* * $Id: PDFGraphics.java,v 1.6 2007/09/22 12:58:40 gil1 Exp $ * * $Date: 2007/09/22 12:58:40 $ * * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package gnu.jpdf; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Composite; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Frame; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.Image; import java.awt.LinearGradientPaint; import java.awt.MultipleGradientPaint; import java.awt.Paint; import java.awt.Polygon; import java.awt.RadialGradientPaint; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.RenderingHints.Key; import java.awt.Shape; import java.awt.Stroke; import java.awt.TexturePaint; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.Ellipse2D; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ColorModel; import java.awt.image.ImageObserver; import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import java.awt.image.renderable.RenderableImage; import java.awt.print.PageFormat; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.ArrayList; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; import jdk.internal.reflect.Reflection; /** * This class is our implementation of AWT's Graphics class. It provides a Java * standard way of rendering into a PDF Document's Page. * * @author Peter T Mount, http://www.retep.org.uk/pdf/ * @author Eric Z. Beard, ericzbeard@hotmail.com * @author Gilbert DeLeeuw, gil1@users.sourceforge.net * @version $Revision: 1.6 $, $Date: 2007/09/22 12:58:40 $ * @see gnu.jpdf.PDFGraphics */ public class PDFGraphics extends Graphics2D implements Serializable { /** * One degree in radians */ private static final double degrees_to_radians = Math.PI / 180.0; private static final int FILL = 1; private static final int STROKE = 2; private static final int CLIP = 3; private static final AffineTransform IDENTITY = new AffineTransform(); private static final Stroke DEF_STROKE = new BasicStroke(); /* * NOTE: The original class is the work of Peter T. Mount, who released it * in the uk.org.retep.pdf package. It was modified by Eric Z. Beard as * follows: * The package name was changed to gnu.pdf. * The formatting was changed a little bit. * This used to subclass an abstract class in a different package with * the same name (confusing). Now it's one concrete class. * drawImage() was implemented * It is still licensed under the LGPL. */ // Implementation notes: // // Pages 333-335 of the PDF Reference Manual // // Unless absolutely required, use the moveto, lineto and rectangle // operators to perform those actions. // They contain some extra optimizations // which will reduce the output size by up to half in some cases. // // About fill operators: For correct operation, any fill operation should // start with closeBlock(), which will ensure any previous path is completed, // otherwise you may find the fill will include previous items private static final DecimalFormat df = new DecimalFormat("#.###", new DecimalFormatSymbols(Locale.ENGLISH)); private static final DecimalFormat matDf = new DecimalFormat("0", new DecimalFormatSymbols(Locale.ENGLISH)); static { matDf.setMaximumFractionDigits(340); } //JPEXS: cache for already used images private static Map usedImages = new WeakHashMap(); private Color background; /** * This is true for any Graphics instance that didn't create the stream. * * @see #create */ private boolean child; private Area clip; private AffineTransform clipTransform; /** * This holds the current clipRectangle */ protected Rectangle clipRectangle; private Composite composite; private Graphics2D dg2 = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB).createGraphics(); /** * This is the current font (in Java format) */ private Font font; /** * Part of the optimizer: When true, we are drawing a path. */ private boolean inStroke; /** * Part of the optimizer: When true, we are within a Text Block. */ private boolean inText; // true if within a Text Block - see newTextBlock() /** * The stroke line cap code; */ private int lineCap = 0; /** * The stroke line join code */ private int lineJoin = 0; /** * The stroke line width */ private float lineWidth = 1.0f; /** * Part of the optimizer: The last known moveto/lineto x coordinate * * @see #moveto * @see #lineto */ private float lx; // last known moveto/lineto coordinates /** * Part of the optimizer: The last known moveto/lineto y coordinate * * @see #moveto * @see #lineto */ private float ly; // last known moveto/lineto coordinates private float miterLimit = 10.0f; /** * Part of the optimizer: When true, the font has changed. */ private boolean newFont; // true if the font changes - see newTextBlock() /** * This is a reference to the PDFPage we are rendering to. */ private PDFPage page; /** * This is the current pen/fill color */ private Paint paint; private AffineTransform paintTransform; /** * This is the current font (in PDF format) */ private PDFFont pdffont; /** * Part of the optimizer: This is written to the stream when the newPath() * is called. np then clears this value. */ private String pre_np; // PDF space transform private AffineTransform pTransform; /** * This is the PrintWriter used to write PDF drawing commands to the Stream */ private RawPrintWriter pw; /** * RenderingHints */ private RenderingHints rhints = new RenderingHints(null); private Stroke stroke; // Start of Graphics2D properties private AffineTransform transform; /** * Part of the optimizer: The last x coordinate when rendering text */ private float tx; // the last coordinate for text rendering /** * Part of the optimizer: The last y coordinate when rendering text */ private float ty; // the last coordinate for text rendering private String shading = null; private String pattern = null; private Set usedAlphas = new HashSet<>(); private Set gsBlendModes = new HashSet<>(); private int currentAlpha = 255; private String blendMode; private int shadingCount = 0; private int objId = 0; private boolean usePTransform = true; private static int[] srgbToLinear = new int[256]; private static int[] linearToSrgb = new int[256]; static { for (int i = 0; i < 256; i++) { srgbToLinear[i] = convertSRGBtoLinearRGB(i); linearToSrgb[i] = convertLinearRGBtoSRGB(i); } } private static int convertSRGBtoLinearRGB(int color) { float input, output; input = color / 255.0f; if (input <= 0.04045f) { output = input / 12.92f; } else { output = (float) Math.pow((input + 0.055) / 1.055, 2.4); } return Math.round(output * 255.0f); } private static int convertLinearRGBtoSRGB(int color) { float input, output; input = color / 255.0f; if (input <= 0.0031308) { output = input * 12.92f; } else { output = (1.055f * ((float) Math.pow(input, (1.0 / 2.4)))) - 0.055f; } return Math.round(output * 255.0f); } /** * @see Graphics2D#addRenderingHints(Map) */ @Override public void addRenderingHints(Map hints) { rhints.putAll(hints); } /** * This produces an arc by breaking it down into one or more Bezier curves. * It is used internally to implement the drawArc and fillArc methods. * * @param axc X coordinate of arc centre * @param ayc Y coordinate of arc centre * @param width of bounding rectangle * @param height of bounding rectangle * @param ang1 Start angle * @param ang2 End angle * @param clockwise true to draw clockwise, false anti-clockwise */ public void arc(double axc, double ayc, double width, double height, double ang1, double ang2, boolean clockwise) { double adiff; double x0, y0; double x3r, y3r; boolean first = true; // may not need this //if( ar < 0 ) { //ang1 += fixed_180; //ang2 += fixed_180; //ar = - ar; //} double ang1r = (ang1 % 360.0) * degrees_to_radians; double sin0 = Math.sin(ang1r); double cos0 = Math.cos(ang1r); x0 = axc + width * cos0; y0 = ayc + height * sin0; // NB: !clockwise here as Java Space is inverted to User Space if (!clockwise) { // Quadrant reduction while (ang1 < ang2) { ang2 -= 360.0; } while ((adiff = ang2 - ang1) < -90.0) { double w = sin0; sin0 = -cos0; cos0 = w; x3r = axc + width * cos0; y3r = ayc + height * sin0; arc_add(first, width, height, x0, y0, x3r, y3r, (x0 + width * cos0), (y0 + height * sin0) ); x0 = x3r; y0 = y3r; ang1 -= 90.0; first = false; } } else { // Quadrant reduction while (ang2 < ang1) { ang2 += 360.0; } while ((adiff = ang2 - ang1) > 90.0) { double w = cos0; cos0 = -sin0; sin0 = w; x3r = axc + width * cos0; y3r = ayc + height * sin0; arc_add(first, width, height, x0, y0, x3r, y3r, (x0 + width * cos0), (y0 + height * sin0) ); x0 = x3r; y0 = y3r; ang1 += 90.0; first = false; } } // Compute the intersection of the tangents. // We know that -fixed_90 <= adiff <= fixed_90. double trad = Math.tan(adiff * (degrees_to_radians / 2)); double ang2r = ang2 * degrees_to_radians; double xt = x0 - trad * width * sin0; double yt = y0 + trad * height * cos0; arc_add(first, width, height, x0, y0, (axc + width * Math.cos(ang2r)), (ayc + height * Math.sin(ang2r)), xt, yt); } /** * Used by the arc method to actually add an arc to the path Important: We * write directly to the stream here, because this method operates in User * space, rather than Java space. * * @param first true if the first arc * @param w width * @param h height * @param x0 coordinate * @param y0 coordinate * @param x3 coordinate * @param y3 coordinate * @param xt coordinate * @param yt coordinate */ private void arc_add(boolean first, double w, double h, double x0, double y0, double x3, double y3, double xt, double yt) { double dx = xt - x0, dy = yt - y0; double dist = dx * dx + dy * dy; double w2 = w * w, h2 = h * h; double r2 = w2 + h2; double fw = 0.0, fh = 0.0; if (dist < (r2 * 1.0e8)) { // JM fw = (w2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / w2))) : 0.0; fh = (h2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / h2))) : 0.0; } // The path must have a starting point if (first) { moveto(x0, y0); } double x = x0 + ((xt - x0) * fw); double y = y0 + ((yt - y0) * fh); x0 = x3 + ((xt - x3) * fw); y0 = y3 + ((yt - y3) * fh); // Finally the actual curve. curveto(x, y, x0, y0, x3, y3); } /** * This simply draws a White Rectangle to clear the area * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void clearRect(int x, int y, int w, int h) { closeBlock(); pw.print("q 1 1 1 RG ");// save state, set colour to White drawRect(x, y, w, h); closeBlock("B Q"); // close fill & stroke, then restore state } /** * @see Graphics2D#clip(Shape) */ @Override public void clip(Shape s) { if (s == null) { setClip(null); return; } Area newClip; if (clip == null) { newClip = new Area(s); } else { newClip = (Area) clip.clone(); newClip.intersect(new Area(s)); } setClip(newClip); } /** * This extra method allows PDF users to clip to a Polygon. * *

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

* Not implemented

* * @param data a 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; } /** *

* 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); Object interpolationHint = getRenderingHint(RenderingHints.KEY_INTERPOLATION); boolean interpolate = interpolationHint == RenderingHints.VALUE_INTERPOLATION_BILINEAR || interpolationHint == RenderingHints.VALUE_INTERPOLATION_BICUBIC; byte[] jpegImageData = getImageJpegData(img); if (jpegImageData != null) { image = new PDFImage(jpegImageData, x, y, w, h, obs, "" + mask.getSerialID() + " 0 R", interpolate); } else { image = new PDFImage(img, x, y, w, h, obs, "" + mask.getSerialID() + " 0 R", interpolate); } // 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"); initAlpha(255); // 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 properties = new Hashtable(); String[] keys = img.getPropertyNames(); if (keys != null) { for (int i = 0; i < keys.length; i++) { properties.put(keys[i], img.getProperty(keys[i])); } } BufferedImage result = new BufferedImage(cm, raster, isAlphaPremultiplied, properties); img.copyData(raster); image = result; } drawImage(image, xform, null); } /** * 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 drawRoundRect(int x, int y, int w, int h, int aw, int ah) { } //============ Oval operations ======================= /** * 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, 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 an 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. /** *

* 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(null /*FIXME!!!*/); polygon(xp, yp, np); if (shadingFill(null)) { 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(Shape s) { 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, s); paintTransform = transform; } } pw.println("/Pattern cs"); pw.println(pattern + " scn"); } } private boolean shadingFill(Shape s) { if (pattern == null && shading != null) { saveState(); pw.println("W n"); if (paint instanceof MultipleGradientPaint) { if (shading.equals("gradient") || !paintTransform.equals(transform)) { initGradientPaint((MultipleGradientPaint) paint, s); 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(s); } 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, s); 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() { if (clip == null) { return null; } return clip; } /** * 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 uncompressesd 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 = usePTransform ? new AffineTransform(pTransform) : new AffineTransform(); 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(); AffineTransform currentTransform = transform; transform = clipTransform; setTransform(currentTransform); } 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"); } } private void initAlpha(int alpha) { if (currentAlpha != alpha || blendMode != null) { String gsId = "/GSAlpha" + alpha; currentAlpha = alpha; if (!usedAlphas.contains(alpha)) { page.addExtGStateResource(gsId + " <>"); usedAlphas.add(currentAlpha); } pw.println(gsId + " gs"); } } public void setBlendMode(String mode) { if (currentAlpha < 0) { currentAlpha = 255; } String gsName = "/GSBlend" + mode + "Alpha" + currentAlpha; if (!gsBlendModes.contains(gsName)) { page.addExtGStateResource(gsName + " <>"); } pw.println(gsName + " gs"); this.blendMode = mode; } /** * 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 initAlpha(c.getAlpha()); // 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 String generateRadialFunctionBody(RadialGradientPaint radGrad, boolean alpha) { double a = ((radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) * (radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) + (radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) * (radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) - radGrad.getRadius() * radGrad.getRadius()); String functionBody = "{\n" + matDf.format(radGrad.getFocusPoint().getX()) + " 2 index sub\n" + matDf.format(radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) + " mul 2 mul\n" //stack size: 3 + matDf.format(radGrad.getFocusPoint().getY()) + " 2 index sub\n" + matDf.format(radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) + " mul 2 mul\n" + "add\n" //b, stack size: 3 + matDf.format(radGrad.getFocusPoint().getX()) + " 3 index sub\n" + "dup mul\n" + matDf.format(radGrad.getFocusPoint().getY()) + " 3 index sub\n" + "dup mul\n" + "add\n" //c, stack size: 4 + "1 index dup mul 4 " + matDf.format(a) + " mul 2 index mul sub\n" //D, stack size: 4 + "0 index 0 lt\n" + "{\n" + "1\n" + "}" + "{" + "0 index 0 gt\n" + "{\n" + "2 index neg 1 index sqrt add 2 " + matDf.format(a) + " mul div\n" // x1, stack size: 5 + "3 index neg 2 index sqrt sub 2 " + matDf.format(a) + " mul div\n" // x2, stack size: 6 + "0 index 2 index gt{0 index} {1 index} ifelse\n" + "exch pop exch pop\n" //x, stack size 5 + "}" + "{\n" + "2 index neg 2 " + matDf.format(a) + " mul div\n" // x, stack size 5 + "} ifelse\n" + "} ifelse\n" + "exch pop exch pop exch pop exch pop exch pop\n"; //remove index0,1,2,3,4 if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT) { functionBody += "dup\n"; } if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE) { functionBody += "dup 1 gt {pop 1} if\n"; } else { functionBody += "dup 1 gt {dup floor sub} if\n"; } if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT) { functionBody += "exch floor 2 mod 1 eq {" + "neg 1 add" + "}\n" + "if\n"; } int num = radGrad.getFractions().length; Color[] rcolors = convertColorSpace(radGrad.getColors(), radGrad.getColorSpace()); for (int i = 0; i < num - 1; i++) { functionBody += "dup " + radGrad.getFractions()[i] + " lt not 1 index " + radGrad.getFractions()[i + 1] + " gt not and\n{\n" + "0 index " + radGrad.getFractions()[i] + " sub " + (radGrad.getFractions()[i + 1] - radGrad.getFractions()[i]) + " div\n"; if (alpha) { functionBody += "0 index " + ((rcolors[i + 1].getAlpha() - rcolors[i].getAlpha()) / 255.0) + " mul " + (rcolors[i].getAlpha() / 255.0) + " add\n"; functionBody += "dup dup\n"; } else { if (radGrad.getColorSpace() == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) { functionBody += "0 index " + ((srgbToLinear[rcolors[i + 1].getRed()] - srgbToLinear[rcolors[i].getRed()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getRed()] / 255.0) + " add\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "1 index " + ((srgbToLinear[rcolors[i + 1].getGreen()] - srgbToLinear[rcolors[i].getGreen()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getGreen()] / 255.0) + " add\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "2 index " + ((srgbToLinear[rcolors[i + 1].getBlue()] - srgbToLinear[rcolors[i].getBlue()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getBlue()] / 255.0) + " add\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n"; } else { functionBody += "0 index " + ((rcolors[i + 1].getRed() - rcolors[i].getRed()) / 255.0) + " mul " + (rcolors[i].getRed() / 255.0) + " add\n" + "1 index " + ((rcolors[i + 1].getGreen() - rcolors[i].getGreen()) / 255.0) + " mul " + (rcolors[i].getGreen() / 255.0) + " add\n" + "2 index " + ((rcolors[i + 1].getBlue() - rcolors[i].getBlue()) / 255.0) + " mul " + (rcolors[i].getBlue() / 255.0) + " add\n"; } } if (i < num - 2) { functionBody += "}\n{\n"; } } functionBody += "}if\n"; for (int i = 0; i < num - 2; i++) { functionBody += "}ifelse\n"; } functionBody += "}\n"; return functionBody; } 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; } List functions2Refs = new ArrayList<>(); Color[] colors = convertColorSpace(grad.getColors(), grad.getColorSpace()); for (int i = 1; i < grad.getColors().length; i++) { final Color color1 = colors[i - 1]; final Color color2 = colors[i]; final MultipleGradientPaint.ColorSpaceType colorSpace = grad.getColorSpace(); if (colorSpace == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) { PDFStream function4 = new PDFStream(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4 /Domain [0 1] /Range [0 1 0 1 0 1]".getBytes("UTF-8")); writeStream(os); } }; OutputStream f4Os = function4.getOutputStream(); int redDelta = srgbToLinear[color2.getRed()] - srgbToLinear[color1.getRed()]; int greenDelta = srgbToLinear[color2.getGreen()] - srgbToLinear[color1.getGreen()]; int blueDelta = srgbToLinear[color2.getBlue()] - srgbToLinear[color1.getBlue()]; String functionBody = "{" + "0 index " + redDelta + " mul " + srgbToLinear[color1.getRed()] + " add 255 div\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "1 index " + greenDelta + " mul " + srgbToLinear[color1.getGreen()] + " add 255 div\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "2 index " + blueDelta + " mul " + srgbToLinear[color1.getBlue()] + " add 255 div\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "}"; try { f4Os.write(functionBody.getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(function4); functions2Refs.add(function4.getSerialID() + " 0 R"); } else { // PDFObject function2 = new PDFObject(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write(("/FunctionType 2 /Domain [0 1] /C0 [" + (((float) color1.getRed()) / 255.0f) + " " + (((float) color1.getGreen()) / 255.0f) + " " + (((float) color1.getBlue()) / 255.0f) + "] /C1 [" + (((float) color2.getRed()) / 255.0f) + " " + (((float) color2.getGreen()) / 255.0f) + " " + (((float) color2.getBlue()) / 255.0f) + "] /N 1\n").getBytes("UTF-8")); writeEnd(os); } }; page.getPDFDocument().add(function2); functions2Refs.add(function2.getSerialID() + " 0 R"); } } List functions2AlphaRefs = new ArrayList<>(); Color[] alphaColors = grad.getColors(); for (int i = 1; i < grad.getColors().length; i++) { final Color color1 = alphaColors[i - 1]; final Color color2 = alphaColors[i]; PDFObject function2 = new PDFObject(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write(("/FunctionType 2 /Domain [0 1] /C0 [" + (((float) color1.getAlpha()) / 255.0f) + " " + (((float) color1.getAlpha()) / 255.0f) + " " + (((float) color1.getAlpha()) / 255.0f) + "] /C1 [" + (((float) color2.getAlpha()) / 255.0f) + " " + (((float) color2.getAlpha()) / 255.0f) + " " + (((float) color2.getAlpha()) / 255.0f) + "] /N 1\n").getBytes("UTF-8")); writeEnd(os); } }; page.getPDFDocument().add(function2); functions2AlphaRefs.add(function2.getSerialID() + " 0 R"); } final MultipleGradientPaint fgrad = grad; PDFObject function3 = new PDFGradientFunction3(fgrad, functions2Refs); page.getPDFDocument().add(function3); PDFObject function3Alpha = new PDFGradientFunction3(fgrad, functions2AlphaRefs); page.getPDFDocument().add(function3Alpha); double glen = 0; double divisor = 1; double maxlen = 256; if ((fgrad instanceof LinearGradientPaint) && (fgrad.getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE)) { LinearGradientPaint linGrad = (LinearGradientPaint) fgrad; Point2D startPoint = new Point2D.Double(); Point2D endPoint = new Point2D.Double(); startPoint = linGrad.getStartPoint(); endPoint = linGrad.getEndPoint(); double deltaX = endPoint.getX() - startPoint.getX(); double deltaY = endPoint.getY() - startPoint.getY(); glen = Math.sqrt(deltaX * deltaX + deltaY * deltaY); if (glen > maxlen) { divisor = glen / maxlen; glen = maxlen; } } final double flen = glen; PDFStream radialFunction; PDFStream radialAlphaFunction; if (useFunctionShading(fgrad)) { RadialGradientPaint radGrad = (RadialGradientPaint) fgrad; radialFunction = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4\n".getBytes("UTF-8")); //pdf reference, page 168 os.write("/Domain [-1000000 1000000 -1000000 1000000]\n".getBytes("UTF-8")); os.write("/Range [0 1 0 1 0 1]\n".getBytes("UTF-8")); //3 - R,G,B writeStream(os); } }; radialAlphaFunction = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4\n".getBytes("UTF-8")); //pdf reference, page 168 os.write("/Domain [-1000000 1000000 -1000000 1000000]\n".getBytes("UTF-8")); os.write("/Range [0 1 0 1 0 1]\n".getBytes("UTF-8")); //3 - R,G,B writeStream(os); } }; //PDF reference, page 176 /*double b = 2 * (focalX - x) * (centerX - focalX) + 2 * (focalY - y) * (centerY - focalY); double c = (focalX - x) * (focalX - x) + (focalY - y) * (focalY - y); double D = b * b - 4 * a * c;*/ //(-b + Math.sqrt(D)) / (2 * a) //D = b * b - 4 * a * c; String functionBody = generateRadialFunctionBody(radGrad, false); OutputStream funOs = radialFunction.getOutputStream(); try { funOs.write(functionBody.getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } String alphaFunctionBody = generateRadialFunctionBody(radGrad, true); funOs = radialAlphaFunction.getOutputStream(); try { funOs.write(alphaFunctionBody.getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(radialFunction); page.getPDFDocument().add(radialAlphaFunction); } else { radialFunction = null; radialAlphaFunction = null; } PDFObject shadingObj = new PdfGradientShading(fgrad, flen, useFunctionShading(fgrad), function3, radialFunction); page.getPDFDocument().add(shadingObj); PDFObject shadingAlphaObj = new PdfGradientShading(fgrad, flen, useFunctionShading(fgrad), function3Alpha, radialAlphaFunction); page.getPDFDocument().add(shadingAlphaObj); shadingCount++; final int fCurrentShadingCount = shadingCount; PDFStream alphaObject = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/Group << /CS /DeviceGray /S /Transparency >>\n".getBytes("UTF-8")); os.write("/Type /XObject\n".getBytes("UTF-8")); os.write("/Resources <<\n".getBytes("UTF-8")); os.write("/Shading <<".getBytes("UTF-8")); os.write(("/ShA" + fCurrentShadingCount + " " + shadingAlphaObj.getSerialID() + " 0 R").getBytes("UTF-8")); os.write(">>\n".getBytes("UTF-8")); //shading os.write(">>\n".getBytes("UTF-8")); //resources os.write("/Subtype /Form\n".getBytes("UTF-8")); os.write("/BBox [-1000000 -1000000 2000000 2000000]\n".getBytes("UTF-8")); //fixme writeStream(os); } }; OutputStream alphaOs = alphaObject.getOutputStream(); try { alphaOs.write(("/ShA" + fCurrentShadingCount + " sh\n").getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(alphaObject); String alphaExtGState = "/GradAlpha" + fCurrentShadingCount + " <<" + "/SMask <<" + "/Type /Mask\n" + "/S /Luminosity\n" + "/G " + alphaObject.getSerialID() + " 0 R" + ">>" + ">>"; if ((grad instanceof LinearGradientPaint) && ((LinearGradientPaint) grad).getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE) { LinearGradientPaint linGrad = (LinearGradientPaint) grad; Point2D startPoint = linGrad.getStartPoint(); Point2D endPoint = linGrad.getEndPoint(); Point2D startPointTrans = new Point2D.Double(); Point2D endPointTrans = new Point2D.Double(); transform.transform(linGrad.getStartPoint(), startPointTrans); transform.transform(linGrad.getEndPoint(), endPointTrans); double deltaX = endPoint.getX() - startPoint.getX(); double deltaY = endPoint.getY() - startPoint.getY(); double tana = deltaX / deltaY; double alfa = Math.atan(tana); AffineTransform m = new AffineTransform(); if (usePTransform) { m.concatenate(pTransform); } m.concatenate(transform); m.concatenate(AffineTransform.getTranslateInstance(startPoint.getX(), endPoint.getY())); m.concatenate(AffineTransform.getScaleInstance(divisor, divisor)); m.concatenate(AffineTransform.getRotateInstance(-alfa)); String matrixStr = "" + matDf.format(m.getScaleX()) + " " + matDf.format(m.getShearY()) + " " + matDf.format(m.getShearX()) + " " + matDf.format(m.getScaleY()) + " " + matDf.format(m.getTranslateX()) + " " + matDf.format(m.getTranslateY()); PDFStream innerPattern = new PDFStream("/Pattern") { @Override public void write(OutputStream os) throws IOException { writeStart(os); double w; double h; w = 1; h = flen; os.write("/PatternType 1\n".getBytes("UTF-8")); os.write("/PaintType 1\n".getBytes("UTF-8")); os.write("/TilingType 2\n".getBytes("UTF-8")); os.write(("/BBox [0 0 " + w + " " + h + "]\n").getBytes("UTF-8")); os.write(("/XStep " + w + "\n").getBytes("UTF-8")); os.write(("/YStep " + h + "\n").getBytes("UTF-8")); os.write(("/Resources << " + "/Shading << /Shin" + fCurrentShadingCount + " " + shadingObj.getSerialID() + " 0 R >>" + "/ExtGState <<" + alphaExtGState + ">>" + ">>\n").getBytes("UTF-8")); os.write(("/Matrix [" + matrixStr + "]\n").getBytes("UTF-8")); writeStream(os); } }; OutputStream patOs = innerPattern.getOutputStream(); try { patOs.write(("/GradAlpha" + fCurrentShadingCount + " gs").getBytes("UTF-8")); patOs.write(("/Shin" + shadingCount + " sh").getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(innerPattern); page.addPatternResource("/p" + shadingCount + " " + innerPattern.getSerialID() + " 0 R"); this.pattern = "/p" + shadingCount; return; } currentAlpha = -1; page.addExtGStateResource(alphaExtGState); pw.println("/GradAlpha" + fCurrentShadingCount + " gs"); page.addShadingResource("/Sh" + shadingCount + " " + shadingObj.getSerialID() + " 0 R "); this.shading = "/Sh" + shadingCount; } /** * Hack: * Images, which have method boolean isJpeg() and byte[] getImageData() can be stored as JPEGs * You must implement an Image which has these methods to be properly able to save JPEGS */ private byte[] getImageJpegData(Image img) { byte[] jpegImageData = null; try { Method jpegMethod = img.getClass().getDeclaredMethod("isJpeg"); boolean isJpeg = (boolean) jpegMethod.invoke(img); if (isJpeg) { Method getImageDataMethod = img.getClass().getDeclaredMethod("getImageData"); jpegImageData = (byte[]) getImageDataMethod.invoke(img); } } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { //ignore } return jpegImageData; } private void initTexturePaint(TexturePaint texturePaint) { byte[] jpegImageData = getImageJpegData(texturePaint.getImage()); BufferedImage img = texturePaint.getImage(); PDFMask mask = new PDFMask(img); page.getPDFDocument().add(mask); Rectangle2D anchorRect = texturePaint.getAnchorRect(); final double w = anchorRect.getWidth(); final double h = anchorRect.getHeight(); Object interpolationHint = getRenderingHint(RenderingHints.KEY_INTERPOLATION); boolean interpolate = interpolationHint == RenderingHints.VALUE_INTERPOLATION_BILINEAR || interpolationHint == RenderingHints.VALUE_INTERPOLATION_BICUBIC; PDFImage image; if (usedImages.containsKey(img)) { image = usedImages.get(img); } else { if (jpegImageData != null) { image = new PDFImage(jpegImageData, 0, 0, img.getWidth(), img.getHeight(), new ImageObserver() { @Override public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { return true; } }, "" + mask.getSerialID() + " 0 R", interpolate); } else { image = new PDFImage(img, 0, 0, img.getWidth(), img.getHeight(), new ImageObserver() { @Override public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { return true; } }, "" + mask.getSerialID() + " 0 R", interpolate); } // The image needs to be registered in several places page.getPDFDocument().setImageName(image); page.getPDFDocument().add(image); usedImages.put(img, image); } AffineTransform m = new AffineTransform(); AffineTransform ptt = new AffineTransform(); if (usePTransform) { ptt.concatenate(pTransform); } ptt.concatenate(transform); m.concatenate(ptt); String matrixStr = "" + matDf.format(m.getScaleX()) + " " + matDf.format(m.getShearY()) + " " + matDf.format(m.getShearX()) + " " + matDf.format(m.getScaleY()) + " " + matDf.format(m.getTranslateX()) + " " + matDf.format(m.getTranslateY()); PDFStream innerPattern = new PDFStream("/Pattern") { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/PatternType 1\n".getBytes("UTF-8")); os.write("/PaintType 1\n".getBytes("UTF-8")); os.write("/TilingType 2\n".getBytes("UTF-8")); os.write(("/BBox [0 0 " + matDf.format(w) + " " + matDf.format(h) + "]\n").getBytes("UTF-8")); os.write(("/XStep " + matDf.format(w) + "\n").getBytes("UTF-8")); os.write(("/YStep " + matDf.format(h) + "\n").getBytes("UTF-8")); os.write(("/Resources << ").getBytes("UTF-8")); os.write("/XObject << ".getBytes("UTF-8")); os.write((image.getName() + " " + image.getSerialID() + " 0 R").getBytes("UTF-8")); os.write(" >> ".getBytes("UTF-8")); os.write((">>\n").getBytes("UTF-8")); //"1 0 0 1 0 0" os.write(("/Matrix [" + matrixStr + "]\n").getBytes("UTF-8")); writeStream(os); } }; OutputStream patOs = innerPattern.getOutputStream(); PrintWriter patwriter = new PrintWriter(patOs); AffineTransform transformToSet; transformToSet = new AffineTransform(w, 0, 0, -h, 0 - anchorRect.getX(), h - anchorRect.getY()); patwriter.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"); transformToSet = new AffineTransform(w, 0, 0, -h, w - anchorRect.getX(), h - anchorRect.getY()); patwriter.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"); transformToSet = new AffineTransform(w, 0, 0, -h, 0 - anchorRect.getX(), 2 * h - anchorRect.getY()); patwriter.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"); transformToSet = new AffineTransform(w, 0, 0, -h, w - anchorRect.getX(), 2 * h - anchorRect.getY()); patwriter.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"); patwriter.flush(); page.getPDFDocument().add(innerPattern); shadingCount++; page.addPatternResource("/p" + shadingCount + " " + innerPattern.getSerialID() + " 0 R"); this.pattern = "/p" + shadingCount; } /** * Not implemented, as this is not supported in the PDF specification. */ @Override public void setPaintMode() { } /** * Sets a rendering hint * * @param arg0 * @param arg1 */ @Override public void setRenderingHint(Key arg0, Object arg1) { if (arg1 != null) { rhints.put(arg0, arg1); } else { rhints.remove(arg0); } } // Add Graphics2D methods. /** * @see Graphics2D#setRenderingHints(Map) */ @Override public void setRenderingHints(Map hints) { rhints.clear(); rhints.putAll(hints); } /** * @see Graphics2D#setStroke(Stroke) */ @Override public void setStroke(Stroke s) { this.stroke = s; if (stroke instanceof BasicStroke) { BasicStroke bs = (BasicStroke) stroke; setLineCap(bs.getEndCap()); setLineJoin(bs.getLineJoin()); setLineWidth(bs.getLineWidth()); setMiterLimit(bs.getMiterLimit()); // TODO: Line dash pattern } } /** * @see Graphics2D#setTransform(AffineTransform) */ @Override public void setTransform(AffineTransform t) { setNewTranform(new AffineTransform(t)); } /** * Not implemented, as this is not supported in the PDF specification. * * @param c1 Color to xor with */ @Override public void setXORMode(Color c1) { } //============ Text operations ======================= /** * @see Graphics2D#shear(double, double) */ @Override public void shear(double shx, double shy) { AffineTransform newTransform = new AffineTransform(transform); newTransform.shear(shx, shy); setNewTranform(newTransform); } /** * @see Graphics2D#transform(AffineTransform) */ @Override public void transform(AffineTransform tx) { AffineTransform newTransform = new AffineTransform(transform); newTransform.concatenate(tx); setNewTranform(newTransform); } /** * @see Graphics2D#translate(double, double) */ @Override public void translate(double tx, double ty) { AffineTransform newTransform = new AffineTransform(transform); newTransform.translate(tx, ty); setNewTranform(newTransform); } /** * @see Graphics#translate(int, int) */ @Override public void translate(int x, int y) { translate((double) x, (double) y); } /** * Converts the Java space coordinates into pdf text space. * * @param x coordinate * @param y coordinate * @param tx coordinate * @param ty coordinate * @return String containing the coordinates in PDF text space */ private String twh(float x, float y, float tx, float ty) { float nx = x, ny = y; float ntx = tx, nty = ty; nx = (float) (x - tx); ny = (float) (y - ty); return "" + df.format(nx) + " " + df.format(ny) + " "; } /** * Converts the Java space coordinates into pdf text space. * * @param x coordinate * @param y coordinate * @return String containing the coordinates in PDF text space */ private String txy(float x, float y) { Point2D ptSrc = new Point2D.Float(x, y); Point2D ptDst = new Point2D.Float(); if (usePTransform) { pTransform.transform(ptSrc, ptDst); } return "" + df.format(ptDst.getX()) + " " + df.format(ptDst.getY()) + " "; } private void setNewTranform(AffineTransform t) { closeBlock(); if (true) { //return; } AffineTransform newTransform = new AffineTransform(t); AffineTransform transformToSet = new AffineTransform(newTransform); if (usePTransform) { if (transform != null) { AffineTransform aInv = new AffineTransform(transform); try { aInv.invert(); } catch (NoninvertibleTransformException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } AffineTransform pInv = new AffineTransform(pTransform); try { pInv.invert(); } catch (NoninvertibleTransformException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } transformToSet = new AffineTransform(); transformToSet.concatenate(aInv); transformToSet.concatenate(pInv); transformToSet.concatenate(pTransform); transformToSet.concatenate(newTransform); } else { transformToSet.preConcatenate(pTransform); } } if (clip != null) { AffineTransform inv = new AffineTransform(newTransform); try { inv.invert(); } catch (NoninvertibleTransformException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } clip = new Area(transform.createTransformedShape(inv.createTransformedShape(clip))); clipRectangle = clip.getBounds(); } transform = newTransform; pw.println("" + 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" ); } private void saveState() { pw.println("q"); } private void restoreState() { pw.println("Q"); } public void drawXObject(Graphics g) { if (g instanceof PDFGraphics) { int objId = ((PDFGraphics) g).objId; pw.println("/MyObj" + objId + " Do"); } } public Graphics2D createXObject() { final PDFPage newPage = new PDFPage(page.pageFormat) { @Override public void addToProcset(String proc) { page.addToProcset(proc); } }; newPage.pdfDocument = page.pdfDocument; PDFStream retObject = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/Type /XObject\n".getBytes("UTF-8")); newPage.writeResources(os); os.write("/Subtype /Form\n".getBytes("UTF-8")); os.write("/BBox [-1000000 -1000000 2000000 2000000]\n".getBytes("UTF-8")); //fixme writeStream(os); } }; page.getPDFDocument().add(retObject); OutputStream os = retObject.getOutputStream(); RawPrintWriter pw2 = new RawPrintWriter(os); page.addXObject("/MyObj" + retObject.getSerialID() + " " + retObject.getSerialID() + " 0 R"); PDFGraphics g = new PDFGraphics(); g.usePTransform = false; g.init(newPage, pw2); g.objId = retObject.getSerialID(); //g.setTransform(new AffineTransform()); return g; } } \ No newline at end of file diff --git a/libsrc/gnujpdf/src/gnu/jpdf/PDFImage.java b/libsrc/gnujpdf/src/gnu/jpdf/PDFImage.java index c080bcb10..e8d35e13d 100644 --- a/libsrc/gnujpdf/src/gnu/jpdf/PDFImage.java +++ b/libsrc/gnujpdf/src/gnu/jpdf/PDFImage.java @@ -57,6 +57,7 @@ public class PDFImage extends PDFStream implements ImageObserver, Serializable { private int width; private int height; private Image img; + private byte[] jpegImageData; private String name; private String mask; @@ -106,10 +107,23 @@ public class PDFImage extends PDFStream implements ImageObserver, Serializable { setImage(img, x, y, img.getWidth(this), img.getHeight(this), obs); } + public PDFImage(byte jpegImageData[], int x, int y, int w, int h, ImageObserver obs, String mask, boolean interpolate) { + this(); + objwidth = w; + objheight = h; + this.mask = mask; + this.interpolate = interpolate; + setJpegImageData(jpegImageData, w, h); + } + public PDFImage(Image img, int x, int y, int w, int h, ImageObserver obs) { this(img, x, y, w, h, obs, null, false); } + public PDFImage(byte jpegImageData[], int x, int y, int w, int h, ImageObserver obs) { + this(jpegImageData, x, y, w, h, obs, null, false); + } + /** * Get the value of width. * @@ -176,6 +190,14 @@ public class PDFImage extends PDFStream implements ImageObserver, Serializable { */ public void setImage(Image img, int x, int y, int w, int h, ImageObserver obs) { this.img = img; + this.jpegImageData = null; + width = w; + height = h; + } + + public void setJpegImageData(byte[] jpegImageData, int w, int h) { + this.jpegImageData = jpegImageData; + this.img = null; width = w; height = h; } @@ -278,14 +300,23 @@ public class PDFImage extends PDFStream implements ImageObserver, Serializable { os.write(">\nendstream\nendobj\n\n".getBytes("UTF-8")); */ ByteArrayOutputStream b = new ByteArrayOutputStream(); - DeflaterOutputStream dos = new DeflaterOutputStream(b); - buf.writeTo(dos); - dos.finish(); - dos.close(); + + if (jpegImageData == null) { + DeflaterOutputStream dos = new DeflaterOutputStream(b); + buf.writeTo(dos); + dos.finish(); + dos.close(); + } else { + buf.writeTo(b); + } // FlatDecode is compatible with the java.util.zip.Deflater class //os.write("/Filter [/FlateDecode /ASCIIHexDecode]\n".getBytes("UTF-8")); - os.write("/Filter [/FlateDecode /ASCII85Decode]\n".getBytes("UTF-8")); + if (jpegImageData != null) { + os.write("/Filter /DCTDecode\n".getBytes("UTF-8")); + } else { + os.write("/Filter [/FlateDecode /ASCII85Decode]\n".getBytes("UTF-8")); + } os.write("/Length ".getBytes("UTF-8")); os.write(Integer.toString(b.size()).getBytes("UTF-8")); os.write("\n>>\nstream\n".getBytes("UTF-8")); @@ -324,45 +355,49 @@ public class PDFImage extends PDFStream implements ImageObserver, Serializable { //System.err.println("Processing image "+width+"x"+height+" pixels"); ByteArrayOutputStream bos = getStream(); - int w = width; - int h = height; - int x = 0; - int y = 0; - int[] pixels = new int[w * h]; - PixelGrabber pg = new PixelGrabber(img, x, y, w, h, pixels, 0, w); - try { - pg.grabPixels(); - } catch (InterruptedException e) { - System.err.println("interrupted waiting for pixels!"); - return; - } - if ((pg.getStatus() & ImageObserver.ABORT) != 0) { - System.err.println("image fetch aborted or errored"); - return; - } - StringBuffer out = new StringBuffer(); - for (int j = 0; j < h; j++) { - for (int i = 0; i < w; i++) { - //System.out.print("p[" + j * w + i+ "]=" + pixels[j * w + i] + "."); - out.append(handlePixel(x + i, y + j, pixels[j * w + i])); - if (out.toString().length() >= 8) { - String tuple = out.substring(0, 8); - out.delete(0, 8); - // Convert !!!!! to 'z' - String encTuple = base85Encoding(tuple); - if (encTuple.equals("!!!!!")) { - encTuple = "z"; + if (jpegImageData != null) { + bos.write(jpegImageData); + } else { + int w = width; + int h = height; + int x = 0; + int y = 0; + int[] pixels = new int[w * h]; + PixelGrabber pg = new PixelGrabber(img, x, y, w, h, pixels, 0, w); + try { + pg.grabPixels(); + } catch (InterruptedException e) { + System.err.println("interrupted waiting for pixels!"); + return; + } + if ((pg.getStatus() & ImageObserver.ABORT) != 0) { + System.err.println("image fetch aborted or errored"); + return; + } + StringBuffer out = new StringBuffer(); + for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + //System.out.print("p[" + j * w + i+ "]=" + pixels[j * w + i] + "."); + out.append(handlePixel(x + i, y + j, pixels[j * w + i])); + if (out.toString().length() >= 8) { + String tuple = out.substring(0, 8); + out.delete(0, 8); + // Convert !!!!! to 'z' + String encTuple = base85Encoding(tuple); + if (encTuple.equals("!!!!!")) { + encTuple = "z"; + } + bos.write(encTuple.getBytes("UTF-8")); } - bos.write(encTuple.getBytes("UTF-8")); } } - } - // This should be the only partial tuple case, + // This should be the only partial tuple case, - String lastTuple = base85Encoding(out.toString()); - //System.out.println("lastTuple: " + lastTuple); - bos.write(lastTuple.getBytes("UTF-8")); - bos.write("~".getBytes("UTF-8")); + String lastTuple = base85Encoding(out.toString()); + //System.out.println("lastTuple: " + lastTuple); + bos.write(lastTuple.getBytes("UTF-8")); + bos.write("~".getBytes("UTF-8")); + } //System.out.println("Processing done"); // this will write the actual stream