Files
jpexs-decompiler/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java
2021-04-05 16:56:59 +02:00

1 line
100 KiB
Java

/*
* $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<Image, PDFImage> usedImages = new WeakHashMap<Image, PDFImage>();
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<Integer> usedAlphas = new HashSet<>();
private Set<String> gsBlendModes = new HashSet<>();
private int currentAlpha = 255;
private String blendMode;
private int shadingCount = 0;
private int objId = 0;
private boolean usePTransform = true;
private static int[] srgbToLinear = new int[256];
private static int[] linearToSrgb = new int[256];
static {
for (int i = 0; i < 256; i++) {
srgbToLinear[i] = convertSRGBtoLinearRGB(i);
linearToSrgb[i] = convertLinearRGBtoSRGB(i);
}
}
private static int convertSRGBtoLinearRGB(int color) {
float input, output;
input = color / 255.0f;
if (input <= 0.04045f) {
output = input / 12.92f;
} else {
output = (float) Math.pow((input + 0.055) / 1.055, 2.4);
}
return Math.round(output * 255.0f);
}
private static int convertLinearRGBtoSRGB(int color) {
float input, output;
input = color / 255.0f;
if (input <= 0.0031308) {
output = input * 12.92f;
} else {
output = (1.055f
* ((float) Math.pow(input, (1.0 / 2.4)))) - 0.055f;
}
return Math.round(output * 255.0f);
}
/**
* @see Graphics2D#addRenderingHints(Map)
*/
@Override
public void addRenderingHints(Map<?, ?> hints) {
rhints.putAll(hints);
}
/**
* This produces an arc by breaking it down into one or more Bezier curves.
* It is used internally to implement the drawArc and fillArc methods.
*
* @param axc X coordinate of arc centre
* @param ayc Y coordinate of arc centre
* @param width of bounding rectangle
* @param height of bounding rectangle
* @param ang1 Start angle
* @param ang2 End angle
* @param clockwise true to draw clockwise, false anti-clockwise
*/
public void arc(double axc, double ayc,
double width, double height,
double ang1, double ang2,
boolean clockwise) {
double adiff;
double x0, y0;
double x3r, y3r;
boolean first = true;
// may not need this
//if( ar < 0 ) {
//ang1 += fixed_180;
//ang2 += fixed_180;
//ar = - ar;
//}
double ang1r = (ang1 % 360.0) * degrees_to_radians;
double sin0 = Math.sin(ang1r);
double cos0 = Math.cos(ang1r);
x0 = axc + width * cos0;
y0 = ayc + height * sin0;
// NB: !clockwise here as Java Space is inverted to User Space
if (!clockwise) {
// Quadrant reduction
while (ang1 < ang2) {
ang2 -= 360.0;
}
while ((adiff = ang2 - ang1) < -90.0) {
double w = sin0;
sin0 = -cos0;
cos0 = w;
x3r = axc + width * cos0;
y3r = ayc + height * sin0;
arc_add(first,
width, height,
x0, y0,
x3r, y3r,
(x0 + width * cos0),
(y0 + height * sin0)
);
x0 = x3r;
y0 = y3r;
ang1 -= 90.0;
first = false;
}
} else {
// Quadrant reduction
while (ang2 < ang1) {
ang2 += 360.0;
}
while ((adiff = ang2 - ang1) > 90.0) {
double w = cos0;
cos0 = -sin0;
sin0 = w;
x3r = axc + width * cos0;
y3r = ayc + height * sin0;
arc_add(first,
width, height,
x0, y0,
x3r, y3r,
(x0 + width * cos0),
(y0 + height * sin0)
);
x0 = x3r;
y0 = y3r;
ang1 += 90.0;
first = false;
}
}
// Compute the intersection of the tangents.
// We know that -fixed_90 <= adiff <= fixed_90.
double trad = Math.tan(adiff * (degrees_to_radians / 2));
double ang2r = ang2 * degrees_to_radians;
double xt = x0 - trad * width * sin0;
double yt = y0 + trad * height * cos0;
arc_add(first, width, height, x0, y0,
(axc + width * Math.cos(ang2r)),
(ayc + height * Math.sin(ang2r)),
xt, yt);
}
/**
* Used by the arc method to actually add an arc to the path Important: We
* write directly to the stream here, because this method operates in User
* space, rather than Java space.
*
* @param first true if the first arc
* @param w width
* @param h height
* @param x0 coordinate
* @param y0 coordinate
* @param x3 coordinate
* @param y3 coordinate
* @param xt coordinate
* @param yt coordinate
*/
private void arc_add(boolean first,
double w, double h,
double x0, double y0,
double x3, double y3,
double xt, double yt) {
double dx = xt - x0, dy = yt - y0;
double dist = dx * dx + dy * dy;
double w2 = w * w, h2 = h * h;
double r2 = w2 + h2;
double fw = 0.0, fh = 0.0;
if (dist < (r2 * 1.0e8)) {
// JM
fw = (w2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / w2))) : 0.0;
fh = (h2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / h2))) : 0.0;
}
// The path must have a starting point
if (first) {
moveto(x0, y0);
}
double x = x0 + ((xt - x0) * fw);
double y = y0 + ((yt - y0) * fh);
x0 = x3 + ((xt - x3) * fw);
y0 = y3 + ((yt - y3) * fh);
// Finally the actual curve.
curveto(x, y, x0, y0, x3, y3);
}
/**
* This simply draws a White Rectangle to clear the area
*
* @param x coordinate
* @param y coordinate
* @param w width
* @param h height
*/
@Override
public void clearRect(int x, int y, int w, int h) {
closeBlock();
pw.print("q 1 1 1 RG ");// save state, set colour to White
drawRect(x, y, w, h);
closeBlock("B Q"); // close fill & stroke, then restore state
}
/**
* @see Graphics2D#clip(Shape)
*/
@Override
public void clip(Shape s) {
if (s == null) {
setClip(null);
return;
}
Area newClip;
if (clip == null) {
newClip = new Area(s);
} else {
newClip = (Area) clip.clone();
newClip.intersect(new Area(s));
}
setClip(newClip);
}
/**
* This extra method allows PDF users to clip to a Polygon.
*
* <p>
* 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");
}
/**
* <p>
* 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.</p>
*
* @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 =======================
/**
* <p>
* 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.</p>
*
* <p>
* Once complete, the child should be released with it's dispose() method
* which will restore the graphics state to it's parent.</p>
*
* @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 <code>PrintWriter</code> 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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);
}
/**
* <p>
* 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.</p>
*
* <p>
* 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.</p>
*
* <p>
* When using PDFPage, you can create another fresh Graphics instance, which
* will draw over this one.</p>
*
*/
@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);
}
/**
* <p>
* Not implemented</p>
*
* <p>
* 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.
* </p>
*
* @param x an <code>int</code> value
* @param y an <code>int</code> value
* @param width an <code>int</code> value
* @param height an <code>int</code> value
* @param raised a <code>boolean</code> 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);
}
/**
* <p>
* Not implemented</p>
*
* @param data a <code>byte[]</code> value
* @param offset an <code>int</code> value
* @param length an <code>int</code> value
* @param x an <code>int</code> value
* @param y an <code>int</code> 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;
}
/**
* <p>
* Draw's an image onto the page, with a backing colour.</p>
*
* @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);
}
/**
* <p>
* Draw's an image onto the page, with a backing colour.</p>
*
* @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);
}
/**
* <p>
* Draws an image onto the page.</p>
*
* <p>
* 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</p>
*
* @param img The java.awt.Image
* @param x coordinate on page
* @param y coordinate on page
* @param w Width on page
* @param h height on page
* @param obs ImageObserver
* @return true if drawn
*/
@Override
public boolean drawImage(Image img, int x, int y, int w, int h,
ImageObserver obs) {
closeBlock();
PDFImage image;
if (usedImages.containsKey(img)) {
image = usedImages.get(img);
} else {
PDFMask mask = new PDFMask(img);
page.getPDFDocument().add(mask);
Object interpolationHint = getRenderingHint(RenderingHints.KEY_INTERPOLATION);
boolean interpolate = interpolationHint == RenderingHints.VALUE_INTERPOLATION_BILINEAR
|| interpolationHint == RenderingHints.VALUE_INTERPOLATION_BICUBIC;
image = new PDFImage(img, x, y, w, h, obs, "" + mask.getSerialID() + " 0 R", interpolate);
// The image needs to be registered in several places
page.getPDFDocument().setImageName(image);
page.getPDFDocument().add(image);
usedImages.put(img, image);
}
page.addToProcset("/ImageC");
page.addImageResource(image.getName() + " " + image.getSerialID()
+ " 0 R");
initAlpha(255);
// JM
/*page.addResource("/XObject << " + image.getName() + " " +
image.getSerialID() + " 0 R >>");*/
// q w 0 0 h x y cm % the coordinate matrix
AffineTransform newTransform = new AffineTransform(w, 0, 0, -h, x, y + h);
AffineTransform transformToSet = newTransform;
pw.print("q " + matDf.format(transformToSet.getScaleX()) + " "
+ matDf.format(transformToSet.getShearY()) + " "
+ matDf.format(transformToSet.getShearX()) + " "
+ matDf.format(transformToSet.getScaleY()) + " "
+ matDf.format(transformToSet.getTranslateX()) + " "
+ matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n");
return false;
}
/**
* Draw's an image onto the page, with scaling
* <p>
* 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
* <p>
* 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.
/**
* <p>
* Draws an oval</p>
*
* @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<String, Object> properties = new Hashtable<String, Object>();
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.
* <p>
* This is not supported yet, as I have no idea what an
* AttributedCharacterIterator is.
* <p>
* 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.
* <p>
* This is not supported yet, as I have no idea what an
* AttributedCharacterIterator is.
* <p>
* 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);
}
/**
* <p>
* Not implemented</p>
*
* @param x an <code>int</code> value
* @param y an <code>int</code> value
* @param width an <code>int</code> value
* @param height an <code>int</code> value
* @param raised a <code>boolean</code> 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.
/**
* <p>
* Draws a filled oval</p>
*
* @param x coordinate
* @param y coordinate
* @param w width
* @param h height
*/
@Override
public void fillOval(int x, int y, int w, int h) {
fillArc(x, y, w, h, 0, 360);
}
//============ Polygon operations =======================
/**
* Fills a polygon.
*
* @param xp Array of x coordinates
* @param yp Array of y coordinates
* @param np number of points in polygon
*/
@Override
public void fillPolygon(int[] xp, int[] yp, int np) {
closeBlock(); // finish off any previous paths
patternFill(null /*FIXME!!!*/);
polygon(xp, yp, np);
if (shadingFill(null)) {
return;
}
closeBlock("b"); // closepath, fill and stroke
}
//============ Image operations =======================
/**
* Fills a rectangle with the current colour
*
* @param x coordinate
* @param y coordinate
* @param w width
* @param h height
*/
@Override
public void fillRect(int x, int y, int w, int h) {
fill(new Rectangle(x, y, w, h));
/*
// end any path & stroke. This ensures the fill is on this
// rectangle, and not on any previous graphics
closeBlock();
patternFill();
drawRect(x, y, w, h);
if (shadingFill()) {
return;
}
closeBlock("B"); // rectangle, fill stroke*/
}
private void patternFill(Shape s) {
if (pattern != null) {
if (paint instanceof TexturePaint) {
if (pattern.equals("texture") || !paintTransform.equals(transform)) {
initTexturePaint((TexturePaint) paint);
paintTransform = transform;
}
}
if (paint instanceof MultipleGradientPaint) {
if (pattern.equals("gradient") || !paintTransform.equals(transform)) {
initGradientPaint((MultipleGradientPaint) paint, s);
paintTransform = transform;
}
}
pw.println("/Pattern cs");
pw.println(pattern + " scn");
}
}
private boolean shadingFill(Shape s) {
if (pattern == null && shading != null) {
saveState();
pw.println("W n");
if (paint instanceof MultipleGradientPaint) {
if (shading.equals("gradient") || !paintTransform.equals(transform)) {
initGradientPaint((MultipleGradientPaint) paint, s);
paintTransform = transform;
}
}
pw.println(shading + " sh");
restoreState();
return true;
}
return false;
}
//============ Round Rectangle operations =======================
/**
* This is not yet implemented
*
* @param x coordinate
* @param y coordinate
* @param w width
* @param h height
* @param aw a-width
* @param ah a-height
*/
@Override
public void fillRoundRect(int x, int y, int w, int h, int aw, int ah) {
}
///////////////////////////////////////////////
//
//
// implementation specific methods
//
//
private void followPath(Shape s, int drawType) {
PathIterator points;
if (s == null) {
return;
}
if (drawType == FILL) {
patternFill(s);
}
if (drawType == STROKE) {
if (!(stroke instanceof BasicStroke)) {
s = stroke.createStrokedShape(s);
followPath(s, FILL);
return;
}
}
// if (drawType==STROKE) {
// setStrokeDiff(stroke, oldStroke);
// oldStroke = stroke;
// setStrokePaint();
// }
// else if (drawType==FILL)
// setFillPaint();
points = s.getPathIterator(IDENTITY);
int segments = 0;
float[] coords = new float[6];
while (!points.isDone()) {
segments++;
int segtype = points.currentSegment(coords);
switch (segtype) {
case PathIterator.SEG_CLOSE:
pw.print("h ");
break;
case PathIterator.SEG_CUBICTO:
curveto(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]);
break;
case PathIterator.SEG_LINETO:
lineto(coords[0], coords[1]);
break;
case PathIterator.SEG_MOVETO:
moveto(coords[0], coords[1]);
break;
case PathIterator.SEG_QUADTO:
curveto(coords[0], coords[1], coords[2], coords[3]);
break;
}
points.next();
}
switch (drawType) {
case FILL:
if (segments > 0) {
if (pattern == null && shading != null) {
saveState();
if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD) {
closeBlock("W*");
} else {
closeBlock("W");
}
pw.println("n");
if (paint instanceof MultipleGradientPaint) {
if (shading.equals("gradient") || !paintTransform.equals(transform)) {
initGradientPaint((MultipleGradientPaint) paint, s);
paintTransform = transform;
}
}
pw.println(shading + " sh");
restoreState();
return;
}
if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD) {
closeBlock("f*");
} else {
closeBlock("f");
}
}
break;
case STROKE:
if (segments > 0) {
closeBlock("S");
}
break;
case CLIP:
default: //drawType==CLIP
if (segments == 0) {
drawRect(0, 0, 0, 0);
}
if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD) {
closeBlock("W*");
} else {
closeBlock("W");
}
}
}
/**
* @see Graphics2D#getBackground()
*/
@Override
public Color getBackground() {
return background;
}
/**
* Returns the Shape of the clipping region As my JDK docs say, this may
* break with Java 2D.
*
* @return Shape of the clipping region
*/
@Override
public Shape getClip() {
if (clip == null) {
return null;
}
return clip;
}
/**
* Returns the Rectangle that fits the current clipping region
*
* @return the Rectangle that fits the current clipping region
*/
@Override
public Rectangle getClipBounds() {
return clipRectangle;
}
//============ Color operations =======================
/**
* Returns the current pen Colour
*
* @return the current pen Colour
*/
@Override
public Color getColor() {
return (paint instanceof Color) ? (Color) paint : Color.black;
}
/**
* @see Graphics2D#getComposite()
*/
@Override
public Composite getComposite() {
return composite;
}
/**
* @see Graphics2D#getDeviceConfiguration()
*/
@Override
public GraphicsConfiguration getDeviceConfiguration() {
return dg2.getDeviceConfiguration();
}
/**
* Return's the current font.
*
* @return the current font.
*/
@Override
public Font getFont() {
if (font == null) {
setFont(new Font("SansSerif", Font.PLAIN, 12));
}
return font;
}
/**
* Returns the FontMetrics for a font.
* <p>
* 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.
*
* <p>
* 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;
}
/**
* <p>
* 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.</p>
*
* <p>
* It also handles if the font has been changed since the current text block
* was started, so your function will be current.</p>
*
* @param x x coordinate in java space
* @param y y coordinate in java space
*/
void newTextBlock(float x, float y) {
// close the current path if there is one
if (inStroke) {
closeBlock();
}
// create the text block if one is not current. If we are, the newFont
// condition at the end catches font changes
if (!inText) {
// This ensures that there is a font available
getFont();
pw.print("q BT ");
tx = ty = 0;
AffineTransform tm = usePTransform ? new AffineTransform(pTransform) : new AffineTransform();
pw.println("" + df.format(tm.getScaleX()) + " "
+ "" + df.format(tm.getShearY()) + " "
+ "" + df.format(tm.getShearX()) + " "
+ "" + df.format(tm.getScaleY()) + " "
+ "" + df.format(tm.getTranslateX()) + " "
+ "" + df.format(tm.getTranslateY()) + " Tm"
);
// produce the text matrix for the media
// switch(mediaRot) {
// case PageFormat.PORTRAIT: // Portrait
// //pw.println("1 0 0 1 0 0 Tm");
// break;
//
// case PageFormat.LANDSCAPE: // Landscape
// pw.println("0 1 -1 0 0 0 Tm"); // rotate
// break;
//
// case 180: // Inverted Portrait
// pw.println("1 0 0 -1 0 0 Tm");
// break;
//
// case PageFormat.REVERSE_LANDSCAPE: // Seascape
// pw.println("0 -1 1 0 0 0 Tm"); // rotate
// break;
// }
// move the text cursor by an absolute amount
pw.print(txy(x, y) + "Td ");
} else {
// move the text cursor by a relative amount
pw.print(twh(x, y, tx, ty) + "Td ");
//pw.print(txy(x,y)+"Td ");
}
// preserve the coordinates for the next time
tx = x;
ty = y;
if (newFont || !inText) {
pw.print(pdffont.getName() + " " + font.getSize() + " Tf ");
}
// later add colour changes here (if required)
inStroke = newFont = false;
inText = true;
}
/**
* This is used to add a polygon to the current path. Used by drawPolygon(),
* drawPolyline() and fillPolygon() etal
*
* @param xp Array of x coordinates
* @param yp Array of y coordinates
* @param np number of points in polygon
* @see #drawPolygon
* @see #drawPolyline
* @see #fillPolygon
*/
public void polygon(int[] xp, int[] yp, int np) {
// newPath() not needed here as moveto does it ;-)
moveto(xp[0], yp[0]);
for (int i = 1; i < np; i++) {
lineto(xp[i], yp[i]);
}
}
/**
* @see Graphics2D#rotate(double)
*/
@Override
public void rotate(double theta) {
AffineTransform newTransform = new AffineTransform(transform);
newTransform.rotate(theta);
setNewTranform(newTransform);
}
/**
* @see Graphics2D#rotate(double, double, double)
*/
@Override
public void rotate(double theta, double x, double y) {
AffineTransform newTransform = new AffineTransform(transform);
newTransform.rotate(theta, x, y);
setNewTranform(newTransform);
}
/**
* @see Graphics2D#scale(double, double)
*/
@Override
public void scale(double sx, double sy) {
AffineTransform newTransform = new AffineTransform(transform);
newTransform.scale(sx, sy);
setNewTranform(newTransform);
}
/**
* @see Graphics2D#setBackground(Color)
*/
@Override
public void setBackground(Color color) {
background = color;
}
/**
* Clips to a set of coordinates
*
* @param x coordinate
* @param y coordinate
* @param w width
* @param h height
*/
@Override
public void setClip(int x, int y, int w, int h) {
/*clipRectangle = new Rectangle(x, y, w, h);
closeBlock(); // finish off any existing paths
drawRect(x, y, w, h);
closeBlock("W n"); // clip to current path*/
setClip(new Rectangle(x, y, w, h));
}
/**
* As my JDK docs say, this may break with Java 2D.
* <p>
* Sets the clipping region to that of a Shape.
*
* @param s Shape to clip to.
*/
@Override
public void setClip(Shape s) {
closeBlock();
if (clip != null) {
restoreState();
AffineTransform currentTransform = transform;
transform = clipTransform;
setTransform(currentTransform);
}
if (s == null) {
clip = null;
return;
}
clipTransform = transform;
clip = new Area(s);
clipRectangle = s.getBounds();
saveState();
followPath(s, CLIP);
pw.println("n");
//setClip(r.x, r.y, r.width, r.height);
}
/**
* Sets the color for drawing
*
* @param c Color to use
*/
@Override
public void setColor(Color c) {
setPaint(c);
}
/**
* @see Graphics2D#setComposite(Composite)
*/
@Override
public void setComposite(Composite comp) {
this.composite = comp;
}
/**
* This extension sets the line width to the default of 1mm which is what
* Java uses when drawing to a PrintJob.
*/
public void setDefaultLineWidth() {
closeBlock(); // draw any path before we change the line width
pw.println("1 w");
}
/**
* This sets the font.
*
* @param f java.awt.Font to set to.
*/
@Override
public void setFont(Font f) {
// optimize: Save some space if the font is already the current one.
if (font != f) {
font = f;
pdffont = page.getFont("/Type1", f.getName(), f.getStyle());
// mark the font as changed
newFont = true;
}
}
public void setExistingTtfFont(Font f) {
if (font != f) {
font = f;
pdffont = page.getFont("/TrueType", f.getName(), f.getStyle());
// mark the font as changed
newFont = true;
}
}
public void setTtfFont(Font f, File file) throws IOException {
if (font != f) {
font = f;
pdffont = page.getEmbeddedFont(f.getName(), f.getStyle(), file);
// mark the font as changed
newFont = true;
}
}
private void setLineCap(int cap) {
int lineCap = 0;
switch (cap) {
case BasicStroke.JOIN_MITER:
lineCap = 0;
break;
case BasicStroke.JOIN_ROUND:
lineCap = 1;
break;
case BasicStroke.JOIN_BEVEL:
lineCap = 2;
break;
}
if (this.lineCap != lineCap) {
closeBlock(); // draw any path before we change the line width
this.lineCap = lineCap;
pw.println("" + lineCap + " J");
}
}
private void setLineJoin(int join) {
int lineJoin = 0;
switch (join) {
case BasicStroke.JOIN_MITER:
lineJoin = 0;
break;
case BasicStroke.JOIN_ROUND:
lineJoin = 1;
break;
case BasicStroke.JOIN_BEVEL:
lineJoin = 2;
break;
}
if (this.lineJoin != lineJoin) {
closeBlock(); // draw any path before we change the line width
this.lineJoin = lineJoin;
pw.println("" + lineJoin + " j");
}
}
/**
* This extension allows the width of the drawn line to be set
*
* @param width Line width in pdf graphic units (points)
*/
public void setLineWidth(float width) {
if (width != this.lineWidth) {
closeBlock(); // draw any path before we change the line width
this.lineWidth = width;
pw.println("" + width + " w");
}
}
private void setMiterLimit(float limit) {
if (limit != this.miterLimit) {
closeBlock(); // draw any path before we change the line width
this.miterLimit = limit;
pw.println("" + limit + " M");
}
}
private void initAlpha(int alpha) {
if (currentAlpha != alpha || blendMode != null) {
String gsId = "/GSAlpha" + alpha;
currentAlpha = alpha;
if (!usedAlphas.contains(alpha)) {
page.addExtGStateResource(gsId + " <</ca " + (currentAlpha / 255.0) + " /CA " + (currentAlpha / 255.0) + ">>");
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 + " <</BM /" + mode + " /ca " + (currentAlpha / 255.0) + " /CA " + (currentAlpha / 255.0) + ">>");
}
pw.println(gsName + " gs");
this.blendMode = mode;
}
/**
* Sets the paint for drawing
*
* @param paint Paint to use
*/
@Override
public void setPaint(Paint paint) {
this.paint = paint;
this.shading = null;
this.pattern = null;
this.paintTransform = null;
if (paint instanceof Color) {
Color c = (Color) paint;
double r = ((double) c.getRed()) / 255.0;
double g = ((double) c.getGreen()) / 255.0;
double b = ((double) c.getBlue()) / 255.0;
closeBlock(); // This ensures any paths are drawn in the previous
initAlpha(c.getAlpha());
// colours
pw.println("" + r + " " + g + " " + b + " rg "
+ r + " " + g + " " + b + " RG");
}
if (paint instanceof MultipleGradientPaint) {
closeBlock();
if ((paint instanceof RadialGradientPaint) || ((paint instanceof LinearGradientPaint) && (((LinearGradientPaint) paint).getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE))) {
shading = "gradient";
} else {
pattern = "gradient";
}
}
if (paint instanceof TexturePaint) {
closeBlock();
pattern = "texture";
}
}
private boolean useFunctionShading(MultipleGradientPaint fgrad) {
return ((fgrad instanceof RadialGradientPaint)
&& fgrad.getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE);
}
private Color[] convertColorSpace(Color[] colors, MultipleGradientPaint.ColorSpaceType colorSpaceType) {
/*if (colorSpaceType == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) {
Color[] ret = new Color[colors.length];
for (int i = 0; i < colors.length; i++) {
int argb = colors[i].getRGB();
int a = argb >>> 24;
int r = srgbToLinear[(argb >> 16) & 0xff];
int g = srgbToLinear[(argb >> 8) & 0xff];
int b = srgbToLinear[(argb) & 0xff];
ret[i] = new Color(r, g, b, a);
}
return ret;
}*/
//return colors;
return colors;
}
private String generateRadialFunctionBody(RadialGradientPaint radGrad, boolean alpha) {
double a = ((radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) * (radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX())
+ (radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) * (radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY())
- radGrad.getRadius() * radGrad.getRadius());
String functionBody = "{\n"
+ matDf.format(radGrad.getFocusPoint().getX()) + " 2 index sub\n"
+ matDf.format(radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) + " mul 2 mul\n" //stack size: 3
+ matDf.format(radGrad.getFocusPoint().getY()) + " 2 index sub\n"
+ matDf.format(radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) + " mul 2 mul\n"
+ "add\n" //b, stack size: 3
+ matDf.format(radGrad.getFocusPoint().getX()) + " 3 index sub\n"
+ "dup mul\n"
+ matDf.format(radGrad.getFocusPoint().getY()) + " 3 index sub\n"
+ "dup mul\n"
+ "add\n" //c, stack size: 4
+ "1 index dup mul 4 " + matDf.format(a) + " mul 2 index mul sub\n" //D, stack size: 4
+ "0 index 0 lt\n"
+ "{\n"
+ "1\n"
+ "}"
+ "{"
+ "0 index 0 gt\n"
+ "{\n"
+ "2 index neg 1 index sqrt add 2 " + matDf.format(a) + " mul div\n" // x1, stack size: 5
+ "3 index neg 2 index sqrt sub 2 " + matDf.format(a) + " mul div\n" // x2, stack size: 6
+ "0 index 2 index gt{0 index} {1 index} ifelse\n"
+ "exch pop exch pop\n"
//x, stack size 5
+ "}"
+ "{\n"
+ "2 index neg 2 " + matDf.format(a) + " mul div\n" // x, stack size 5
+ "} ifelse\n"
+ "} ifelse\n"
+ "exch pop exch pop exch pop exch pop exch pop\n"; //remove index0,1,2,3,4
if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT) {
functionBody += "dup\n";
}
if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE) {
functionBody += "dup 1 gt {pop 1} if\n";
} else {
functionBody += "dup 1 gt {dup floor sub} if\n";
}
if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT) {
functionBody += "exch floor 2 mod 1 eq {"
+ "neg 1 add"
+ "}\n"
+ "if\n";
}
int num = radGrad.getFractions().length;
Color[] rcolors = convertColorSpace(radGrad.getColors(), radGrad.getColorSpace());
for (int i = 0; i < num - 1; i++) {
functionBody += "dup " + radGrad.getFractions()[i] + " lt not 1 index " + radGrad.getFractions()[i + 1] + " gt not and\n{\n"
+ "0 index " + radGrad.getFractions()[i] + " sub " + (radGrad.getFractions()[i + 1] - radGrad.getFractions()[i]) + " div\n";
if (alpha) {
functionBody += "0 index " + ((rcolors[i + 1].getAlpha() - rcolors[i].getAlpha()) / 255.0) + " mul " + (rcolors[i].getAlpha() / 255.0) + " add\n";
functionBody += "dup dup\n";
} else {
if (radGrad.getColorSpace() == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) {
functionBody += "0 index " + ((srgbToLinear[rcolors[i + 1].getRed()] - srgbToLinear[rcolors[i].getRed()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getRed()] / 255.0) + " add\n"
+ "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n"
+ "1 index " + ((srgbToLinear[rcolors[i + 1].getGreen()] - srgbToLinear[rcolors[i].getGreen()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getGreen()] / 255.0) + " add\n"
+ "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n"
+ "2 index " + ((srgbToLinear[rcolors[i + 1].getBlue()] - srgbToLinear[rcolors[i].getBlue()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getBlue()] / 255.0) + " add\n"
+ "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n";
} else {
functionBody += "0 index " + ((rcolors[i + 1].getRed() - rcolors[i].getRed()) / 255.0) + " mul " + (rcolors[i].getRed() / 255.0) + " add\n"
+ "1 index " + ((rcolors[i + 1].getGreen() - rcolors[i].getGreen()) / 255.0) + " mul " + (rcolors[i].getGreen() / 255.0) + " add\n"
+ "2 index " + ((rcolors[i + 1].getBlue() - rcolors[i].getBlue()) / 255.0) + " mul " + (rcolors[i].getBlue() / 255.0) + " add\n";
}
}
if (i < num - 2) {
functionBody += "}\n{\n";
}
}
functionBody += "}if\n";
for (int i = 0; i < num - 2; i++) {
functionBody += "}ifelse\n";
}
functionBody += "}\n";
return functionBody;
}
private void initGradientPaint(MultipleGradientPaint grad, Shape fillShape) {
if ((grad instanceof LinearGradientPaint) && (((LinearGradientPaint) grad).getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT)) {
LinearGradientPaint linGrad = (LinearGradientPaint) grad;
Point2D start = linGrad.getStartPoint();
Point2D end = linGrad.getEndPoint();
double deltaX = end.getX() - start.getX();
double deltaY = end.getY() - start.getY();
Point2D newEnd = new Point2D.Double(end.getX() + deltaX, end.getY() + deltaY);
int colorCount = grad.getFractions().length;
float fractions2[] = new float[colorCount * 2 - 1];
Color colors2[] = new Color[colorCount * 2 - 1];
float fractionsrev[] = new float[linGrad.getFractions().length];
Color colorsrev[] = new Color[linGrad.getColors().length];
for (int i = 0; i < fractionsrev.length; i++) {
colorsrev[i] = linGrad.getColors()[i];
fractionsrev[i] = linGrad.getFractions()[i];
}
for (int i = 0; i < colorCount; i++) {
colors2[i] = colorsrev[i];
fractions2[i] = fractionsrev[i] / 2;
}
for (int i = 0; i < colorCount; i++) {
colors2[colors2.length - i - 1] = colorsrev[i];
fractions2[colors2.length - i - 1] = 1f - fractionsrev[i] / 2;
}
LinearGradientPaint linGrad2 = new LinearGradientPaint(start, newEnd, fractions2, colors2, MultipleGradientPaint.CycleMethod.REPEAT);
grad = linGrad2;
}
List<String> functions2Refs = new ArrayList<>();
Color[] colors = convertColorSpace(grad.getColors(), grad.getColorSpace());
for (int i = 1; i < grad.getColors().length; i++) {
final Color color1 = colors[i - 1];
final Color color2 = colors[i];
final MultipleGradientPaint.ColorSpaceType colorSpace = grad.getColorSpace();
if (colorSpace == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) {
PDFStream function4 = new PDFStream(null) {
@Override
public void write(OutputStream os) throws IOException {
writeStart(os);
os.write("/FunctionType 4 /Domain [0 1] /Range [0 1 0 1 0 1]".getBytes());
writeStream(os);
}
};
OutputStream f4Os = function4.getOutputStream();
int redDelta = srgbToLinear[color2.getRed()] - srgbToLinear[color1.getRed()];
int greenDelta = srgbToLinear[color2.getGreen()] - srgbToLinear[color1.getGreen()];
int blueDelta = srgbToLinear[color2.getBlue()] - srgbToLinear[color1.getBlue()];
String functionBody = "{"
+ "0 index " + redDelta + " mul " + srgbToLinear[color1.getRed()] + " add 255 div\n"
+ "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n"
+ "1 index " + greenDelta + " mul " + srgbToLinear[color1.getGreen()] + " add 255 div\n"
+ "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n"
+ "2 index " + blueDelta + " mul " + srgbToLinear[color1.getBlue()] + " add 255 div\n"
+ "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n"
+ "}";
try {
f4Os.write(functionBody.getBytes());
} catch (IOException ex) {
Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex);
}
page.getPDFDocument().add(function4);
functions2Refs.add(function4.getSerialID() + " 0 R");
} else {
//
PDFObject function2 = new PDFObject(null) {
@Override
public void write(OutputStream os) throws IOException {
writeStart(os);
os.write(("/FunctionType 2 /Domain [0 1] /C0 ["
+ (((float) color1.getRed()) / 255.0f) + " "
+ (((float) color1.getGreen()) / 255.0f) + " "
+ (((float) color1.getBlue()) / 255.0f) + "] /C1 ["
+ (((float) color2.getRed()) / 255.0f) + " "
+ (((float) color2.getGreen()) / 255.0f) + " "
+ (((float) color2.getBlue()) / 255.0f) + "] /N 1\n").getBytes());
writeEnd(os);
}
};
page.getPDFDocument().add(function2);
functions2Refs.add(function2.getSerialID() + " 0 R");
}
}
List<String> 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();
if (usePTransform) {
m.concatenate(pTransform);
}
m.concatenate(transform);
m.concatenate(AffineTransform.getTranslateInstance(startPoint.getX(), endPoint.getY()));
m.concatenate(AffineTransform.getScaleInstance(divisor, divisor));
m.concatenate(AffineTransform.getRotateInstance(-alfa));
String matrixStr = "" + matDf.format(m.getScaleX()) + " "
+ matDf.format(m.getShearY()) + " " + matDf.format(m.getShearX()) + " " + matDf.format(m.getScaleY()) + " "
+ matDf.format(m.getTranslateX()) + " " + matDf.format(m.getTranslateY());
PDFStream innerPattern = new PDFStream("/Pattern") {
@Override
public void write(OutputStream os) throws IOException {
writeStart(os);
double w;
double h;
w = 1;
h = flen;
os.write("/PatternType 1\n".getBytes());
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();
Object interpolationHint = getRenderingHint(RenderingHints.KEY_INTERPOLATION);
boolean interpolate = interpolationHint == RenderingHints.VALUE_INTERPOLATION_BILINEAR
|| interpolationHint == RenderingHints.VALUE_INTERPOLATION_BICUBIC;
PDFImage image = new PDFImage(img, 0, 0, img.getWidth(), img.getHeight(), new ImageObserver() {
@Override
public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
return true;
}
}, "" + mask.getSerialID() + " 0 R", interpolate);
// The image needs to be registered in several places
page.getPDFDocument().setImageName(image);
page.getPDFDocument().add(image);
usedImages.put(img, image);
AffineTransform m = new AffineTransform();
AffineTransform ptt = new AffineTransform();
if (usePTransform) {
ptt.concatenate(pTransform);
}
ptt.concatenate(transform);
m.concatenate(ptt);
String matrixStr = "" + matDf.format(m.getScaleX()) + " "
+ matDf.format(m.getShearY()) + " " + matDf.format(m.getShearX()) + " " + matDf.format(m.getScaleY()) + " "
+ matDf.format(m.getTranslateX()) + " " + matDf.format(m.getTranslateY());
PDFStream innerPattern = new PDFStream("/Pattern") {
@Override
public void write(OutputStream os) throws IOException {
writeStart(os);
os.write("/PatternType 1\n".getBytes());
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();
if (usePTransform) {
pTransform.transform(ptSrc, ptDst);
}
return "" + df.format(ptDst.getX()) + " " + df.format(ptDst.getY()) + " ";
}
private void setNewTranform(AffineTransform t) {
closeBlock();
if (true) {
//return;
}
AffineTransform newTransform = new AffineTransform(t);
AffineTransform transformToSet = new AffineTransform(newTransform);
if (usePTransform) {
if (transform != null) {
AffineTransform aInv = new AffineTransform(transform);
try {
aInv.invert();
} catch (NoninvertibleTransformException ex) {
Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex);
}
AffineTransform pInv = new AffineTransform(pTransform);
try {
pInv.invert();
} catch (NoninvertibleTransformException ex) {
Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex);
}
transformToSet = new AffineTransform();
transformToSet.concatenate(aInv);
transformToSet.concatenate(pInv);
transformToSet.concatenate(pTransform);
transformToSet.concatenate(newTransform);
} else {
transformToSet.preConcatenate(pTransform);
}
}
if (clip != null) {
AffineTransform inv = new AffineTransform(newTransform);
try {
inv.invert();
} catch (NoninvertibleTransformException ex) {
Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex);
}
clip = new Area(transform.createTransformedShape(inv.createTransformedShape(clip)));
clipRectangle = clip.getBounds();
}
transform = newTransform;
pw.println("" + matDf.format(transformToSet.getScaleX()) + " "
+ "" + matDf.format(transformToSet.getShearY()) + " "
+ "" + matDf.format(transformToSet.getShearX()) + " "
+ "" + matDf.format(transformToSet.getScaleY()) + " "
+ "" + matDf.format(transformToSet.getTranslateX()) + " "
+ "" + matDf.format(transformToSet.getTranslateY()) + " cm"
);
}
private void saveState() {
pw.println("q");
}
private void restoreState() {
pw.println("Q");
}
public void drawXObject(Graphics g) {
if (g instanceof PDFGraphics) {
int objId = ((PDFGraphics) g).objId;
pw.println("/MyObj" + objId + " Do");
}
}
public Graphics2D createXObject() {
final PDFPage newPage = new PDFPage(page.pageFormat) {
@Override
public void addToProcset(String proc) {
page.addToProcset(proc);
}
};
newPage.pdfDocument = page.pdfDocument;
PDFStream retObject = new PDFStream() {
@Override
public void write(OutputStream os) throws IOException {
writeStart(os);
os.write("/Type /XObject\n".getBytes());
newPage.writeResources(os);
os.write("/Subtype /Form\n".getBytes());
os.write("/BBox [-1000000 -1000000 2000000 2000000]\n".getBytes()); //fixme
writeStream(os);
}
};
page.getPDFDocument().add(retObject);
OutputStream os = retObject.getOutputStream();
RawPrintWriter pw2 = new RawPrintWriter(os);
page.addXObject("/MyObj" + retObject.getSerialID() + " " + retObject.getSerialID() + " 0 R");
PDFGraphics g = new PDFGraphics();
g.usePTransform = false;
g.init(newPage, pw2);
g.objId = retObject.getSerialID();
//g.setTransform(new AffineTransform());
return g;
}
}