Make first real commit: copy of CaRMetal 4.2.8
This commit is contained in:
parent
002acfc88e
commit
c312811084
1120 changed files with 226843 additions and 1 deletions
103
de/erichseifert/vectorgraphics2d/DataUtils.java
Normal file
103
de/erichseifert/vectorgraphics2d/DataUtils.java
Normal 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();
|
||||
}
|
||||
}
|
459
de/erichseifert/vectorgraphics2d/EPSGraphics2D.java
Normal file
459
de/erichseifert/vectorgraphics2d/EPSGraphics2D.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
125
de/erichseifert/vectorgraphics2d/GraphicsUtils.java
Normal file
125
de/erichseifert/vectorgraphics2d/GraphicsUtils.java
Normal 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;
|
||||
}
|
||||
}
|
637
de/erichseifert/vectorgraphics2d/PDFGraphics2D.java
Normal file
637
de/erichseifert/vectorgraphics2d/PDFGraphics2D.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
371
de/erichseifert/vectorgraphics2d/SVGGraphics2D.java
Normal file
371
de/erichseifert/vectorgraphics2d/SVGGraphics2D.java
Normal 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("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
957
de/erichseifert/vectorgraphics2d/VectorGraphics2D.java
Normal file
957
de/erichseifert/vectorgraphics2d/VectorGraphics2D.java
Normal 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;
|
||||
}
|
||||
}
|
25
de/erichseifert/vectorgraphics2d/package-info.java
Normal file
25
de/erichseifert/vectorgraphics2d/package-info.java
Normal 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;
|
Loading…
Add table
Add a link
Reference in a new issue