Make first real commit: copy of CaRMetal 4.2.8

This commit is contained in:
Glen Whitney 2018-09-04 22:51:42 -04:00
parent 002acfc88e
commit c312811084
1120 changed files with 226843 additions and 1 deletions

View file

@ -0,0 +1,103 @@
/*
* VectorGraphics2D: Vector export for Java(R) Graphics2D
*
* (C) Copyright 2010 Erich Seifert <dev[at]erichseifert.de>
*
* This file is part of VectorGraphics2D.
*
* VectorGraphics2D 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 3 of the License, or
* (at your option) any later version.
*
* VectorGraphics2D 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 VectorGraphics2D. If not, see <http://www.gnu.org/licenses/>.
*/
package de.erichseifert.vectorgraphics2d;
import java.util.HashMap;
import java.util.Map;
/**
* Abstract class that contains utility functions for working with data
* collections like maps or lists.
*/
public abstract class DataUtils {
/**
* Default constructor that prevents creation of class.
*/
protected DataUtils() {
throw new UnsupportedOperationException();
}
/**
* Creates a mapping from two arrays, one with keys, one with values.
* @param <K> Data type of the keys.
* @param <V> Data type of the values.
* @param keys Array containing the keys.
* @param values Array containing the values.
* @return Map with keys and values from the specified arrays.
*/
public static <K,V> Map<K, V> map(K[] keys, V[] values) {
// Check for valid parameters
if (keys.length != values.length) {
throw new IllegalArgumentException(
"Number of keys and values is different. " +
"Cannot create map.");
}
// Fill map with keys and values
Map<K, V> map = new HashMap<K, V>();
for (int i = 0; i < keys.length; i++) {
K key = keys[i];
V value = values[i];
map.put(key, value);
}
return map;
}
/**
* Returns a string with all float values divided by a specified separator.
* @param separator Separator string.
* @param elements Float array.
* @return Joined string.
*/
public static String join(String separator, float... elements) {
if (elements == null || elements.length == 0) {
return "";
}
StringBuffer sb = new StringBuffer(elements.length*3);
for (int i = 0; i < elements.length; i++) {
if (i > 0) {
sb.append(separator);
}
sb.append(elements[i]);
}
return sb.toString();
}
/**
* Returns a string with all float values divided by a specified separator.
* @param separator Separator string.
* @param elements Double array.
* @return Joined string.
*/
public static String join(String separator, double... elements) {
if (elements == null || elements.length == 0) {
return "";
}
StringBuffer sb = new StringBuffer(elements.length*3);
for (int i = 0; i < elements.length; i++) {
if (i > 0) {
sb.append(separator);
}
sb.append(elements[i]);
}
return sb.toString();
}
}

View file

@ -0,0 +1,459 @@
/*
* VectorGraphics2D: Vector export for Java(R) Graphics2D
*
* (C) Copyright 2010 Erich Seifert <dev[at]erichseifert.de>
*
* This file is part of VectorGraphics2D.
*
* VectorGraphics2D 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 3 of the License, or
* (at your option) any later version.
*
* VectorGraphics2D 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 VectorGraphics2D. If not, see <http://www.gnu.org/licenses/>.
*/
package de.erichseifert.vectorgraphics2d;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Image;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Map;
/**
* <code>Graphics2D</code> implementation that saves all operations to a string
* in the <i>Encapsulated PostScript®</i> (EPS) format.
*/
public class EPSGraphics2D extends VectorGraphics2D {
/** Constant to convert values from millimeters to PostScript® units
(1/72th inch). */
// protected static final double MM_IN_UNITS = 72.0 / 25.4;
protected static final double MM_IN_UNITS = 1.0;
/** Mapping of stroke endcap values from Java to PostScript®. */
private static final Map<Integer, Integer> STROKE_ENDCAPS = DataUtils.map(
new Integer[] { BasicStroke.CAP_BUTT, BasicStroke.CAP_ROUND, BasicStroke.CAP_SQUARE },
new Integer[] { 0, 1, 2 }
);
/** Mapping of line join values for path drawing from Java to
PostScript®. */
private static final Map<Integer, Integer> STROKE_LINEJOIN = DataUtils.map(
new Integer[] { BasicStroke.JOIN_MITER, BasicStroke.JOIN_ROUND, BasicStroke.JOIN_BEVEL },
new Integer[] { 0, 1, 2 }
);
/**
* Constructor that initializes a new <code>EPSGraphics2D</code> instance.
* The document dimension must be specified as parameters.
*/
public EPSGraphics2D(double x, double y, double width, double height) {
super(x, y, width, height);
writeHeader();
}
@Override
protected void writeString(String str, double x, double y) {
// Escape string
str = str.replaceAll("\\\\", "\\\\\\\\").replaceAll("\t", "\\\\t")
.replaceAll("\b", "\\\\b").replaceAll("\f", "\\\\f")
.replaceAll("\\(", "\\\\(").replaceAll("\\)", "\\\\)");
//float fontSize = getFont().getSize2D();
//float leading = getFont().getLineMetrics("", getFontRenderContext())
// .getLeading();
write("gsave 1 -1 scale ");
/*
// Extract lines
String[] lines = str.replaceAll("\r\n", "\n").replaceAll("\r", "\n")
.split("\n");
// Output lines
for (int i = 0; i < lines.length; i++) {
String line = lines[i];
write(x, " -", y + i*fontSize + ((i>0) ? leading : 0f),
" M (", line, ") show ");
}
*/
str = str.replaceAll("[\r\n]", "");
write(x, " -", y, " M (", str, ") show ");
writeln("grestore");
}
@Override
public void setStroke(Stroke s) {
BasicStroke bsPrev;
if (getStroke() instanceof BasicStroke) {
bsPrev = (BasicStroke) getStroke();
} else {
bsPrev = new BasicStroke();
}
super.setStroke(s);
if (s instanceof BasicStroke) {
BasicStroke bs = (BasicStroke) s;
if (bs.getLineWidth() != bsPrev.getLineWidth()) {
writeln(bs.getLineWidth(), " setlinewidth");
}
if (bs.getLineJoin() != bsPrev.getLineJoin()) {
writeln(STROKE_LINEJOIN.get(bs.getLineJoin()), " setlinejoin");
}
if (bs.getEndCap() != bsPrev.getEndCap()) {
writeln(STROKE_ENDCAPS.get(bs.getEndCap()), " setlinecap");
}
if ((!Arrays.equals(bs.getDashArray(), bsPrev.getDashArray())) ||
(bs.getDashPhase() != bsPrev.getDashPhase())) {
writeln("[", DataUtils.join(" ", bs.getDashArray()), "] ",
bs.getDashPhase(), " setdash");
}
}
}
@Override
protected void writeImage(Image img, int imgWidth, int imgHeight,
double x, double y, double width, double height) {
BufferedImage bufferedImg = GraphicsUtils.toBufferedImage(img);
String imgData = getEps(bufferedImg);
int bands = bufferedImg.getSampleModel().getNumBands();
int bitsPerPixel = (int) Math.ceil(
bufferedImg.getColorModel().getPixelSize() / 8.0) * 8;
int bitsPerSample = bitsPerPixel / bands;
if (bands > 3) {
bands = 3;
}
writeln("gsave");
writeln(x, " ", y, " ", width, " ", height, " ",
imgWidth, " ", imgHeight, " ", bitsPerSample, " img false ", bands,
" colorimage"
);
writeln(imgData, ">");
writeln("grestore");
}
@Override
public void setColor(Color c) {
Color color = getColor();
if (c != null) {
super.setColor(c);
// TODO Add transparency hints for PDF conversion?
/*if (color.getAlpha() != c.getAlpha()) {
double a = c.getAlpha()/255.0;
writeln("[ /ca ", a, " /SetTransparency pdfmark");
}*/
if (color.getRed() != c.getRed() || color.getGreen() != c.getGreen()
|| color.getBlue() != c.getBlue()) {
double r = c.getRed()/255.0;
double g = c.getGreen()/255.0;
double b = c.getBlue()/255.0;
writeln(r, " ", g, " ", b, " rgb");
}
}
}
@Override
public void setFont(Font font) {
if (!getFont().equals(font)) {
super.setFont(font);
writeln("/", font.getPSName(), " ", font.getSize2D(),
" selectfont");
}
}
@Override
public void setClip(Shape clip) {
if (getClip() != null) {
writeln("cliprestore");
}
super.setClip(clip);
if (getClip() != null) {
writeShape(getClip());
writeln(" clip");
}
}
@Override
public void setTransform(AffineTransform tx) {
if (getTransform().equals(tx)) {
return;
}
super.setTransform(tx);
double[] matrix = new double[6];
getTransform().getMatrix(matrix);
writeln("basematrix setmatrix [", DataUtils.join(" ", matrix),
"] concat");
}
@Override
public void translate(double tx, double ty) {
super.translate(tx, ty);
if ((tx != 0.0) || (ty != 0.0)) {
writeln(tx, " ", ty, " translate");
}
}
@Override
public void scale(double sx, double sy) {
super.scale(sx, sy);
if ((sx != 1.0) || (sy != 1.0)) {
writeln(sx, " ", sy, " scale");
}
}
@Override
public void rotate(double theta) {
super.rotate(theta);
if (theta != 0.0) {
writeln(theta/Math.PI*180.0, " rotate");
}
}
@Override
public void rotate(double theta, double x, double y) {
super.rotate(theta, x, y);
if (theta != 0.0) {
writeln(x, " ", y, " translate ", theta/Math.PI*180.0, " rotate ",
-x, " ", -y, " translate");
}
}
@Override
public void shear(double sx, double sy) {
super.shear(sx, sy);
if ((sx != 0.0) || (sy != 0.0)) {
setTransform(getTransform());
}
}
@Override
protected void writeHeader() {
Rectangle2D bounds = getBounds();
double x = bounds.getX() * MM_IN_UNITS;
double y = bounds.getY() * MM_IN_UNITS;
double w = bounds.getWidth() * MM_IN_UNITS;
double h = bounds.getHeight() * MM_IN_UNITS;
writeln("%!PS-Adobe-3.0 EPSF-3.0");
writeln("%%BoundingBox: ",
(int) Math.floor(x), " ", (int) Math.floor(y), " ",
(int) Math.ceil(x + w), " ", (int) Math.ceil(y + h));
writeln("%%HiResBoundingBox: ", x, " ", y, " ", x + w, " ", y + h);
writeln("%%LanguageLevel: 3");
writeln("%%Pages: 1");
writeln("%%Page: 1 1");
// Utility functions
writeln("/M /moveto load def");
writeln("/L /lineto load def");
writeln("/C /curveto load def");
writeln("/Z /closepath load def");
writeln("/RL /rlineto load def");
writeln("/rgb /setrgbcolor load def");
writeln("/rect { ",
"/height exch def /width exch def /y exch def /x exch def ",
"x y M width 0 RL 0 height RL width neg 0 RL ",
"} bind def");
writeln("/ellipse { ",
"/endangle exch def /startangle exch def ",
"/ry exch def /rx exch def /y exch def /x exch def ",
"/savematrix matrix currentmatrix def ",
"x y translate rx ry scale 0 0 1 startangle endangle arcn ",
"savematrix setmatrix ",
"} bind def");
writeln("/img { ",
"/bits exch def /imgheight exch def /imgwidth exch def ",
"/height exch def /width exch def /y exch def /x exch def ",
"x y translate width height scale ",
"imgwidth imgheight bits [imgwidth 0 0 imgheight 0 0] currentfile ",
"/ASCIIHexDecode filter ",
"} bind def");
// Set default font
writeln("/", getFont().getPSName(), " ", getFont().getSize2D(),
" selectfont");
//writeln("<< /AllowTransparency true >> setdistillerparams"); // TODO
// Save state
writeln("gsave");
// Save state
writeln("clipsave");
// Settings
writeln("/DeviceRGB setcolorspace");
// Adjust page size and page origin
writeln("0 ", h, " translate");
writeln(MM_IN_UNITS, " -", MM_IN_UNITS, " scale");
writeln("/basematrix matrix currentmatrix def");
}
/**
* Utility method for writing a tag closing fragment for drawing operations.
*/
@Override
protected void writeClosingDraw(Shape s) {
writeln(" stroke");
}
/**
* Utility method for writing a tag closing fragment for filling operations.
*/
@Override
protected void writeClosingFill(Shape s) {
// TODO Omit fill operation if paint isn't a Color object
writeln(" fill");
if (!(getPaint() instanceof Color)) {
super.writeClosingFill(s);
}
}
/**
* Utility method for writing an arbitrary shape to.
* It tries to translate Java2D shapes to the corresponding EPS shape
* commands.
*/
@Override
protected void writeShape(Shape s) {
write("newpath ");
if (s instanceof Line2D) {
Line2D l = (Line2D) s;
double x1 = l.getX1();
double y1 = l.getY1();
double x2 = l.getX2();
double y2 = l.getY2();
write(x1, " ", y1, " M ", x2, " ", y2, " L");
return;
} else if (s instanceof Rectangle2D) {
Rectangle2D r = (Rectangle2D) s;
double x = r.getX();
double y = r.getY();
double width = r.getWidth();
double height = r.getHeight();
write(x, " ", y, " ", width, " ", height, " rect Z");
return;
} else if (s instanceof Ellipse2D) {
Ellipse2D e = (Ellipse2D) s;
double x = e.getX() + e.getWidth()/2.0;
double y = e.getY() + e.getHeight()/2.0;
double rx = e.getWidth()/2.0;
double ry = e.getHeight()/2.0;
write(x, " ", y, " ", rx, " ", ry, " ", 360.0, " ", 0.0,
" ellipse Z");
return;
} else if (s instanceof Arc2D) {
Arc2D e = (Arc2D) s;
double x = (e.getX() + e.getWidth()/2.0);
double y = (e.getY() + e.getHeight()/2.0);
double rx = e.getWidth()/2.0;
double ry = e.getHeight()/2.0;
double startAngle = -e.getAngleStart();
double endAngle = -(e.getAngleStart() + e.getAngleExtent());
write(x, " ", y, " ", rx, " ", ry, " ", startAngle, " ", endAngle,
" ellipse");
if (e.getArcType() == Arc2D.CHORD) {
write(" Z");
} else if (e.getArcType() == Arc2D.PIE) {
write(" ", x, " ", y, " L Z");
}
return;
} else {
PathIterator segments = s.getPathIterator(null);
double[] coordsCur = new double[6];
double[] pointPrev = new double[2];
for (int i = 0; !segments.isDone(); i++, segments.next()) {
if (i > 0) {
write(" ");
}
int segmentType = segments.currentSegment(coordsCur);
switch (segmentType) {
case PathIterator.SEG_MOVETO:
write(coordsCur[0], " ", coordsCur[1], " M");
pointPrev[0] = coordsCur[0];
pointPrev[1] = coordsCur[1];
break;
case PathIterator.SEG_LINETO:
write(coordsCur[0], " ", coordsCur[1], " L");
pointPrev[0] = coordsCur[0];
pointPrev[1] = coordsCur[1];
break;
case PathIterator.SEG_CUBICTO:
write(coordsCur[0], " ", coordsCur[1], " ",
coordsCur[2], " ", coordsCur[3], " ",
coordsCur[4], " ", coordsCur[5], " C");
pointPrev[0] = coordsCur[4];
pointPrev[1] = coordsCur[5];
break;
case PathIterator.SEG_QUADTO:
double x1 = pointPrev[0] + 2.0/3.0*(coordsCur[0] - pointPrev[0]);
double y1 = pointPrev[1] + 2.0/3.0*(coordsCur[1] - pointPrev[1]);
double x2 = coordsCur[0] + 1.0/3.0*(coordsCur[2] - coordsCur[0]);
double y2 = coordsCur[1] + 1.0/3.0*(coordsCur[3] - coordsCur[1]);
double x3 = coordsCur[2];
double y3 = coordsCur[3];
write(x1, " ", y1, " ", x2, " ", y2, " ", x3, " ", y3,
" C");
pointPrev[0] = x3;
pointPrev[1] = y3;
break;
case PathIterator.SEG_CLOSE:
write("Z");
break;
default:
throw new IllegalStateException("Unknown path operation.");
}
}
}
}
public static String getEps(BufferedImage bufferedImg) {
int width = bufferedImg.getWidth();
int height = bufferedImg.getHeight();
int bands = bufferedImg.getSampleModel().getNumBands();
StringBuffer str = new StringBuffer(width*height*bands*2);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int pixel = bufferedImg.getRGB(x, y) & 0xffffff;
if (bands >= 3) {
String hex = String.format("%06x", pixel);
str.append(hex);
} else if (bands == 1) {
str.append(String.format("%02x", pixel));
}
}
str.append("\n");
}
return str.toString();
}
@Override
protected String getFooter() {
return "grestore % Restore state\n%%EOF\n";
}
@Override
public byte[] getBytes() {
try {
return toString().getBytes("ISO-8859-1");
} catch (UnsupportedEncodingException e) {
return super.getBytes();
}
}
}

View file

@ -0,0 +1,125 @@
/*
* VectorGraphics2D: Vector export for Java(R) Graphics2D
*
* (C) Copyright 2010 Erich Seifert <dev[at]erichseifert.de>
*
* This file is part of VectorGraphics2D.
*
* VectorGraphics2D 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 3 of the License, or
* (at your option) any later version.
*
* VectorGraphics2D 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 VectorGraphics2D. If not, see <http://www.gnu.org/licenses/>.
*/
package de.erichseifert.vectorgraphics2d;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.PixelGrabber;
import javax.swing.ImageIcon;
/**
* Abstract class that contains utility functions for working with graphics.
* For example, this includes font handling.
*/
public abstract class GraphicsUtils {
/**
* Default constructor that prevents creation of class.
*/
protected GraphicsUtils() {
throw new UnsupportedOperationException();
}
/**
* This method returns <code>true</code> if the specified image
* has transparent pixels.
* Taken from http://www.exampledepot.com/egs/java.awt.image/HasAlpha.html
* @param image
* @return <code>true</code> if the specified image has transparent pixels,
* <code>false</code> otherwise
*/
public static boolean hasAlpha(Image image) {
// If buffered image, the color model is readily available
if (image instanceof BufferedImage) {
BufferedImage bimage = (BufferedImage) image;
return bimage.getColorModel().hasAlpha();
}
// Use a pixel grabber to retrieve the image's color model;
// grabbing a single pixel is usually sufficient
PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
try {
pg.grabPixels();
} catch (InterruptedException e) {
}
// Get the image's color model
ColorModel cm = pg.getColorModel();
return cm.hasAlpha();
}
/**
* This method returns a buffered image with the contents of an image.
* Taken from http://www.exampledepot.com/egs/java.awt.image/Image2Buf.html
* @param image Image ot be converted
* @return a buffered image with the contents of the specified image
*/
public static BufferedImage toBufferedImage(Image image) {
if (image instanceof BufferedImage) {
return (BufferedImage) image;
}
// This code ensures that all the pixels in the image are loaded
image = new ImageIcon(image).getImage();
// Determine if the image has transparent pixels
boolean hasAlpha = hasAlpha(image);
// Create a buffered image with a format that's compatible with the
// screen
BufferedImage bimage = null;
GraphicsEnvironment ge = GraphicsEnvironment
.getLocalGraphicsEnvironment();
try {
// Determine the type of transparency of the new buffered image
int transparency = Transparency.OPAQUE;
if (hasAlpha) {
transparency = Transparency.BITMASK;
}
// Create the buffered image
GraphicsDevice gs = ge.getDefaultScreenDevice();
GraphicsConfiguration gc = gs.getDefaultConfiguration();
bimage = gc.createCompatibleImage(
image.getWidth(null), image.getHeight(null), transparency);
} catch (HeadlessException e) {
// The system does not have a screen
bimage = null;
}
if (bimage == null) {
// Create a buffered image using the default color model
int type = BufferedImage.TYPE_INT_RGB;
if (hasAlpha) {
type = BufferedImage.TYPE_INT_ARGB;
}
bimage = new BufferedImage(
image.getWidth(null), image.getHeight(null), type);
}
// Copy image to buffered image
Graphics g = bimage.createGraphics();
// Paint the image onto the buffered image
g.drawImage(image, 0, 0, null);
g.dispose();
return bimage;
}
}

View file

@ -0,0 +1,637 @@
/*
* VectorGraphics2D: Vector export for Java(R) Graphics2D
*
* (C) Copyright 2010 Erich Seifert <dev[at]erichseifert.de>
*
* This file is part of VectorGraphics2D.
*
* VectorGraphics2D 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 3 of the License, or
* (at your option) any later version.
*
* VectorGraphics2D 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 VectorGraphics2D. If not, see <http://www.gnu.org/licenses/>.
*/
package de.erichseifert.vectorgraphics2d;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Image;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import rene.zirkel.objects.AngleObject;
/**
* <code>Graphics2D</code> implementation that saves all operations to a string
* in the <i>Portable Document Format</i> (PDF).
*/
public class PDFGraphics2D extends VectorGraphics2D {
/** Prefix string for PDF font resource ids. */
protected static final String FONT_RESOURCE_PREFIX = "F";
/** Prefix string for PDF image resource ids. */
protected static final String IMAGE_RESOURCE_PREFIX = "Im";
/** Prefix string for PDF transparency resource ids. */
protected static final String TRANSPARENCY_RESOURCE_PREFIX = "T";
/** Constant to convert values from millimeters to PostScript®/PDF units (1/72th inch). */
protected static final double MM_IN_UNITS = 72.0 / 25.4;
/** Mapping of stroke endcap values from Java to PDF. */
private static final Map<Integer, Integer> STROKE_ENDCAPS = DataUtils.map(
new Integer[] { BasicStroke.CAP_BUTT, BasicStroke.CAP_ROUND, BasicStroke.CAP_SQUARE },
new Integer[] { 0, 1, 2 }
);
/** Mapping of line join values for path drawing from Java to PDF. */
private static final Map<Integer, Integer> STROKE_LINEJOIN = DataUtils.map(
new Integer[] { BasicStroke.JOIN_MITER, BasicStroke.JOIN_ROUND, BasicStroke.JOIN_BEVEL },
new Integer[] { 0, 1, 2 }
);
/** Id of the current PDF object. */
private int curObjId;
/** Mapping from objects to file positions. */
private final Map<Integer, Integer> objPositions;
/** Mapping from transparency levels to transparency resource ids. */
private final Map<Double, String> transpResources;
/** Mapping from image data to image resource ids. */
private final Map<BufferedImage, String> imageResources;
/** Mapping from font objects to font resource ids. */
private final Map<Font, String> fontResources;
/** File position of the actual content. */
private int contentStart;
/**
* Constructor that initializes a new <code>PDFGraphics2D</code> instance.
* The document dimension must be specified as parameters.
*/
public PDFGraphics2D(double x, double y, double width, double height) {
super(x, y, width, height);
curObjId = 1;
objPositions = new TreeMap<Integer, Integer>();
transpResources = new TreeMap<Double, String>();
imageResources = new LinkedHashMap<BufferedImage, String>();
fontResources = new LinkedHashMap<Font, String>();
writeHeader();
}
@Override
protected void writeString(String str, double x, double y) {
// Escape string
str = str.replaceAll("\\\\", "\\\\\\\\")
.replaceAll("\t", "\\\\t").replaceAll("\b", "\\\\b").replaceAll("\f", "\\\\f")
.replaceAll("\\(", "\\\\(").replaceAll("\\)", "\\\\)");
x+=getTransform().getTranslateX();
y+=getTransform().getTranslateY();
float fontSize = getFont().getSize2D();
//float leading = getFont().getLineMetrics("", getFontRenderContext()).getLeading();
// Start text and save current graphics state
writeln("q BT");
String fontResourceId = getFontResource(getFont());
writeln("/", fontResourceId, " ", fontSize, " Tf");
// Set leading
//writeln(fontSize + leading, " TL");
// Undo swapping of y axis for text
writeln("1 0 0 -1 ", x, " ", y, " cm");
/*
// Extract lines
String[] lines = str.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n");
// Paint lines
for (int i = 0; i < lines.length; i++) {
writeln("(", lines[i], ") ", (i == 0) ? "Tj" : "'");
}*/
str = str.replaceAll("[\r\n]", "");
writeln("(", str, ") Tj");
// End text and restore previous graphics state
writeln("ET Q");
}
@Override
public void setStroke(Stroke s) {
BasicStroke bsPrev;
if (getStroke() instanceof BasicStroke) {
bsPrev = (BasicStroke) getStroke();
} else {
bsPrev = new BasicStroke();
}
super.setStroke(s);
if (s instanceof BasicStroke) {
BasicStroke bs = (BasicStroke) s;
if (bs.getLineWidth() != bsPrev.getLineWidth()) {
writeln(bs.getLineWidth(), " w");
}
if (bs.getLineJoin() != bsPrev.getLineJoin()) {
writeln(STROKE_LINEJOIN.get(bs.getLineJoin()), " j");
}
if (bs.getEndCap() != bsPrev.getEndCap()) {
writeln(STROKE_ENDCAPS.get(bs.getEndCap()), " J");
}
if ((!Arrays.equals(bs.getDashArray(), bsPrev.getDashArray()))
|| (bs.getDashPhase() != bsPrev.getDashPhase())) {
writeln("[", DataUtils.join(" ", bs.getDashArray()), "] ",
bs.getDashPhase(), " d");
}
}
}
@Override
protected void writeImage(Image img, int imgWidth, int imgHeight,
double x, double y, double width, double height) {
BufferedImage bufferedImg = GraphicsUtils.toBufferedImage(img);
String imageResourceId = getImageResource(bufferedImg);
// Save graphics state
write("q ");
// Take current transformations into account
AffineTransform txCurrent = getTransform();
if (!txCurrent.isIdentity()) {
double[] matrix = new double[6];
txCurrent.getMatrix(matrix);
write(DataUtils.join(" ", matrix), " cm ");
}
// Move image to correct position and scale it to (width, height)
write(width, " 0 0 ", height, " ", x, " ", y, " cm ");
// Swap y axis
write("1 0 0 -1 0 1 cm ");
// Draw image
write("/", imageResourceId, " Do ");
// Restore old graphics state
writeln("Q");
}
@Override
public void setColor(Color c) {
Color color = getColor();
if (c != null) {
super.setColor(c);
if (color.getAlpha() != c.getAlpha()) {
// Add a new graphics state to resources
double a = c.getAlpha()/255.0;
String transpResourceId = getTransparencyResource(a);
writeln("/", transpResourceId, " gs");
}
if (color.getRed() != c.getRed() || color.getGreen() != c.getGreen()
|| color.getBlue() != c.getBlue()) {
double r = c.getRed()/255.0;
double g = c.getGreen()/255.0;
double b = c.getBlue()/255.0;
write(r, " ", g, " ", b, " rg ");
writeln(r, " ", g, " ", b, " RG");
}
}
}
@Override
public void setClip(Shape clip) {
if (getClip() != null) {
writeln("Q");
}
super.setClip(clip);
if (getClip() != null) {
writeln("q");
writeShape(getClip());
writeln(" W n");
}
}
// TODO Correct transformations
/*
@Override
protected void setAffineTransform(AffineTransform tx) {
if (getTransform().equals(tx)) {
return;
}
// Undo previous transforms
if (isTransformed()) {
writeln("Q");
}
// Set new transform
super.setAffineTransform(tx);
// Write transform to document
if (isTransformed()) {
writeln("q");
double[] matrix = new double[6];
getTransform().getMatrix(matrix);
writeln(DataUtils.join(" ", matrix), " cm");
}
}
//*/
@Override
protected void writeHeader() {
Rectangle2D bounds = getBounds();
int x = (int) Math.floor(bounds.getX() * MM_IN_UNITS);
int y = (int) Math.floor(bounds.getY() * MM_IN_UNITS);
int w = (int) Math.ceil(bounds.getWidth() * MM_IN_UNITS);
int h = (int) Math.ceil(bounds.getHeight() * MM_IN_UNITS);
writeln("%PDF-1.4");
// Object 1
writeObj(
"Type", "/Catalog",
"Pages", "2 0 R"
);
// Object 2
writeObj(
"Type", "/Pages",
"Kids", "[3 0 R]",
"Count", "1"
);
// Object 3
writeObj(
"Type", "/Page",
"Parent", "2 0 R",
"MediaBox", String.format("[%d %d %d %d]", x, y, w, h),
"Contents", "4 0 R",
"Resources", "6 0 R"
);
// Object 5
writeln(nextObjId(size()), " 0 obj");
writeDict("Length", "5 0 R");
writeln("stream");
contentStart = size();
writeln("q");
// Adjust page size and page origin
writeln(MM_IN_UNITS, " 0 0 ", -MM_IN_UNITS, " 0 ", h, " cm");
}
/**
* Write a PDF dictionary from the specified collection of objects.
* The passed objects are converted to strings. Every object with odd
* position is used as key, every object with even position is used
* as value.
* @param strs Objects to be written to dictionary
*/
protected void writeDict(Object... strs) {
writeln("<<");
for (int i = 0; i < strs.length; i += 2) {
writeln("/", strs[i], " ", strs[i + 1]);
}
writeln(">>");
}
/**
* Write a collection of elements to the document stream as PDF object.
* The passed objects are converted to strings.
* @param strs Objects to be written to the document stream.
* @return Id of the PDF object that was written.
*/
protected int writeObj(Object... strs) {
int objId = nextObjId(size());
writeln(objId, " 0 obj");
writeDict(strs);
writeln("endobj");
return objId;
}
/**
* Returns the next PDF object id without incrementing.
* @return Next PDF object id.
*/
protected int peekObjId() {
return curObjId + 1;
}
/**
* Returns a new PDF object id with every call.
* @param position File position of the object.
* @return A new PDF object id.
*/
private int nextObjId(int position) {
objPositions.put(curObjId, position);
return curObjId++;
}
/**
* Returns the resource for the specified transparency level.
* @param a Transparency level.
* @return A new PDF object id.
*/
protected String getTransparencyResource(double a) {
String name = transpResources.get(a);
if (name == null) {
name = String.format("%s%d", TRANSPARENCY_RESOURCE_PREFIX,
transpResources.size() + 1);
transpResources.put(a, name);
}
return name;
}
/**
* Returns the resource for the specified image data.
* @param bufferedImg Image object with data.
* @return A new PDF object id.
*/
protected String getImageResource(BufferedImage bufferedImg) {
String name = imageResources.get(bufferedImg);
if (name == null) {
name = String.format("%s%d", IMAGE_RESOURCE_PREFIX,
imageResources.size() + 1);
imageResources.put(bufferedImg, name);
}
return name;
}
/**
* Returns the resource describing the specified font.
* @param font Font to be described.
* @return A new PDF object id.
*/
protected String getFontResource(Font font) {
String name = fontResources.get(font);
if (name == null) {
name = String.format("%s%d", FONT_RESOURCE_PREFIX,
fontResources.size() + 1);
fontResources.put(font, name);
}
return name;
}
/**
* Utility method for writing a tag closing fragment for drawing
* operations.
*/
@Override
protected void writeClosingDraw(Shape s) {
writeln(" S");
}
/**
* Utility method for writing a tag closing fragment for filling
* operations.
*/
@Override
protected void writeClosingFill(Shape s) {
writeln(" f");
if (!(getPaint() instanceof Color)) {
super.writeClosingFill(s);
}
}
/**
* Utility method for writing an arbitrary shape to.
* It tries to translate Java2D shapes to the corresponding PDF shape
* commands.
*/
@Override
protected void writeShape(Shape s) {
// TODO Correct transformations
// if (s instanceof Line2D) {
// Line2D l = (Line2D) s;
// double x1 = l.getX1();
// double y1 = l.getY1();
// double x2 = l.getX2();
// double y2 = l.getY2();
// write(x1, " ", y1, " m ", x2, " ", y2, " l");
// } else if (s instanceof Rectangle2D) {
// Rectangle2D r = (Rectangle2D) s;
// double x = r.getX();
// double y = r.getY();
// double width = r.getWidth();
// double height = r.getHeight();
// write(x, " ", y, " ", width, " ", height, " re");
// } else
{
s = getTransform().createTransformedShape(s);
PathIterator segments = s.getPathIterator(null);
double[] coordsCur = new double[6];
double[] pointPrev = new double[2];
for (int i = 0; !segments.isDone(); i++, segments.next()) {
if (i > 0) {
write(" ");
}
int segmentType = segments.currentSegment(coordsCur);
switch (segmentType) {
case PathIterator.SEG_MOVETO:
write(coordsCur[0], " ", coordsCur[1], " m");
pointPrev[0] = coordsCur[0];
pointPrev[1] = coordsCur[1];
break;
case PathIterator.SEG_LINETO:
write(coordsCur[0], " ", coordsCur[1], " l");
pointPrev[0] = coordsCur[0];
pointPrev[1] = coordsCur[1];
break;
case PathIterator.SEG_CUBICTO:
write(coordsCur[0], " ", coordsCur[1], " ",
coordsCur[2], " ", coordsCur[3], " ",
coordsCur[4], " ", coordsCur[5], " c");
pointPrev[0] = coordsCur[4];
pointPrev[1] = coordsCur[5];
break;
case PathIterator.SEG_QUADTO:
double x1 = pointPrev[0] + 2.0/3.0*(coordsCur[0] - pointPrev[0]);
double y1 = pointPrev[1] + 2.0/3.0*(coordsCur[1] - pointPrev[1]);
double x2 = coordsCur[0] + 1.0/3.0*(coordsCur[2] - coordsCur[0]);
double y2 = coordsCur[1] + 1.0/3.0*(coordsCur[3] - coordsCur[1]);
double x3 = coordsCur[2];
double y3 = coordsCur[3];
write(x1, " ", y1, " ", x2, " ", y2, " ",
x3, " ", y3, " c");
pointPrev[0] = x3;
pointPrev[1] = y3;
break;
case PathIterator.SEG_CLOSE:
write("h");
break;
default:
throw new IllegalStateException("Unknown path operation.");
}
}
}
}
/**
* Returns a string which represents the data of the specified image.
* @param bufferedImg Image to convert.
* @return String with image data.
*/
private String getPdf(BufferedImage bufferedImg) {
int width = bufferedImg.getWidth();
int height = bufferedImg.getHeight();
int bands = bufferedImg.getSampleModel().getNumBands();
StringBuffer str = new StringBuffer(width*height*bands*2);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int pixel = bufferedImg.getRGB(x, y) & 0xffffff;
if (bands >= 3) {
String hex = String.format("%06x", pixel);
str.append(hex);
} else if (bands == 1) {
str.append(String.format("%02x", pixel));
}
}
str.append('\n');
}
return str.append('>').toString();
}
@Override
protected String getFooter() {
StringBuffer footer = new StringBuffer();
// TODO Correct transformations
/*if (isTransformed()) {
footer.append("Q\n");
}*/
if (getClip() != null) {
footer.append("Q\n");
}
footer.append("Q");
int contentEnd = size() + footer.length();
footer.append('\n');
footer.append("endstream\n");
footer.append("endobj\n");
int lenObjId = nextObjId(size() + footer.length());
footer.append(lenObjId).append(" 0 obj\n");
footer.append(contentEnd - contentStart).append('\n');
footer.append("endobj\n");
int resourcesObjId = nextObjId(size() + footer.length());
footer.append(resourcesObjId).append(" 0 obj\n");
footer.append("<<\n");
footer.append(" /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]\n");
// Add resources for fonts
if (!fontResources.isEmpty()) {
footer.append(" /Font <<\n");
for (Map.Entry<Font, String> entry : fontResources.entrySet()) {
Font font = entry.getKey();
String resourceId = entry.getValue();
footer.append(" /").append(resourceId)
.append(" << /Type /Font")
.append(" /Subtype /").append("TrueType")
.append(" /BaseFont /").append(font.getPSName())
.append(" /Encoding /").append("WinAnsiEncoding")
.append(" >>\n");
}
footer.append(" >>\n");
}
// Add resources for images
if (!imageResources.isEmpty()) {
footer.append(" /XObject <<\n");
int objIdOffset = 0;
for (Map.Entry<BufferedImage, String> entry :
imageResources.entrySet()) {
String resourceId = entry.getValue();
footer.append(" /").append(resourceId).append(' ')
.append(curObjId + objIdOffset).append(" 0 R\n");
objIdOffset++;
}
footer.append(" >>\n");
}
// Add resources for transparency levels
if (!transpResources.isEmpty()) {
footer.append(" /ExtGState <<\n");
for (Map.Entry<Double, String> entry : transpResources.entrySet()) {
Double alpha = entry.getKey();
String resourceId = entry.getValue();
footer.append(" /").append(resourceId)
.append(" << /Type /ExtGState")
.append(" /ca ").append(alpha).append(" /CA ").append(alpha)
.append(" >>\n");
}
footer.append(" >>\n");
}
footer.append(">>\n");
footer.append("endobj\n");
// Add data of images
for (BufferedImage image : imageResources.keySet()) {
int imageObjId = nextObjId(size() + footer.length());
footer.append(imageObjId).append(" 0 obj\n");
footer.append("<<\n");
String imageData = getPdf(image);
footer.append("/Type /XObject\n")
.append("/Subtype /Image\n")
.append("/Width ").append(image.getWidth()).append('\n')
.append("/Height ").append(image.getHeight()).append('\n')
.append("/ColorSpace /DeviceRGB\n")
.append("/BitsPerComponent 8\n")
.append("/Length ").append(imageData.length()).append('\n')
.append("/Filter /ASCIIHexDecode\n")
.append(">>\n")
.append("stream\n")
.append(imageData)
.append("\nendstream\n")
.append("endobj\n");
}
int objs = objPositions.size() + 1;
int xrefPos = size() + footer.length();
footer.append("xref\n");
footer.append("0 ").append(objs).append('\n');
// lines of xref entries must must be exactly 20 bytes long
// (including line break) and thus end with <SPACE NEWLINE>
footer.append(String.format("%010d %05d", 0, 65535)).append(" f \n");
for (int pos : objPositions.values()) {
footer.append(String.format("%010d %05d", pos, 0)).append(" n \n");
}
footer.append("trailer\n");
footer.append("<<\n");
footer.append("/Size ").append(objs).append('\n');
footer.append("/Root 1 0 R\n");
footer.append(">>\n");
footer.append("startxref\n");
footer.append(xrefPos).append('\n');
footer.append("%%EOF\n");
return footer.toString();
}
@Override
public String toString() {
String doc = super.toString();
//doc = doc.replaceAll("q\n[0-9]+\\.?[0-9]* [0-9]+\\.?[0-9]* [0-9]+\\.?[0-9]* [0-9]+\\.?[0-9]* [0-9]+\\.?[0-9]* [0-9]+\\.?[0-9]* cm\nQ\n", "");
return doc;
}
@Override
public byte[] getBytes() {
try {
return toString().getBytes("ISO-8859-1");
} catch (UnsupportedEncodingException e) {
return super.getBytes();
}
}
}

View file

@ -0,0 +1,371 @@
/*
* VectorGraphics2D: Vector export for Java(R) Graphics2D
*
* (C) Copyright 2010 Erich Seifert <dev[at]erichseifert.de>
*
* This file is part of VectorGraphics2D.
*
* VectorGraphics2D 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 3 of the License, or
* (at your option) any later version.
*
* VectorGraphics2D 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 VectorGraphics2D. If not, see <http://www.gnu.org/licenses/>.
*/
package de.erichseifert.vectorgraphics2d;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Image;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.xml.bind.DatatypeConverter;
/**
* <code>Graphics2D</code> implementation that saves all operations to a string
* in the <i>Scaled Vector Graphics</i> (SVG) format.
*/
public class SVGGraphics2D extends VectorGraphics2D {
/** Mapping of stroke endcap values from Java to SVG. */
private static final Map<Integer, String> STROKE_ENDCAPS = DataUtils.map(
new Integer[] { BasicStroke.CAP_BUTT, BasicStroke.CAP_ROUND, BasicStroke.CAP_SQUARE },
new String[] { "butt", "round", "square" }
);
/** Mapping of line join values for path drawing from Java to SVG. */
private static final Map<Integer, String> STROKE_LINEJOIN = DataUtils.map(
new Integer[] { BasicStroke.JOIN_MITER, BasicStroke.JOIN_ROUND, BasicStroke.JOIN_BEVEL },
new String[] { "miter", "round", "bevel" }
);
/** Prefix string for ids of clipping paths. */
private static final String CLIP_PATH_ID = "clip";
/** Number of the current clipping path. */
private long clipCounter;
/**
* Constructor that initializes a new <code>SVGGraphics2D</code> instance.
* The document dimension must be specified as parameters.
*/
public SVGGraphics2D(double x, double y, double width, double height) {
super(x, y, width, height);
writeHeader();
}
@Override
protected void writeString(String str, double x, double y) {
// Escape string
str = str.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
float fontSize = getFont().getSize2D();
//float leading = getFont().getLineMetrics("", getFontRenderContext()).getLeading();
/*
// Extract lines
String[] lines = str.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n");
// Output lines
writeln("<text style=\"font:", fontSize, "px ", getFont().getFamily(), "\">");
for (int i = 0; i < lines.length; i++) {
String line = lines[i];
writeln(" <tspan x=\"", x, "\" y=\"", y + i*fontSize + ((i>0) ? leading : 0f), "\">", line, "</tspan>");
}
writeln("</text>");
*/
str = str.replaceAll("[\r\n]", "");
writeln("<text x=\"", x, "\" y=\"", y, "\" style=\"font:",
fontSize, "px ", getFont().getFamily(), "\">", str, "</text>");
}
@Override
protected void writeImage(Image img, int imgWidth, int imgHeight, double x,
double y, double width, double height) {
BufferedImage bufferedImg = GraphicsUtils.toBufferedImage(img);
String imgData = getSvg(bufferedImg);
write("<image x=\"" , x, "\" y=\"" , y, "\" ",
"width=\"" , width, "\" height=\"" , height, "\" ",
"xlink:href=\"", imgData, "\" ",
"/>");
}
@Override
public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) {
Path2D s = new Path2D.Double(Path2D.WIND_NON_ZERO, xPoints.length);
write("<polygon points=\"");
for (int i = 0; i < nPoints; i++) {
if (i == 0) {
s.moveTo(xPoints[i], yPoints[i]);
} else {
s.lineTo(xPoints[i], yPoints[i]);
write(" ");
}
write(xPoints[i], ",", yPoints[i]);
}
write("\" ");
s.closePath();
writeClosingDraw(s);
}
@Override
public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) {
Path2D s = new Path2D.Double(Path2D.WIND_NON_ZERO, xPoints.length);
write("<polyline points=\"");
for (int i = 0; i < nPoints; i++) {
if (i == 0) {
s.moveTo(xPoints[i], yPoints[i]);
} else {
s.lineTo(xPoints[i], yPoints[i]);
write(" ");
}
write(xPoints[i], ",", yPoints[i]);
}
write("\" ");
writeClosingDraw(s);
}
@Override
public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) {
Path2D s = new Path2D.Double(Path2D.WIND_NON_ZERO, xPoints.length);
write("<polygon points=\"");
for (int i = 0; i < nPoints; i++) {
if (i == 0) {
s.moveTo(xPoints[i], yPoints[i]);
} else {
s.lineTo(xPoints[i], yPoints[i]);
write(" ");
}
write(xPoints[i], ",", yPoints[i]);
}
write("\" ");
s.closePath();
writeClosingFill(s);
}
@Override
public void setClip(Shape clip) {
super.setClip(clip);
if (getClip() != null) {
writeln("<clipPath id=\"", CLIP_PATH_ID, ++clipCounter, "\">");
writeShape(getClip());
writeln("/>");
writeln("</clipPath>");
}
}
@Override
protected void setAffineTransform(AffineTransform tx) {
if (getTransform().equals(tx)) {
return;
}
// Close previous transformation group
if (isTransformed()) {
writeln("</g>");
}
// Set transformation matrix
super.setAffineTransform(tx);
// Begin new transformation group
if (isTransformed()) {
double[] matrix = new double[6];
getTransform().getMatrix(matrix);
write("<g transform=\"matrix(",
DataUtils.join(" ", matrix),") \">");
}
}
@Override
protected void writeHeader() {
Rectangle2D bounds = getBounds();
double x = bounds.getX();
double y = bounds.getY();
double w = bounds.getWidth();
double h = bounds.getHeight();
writeln("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
writeln("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" ",
"\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">");
writeln("<svg version=\"1.2\" xmlns=\"http://www.w3.org/2000/svg\" ",
"xmlns:xlink=\"http://www.w3.org/1999/xlink\" ",
"x=\"", x, "mm\" y=\"", y, "mm\" ",
"width=\"", w, "mm\" height=\"", h, "mm\" " +
"viewBox=\"", x, " ", y, " ", w, " ", h, "\"",
">");
writeln("<style type=\"text/css\"><![CDATA[");
writeln("text { font:", getFont().getSize2D(), "px ",
getFont().getFamily(), "; }");
writeln("]]></style>");
}
@Override
protected void writeClosingDraw(Shape s) {
write("style=\"fill:none;stroke:", getSvg(getColor()));
if (getStroke() instanceof BasicStroke) {
BasicStroke stroke = (BasicStroke) getStroke();
if (stroke.getLineWidth() != 1f) {
write(";stroke-width:", stroke.getLineWidth());
}
if (stroke.getEndCap() != BasicStroke.CAP_BUTT) {
write(";stroke-linecap:", STROKE_ENDCAPS.get(stroke.getEndCap()));
}
if (stroke.getLineJoin() != BasicStroke.JOIN_MITER) {
write(";stroke-linejoin:",
STROKE_LINEJOIN.get(stroke.getLineJoin()));
}
//write(";stroke-miterlimit:", s.getMiterLimit());
if (stroke.getDashArray() != null && stroke.getDashArray().length > 0) {
write(";stroke-dasharray:",
DataUtils.join(",", stroke.getDashArray()));
write(";stroke-dashoffset:", stroke.getDashPhase());
}
}
if (getClip() != null) {
write("\" clip-path=\"url(#", CLIP_PATH_ID, clipCounter, ")");
}
writeln("\" />");
}
@Override
protected void writeClosingFill(Shape s) {
if (getPaint() instanceof Color) {
write("style=\"fill:", getSvg(getColor()), ";stroke:none");
if (getClip() != null) {
write("\" clip-path=\"url(#", CLIP_PATH_ID, clipCounter, ")");
}
writeln("\" />");
} else {
write("style=\"stroke:none\" />");
super.writeClosingFill(s);
}
}
@Override
protected void writeShape(Shape s) {
if (s instanceof Line2D) {
Line2D l = (Line2D) s;
double x1 = l.getX1();
double y1 = l.getY1();
double x2 = l.getX2();
double y2 = l.getY2();
write("<line x1=\"", x1, "\" y1=\"", y1,
"\" x2=\"", x2, "\" y2=\"", y2, "\" ");
} else if (s instanceof Rectangle2D) {
Rectangle2D r = (Rectangle2D) s;
double x = r.getX();
double y = r.getY();
double width = r.getWidth();
double height = r.getHeight();
write("<rect x=\"", x, "\" y=\"", y,
"\" width=\"", width, "\" height=\"", height, "\" ");
} else if (s instanceof RoundRectangle2D) {
RoundRectangle2D r = (RoundRectangle2D) s;
double x = r.getX();
double y = r.getY();
double width = r.getWidth();
double height = r.getHeight();
double arcWidth = r.getArcWidth();
double arcHeight = r.getArcHeight();
write("<rect x=\"", x, "\" y=\"", y,
"\" width=\"", width, "\" height=\"", height,
"\" rx=\"", arcWidth, "\" ry=\"", arcHeight, "\" ");
} else if (s instanceof Ellipse2D) {
Ellipse2D e = (Ellipse2D) s;
double x = e.getX();
double y = e.getY();
double rx = e.getWidth()/2.0;
double ry = e.getHeight()/2.0;
write("<ellipse cx=\"", x + rx, "\" cy=\"", y + ry,
"\" rx=\"", rx, "\" ry=\"", ry, "\" ");
} else {
write("<path d=\"");
PathIterator segments = s.getPathIterator(null);
double[] coords = new double[6];
for (int i = 0; !segments.isDone(); i++, segments.next()) {
if (i > 0) {
write(" ");
}
int segmentType = segments.currentSegment(coords);
switch (segmentType) {
case PathIterator.SEG_MOVETO:
write("M", coords[0], ",", coords[1]);
break;
case PathIterator.SEG_LINETO:
write("L", coords[0], ",", coords[1]);
break;
case PathIterator.SEG_CUBICTO:
write("C", coords[0], ",", coords[1], " ",
coords[2], ",", coords[3], " ",
coords[4], ",", coords[5]);
break;
case PathIterator.SEG_QUADTO:
write("Q", coords[0], ",", coords[1], " ",
coords[2], ",", coords[3]);
break;
case PathIterator.SEG_CLOSE:
write("Z");
break;
default:
throw new IllegalStateException("Unknown path operation.");
}
}
write("\" ");
}
}
private static String getSvg(Color c) {
String color = "rgb(" + c.getRed() + "," + c.getGreen() + "," +
c.getBlue() + ")";
if (c.getAlpha() < 255) {
double opacity = c.getAlpha()/255.0;
color += ";opacity:" + opacity;
}
return color;
}
private static String getSvg(BufferedImage bufferedImg) {
ByteArrayOutputStream data = new ByteArrayOutputStream();
try {
ImageIO.write(bufferedImg, "png", data);
} catch (IOException e) {
return "";
}
String dataBase64 = DatatypeConverter
.printBase64Binary(data.toByteArray());
return "data:image/png;base64," + dataBase64;
}
@Override
protected String getFooter() {
String footer = "";
// Close any previous transformation groups
if (isTransformed()) {
footer += "</g>\n";
}
footer += "</svg>\n";
return footer;
}
@Override
public String toString() {
String doc = super.toString();
doc = doc.replaceAll("<g transform=\"[^\"]*\"></g>\n", "");
return doc;
}
}

View file

@ -0,0 +1,957 @@
/*
* VectorGraphics2D: Vector export for Java(R) Graphics2D
*
* (C) Copyright 2010 Erich Seifert <dev[at]erichseifert.de>
*
* This file is part of VectorGraphics2D.
*
* VectorGraphics2D 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 3 of the License, or
* (at your option) any later version.
*
* VectorGraphics2D 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 VectorGraphics2D. If not, see <http://www.gnu.org/licenses/>.
*/
package de.erichseifert.vectorgraphics2d;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
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.MultipleGradientPaint;
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.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.AffineTransformOp;
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.io.UnsupportedEncodingException;
import java.text.AttributedCharacterIterator;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* Base for classed that want to implement vector export.
* @author Erich Seifert
*/
public abstract class VectorGraphics2D extends Graphics2D {
/** Constants to define how fonts are rendered. */
public static enum FontRendering {
/** Constant indicating that fonts should be rendered as
text objects. */
TEXT,
/** Constant indicating that fonts should be converted to vectors. */
VECTORS
}
/** Maximal resolution for image rastering. */
private static final int DEFAULT_PAINT_IMAGE_SIZE_MAXIMUM = 128;
/** Document contents. */
private final StringBuffer document;
/** Rectangular bounds of the documents. */
private final Rectangle2D bounds;
/** Resolution in dots per inch that is used to raster paints. */
private double resolution;
/** Maximal size of images that are used to raster paints. */
private int rasteredImageSizeMaximum;
/** Font rendering mode. */
private FontRendering fontRendering;
/** Flag that stores whether affine transformations have been applied. */
private boolean transformed;
/** Rendering hints. */
private final RenderingHints hints;
/** Current background color. */
private Color background;
/** Current foreground color. */
private Color color;
/** Shape used for clipping paint operations. */
private Shape clip;
/** Method used for compositing. */
private Composite composite;
/** Device configuration settings. */
private final GraphicsConfiguration deviceConfig;
/** Current font. */
private Font font;
/** Context settings used to render fonts. */
private final FontRenderContext fontRenderContext;
/** Paint used to fill shapes. */
private Paint paint;
/** Stroke used for drawing shapes. */
private Stroke stroke;
/** Current transformation matrix. */
private final AffineTransform transform;
/** XOR mode used for rendering. */
private Color xorMode;
/**
* Constructor to initialize a new {@code VectorGraphics2D} document.
* The dimensions of the document must be passed.
* @param x Horizontal position of document origin.
* @param y Vertical position of document origin.
* @param width Width of document.
* @param height Height of document.
*/
public VectorGraphics2D(double x, double y, double width, double height) {
hints = new RenderingHints(new HashMap<RenderingHints.Key, Object>());
document = new StringBuffer();
bounds = new Rectangle2D.Double(x, y, width, height);
fontRendering = FontRendering.TEXT;
resolution = 72.0;
rasteredImageSizeMaximum = DEFAULT_PAINT_IMAGE_SIZE_MAXIMUM;
background = Color.WHITE;
color = Color.BLACK;
composite = AlphaComposite.getInstance(AlphaComposite.CLEAR);
deviceConfig = null;
font = Font.decode(null);
fontRenderContext = new FontRenderContext(null, false, true);
paint = color;
stroke = new BasicStroke(1f);
transform = new AffineTransform();
transformed = false;
xorMode = Color.BLACK;
}
@Override
public void addRenderingHints(Map<?,?> hints) {
this.hints.putAll(hints);
}
@Override
public void clip(Shape s) {
if ((getClip() != null) && (s != null)) {
Area clipAreaOld = new Area(getClip());
Area clipAreaNew = new Area(s);
clipAreaNew.intersect(clipAreaOld);
s = clipAreaNew;
}
setClip(s);
}
@Override
public void draw(Shape s) {
writeShape(s);
writeClosingDraw(s);
}
@Override
public void drawGlyphVector(GlyphVector g, float x, float y) {
draw(g.getOutline(x, y));
}
@Override
public boolean drawImage(Image img, AffineTransform xform,
ImageObserver obs) {
BufferedImage bimg = getTransformedImage(img, xform);
drawImage(bimg, null, bimg.getMinX(), bimg.getMinY());
return true;
}
@Override
public void drawImage(BufferedImage img, BufferedImageOp op,
int x, int y) {
if (op != null) {
img = op.filter(img, null);
}
drawImage(img, x, y, img.getWidth(), img.getHeight(), null);
}
@Override
public void drawRenderableImage(RenderableImage img,
AffineTransform xform) {
drawRenderedImage(img.createDefaultRendering(), xform);
}
@Override
public void drawRenderedImage(RenderedImage img,
AffineTransform xform) {
// TODO Implement
//throw new UnsupportedOperationException("Rendered images aren't supported.");
}
@Override
public void drawString(String str, int x, int y) {
drawString(str, (float) x, (float) y);
}
@Override
public void drawString(String str, float x, float y) {
if (str != null && str.trim().isEmpty()) {
return;
}
switch (getFontRendering()) {
case VECTORS:
TextLayout layout = new TextLayout(str, getFont(),
getFontRenderContext());
Shape s = layout.getOutline(
AffineTransform.getTranslateInstance(x, y));
fill(s);
break;
case TEXT:
writeString(str, x, y);
break;
default:
throw new IllegalStateException("Unknown font rendering mode.");
}
}
@Override
public void drawString(AttributedCharacterIterator iterator,
int x, int y) {
drawString(iterator, (float) x, (float) y);
}
@Override
public void drawString(AttributedCharacterIterator iterator,
float x, float y) {
// TODO Take text formatting into account
StringBuffer buf = new StringBuffer();
for (char c = iterator.first(); c != AttributedCharacterIterator.DONE;
c = iterator.next()) {
buf.append(c);
}
drawString(buf.toString(), x, y);
}
@Override
public void fill(Shape s) {
writeShape(s);
writeClosingFill(s);
}
@Override
public Color getBackground() {
return background;
}
@Override
public Composite getComposite() {
return composite;
}
@Override
public GraphicsConfiguration getDeviceConfiguration() {
return deviceConfig;
}
@Override
public FontRenderContext getFontRenderContext() {
return fontRenderContext;
}
@Override
public Paint getPaint() {
return paint;
}
@Override
public Object getRenderingHint(RenderingHints.Key hintKey) {
if (RenderingHints.KEY_ANTIALIASING.equals(hintKey)) {
return RenderingHints.VALUE_ANTIALIAS_OFF;
} else if (RenderingHints.KEY_TEXT_ANTIALIASING.equals(hintKey)) {
return RenderingHints.VALUE_TEXT_ANTIALIAS_OFF;
} else if (RenderingHints.KEY_FRACTIONALMETRICS.equals(hintKey)) {
return RenderingHints.VALUE_FRACTIONALMETRICS_ON;
}
return hints.get(hintKey);
}
@Override
public RenderingHints getRenderingHints() {
return hints;
}
@Override
public Stroke getStroke() {
return stroke;
}
@Override
public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
if (onStroke) {
Shape sStroke = getStroke().createStrokedShape(s);
return sStroke.intersects(rect);
} else {
return s.intersects(rect);
}
}
@Override
public void setBackground(Color color) {
background = color;
}
@Override
public void setComposite(Composite comp) {
composite = comp;
}
@Override
public void setPaint(Paint paint) {
if (paint != null) {
this.paint = paint;
if (paint instanceof Color) {
setColor((Color) paint);
} else if (paint instanceof MultipleGradientPaint) {
// Set brightest or least opaque color for gradients
Color[] colors = ((MultipleGradientPaint) paint).getColors();
if (colors.length == 1) {
setColor(colors[0]);
} else if (colors.length > 1) {
Color colLight = colors[0];
float brightness = getBrightness(colLight);
int alpha = colLight.getAlpha();
for (int i = 1; i < colors.length; i++) {
Color c = colors[i];
float b = getBrightness(c);
int a = c.getAlpha();
if (b < brightness || a < alpha) {
colLight = c;
brightness = b;
}
}
setColor(colLight);
}
}
}
}
/**
* Utility method to get the brightness of a specified color.
* @param c Color.
* @return Brightness value between 0f (black) and 1f (white).
*/
private static float getBrightness(Color c) {
return Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null)[2];
}
@Override
public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue) {
hints.put(hintKey, hintValue);
}
@Override
public void setRenderingHints(Map<?, ?> hints) {
this.hints.putAll(hints);
}
@Override
public void setStroke(Stroke s) {
stroke = s;
}
@Override
public AffineTransform getTransform() {
return new AffineTransform(transform);
}
@Override
public void setTransform(AffineTransform tx) {
setAffineTransform(tx);
}
/**
* Sets the current transformation.
* @param tx Current transformation
*/
protected void setAffineTransform(AffineTransform tx) {
if (!transform.equals(tx)) {
transform.setTransform(tx);
transformed = true;
}
}
@Override
public void shear(double shx, double shy) {
AffineTransform transform = getTransform();
transform.shear(shx, shy);
setAffineTransform(transform);
}
@Override
public void transform(AffineTransform tx) {
AffineTransform transform = getTransform();
transform.concatenate(tx);
setAffineTransform(transform);
}
@Override
public void translate(int x, int y) {
translate((double) x, (double) y);
}
@Override
public void translate(double tx, double ty) {
AffineTransform transform = getTransform();
transform.translate(tx, ty);
setAffineTransform(transform);
}
@Override
public void rotate(double theta) {
AffineTransform transform = getTransform();
transform.rotate(theta);
setAffineTransform(transform);
}
@Override
public void rotate(double theta, double x, double y) {
AffineTransform transform = getTransform();
transform.rotate(theta, x, y);
setAffineTransform(transform);
}
@Override
public void scale(double sx, double sy) {
AffineTransform transform = getTransform();
transform.scale(sx, sy);
setAffineTransform(transform);
}
@Override
public void clearRect(int x, int y, int width, int height) {
// TODO Implement
//throw new UnsupportedOperationException("clearRect() isn't supported by VectorGraphics2D.");
}
@Override
public void clipRect(int x, int y, int width, int height) {
clip(new Rectangle(x, y, width, height));
}
@Override
public void copyArea(int x, int y, int width, int height, int dx, int dy) {
// TODO Implement
//throw new UnsupportedOperationException("copyArea() isn't supported by VectorGraphics2D.");
}
@Override
public Graphics create() {
// TODO Implement
return this;
}
@Override
public void dispose() {
// TODO Implement
}
@Override
public void drawArc(int x, int y, int width, int height,
int startAngle, int arcAngle) {
draw(new Arc2D.Double(x, y, width, height,
startAngle, arcAngle, Arc2D.OPEN));
}
@Override
public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
return drawImage(img, x, y,
img.getWidth(observer), img.getHeight(observer), observer);
}
@Override
public boolean drawImage(Image img, int x, int y, Color bgcolor,
ImageObserver observer) {
return drawImage(img, x, y,
img.getWidth(observer), img.getHeight(observer), observer);
}
@Override
public boolean drawImage(Image img, int x, int y, int width, int height,
ImageObserver observer) {
int imgWidth = img.getWidth(observer);
int imgHeight = img.getHeight(observer);
writeImage(img, imgWidth, imgHeight, x, y, width, height);
return true; // TODO Return only true if image data was complete
}
@Override
public boolean drawImage(Image img, int x, int y, int width, int height,
Color bgcolor, ImageObserver observer) {
return drawImage(img, x, y, width, height, observer);
}
@Override
public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
int sx1, int sy1, int sx2, int sy2, ImageObserver observer) {
if (img == null) {
return true;
}
int sx = Math.min(sx1, sx2);
int sy = Math.min(sy1, sy2);
int sw = Math.abs(sx2 - sx1);
int sh = Math.abs(sy2 - sy1);
int dx = Math.min(dx1, dx2);
int dy = Math.min(dy1, dy2);
int dw = Math.abs(dx2 - dx1);
int dh = Math.abs(dy2 - dy1);
// Draw image
BufferedImage bufferedImg = GraphicsUtils.toBufferedImage(img);
Image cropped = bufferedImg.getSubimage(sx, sy, sw, sh);
return drawImage(cropped, dx, dy, dw, dh, observer);
}
@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) {
if (img == null) {
return true;
}
int sx = Math.min(sx1, sx2);
int sy = Math.min(sy1, sy2);
int sw = Math.abs(sx2 - sx1);
int sh = Math.abs(sy2 - sy1);
int dx = Math.min(dx1, dx2);
int dy = Math.min(dy1, dy2);
int dw = Math.abs(dx2 - dx1);
int dh = Math.abs(dy2 - dy1);
// Fill Rectangle with bgcolor
Color bgcolorOld = getColor();
setColor(bgcolor);
fill(new Rectangle(dx, dy, dw, dh));
setColor(bgcolorOld);
// Draw image on rectangle
BufferedImage bufferedImg = GraphicsUtils.toBufferedImage(img);
Image cropped = bufferedImg.getSubimage(sx, sy, sw, sh);
return drawImage(cropped, dx, dy, dw, dh, observer);
}
@Override
public void drawLine(int x1, int y1, int x2, int y2) {
draw(new Line2D.Double(x1, y1, x2, y2));
}
@Override
public void drawOval(int x, int y, int width, int height) {
draw(new Ellipse2D.Double(x, y, width, height));
}
@Override
public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) {
Path2D p = new Path2D.Float();
for (int i = 0; i < nPoints; i++) {
if (i > 0) {
p.lineTo(xPoints[i], yPoints[i]);
} else {
p.moveTo(xPoints[i], yPoints[i]);
}
}
p.closePath();
draw(p);
}
@Override
public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) {
Path2D p = new Path2D.Float();
for (int i = 0; i < nPoints; i++) {
if (i > 0) {
p.lineTo(xPoints[i], yPoints[i]);
} else {
p.moveTo(xPoints[i], yPoints[i]);
}
}
draw(p);
}
@Override
public void drawRect(int x, int y, int width, int height) {
draw(new Rectangle2D.Double(x, y, width, height));
}
@Override
public void drawRoundRect(int x, int y, int width, int height,
int arcWidth, int arcHeight) {
draw(new RoundRectangle2D.Double(x, y, width, height,
arcWidth, arcHeight));
}
@Override
public void fillArc(int x, int y, int width, int height,
int startAngle, int arcAngle) {
fill(new Arc2D.Double(x, y, width, height,
startAngle, arcAngle, Arc2D.PIE));
}
@Override
public void fillOval(int x, int y, int width, int height) {
fill(new Ellipse2D.Double(x, y, width, height));
}
@Override
public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) {
Path2D p = new Path2D.Float();
for (int i = 0; i < nPoints; i++) {
if (i > 0) {
p.lineTo(xPoints[i], yPoints[i]);
} else {
p.moveTo(xPoints[i], yPoints[i]);
}
}
p.closePath();
fill(p);
}
@Override
public void fillRect(int x, int y, int width, int height) {
fill(new Rectangle2D.Double(x, y, width, height));
}
@Override
public void fillRoundRect(int x, int y, int width, int height,
int arcWidth, int arcHeight) {
fill(new RoundRectangle2D.Double(x, y, width, height,
arcWidth, arcHeight));
}
@Override
public Shape getClip() {
return clip;
}
@Override
public Rectangle getClipBounds() {
if (clip == null) {
return null;
}
return clip.getBounds();
}
@Override
public Color getColor() {
return color;
}
@Override
public Font getFont() {
return font;
}
@Override
public FontMetrics getFontMetrics(Font f) {
// TODO Find a better way for creating a new FontMetrics instance
BufferedImage bi =
new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE);
Graphics g = bi.getGraphics();
FontMetrics fontMetrics = g.getFontMetrics(font);
g.dispose();
bi = null;
return fontMetrics;
}
@Override
public void setClip(Shape clip) {
this.clip = clip;
}
@Override
public void setClip(int x, int y, int width, int height) {
setClip(new Rectangle(x, y, width, height));
}
@Override
public void setColor(Color c) {
color = c;
}
@Override
public void setFont(Font font) {
if (!this.font.equals(font)) {
this.font = font;
}
}
@Override
public void setPaintMode() {
// TODO Implement
//throw new UnsupportedOperationException("setPaintMode() isn't supported.");
}
@Override
public void setXORMode(Color c1) {
xorMode = c1;
}
/**
* Utility method for writing multiple objects to the SVG document.
* @param strs Objects to be written
*/
protected void write(Object... strs) {
for (Object o : strs) {
String str = o.toString();
if ((o instanceof Double) || (o instanceof Float)) {
str = String.format(Locale.ENGLISH, "%.7f", o)
.replaceAll("\\.?0+$", "");
}
document.append(str);
}
}
/**
* Utility method for writing a line of multiple objects to the
* SVG document.
* @param strs Objects to be written
*/
protected void writeln(Object... strs) {
write(strs);
write("\n");
}
/**
* Write the specified shape to the document. This does not necessarily
* contain the actual command to paint the shape.
* @param s Shape to be written.
*/
protected abstract void writeShape(Shape s);
/**
* Write the specified image to the document. A number of dimensions will
* specify how the image will be placed in the document.
* @param img Image to be rendered.
* @param imgWidth Number of pixels in horizontal direction.
* @param imgHeight Number of pixels in vertical direction
* @param x Horizontal position in document units where the
* upper left corner of the image should be placed.
* @param y Vertical position in document units where the
* upper left corner of the image should be placed.
* @param width Width of the image in document units.
* @param height Height of the image in document units.
*/
protected abstract void writeImage(Image img, int imgWidth, int imgHeight,
double x, double y, double width, double height);
/**
* Write a text string to the document at a specified position.
* @param str Text to be rendered.
* @param x Horizontal position in document units.
* @param y Vertical position in document units.
*/
protected abstract void writeString(String str, double x, double y);
/**
* Write a command to draw the outline of a previously inserted shape.
* @param s Shape that should be drawn.
*/
protected abstract void writeClosingDraw(Shape s);
/**
* Write a command to fill the outline of a previously inserted shape.
* @param s Shape that should be filled.
*/
protected void writeClosingFill(Shape s) {
Rectangle2D shapeBounds = s.getBounds2D();
// Calculate dimensions of shape with current transformations
int wImage = (int) Math.ceil(shapeBounds.getWidth()*getResolution());
int hImage = (int) Math.ceil(shapeBounds.getHeight()*getResolution());
// Limit the size of images
wImage = Math.min(wImage, rasteredImageSizeMaximum);
hImage = Math.min(hImage, rasteredImageSizeMaximum);
// Create image to paint draw gradient with current transformations
BufferedImage paintImage = new BufferedImage(
wImage, hImage, BufferedImage.TYPE_INT_ARGB);
// Paint shape
Graphics2D g = (Graphics2D) paintImage.getGraphics();
g.scale(wImage/shapeBounds.getWidth(), hImage/shapeBounds.getHeight());
g.translate(-shapeBounds.getX(), -shapeBounds.getY());
g.setPaint(getPaint());
g.fill(s);
// Free resources
g.dispose();
// Output image of gradient
writeImage(paintImage, wImage, hImage,
shapeBounds.getX(), shapeBounds.getY(),
shapeBounds.getWidth(), shapeBounds.getHeight());
}
/**
* Write the header to start a new document.
*/
protected abstract void writeHeader();
/**
* Returns a string of the footer to end a document.
*/
protected abstract String getFooter();
/**
* Returns a transformed version of an image.
* @param image Image to be transformed
* @param xform Affine transform to be applied
* @return Image with transformed content
*/
private BufferedImage getTransformedImage(Image image,
AffineTransform xform) {
Integer interpolationType =
(Integer) hints.get(RenderingHints.KEY_INTERPOLATION);
if (RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR
.equals(interpolationType)) {
interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
} else if (RenderingHints.VALUE_INTERPOLATION_BILINEAR
.equals(interpolationType)) {
interpolationType = AffineTransformOp.TYPE_BILINEAR;
} else {
interpolationType = AffineTransformOp.TYPE_BICUBIC;
}
AffineTransformOp op = new AffineTransformOp(xform, interpolationType);
BufferedImage bufferedImage = GraphicsUtils.toBufferedImage(image);
return op.filter(bufferedImage, null);
}
/**
* Returns whether a distorting transformation has been applied to the
* document.
* @return <code>true</code> if the document is distorted,
* otherwise <code>false</code>.
*/
protected boolean isDistorted() {
if (!isTransformed()) {
return false;
}
int type = transform.getType();
int otherButTranslatedOrScaled = ~(AffineTransform.TYPE_TRANSLATION
| AffineTransform.TYPE_MASK_SCALE);
return (type & otherButTranslatedOrScaled) != 0;
}
@Override
public String toString() {
return document.toString() + getFooter();
}
/**
* Encodes the painted data into a sequence of bytes.
* @return A byte array containing the data in the current file format.
*/
public byte[] getBytes() {
try {
return toString().getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
return toString().getBytes();
}
}
/**
* Returns the dimensions of the document.
* @return dimensions of the document.
*/
public Rectangle2D getBounds() {
Rectangle2D b = new Rectangle2D.Double();
b.setFrame(bounds);
return b;
}
/**
* Returns the number of bytes of the document.
* @return size of the document in bytes.
*/
protected int size() {
return document.length();
}
/**
* Returns how fonts should be rendered.
* @return Font rendering mode.
*/
public FontRendering getFontRendering() {
return fontRendering;
}
/**
* Sets how fonts should be rendered. For example, they can be converted
* to vector shapes.
* @param mode New font rendering mode.
*/
public void setFontRendering(FontRendering mode) {
fontRendering = mode;
}
/**
* Returns whether an affine transformation like translation, scaling,
* rotation or shearing has been applied to this graphics instance.
* @return <code>true</code> if the instance has been transformed,
* <code>false</code> otherwise
*/
protected boolean isTransformed() {
return transformed;
}
/**
* Returns the resolution in pixels per inch.
* @return Resolution in pixels per inch.
*/
public double getResolution() {
return resolution;
}
/**
* Sets the resolution in pixels per inch.
* @param resolution New resolution in pixels per inch.
*/
public void setResolution(double resolution) {
if (resolution <= 0.0) {
throw new IllegalArgumentException(
"Only positive non-zero values allowed");
}
this.resolution = resolution;
}
/**
* Returns the maximal size of images which are used to raster paints
* like e.g. gradients, or patterns. The default value is 128.
* @return Current maximal image size in pixels.
*/
public int getRasteredImageSizeMaximum() {
return rasteredImageSizeMaximum;
}
/**
* Sets the maximal size of images which are used to raster paints
* like e.g. gradients, or patterns.
* @param paintImageSizeMaximum New maximal image size in pixels.
*/
public void setRasteredImageSizeMaximum(int paintImageSizeMaximum) {
this.rasteredImageSizeMaximum = paintImageSizeMaximum;
}
}

View file

@ -0,0 +1,25 @@
/*
* VectorGraphics2D: Vector export for Java(R) Graphics2D
*
* (C) Copyright 2010 Erich Seifert <dev[at]erichseifert.de>
*
* This file is part of VectorGraphics2D.
*
* VectorGraphics2D 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 3 of the License, or
* (at your option) any later version.
*
* VectorGraphics2D 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 VectorGraphics2D. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Main classes.
*/
package de.erichseifert.vectorgraphics2d;