From bcbfea5666bded2da3ca3b5ac81bbfcfe645a9f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sun, 21 Mar 2021 17:04:32 +0100 Subject: [PATCH] gnujpdf blend modes --- lib/gnujpdf.jar | Bin 193917 -> 194468 bytes .../flash/exporters/BlendModeSetable.java | 9 + .../flash/exporters/DualGraphics2D.java | 457 ---------------- .../flash/exporters/DualPdfGraphics2D.java | 514 ++++++++++++++++++ .../flash/exporters/FrameExporter.java | 2 +- .../decompiler/flash/timeline/Timeline.java | 105 ++-- libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java | 2 +- 7 files changed, 580 insertions(+), 509 deletions(-) create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/BlendModeSetable.java delete mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/DualGraphics2D.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/DualPdfGraphics2D.java diff --git a/lib/gnujpdf.jar b/lib/gnujpdf.jar index 5da33010dafe2e7205ac1cdad5d96f090306b782..dd0754ea614738fc807ee34dc705d163cc3eacc7 100644 GIT binary patch delta 34676 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 delta 34187 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 hints) { - first.setRenderingHints(hints); - second.setRenderingHints(hints); - } - - @Override - public void addRenderingHints(Map hints) { - first.addRenderingHints(hints); - second.addRenderingHints(hints); - } - - @Override - public RenderingHints getRenderingHints() { - return first.getRenderingHints(); - } - - @Override - public void translate(int x, int y) { - first.translate(x, y); - second.translate(x, y); - } - - @Override - public void translate(double tx, double ty) { - first.translate(tx, ty); - second.translate(tx, ty); - } - - @Override - public void rotate(double theta) { - first.rotate(theta); - second.rotate(theta); - } - - @Override - public void rotate(double theta, double x, double y) { - first.rotate(theta, x, y); - second.rotate(theta, x, y); - } - - @Override - public void scale(double sx, double sy) { - first.scale(sx, sy); - second.scale(sx, sy); - } - - @Override - public void shear(double shx, double shy) { - first.shear(shx, shy); - second.shear(shx, shy); - } - - @Override - public void transform(AffineTransform Tx) { - first.transform(Tx); - second.transform(Tx); - } - - @Override - public void setTransform(AffineTransform Tx) { - first.setTransform(Tx); - second.setTransform(Tx); - } - - @Override - public AffineTransform getTransform() { - return first.getTransform(); - } - - @Override - public Paint getPaint() { - return first.getPaint(); - } - - @Override - public Composite getComposite() { - return first.getComposite(); - } - - @Override - public void setBackground(Color color) { - first.setBackground(color); - second.setBackground(color); - } - - @Override - public Color getBackground() { - return first.getBackground(); - } - - @Override - public Stroke getStroke() { - return first.getStroke(); - } - - @Override - public void clip(Shape s) { - first.clip(s); - second.clip(s); - } - - @Override - public FontRenderContext getFontRenderContext() { - return first.getFontRenderContext(); - } - - @Override - public Graphics create() { - return this; //? - } - - @Override - public Color getColor() { - return first.getColor(); - } - - @Override - public void setColor(Color c) { - first.setColor(c); - second.setColor(c); - } - - @Override - public void setPaintMode() { - first.setPaintMode(); - second.setPaintMode(); - } - - @Override - public void setXORMode(Color c1) { - first.setXORMode(c1); - second.setXORMode(c1); - } - - @Override - public Font getFont() { - return first.getFont(); - } - - @Override - public void setFont(Font font) { - first.setFont(font); - second.setFont(font); - } - - @Override - public FontMetrics getFontMetrics(Font f) { - return first.getFontMetrics(); - } - - @Override - public Rectangle getClipBounds() { - return first.getClipBounds(); - } - - @Override - public void clipRect(int x, int y, int width, int height) { - first.clearRect(x, y, width, height); - second.clipRect(x, y, width, height); - } - - @Override - public void setClip(int x, int y, int width, int height) { - first.setClip(x, y, width, height); - second.setClip(x, y, width, height); - } - - @Override - public Shape getClip() { - return first.getClip(); - } - - @Override - public void setClip(Shape clip) { - first.setClip(clip); - second.setClip(clip); - } - - @Override - public void copyArea(int x, int y, int width, int height, int dx, int dy) { - first.copyArea(x, y, width, height, dx, dy); - second.copyArea(x, y, width, height, dx, dy); - } - - @Override - public void drawLine(int x1, int y1, int x2, int y2) { - first.drawLine(x1, y1, x2, y2); - second.drawLine(x1, y1, x2, y2); - } - - @Override - public void fillRect(int x, int y, int width, int height) { - first.fillRect(x, y, width, height); - second.fillRect(x, y, width, height); - } - - @Override - public void clearRect(int x, int y, int width, int height) { - first.clearRect(x, y, width, height); - second.clearRect(x, y, width, height); - } - - @Override - public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { - first.drawRoundRect(x, y, width, height, arcWidth, arcHeight); - second.drawRoundRect(x, y, width, height, arcWidth, arcHeight); - } - - @Override - public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { - first.fillRoundRect(x, y, width, height, arcWidth, arcHeight); - second.fillRoundRect(x, y, width, height, arcWidth, arcHeight); - } - - @Override - public void drawOval(int x, int y, int width, int height) { - first.drawOval(x, y, width, height); - second.drawOval(x, y, width, height); - } - - @Override - public void fillOval(int x, int y, int width, int height) { - first.fillOval(x, y, width, height); - second.fillOval(x, y, width, height); - } - - @Override - public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) { - first.drawArc(x, y, width, height, startAngle, arcAngle); - second.drawArc(x, y, width, height, startAngle, arcAngle); - } - - @Override - public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) { - first.fillArc(x, y, width, height, startAngle, arcAngle); - second.fillArc(x, y, width, height, startAngle, arcAngle); - } - - @Override - public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) { - first.drawPolyline(xPoints, yPoints, nPoints); - second.drawPolyline(xPoints, yPoints, nPoints); - } - - @Override - public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) { - first.drawPolyline(xPoints, yPoints, nPoints); - second.drawPolyline(xPoints, yPoints, nPoints); - } - - @Override - public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) { - first.fillPolygon(xPoints, yPoints, nPoints); - second.fillPolygon(xPoints, yPoints, nPoints); - } - - @Override - public boolean drawImage(Image img, int x, int y, ImageObserver observer) { - boolean ok1 = first.drawImage(img, x, y, observer); - boolean ok2 = second.drawImage(img, x, y, observer); - return ok1 && ok2; - } - - @Override - public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) { - boolean ok1 = first.drawImage(img, x, y, width, height, observer); - boolean ok2 = second.drawImage(img, x, y, width, height, observer); - return ok1 && ok2; - } - - @Override - public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) { - boolean ok1 = first.drawImage(img, x, y, bgcolor, observer); - boolean ok2 = second.drawImage(img, x, y, bgcolor, observer); - return ok1 && ok2; - } - - @Override - public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) { - boolean ok1 = first.drawImage(img, x, y, width, height, bgcolor, observer); - boolean ok2 = second.drawImage(img, x, y, width, height, bgcolor, observer); - return ok1 && ok2; - } - - @Override - public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer) { - boolean ok1 = first.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer); - boolean ok2 = second.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer); - return ok1 && ok2; - } - - @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 observer) { - boolean ok1 = first.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer); - boolean ok2 = second.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer); - return ok1 && ok2; - } - - @Override - public void dispose() { - first.dispose(); - second.dispose(); - } - -} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/DualPdfGraphics2D.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/DualPdfGraphics2D.java new file mode 100644 index 000000000..801726fbd --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/DualPdfGraphics2D.java @@ -0,0 +1,514 @@ +package com.jpexs.decompiler.flash.exporters; + +import gnu.jpdf.PDFGraphics; +import java.awt.Color; +import java.awt.Composite; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.Image; +import java.awt.Paint; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; +import java.awt.image.ImageObserver; +import java.awt.image.RenderedImage; +import java.awt.image.renderable.RenderableImage; +import java.text.AttributedCharacterIterator; +import java.util.Map; + +/** + * + * @author JPEXS + */ +public class DualPdfGraphics2D extends Graphics2D implements BlendModeSetable { + + private final Graphics2D imageGraphics; + + private final PDFGraphics pdfGraphics; + + public DualPdfGraphics2D(Graphics2D first, PDFGraphics second) { + this.imageGraphics = first; + this.pdfGraphics = second; + } + + @Override + public void draw(Shape s) { + imageGraphics.draw(s); + pdfGraphics.draw(s); + } + + @Override + public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) { + boolean ok1 = imageGraphics.drawImage(img, xform, obs); + boolean ok2 = pdfGraphics.drawImage(img, xform, obs); + return ok1 && ok2; //? + } + + @Override + public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) { + imageGraphics.drawImage(img, op, x, y); + pdfGraphics.drawImage(img, op, x, y); + } + + @Override + public void drawRenderedImage(RenderedImage img, AffineTransform xform) { + imageGraphics.drawRenderedImage(img, xform); + pdfGraphics.drawRenderedImage(img, xform); + } + + @Override + public void drawRenderableImage(RenderableImage img, AffineTransform xform) { + imageGraphics.drawRenderableImage(img, xform); + pdfGraphics.drawRenderableImage(img, xform); + } + + @Override + public void drawString(String str, int x, int y) { + imageGraphics.drawString(str, x, y); + pdfGraphics.drawString(str, x, y); + } + + @Override + public void drawString(String str, float x, float y) { + imageGraphics.drawString(str, x, y); + pdfGraphics.drawString(str, x, y); + } + + @Override + public void drawString(AttributedCharacterIterator iterator, int x, int y) { + imageGraphics.drawString(iterator, x, y); + pdfGraphics.drawString(iterator, x, y); + } + + @Override + public void drawString(AttributedCharacterIterator iterator, float x, float y) { + imageGraphics.drawString(iterator, x, y); + pdfGraphics.drawString(iterator, x, y); + } + + @Override + public void drawGlyphVector(GlyphVector g, float x, float y) { + imageGraphics.drawGlyphVector(g, x, y); + pdfGraphics.drawGlyphVector(g, x, y); + } + + @Override + public void fill(Shape s) { + imageGraphics.fill(s); + pdfGraphics.fill(s); + } + + @Override + public boolean hit(Rectangle rect, Shape s, boolean onStroke) { + boolean ok1 = imageGraphics.hit(rect, s, onStroke); + boolean ok2 = pdfGraphics.hit(rect, s, onStroke); + return ok1 && ok2; //? + } + + @Override + public GraphicsConfiguration getDeviceConfiguration() { + return imageGraphics.getDeviceConfiguration(); //? + } + + @Override + public void setComposite(Composite comp) { + imageGraphics.setComposite(comp); + pdfGraphics.setComposite(comp); + } + + @Override + public void setPaint(Paint paint) { + imageGraphics.setPaint(paint); + pdfGraphics.setPaint(paint); + } + + @Override + public void setStroke(Stroke s) { + imageGraphics.setStroke(s); + pdfGraphics.setStroke(s); + } + + @Override + public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue) { + imageGraphics.setRenderingHint(hintKey, hintValue); + pdfGraphics.setRenderingHint(hintKey, hintValue); + } + + @Override + public Object getRenderingHint(RenderingHints.Key hintKey) { + return imageGraphics.getRenderingHint(hintKey); //? + } + + @Override + public void setRenderingHints(Map hints) { + imageGraphics.setRenderingHints(hints); + pdfGraphics.setRenderingHints(hints); + } + + @Override + public void addRenderingHints(Map hints) { + imageGraphics.addRenderingHints(hints); + pdfGraphics.addRenderingHints(hints); + } + + @Override + public RenderingHints getRenderingHints() { + return imageGraphics.getRenderingHints(); + } + + @Override + public void translate(int x, int y) { + imageGraphics.translate(x, y); + pdfGraphics.translate(x, y); + } + + @Override + public void translate(double tx, double ty) { + imageGraphics.translate(tx, ty); + pdfGraphics.translate(tx, ty); + } + + @Override + public void rotate(double theta) { + imageGraphics.rotate(theta); + pdfGraphics.rotate(theta); + } + + @Override + public void rotate(double theta, double x, double y) { + imageGraphics.rotate(theta, x, y); + pdfGraphics.rotate(theta, x, y); + } + + @Override + public void scale(double sx, double sy) { + imageGraphics.scale(sx, sy); + pdfGraphics.scale(sx, sy); + } + + @Override + public void shear(double shx, double shy) { + imageGraphics.shear(shx, shy); + pdfGraphics.shear(shx, shy); + } + + @Override + public void transform(AffineTransform Tx) { + imageGraphics.transform(Tx); + pdfGraphics.transform(Tx); + } + + @Override + public void setTransform(AffineTransform Tx) { + imageGraphics.setTransform(Tx); + pdfGraphics.setTransform(Tx); + } + + @Override + public AffineTransform getTransform() { + return imageGraphics.getTransform(); + } + + @Override + public Paint getPaint() { + return imageGraphics.getPaint(); + } + + @Override + public Composite getComposite() { + return imageGraphics.getComposite(); + } + + @Override + public void setBackground(Color color) { + imageGraphics.setBackground(color); + pdfGraphics.setBackground(color); + } + + @Override + public Color getBackground() { + return imageGraphics.getBackground(); + } + + @Override + public Stroke getStroke() { + return imageGraphics.getStroke(); + } + + @Override + public void clip(Shape s) { + imageGraphics.clip(s); + pdfGraphics.clip(s); + } + + @Override + public FontRenderContext getFontRenderContext() { + return imageGraphics.getFontRenderContext(); + } + + @Override + public Graphics create() { + return this; //? + } + + @Override + public Color getColor() { + return imageGraphics.getColor(); + } + + @Override + public void setColor(Color c) { + imageGraphics.setColor(c); + pdfGraphics.setColor(c); + } + + @Override + public void setPaintMode() { + imageGraphics.setPaintMode(); + pdfGraphics.setPaintMode(); + } + + @Override + public void setXORMode(Color c1) { + imageGraphics.setXORMode(c1); + pdfGraphics.setXORMode(c1); + } + + @Override + public Font getFont() { + return imageGraphics.getFont(); + } + + @Override + public void setFont(Font font) { + imageGraphics.setFont(font); + pdfGraphics.setFont(font); + } + + @Override + public FontMetrics getFontMetrics(Font f) { + return imageGraphics.getFontMetrics(); + } + + @Override + public Rectangle getClipBounds() { + return imageGraphics.getClipBounds(); + } + + @Override + public void clipRect(int x, int y, int width, int height) { + imageGraphics.clearRect(x, y, width, height); + pdfGraphics.clipRect(x, y, width, height); + } + + @Override + public void setClip(int x, int y, int width, int height) { + imageGraphics.setClip(x, y, width, height); + pdfGraphics.setClip(x, y, width, height); + } + + @Override + public Shape getClip() { + return imageGraphics.getClip(); + } + + @Override + public void setClip(Shape clip) { + imageGraphics.setClip(clip); + pdfGraphics.setClip(clip); + } + + @Override + public void copyArea(int x, int y, int width, int height, int dx, int dy) { + imageGraphics.copyArea(x, y, width, height, dx, dy); + pdfGraphics.copyArea(x, y, width, height, dx, dy); + } + + @Override + public void drawLine(int x1, int y1, int x2, int y2) { + imageGraphics.drawLine(x1, y1, x2, y2); + pdfGraphics.drawLine(x1, y1, x2, y2); + } + + @Override + public void fillRect(int x, int y, int width, int height) { + imageGraphics.fillRect(x, y, width, height); + pdfGraphics.fillRect(x, y, width, height); + } + + @Override + public void clearRect(int x, int y, int width, int height) { + imageGraphics.clearRect(x, y, width, height); + pdfGraphics.clearRect(x, y, width, height); + } + + @Override + public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { + imageGraphics.drawRoundRect(x, y, width, height, arcWidth, arcHeight); + pdfGraphics.drawRoundRect(x, y, width, height, arcWidth, arcHeight); + } + + @Override + public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { + imageGraphics.fillRoundRect(x, y, width, height, arcWidth, arcHeight); + pdfGraphics.fillRoundRect(x, y, width, height, arcWidth, arcHeight); + } + + @Override + public void drawOval(int x, int y, int width, int height) { + imageGraphics.drawOval(x, y, width, height); + pdfGraphics.drawOval(x, y, width, height); + } + + @Override + public void fillOval(int x, int y, int width, int height) { + imageGraphics.fillOval(x, y, width, height); + pdfGraphics.fillOval(x, y, width, height); + } + + @Override + public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) { + imageGraphics.drawArc(x, y, width, height, startAngle, arcAngle); + pdfGraphics.drawArc(x, y, width, height, startAngle, arcAngle); + } + + @Override + public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) { + imageGraphics.fillArc(x, y, width, height, startAngle, arcAngle); + pdfGraphics.fillArc(x, y, width, height, startAngle, arcAngle); + } + + @Override + public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) { + imageGraphics.drawPolyline(xPoints, yPoints, nPoints); + pdfGraphics.drawPolyline(xPoints, yPoints, nPoints); + } + + @Override + public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) { + imageGraphics.drawPolyline(xPoints, yPoints, nPoints); + pdfGraphics.drawPolyline(xPoints, yPoints, nPoints); + } + + @Override + public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) { + imageGraphics.fillPolygon(xPoints, yPoints, nPoints); + pdfGraphics.fillPolygon(xPoints, yPoints, nPoints); + } + + @Override + public boolean drawImage(Image img, int x, int y, ImageObserver observer) { + boolean ok1 = imageGraphics.drawImage(img, x, y, observer); + boolean ok2 = pdfGraphics.drawImage(img, x, y, observer); + return ok1 && ok2; + } + + @Override + public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) { + boolean ok1 = imageGraphics.drawImage(img, x, y, width, height, observer); + boolean ok2 = pdfGraphics.drawImage(img, x, y, width, height, observer); + return ok1 && ok2; + } + + @Override + public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) { + boolean ok1 = imageGraphics.drawImage(img, x, y, bgcolor, observer); + boolean ok2 = pdfGraphics.drawImage(img, x, y, bgcolor, observer); + return ok1 && ok2; + } + + @Override + public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) { + boolean ok1 = imageGraphics.drawImage(img, x, y, width, height, bgcolor, observer); + boolean ok2 = pdfGraphics.drawImage(img, x, y, width, height, bgcolor, observer); + return ok1 && ok2; + } + + @Override + public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer) { + boolean ok1 = imageGraphics.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer); + boolean ok2 = pdfGraphics.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer); + return ok1 && ok2; + } + + @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 observer) { + boolean ok1 = imageGraphics.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer); + boolean ok2 = pdfGraphics.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer); + return ok1 && ok2; + } + + @Override + public void dispose() { + imageGraphics.dispose(); + pdfGraphics.dispose(); + } + + @Override + public void setBlendMode(int mode) { + switch (mode) { + case 0: + case 1: + pdfGraphics.setBlendMode("Normal"); + break; + case 2: + //Layer + pdfGraphics.setBlendMode("Normal"); + break; + case 3: + pdfGraphics.setBlendMode("Multiply"); + break; + case 4: + pdfGraphics.setBlendMode("Screen"); + break; + case 5: + pdfGraphics.setBlendMode("Lighten"); + break; + case 6: + pdfGraphics.setBlendMode("Darken"); + break; + case 7: + pdfGraphics.setBlendMode("Difference"); + break; + case 8: + //Add + pdfGraphics.setBlendMode("Normal"); + break; + case 9: + //Subtract + pdfGraphics.setBlendMode("Normal"); + break; + case 10: + //Invert + pdfGraphics.setBlendMode("Normal"); + break; + case 11: + //Alpha + pdfGraphics.setBlendMode("Normal"); + break; + case 12: + //Erase + pdfGraphics.setBlendMode("Normal"); + break; + case 13: + pdfGraphics.setBlendMode("Overlay"); + break; + case 14: + pdfGraphics.setBlendMode("HardLight"); + break; + default: // Not implemented + pdfGraphics.setBlendMode("Normal"); + break; + } + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FrameExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FrameExporter.java index b4f6dc585..11679bcda 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FrameExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FrameExporter.java @@ -533,7 +533,7 @@ public class FrameExporter { return compositeGraphics; } final Graphics2D parentGraphics = (Graphics2D) super.getGraphics(); - compositeGraphics = new DualGraphics2D(parentGraphics, g); + compositeGraphics = new DualPdfGraphics2D(parentGraphics, (PDFGraphics) g); return compositeGraphics; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java index 9b869c7b9..416d92d69 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java @@ -17,6 +17,7 @@ package com.jpexs.decompiler.flash.timeline; import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.exporters.BlendModeSetable; import com.jpexs.decompiler.flash.exporters.FrameExporter; import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle; import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; @@ -694,7 +695,6 @@ public class Timeline { //strokeTransform = strokeTransform.preConcatenate(Matrix.getTranslateInstance(-rect.xMin, -rect.yMin)); //strokeTransform = strokeTransform.clone(); //strokeTransform.translate(-rect.xMin, -rect.yMin); - if (drawable instanceof ButtonTag) { dframe = ButtonTag.FRAME_UP; if (renderContext.cursorPosition != null) { @@ -710,7 +710,6 @@ public class Timeline { } } - if (filters != null && !filters.isEmpty()) { canUseSameImage = false; } @@ -767,55 +766,58 @@ public class Timeline { AffineTransform trans = drawMatrix.toTransform(); - switch (blendMode) { - case 0: - case 1: - g.setComposite(AlphaComposite.SrcOver); - break; - case 2: // Layer - g.setComposite(AlphaComposite.SrcOver); - break; - case 3: - g.setComposite(BlendComposite.Multiply); - break; - case 4: - g.setComposite(BlendComposite.Screen); - break; - case 5: - g.setComposite(BlendComposite.Lighten); - break; - case 6: - g.setComposite(BlendComposite.Darken); - break; - case 7: - g.setComposite(BlendComposite.Difference); - break; - case 8: - g.setComposite(BlendComposite.Add); - break; - case 9: - g.setComposite(BlendComposite.Subtract); - break; - case 10: - g.setComposite(BlendComposite.Invert); - break; - case 11: - g.setComposite(BlendComposite.Alpha); - break; - case 12: - g.setComposite(BlendComposite.Erase); - break; - case 13: - g.setComposite(BlendComposite.Overlay); - break; - case 14: - g.setComposite(BlendComposite.HardLight); - break; - default: // Not implemented - g.setComposite(AlphaComposite.SrcOver); - break; + if (g instanceof BlendModeSetable) { + ((BlendModeSetable) g).setBlendMode(blendMode); + } else { + switch (blendMode) { + case 0: + case 1: + g.setComposite(AlphaComposite.SrcOver); + break; + case 2: // Layer + g.setComposite(AlphaComposite.SrcOver); + break; + case 3: + g.setComposite(BlendComposite.Multiply); + break; + case 4: + g.setComposite(BlendComposite.Screen); + break; + case 5: + g.setComposite(BlendComposite.Lighten); + break; + case 6: + g.setComposite(BlendComposite.Darken); + break; + case 7: + g.setComposite(BlendComposite.Difference); + break; + case 8: + g.setComposite(BlendComposite.Add); + break; + case 9: + g.setComposite(BlendComposite.Subtract); + break; + case 10: + g.setComposite(BlendComposite.Invert); + break; + case 11: + g.setComposite(BlendComposite.Alpha); + break; + case 12: + g.setComposite(BlendComposite.Erase); + break; + case 13: + g.setComposite(BlendComposite.Overlay); + break; + case 14: + g.setComposite(BlendComposite.HardLight); + break; + default: // Not implemented + g.setComposite(AlphaComposite.SrcOver); + break; + } } - if (clipDepth > -1) { BufferedImage mask = new BufferedImage(image.getWidth(), image.getHeight(), image.getType()); Graphics2D gm = (Graphics2D) mask.getGraphics(); @@ -855,6 +857,9 @@ public class Timeline { g.setTransform(drawMatrix.toTransform()); g.drawImage(img.getBufferedImage(), 0, 0, null); } + if (g instanceof BlendModeSetable) { + ((BlendModeSetable) g).setBlendMode(0); + } } } diff --git a/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java b/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java index 6710eb786..c5a7c4f53 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 int currentAlpha = 255; private int shadingCount = 0; private static int[] srgbToLinear = new int[256]; static { for (int i = 0; i < 256; i++) { srgbToLinear[i] = convertSRGBtoLinearRGB(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); } /** * @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); image = new PDFImage(img, x, y, w, h, obs, "" + mask.getSerialID() + " 0 R"); // The image needs to be registered in several places page.getPDFDocument().setImageName(image); page.getPDFDocument().add(image); usedImages.put(img, image); } page.addToProcset("/ImageC"); page.addImageResource(image.getName() + " " + image.getSerialID() + " 0 R"); // JM /*page.addResource("/XObject << " + image.getName() + " " + image.getSerialID() + " 0 R >>");*/ // q w 0 0 h x y cm % the coordinate matrix AffineTransform newTransform = new AffineTransform(w, 0, 0, -h, x, y + h); AffineTransform transformToSet = newTransform; pw.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); return false; } /** * Draw's an image onto the page, with scaling *

* This is not yet supported. * * @param img The java.awt.Image * @param dx1 coordinate on page * @param dy1 coordinate on page * @param dx2 coordinate on page * @param dy2 coordinate on page * @param sx1 coordinate on image * @param sy1 coordinate on image * @param sx2 coordinate on image * @param sy2 coordinate on image * @param bgcolor Background colour * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver obs) { return false; } //============ Clipping operations ======================= /** * Draw's an image onto the page, with scaling *

* This is not yet supported. * * @param img The java.awt.Image * @param dx1 coordinate on page * @param dy1 coordinate on page * @param dx2 coordinate on page * @param dy2 coordinate on page * @param sx1 coordinate on image * @param sy1 coordinate on image * @param sx2 coordinate on image * @param sy2 coordinate on image * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver obs) { // This shouldn't be too bad, just change the coordinate matrix return false; } /** * Draws a line between two coordinates. * * If the first coordinate is the same as the last one drawn (i.e. a * previous drawLine, moveto, etc) it is ignored. * * @param x1 coordinate * @param y1 coordinate * @param x2 coordinate * @param y2 coordinate */ @Override public void drawLine(int x1, int y1, int x2, int y2) { moveto(x1, y1); lineto(x2, y2); } //============ Arcs operations ============================== // These are the standard Graphics operators. They use the // arc extension operators to achieve the affect. /** *

* Draws an oval

* * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void drawOval(int x, int y, int w, int h) { drawArc(x, y, w, h, 0, 360); } /** * Draws a polygon, linking the first and last coordinates. * * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polygon */ @Override public void drawPolygon(int[] xp, int[] yp, int np) { polygon(xp, yp, np); closeBlock("s"); // close path and stroke } /** * Draws a polyline. The first and last coordinates are not linked. * * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polyline */ @Override public void drawPolyline(int[] xp, int[] yp, int np) { polygon(xp, yp, np); // no stroke, as we keep the optimizer in stroke state } /** * We override Graphics.drawRect as it doesn't join the 4 lines. Also, PDF * provides us with a Rectangle operator, so we will use that. * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void drawRect(int x, int y, int w, int h) { draw(new Rectangle(x, y, w, h)); /*newPath(); pw.print(cxy(x, y) + cwh(w, h) + "re "); // rectangle lx = x; // I don't know if this is correct, but lets see if PDF ends ly = y; // the rectangle at it's start. // stroke (optimized)*/ } /** * @see Graphics2D#drawRenderableImage(RenderableImage, AffineTransform) */ @Override public void drawRenderableImage(RenderableImage img, AffineTransform xform) { drawRenderedImage(img.createDefaultRendering(), xform); } /** * @see Graphics2D#drawRenderedImage(RenderedImage, AffineTransform) */ @Override public void drawRenderedImage(RenderedImage img, AffineTransform xform) { BufferedImage image = null; if (img instanceof BufferedImage) { image = (BufferedImage) img; } else { ColorModel cm = img.getColorModel(); int width = img.getWidth(); int height = img.getHeight(); WritableRaster raster = cm.createCompatibleWritableRaster(width, height); boolean isAlphaPremultiplied = cm.isAlphaPremultiplied(); Hashtable 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() { return null; } /** * Returns the Rectangle that fits the current clipping region * * @return the Rectangle that fits the current clipping region */ @Override public Rectangle getClipBounds() { return clipRectangle; } //============ Color operations ======================= /** * Returns the current pen Colour * * @return the current pen Colour */ @Override public Color getColor() { return (paint instanceof Color) ? (Color) paint : Color.black; } /** * @see Graphics2D#getComposite() */ @Override public Composite getComposite() { return composite; } /** * @see Graphics2D#getDeviceConfiguration() */ @Override public GraphicsConfiguration getDeviceConfiguration() { return dg2.getDeviceConfiguration(); } /** * Return's the current font. * * @return the current font. */ @Override public Font getFont() { if (font == null) { setFont(new Font("SansSerif", Font.PLAIN, 12)); } return font; } /** * Returns the FontMetrics for a font. *

* This doesn't work correctly. Perhaps having some way of mapping the base * 14 fonts to our own FontMetrics implementation? * * @param font The java.awt.Font to return the metrics for * @return FontMetrics for a font */ @Override public FontMetrics getFontMetrics(Font font) { Frame dummy = new Frame(); dummy.addNotify(); Image image = dummy.createImage(100, 100); if (image == null) { System.err.println("getFontMetrics: image is null"); } Graphics graphics = image.getGraphics(); return graphics.getFontMetrics(font); } /** * @see Graphics2D#getFontRenderContext() */ @Override public FontRenderContext getFontRenderContext() { boolean antialias = RenderingHints.VALUE_TEXT_ANTIALIAS_ON.equals(getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING)); boolean fractions = RenderingHints.VALUE_FRACTIONALMETRICS_ON.equals(getRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS)); return new FontRenderContext(new AffineTransform(), antialias, fractions); } /** * Returns the associated PDFPage for this graphic * * @return the associated PDFPage for this graphic */ public PDFPage getPage() { return page; } /** * Returns the current pen Colour * * @return the current pen Colour */ @Override public Paint getPaint() { return paint; } /** * @param arg0 a key * @return the rendering hint */ @Override public Object getRenderingHint(Key arg0) { return rhints.get(arg0); } /** * @see Graphics2D#getRenderingHints() */ @Override public RenderingHints getRenderingHints() { return rhints; } /** * @see Graphics2D#getStroke() */ @Override public Stroke getStroke() { return stroke; } /** * @see Graphics2D#getTransform() */ @Override public AffineTransform getTransform() { return new AffineTransform(transform); } /** * Returns the PrintWriter handling the underlying stream * * @return the PrintWriter handling the underlying stream */ public RawPrintWriter getWriter() { return pw; } /** * @see Graphics2D#hit(Rectangle, Shape, boolean) */ @Override public boolean hit(Rectangle rect, Shape s, boolean onStroke) { if (onStroke) { s = stroke.createStrokedShape(s); } Area area = new Area(s); if (clip != null) { area.intersect(clip); } return area.intersects(rect.x, rect.y, rect.width, rect.height); } /** * This initialises the stream by saving the current graphics state, and * setting up the default line width (for us). * * It also sets up the instance ready for graphic operations and any * optimisations. * *

* For child instances, the stream is already open, so this should keep * things happy. */ private void init() { PageFormat pf = page.getPageFormat(); // save graphics state (restored by dispose) if (child) { pw.print("q "); } // now initialise the instance //setColor(Color.black); paint = Color.black; // possible: if parent.color is not black, then force black? // must check to see what AWT does? // Original User Space Transform (identity) // Transform from Java Space to PDF Space pTransform = new AffineTransform(); pTransform.translate(0, pf.getHeight()); pTransform.scale(1d, -1d); // Combined Transform User->Java->PDF setNewTranform(new AffineTransform()); // Set the line width setStroke(DEF_STROKE); } /** * This is called by PDFPage when creating a Graphcis instance. * * @param page The PDFPage to draw onto. */ protected void init(PDFPage page) { this.page = page; // We are the parent instance child = false; // Now create a stream to store the graphics in PDFStream stream = new PDFStream(); // To view detail in 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 = new AffineTransform(pTransform); pw.println("" + df.format(tm.getScaleX()) + " " + "" + df.format(tm.getShearY()) + " " + "" + df.format(tm.getShearX()) + " " + "" + df.format(tm.getScaleY()) + " " + "" + df.format(tm.getTranslateX()) + " " + "" + df.format(tm.getTranslateY()) + " Tm" ); // produce the text matrix for the media // switch(mediaRot) { // case PageFormat.PORTRAIT: // Portrait // //pw.println("1 0 0 1 0 0 Tm"); // break; // // case PageFormat.LANDSCAPE: // Landscape // pw.println("0 1 -1 0 0 0 Tm"); // rotate // break; // // case 180: // Inverted Portrait // pw.println("1 0 0 -1 0 0 Tm"); // break; // // case PageFormat.REVERSE_LANDSCAPE: // Seascape // pw.println("0 -1 1 0 0 0 Tm"); // rotate // break; // } // move the text cursor by an absolute amount pw.print(txy(x, y) + "Td "); } else { // move the text cursor by a relative amount pw.print(twh(x, y, tx, ty) + "Td "); //pw.print(txy(x,y)+"Td "); } // preserve the coordinates for the next time tx = x; ty = y; if (newFont || !inText) { pw.print(pdffont.getName() + " " + font.getSize() + " Tf "); } // later add colour changes here (if required) inStroke = newFont = false; inText = true; } /** * This is used to add a polygon to the current path. Used by drawPolygon(), * drawPolyline() and fillPolygon() etal * * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polygon * @see #drawPolygon * @see #drawPolyline * @see #fillPolygon */ public void polygon(int[] xp, int[] yp, int np) { // newPath() not needed here as moveto does it ;-) moveto(xp[0], yp[0]); for (int i = 1; i < np; i++) { lineto(xp[i], yp[i]); } } /** * @see Graphics2D#rotate(double) */ @Override public void rotate(double theta) { AffineTransform newTransform = new AffineTransform(transform); newTransform.rotate(theta); setNewTranform(newTransform); } /** * @see Graphics2D#rotate(double, double, double) */ @Override public void rotate(double theta, double x, double y) { AffineTransform newTransform = new AffineTransform(transform); newTransform.rotate(theta, x, y); setNewTranform(newTransform); } /** * @see Graphics2D#scale(double, double) */ @Override public void scale(double sx, double sy) { AffineTransform newTransform = new AffineTransform(transform); newTransform.scale(sx, sy); setNewTranform(newTransform); } /** * @see Graphics2D#setBackground(Color) */ @Override public void setBackground(Color color) { background = color; } /** * Clips to a set of coordinates * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void setClip(int x, int y, int w, int h) { /*clipRectangle = new Rectangle(x, y, w, h); closeBlock(); // finish off any existing paths drawRect(x, y, w, h); closeBlock("W n"); // clip to current path*/ setClip(new Rectangle(x, y, w, h)); } /** * As my JDK docs say, this may break with Java 2D. *

* Sets the clipping region to that of a Shape. * * @param s Shape to clip to. */ @Override public void setClip(Shape s) { closeBlock(); if (clip != null) { restoreState(); transform = clipTransform; } if (s == null) { clip = null; return; } clipTransform = transform; clip = new Area(s); clipRectangle = s.getBounds(); saveState(); followPath(s, CLIP); pw.println("n"); //setClip(r.x, r.y, r.width, r.height); } /** * Sets the color for drawing * * @param c Color to use */ @Override public void setColor(Color c) { setPaint(c); } /** * @see Graphics2D#setComposite(Composite) */ @Override public void setComposite(Composite comp) { this.composite = comp; } /** * This extension sets the line width to the default of 1mm which is what * Java uses when drawing to a PrintJob. */ public void setDefaultLineWidth() { closeBlock(); // draw any path before we change the line width pw.println("1 w"); } /** * This sets the font. * * @param f java.awt.Font to set to. */ @Override public void setFont(Font f) { // optimize: Save some space if the font is already the current one. if (font != f) { font = f; pdffont = page.getFont("/Type1", f.getName(), f.getStyle()); // mark the font as changed newFont = true; } } public void setExistingTtfFont(Font f) { if (font != f) { font = f; pdffont = page.getFont("/TrueType", f.getName(), f.getStyle()); // mark the font as changed newFont = true; } } public void setTtfFont(Font f, File file) throws IOException { if (font != f) { font = f; pdffont = page.getEmbeddedFont(f.getName(), f.getStyle(), file); // mark the font as changed newFont = true; } } private void setLineCap(int cap) { int lineCap = 0; switch (cap) { case BasicStroke.JOIN_MITER: lineCap = 0; break; case BasicStroke.JOIN_ROUND: lineCap = 1; break; case BasicStroke.JOIN_BEVEL: lineCap = 2; break; } if (this.lineCap != lineCap) { closeBlock(); // draw any path before we change the line width this.lineCap = lineCap; pw.println("" + lineCap + " J"); } } private void setLineJoin(int join) { int lineJoin = 0; switch (join) { case BasicStroke.JOIN_MITER: lineJoin = 0; break; case BasicStroke.JOIN_ROUND: lineJoin = 1; break; case BasicStroke.JOIN_BEVEL: lineJoin = 2; break; } if (this.lineJoin != lineJoin) { closeBlock(); // draw any path before we change the line width this.lineJoin = lineJoin; pw.println("" + lineJoin + " j"); } } /** * This extension allows the width of the drawn line to be set * * @param width Line width in pdf graphic units (points) */ public void setLineWidth(float width) { if (width != this.lineWidth) { closeBlock(); // draw any path before we change the line width this.lineWidth = width; pw.println("" + width + " w"); } } private void setMiterLimit(float limit) { if (limit != this.miterLimit) { closeBlock(); // draw any path before we change the line width this.miterLimit = limit; pw.println("" + limit + " M"); } } /** * Sets the paint for drawing * * @param paint Paint to use */ @Override public void setPaint(Paint paint) { this.paint = paint; this.shading = null; this.pattern = null; this.paintTransform = null; if (paint instanceof Color) { Color c = (Color) paint; double r = ((double) c.getRed()) / 255.0; double g = ((double) c.getGreen()) / 255.0; double b = ((double) c.getBlue()) / 255.0; closeBlock(); // This ensures any paths are drawn in the previous if (currentAlpha != c.getAlpha()) { String gsId = "/GSAlpha" + c.getAlpha(); currentAlpha = c.getAlpha(); if (!usedAlphas.contains(c.getAlpha())) { page.addExtGStateResource(gsId + " <>"); usedAlphas.add(currentAlpha); } pw.println(gsId + " gs"); } // colours pw.println("" + r + " " + g + " " + b + " rg " + r + " " + g + " " + b + " RG"); } if (paint instanceof MultipleGradientPaint) { closeBlock(); if ((paint instanceof RadialGradientPaint) || ((paint instanceof LinearGradientPaint) && (((LinearGradientPaint) paint).getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE))) { shading = "gradient"; } else { pattern = "gradient"; } } if (paint instanceof TexturePaint) { closeBlock(); pattern = "texture"; } } private boolean 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 { 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]; 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()); 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()); 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()); //pdf reference, page 168 os.write("/Domain [-1000000 1000000 -1000000 1000000]\n".getBytes()); os.write("/Range [0 1 0 1 0 1]\n".getBytes()); //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()); //pdf reference, page 168 os.write("/Domain [-1000000 1000000 -1000000 1000000]\n".getBytes()); os.write("/Range [0 1 0 1 0 1]\n".getBytes()); //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()); } 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()); } 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()); os.write("/Type /XObject\n".getBytes()); os.write("/Resources <<\n".getBytes()); os.write("/Shading <<".getBytes()); os.write(("/ShA" + fCurrentShadingCount + " " + shadingAlphaObj.getSerialID() + " 0 R").getBytes()); os.write(">>\n".getBytes()); //shading os.write(">>\n".getBytes()); //resources os.write("/Subtype /Form\n".getBytes()); os.write("/BBox [-1000000 -1000000 2000000 2000000]\n".getBytes()); //fixme writeStream(os); } }; OutputStream alphaOs = alphaObject.getOutputStream(); try { alphaOs.write(("/ShA" + fCurrentShadingCount + " sh\n").getBytes()); } 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(); 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()); os.write("/PaintType 1\n".getBytes()); os.write("/TilingType 2\n".getBytes()); os.write(("/BBox [0 0 " + w + " " + h + "]\n").getBytes()); os.write(("/XStep " + w + "\n").getBytes()); os.write(("/YStep " + h + "\n").getBytes()); os.write(("/Resources << " + "/Shading << /Shin" + fCurrentShadingCount + " " + shadingObj.getSerialID() + " 0 R >>" + "/ExtGState <<" + alphaExtGState + ">>" + ">>\n").getBytes()); os.write(("/Matrix [" + matrixStr + "]\n").getBytes()); writeStream(os); } }; OutputStream patOs = innerPattern.getOutputStream(); try { patOs.write(("/GradAlpha" + fCurrentShadingCount + " gs").getBytes()); patOs.write(("/Shin" + shadingCount + " sh").getBytes()); } 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(); 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"); // 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(); 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()); os.write("/PaintType 1\n".getBytes()); os.write("/TilingType 2\n".getBytes()); os.write(("/BBox [0 0 " + matDf.format(w) + " " + matDf.format(h) + "]\n").getBytes()); os.write(("/XStep " + matDf.format(w) + "\n").getBytes()); os.write(("/YStep " + matDf.format(h) + "\n").getBytes()); os.write(("/Resources << ").getBytes()); os.write("/XObject << ".getBytes()); os.write((image.getName() + " " + image.getSerialID() + " 0 R").getBytes()); os.write(" >> ".getBytes()); os.write((">>\n").getBytes()); //"1 0 0 1 0 0" os.write(("/Matrix [" + matrixStr + "]\n").getBytes()); 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(); 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 (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); } 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"); } } \ 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.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 static int[] srgbToLinear = new int[256]; static { for (int i = 0; i < 256; i++) { srgbToLinear[i] = convertSRGBtoLinearRGB(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); } /** * @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); image = new PDFImage(img, x, y, w, h, obs, "" + mask.getSerialID() + " 0 R"); // The image needs to be registered in several places page.getPDFDocument().setImageName(image); page.getPDFDocument().add(image); usedImages.put(img, image); } page.addToProcset("/ImageC"); page.addImageResource(image.getName() + " " + image.getSerialID() + " 0 R"); 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() { return null; } /** * Returns the Rectangle that fits the current clipping region * * @return the Rectangle that fits the current clipping region */ @Override public Rectangle getClipBounds() { return clipRectangle; } //============ Color operations ======================= /** * Returns the current pen Colour * * @return the current pen Colour */ @Override public Color getColor() { return (paint instanceof Color) ? (Color) paint : Color.black; } /** * @see Graphics2D#getComposite() */ @Override public Composite getComposite() { return composite; } /** * @see Graphics2D#getDeviceConfiguration() */ @Override public GraphicsConfiguration getDeviceConfiguration() { return dg2.getDeviceConfiguration(); } /** * Return's the current font. * * @return the current font. */ @Override public Font getFont() { if (font == null) { setFont(new Font("SansSerif", Font.PLAIN, 12)); } return font; } /** * Returns the FontMetrics for a font. *

* This doesn't work correctly. Perhaps having some way of mapping the base * 14 fonts to our own FontMetrics implementation? * * @param font The java.awt.Font to return the metrics for * @return FontMetrics for a font */ @Override public FontMetrics getFontMetrics(Font font) { Frame dummy = new Frame(); dummy.addNotify(); Image image = dummy.createImage(100, 100); if (image == null) { System.err.println("getFontMetrics: image is null"); } Graphics graphics = image.getGraphics(); return graphics.getFontMetrics(font); } /** * @see Graphics2D#getFontRenderContext() */ @Override public FontRenderContext getFontRenderContext() { boolean antialias = RenderingHints.VALUE_TEXT_ANTIALIAS_ON.equals(getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING)); boolean fractions = RenderingHints.VALUE_FRACTIONALMETRICS_ON.equals(getRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS)); return new FontRenderContext(new AffineTransform(), antialias, fractions); } /** * Returns the associated PDFPage for this graphic * * @return the associated PDFPage for this graphic */ public PDFPage getPage() { return page; } /** * Returns the current pen Colour * * @return the current pen Colour */ @Override public Paint getPaint() { return paint; } /** * @param arg0 a key * @return the rendering hint */ @Override public Object getRenderingHint(Key arg0) { return rhints.get(arg0); } /** * @see Graphics2D#getRenderingHints() */ @Override public RenderingHints getRenderingHints() { return rhints; } /** * @see Graphics2D#getStroke() */ @Override public Stroke getStroke() { return stroke; } /** * @see Graphics2D#getTransform() */ @Override public AffineTransform getTransform() { return new AffineTransform(transform); } /** * Returns the PrintWriter handling the underlying stream * * @return the PrintWriter handling the underlying stream */ public RawPrintWriter getWriter() { return pw; } /** * @see Graphics2D#hit(Rectangle, Shape, boolean) */ @Override public boolean hit(Rectangle rect, Shape s, boolean onStroke) { if (onStroke) { s = stroke.createStrokedShape(s); } Area area = new Area(s); if (clip != null) { area.intersect(clip); } return area.intersects(rect.x, rect.y, rect.width, rect.height); } /** * This initialises the stream by saving the current graphics state, and * setting up the default line width (for us). * * It also sets up the instance ready for graphic operations and any * optimisations. * *

* For child instances, the stream is already open, so this should keep * things happy. */ private void init() { PageFormat pf = page.getPageFormat(); // save graphics state (restored by dispose) if (child) { pw.print("q "); } // now initialise the instance //setColor(Color.black); paint = Color.black; // possible: if parent.color is not black, then force black? // must check to see what AWT does? // Original User Space Transform (identity) // Transform from Java Space to PDF Space pTransform = new AffineTransform(); pTransform.translate(0, pf.getHeight()); pTransform.scale(1d, -1d); // Combined Transform User->Java->PDF setNewTranform(new AffineTransform()); // Set the line width setStroke(DEF_STROKE); } /** * This is called by PDFPage when creating a Graphcis instance. * * @param page The PDFPage to draw onto. */ protected void init(PDFPage page) { this.page = page; // We are the parent instance child = false; // Now create a stream to store the graphics in PDFStream stream = new PDFStream(); // To view detail in 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 = new AffineTransform(pTransform); pw.println("" + df.format(tm.getScaleX()) + " " + "" + df.format(tm.getShearY()) + " " + "" + df.format(tm.getShearX()) + " " + "" + df.format(tm.getScaleY()) + " " + "" + df.format(tm.getTranslateX()) + " " + "" + df.format(tm.getTranslateY()) + " Tm" ); // produce the text matrix for the media // switch(mediaRot) { // case PageFormat.PORTRAIT: // Portrait // //pw.println("1 0 0 1 0 0 Tm"); // break; // // case PageFormat.LANDSCAPE: // Landscape // pw.println("0 1 -1 0 0 0 Tm"); // rotate // break; // // case 180: // Inverted Portrait // pw.println("1 0 0 -1 0 0 Tm"); // break; // // case PageFormat.REVERSE_LANDSCAPE: // Seascape // pw.println("0 -1 1 0 0 0 Tm"); // rotate // break; // } // move the text cursor by an absolute amount pw.print(txy(x, y) + "Td "); } else { // move the text cursor by a relative amount pw.print(twh(x, y, tx, ty) + "Td "); //pw.print(txy(x,y)+"Td "); } // preserve the coordinates for the next time tx = x; ty = y; if (newFont || !inText) { pw.print(pdffont.getName() + " " + font.getSize() + " Tf "); } // later add colour changes here (if required) inStroke = newFont = false; inText = true; } /** * This is used to add a polygon to the current path. Used by drawPolygon(), * drawPolyline() and fillPolygon() etal * * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polygon * @see #drawPolygon * @see #drawPolyline * @see #fillPolygon */ public void polygon(int[] xp, int[] yp, int np) { // newPath() not needed here as moveto does it ;-) moveto(xp[0], yp[0]); for (int i = 1; i < np; i++) { lineto(xp[i], yp[i]); } } /** * @see Graphics2D#rotate(double) */ @Override public void rotate(double theta) { AffineTransform newTransform = new AffineTransform(transform); newTransform.rotate(theta); setNewTranform(newTransform); } /** * @see Graphics2D#rotate(double, double, double) */ @Override public void rotate(double theta, double x, double y) { AffineTransform newTransform = new AffineTransform(transform); newTransform.rotate(theta, x, y); setNewTranform(newTransform); } /** * @see Graphics2D#scale(double, double) */ @Override public void scale(double sx, double sy) { AffineTransform newTransform = new AffineTransform(transform); newTransform.scale(sx, sy); setNewTranform(newTransform); } /** * @see Graphics2D#setBackground(Color) */ @Override public void setBackground(Color color) { background = color; } /** * Clips to a set of coordinates * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void setClip(int x, int y, int w, int h) { /*clipRectangle = new Rectangle(x, y, w, h); closeBlock(); // finish off any existing paths drawRect(x, y, w, h); closeBlock("W n"); // clip to current path*/ setClip(new Rectangle(x, y, w, h)); } /** * As my JDK docs say, this may break with Java 2D. *

* Sets the clipping region to that of a Shape. * * @param s Shape to clip to. */ @Override public void setClip(Shape s) { closeBlock(); if (clip != null) { restoreState(); transform = clipTransform; } if (s == null) { clip = null; return; } clipTransform = transform; clip = new Area(s); clipRectangle = s.getBounds(); saveState(); followPath(s, CLIP); pw.println("n"); //setClip(r.x, r.y, r.width, r.height); } /** * Sets the color for drawing * * @param c Color to use */ @Override public void setColor(Color c) { setPaint(c); } /** * @see Graphics2D#setComposite(Composite) */ @Override public void setComposite(Composite comp) { this.composite = comp; } /** * This extension sets the line width to the default of 1mm which is what * Java uses when drawing to a PrintJob. */ public void setDefaultLineWidth() { closeBlock(); // draw any path before we change the line width pw.println("1 w"); } /** * This sets the font. * * @param f java.awt.Font to set to. */ @Override public void setFont(Font f) { // optimize: Save some space if the font is already the current one. if (font != f) { font = f; pdffont = page.getFont("/Type1", f.getName(), f.getStyle()); // mark the font as changed newFont = true; } } public void setExistingTtfFont(Font f) { if (font != f) { font = f; pdffont = page.getFont("/TrueType", f.getName(), f.getStyle()); // mark the font as changed newFont = true; } } public void setTtfFont(Font f, File file) throws IOException { if (font != f) { font = f; pdffont = page.getEmbeddedFont(f.getName(), f.getStyle(), file); // mark the font as changed newFont = true; } } private void setLineCap(int cap) { int lineCap = 0; switch (cap) { case BasicStroke.JOIN_MITER: lineCap = 0; break; case BasicStroke.JOIN_ROUND: lineCap = 1; break; case BasicStroke.JOIN_BEVEL: lineCap = 2; break; } if (this.lineCap != lineCap) { closeBlock(); // draw any path before we change the line width this.lineCap = lineCap; pw.println("" + lineCap + " J"); } } private void setLineJoin(int join) { int lineJoin = 0; switch (join) { case BasicStroke.JOIN_MITER: lineJoin = 0; break; case BasicStroke.JOIN_ROUND: lineJoin = 1; break; case BasicStroke.JOIN_BEVEL: lineJoin = 2; break; } if (this.lineJoin != lineJoin) { closeBlock(); // draw any path before we change the line width this.lineJoin = lineJoin; pw.println("" + lineJoin + " j"); } } /** * This extension allows the width of the drawn line to be set * * @param width Line width in pdf graphic units (points) */ public void setLineWidth(float width) { if (width != this.lineWidth) { closeBlock(); // draw any path before we change the line width this.lineWidth = width; pw.println("" + width + " w"); } } private void setMiterLimit(float limit) { if (limit != this.miterLimit) { closeBlock(); // draw any path before we change the line width this.miterLimit = limit; pw.println("" + limit + " M"); } } 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 { 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]; 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()); 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()); 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()); //pdf reference, page 168 os.write("/Domain [-1000000 1000000 -1000000 1000000]\n".getBytes()); os.write("/Range [0 1 0 1 0 1]\n".getBytes()); //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()); //pdf reference, page 168 os.write("/Domain [-1000000 1000000 -1000000 1000000]\n".getBytes()); os.write("/Range [0 1 0 1 0 1]\n".getBytes()); //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()); } 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()); } 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()); os.write("/Type /XObject\n".getBytes()); os.write("/Resources <<\n".getBytes()); os.write("/Shading <<".getBytes()); os.write(("/ShA" + fCurrentShadingCount + " " + shadingAlphaObj.getSerialID() + " 0 R").getBytes()); os.write(">>\n".getBytes()); //shading os.write(">>\n".getBytes()); //resources os.write("/Subtype /Form\n".getBytes()); os.write("/BBox [-1000000 -1000000 2000000 2000000]\n".getBytes()); //fixme writeStream(os); } }; OutputStream alphaOs = alphaObject.getOutputStream(); try { alphaOs.write(("/ShA" + fCurrentShadingCount + " sh\n").getBytes()); } 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(); 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()); os.write("/PaintType 1\n".getBytes()); os.write("/TilingType 2\n".getBytes()); os.write(("/BBox [0 0 " + w + " " + h + "]\n").getBytes()); os.write(("/XStep " + w + "\n").getBytes()); os.write(("/YStep " + h + "\n").getBytes()); os.write(("/Resources << " + "/Shading << /Shin" + fCurrentShadingCount + " " + shadingObj.getSerialID() + " 0 R >>" + "/ExtGState <<" + alphaExtGState + ">>" + ">>\n").getBytes()); os.write(("/Matrix [" + matrixStr + "]\n").getBytes()); writeStream(os); } }; OutputStream patOs = innerPattern.getOutputStream(); try { patOs.write(("/GradAlpha" + fCurrentShadingCount + " gs").getBytes()); patOs.write(("/Shin" + shadingCount + " sh").getBytes()); } 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(); 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"); // 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(); 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()); os.write("/PaintType 1\n".getBytes()); os.write("/TilingType 2\n".getBytes()); os.write(("/BBox [0 0 " + matDf.format(w) + " " + matDf.format(h) + "]\n").getBytes()); os.write(("/XStep " + matDf.format(w) + "\n").getBytes()); os.write(("/YStep " + matDf.format(h) + "\n").getBytes()); os.write(("/Resources << ").getBytes()); os.write("/XObject << ".getBytes()); os.write((image.getName() + " " + image.getSerialID() + " 0 R").getBytes()); os.write(" >> ".getBytes()); os.write((">>\n").getBytes()); //"1 0 0 1 0 0" os.write(("/Matrix [" + matrixStr + "]\n").getBytes()); 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(); 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 (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); } 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"); } } \ No newline at end of file