add graphics2d-pdfbox subproject
|
@ -6,3 +6,5 @@ gradle.wrapper.version = 6.6.1
|
||||||
pdfbox.version = 2.0.19
|
pdfbox.version = 2.0.19
|
||||||
zxing.version = 3.3.1
|
zxing.version = 3.3.1
|
||||||
reflections.version = 0.9.11
|
reflections.version = 0.9.11
|
||||||
|
jfreechart.version = 1.5.1
|
||||||
|
batik.version = 1.13
|
5
graphics2d-pdfbox/build.gradle
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
dependencies {
|
||||||
|
implementation "org.apache.pdfbox:pdfbox:${project.property('pdfbox.version')}"
|
||||||
|
testImplementation "org.jfree:jfreechart:${project.property('jfreechart.version')}"
|
||||||
|
testImplementation "org.apache.xmlgraphics:batik-swing:${project.property('batik.version')}"
|
||||||
|
}
|
6
graphics2d-pdfbox/src/main/java/module-info.java
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
module org.xbib.graphics.graphics2d.pdfbox {
|
||||||
|
exports org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
requires transitive org.apache.pdfbox;
|
||||||
|
requires transitive java.desktop;
|
||||||
|
requires java.logging;
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This color class represents a CMYK Color. You can use this class if you want
|
||||||
|
* to paint with DeviceCMYK Colors
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class CMYKColor extends Color {
|
||||||
|
private final float c, m, y, k;
|
||||||
|
private final PDColorSpace colorSpace;
|
||||||
|
|
||||||
|
public CMYKColor(int c, int m, int y, int k, int alpha) {
|
||||||
|
this(c / 255f, m / 255f, y / 255f, k / 255f, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CMYKColor(int c, int m, int y, int k) {
|
||||||
|
this(c / 255f, m / 255f, y / 255f, k / 255f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CMYKColor(float c, float m, float y, float k) {
|
||||||
|
this(c, m, y, k, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int toRGBValue(float c, float m, float y, float k, int alpha, PDColorSpace colorSpace) {
|
||||||
|
float[] rgb;
|
||||||
|
try {
|
||||||
|
rgb = colorSpace.toRGB(new float[]{c, m, y, k});
|
||||||
|
int r = ((int) (rgb[0] * 0xFF)) & 0xFF;
|
||||||
|
int g = ((int) (rgb[1] * 0xFF)) & 0xFF;
|
||||||
|
int b = ((int) (rgb[2] * 0xFF)) & 0xFF;
|
||||||
|
return alpha << 24 | r << 16 | g << 8 | b;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CMYKColor(float c, float m, float y, float k, int alpha) {
|
||||||
|
this(c, m, y, k, alpha, PDDeviceCMYK.INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CMYKColor(float c, float m, float y, float k, int alpha, PDColorSpace colorSpace) {
|
||||||
|
super(toRGBValue(c, m, y, k, alpha, colorSpace), true);
|
||||||
|
this.c = c;
|
||||||
|
this.m = m;
|
||||||
|
this.y = y;
|
||||||
|
this.k = k;
|
||||||
|
this.colorSpace = colorSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getC() {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getM() {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getY() {
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getK() {
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the PDColor represented by this color object
|
||||||
|
*/
|
||||||
|
public PDColor toPDColor() {
|
||||||
|
return new PDColor(new float[]{getC(), getM(), getY(), getK()}, colorSpace);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map Color to PDColor
|
||||||
|
*/
|
||||||
|
public interface ColorMapper {
|
||||||
|
/**
|
||||||
|
* Map the given Color to a PDColor
|
||||||
|
*
|
||||||
|
* @param contentStream the content stream
|
||||||
|
* @param color the color to map
|
||||||
|
* @return the mapped color
|
||||||
|
*/
|
||||||
|
PDColor mapColor(PDPageContentStream contentStream, Color color);
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
|
||||||
|
public class DefaultColorMapper implements ColorMapper {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PDColor mapColor(PDPageContentStream contentStream, Color color) {
|
||||||
|
if (color == null) {
|
||||||
|
return new PDColor(new float[]{1f, 1f, 1f}, PDDeviceRGB.INSTANCE);
|
||||||
|
}
|
||||||
|
if (color.getClass().getSimpleName().equals("CMYKColor")) {
|
||||||
|
float c = DefaultPaintApplier.getPropertyValue(color, "getC");
|
||||||
|
float m = DefaultPaintApplier.getPropertyValue(color, "getM");
|
||||||
|
float y = DefaultPaintApplier.getPropertyValue(color, "getY");
|
||||||
|
float k = DefaultPaintApplier.getPropertyValue(color, "getK");
|
||||||
|
return new PDColor(new float[]{c, m, y, k}, PDDeviceCMYK.INSTANCE);
|
||||||
|
}
|
||||||
|
// Our own CMYK Color class
|
||||||
|
if (color instanceof CMYKColor) {
|
||||||
|
return ((CMYKColor) color).toPDColor();
|
||||||
|
}
|
||||||
|
float[] components = new float[]{color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f};
|
||||||
|
return new PDColor(components, PDDeviceRGB.INSTANCE);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import java.awt.Shape;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation which does nothing. You can derive from it to only
|
||||||
|
* override the needed methods
|
||||||
|
*/
|
||||||
|
public class DefaultDrawControl implements DrawControl {
|
||||||
|
|
||||||
|
public static final DefaultDrawControl INSTANCE = new DefaultDrawControl();
|
||||||
|
|
||||||
|
protected DefaultDrawControl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Shape transformShapeBeforeFill(Shape shape, IDrawControlEnv env) {
|
||||||
|
return shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Shape transformShapeBeforeDraw(Shape shape, IDrawControlEnv env) {
|
||||||
|
return shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterShapeFill(Shape shape, IDrawControlEnv env) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterShapeDraw(Shape shape, IDrawControlEnv env) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,601 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import org.apache.fontbox.ttf.TrueTypeCollection;
|
||||||
|
import org.apache.fontbox.ttf.TrueTypeFont;
|
||||||
|
import org.apache.pdfbox.io.IOUtils;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDType0Font;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||||
|
import org.apache.pdfbox.util.Matrix;
|
||||||
|
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.FontFormatException;
|
||||||
|
import java.awt.FontMetrics;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Paint;
|
||||||
|
import java.awt.font.FontRenderContext;
|
||||||
|
import java.awt.font.LineMetrics;
|
||||||
|
import java.awt.font.TextAttribute;
|
||||||
|
import java.awt.geom.Rectangle2D;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.text.AttributedCharacterIterator;
|
||||||
|
import java.text.CharacterIterator;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation to draw fonts. You can reuse instances of this class
|
||||||
|
* within a PDDocument for more then one {@link PdfBoxGraphics2D}.
|
||||||
|
* <p>
|
||||||
|
* Just ensure that you call close after you closed the PDDocument to free any
|
||||||
|
* temporary files.
|
||||||
|
*/
|
||||||
|
public class DefaultFontTextDrawer implements FontTextDrawer, Closeable {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(DefaultFontTextDrawer.class.getName());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
for (File tempFile : tempFiles) {
|
||||||
|
tempFile.delete();
|
||||||
|
}
|
||||||
|
tempFiles.clear();
|
||||||
|
fontFiles.clear();
|
||||||
|
fontMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FontEntry {
|
||||||
|
String overrideName;
|
||||||
|
File file;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<FontEntry> fontFiles = new ArrayList<FontEntry>();
|
||||||
|
private final List<File> tempFiles = new ArrayList<File>();
|
||||||
|
private final Map<String, PDFont> fontMap = new HashMap<String, PDFont>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a font. If possible, try to use a font file, i.e.
|
||||||
|
* {@link #registerFont(String, File)}. This method will lead to the creation of
|
||||||
|
* a temporary file which stores the font data.
|
||||||
|
*
|
||||||
|
* @param fontName the name of the font to use. If null, the name is taken from the
|
||||||
|
* font.
|
||||||
|
* @param fontStream the input stream of the font. This file must be a ttf/otf file!
|
||||||
|
* You have to close the stream outside, this method will not close
|
||||||
|
* the stream.
|
||||||
|
* @throws IOException when something goes wrong with reading the font or writing the
|
||||||
|
* font to the content stream of the PDF:
|
||||||
|
*/
|
||||||
|
public void registerFont(String fontName, InputStream fontStream) throws IOException {
|
||||||
|
File fontFile = File.createTempFile("pdfboxgfx2dfont", ".ttf");
|
||||||
|
try (FileOutputStream out = new FileOutputStream(fontFile)) {
|
||||||
|
IOUtils.copy(fontStream, out);
|
||||||
|
}
|
||||||
|
fontFile.deleteOnExit();
|
||||||
|
tempFiles.add(fontFile);
|
||||||
|
registerFont(fontName, fontFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a font.
|
||||||
|
*
|
||||||
|
* @param fontName the name of the font to use. If null, the name is taken from the
|
||||||
|
* font.
|
||||||
|
* @param fontFile the font file. This file must exist for the live time of this
|
||||||
|
* object, as the font data will be read lazy on demand
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public void registerFont(String fontName, File fontFile) {
|
||||||
|
if (!fontFile.exists())
|
||||||
|
throw new IllegalArgumentException("Font " + fontFile + " does not exist!");
|
||||||
|
FontEntry entry = new FontEntry();
|
||||||
|
entry.overrideName = fontName;
|
||||||
|
entry.file = fontFile;
|
||||||
|
fontFiles.add(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override for registerFont(null,fontFile)
|
||||||
|
*
|
||||||
|
* @param fontFile the font file
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public void registerFont(File fontFile) {
|
||||||
|
registerFont(null, fontFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override for registerFont(null,fontStream)
|
||||||
|
*
|
||||||
|
* @param fontStream the font file
|
||||||
|
* @throws IOException when something goes wrong with reading the font or writing the
|
||||||
|
* font to the content stream of the PDF:
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public void registerFont(InputStream fontStream) throws IOException {
|
||||||
|
registerFont(null, fontStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a font which is already associated with the PDDocument
|
||||||
|
*
|
||||||
|
* @param name the name of the font as returned by
|
||||||
|
* {@link Font#getFontName()}. This name is used for the
|
||||||
|
* mapping the java.awt.Font to this PDFont.
|
||||||
|
* @param font the PDFont to use. This font must be loaded in the current
|
||||||
|
* document.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public void registerFont(String name, PDFont font) {
|
||||||
|
fontMap.put(name, font);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if the font mapping is populated on demand. This is usually only
|
||||||
|
* the case if this class has been derived. The default implementation
|
||||||
|
* just checks for this.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
protected boolean hasDynamicFontMapping() {
|
||||||
|
return getClass() != DefaultFontTextDrawer.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canDrawText(AttributedCharacterIterator iterator, IFontTextDrawerEnv env)
|
||||||
|
throws IOException, FontFormatException {
|
||||||
|
/*
|
||||||
|
* When no font is registered we can not display the text using a font...
|
||||||
|
*/
|
||||||
|
if (fontMap.size() == 0 && fontFiles.size() == 0 && !hasDynamicFontMapping())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
boolean run = true;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
while (run) {
|
||||||
|
|
||||||
|
Font attributeFont = (Font) iterator.getAttribute(TextAttribute.FONT);
|
||||||
|
if (attributeFont == null)
|
||||||
|
attributeFont = env.getFont();
|
||||||
|
if (mapFont(attributeFont, env) == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We can not do a Background on the text currently.
|
||||||
|
*/
|
||||||
|
if (iterator.getAttribute(TextAttribute.BACKGROUND) != null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
boolean isStrikeThrough = TextAttribute.STRIKETHROUGH_ON
|
||||||
|
.equals(iterator.getAttribute(TextAttribute.STRIKETHROUGH));
|
||||||
|
boolean isUnderline = TextAttribute.UNDERLINE_ON
|
||||||
|
.equals(iterator.getAttribute(TextAttribute.UNDERLINE));
|
||||||
|
boolean isLigatures = TextAttribute.LIGATURES_ON
|
||||||
|
.equals(iterator.getAttribute(TextAttribute.LIGATURES));
|
||||||
|
if (isStrikeThrough || isUnderline || isLigatures)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
run = iterateRun(iterator, sb);
|
||||||
|
String s = sb.toString();
|
||||||
|
int l = s.length();
|
||||||
|
for (int i = 0; i < l; ) {
|
||||||
|
int codePoint = s.codePointAt(i);
|
||||||
|
switch (Character.getDirectionality(codePoint)) {
|
||||||
|
/*
|
||||||
|
* We can handle normal LTR.
|
||||||
|
*/
|
||||||
|
case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
|
||||||
|
case Character.DIRECTIONALITY_EUROPEAN_NUMBER:
|
||||||
|
case Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR:
|
||||||
|
case Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR:
|
||||||
|
case Character.DIRECTIONALITY_WHITESPACE:
|
||||||
|
case Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR:
|
||||||
|
case Character.DIRECTIONALITY_NONSPACING_MARK:
|
||||||
|
case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL:
|
||||||
|
case Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR:
|
||||||
|
case Character.DIRECTIONALITY_SEGMENT_SEPARATOR:
|
||||||
|
case Character.DIRECTIONALITY_OTHER_NEUTRALS:
|
||||||
|
case Character.DIRECTIONALITY_ARABIC_NUMBER:
|
||||||
|
break;
|
||||||
|
case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
|
||||||
|
case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
|
||||||
|
case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
|
||||||
|
case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
|
||||||
|
case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT:
|
||||||
|
/*
|
||||||
|
* We can not handle this
|
||||||
|
*/
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
/*
|
||||||
|
* Default: We can not handle this
|
||||||
|
*/
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!attributeFont.canDisplay(codePoint))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
i += Character.charCount(codePoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawText(AttributedCharacterIterator iterator, IFontTextDrawerEnv env)
|
||||||
|
throws IOException, FontFormatException {
|
||||||
|
PDPageContentStream contentStream = env.getContentStream();
|
||||||
|
|
||||||
|
contentStream.beginText();
|
||||||
|
|
||||||
|
Matrix textMatrix = new Matrix();
|
||||||
|
textMatrix.scale(1, -1);
|
||||||
|
contentStream.setTextMatrix(textMatrix);
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
boolean run = true;
|
||||||
|
while (run) {
|
||||||
|
|
||||||
|
Font attributeFont = (Font) iterator.getAttribute(TextAttribute.FONT);
|
||||||
|
if (attributeFont == null)
|
||||||
|
attributeFont = env.getFont();
|
||||||
|
|
||||||
|
Number fontSize = ((Number) iterator.getAttribute(TextAttribute.SIZE));
|
||||||
|
if (fontSize != null)
|
||||||
|
attributeFont = attributeFont.deriveFont(fontSize.floatValue());
|
||||||
|
PDFont font = applyFont(attributeFont, env);
|
||||||
|
|
||||||
|
Paint paint = (Paint) iterator.getAttribute(TextAttribute.FOREGROUND);
|
||||||
|
if (paint == null)
|
||||||
|
paint = env.getPaint();
|
||||||
|
|
||||||
|
boolean isStrikeThrough = TextAttribute.STRIKETHROUGH_ON
|
||||||
|
.equals(iterator.getAttribute(TextAttribute.STRIKETHROUGH));
|
||||||
|
boolean isUnderline = TextAttribute.UNDERLINE_ON
|
||||||
|
.equals(iterator.getAttribute(TextAttribute.UNDERLINE));
|
||||||
|
boolean isLigatures = TextAttribute.LIGATURES_ON
|
||||||
|
.equals(iterator.getAttribute(TextAttribute.LIGATURES));
|
||||||
|
|
||||||
|
run = iterateRun(iterator, sb);
|
||||||
|
String text = sb.toString();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Apply the paint
|
||||||
|
*/
|
||||||
|
env.applyPaint(paint, null);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we force the text write we may encounter situations where the font can not
|
||||||
|
* display the characters. PDFBox will throw an exception in this case. We will
|
||||||
|
* just silently ignore the text and not display it instead.
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
showTextOnStream(env, contentStream, attributeFont, font, isStrikeThrough,
|
||||||
|
isUnderline, isLigatures, text);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
if (font instanceof PDType1Font && !font.isEmbedded()) {
|
||||||
|
/*
|
||||||
|
* We tried to use a builtin default font, but it does not have the needed
|
||||||
|
* characters. So we use a embedded font as fallback.
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
if (fallbackFontUnknownEncodings == null)
|
||||||
|
fallbackFontUnknownEncodings = findFallbackFont(env);
|
||||||
|
if (fallbackFontUnknownEncodings != null) {
|
||||||
|
env.getContentStream().setFont(fallbackFontUnknownEncodings,
|
||||||
|
attributeFont.getSize2D());
|
||||||
|
showTextOnStream(env, contentStream, attributeFont,
|
||||||
|
fallbackFontUnknownEncodings, isStrikeThrough, isUnderline,
|
||||||
|
isLigatures, text);
|
||||||
|
e = null;
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e1) {
|
||||||
|
e = e1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e != null)
|
||||||
|
logger.log(Level.WARNING, "PDFBoxGraphics: Can not map text " + text + " with font "
|
||||||
|
+ attributeFont.getFontName() + ": " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contentStream.endText();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FontMetrics getFontMetrics(final Font f, IFontTextDrawerEnv env)
|
||||||
|
throws IOException, FontFormatException {
|
||||||
|
final FontMetrics defaultMetrics = env.getCalculationGraphics().getFontMetrics(f);
|
||||||
|
final PDFont pdFont = mapFont(f, env);
|
||||||
|
/*
|
||||||
|
* By default we delegate to the buffered image based calculation. This is wrong
|
||||||
|
* as soon as we use the native PDF Box font, as those have sometimes different widths.
|
||||||
|
*
|
||||||
|
* But it is correct and fine as long as we use vector shapes.
|
||||||
|
*/
|
||||||
|
if (pdFont == null)
|
||||||
|
return defaultMetrics;
|
||||||
|
return new FontMetrics(f) {
|
||||||
|
public int getDescent() {
|
||||||
|
return defaultMetrics.getDescent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return defaultMetrics.getHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxAscent() {
|
||||||
|
return defaultMetrics.getMaxAscent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxDescent() {
|
||||||
|
return defaultMetrics.getMaxDescent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasUniformLineMetrics() {
|
||||||
|
return defaultMetrics.hasUniformLineMetrics();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineMetrics getLineMetrics(String str, Graphics context) {
|
||||||
|
return defaultMetrics.getLineMetrics(str, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineMetrics getLineMetrics(String str, int beginIndex, int limit,
|
||||||
|
Graphics context) {
|
||||||
|
return defaultMetrics.getLineMetrics(str, beginIndex, limit, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineMetrics getLineMetrics(char[] chars, int beginIndex, int limit,
|
||||||
|
Graphics context) {
|
||||||
|
return defaultMetrics.getLineMetrics(chars, beginIndex, limit, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineMetrics getLineMetrics(CharacterIterator ci, int beginIndex, int limit,
|
||||||
|
Graphics context) {
|
||||||
|
return defaultMetrics.getLineMetrics(ci, beginIndex, limit, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rectangle2D getStringBounds(String str, Graphics context) {
|
||||||
|
return defaultMetrics.getStringBounds(str, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rectangle2D getStringBounds(String str, int beginIndex, int limit,
|
||||||
|
Graphics context) {
|
||||||
|
return defaultMetrics.getStringBounds(str, beginIndex, limit, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rectangle2D getStringBounds(char[] chars, int beginIndex, int limit,
|
||||||
|
Graphics context) {
|
||||||
|
return defaultMetrics.getStringBounds(chars, beginIndex, limit, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rectangle2D getStringBounds(CharacterIterator ci, int beginIndex, int limit,
|
||||||
|
Graphics context) {
|
||||||
|
return defaultMetrics.getStringBounds(ci, beginIndex, limit, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rectangle2D getMaxCharBounds(Graphics context) {
|
||||||
|
return defaultMetrics.getMaxCharBounds(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAscent() {
|
||||||
|
return defaultMetrics.getAscent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxAdvance() {
|
||||||
|
return defaultMetrics.getMaxAdvance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLeading() {
|
||||||
|
return defaultMetrics.getLeading();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FontRenderContext getFontRenderContext() {
|
||||||
|
return defaultMetrics.getFontRenderContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int charWidth(char ch) {
|
||||||
|
char[] chars = {ch};
|
||||||
|
return charsWidth(chars, 0, chars.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int charWidth(int codePoint) {
|
||||||
|
char[] data = Character.toChars(codePoint);
|
||||||
|
return charsWidth(data, 0, data.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int charsWidth(char[] data, int off, int len) {
|
||||||
|
return stringWidth(new String(data, off, len));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int stringWidth(String str) {
|
||||||
|
try {
|
||||||
|
return (int) (pdFont.getStringWidth(str) / 1000 * f.getSize());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
/*
|
||||||
|
* We let unknown chars be handled with
|
||||||
|
*/
|
||||||
|
return defaultMetrics.stringWidth(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] getWidths() {
|
||||||
|
try {
|
||||||
|
int[] first256Widths = new int[256];
|
||||||
|
for (int i = 0; i < first256Widths.length; i++)
|
||||||
|
first256Widths[i] = (int) (pdFont.getWidth(i) / 1000 * f.getSize());
|
||||||
|
return first256Widths;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private PDFont fallbackFontUnknownEncodings;
|
||||||
|
|
||||||
|
private PDFont findFallbackFont(IFontTextDrawerEnv env) throws IOException {
|
||||||
|
/*
|
||||||
|
* We search for the right font in the folders... We try to use
|
||||||
|
* LucidaSansRegular and if not found Arial, because this fonts often exists. We
|
||||||
|
* use the Java default font as fallback.
|
||||||
|
*
|
||||||
|
* Normally this method is only used and called if a default font misses some
|
||||||
|
* special characters, e.g. Hebrew or Arabic characters.
|
||||||
|
*/
|
||||||
|
String javaHome = System.getProperty("java.home", ".");
|
||||||
|
String javaFontDir = javaHome + "/lib/fonts";
|
||||||
|
String windir = System.getenv("WINDIR");
|
||||||
|
if (windir == null)
|
||||||
|
windir = javaFontDir;
|
||||||
|
File[] paths = new File[]{new File(new File(windir), "fonts"),
|
||||||
|
new File(System.getProperty("user.dir", ".")),
|
||||||
|
// Mac Fonts
|
||||||
|
new File("/Library/Fonts"), new File("/System/Library/Fonts/Supplemental/"),
|
||||||
|
// Unix Fonts
|
||||||
|
new File("/usr/share/fonts/truetype"), new File("/usr/share/fonts/truetype/dejavu"),
|
||||||
|
new File("/usr/share/fonts/truetype/liberation"),
|
||||||
|
new File("/usr/share/fonts/truetype/noto"), new File(javaFontDir)};
|
||||||
|
for (String fontFileName : new String[]{"LucidaSansRegular.ttf", "arial.ttf", "Arial.ttf",
|
||||||
|
"DejaVuSans.ttf", "LiberationMono-Regular.ttf", "NotoSerif-Regular.ttf",
|
||||||
|
"Arial Unicode.ttf", "Tahoma.ttf"}) {
|
||||||
|
for (File path : paths) {
|
||||||
|
File arialFile = new File(path, fontFileName);
|
||||||
|
if (arialFile.exists()) {
|
||||||
|
// We try to use the first font we can find and use.
|
||||||
|
PDType0Font pdType0Font = tryToLoadFont(env, arialFile);
|
||||||
|
if (pdType0Font != null)
|
||||||
|
return pdType0Font;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PDType0Font tryToLoadFont(IFontTextDrawerEnv env, File foundFontFile) throws IOException {
|
||||||
|
try {
|
||||||
|
return PDType0Font.load(env.getDocument(), foundFontFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// The font may be have a embed restriction.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showTextOnStream(IFontTextDrawerEnv env, PDPageContentStream contentStream,
|
||||||
|
Font attributeFont, PDFont font, boolean isStrikeThrough, boolean isUnderline,
|
||||||
|
boolean isLigatures, String text) throws IOException {
|
||||||
|
if (isStrikeThrough || isUnderline) {
|
||||||
|
// noinspection unused
|
||||||
|
float stringWidth = font.getStringWidth(text);
|
||||||
|
// noinspection unused
|
||||||
|
LineMetrics lineMetrics = attributeFont
|
||||||
|
.getLineMetrics(text, env.getFontRenderContext());
|
||||||
|
/*
|
||||||
|
* TODO: We can not draw that yet, we must do that later. While in textmode its
|
||||||
|
* not possible to draw lines...
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
// noinspection StatementWithEmptyBody
|
||||||
|
if (isLigatures) {
|
||||||
|
/*
|
||||||
|
* No idea how to map this ...
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
contentStream.showText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PDFont applyFont(Font font, IFontTextDrawerEnv env)
|
||||||
|
throws IOException, FontFormatException {
|
||||||
|
PDFont fontToUse = mapFont(font, env);
|
||||||
|
if (fontToUse == null) {
|
||||||
|
/*
|
||||||
|
* If we have no font but are forced to apply a font, we just use the default
|
||||||
|
* builtin PDF font...
|
||||||
|
*/
|
||||||
|
fontToUse = DefaultFontTextDrawerDefaultFonts.chooseMatchingHelvetica(font);
|
||||||
|
}
|
||||||
|
env.getContentStream().setFont(fontToUse, font.getSize2D());
|
||||||
|
return fontToUse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to map the java.awt.Font to a PDFont.
|
||||||
|
*
|
||||||
|
* @param font the java.awt.Font for which a mapping should be found
|
||||||
|
* @param env environment of the font mapper
|
||||||
|
* @return the PDFont or null if none can be found.
|
||||||
|
* @throws IOException when the font can not be loaded
|
||||||
|
* @throws FontFormatException when the font file can not be loaded
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
protected PDFont mapFont(final Font font, final IFontTextDrawerEnv env)
|
||||||
|
throws IOException, FontFormatException {
|
||||||
|
/*
|
||||||
|
* If we have any font registering's, we must perform them now
|
||||||
|
*/
|
||||||
|
for (final FontEntry fontEntry : fontFiles) {
|
||||||
|
if (fontEntry.overrideName == null) {
|
||||||
|
Font javaFont = Font.createFont(Font.TRUETYPE_FONT, fontEntry.file);
|
||||||
|
fontEntry.overrideName = javaFont.getFontName();
|
||||||
|
}
|
||||||
|
if (fontEntry.file.getName().toLowerCase(Locale.US).endsWith(".ttc")) {
|
||||||
|
TrueTypeCollection collection = new TrueTypeCollection(fontEntry.file);
|
||||||
|
collection.processAllFonts(new TrueTypeCollection.TrueTypeFontProcessor() {
|
||||||
|
@Override
|
||||||
|
public void process(TrueTypeFont ttf) throws IOException {
|
||||||
|
PDFont pdFont = PDType0Font.load(env.getDocument(), ttf, true);
|
||||||
|
fontMap.put(fontEntry.overrideName, pdFont);
|
||||||
|
fontMap.put(pdFont.getName(), pdFont);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* We load the font using the file.
|
||||||
|
*/
|
||||||
|
PDFont pdFont = PDType0Font.load(env.getDocument(), fontEntry.file);
|
||||||
|
fontMap.put(fontEntry.overrideName, pdFont);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fontFiles.clear();
|
||||||
|
|
||||||
|
return fontMap.get(font.getFontName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean iterateRun(AttributedCharacterIterator iterator, StringBuilder sb) {
|
||||||
|
sb.setLength(0);
|
||||||
|
int charCount = iterator.getRunLimit() - iterator.getRunStart();
|
||||||
|
while (charCount-- >= 0) {
|
||||||
|
char c = iterator.current();
|
||||||
|
iterator.next();
|
||||||
|
if (c == AttributedCharacterIterator.DONE) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
sb.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||||
|
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.FontFormatException;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link DefaultFontTextDrawer}, but tries to use default fonts
|
||||||
|
* whenever possible. Default fonts are not embedded. You can register
|
||||||
|
* additional font files. If no font mapping is found, Helvetica is used.
|
||||||
|
* This will fallback to vectorized text if any kind of RTL text is rendered
|
||||||
|
* and/or any other not supported feature is used.
|
||||||
|
*/
|
||||||
|
public class DefaultFontTextDrawerDefaultFonts extends DefaultFontTextDrawer {
|
||||||
|
@Override
|
||||||
|
protected PDFont mapFont(Font font, IFontTextDrawerEnv env) throws IOException, FontFormatException {
|
||||||
|
PDFont pdFont = mapDefaultFonts(font);
|
||||||
|
if (pdFont != null)
|
||||||
|
return pdFont;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Do we have a manual registered mapping with a font file?
|
||||||
|
*/
|
||||||
|
pdFont = super.mapFont(font, env);
|
||||||
|
if (pdFont != null)
|
||||||
|
return pdFont;
|
||||||
|
return chooseMatchingHelvetica(font);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a PDFont for the given font object, which does not need to be embedded.
|
||||||
|
*
|
||||||
|
* @param font font for which to find a suitable default font
|
||||||
|
* @return null if no default font is found or a default font which does not
|
||||||
|
* need to be embedded.
|
||||||
|
*/
|
||||||
|
public static PDFont mapDefaultFonts(Font font) {
|
||||||
|
/*
|
||||||
|
* Map default font names to the matching families.
|
||||||
|
*/
|
||||||
|
if (fontNameEqualsAnyOf(font, Font.SANS_SERIF, Font.DIALOG, Font.DIALOG_INPUT, "Arial", "Helvetica"))
|
||||||
|
return chooseMatchingHelvetica(font);
|
||||||
|
if (fontNameEqualsAnyOf(font, Font.MONOSPACED, "courier", "courier new"))
|
||||||
|
return chooseMatchingCourier(font);
|
||||||
|
if (fontNameEqualsAnyOf(font, Font.SERIF, "Times", "Times New Roman", "Times Roman"))
|
||||||
|
return chooseMatchingTimes(font);
|
||||||
|
if (fontNameEqualsAnyOf(font, "Symbol"))
|
||||||
|
return PDType1Font.SYMBOL;
|
||||||
|
if (fontNameEqualsAnyOf(font, "ZapfDingbats", "Dingbats"))
|
||||||
|
return PDType1Font.ZAPF_DINGBATS;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean fontNameEqualsAnyOf(Font font, String... names) {
|
||||||
|
String name = font.getName();
|
||||||
|
for (String fontName : names) {
|
||||||
|
if (fontName.equalsIgnoreCase(name))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a PDType1Font.TIMES-variant, which matches the given font
|
||||||
|
*
|
||||||
|
* @param font Font to get the styles from
|
||||||
|
* @return a PDFont Times variant which matches the style in the given Font
|
||||||
|
* object.
|
||||||
|
*/
|
||||||
|
public static PDFont chooseMatchingTimes(Font font) {
|
||||||
|
if ((font.getStyle() & (Font.ITALIC | Font.BOLD)) == (Font.ITALIC | Font.BOLD))
|
||||||
|
return PDType1Font.TIMES_BOLD_ITALIC;
|
||||||
|
if ((font.getStyle() & Font.ITALIC) == Font.ITALIC)
|
||||||
|
return PDType1Font.TIMES_ITALIC;
|
||||||
|
if ((font.getStyle() & Font.BOLD) == Font.BOLD)
|
||||||
|
return PDType1Font.TIMES_BOLD;
|
||||||
|
return PDType1Font.TIMES_ROMAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a PDType1Font.COURIER-variant, which matches the given font
|
||||||
|
*
|
||||||
|
* @param font Font to get the styles from
|
||||||
|
* @return a PDFont Courier variant which matches the style in the given Font
|
||||||
|
* object.
|
||||||
|
*/
|
||||||
|
public static PDFont chooseMatchingCourier(Font font) {
|
||||||
|
if ((font.getStyle() & (Font.ITALIC | Font.BOLD)) == (Font.ITALIC | Font.BOLD))
|
||||||
|
return PDType1Font.COURIER_BOLD_OBLIQUE;
|
||||||
|
if ((font.getStyle() & Font.ITALIC) == Font.ITALIC)
|
||||||
|
return PDType1Font.COURIER_OBLIQUE;
|
||||||
|
if ((font.getStyle() & Font.BOLD) == Font.BOLD)
|
||||||
|
return PDType1Font.COURIER_BOLD;
|
||||||
|
return PDType1Font.COURIER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a PDType1Font.HELVETICA-variant, which matches the given font
|
||||||
|
*
|
||||||
|
* @param font Font to get the styles from
|
||||||
|
* @return a PDFont Helvetica variant which matches the style in the given Font
|
||||||
|
* object.
|
||||||
|
*/
|
||||||
|
public static PDFont chooseMatchingHelvetica(Font font) {
|
||||||
|
if ((font.getStyle() & (Font.ITALIC | Font.BOLD)) == (Font.ITALIC | Font.BOLD))
|
||||||
|
return PDType1Font.HELVETICA_BOLD_OBLIQUE;
|
||||||
|
if ((font.getStyle() & Font.ITALIC) == Font.ITALIC)
|
||||||
|
return PDType1Font.HELVETICA_OBLIQUE;
|
||||||
|
if ((font.getStyle() & Font.BOLD) == Font.BOLD)
|
||||||
|
return PDType1Font.HELVETICA_BOLD;
|
||||||
|
return PDType1Font.HELVETICA;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import java.text.AttributedCharacterIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Always draw using text, even if we know that we can not map the text correct
|
||||||
|
*/
|
||||||
|
public class DefaultFontTextForcedDrawer extends DefaultFontTextDrawerDefaultFonts {
|
||||||
|
@Override
|
||||||
|
public boolean canDrawText(AttributedCharacterIterator iterator, IFontTextDrawerEnv env) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,864 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.cos.COSArray;
|
||||||
|
import org.apache.pdfbox.cos.COSBase;
|
||||||
|
import org.apache.pdfbox.cos.COSBoolean;
|
||||||
|
import org.apache.pdfbox.cos.COSDictionary;
|
||||||
|
import org.apache.pdfbox.cos.COSFloat;
|
||||||
|
import org.apache.pdfbox.cos.COSName;
|
||||||
|
import org.apache.pdfbox.cos.COSStream;
|
||||||
|
import org.apache.pdfbox.multipdf.PDFCloneUtility;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDResources;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.COSObjectable;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.function.PDFunctionType3;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.color.PDPattern;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.pattern.PDTilingPattern;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.shading.PDShading;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.shading.PDShadingType3;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.shading.ShadingPaint;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
|
||||||
|
import org.apache.pdfbox.util.Matrix;
|
||||||
|
|
||||||
|
import java.awt.AlphaComposite;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Composite;
|
||||||
|
import java.awt.GradientPaint;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.MultipleGradientPaint;
|
||||||
|
import java.awt.Paint;
|
||||||
|
import java.awt.TexturePaint;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.geom.Point2D;
|
||||||
|
import java.awt.geom.Rectangle2D;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default paint mapper.
|
||||||
|
*/
|
||||||
|
public class DefaultPaintApplier implements PaintApplier {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(DefaultPaintApplier.class.getName());
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
protected static class PaintApplierState {
|
||||||
|
protected PDDocument document;
|
||||||
|
protected PDPageContentStream contentStream;
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
protected ColorMapper colorMapper;
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
protected ImageEncoder imageEncoder;
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
protected PDResources resources;
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
protected PDExtendedGraphicsState pdExtendedGraphicsState;
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
protected Composite composite;
|
||||||
|
private COSDictionary dictExtendedState;
|
||||||
|
private IPaintEnv env;
|
||||||
|
public AffineTransform tf;
|
||||||
|
/**
|
||||||
|
* This transform is only set, when we apply a nested
|
||||||
|
* paint (e.g. a TilingPattern's paint)
|
||||||
|
*/
|
||||||
|
protected AffineTransform nestedTransform;
|
||||||
|
|
||||||
|
private void ensureExtendedState() {
|
||||||
|
if (pdExtendedGraphicsState == null) {
|
||||||
|
this.dictExtendedState = new COSDictionary();
|
||||||
|
this.dictExtendedState.setItem(COSName.TYPE, COSName.EXT_G_STATE);
|
||||||
|
pdExtendedGraphicsState = new PDExtendedGraphicsState(this.dictExtendedState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ExtGStateCache extGStateCache = new ExtGStateCache();
|
||||||
|
private final PDShadingCache shadingCache = new PDShadingCache();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PDShading applyPaint(Paint paint, PDPageContentStream contentStream, AffineTransform tf,
|
||||||
|
IPaintEnv env) throws IOException {
|
||||||
|
PaintApplierState state = new PaintApplierState();
|
||||||
|
state.document = env.getDocument();
|
||||||
|
state.resources = env.getResources();
|
||||||
|
state.contentStream = contentStream;
|
||||||
|
state.colorMapper = env.getColorMapper();
|
||||||
|
state.imageEncoder = env.getImageEncoder();
|
||||||
|
state.composite = env.getComposite();
|
||||||
|
state.pdExtendedGraphicsState = null;
|
||||||
|
state.env = env;
|
||||||
|
state.tf = tf;
|
||||||
|
state.nestedTransform = null;
|
||||||
|
PDShading shading = applyPaint(paint, state);
|
||||||
|
if (state.pdExtendedGraphicsState != null)
|
||||||
|
contentStream.setGraphicsStateParameters(
|
||||||
|
extGStateCache.makeUnqiue(state.pdExtendedGraphicsState));
|
||||||
|
return shading;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
protected void applyAsStrokingColor(Color color, PaintApplierState state) throws IOException {
|
||||||
|
PDPageContentStream contentStream = state.contentStream;
|
||||||
|
ColorMapper colorMapper = state.colorMapper;
|
||||||
|
contentStream.setStrokingColor(colorMapper.mapColor(contentStream, color));
|
||||||
|
contentStream.setNonStrokingColor(colorMapper.mapColor(contentStream, color));
|
||||||
|
|
||||||
|
int alpha = color.getAlpha();
|
||||||
|
if (alpha < 255) {
|
||||||
|
state.ensureExtendedState();
|
||||||
|
Float strokingAlphaConstant = state.pdExtendedGraphicsState.getStrokingAlphaConstant();
|
||||||
|
if (strokingAlphaConstant == null)
|
||||||
|
strokingAlphaConstant = 1f;
|
||||||
|
state.pdExtendedGraphicsState
|
||||||
|
.setStrokingAlphaConstant(strokingAlphaConstant * (alpha / 255f));
|
||||||
|
Float nonStrokingAlphaConstant = state.pdExtendedGraphicsState
|
||||||
|
.getNonStrokingAlphaConstant();
|
||||||
|
if (nonStrokingAlphaConstant == null)
|
||||||
|
nonStrokingAlphaConstant = 1f;
|
||||||
|
state.pdExtendedGraphicsState
|
||||||
|
.setNonStrokingAlphaConstant(nonStrokingAlphaConstant * (alpha / 255f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PDShading applyPaint(Paint paint, PaintApplierState state) throws IOException {
|
||||||
|
applyComposite(state);
|
||||||
|
if (paint == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
String simpleName = paint.getClass().getSimpleName();
|
||||||
|
if (paint instanceof Color) {
|
||||||
|
applyAsStrokingColor((Color) paint, state);
|
||||||
|
} else if (simpleName.equals("LinearGradientPaint")) {
|
||||||
|
return shadingCache.makeUnqiue(buildLinearGradientShading(paint, state));
|
||||||
|
} else if (simpleName.equals("RadialGradientPaint")) {
|
||||||
|
return shadingCache.makeUnqiue(buildRadialGradientShading(paint, state));
|
||||||
|
} else if (simpleName.equals("PatternPaint")) {
|
||||||
|
applyPatternPaint(paint, state);
|
||||||
|
} else if (simpleName.equals("TilingPaint")) {
|
||||||
|
//applyPdfBoxTilingPaint(paint, state);
|
||||||
|
} else if (paint instanceof GradientPaint) {
|
||||||
|
return shadingCache.makeUnqiue(buildGradientShading((GradientPaint) paint, state));
|
||||||
|
} else if (paint instanceof TexturePaint) {
|
||||||
|
applyTexturePaint((TexturePaint) paint, state);
|
||||||
|
} else if (paint instanceof ShadingPaint) {
|
||||||
|
// PDFBox paint, we can import the shading directly
|
||||||
|
return shadingCache
|
||||||
|
.makeUnqiue(importPDFBoxShadingPaint((ShadingPaint<?>) paint, state));
|
||||||
|
} else {
|
||||||
|
logger.log(Level.WARNING, "Don't know paint " + paint.getClass().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PDShading importPDFBoxShadingPaint(ShadingPaint<?> paint, PaintApplierState state)
|
||||||
|
throws IOException {
|
||||||
|
PDFCloneUtility pdfCloneUtility = new PDFCloneUtility(state.document);
|
||||||
|
|
||||||
|
Matrix matrix = paint.getMatrix();
|
||||||
|
PDShading shading = paint.getShading();
|
||||||
|
|
||||||
|
state.contentStream.transform(matrix);
|
||||||
|
return PDShading.create((COSDictionary) pdfCloneUtility
|
||||||
|
.cloneForNewDocument(shading.getCOSObject()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyPatternPaint(Paint paint, PaintApplierState state) throws IOException {
|
||||||
|
Rectangle2D anchorRect = getPropertyValue(paint, "getPatternRect");
|
||||||
|
AffineTransform paintPatternTransform = getPropertyValue(paint, "getPatternTransform");
|
||||||
|
PDTilingPattern pattern = new PDTilingPattern();
|
||||||
|
pattern.setPaintType(PDTilingPattern.PAINT_COLORED);
|
||||||
|
pattern.setTilingType(PDTilingPattern.TILING_CONSTANT_SPACING_FASTER_TILING);
|
||||||
|
|
||||||
|
pattern.setBBox(new PDRectangle((float) anchorRect.getX(), (float) anchorRect.getY(),
|
||||||
|
(float) anchorRect.getWidth(), (float) anchorRect.getHeight()));
|
||||||
|
pattern.setXStep((float) anchorRect.getWidth());
|
||||||
|
pattern.setYStep((float) anchorRect.getHeight());
|
||||||
|
|
||||||
|
AffineTransform patternTransform = new AffineTransform();
|
||||||
|
if (paintPatternTransform != null) {
|
||||||
|
paintPatternTransform = new AffineTransform(paintPatternTransform);
|
||||||
|
paintPatternTransform.preConcatenate(state.tf);
|
||||||
|
patternTransform.concatenate(paintPatternTransform);
|
||||||
|
} else
|
||||||
|
patternTransform.concatenate(state.tf);
|
||||||
|
patternTransform.scale(1f, -1f);
|
||||||
|
pattern.setMatrix(patternTransform);
|
||||||
|
|
||||||
|
PDAppearanceStream appearance = new PDAppearanceStream(state.document);
|
||||||
|
appearance.setResources(pattern.getResources());
|
||||||
|
appearance.setBBox(pattern.getBBox());
|
||||||
|
|
||||||
|
Object graphicsNode = getPropertyValue(paint, "getGraphicsNode");
|
||||||
|
PdfBoxGraphics2D pdfBoxGraphics2D = new PdfBoxGraphics2D(state.document, pattern.getBBox(),
|
||||||
|
state.env.getGraphics2D());
|
||||||
|
try {
|
||||||
|
Method paintMethod = graphicsNode.getClass().getMethod("paint", Graphics2D.class);
|
||||||
|
paintMethod.invoke(graphicsNode, pdfBoxGraphics2D);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.log(Level.WARNING, "PaintApplier error while drawing Batik PatternPaint " + e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pdfBoxGraphics2D.dispose();
|
||||||
|
PDFormXObject xFormObject = pdfBoxGraphics2D.getXFormObject();
|
||||||
|
|
||||||
|
PDPageContentStream imageContentStream = new PDPageContentStream(state.document, appearance,
|
||||||
|
((COSStream) pattern.getCOSObject()).createOutputStream());
|
||||||
|
imageContentStream.drawForm(xFormObject);
|
||||||
|
imageContentStream.close();
|
||||||
|
|
||||||
|
PDColorSpace patternCS1 = new PDPattern(null);
|
||||||
|
COSName tilingPatternName = state.resources.add(pattern);
|
||||||
|
PDColor patternColor = new PDColor(tilingPatternName, patternCS1);
|
||||||
|
|
||||||
|
state.contentStream.setNonStrokingColor(patternColor);
|
||||||
|
state.contentStream.setStrokingColor(patternColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*private void applyPdfBoxTilingPaint(Paint paint, PaintApplierState state) {
|
||||||
|
Paint tilingPaint = PrivateFieldAccessor.getPrivateField(paint, "paint");
|
||||||
|
Matrix patternMatrix = PrivateFieldAccessor.getPrivateField(paint, "patternMatrix");
|
||||||
|
state.nestedTransform = patternMatrix.createAffineTransform();
|
||||||
|
applyPaint(tilingPaint, state);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
private void applyComposite(PaintApplierState state) {
|
||||||
|
if (state.composite == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Possibly set the alpha constant
|
||||||
|
float alpha = 1;
|
||||||
|
COSName blendMode = COSName.COMPATIBLE;
|
||||||
|
int rule = AlphaComposite.SRC;
|
||||||
|
|
||||||
|
if (state.composite instanceof AlphaComposite) {
|
||||||
|
AlphaComposite composite = (AlphaComposite) state.composite;
|
||||||
|
alpha = composite.getAlpha();
|
||||||
|
rule = composite.getRule();
|
||||||
|
} else if (state.composite.getClass().getSimpleName().equals("SVGComposite")) {
|
||||||
|
alpha = getPropertyValue(state.composite, "alpha");
|
||||||
|
rule = getPropertyValue(state.composite, "rule");
|
||||||
|
} else {
|
||||||
|
logger.log(Level.WARNING, "Unknown composite " + state.composite.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
|
||||||
|
state.ensureExtendedState();
|
||||||
|
if (alpha < 1) {
|
||||||
|
assert state.pdExtendedGraphicsState != null;
|
||||||
|
state.pdExtendedGraphicsState.setStrokingAlphaConstant(alpha);
|
||||||
|
state.pdExtendedGraphicsState.setNonStrokingAlphaConstant(alpha);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Try to map the alpha rule into blend modes
|
||||||
|
*/
|
||||||
|
switch (rule) {
|
||||||
|
case AlphaComposite.CLEAR:
|
||||||
|
break;
|
||||||
|
case AlphaComposite.SRC:
|
||||||
|
blendMode = COSName.NORMAL;
|
||||||
|
break;
|
||||||
|
case AlphaComposite.SRC_OVER:
|
||||||
|
blendMode = COSName.COMPATIBLE;
|
||||||
|
break;
|
||||||
|
case AlphaComposite.XOR:
|
||||||
|
blendMode = COSName.EXCLUSION;
|
||||||
|
break;
|
||||||
|
case AlphaComposite.DST:
|
||||||
|
break;
|
||||||
|
case AlphaComposite.DST_ATOP:
|
||||||
|
break;
|
||||||
|
case AlphaComposite.SRC_ATOP:
|
||||||
|
blendMode = COSName.COMPATIBLE;
|
||||||
|
break;
|
||||||
|
case AlphaComposite.DST_IN:
|
||||||
|
break;
|
||||||
|
case AlphaComposite.DST_OUT:
|
||||||
|
break;
|
||||||
|
case AlphaComposite.SRC_IN:
|
||||||
|
break;
|
||||||
|
case AlphaComposite.SRC_OUT:
|
||||||
|
break;
|
||||||
|
case AlphaComposite.DST_OVER:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
state.dictExtendedState.setItem(COSName.BM, blendMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Point2D clonePoint(Point2D point2D) {
|
||||||
|
return new Point2D.Double(point2D.getX(), point2D.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Very small number, everything smaller than this is zero for us.
|
||||||
|
*/
|
||||||
|
private static final double EPSILON = 0.00001;
|
||||||
|
|
||||||
|
private PDShading buildLinearGradientShading(Paint paint, PaintApplierState state)
|
||||||
|
throws IOException {
|
||||||
|
/*
|
||||||
|
* Batik has a copy of RadialGradientPaint, but it has the same structure as the AWT RadialGradientPaint. So we use
|
||||||
|
* Reflection to access the fields of both these classes.
|
||||||
|
*/
|
||||||
|
boolean isBatikGradient = paint.getClass().getPackage().getName()
|
||||||
|
.equals("org.apache.batik.ext.awt");
|
||||||
|
boolean isObjectBoundingBox = false;
|
||||||
|
if (isBatikGradient) {
|
||||||
|
AffineTransform gradientTransform = getPropertyValue(paint, "getTransform");
|
||||||
|
if (!gradientTransform.isIdentity()) {
|
||||||
|
/*
|
||||||
|
* If the scale is not square, we need to use the object bounding box logic
|
||||||
|
*/
|
||||||
|
if (Math.abs(gradientTransform.getScaleX() - gradientTransform.getScaleY())
|
||||||
|
> EPSILON)
|
||||||
|
isObjectBoundingBox = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isObjectBoundingBox) {
|
||||||
|
return linearGradientObjectBoundingBoxShading(paint, state);
|
||||||
|
} else {
|
||||||
|
return linearGradientUserSpaceOnUseShading(paint, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PDShading linearGradientObjectBoundingBoxShading(Paint paint, PaintApplierState state)
|
||||||
|
throws IOException {
|
||||||
|
/*
|
||||||
|
* I found this Stack Overflow question to be useful: https://stackoverflow.com/questions/50617275/svg-linear-gradients-
|
||||||
|
* objectboundingbox-vs-userspaceonuse SVG has 2 different gradient display modes objectBoundingBox & userSpaceOnUse The
|
||||||
|
* default is objectBoundingBox. PDF Axial gradients seem to be capable of displaying in any manner, but the default is
|
||||||
|
* the normal rendered at a 90 degree angle from the gradient vector. This looks like an SVG in userSpaceOnUse mode. So
|
||||||
|
* the task becomes how can we map the default of one format to a non-default mode in another so that the PDF an axial
|
||||||
|
* gradient looks like an SVG with a linear gradient.
|
||||||
|
*
|
||||||
|
* The approach I've used is as follows: Draw the axial gradient on a 1x1 box. A perfect square is a special case where
|
||||||
|
* the PDF defaults display matches the SVG default display. Then, use the gradient transform attached to the paint to
|
||||||
|
* warp the space containing the box & distort it to a larger rectangle (which may, or may not, still be a square). This
|
||||||
|
* makes the gradient in the PDF look like the gradient in an SVG if the SVG is using the objectBoundingBox mode.
|
||||||
|
*
|
||||||
|
* Note: there is some trickery with shape inversion because SVGs lay out from the top down & PDFs lay out from the
|
||||||
|
* bottom up.
|
||||||
|
*/
|
||||||
|
PDShadingType3 shading = setupBasicLinearShading(paint, state);
|
||||||
|
|
||||||
|
Point2D startPoint = clonePoint(getPropertyValue(paint, "getStartPoint"));
|
||||||
|
Point2D endPoint = clonePoint(getPropertyValue(paint, "getEndPoint"));
|
||||||
|
AffineTransform gradientTransform = getPropertyValue(paint, "getTransform");
|
||||||
|
state.tf.concatenate(gradientTransform);
|
||||||
|
|
||||||
|
// noinspection unused
|
||||||
|
MultipleGradientPaint.CycleMethod cycleMethod = getCycleMethod(paint);
|
||||||
|
// noinspection unused
|
||||||
|
MultipleGradientPaint.ColorSpaceType colorSpaceType = getColorSpaceType(paint);
|
||||||
|
|
||||||
|
// Note: all of the start and end points I've seen for linear gradients
|
||||||
|
// that use the objectBoundingBox mode define a 1x1 box. I don't know if
|
||||||
|
// this can be guaranteed.
|
||||||
|
setupShadingCoords(shading, startPoint, endPoint);
|
||||||
|
|
||||||
|
// We need the rectangle here so that the call to clip(useEvenOdd)
|
||||||
|
// in PdfBoxGraphics2D.java clips to the right frame of reference
|
||||||
|
//
|
||||||
|
// Note: tricky stuff follows . . .
|
||||||
|
// We're deliberately creating a bounding box with a negative height.
|
||||||
|
// Why? Because that contentsStream.transform() is going to invert it
|
||||||
|
// so that it has a positive height. It will always invert because
|
||||||
|
// SVGs & PDFs have opposite layout directions.
|
||||||
|
// If we started with a positive height, then inverted to a negative height
|
||||||
|
// we end up with a negative height clipping box in the output PDF
|
||||||
|
// and some PDF viewers cannot handle that.
|
||||||
|
// e.g. Adobe acrobat will display the PDF one way & Mac Preview
|
||||||
|
// will display it another.
|
||||||
|
float calculatedX = (float) Math.min(startPoint.getX(), endPoint.getX());
|
||||||
|
float calculatedY = (float) Math.max(1.0f, Math.max(startPoint.getY(), endPoint.getY()));
|
||||||
|
float calculatedWidth = Math
|
||||||
|
.max(1.0f, Math.abs((float) (endPoint.getX() - startPoint.getX())));
|
||||||
|
float negativeHeight =
|
||||||
|
-1.0f * Math.max(1.0f, Math.abs((float) (endPoint.getY() - startPoint.getY())));
|
||||||
|
|
||||||
|
state.contentStream.addRect(calculatedX, calculatedY, calculatedWidth, negativeHeight);
|
||||||
|
|
||||||
|
state.env.getGraphics2D().markPathIsOnStream();
|
||||||
|
state.env.getGraphics2D().internalClip(false);
|
||||||
|
|
||||||
|
// Warp the 1x1 box containing the gradient to fill a larger rectangular space
|
||||||
|
state.contentStream.transform(new Matrix(state.tf));
|
||||||
|
|
||||||
|
return shading;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupShadingCoords(PDShadingType3 shading, Point2D startPoint, Point2D endPoint) {
|
||||||
|
COSArray coords = new COSArray();
|
||||||
|
coords.add(new COSFloat((float) startPoint.getX()));
|
||||||
|
coords.add(new COSFloat((float) startPoint.getY()));
|
||||||
|
coords.add(new COSFloat((float) endPoint.getX()));
|
||||||
|
coords.add(new COSFloat((float) endPoint.getY()));
|
||||||
|
shading.setCoords(coords);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the default gradient mode for both SVG and java.awt gradients.
|
||||||
|
*/
|
||||||
|
private PDShading linearGradientUserSpaceOnUseShading(Paint paint, PaintApplierState state)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
PDShadingType3 shading = setupBasicLinearShading(paint, state);
|
||||||
|
|
||||||
|
Point2D startPoint = clonePoint(getPropertyValue(paint, "getStartPoint"));
|
||||||
|
Point2D endPoint = clonePoint(getPropertyValue(paint, "getEndPoint"));
|
||||||
|
AffineTransform gradientTransform = getPropertyValue(paint, "getTransform");
|
||||||
|
state.tf.concatenate(gradientTransform);
|
||||||
|
|
||||||
|
// noinspection unused
|
||||||
|
MultipleGradientPaint.CycleMethod cycleMethod = getCycleMethod(paint);
|
||||||
|
// noinspection unused
|
||||||
|
MultipleGradientPaint.ColorSpaceType colorSpaceType = getColorSpaceType(paint);
|
||||||
|
|
||||||
|
state.tf.transform(startPoint, startPoint);
|
||||||
|
state.tf.transform(endPoint, endPoint);
|
||||||
|
|
||||||
|
setupShadingCoords(shading, startPoint, endPoint);
|
||||||
|
|
||||||
|
return shading;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PDShadingType3 setupBasicLinearShading(Paint paint, PaintApplierState state)
|
||||||
|
throws IOException {
|
||||||
|
PDShadingType3 shading = new PDShadingType3(new COSDictionary());
|
||||||
|
Color[] colors = getPropertyValue(paint, "getColors");
|
||||||
|
Color firstColor = colors[0];
|
||||||
|
PDColor firstColorMapped = state.colorMapper.mapColor(state.contentStream, firstColor);
|
||||||
|
applyAsStrokingColor(firstColor, state);
|
||||||
|
float[] fractions = getPropertyValue(paint, "getFractions");
|
||||||
|
PDFunctionType3 type3 = buildType3Function(colors, fractions, state);
|
||||||
|
shading.setAntiAlias(true);
|
||||||
|
shading.setShadingType(PDShading.SHADING_TYPE2);
|
||||||
|
shading.setColorSpace(firstColorMapped.getColorSpace());
|
||||||
|
shading.setFunction(type3);
|
||||||
|
shading.setExtend(setupExtends());
|
||||||
|
return shading;
|
||||||
|
}
|
||||||
|
|
||||||
|
private COSArray setupExtends() {
|
||||||
|
COSArray extend = new COSArray();
|
||||||
|
/*
|
||||||
|
* We need to always extend the gradient
|
||||||
|
*/
|
||||||
|
extend.add(COSBoolean.TRUE);
|
||||||
|
extend.add(COSBoolean.TRUE);
|
||||||
|
return extend;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map the cycleMethod of the GradientPaint to the java.awt.MultipleGradientPaint.CycleMethod enum.
|
||||||
|
*
|
||||||
|
* @param paint the paint to get the cycleMethod from (if not in any other way possible using reflection)
|
||||||
|
* @return the CycleMethod
|
||||||
|
*/
|
||||||
|
private MultipleGradientPaint.CycleMethod getCycleMethod(Paint paint) {
|
||||||
|
if (paint instanceof MultipleGradientPaint)
|
||||||
|
return ((MultipleGradientPaint) paint).getCycleMethod();
|
||||||
|
if (paint.getClass().getPackage().getName().equals("org.apache.batik.ext.awt")) {
|
||||||
|
setupBatikReflectionAccess(paint);
|
||||||
|
Object cycleMethod = getPropertyValue(paint, "getCycleMethod");
|
||||||
|
if (cycleMethod == BATIK_GRADIENT_NO_CYCLE)
|
||||||
|
return MultipleGradientPaint.CycleMethod.NO_CYCLE;
|
||||||
|
if (cycleMethod == BATIK_GRADIENT_REFLECT)
|
||||||
|
return MultipleGradientPaint.CycleMethod.REFLECT;
|
||||||
|
if (cycleMethod == BATIK_GRADIENT_REPEAT)
|
||||||
|
return MultipleGradientPaint.CycleMethod.REPEAT;
|
||||||
|
}
|
||||||
|
return MultipleGradientPaint.CycleMethod.NO_CYCLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MultipleGradientPaint.ColorSpaceType getColorSpaceType(Paint paint) {
|
||||||
|
if (paint instanceof MultipleGradientPaint)
|
||||||
|
return ((MultipleGradientPaint) paint).getColorSpace();
|
||||||
|
if (paint.getClass().getPackage().getName().equals("org.apache.batik.ext.awt")) {
|
||||||
|
setupBatikReflectionAccess(paint);
|
||||||
|
Object cycleMethod = getPropertyValue(paint, "getColorSpace");
|
||||||
|
if (cycleMethod == BATIK_COLORSPACE_SRGB)
|
||||||
|
return MultipleGradientPaint.ColorSpaceType.SRGB;
|
||||||
|
if (cycleMethod == BATIK_COLORSPACE_LINEAR_RGB)
|
||||||
|
return MultipleGradientPaint.ColorSpaceType.LINEAR_RGB;
|
||||||
|
}
|
||||||
|
return MultipleGradientPaint.ColorSpaceType.SRGB;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object BATIK_GRADIENT_NO_CYCLE;
|
||||||
|
private Object BATIK_GRADIENT_REFLECT;
|
||||||
|
private Object BATIK_GRADIENT_REPEAT;
|
||||||
|
private Object BATIK_COLORSPACE_SRGB;
|
||||||
|
private Object BATIK_COLORSPACE_LINEAR_RGB;
|
||||||
|
|
||||||
|
private void setupBatikReflectionAccess(Paint paint) {
|
||||||
|
/*
|
||||||
|
* As we don't have Batik on our class path we need to access it by reflection if the user application is using Batik
|
||||||
|
*/
|
||||||
|
if (BATIK_GRADIENT_NO_CYCLE != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class<?> cls = paint.getClass();
|
||||||
|
if (cls.getSimpleName().equals("MultipleGradientPaint")) {
|
||||||
|
BATIK_GRADIENT_NO_CYCLE = cls.getDeclaredField("NO_CYCLE");
|
||||||
|
BATIK_GRADIENT_REFLECT = cls.getDeclaredField("REFLECT");
|
||||||
|
BATIK_GRADIENT_REPEAT = cls.getDeclaredField("REPEAT");
|
||||||
|
BATIK_COLORSPACE_SRGB = cls.getDeclaredField("SRGB");
|
||||||
|
BATIK_COLORSPACE_LINEAR_RGB = cls.getDeclaredField("LINEAR_RGB");
|
||||||
|
}
|
||||||
|
} catch (NoSuchFieldException ignored) {
|
||||||
|
/*
|
||||||
|
* Can not detect Batik CycleMethods :(
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PDShading buildRadialGradientShading(Paint paint, PaintApplierState state)
|
||||||
|
throws IOException {
|
||||||
|
/*
|
||||||
|
* Batik has a copy of RadialGradientPaint, but it has the same structure as the AWT RadialGradientPaint. So we use
|
||||||
|
* Reflection to access the fields of both these classes.
|
||||||
|
*/
|
||||||
|
Color[] colors = getPropertyValue(paint, "getColors");
|
||||||
|
Color firstColor = colors[0];
|
||||||
|
PDColor firstColorMapped = state.colorMapper.mapColor(state.contentStream, firstColor);
|
||||||
|
applyAsStrokingColor(firstColor, state);
|
||||||
|
|
||||||
|
PDShadingType3 shading = new PDShadingType3(new COSDictionary());
|
||||||
|
shading.setAntiAlias(true);
|
||||||
|
shading.setShadingType(PDShading.SHADING_TYPE3);
|
||||||
|
shading.setColorSpace(firstColorMapped.getColorSpace());
|
||||||
|
float[] fractions = getPropertyValue(paint, "getFractions");
|
||||||
|
Point2D centerPoint = clonePoint(getPropertyValue(paint, "getCenterPoint"));
|
||||||
|
Point2D focusPoint = clonePoint(getPropertyValue(paint, "getFocusPoint"));
|
||||||
|
AffineTransform gradientTransform = getPropertyValue(paint, "getTransform");
|
||||||
|
state.tf.concatenate(gradientTransform);
|
||||||
|
state.tf.transform(centerPoint, centerPoint);
|
||||||
|
state.tf.transform(focusPoint, focusPoint);
|
||||||
|
|
||||||
|
float radius = getPropertyValue(paint, "getRadius");
|
||||||
|
radius = (float) Math.abs(radius * state.tf.getScaleX());
|
||||||
|
|
||||||
|
COSArray coords = new COSArray();
|
||||||
|
|
||||||
|
coords.add(new COSFloat((float) centerPoint.getX()));
|
||||||
|
coords.add(new COSFloat((float) centerPoint.getY()));
|
||||||
|
coords.add(new COSFloat(0));
|
||||||
|
coords.add(new COSFloat((float) focusPoint.getX()));
|
||||||
|
coords.add(new COSFloat((float) focusPoint.getY()));
|
||||||
|
coords.add(new COSFloat(radius));
|
||||||
|
shading.setCoords(coords);
|
||||||
|
|
||||||
|
PDFunctionType3 type3 = buildType3Function(colors, fractions, state);
|
||||||
|
|
||||||
|
shading.setFunction(type3);
|
||||||
|
shading.setExtend(setupExtends());
|
||||||
|
return shading;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PDShading buildGradientShading(GradientPaint gradientPaint, PaintApplierState state)
|
||||||
|
throws IOException {
|
||||||
|
Color[] colors = new Color[]{gradientPaint.getColor1(), gradientPaint.getColor2()};
|
||||||
|
Color firstColor = colors[0];
|
||||||
|
PDColor firstColorMapped = state.colorMapper.mapColor(state.contentStream, firstColor);
|
||||||
|
applyAsStrokingColor(firstColor, state);
|
||||||
|
|
||||||
|
PDShadingType3 shading = new PDShadingType3(new COSDictionary());
|
||||||
|
shading.setShadingType(PDShading.SHADING_TYPE2);
|
||||||
|
shading.setColorSpace(firstColorMapped.getColorSpace());
|
||||||
|
float[] fractions = new float[]{0, 1};
|
||||||
|
PDFunctionType3 type3 = buildType3Function(colors, fractions, state);
|
||||||
|
|
||||||
|
Point2D startPoint = gradientPaint.getPoint1();
|
||||||
|
Point2D endPoint = gradientPaint.getPoint2();
|
||||||
|
|
||||||
|
state.tf.transform(startPoint, startPoint);
|
||||||
|
state.tf.transform(endPoint, endPoint);
|
||||||
|
|
||||||
|
setupShadingCoords(shading, startPoint, endPoint);
|
||||||
|
|
||||||
|
shading.setFunction(type3);
|
||||||
|
shading.setExtend(setupExtends());
|
||||||
|
return shading;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyTexturePaint(TexturePaint texturePaint, PaintApplierState state)
|
||||||
|
throws IOException {
|
||||||
|
Rectangle2D anchorRect = texturePaint.getAnchorRect();
|
||||||
|
PDTilingPattern pattern = new PDTilingPattern();
|
||||||
|
pattern.setPaintType(PDTilingPattern.PAINT_COLORED);
|
||||||
|
pattern.setTilingType(PDTilingPattern.TILING_CONSTANT_SPACING_FASTER_TILING);
|
||||||
|
|
||||||
|
pattern.setBBox(new PDRectangle((float) anchorRect.getX(), (float) anchorRect.getY(),
|
||||||
|
(float) anchorRect.getWidth(), (float) anchorRect.getHeight()));
|
||||||
|
pattern.setXStep((float) anchorRect.getWidth());
|
||||||
|
pattern.setYStep((float) anchorRect.getHeight());
|
||||||
|
|
||||||
|
AffineTransform patternTransform = new AffineTransform();
|
||||||
|
patternTransform.translate(0, anchorRect.getHeight());
|
||||||
|
patternTransform.scale(1f, -1f);
|
||||||
|
pattern.setMatrix(patternTransform);
|
||||||
|
|
||||||
|
PDAppearanceStream appearance = new PDAppearanceStream(state.document);
|
||||||
|
appearance.setResources(pattern.getResources());
|
||||||
|
appearance.setBBox(pattern.getBBox());
|
||||||
|
|
||||||
|
PDPageContentStream imageContentStream = new PDPageContentStream(state.document, appearance,
|
||||||
|
((COSStream) pattern.getCOSObject()).createOutputStream());
|
||||||
|
BufferedImage texturePaintImage = texturePaint.getImage();
|
||||||
|
PDImageXObject imageXObject = state.imageEncoder
|
||||||
|
.encodeImage(state.document, imageContentStream, texturePaintImage);
|
||||||
|
|
||||||
|
float ratioW = (float) ((anchorRect.getWidth()) / texturePaintImage.getWidth());
|
||||||
|
float ratioH = (float) ((anchorRect.getHeight()) / texturePaintImage.getHeight());
|
||||||
|
float paintHeight = (texturePaintImage.getHeight()) * ratioH;
|
||||||
|
if (state.nestedTransform != null) {
|
||||||
|
imageContentStream.transform(new Matrix(state.nestedTransform));
|
||||||
|
}
|
||||||
|
imageContentStream.drawImage(imageXObject, (float) anchorRect.getX(),
|
||||||
|
(float) (paintHeight + anchorRect.getY()), texturePaintImage.getWidth() * ratioW,
|
||||||
|
-paintHeight);
|
||||||
|
imageContentStream.close();
|
||||||
|
|
||||||
|
PDColorSpace patternCS1 = new PDPattern(null, imageXObject.getColorSpace());
|
||||||
|
COSName tilingPatternName = state.resources.add(pattern);
|
||||||
|
PDColor patternColor = new PDColor(tilingPatternName, patternCS1);
|
||||||
|
|
||||||
|
state.contentStream.setNonStrokingColor(patternColor);
|
||||||
|
state.contentStream.setStrokingColor(patternColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a color gradient as a type3 function
|
||||||
|
*
|
||||||
|
* @param colors The colors to encode
|
||||||
|
* @param fractions the fractions for encoding
|
||||||
|
* @param state our state, this is needed for color mapping
|
||||||
|
* @return the type3 function
|
||||||
|
*/
|
||||||
|
private PDFunctionType3 buildType3Function(Color[] colors, float[] fractions,
|
||||||
|
PaintApplierState state) {
|
||||||
|
COSDictionary function = new COSDictionary();
|
||||||
|
function.setInt(COSName.FUNCTION_TYPE, 3);
|
||||||
|
|
||||||
|
COSArray domain = new COSArray();
|
||||||
|
domain.add(new COSFloat(0));
|
||||||
|
domain.add(new COSFloat(1));
|
||||||
|
|
||||||
|
COSArray encode = new COSArray();
|
||||||
|
|
||||||
|
COSArray range = new COSArray();
|
||||||
|
range.add(new COSFloat(0));
|
||||||
|
range.add(new COSFloat(1));
|
||||||
|
|
||||||
|
List<Color> colorList = new ArrayList<Color>(Arrays.asList(colors));
|
||||||
|
COSArray bounds = new COSArray();
|
||||||
|
if (Math.abs(fractions[0]) > EPSILON) {
|
||||||
|
/*
|
||||||
|
* We need to insert a "keyframe" for fraction 0. See also java.awt.LinearGradientPaint for future information
|
||||||
|
*/
|
||||||
|
colorList.add(0, colors[0]);
|
||||||
|
bounds.add(new COSFloat(fractions[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We always add the inner fractions
|
||||||
|
*/
|
||||||
|
for (int i = 1; i < fractions.length - 1; i++) {
|
||||||
|
float fraction = fractions[i];
|
||||||
|
bounds.add(new COSFloat(fraction));
|
||||||
|
}
|
||||||
|
if (Math.abs(fractions[fractions.length - 1] - 1f) > EPSILON) {
|
||||||
|
/*
|
||||||
|
* We also need to insert a "keyframe" at the end for fraction 1
|
||||||
|
*/
|
||||||
|
colorList.add(colors[colors.length - 1]);
|
||||||
|
bounds.add(new COSFloat(fractions[fractions.length - 1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
COSArray type2Functions = buildType2Functions(colorList, domain, encode, state);
|
||||||
|
|
||||||
|
function.setItem(COSName.FUNCTIONS, type2Functions);
|
||||||
|
function.setItem(COSName.BOUNDS, bounds);
|
||||||
|
function.setItem(COSName.ENCODE, encode);
|
||||||
|
|
||||||
|
PDFunctionType3 type3 = new PDFunctionType3(function);
|
||||||
|
type3.setDomainValues(domain);
|
||||||
|
return type3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a type2 function to interpolate between the given colors.
|
||||||
|
*
|
||||||
|
* @param colors the color to encode
|
||||||
|
* @param domain the domain which should already been setuped. It will be used for the Type2 function
|
||||||
|
* @param encode will get the domain information per color channel, i.e. colors.length x [0, 1]
|
||||||
|
* @param state our internal state, this is needed for color mapping
|
||||||
|
* @return the Type2 function COSArray
|
||||||
|
*/
|
||||||
|
private COSArray buildType2Functions(List<Color> colors, COSArray domain, COSArray encode,
|
||||||
|
PaintApplierState state) {
|
||||||
|
Color prevColor = colors.get(0);
|
||||||
|
|
||||||
|
COSArray functions = new COSArray();
|
||||||
|
for (int i = 1; i < colors.size(); i++) {
|
||||||
|
Color color = colors.get(i);
|
||||||
|
PDColor prevPdColor = state.colorMapper.mapColor(state.contentStream, prevColor);
|
||||||
|
PDColor pdColor = state.colorMapper.mapColor(state.contentStream, color);
|
||||||
|
COSArray c0 = new COSArray();
|
||||||
|
COSArray c1 = new COSArray();
|
||||||
|
for (float component : prevPdColor.getComponents())
|
||||||
|
c0.add(new COSFloat(component));
|
||||||
|
for (float component : pdColor.getComponents())
|
||||||
|
c1.add(new COSFloat(component));
|
||||||
|
|
||||||
|
COSDictionary type2Function = new COSDictionary();
|
||||||
|
type2Function.setInt(COSName.FUNCTION_TYPE, 2);
|
||||||
|
type2Function.setItem(COSName.C0, c0);
|
||||||
|
type2Function.setItem(COSName.C1, c1);
|
||||||
|
type2Function.setInt(COSName.N, 1);
|
||||||
|
type2Function.setItem(COSName.DOMAIN, domain);
|
||||||
|
functions.add(type2Function);
|
||||||
|
|
||||||
|
encode.add(new COSFloat(0));
|
||||||
|
encode.add(new COSFloat(1));
|
||||||
|
prevColor = color;
|
||||||
|
}
|
||||||
|
return functions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a property value from an object using reflection
|
||||||
|
*
|
||||||
|
* @param obj The object to get a property from.
|
||||||
|
* @param propertyGetter method name of the getter, i.e. "getXY".
|
||||||
|
* @param <T> the type of the property you want to get.
|
||||||
|
* @return the value read from the object
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected static <T> T getPropertyValue(Object obj, String propertyGetter) {
|
||||||
|
if (obj == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Class<?> c = obj.getClass();
|
||||||
|
while (c != null) {
|
||||||
|
try {
|
||||||
|
Method m = c.getMethod(propertyGetter, (Class<?>[]) null);
|
||||||
|
return (T) m.invoke(obj);
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||||
|
logger.log(Level.WARNING, e.getMessage(), e);
|
||||||
|
}
|
||||||
|
c = c.getSuperclass();
|
||||||
|
}
|
||||||
|
throw new NullPointerException("Method " + propertyGetter + " not found on object " + obj.getClass().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static abstract class COSResourceCacheBase<TObject extends COSObjectable> {
|
||||||
|
private final Map<Integer, List<TObject>> states = new HashMap<>();
|
||||||
|
|
||||||
|
private static boolean equalsCOSDictionary(COSDictionary cosDictionary,
|
||||||
|
COSDictionary cosDictionary1) {
|
||||||
|
if (cosDictionary.size() != cosDictionary1.size())
|
||||||
|
return false;
|
||||||
|
for (COSName name : cosDictionary.keySet()) {
|
||||||
|
COSBase item = cosDictionary.getItem(name);
|
||||||
|
COSBase item2 = cosDictionary1.getItem(name);
|
||||||
|
if (!equalsCOSBase(item, item2))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean equalsCOSBase(COSBase item, COSBase item2) {
|
||||||
|
if (item == item2)
|
||||||
|
return true;
|
||||||
|
if (item == null)
|
||||||
|
return false;
|
||||||
|
if (item2 == null)
|
||||||
|
return false;
|
||||||
|
/*
|
||||||
|
* Can the items be compared directly?
|
||||||
|
*/
|
||||||
|
if (item.equals(item2))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (item instanceof COSDictionary && item2 instanceof COSDictionary)
|
||||||
|
return equalsCOSDictionary((COSDictionary) item, (COSDictionary) item2);
|
||||||
|
|
||||||
|
// noinspection SimplifiableIfStatement
|
||||||
|
if (item instanceof COSArray && item2 instanceof COSArray)
|
||||||
|
return equalsCOSArray((COSArray) item, (COSArray) item2);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean equalsCOSArray(COSArray item, COSArray item2) {
|
||||||
|
if (item.size() != item2.size())
|
||||||
|
return false;
|
||||||
|
for (int i = 0; i < item.size(); i++) {
|
||||||
|
COSBase i1 = item.getObject(i);
|
||||||
|
COSBase i2 = item2.getObject(i);
|
||||||
|
if (!equalsCOSBase(i1, i2))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract int getKey(TObject obj);
|
||||||
|
|
||||||
|
TObject makeUnqiue(TObject state) {
|
||||||
|
int key = getKey(state);
|
||||||
|
List<TObject> pdExtendedGraphicsStates = states.get(key);
|
||||||
|
if (pdExtendedGraphicsStates == null) {
|
||||||
|
pdExtendedGraphicsStates = new ArrayList<TObject>();
|
||||||
|
states.put(key, pdExtendedGraphicsStates);
|
||||||
|
}
|
||||||
|
for (TObject s : pdExtendedGraphicsStates) {
|
||||||
|
if (stateEquals(s, state))
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
pdExtendedGraphicsStates.add(state);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean stateEquals(TObject s, TObject state) {
|
||||||
|
COSBase base1 = s.getCOSObject();
|
||||||
|
COSBase base2 = state.getCOSObject();
|
||||||
|
return equalsCOSBase(base1, base2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ExtGStateCache extends COSResourceCacheBase<PDExtendedGraphicsState> {
|
||||||
|
@Override
|
||||||
|
protected int getKey(PDExtendedGraphicsState obj) {
|
||||||
|
return obj.getCOSObject().size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PDShadingCache extends COSResourceCacheBase<PDShading> {
|
||||||
|
@Override
|
||||||
|
protected int getKey(PDShading obj) {
|
||||||
|
return obj.getCOSObject().size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import java.awt.Paint;
|
||||||
|
import java.awt.Shape;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows you to influence the fill and draw operations. You can alter the shape
|
||||||
|
* to draw/fill, you can even filter out the complete draw/fill operation.
|
||||||
|
* And you can draw additional stuff after the draw/fill operation, e.g. to
|
||||||
|
* implement overfill.
|
||||||
|
*/
|
||||||
|
public interface DrawControl {
|
||||||
|
/**
|
||||||
|
* You may optional change the shape that is going to be filled. You can also do
|
||||||
|
* other stuff here like drawing an overfill before the real shape.
|
||||||
|
*
|
||||||
|
* @param shape the shape that will be drawn
|
||||||
|
* @param env Environment
|
||||||
|
* @return the shape to be filled. If you return null, nothing will be filled
|
||||||
|
*/
|
||||||
|
Shape transformShapeBeforeFill(Shape shape, IDrawControlEnv env);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You may optional change the shape that is going to be drawn. You can also do
|
||||||
|
* other stuff here like drawing an overfill before the real shape.
|
||||||
|
*
|
||||||
|
* @param shape the shape that will be drawn
|
||||||
|
* @param env Environment
|
||||||
|
* @return the shape to be filled. If you return null, nothing will be drawn
|
||||||
|
*/
|
||||||
|
Shape transformShapeBeforeDraw(Shape shape, IDrawControlEnv env);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after shape was filled. This method is always called, even if
|
||||||
|
* {@link #transformShapeBeforeFill(Shape, IDrawControlEnv)} returns
|
||||||
|
* null.
|
||||||
|
*
|
||||||
|
* @param shape the shape that was filled. This is the original shape, not the one
|
||||||
|
* transformed by
|
||||||
|
* {@link #transformShapeBeforeFill(Shape, IDrawControlEnv)}.
|
||||||
|
* @param env Environment
|
||||||
|
*/
|
||||||
|
void afterShapeFill(Shape shape, IDrawControlEnv env);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after shape was drawn. This method is always called, even if
|
||||||
|
* {@link #transformShapeBeforeDraw(Shape, IDrawControlEnv)} returns
|
||||||
|
* null.
|
||||||
|
*
|
||||||
|
* @param shape the shape that was drawn. This is the original shape, not the one
|
||||||
|
* transformed by
|
||||||
|
* {@link #transformShapeBeforeDraw(Shape, IDrawControlEnv)}.
|
||||||
|
* @param env Environment
|
||||||
|
*/
|
||||||
|
void afterShapeDraw(Shape shape, IDrawControlEnv env);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The environment of the draw operation
|
||||||
|
*/
|
||||||
|
interface IDrawControlEnv {
|
||||||
|
/**
|
||||||
|
* @return the current paint set on the graphics.
|
||||||
|
*/
|
||||||
|
Paint getPaint();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the graphics currently drawn on
|
||||||
|
*/
|
||||||
|
PdfBoxGraphics2D getGraphics();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDResources;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
|
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.FontFormatException;
|
||||||
|
import java.awt.FontMetrics;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Paint;
|
||||||
|
import java.awt.Shape;
|
||||||
|
import java.awt.font.FontRenderContext;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.AttributedCharacterIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw text using Fonts
|
||||||
|
*/
|
||||||
|
public interface FontTextDrawer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enviroment for font based drawing of text
|
||||||
|
*/
|
||||||
|
interface IFontTextDrawerEnv {
|
||||||
|
/**
|
||||||
|
* @return the document we are writing to
|
||||||
|
*/
|
||||||
|
PDDocument getDocument();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the content stream
|
||||||
|
*/
|
||||||
|
PDPageContentStream getContentStream();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the current font set on the graphics. This is the "default" font to
|
||||||
|
* use when no other font is set on the
|
||||||
|
* {@link AttributedCharacterIterator}.
|
||||||
|
*/
|
||||||
|
Font getFont();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the current paint set on the graphics. This is the "default" paint
|
||||||
|
* when no other paint is set on on the
|
||||||
|
* {@link AttributedCharacterIterator}.
|
||||||
|
*/
|
||||||
|
Paint getPaint();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the given paint on the current content stream
|
||||||
|
*
|
||||||
|
* @param paint Paint to apply
|
||||||
|
* @param shapeToDraw the shape to draw of the text, if known. This is needed to
|
||||||
|
* calculate correct gradients.
|
||||||
|
* @throws IOException if an IO error occurs when writing the paint to the content
|
||||||
|
* stream.
|
||||||
|
*/
|
||||||
|
void applyPaint(Paint paint, Shape shapeToDraw) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the {@link Graphics2D} {@link FontRenderContext}
|
||||||
|
*/
|
||||||
|
FontRenderContext getFontRenderContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the bbox of the {@link PdfBoxGraphics2D}
|
||||||
|
*/
|
||||||
|
PDRectangle getGraphicsBBox();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the resource of the content stream
|
||||||
|
*/
|
||||||
|
PDResources getResources();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the default calcuation BufferedImage based graphics.
|
||||||
|
*/
|
||||||
|
Graphics2D getCalculationGraphics();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param iterator Has the text and all its properties
|
||||||
|
* @param env Environment
|
||||||
|
* @return true when the given text can be fully drawn using fonts. return false
|
||||||
|
* to have the text drawn as vector shapes
|
||||||
|
* @throws IOException when a font can not be loaded or a paint can't be applied.
|
||||||
|
* @throws FontFormatException when the font file can not be loaded
|
||||||
|
*/
|
||||||
|
boolean canDrawText(AttributedCharacterIterator iterator, IFontTextDrawerEnv env)
|
||||||
|
throws IOException, FontFormatException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param iterator The text with all properties
|
||||||
|
* @param env Environment
|
||||||
|
* @throws IOException when a font can not be loaded or a paint can't be applied.
|
||||||
|
* @throws FontFormatException when the font file can not be loaded
|
||||||
|
*/
|
||||||
|
void drawText(AttributedCharacterIterator iterator, IFontTextDrawerEnv env)
|
||||||
|
throws IOException, FontFormatException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param f
|
||||||
|
* @param env
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
FontMetrics getFontMetrics(Font f, IFontTextDrawerEnv env)
|
||||||
|
throws IOException, FontFormatException;
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
|
|
||||||
|
import java.awt.Image;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode and compress an image as PDImageXObject
|
||||||
|
*/
|
||||||
|
public interface ImageEncoder {
|
||||||
|
/**
|
||||||
|
* Encode the given image into the a PDImageXObject
|
||||||
|
*
|
||||||
|
* @param document the PDF document
|
||||||
|
* @param contentStream the content stream of the page
|
||||||
|
* @param image the image to encode
|
||||||
|
* @return the encoded image
|
||||||
|
*/
|
||||||
|
PDImageXObject encodeImage(PDDocument document, PDPageContentStream contentStream, Image image);
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
|
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Image;
|
||||||
|
import java.awt.color.ColorSpace;
|
||||||
|
import java.awt.color.ICC_ColorSpace;
|
||||||
|
import java.awt.color.ICC_Profile;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.ref.SoftReference;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes all images using lossless compression. Tries to reuse images as much
|
||||||
|
* as possible. You can share an instance of this class with multiple
|
||||||
|
* PdfBoxGraphics2D objects.
|
||||||
|
*/
|
||||||
|
public class LosslessImageEncoder implements ImageEncoder {
|
||||||
|
private Map<ImageSoftReference, SoftReference<PDImageXObject>> imageMap = new HashMap<>();
|
||||||
|
private Map<ProfileSoftReference, SoftReference<PDColorSpace>> profileMap = new HashMap<>();
|
||||||
|
private SoftReference<PDDocument> doc;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PDImageXObject encodeImage(PDDocument document, PDPageContentStream contentStream, Image image) {
|
||||||
|
final BufferedImage bi;
|
||||||
|
|
||||||
|
if (image instanceof BufferedImage) {
|
||||||
|
bi = (BufferedImage) image;
|
||||||
|
} else {
|
||||||
|
int width = image.getWidth(null);
|
||||||
|
int height = image.getHeight(null);
|
||||||
|
bi = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
|
||||||
|
Graphics graphics = bi.getGraphics();
|
||||||
|
if (!graphics.drawImage(image, 0, 0, null, null))
|
||||||
|
throw new IllegalStateException("Not fully loaded images are not supported.");
|
||||||
|
graphics.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (doc == null || doc.get() != document) {
|
||||||
|
imageMap = new HashMap<>();
|
||||||
|
profileMap = new HashMap<>();
|
||||||
|
doc = new SoftReference<>(document);
|
||||||
|
}
|
||||||
|
SoftReference<PDImageXObject> pdImageXObjectSoftReference = imageMap.get(new ImageSoftReference(image));
|
||||||
|
PDImageXObject imageXObject = pdImageXObjectSoftReference == null ? null
|
||||||
|
: pdImageXObjectSoftReference.get();
|
||||||
|
if (imageXObject == null) {
|
||||||
|
imageXObject = LosslessFactory.createFromImage(document, bi);
|
||||||
|
if (bi.getColorModel().getColorSpace() instanceof ICC_ColorSpace) {
|
||||||
|
ICC_Profile profile = ((ICC_ColorSpace) bi.getColorModel().getColorSpace()).getProfile();
|
||||||
|
if (((ICC_ColorSpace) bi.getColorModel().getColorSpace()).getProfile() != ICC_Profile
|
||||||
|
.getInstance(ColorSpace.CS_sRGB)) {
|
||||||
|
SoftReference<PDColorSpace> pdProfileRef = profileMap.get(new ProfileSoftReference(profile));
|
||||||
|
PDColorSpace pdProfile = pdProfileRef == null ? null : pdProfileRef.get();
|
||||||
|
if (pdProfile == null) {
|
||||||
|
pdProfile = imageXObject.getColorSpace();
|
||||||
|
if (pdProfile instanceof PDICCBased) {
|
||||||
|
profileMap.put(new ProfileSoftReference(profile),
|
||||||
|
new SoftReference<>(pdProfile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imageXObject.setColorSpace(pdProfile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imageMap.put(new ImageSoftReference(image), new SoftReference<>(imageXObject));
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageXObject;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Could not encode Image", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ImageSoftReference extends SoftReference<Image> {
|
||||||
|
ImageSoftReference(Image referent) {
|
||||||
|
super(referent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
assert obj instanceof ImageSoftReference;
|
||||||
|
return ((ImageSoftReference) obj).get() == get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
Image image = get();
|
||||||
|
if (image == null)
|
||||||
|
return 0;
|
||||||
|
return image.hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ProfileSoftReference extends SoftReference<ICC_Profile> {
|
||||||
|
ProfileSoftReference(ICC_Profile referent) {
|
||||||
|
super(referent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
assert obj instanceof ProfileSoftReference;
|
||||||
|
return ((ProfileSoftReference) obj).get() == get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
ICC_Profile image = get();
|
||||||
|
if (image == null)
|
||||||
|
return 0;
|
||||||
|
return image.hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDResources;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.shading.PDShading;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Composite;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Paint;
|
||||||
|
import java.awt.Shape;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the given paint on the Content Stream.
|
||||||
|
*/
|
||||||
|
public interface PaintApplier {
|
||||||
|
/**
|
||||||
|
* Apply the paint on the ContentStream
|
||||||
|
*
|
||||||
|
* @param paint the paint which should be applied
|
||||||
|
* @param contentStream the content stream to apply the paint on
|
||||||
|
* @param currentTransform the current transform of the Graphics2D relative to the
|
||||||
|
* contentStream default coordinate space. This is always a copy of the
|
||||||
|
* current transform, so we can modify it.
|
||||||
|
* @param env Environment for mapping the paint.
|
||||||
|
* @return null or a PDShading which should be used to fill a shape.
|
||||||
|
* @throws IOException if its not possible to write the paint into the contentStream
|
||||||
|
*/
|
||||||
|
PDShading applyPaint(Paint paint, PDPageContentStream contentStream,
|
||||||
|
AffineTransform currentTransform, IPaintEnv env) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The different mappers used by the paint applier. This interface is
|
||||||
|
* implemented internally by {@link PdfBoxGraphics2D}
|
||||||
|
*/
|
||||||
|
interface IPaintEnv {
|
||||||
|
/**
|
||||||
|
* @return the color mapper
|
||||||
|
*/
|
||||||
|
ColorMapper getColorMapper();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the image encoder
|
||||||
|
*/
|
||||||
|
ImageEncoder getImageEncoder();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the document
|
||||||
|
*/
|
||||||
|
PDDocument getDocument();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the resource of the content stream
|
||||||
|
*/
|
||||||
|
PDResources getResources();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the {@link Graphics2D} {@link Composite}
|
||||||
|
*/
|
||||||
|
Composite getComposite();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The PdfBoxGraphics2D
|
||||||
|
*/
|
||||||
|
PdfBoxGraphics2D getGraphics2D();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the {@link Graphics2D} XOR Mode {@link Color} or null if paint mode
|
||||||
|
* is active.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
Color getXORMode();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The shape information is need to be able to correctly render grandients.
|
||||||
|
*
|
||||||
|
* @return get the shape which will be drawn or filled with this paint. Null is
|
||||||
|
* returned if no shape is known.
|
||||||
|
*/
|
||||||
|
Shape getShapeToDraw();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.cos.COSName;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.color.ICC_ColorSpace;
|
||||||
|
import java.awt.color.ICC_Profile;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
PdfBoxGraphics2D pdfBoxGraphics2D = new PdfBoxGraphics2D(this.doc, (int)(width), (int)(height));
|
||||||
|
PdfBoxGraphics2DColorMapper colorMapper = new RGBtoCMYKColorMapper(icc_profile);
|
||||||
|
pdfBoxGraphics2D.setColorMapper(colorMapper);
|
||||||
|
|
||||||
|
Where icc_profile is an instance of java.awt.color.ICC_Profile that supports a CMYK
|
||||||
|
colorspace. For testing purposes, we're using ISOcoated_v2_300_bas.icc which ships
|
||||||
|
with PDFBox.
|
||||||
|
*/
|
||||||
|
public class RGBtoCMYKColorMapper extends DefaultColorMapper {
|
||||||
|
ICC_ColorSpace icc_colorspace;
|
||||||
|
PDICCBased pdProfile;
|
||||||
|
|
||||||
|
public RGBtoCMYKColorMapper(ICC_Profile icc_profile, PDDocument document) throws IOException {
|
||||||
|
icc_colorspace = new ICC_ColorSpace(icc_profile);
|
||||||
|
this.pdProfile = new PDICCBased(document);
|
||||||
|
OutputStream outputStream = pdProfile.getPDStream().createOutputStream(COSName.FLATE_DECODE);
|
||||||
|
outputStream.write(icc_profile.getData());
|
||||||
|
outputStream.close();
|
||||||
|
pdProfile.getPDStream().getCOSObject().setInt(COSName.N, 4);
|
||||||
|
pdProfile.getPDStream().getCOSObject().setItem(COSName.ALTERNATE, COSName.DEVICECMYK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PDColor mapColor(PDPageContentStream contentStream, Color rgbColor) {
|
||||||
|
int r = rgbColor.getRed();
|
||||||
|
int g = rgbColor.getGreen();
|
||||||
|
int b = rgbColor.getBlue();
|
||||||
|
int[] rgbInts = {r, g, b};
|
||||||
|
float[] rgbFoats = rgbIntToFloat(rgbInts);
|
||||||
|
float[] cmykFloats = icc_colorspace.fromRGB(rgbFoats);
|
||||||
|
|
||||||
|
PDColor cmykColor = new PDColor(cmykFloats, pdProfile);
|
||||||
|
return cmykColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float[] rgbIntToFloat(int[] rgbInts) {
|
||||||
|
// the input ints are in the range 0 to 255
|
||||||
|
// the output floats need to be in the range 0.0 to 1.0
|
||||||
|
float red = (float) rgbInts[0] / 255.0F;
|
||||||
|
float green = (float) rgbInts[1] / 255.0F;
|
||||||
|
float blue = (float) rgbInts[2] / 255.0F;
|
||||||
|
float[] rgbFloats = new float[]{red, green, blue};
|
||||||
|
return rgbFloats;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||||
|
import org.apache.pdfbox.util.Matrix;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class DanglingGfxCaseTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDanglingGfx() throws IOException {
|
||||||
|
|
||||||
|
PDDocument document = new PDDocument();
|
||||||
|
PDPage page = new PDPage(PDRectangle.A4);
|
||||||
|
document.addPage(page);
|
||||||
|
|
||||||
|
PDPageContentStream contentStream = new PDPageContentStream(document, page);
|
||||||
|
PdfBoxGraphics2D pdfBoxGraphics2D = new PdfBoxGraphics2D(document, 400, 400);
|
||||||
|
|
||||||
|
PdfBoxGraphics2D child = pdfBoxGraphics2D.create(10, 10, 40, 40);
|
||||||
|
child.setColor(Color.RED);
|
||||||
|
child.fillRect(0, 0, 100, 100);
|
||||||
|
|
||||||
|
PdfBoxGraphics2D child2 = child.create(20, 20, 10, 10);
|
||||||
|
child2.setColor(Color.GREEN);
|
||||||
|
child2.drawOval(0, 0, 5, 5);
|
||||||
|
|
||||||
|
child.create();
|
||||||
|
|
||||||
|
pdfBoxGraphics2D.disposeDanglingChildGraphics();
|
||||||
|
pdfBoxGraphics2D.dispose();
|
||||||
|
|
||||||
|
PDFormXObject appearanceStream = pdfBoxGraphics2D.getXFormObject();
|
||||||
|
Matrix matrix = new Matrix();
|
||||||
|
matrix.translate(0, 20);
|
||||||
|
contentStream.transform(matrix);
|
||||||
|
contentStream.drawForm(appearanceStream);
|
||||||
|
contentStream.close();
|
||||||
|
|
||||||
|
File file = new File("build/test/dangling_test.pdf");
|
||||||
|
file.getParentFile().mkdirs();
|
||||||
|
document.save(file);
|
||||||
|
document.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDanglingDisposeException() {
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> {
|
||||||
|
PDDocument document = new PDDocument();
|
||||||
|
PDPage page = new PDPage(PDRectangle.A4);
|
||||||
|
document.addPage(page);
|
||||||
|
PdfBoxGraphics2D pdfBoxGraphics2D = new PdfBoxGraphics2D(document, 400, 400);
|
||||||
|
pdfBoxGraphics2D.create();
|
||||||
|
pdfBoxGraphics2D.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDanglingDisposeException2() {
|
||||||
|
Assertions.assertThrows(IllegalStateException.class, () -> {
|
||||||
|
PDDocument document = new PDDocument();
|
||||||
|
PDPage page = new PDPage(PDRectangle.A4);
|
||||||
|
document.addPage(page);
|
||||||
|
PdfBoxGraphics2D pdfBoxGraphics2D = new PdfBoxGraphics2D(document, 400, 400);
|
||||||
|
pdfBoxGraphics2D.create().disposeDanglingChildGraphics();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.FontFormatException;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class FontTest extends PdfBoxGraphics2DTestBase {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAntonioFont() throws IOException, FontFormatException {
|
||||||
|
final Font antonioRegular = Font
|
||||||
|
.createFont(Font.TRUETYPE_FONT,
|
||||||
|
PdfBoxGraphics2dTest.class.getResourceAsStream("antonio/Antonio-Regular.ttf"))
|
||||||
|
.deriveFont(15f);
|
||||||
|
exportGraphic("fonts", "antonio", new GraphicsExporter() {
|
||||||
|
@Override
|
||||||
|
public void draw(Graphics2D gfx) throws IOException, FontFormatException {
|
||||||
|
gfx.setColor(Color.BLACK);
|
||||||
|
gfx.setFont(antonioRegular);
|
||||||
|
gfx.drawString("Für älter österlich, Umlauts are not always fun.", 10, 50);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDType0Font;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.FontFormatException;
|
||||||
|
import java.awt.FontMetrics;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
|
public class FontWidthDiscrepancyTest extends PdfBoxGraphics2DTestBase {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAntonioFontWidth() throws IOException, FontFormatException {
|
||||||
|
|
||||||
|
final String testString = "MMMMMMMMMMMMMMMMMMMMMM";
|
||||||
|
final float fontSize = 20f;
|
||||||
|
final Font antonioRegular = Font.createFont(Font.TRUETYPE_FONT,
|
||||||
|
PdfBoxGraphics2dTest.class.getResourceAsStream("antonio/Antonio-Regular.ttf"))
|
||||||
|
.deriveFont(fontSize);
|
||||||
|
|
||||||
|
final PDDocument doc = new PDDocument();
|
||||||
|
final PDFont pdFont = PDType0Font.load(doc,
|
||||||
|
PdfBoxGraphics2dTest.class.getResourceAsStream("antonio/Antonio-Regular.ttf"));
|
||||||
|
|
||||||
|
final Graphics2D gfx = new PdfBoxGraphics2D(doc, 400, 400);
|
||||||
|
|
||||||
|
final float pdfWidth = pdFont.getStringWidth(testString) / 1000 * fontSize;
|
||||||
|
final int gfxWidth = gfx.getFontMetrics(antonioRegular).stringWidth(testString);
|
||||||
|
gfx.dispose();
|
||||||
|
doc.close();
|
||||||
|
|
||||||
|
exportGraphic("fontWidthDiscrepancy", "antonio-m", new GraphicsExporter() {
|
||||||
|
@Override
|
||||||
|
public void draw(Graphics2D gfx) throws IOException, FontFormatException {
|
||||||
|
gfx.setFont(antonioRegular);
|
||||||
|
gfx.setColor(Color.GREEN);
|
||||||
|
gfx.drawString(testString, 10, 10);
|
||||||
|
gfx.setColor(Color.RED);
|
||||||
|
gfx.drawLine(10, 1, (int) (10 + pdfWidth), 1);
|
||||||
|
gfx.setColor(Color.BLUE);
|
||||||
|
gfx.drawLine(10, 15, 10 + gfxWidth, 15);
|
||||||
|
|
||||||
|
gfx.setColor(Color.magenta);
|
||||||
|
FontMetrics fontMetrics = gfx.getFontMetrics();
|
||||||
|
int currentMeasurement = fontMetrics.stringWidth(testString);
|
||||||
|
gfx.drawLine(10, 25, 10 + currentMeasurement, 25);
|
||||||
|
|
||||||
|
gfx.drawLine(10, 5, 10 + fontMetrics.charWidth('M'), 5);
|
||||||
|
|
||||||
|
assertNotNull(fontMetrics.getWidths());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,408 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||||
|
import org.apache.pdfbox.util.Matrix;
|
||||||
|
import org.jfree.chart.ChartFactory;
|
||||||
|
//import org.jfree.chart.ChartUtilities;
|
||||||
|
import org.jfree.chart.ChartUtils;
|
||||||
|
import org.jfree.chart.JFreeChart;
|
||||||
|
import org.jfree.chart.axis.NumberAxis;
|
||||||
|
import org.jfree.chart.labels.StandardCategoryToolTipGenerator;
|
||||||
|
import org.jfree.chart.plot.*;
|
||||||
|
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
|
||||||
|
import org.jfree.chart.title.LegendTitle;
|
||||||
|
import org.jfree.chart.title.TextTitle;
|
||||||
|
import org.jfree.chart.ui.RectangleEdge;
|
||||||
|
import org.jfree.chart.util.TableOrder;
|
||||||
|
import org.jfree.data.category.CategoryDataset;
|
||||||
|
import org.jfree.data.category.DefaultCategoryDataset;
|
||||||
|
import org.jfree.data.category.IntervalCategoryDataset;
|
||||||
|
import org.jfree.data.gantt.Task;
|
||||||
|
import org.jfree.data.gantt.TaskSeries;
|
||||||
|
import org.jfree.data.gantt.TaskSeriesCollection;
|
||||||
|
import org.jfree.data.time.SimpleTimePeriod;
|
||||||
|
import org.jfree.data.xy.XYDataset;
|
||||||
|
import org.jfree.data.xy.XYSeries;
|
||||||
|
import org.jfree.data.xy.XYSeriesCollection;
|
||||||
|
//import org.jfree.ui.RectangleEdge;
|
||||||
|
//import org.jfree.util.TableOrder;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class MultiPageTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultiPageJFreeChart() throws IOException {
|
||||||
|
File parentDir = new File("build/test/multipage");
|
||||||
|
// noinspection ResultOfMethodCallIgnored
|
||||||
|
parentDir.mkdirs();
|
||||||
|
File targetPDF = new File(parentDir, "multipage.pdf");
|
||||||
|
PDDocument document = new PDDocument();
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
PDPage page = new PDPage(PDRectangle.A4);
|
||||||
|
document.addPage(page);
|
||||||
|
|
||||||
|
PDPageContentStream contentStream = new PDPageContentStream(document, page);
|
||||||
|
PdfBoxGraphics2D pdfBoxGraphics2D = new PdfBoxGraphics2D(document, 800, 400);
|
||||||
|
drawOnGraphics(pdfBoxGraphics2D, i);
|
||||||
|
pdfBoxGraphics2D.dispose();
|
||||||
|
|
||||||
|
PDFormXObject appearanceStream = pdfBoxGraphics2D.getXFormObject();
|
||||||
|
Matrix matrix = new Matrix();
|
||||||
|
matrix.translate(0, 30);
|
||||||
|
matrix.scale(0.7f, 1f);
|
||||||
|
|
||||||
|
contentStream.saveGraphicsState();
|
||||||
|
contentStream.transform(matrix);
|
||||||
|
contentStream.drawForm(appearanceStream);
|
||||||
|
contentStream.restoreGraphicsState();
|
||||||
|
|
||||||
|
contentStream.close();
|
||||||
|
}
|
||||||
|
document.save(targetPDF);
|
||||||
|
document.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawOnGraphics(PdfBoxGraphics2D gfx, int i) {
|
||||||
|
Rectangle rectangle = new Rectangle(800, 400);
|
||||||
|
switch (i) {
|
||||||
|
case 0:
|
||||||
|
case 3: {
|
||||||
|
final XYDataset dataset = createDatasetXY();
|
||||||
|
final JFreeChart chart = createChartXY(dataset);
|
||||||
|
chart.draw(gfx, rectangle);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
final IntervalCategoryDataset dataset = createDatasetGantt();
|
||||||
|
final JFreeChart chart = createChartGantt(dataset);
|
||||||
|
chart.draw(gfx, rectangle);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
final CategoryDataset dataset = createDatasetCategory();
|
||||||
|
final JFreeChart chart = createChartCategory(dataset);
|
||||||
|
chart.draw(gfx, rectangle);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 4: {
|
||||||
|
final CategoryDataset dataset = createDatasetCategory();
|
||||||
|
final JFreeChart chart = createSpiderChart(dataset);
|
||||||
|
chart.draw(gfx, rectangle);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 5: {
|
||||||
|
final DefaultCategoryDataset dataset = new DefaultCategoryDataset();
|
||||||
|
dataset.addValue(0.0, "Row 0", "Column 0");
|
||||||
|
dataset.addValue(0.0, "Row 0", "Column 1");
|
||||||
|
dataset.addValue(0.0, "Row 0", "Column 2");
|
||||||
|
dataset.addValue(0.0, "Row 0", "Column 3");
|
||||||
|
dataset.addValue(0.0, "Row 0", "Column 4");
|
||||||
|
final JFreeChart chart = createSpiderChart(dataset);
|
||||||
|
chart.setTitle("Invalid Spider Chart");
|
||||||
|
chart.draw(gfx, rectangle);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a sample dataset.
|
||||||
|
*
|
||||||
|
* @return a sample dataset.
|
||||||
|
*/
|
||||||
|
private XYDataset createDatasetXY() {
|
||||||
|
|
||||||
|
final XYSeries series1 = new XYSeries("First");
|
||||||
|
series1.add(1.0, 1.0);
|
||||||
|
series1.add(2.0, 4.0);
|
||||||
|
series1.add(3.0, 3.0);
|
||||||
|
series1.add(4.0, 5.0);
|
||||||
|
series1.add(5.0, 5.0);
|
||||||
|
series1.add(6.0, 7.0);
|
||||||
|
series1.add(7.0, 7.0);
|
||||||
|
series1.add(8.0, 8.0);
|
||||||
|
|
||||||
|
final XYSeries series2 = new XYSeries("Second");
|
||||||
|
series2.add(1.0, 5.0);
|
||||||
|
series2.add(2.0, 7.0);
|
||||||
|
series2.add(3.0, 6.0);
|
||||||
|
series2.add(4.0, 8.0);
|
||||||
|
series2.add(5.0, 4.0);
|
||||||
|
series2.add(6.0, 4.0);
|
||||||
|
series2.add(7.0, 2.0);
|
||||||
|
series2.add(8.0, 1.0);
|
||||||
|
|
||||||
|
final XYSeries series3 = new XYSeries("Third");
|
||||||
|
series3.add(3.0, 4.0);
|
||||||
|
series3.add(4.0, 3.0);
|
||||||
|
series3.add(5.0, 2.0);
|
||||||
|
series3.add(6.0, 3.0);
|
||||||
|
series3.add(7.0, 6.0);
|
||||||
|
series3.add(8.0, 3.0);
|
||||||
|
series3.add(9.0, 4.0);
|
||||||
|
series3.add(10.0, 3.0);
|
||||||
|
|
||||||
|
final XYSeriesCollection dataset = new XYSeriesCollection();
|
||||||
|
dataset.addSeries(series1);
|
||||||
|
dataset.addSeries(series2);
|
||||||
|
dataset.addSeries(series3);
|
||||||
|
|
||||||
|
return dataset;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a chart.
|
||||||
|
*
|
||||||
|
* @param dataset the data for the chart.
|
||||||
|
* @return a chart.
|
||||||
|
*/
|
||||||
|
private JFreeChart createChartXY(final XYDataset dataset) {
|
||||||
|
|
||||||
|
// create the chart...
|
||||||
|
final JFreeChart chart = ChartFactory.createXYLineChart("Line Chart Demo 6", // chart
|
||||||
|
// title
|
||||||
|
"X", // x axis label
|
||||||
|
"Y", // y axis label
|
||||||
|
dataset, // data
|
||||||
|
PlotOrientation.VERTICAL, true, // include legend
|
||||||
|
true, // tooltips
|
||||||
|
false // urls
|
||||||
|
);
|
||||||
|
|
||||||
|
// NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART...
|
||||||
|
chart.setBackgroundPaint(Color.white);
|
||||||
|
|
||||||
|
// final StandardLegend legend = (StandardLegend) chart.getLegend();
|
||||||
|
// legend.setDisplaySeriesShapes(true);
|
||||||
|
|
||||||
|
// get a reference to the plot for further customisation...
|
||||||
|
final XYPlot plot = chart.getXYPlot();
|
||||||
|
plot.setBackgroundPaint(Color.lightGray);
|
||||||
|
// plot.setAxisOffset(new Spacer(Spacer.ABSOLUTE, 5.0, 5.0, 5.0, 5.0));
|
||||||
|
plot.setDomainGridlinePaint(Color.white);
|
||||||
|
plot.setRangeGridlinePaint(Color.white);
|
||||||
|
|
||||||
|
final XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
|
||||||
|
renderer.setSeriesLinesVisible(0, false);
|
||||||
|
renderer.setSeriesShapesVisible(1, false);
|
||||||
|
plot.setRenderer(renderer);
|
||||||
|
|
||||||
|
// change the auto tick unit selection to integer units only...
|
||||||
|
final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
|
||||||
|
rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
|
||||||
|
// OPTIONAL CUSTOMISATION COMPLETED.
|
||||||
|
|
||||||
|
return chart;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a sample dataset for a Gantt chart.
|
||||||
|
*
|
||||||
|
* @return The dataset.
|
||||||
|
*/
|
||||||
|
private static IntervalCategoryDataset createDatasetGantt() {
|
||||||
|
|
||||||
|
final TaskSeries s1 = new TaskSeries("Scheduled");
|
||||||
|
s1.add(new Task("Write Proposal",
|
||||||
|
new SimpleTimePeriod(date(1, Calendar.APRIL, 2001), date(5, Calendar.APRIL, 2001))));
|
||||||
|
s1.add(new Task("Obtain Approval",
|
||||||
|
new SimpleTimePeriod(date(9, Calendar.APRIL, 2001), date(9, Calendar.APRIL, 2001))));
|
||||||
|
s1.add(new Task("Requirements Analysis",
|
||||||
|
new SimpleTimePeriod(date(10, Calendar.APRIL, 2001), date(5, Calendar.MAY, 2001))));
|
||||||
|
s1.add(new Task("Design Phase",
|
||||||
|
new SimpleTimePeriod(date(6, Calendar.MAY, 2001), date(30, Calendar.MAY, 2001))));
|
||||||
|
s1.add(new Task("Design Signoff",
|
||||||
|
new SimpleTimePeriod(date(2, Calendar.JUNE, 2001), date(2, Calendar.JUNE, 2001))));
|
||||||
|
s1.add(new Task("Alpha Implementation",
|
||||||
|
new SimpleTimePeriod(date(3, Calendar.JUNE, 2001), date(31, Calendar.JULY, 2001))));
|
||||||
|
s1.add(new Task("Design Review",
|
||||||
|
new SimpleTimePeriod(date(1, Calendar.AUGUST, 2001), date(8, Calendar.AUGUST, 2001))));
|
||||||
|
s1.add(new Task("Revised Design Signoff",
|
||||||
|
new SimpleTimePeriod(date(10, Calendar.AUGUST, 2001), date(10, Calendar.AUGUST, 2001))));
|
||||||
|
s1.add(new Task("Beta Implementation",
|
||||||
|
new SimpleTimePeriod(date(12, Calendar.AUGUST, 2001), date(12, Calendar.SEPTEMBER, 2001))));
|
||||||
|
s1.add(new Task("Testing",
|
||||||
|
new SimpleTimePeriod(date(13, Calendar.SEPTEMBER, 2001), date(31, Calendar.OCTOBER, 2001))));
|
||||||
|
s1.add(new Task("Final Implementation",
|
||||||
|
new SimpleTimePeriod(date(1, Calendar.NOVEMBER, 2001), date(15, Calendar.NOVEMBER, 2001))));
|
||||||
|
s1.add(new Task("Signoff",
|
||||||
|
new SimpleTimePeriod(date(28, Calendar.NOVEMBER, 2001), date(30, Calendar.NOVEMBER, 2001))));
|
||||||
|
|
||||||
|
final TaskSeries s2 = new TaskSeries("Actual");
|
||||||
|
s2.add(new Task("Write Proposal",
|
||||||
|
new SimpleTimePeriod(date(1, Calendar.APRIL, 2001), date(5, Calendar.APRIL, 2001))));
|
||||||
|
s2.add(new Task("Obtain Approval",
|
||||||
|
new SimpleTimePeriod(date(9, Calendar.APRIL, 2001), date(9, Calendar.APRIL, 2001))));
|
||||||
|
s2.add(new Task("Requirements Analysis",
|
||||||
|
new SimpleTimePeriod(date(10, Calendar.APRIL, 2001), date(15, Calendar.MAY, 2001))));
|
||||||
|
s2.add(new Task("Design Phase",
|
||||||
|
new SimpleTimePeriod(date(15, Calendar.MAY, 2001), date(17, Calendar.JUNE, 2001))));
|
||||||
|
s2.add(new Task("Design Signoff",
|
||||||
|
new SimpleTimePeriod(date(30, Calendar.JUNE, 2001), date(30, Calendar.JUNE, 2001))));
|
||||||
|
s2.add(new Task("Alpha Implementation",
|
||||||
|
new SimpleTimePeriod(date(1, Calendar.JULY, 2001), date(12, Calendar.SEPTEMBER, 2001))));
|
||||||
|
s2.add(new Task("Design Review",
|
||||||
|
new SimpleTimePeriod(date(12, Calendar.SEPTEMBER, 2001), date(22, Calendar.SEPTEMBER, 2001))));
|
||||||
|
s2.add(new Task("Revised Design Signoff",
|
||||||
|
new SimpleTimePeriod(date(25, Calendar.SEPTEMBER, 2001), date(27, Calendar.SEPTEMBER, 2001))));
|
||||||
|
s2.add(new Task("Beta Implementation",
|
||||||
|
new SimpleTimePeriod(date(27, Calendar.SEPTEMBER, 2001), date(30, Calendar.OCTOBER, 2001))));
|
||||||
|
s2.add(new Task("Testing",
|
||||||
|
new SimpleTimePeriod(date(31, Calendar.OCTOBER, 2001), date(17, Calendar.NOVEMBER, 2001))));
|
||||||
|
s2.add(new Task("Final Implementation",
|
||||||
|
new SimpleTimePeriod(date(18, Calendar.NOVEMBER, 2001), date(5, Calendar.DECEMBER, 2001))));
|
||||||
|
s2.add(new Task("Signoff",
|
||||||
|
new SimpleTimePeriod(date(10, Calendar.DECEMBER, 2001), date(11, Calendar.DECEMBER, 2001))));
|
||||||
|
|
||||||
|
final TaskSeriesCollection collection = new TaskSeriesCollection();
|
||||||
|
collection.add(s1);
|
||||||
|
collection.add(s2);
|
||||||
|
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method for creating <code>Date</code> objects.
|
||||||
|
*
|
||||||
|
* @param day the date.
|
||||||
|
* @param month the month.
|
||||||
|
* @param year the year.
|
||||||
|
* @return a date.
|
||||||
|
*/
|
||||||
|
private static Date date(final int day, final int month, @SuppressWarnings("SameParameterValue") final int year) {
|
||||||
|
final Calendar calendar = Calendar.getInstance();
|
||||||
|
calendar.set(year, month, day);
|
||||||
|
return calendar.getTime();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a chart.
|
||||||
|
*
|
||||||
|
* @param dataset the dataset.
|
||||||
|
* @return The chart.
|
||||||
|
*/
|
||||||
|
private JFreeChart createChartGantt(final IntervalCategoryDataset dataset) {
|
||||||
|
return ChartFactory.createGanttChart("Gantt Chart Demo", // chart
|
||||||
|
// title
|
||||||
|
"Task", // domain axis label
|
||||||
|
"Date", // range axis label
|
||||||
|
dataset, // data
|
||||||
|
true, // include legend
|
||||||
|
true, // tooltips
|
||||||
|
false // urls
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a sample dataset.
|
||||||
|
*
|
||||||
|
* @return A sample dataset.
|
||||||
|
*/
|
||||||
|
private CategoryDataset createDatasetCategory() {
|
||||||
|
final DefaultCategoryDataset dataset = new DefaultCategoryDataset();
|
||||||
|
dataset.addValue(5.6, "Row 0", "Column 0");
|
||||||
|
dataset.addValue(3.2, "Row 0", "Column 1");
|
||||||
|
dataset.addValue(1.8, "Row 0", "Column 2");
|
||||||
|
dataset.addValue(0.2, "Row 0", "Column 3");
|
||||||
|
dataset.addValue(4.1, "Row 0", "Column 4");
|
||||||
|
|
||||||
|
dataset.addValue(9.8, "Row 1", "Column 0");
|
||||||
|
dataset.addValue(6.3, "Row 1", "Column 1");
|
||||||
|
dataset.addValue(0.1, "Row 1", "Column 2");
|
||||||
|
dataset.addValue(1.9, "Row 1", "Column 3");
|
||||||
|
dataset.addValue(9.6, "Row 1", "Column 4");
|
||||||
|
|
||||||
|
dataset.addValue(7.0, "Row 2", "Column 0");
|
||||||
|
dataset.addValue(5.2, "Row 2", "Column 1");
|
||||||
|
dataset.addValue(2.8, "Row 2", "Column 2");
|
||||||
|
dataset.addValue(8.8, "Row 2", "Column 3");
|
||||||
|
dataset.addValue(7.2, "Row 2", "Column 4");
|
||||||
|
|
||||||
|
dataset.addValue(9.5, "Row 3", "Column 0");
|
||||||
|
dataset.addValue(1.2, "Row 3", "Column 1");
|
||||||
|
dataset.addValue(4.5, "Row 3", "Column 2");
|
||||||
|
dataset.addValue(4.4, "Row 3", "Column 3");
|
||||||
|
dataset.addValue(0.2, "Row 3", "Column 4");
|
||||||
|
|
||||||
|
dataset.addValue(3.5, "Row 4", "Column 0");
|
||||||
|
dataset.addValue(6.7, "Row 4", "Column 1");
|
||||||
|
dataset.addValue(9.0, "Row 4", "Column 2");
|
||||||
|
dataset.addValue(1.0, "Row 4", "Column 3");
|
||||||
|
dataset.addValue(5.2, "Row 4", "Column 4");
|
||||||
|
|
||||||
|
dataset.addValue(5.1, "Row 5", "Column 0");
|
||||||
|
dataset.addValue(6.7, "Row 5", "Column 1");
|
||||||
|
dataset.addValue(0.9, "Row 5", "Column 2");
|
||||||
|
dataset.addValue(3.3, "Row 5", "Column 3");
|
||||||
|
dataset.addValue(3.9, "Row 5", "Column 4");
|
||||||
|
|
||||||
|
dataset.addValue(5.6, "Row 6", "Column 0");
|
||||||
|
dataset.addValue(5.6, "Row 6", "Column 1");
|
||||||
|
dataset.addValue(5.6, "Row 6", "Column 2");
|
||||||
|
dataset.addValue(5.6, "Row 6", "Column 3");
|
||||||
|
dataset.addValue(5.6, "Row 6", "Column 4");
|
||||||
|
|
||||||
|
dataset.addValue(7.5, "Row 7", "Column 0");
|
||||||
|
dataset.addValue(9.0, "Row 7", "Column 1");
|
||||||
|
dataset.addValue(3.4, "Row 7", "Column 2");
|
||||||
|
dataset.addValue(4.1, "Row 7", "Column 3");
|
||||||
|
dataset.addValue(0.5, "Row 7", "Column 4");
|
||||||
|
|
||||||
|
return dataset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a sample chart for the given dataset.
|
||||||
|
*
|
||||||
|
* @param dataset the dataset.
|
||||||
|
* @return A sample chart.
|
||||||
|
*/
|
||||||
|
private JFreeChart createChartCategory(final CategoryDataset dataset) {
|
||||||
|
final JFreeChart chart = ChartFactory.createMultiplePieChart3D("Multiple Pie Chart Demo 4", dataset,
|
||||||
|
TableOrder.BY_COLUMN, false, true, false);
|
||||||
|
chart.setBackgroundPaint(new Color(216, 255, 216));
|
||||||
|
final MultiplePiePlot plot = (MultiplePiePlot) chart.getPlot();
|
||||||
|
final JFreeChart subchart = plot.getPieChart();
|
||||||
|
// final StandardLegend legend = new StandardLegend();
|
||||||
|
// legend.setItemFont(new Font("SansSerif", Font.PLAIN, 8));
|
||||||
|
// legend.setAnchor(Legend.SOUTH);
|
||||||
|
// subchart.setLegend(legend);
|
||||||
|
plot.setLimit(0.10);
|
||||||
|
final PiePlot p = (PiePlot) subchart.getPlot();
|
||||||
|
// p.setLabelGenerator(new StandardPieItemLabelGenerator("{0}"));
|
||||||
|
p.setLabelFont(new Font("SansSerif", Font.PLAIN, 8));
|
||||||
|
p.setInteriorGap(0.30);
|
||||||
|
|
||||||
|
return chart;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JFreeChart createSpiderChart(CategoryDataset dataset) {
|
||||||
|
SpiderWebPlot plot = new SpiderWebPlot(dataset);
|
||||||
|
plot.setStartAngle(54);
|
||||||
|
plot.setInteriorGap(0.40);
|
||||||
|
plot.setToolTipGenerator(new StandardCategoryToolTipGenerator());
|
||||||
|
JFreeChart chart = new JFreeChart("Spider Web Chart Demo 1", TextTitle.DEFAULT_FONT, plot, false);
|
||||||
|
LegendTitle legend = new LegendTitle(plot);
|
||||||
|
legend.setPosition(RectangleEdge.BOTTOM);
|
||||||
|
chart.addSubtitle(legend);
|
||||||
|
ChartUtils.applyCurrentTheme(chart);
|
||||||
|
return chart;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.awt.Font;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
|
public class PdfBoxGraphics2DFontTextDrawerDefaultFontsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFontStyleMatching() {
|
||||||
|
Font anyFont = Font.decode("Dialog");
|
||||||
|
Font anyFontBold = anyFont.deriveFont(Font.BOLD);
|
||||||
|
Font anyFontItalic = anyFont.deriveFont(Font.ITALIC);
|
||||||
|
Font anyFontBoldItalic = anyFont.deriveFont(Font.BOLD | Font.ITALIC);
|
||||||
|
|
||||||
|
assertEquals(PDType1Font.COURIER, DefaultFontTextDrawerDefaultFonts.chooseMatchingCourier(anyFont));
|
||||||
|
assertEquals(PDType1Font.COURIER_BOLD,
|
||||||
|
DefaultFontTextDrawerDefaultFonts.chooseMatchingCourier(anyFontBold));
|
||||||
|
assertEquals(PDType1Font.COURIER_OBLIQUE,
|
||||||
|
DefaultFontTextDrawerDefaultFonts.chooseMatchingCourier(anyFontItalic));
|
||||||
|
assertEquals(PDType1Font.COURIER_BOLD_OBLIQUE,
|
||||||
|
DefaultFontTextDrawerDefaultFonts.chooseMatchingCourier(anyFontBoldItalic));
|
||||||
|
|
||||||
|
assertEquals(PDType1Font.HELVETICA,
|
||||||
|
DefaultFontTextDrawerDefaultFonts.chooseMatchingHelvetica(anyFont));
|
||||||
|
assertEquals(PDType1Font.HELVETICA_BOLD,
|
||||||
|
DefaultFontTextDrawerDefaultFonts.chooseMatchingHelvetica(anyFontBold));
|
||||||
|
assertEquals(PDType1Font.HELVETICA_OBLIQUE,
|
||||||
|
DefaultFontTextDrawerDefaultFonts.chooseMatchingHelvetica(anyFontItalic));
|
||||||
|
assertEquals(PDType1Font.HELVETICA_BOLD_OBLIQUE,
|
||||||
|
DefaultFontTextDrawerDefaultFonts.chooseMatchingHelvetica(anyFontBoldItalic));
|
||||||
|
|
||||||
|
assertEquals(PDType1Font.TIMES_ROMAN, DefaultFontTextDrawerDefaultFonts.chooseMatchingTimes(anyFont));
|
||||||
|
assertEquals(PDType1Font.TIMES_BOLD,
|
||||||
|
DefaultFontTextDrawerDefaultFonts.chooseMatchingTimes(anyFontBold));
|
||||||
|
assertEquals(PDType1Font.TIMES_ITALIC,
|
||||||
|
DefaultFontTextDrawerDefaultFonts.chooseMatchingTimes(anyFontItalic));
|
||||||
|
assertEquals(PDType1Font.TIMES_BOLD_ITALIC,
|
||||||
|
DefaultFontTextDrawerDefaultFonts.chooseMatchingTimes(anyFontBoldItalic));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDefaultFontMapping() {
|
||||||
|
assertEquals(PDType1Font.HELVETICA,
|
||||||
|
DefaultFontTextDrawerDefaultFonts.mapDefaultFonts(Font.decode(Font.DIALOG)));
|
||||||
|
assertEquals(PDType1Font.HELVETICA,
|
||||||
|
DefaultFontTextDrawerDefaultFonts.mapDefaultFonts(Font.decode(Font.DIALOG_INPUT)));
|
||||||
|
assertEquals(PDType1Font.HELVETICA,
|
||||||
|
DefaultFontTextDrawerDefaultFonts.mapDefaultFonts(Font.decode("Arial")));
|
||||||
|
|
||||||
|
assertEquals(PDType1Font.COURIER,
|
||||||
|
DefaultFontTextDrawerDefaultFonts.mapDefaultFonts(Font.decode(Font.MONOSPACED)));
|
||||||
|
|
||||||
|
assertEquals(PDType1Font.TIMES_ROMAN,
|
||||||
|
DefaultFontTextDrawerDefaultFonts.mapDefaultFonts(Font.decode(Font.SERIF)));
|
||||||
|
|
||||||
|
assertEquals(PDType1Font.ZAPF_DINGBATS,
|
||||||
|
DefaultFontTextDrawerDefaultFonts.mapDefaultFonts(Font.decode("Dingbats")));
|
||||||
|
|
||||||
|
assertEquals(PDType1Font.SYMBOL,
|
||||||
|
DefaultFontTextDrawerDefaultFonts.mapDefaultFonts(Font.decode("Symbol")));
|
||||||
|
|
||||||
|
assertNull(DefaultFontTextDrawerDefaultFonts.mapDefaultFonts(Font.decode("Georgia")));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDFontFactory;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||||
|
import org.apache.pdfbox.util.Matrix;
|
||||||
|
|
||||||
|
import java.awt.FontFormatException;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
class PdfBoxGraphics2DTestBase {
|
||||||
|
|
||||||
|
enum Mode {
|
||||||
|
DefaultVectorized, FontTextIfPossible, ForceFontText, DefaultFontText
|
||||||
|
}
|
||||||
|
|
||||||
|
void exportGraphic(String dir, String name, GraphicsExporter exporter) {
|
||||||
|
try {
|
||||||
|
PDDocument document = new PDDocument();
|
||||||
|
PDFont pdArial = PDType1Font.HELVETICA;
|
||||||
|
File parentDir = new File("build/test/" + dir);
|
||||||
|
parentDir.mkdirs();
|
||||||
|
|
||||||
|
BufferedImage image = new BufferedImage(400, 400, BufferedImage.TYPE_4BYTE_ABGR);
|
||||||
|
Graphics2D imageGraphics = image.createGraphics();
|
||||||
|
exporter.draw(imageGraphics);
|
||||||
|
imageGraphics.dispose();
|
||||||
|
ImageIO.write(image, "PNG", new File(parentDir, name + ".png"));
|
||||||
|
|
||||||
|
for (Mode m : Mode.values()) {
|
||||||
|
PDPage page = new PDPage(PDRectangle.A4);
|
||||||
|
document.addPage(page);
|
||||||
|
|
||||||
|
PDPageContentStream contentStream = new PDPageContentStream(document, page);
|
||||||
|
PdfBoxGraphics2D pdfBoxGraphics2D = new PdfBoxGraphics2D(document, 400, 400);
|
||||||
|
DefaultFontTextDrawer fontTextDrawer = null;
|
||||||
|
contentStream.beginText();
|
||||||
|
contentStream.setStrokingColor(0f, 0f, 0f);
|
||||||
|
contentStream.setNonStrokingColor(0f, 0f, 0f);
|
||||||
|
contentStream.setFont(PDType1Font.HELVETICA_BOLD, 15);
|
||||||
|
contentStream.setTextMatrix(Matrix.getTranslateInstance(10, 800));
|
||||||
|
contentStream.showText("Mode " + m);
|
||||||
|
contentStream.endText();
|
||||||
|
switch (m) {
|
||||||
|
case FontTextIfPossible:
|
||||||
|
fontTextDrawer = new DefaultFontTextDrawer();
|
||||||
|
registerFots(fontTextDrawer);
|
||||||
|
break;
|
||||||
|
case DefaultFontText: {
|
||||||
|
fontTextDrawer = new DefaultFontTextDrawerDefaultFonts();
|
||||||
|
registerFots(fontTextDrawer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ForceFontText:
|
||||||
|
fontTextDrawer = new DefaultFontTextForcedDrawer();
|
||||||
|
registerFots(fontTextDrawer);
|
||||||
|
fontTextDrawer.registerFont("Arial", pdArial);
|
||||||
|
break;
|
||||||
|
case DefaultVectorized:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (fontTextDrawer != null) {
|
||||||
|
pdfBoxGraphics2D.setFontTextDrawer(fontTextDrawer);
|
||||||
|
}
|
||||||
|
exporter.draw(pdfBoxGraphics2D);
|
||||||
|
pdfBoxGraphics2D.dispose();
|
||||||
|
PDFormXObject appearanceStream = pdfBoxGraphics2D.getXFormObject();
|
||||||
|
Matrix matrix = new Matrix();
|
||||||
|
matrix.translate(0, 20);
|
||||||
|
contentStream.transform(matrix);
|
||||||
|
contentStream.drawForm(appearanceStream);
|
||||||
|
matrix.scale(1.5f, 1.5f);
|
||||||
|
matrix.translate(0, 100);
|
||||||
|
contentStream.transform(matrix);
|
||||||
|
contentStream.drawForm(appearanceStream);
|
||||||
|
contentStream.close();
|
||||||
|
}
|
||||||
|
document.save(new File(parentDir, name + ".pdf"));
|
||||||
|
document.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerFots(DefaultFontTextDrawer fontTextDrawer) {
|
||||||
|
fontTextDrawer.registerFont(new File(
|
||||||
|
"src/test/resources/org/xbib/graphics/graphics2d/pdfbox/DejaVuSerifCondensed.ttf"));
|
||||||
|
fontTextDrawer.registerFont(new File(
|
||||||
|
"src/test/resources/org/xbib/graphics/graphics2d/pdfbox/antonio/Antonio-Regular.ttf"));
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GraphicsExporter {
|
||||||
|
void draw(Graphics2D gfx) throws IOException, FontFormatException;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,282 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.awt.AlphaComposite;
|
||||||
|
import java.awt.BasicStroke;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Composite;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.GradientPaint;
|
||||||
|
import java.awt.LinearGradientPaint;
|
||||||
|
import java.awt.RadialGradientPaint;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.RenderingHints;
|
||||||
|
import java.awt.TexturePaint;
|
||||||
|
import java.awt.font.TextAttribute;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.geom.Ellipse2D;
|
||||||
|
import java.awt.geom.Path2D;
|
||||||
|
import java.awt.geom.Rectangle2D;
|
||||||
|
import java.awt.geom.RoundRectangle2D;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.text.AttributedString;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
public class PdfBoxGraphics2dTest extends PdfBoxGraphics2DTestBase {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNegativeShapesAndComposite() {
|
||||||
|
exportGraphic("simple", "negativeWithComposite", gfx -> {
|
||||||
|
RoundRectangle2D.Float rect = new RoundRectangle2D.Float(10f, 10f, 20f, 20f, 5f,
|
||||||
|
6f);
|
||||||
|
|
||||||
|
AffineTransform transformIdentity = new AffineTransform();
|
||||||
|
AffineTransform transformMirrored = AffineTransform.getTranslateInstance(0, 100);
|
||||||
|
transformMirrored.scale(1, -0.5);
|
||||||
|
for (AffineTransform tf : new AffineTransform[]{transformIdentity,
|
||||||
|
transformMirrored}) {
|
||||||
|
gfx.setTransform(tf);
|
||||||
|
gfx.setColor(Color.red);
|
||||||
|
gfx.fill(rect);
|
||||||
|
gfx.setStroke(new BasicStroke(2f));
|
||||||
|
gfx.draw(rect);
|
||||||
|
GradientPaint gp = new GradientPaint(10.0f, 25.0f, Color.blue, (float) 100,
|
||||||
|
(float) 100, Color.red);
|
||||||
|
gfx.setPaint(gp);
|
||||||
|
gfx.fill(AffineTransform.getTranslateInstance(30f, 20f)
|
||||||
|
.createTransformedShape(rect));
|
||||||
|
Composite composite = gfx.getComposite();
|
||||||
|
gfx.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f));
|
||||||
|
gfx.setColor(Color.cyan);
|
||||||
|
gfx.fillRect(15, 0, 40, 40);
|
||||||
|
gfx.setColor(Color.green);
|
||||||
|
gfx.drawRect(20, 10, 50, 50);
|
||||||
|
gfx.setColor(Color.magenta);
|
||||||
|
gfx.fill(new Ellipse2D.Double(20, 20, 100, 100));
|
||||||
|
gfx.setColor(Color.orange);
|
||||||
|
gfx.fill(new Ellipse2D.Double(20, 20, -100, 100));
|
||||||
|
gfx.setPaint(gp);
|
||||||
|
gfx.fill(new Ellipse2D.Double(10, 80, 20, 20));
|
||||||
|
gfx.fill(new Ellipse2D.Double(10, 100, -20, -20));
|
||||||
|
gfx.setComposite(composite);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGradients() {
|
||||||
|
exportGraphic("simple", "gradients", gfx -> {
|
||||||
|
LinearGradientPaint linearGradientPaint = new LinearGradientPaint(0, 0, 100, 200,
|
||||||
|
new float[]{0.0f, .2f, .4f, .9f, 1f},
|
||||||
|
new Color[]{Color.YELLOW, Color.GREEN, Color.RED, Color.BLUE,
|
||||||
|
Color.GRAY});
|
||||||
|
gfx.setPaint(linearGradientPaint);
|
||||||
|
gfx.fill(new Rectangle.Float(10, 10, 100, 50));
|
||||||
|
gfx.fill(new Rectangle.Float(120, 10, 50, 50));
|
||||||
|
gfx.fill(new Rectangle.Float(200, 10, 50, 100));
|
||||||
|
RadialGradientPaint radialGradientPaint = new RadialGradientPaint(200, 200, 200,
|
||||||
|
new float[]{0.0f, .2f, .4f, .9f, 1f},
|
||||||
|
new Color[]{Color.YELLOW, Color.GREEN, Color.RED, Color.BLUE,
|
||||||
|
Color.GRAY});
|
||||||
|
gfx.setPaint(radialGradientPaint);
|
||||||
|
gfx.fill(new Rectangle.Float(10, 120, 100, 50));
|
||||||
|
gfx.fill(new Rectangle.Float(120, 120, 50, 50));
|
||||||
|
gfx.fill(new Rectangle.Float(200, 120, 50, 100));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBuildPatternFill() {
|
||||||
|
exportGraphic("simple", "patternfill", gfx -> {
|
||||||
|
Composite composite = gfx.getComposite();
|
||||||
|
RadialGradientPaint radialGradientPaint = new RadialGradientPaint(200, 200, 200,
|
||||||
|
new float[]{0.0f, .2f, .4f, .9f, 1f},
|
||||||
|
new Color[]{Color.YELLOW, Color.GREEN, Color.RED, Color.BLUE,
|
||||||
|
Color.GRAY});
|
||||||
|
gfx.setPaint(radialGradientPaint);
|
||||||
|
gfx.setStroke(new BasicStroke(20));
|
||||||
|
gfx.draw(new Ellipse2D.Double(100, 100, 80, 80));
|
||||||
|
gfx.draw(new Ellipse2D.Double(150, 150, 50, 80));
|
||||||
|
gfx.shear(0.4, 0.2);
|
||||||
|
gfx.draw(new Ellipse2D.Double(150, 150, 50, 80));
|
||||||
|
gfx.setComposite(composite);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDifferentFonts() {
|
||||||
|
exportGraphic("simple", "fonts", gfx -> {
|
||||||
|
Font sansSerif = new Font(Font.SANS_SERIF, Font.PLAIN, 15);
|
||||||
|
Font embeddedFont = Font.createFont(Font.TRUETYPE_FONT,
|
||||||
|
PdfBoxGraphics2dTest.class.getResourceAsStream("DejaVuSerifCondensed.ttf"))
|
||||||
|
.deriveFont(15f);
|
||||||
|
Font monoFont = Font.decode(Font.MONOSPACED).deriveFont(15f);
|
||||||
|
Font serifFont = Font.decode(Font.SERIF).deriveFont(15f);
|
||||||
|
int y = 50;
|
||||||
|
for (Font f : new Font[]{sansSerif, embeddedFont, monoFont, serifFont}) {
|
||||||
|
int x = 10;
|
||||||
|
gfx.setPaint(Color.BLACK);
|
||||||
|
gfx.setFont(f);
|
||||||
|
String txt = f.getFontName() + ": ";
|
||||||
|
gfx.drawString(txt, x, y);
|
||||||
|
x += gfx.getFontMetrics().stringWidth(txt);
|
||||||
|
|
||||||
|
txt = "Normal ";
|
||||||
|
gfx.drawString(txt, x, y);
|
||||||
|
x += gfx.getFontMetrics().stringWidth(txt);
|
||||||
|
|
||||||
|
gfx.setPaint(new CMYKColor(1f, 0.5f, 1f, 0.1f, 128));
|
||||||
|
txt = "Bold ";
|
||||||
|
gfx.setFont(f.deriveFont(Font.BOLD));
|
||||||
|
gfx.drawString(txt, x, y);
|
||||||
|
x += gfx.getFontMetrics().stringWidth(txt);
|
||||||
|
|
||||||
|
gfx.setPaint(new CMYKColor(128, 128, 128, 0));
|
||||||
|
txt = "Italic ";
|
||||||
|
gfx.setFont(f.deriveFont(Font.ITALIC));
|
||||||
|
gfx.drawString(txt, x, y);
|
||||||
|
x += gfx.getFontMetrics().stringWidth(txt);
|
||||||
|
|
||||||
|
gfx.setPaint(new CMYKColor(255, 255, 255, 255));
|
||||||
|
txt = "Bold-Italic ";
|
||||||
|
gfx.setFont(f.deriveFont(Font.ITALIC | Font.BOLD));
|
||||||
|
gfx.drawString(txt, x, y);
|
||||||
|
gfx.getFontMetrics().stringWidth(txt);
|
||||||
|
|
||||||
|
y += 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testImageEncoding() {
|
||||||
|
exportGraphic("imageenc", "imageenc", gfx -> {
|
||||||
|
BufferedImage img2 = ImageIO
|
||||||
|
.read(PdfBoxGraphics2dTest.class.getResourceAsStream("pixeltest.png"));
|
||||||
|
BufferedImage img3 = ImageIO
|
||||||
|
.read(PdfBoxGraphics2dTest.class.getResourceAsStream("Rose-ProPhoto.jpg"));
|
||||||
|
BufferedImage img4 = ImageIO
|
||||||
|
.read(PdfBoxGraphics2dTest.class.getResourceAsStream("Italy-P3.jpg"));
|
||||||
|
BufferedImage img5 = ImageIO
|
||||||
|
.read(PdfBoxGraphics2dTest.class.getResourceAsStream("16bit-image1.png"));
|
||||||
|
BufferedImage img6 = ImageIO
|
||||||
|
.read(PdfBoxGraphics2dTest.class.getResourceAsStream("16bit-image2.png"));
|
||||||
|
|
||||||
|
gfx.drawImage(img2, 70, 50, 100, 50, null);
|
||||||
|
gfx.drawImage(img3, 30, 200, 75, 50, null);
|
||||||
|
gfx.drawImage(img4, 170, 10, 60, 40, null);
|
||||||
|
gfx.drawImage(img5, 270, 10, 16, 16, null);
|
||||||
|
gfx.drawImage(img5, 270, 30, 64, 64, null);
|
||||||
|
gfx.drawImage(img6, 270, 200, 100, 100, null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEvenOddRules() {
|
||||||
|
|
||||||
|
exportGraphic("simple", "evenOdd", gfx -> {
|
||||||
|
gfx.setColor(Color.YELLOW);
|
||||||
|
gfx.fillPolygon(new int[]{80, 129, 0, 160, 31},
|
||||||
|
new int[]{0, 152, 58, 58, 152}, 5);
|
||||||
|
Path2D.Double s = new Path2D.Double();
|
||||||
|
s.moveTo(80, 0);
|
||||||
|
s.lineTo(129, 152);
|
||||||
|
s.lineTo(0, 58);
|
||||||
|
s.lineTo(160, 58);
|
||||||
|
s.lineTo(31, 152);
|
||||||
|
s.setWindingRule(Path2D.WIND_EVEN_ODD);
|
||||||
|
gfx.setColor(Color.BLUE);
|
||||||
|
gfx.translate(200, 0);
|
||||||
|
gfx.fill(s);
|
||||||
|
s.setWindingRule(Path2D.WIND_NON_ZERO);
|
||||||
|
gfx.setColor(Color.GREEN);
|
||||||
|
gfx.translate(0, 200);
|
||||||
|
gfx.fill(s);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleGraphics2d() {
|
||||||
|
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("JPEG");
|
||||||
|
while (readers.hasNext()) {
|
||||||
|
readers.next();
|
||||||
|
}
|
||||||
|
exportGraphic("simple", "simple", gfx -> {
|
||||||
|
BufferedImage imgColorTest = ImageIO
|
||||||
|
.read(PdfBoxGraphics2dTest.class.getResourceAsStream("colortest.png"));
|
||||||
|
BufferedImage img2 = ImageIO
|
||||||
|
.read(PdfBoxGraphics2dTest.class.getResourceAsStream("pixeltest.png"));
|
||||||
|
BufferedImage img3 = ImageIO
|
||||||
|
.read(PdfBoxGraphics2dTest.class.getResourceAsStream("Rose-ProPhoto.jpg"));
|
||||||
|
BufferedImage img4 = ImageIO
|
||||||
|
.read(PdfBoxGraphics2dTest.class.getResourceAsStream("Italy-P3.jpg"));
|
||||||
|
|
||||||
|
gfx.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||||
|
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||||
|
gfx.drawImage(imgColorTest, 70, 50, 100, 50, null);
|
||||||
|
|
||||||
|
gfx.drawImage(img3, 30, 200, 75, 50, null);
|
||||||
|
gfx.drawImage(img3, 110, 200, 50, 50, null);
|
||||||
|
gfx.drawImage(img4, 170, 10, 60, 40, null);
|
||||||
|
|
||||||
|
gfx.setColor(Color.YELLOW);
|
||||||
|
gfx.drawRect(20, 20, 100, 100);
|
||||||
|
gfx.setColor(Color.GREEN);
|
||||||
|
gfx.fillRect(10, 10, 50, 50);
|
||||||
|
|
||||||
|
gfx.setColor(new CMYKColor(255, 128, 0, 128, 200));
|
||||||
|
gfx.drawString("Hello World!", 30, 120);
|
||||||
|
gfx.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||||
|
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
|
||||||
|
gfx.drawImage(img2, 30, 50, 50, 50, null);
|
||||||
|
|
||||||
|
Font font = new Font("SansSerif", Font.PLAIN, 30);
|
||||||
|
Font font2 = Font.createFont(Font.TRUETYPE_FONT,
|
||||||
|
PdfBoxGraphics2dTest.class.getResourceAsStream("DejaVuSerifCondensed.ttf"))
|
||||||
|
.deriveFont(20f);
|
||||||
|
final String words = "Valour fate kinship darkness";
|
||||||
|
|
||||||
|
AttributedString as1 = new AttributedString(words);
|
||||||
|
as1.addAttribute(TextAttribute.FONT, font);
|
||||||
|
|
||||||
|
Rectangle2D valour = font2.getStringBounds("Valour", gfx.getFontRenderContext());
|
||||||
|
GradientPaint gp = new GradientPaint(10.0f, 25.0f, Color.blue,
|
||||||
|
(float) valour.getWidth(), (float) valour.getHeight(), Color.red);
|
||||||
|
|
||||||
|
gfx.setColor(Color.GREEN);
|
||||||
|
as1.addAttribute(TextAttribute.FOREGROUND, gp, 0, 6);
|
||||||
|
as1.addAttribute(TextAttribute.KERNING, TextAttribute.KERNING_ON, 0, 6);
|
||||||
|
as1.addAttribute(TextAttribute.FONT, font2, 0, 6);
|
||||||
|
as1.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON, 7, 11);
|
||||||
|
as1.addAttribute(TextAttribute.BACKGROUND, Color.LIGHT_GRAY, 12, 19);
|
||||||
|
as1.addAttribute(TextAttribute.FONT, font2, 20, 28);
|
||||||
|
as1.addAttribute(TextAttribute.LIGATURES, TextAttribute.LIGATURES_ON, 20, 28);
|
||||||
|
as1.addAttribute(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON, 20,
|
||||||
|
28);
|
||||||
|
|
||||||
|
gfx.drawString(as1.getIterator(), 15, 160);
|
||||||
|
|
||||||
|
// Hello World - in arabic and hebrew
|
||||||
|
Font font3 = new Font("SansSerif", Font.PLAIN, 40);
|
||||||
|
gfx.setFont(font3);
|
||||||
|
gfx.setColor(Color.BLACK);
|
||||||
|
gfx.drawString("مرحبا بالعالم", 200, 100);
|
||||||
|
gfx.setPaint(
|
||||||
|
new TexturePaint(imgColorTest, new Rectangle2D.Float(5f, 7f, 100f, 20f)));
|
||||||
|
gfx.drawString("مرحبا بالعالم", 200, 250);
|
||||||
|
gfx.drawString("שלום עולם", 200, 200);
|
||||||
|
|
||||||
|
gfx.setClip(new Ellipse2D.Float(360, 360, 60, 80));
|
||||||
|
gfx.fillRect(300, 300, 100, 100);
|
||||||
|
gfx.setClip(null);
|
||||||
|
gfx.fillRect(360, 360, 10, 10);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,213 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.color.PDPattern;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImage;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
|
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||||
|
import org.apache.pdfbox.rendering.PageDrawer;
|
||||||
|
import org.apache.pdfbox.rendering.PageDrawerParameters;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.awt.BasicStroke;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Paint;
|
||||||
|
import java.awt.Shape;
|
||||||
|
import java.awt.Stroke;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class PdfRerenderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPDFRerender() throws IOException {
|
||||||
|
rerenderPDF("heart.pdf");
|
||||||
|
rerenderPDF("barChart.pdf");
|
||||||
|
rerenderPDF("compuserver_msn_Ford_Focus.pdf");
|
||||||
|
rerenderPDF("patternfill.pdf");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleRerender() throws IOException {
|
||||||
|
simplePDFRerender("antonio_sample.pdf");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleRerenderAsBitmap() throws IOException {
|
||||||
|
simplePDFRerenderAsBitmap("antonio_sample.pdf", false);
|
||||||
|
simplePDFRerenderAsBitmap("antonio_sample.pdf", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simplePDFRerenderAsBitmap(String name, boolean lossless) throws IOException {
|
||||||
|
File parentDir = new File("build/test");
|
||||||
|
parentDir.mkdirs();
|
||||||
|
PDDocument document = new PDDocument();
|
||||||
|
PDDocument sourceDoc = PDDocument.load(PdfRerenderTest.class.getResourceAsStream(name));
|
||||||
|
|
||||||
|
for (PDPage sourcePage : sourceDoc.getPages()) {
|
||||||
|
PDRectangle mediaBox = sourcePage.getMediaBox();
|
||||||
|
PDPage rerenderedPage = new PDPage(mediaBox);
|
||||||
|
document.addPage(rerenderedPage);
|
||||||
|
try (PDPageContentStream cb = new PDPageContentStream(document, rerenderedPage)) {
|
||||||
|
|
||||||
|
PDFRenderer pdfRenderer = new PDFRenderer(sourceDoc);
|
||||||
|
float targetDPI = 300;
|
||||||
|
BufferedImage bufferedImage = pdfRenderer
|
||||||
|
.renderImage(sourceDoc.getPages().indexOf(sourcePage), targetDPI / 72.0f);
|
||||||
|
|
||||||
|
PDImageXObject image;
|
||||||
|
if (lossless)
|
||||||
|
image = LosslessFactory.createFromImage(document, bufferedImage);
|
||||||
|
else
|
||||||
|
image = JPEGFactory.createFromImage(document, bufferedImage, 0.7f);
|
||||||
|
|
||||||
|
cb.drawImage(image, 0, 0, mediaBox.getWidth(), mediaBox.getHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.save(new File(parentDir, "simple_bitmap_" + (lossless ? "" : "_jpeg_") + name));
|
||||||
|
document.close();
|
||||||
|
sourceDoc.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void simplePDFRerender(String name) throws IOException {
|
||||||
|
File parentDir = new File("build/test");
|
||||||
|
parentDir.mkdirs();
|
||||||
|
|
||||||
|
PDDocument document = new PDDocument();
|
||||||
|
PDDocument sourceDoc = PDDocument.load(PdfRerenderTest.class.getResourceAsStream(name));
|
||||||
|
|
||||||
|
for (PDPage sourcePage : sourceDoc.getPages()) {
|
||||||
|
PDPage rerenderedPage = new PDPage(sourcePage.getMediaBox());
|
||||||
|
document.addPage(rerenderedPage);
|
||||||
|
try (PDPageContentStream cb = new PDPageContentStream(document, rerenderedPage)) {
|
||||||
|
PdfBoxGraphics2D gfx = new PdfBoxGraphics2D(document, sourcePage.getMediaBox());
|
||||||
|
PDFRenderer pdfRenderer = new PDFRenderer(sourceDoc);
|
||||||
|
pdfRenderer.renderPageToGraphics(sourceDoc.getPages().indexOf(sourcePage), gfx);
|
||||||
|
gfx.dispose();
|
||||||
|
|
||||||
|
PDFormXObject xFormObject = gfx.getXFormObject();
|
||||||
|
cb.drawForm(xFormObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.save(new File(parentDir, "simple_rerender" + name));
|
||||||
|
document.close();
|
||||||
|
sourceDoc.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rerenderPDF(String name) throws IOException {
|
||||||
|
File parentDir = new File("build/test");
|
||||||
|
// noinspection ResultOfMethodCallIgnored
|
||||||
|
parentDir.mkdirs();
|
||||||
|
|
||||||
|
PDDocument document = new PDDocument();
|
||||||
|
PDDocument sourceDoc = PDDocument.load(PdfRerenderTest.class.getResourceAsStream(name));
|
||||||
|
|
||||||
|
for (PDPage sourcePage : sourceDoc.getPages()) {
|
||||||
|
PDPage rerenderedPage = new PDPage(sourcePage.getMediaBox());
|
||||||
|
document.addPage(rerenderedPage);
|
||||||
|
try (PDPageContentStream cb = new PDPageContentStream(document, rerenderedPage)) {
|
||||||
|
PdfBoxGraphics2D gfx = new PdfBoxGraphics2D(document, sourcePage.getMediaBox());
|
||||||
|
|
||||||
|
// Do overfill for red with a transparent green
|
||||||
|
gfx.setDrawControl(new DefaultDrawControl() {
|
||||||
|
boolean insideOwnDraw = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterShapeFill(Shape shape, IDrawControlEnv env) {
|
||||||
|
afterShapeDraw(shape, env);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterShapeDraw(Shape shape, IDrawControlEnv env) {
|
||||||
|
if (insideOwnDraw)
|
||||||
|
return;
|
||||||
|
insideOwnDraw = true;
|
||||||
|
Paint paint = env.getPaint();
|
||||||
|
if (paint instanceof Color) {
|
||||||
|
if (paint.equals(Color.RED)) {
|
||||||
|
// We overfill with black a little bit
|
||||||
|
PdfBoxGraphics2D graphics = env.getGraphics();
|
||||||
|
Stroke prevStroke = graphics.getStroke();
|
||||||
|
float additinalStrokeWidth = 1f;
|
||||||
|
if (prevStroke instanceof BasicStroke) {
|
||||||
|
BasicStroke basicStroke = ((BasicStroke) prevStroke);
|
||||||
|
graphics.setStroke(new BasicStroke(
|
||||||
|
basicStroke.getLineWidth() + additinalStrokeWidth,
|
||||||
|
basicStroke.getEndCap(), basicStroke.getLineJoin(),
|
||||||
|
basicStroke.getMiterLimit(), basicStroke.getDashArray(),
|
||||||
|
basicStroke.getDashPhase()));
|
||||||
|
} else {
|
||||||
|
graphics.setStroke(new BasicStroke(additinalStrokeWidth));
|
||||||
|
}
|
||||||
|
graphics.setPaint(new Color(0, 255, 0, 128));
|
||||||
|
graphics.draw(shape);
|
||||||
|
|
||||||
|
graphics.setPaint(paint);
|
||||||
|
graphics.setStroke(prevStroke);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
insideOwnDraw = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
PDFRenderer pdfRenderer = new PDFRenderer(sourceDoc) {
|
||||||
|
@Override
|
||||||
|
protected PageDrawer createPageDrawer(PageDrawerParameters parameters)
|
||||||
|
throws IOException {
|
||||||
|
return new PageDrawer(parameters) {
|
||||||
|
@Override
|
||||||
|
protected Paint getPaint(PDColor color) throws IOException {
|
||||||
|
PDColorSpace colorSpace = color.getColorSpace();
|
||||||
|
|
||||||
|
// We always must handle patterns recursive
|
||||||
|
if (colorSpace instanceof PDPattern)
|
||||||
|
return super.getPaint(color);
|
||||||
|
|
||||||
|
// Now our special logic
|
||||||
|
if (colorSpace instanceof PDDeviceRGB) {
|
||||||
|
float[] components = color.getComponents();
|
||||||
|
boolean allBlack = true;
|
||||||
|
for (float f : components) {
|
||||||
|
if (f > 0.0) {
|
||||||
|
allBlack = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (allBlack) {
|
||||||
|
return new CMYKColor(1f, 0.0f, 0.2f, 0.1f, 128);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// All other colors just stay the same...
|
||||||
|
return super.getPaint(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawImage(PDImage pdImage) {
|
||||||
|
// We dont like images, just skip them all
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pdfRenderer.renderPageToGraphics(sourceDoc.getPages().indexOf(sourcePage), gfx);
|
||||||
|
gfx.dispose();
|
||||||
|
|
||||||
|
PDFormXObject xFormObject = gfx.getXFormObject();
|
||||||
|
cb.drawForm(xFormObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.save(new File(parentDir, "rerendered_" + name));
|
||||||
|
document.close();
|
||||||
|
sourceDoc.close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
package org.xbib.graphics.graphics2d.pdfbox;
|
||||||
|
|
||||||
|
import org.apache.batik.anim.dom.SAXSVGDocumentFactory;
|
||||||
|
import org.apache.batik.bridge.BridgeContext;
|
||||||
|
import org.apache.batik.bridge.DocumentLoader;
|
||||||
|
import org.apache.batik.bridge.GVTBuilder;
|
||||||
|
import org.apache.batik.bridge.UserAgent;
|
||||||
|
import org.apache.batik.bridge.UserAgentAdapter;
|
||||||
|
import org.apache.batik.gvt.GraphicsNode;
|
||||||
|
import org.apache.batik.util.XMLResourceDescriptor;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||||
|
import org.apache.pdfbox.util.Matrix;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.color.ICC_Profile;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class RenderSVGsTest extends PdfBoxGraphics2DTestBase {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSVGs() throws IOException {
|
||||||
|
renderSVG("barChart.svg", 0.45);
|
||||||
|
renderSVG("gump-bench.svg", 1);
|
||||||
|
renderSVG("json.svg", 150);
|
||||||
|
renderSVG("heart.svg", 200);
|
||||||
|
renderSVG("displayWebStats.svg", 200);
|
||||||
|
renderSVG("compuserver_msn_Ford_Focus.svg", 0.7);
|
||||||
|
renderSVG("watermark.svg", 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void renderFailureCases() throws IOException {
|
||||||
|
// renderSVG("openhtml_536.svg", 1);
|
||||||
|
renderSVG("openhtml_538_gradient.svg", .5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGradientSVGEmulateObjectBoundingBox() throws IOException {
|
||||||
|
renderSVG("long-gradient.svg", 0.55);
|
||||||
|
renderSVG("tall-gradient.svg", 0.33);
|
||||||
|
renderSVG("near-square-gradient.svg", 0.30);
|
||||||
|
renderSVG("square-gradient.svg", 0.55);
|
||||||
|
renderSVG("tall-gradient-downward-slope.svg", 0.33);
|
||||||
|
renderSVG("horizontal-gradient.svg", 0.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSVGinCMYKColorspace() throws IOException {
|
||||||
|
renderSVGCMYK("atmospheric-composiition.svg", 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderSVG(String name, final double scale) throws IOException {
|
||||||
|
String uri = RenderSVGsTest.class.getResource(name).toString();
|
||||||
|
|
||||||
|
// create the document
|
||||||
|
String parser = XMLResourceDescriptor.getXMLParserClassName();
|
||||||
|
SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(parser);
|
||||||
|
Document document = f.createDocument(uri, RenderSVGsTest.class.getResourceAsStream(name));
|
||||||
|
|
||||||
|
// create the GVT
|
||||||
|
UserAgent userAgent = new UserAgentAdapter();
|
||||||
|
DocumentLoader loader = new DocumentLoader(userAgent);
|
||||||
|
BridgeContext bctx = new BridgeContext(userAgent, loader);
|
||||||
|
bctx.setDynamicState(BridgeContext.STATIC);
|
||||||
|
GVTBuilder builder = new GVTBuilder();
|
||||||
|
final GraphicsNode gvtRoot = builder.build(bctx, document);
|
||||||
|
|
||||||
|
this.exportGraphic("svg", name.replace(".svg", ""), new GraphicsExporter() {
|
||||||
|
@Override
|
||||||
|
public void draw(Graphics2D gfx) {
|
||||||
|
gfx.scale(scale, scale);
|
||||||
|
gvtRoot.paint(gfx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderSVGCMYK(String name, final double scale) throws IOException {
|
||||||
|
String uri = RenderSVGsTest.class.getResource(name).toString();
|
||||||
|
|
||||||
|
// create the document
|
||||||
|
String parser = XMLResourceDescriptor.getXMLParserClassName();
|
||||||
|
SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(parser);
|
||||||
|
Document document = f.createDocument(uri, RenderSVGsTest.class.getResourceAsStream(name));
|
||||||
|
|
||||||
|
// create the GVT
|
||||||
|
UserAgent userAgent = new UserAgentAdapter();
|
||||||
|
DocumentLoader loader = new DocumentLoader(userAgent);
|
||||||
|
BridgeContext bctx = new BridgeContext(userAgent, loader);
|
||||||
|
bctx.setDynamicState(BridgeContext.STATIC);
|
||||||
|
GVTBuilder builder = new GVTBuilder();
|
||||||
|
final GraphicsNode gvtRoot = builder.build(bctx, document);
|
||||||
|
|
||||||
|
PDDocument pdfDocument = new PDDocument();
|
||||||
|
|
||||||
|
File parentDir = new File("build/test/svg");
|
||||||
|
parentDir.mkdirs();
|
||||||
|
|
||||||
|
PDPage page = new PDPage(PDRectangle.A4);
|
||||||
|
pdfDocument.addPage(page);
|
||||||
|
|
||||||
|
PDPageContentStream contentStream = new PDPageContentStream(pdfDocument, page);
|
||||||
|
|
||||||
|
PdfBoxGraphics2D pdfBoxGraphics2D = new PdfBoxGraphics2D(pdfDocument, 400, 400);
|
||||||
|
|
||||||
|
ICC_Profile icc_profile = ICC_Profile.getInstance(PDDocument.class.getResourceAsStream(
|
||||||
|
"/org/apache/pdfbox/resources/icc/ISOcoated_v2_300_bas.icc"));
|
||||||
|
DefaultColorMapper colorMapper = new RGBtoCMYKColorMapper(icc_profile, pdfDocument);
|
||||||
|
pdfBoxGraphics2D.setColorMapper(colorMapper);
|
||||||
|
|
||||||
|
FontTextDrawer fontTextDrawer = null;
|
||||||
|
contentStream.beginText();
|
||||||
|
contentStream.setStrokingColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
contentStream.setNonStrokingColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
contentStream.setFont(PDType1Font.HELVETICA_BOLD, 15);
|
||||||
|
contentStream.setTextMatrix(Matrix.getTranslateInstance(10, 800));
|
||||||
|
contentStream.showText("Mode: CMYK colorspace");
|
||||||
|
contentStream.endText();
|
||||||
|
fontTextDrawer = new DefaultFontTextDrawer();
|
||||||
|
|
||||||
|
pdfBoxGraphics2D.setFontTextDrawer(fontTextDrawer);
|
||||||
|
|
||||||
|
pdfBoxGraphics2D.scale(scale, scale);
|
||||||
|
gvtRoot.paint(pdfBoxGraphics2D);
|
||||||
|
pdfBoxGraphics2D.dispose();
|
||||||
|
|
||||||
|
PDFormXObject appearanceStream = pdfBoxGraphics2D.getXFormObject();
|
||||||
|
Matrix matrix = new Matrix();
|
||||||
|
matrix.translate(0, 300);
|
||||||
|
contentStream.transform(matrix);
|
||||||
|
contentStream.drawForm(appearanceStream);
|
||||||
|
|
||||||
|
contentStream.close();
|
||||||
|
|
||||||
|
String baseName = name.substring(0, name.lastIndexOf('.'));
|
||||||
|
pdfDocument.save(new File(parentDir, baseName + ".pdf"));
|
||||||
|
pdfDocument.close();
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 633 B |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 232 KiB |
After Width: | Height: | Size: 208 KiB |
|
@ -0,0 +1,43 @@
|
||||||
|
Copyright (c) 2011-12, vernon adams (vern@newtypography.co.uk), with Reserved Font Names 'Antonio'
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
After Width: | Height: | Size: 8.4 KiB |
|
@ -0,0 +1,119 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
contributor license agreements. See the NOTICE file distributed with
|
||||||
|
this work for additional information regarding copyright ownership.
|
||||||
|
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
(the "License"); you may not use this file except in compliance with
|
||||||
|
the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<!-- ========================================================================= -->
|
||||||
|
<!-- Illustrates how SVG can be used for high quality graphs. -->
|
||||||
|
<!-- -->
|
||||||
|
<!-- @author vincent.hardy@eng.sun.com -->
|
||||||
|
<!-- @author neeme.praks@one.lv -->
|
||||||
|
<!-- @version $Id$ -->
|
||||||
|
<!-- ========================================================================= -->
|
||||||
|
|
||||||
|
<?xml-stylesheet type="text/css" href="tests/resources/style/test.css" ?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="body" width="450" height="500" xml:space="preserve" viewBox="0 0 450 500">
|
||||||
|
<title>Bar Chart</title>
|
||||||
|
|
||||||
|
<g id="barChart" transform="translate(40, 100)" fill-rule="evenodd" clip-rule="evenodd" stroke="none" class="legend"
|
||||||
|
stroke-width="1" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" style="text-anchor:start">
|
||||||
|
|
||||||
|
<g id="GridAndLegend" style="stroke:none;">
|
||||||
|
<g stroke="black">
|
||||||
|
|
||||||
|
<!-- "floor" and "wall" -->
|
||||||
|
<path fill="lightgray" stroke="darkgray" d="M 27,240 l 15,-15 v -224 l -15,15" />
|
||||||
|
<path fill="lightgray" stroke="darkgray" d="M 41,225 v -224 h 316 v 224" />
|
||||||
|
<path fill="darkgray" stroke="none" d="M 27,240 l 15,-15 h 316 l -15,15" />
|
||||||
|
|
||||||
|
<!-- axis lines -->
|
||||||
|
<path d="M 27,240 h 316"/>
|
||||||
|
<path d="M 27,240 v -224"/>
|
||||||
|
|
||||||
|
<!-- value axis major gridlines -->
|
||||||
|
<g style="fill:none;">
|
||||||
|
<path d="M 27,202 l 15,-15 h 316" />
|
||||||
|
<path d="M 27,165 l 15,-15 h 316" />
|
||||||
|
<path d="M 27,127 l 15,-15 h 316" />
|
||||||
|
<path d="M 27, 90 l 15,-15 h 316" />
|
||||||
|
<path d="M 27, 53 l 15,-15 h 316" />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- category axis major ticks -->
|
||||||
|
<path d="M 27,245 v -5"/>
|
||||||
|
<path d="M 106,245 v -5"/>
|
||||||
|
<path d="M 185,245 v -5"/>
|
||||||
|
<path d="M 264,245 v -5"/>
|
||||||
|
|
||||||
|
<!-- value axis minor ticks -->
|
||||||
|
<path d="M 22,240 h 5"/>
|
||||||
|
<path d="M 22,202 h 5"/>
|
||||||
|
<path d="M 22,165 h 5"/>
|
||||||
|
<path d="M 22,127 h 5"/>
|
||||||
|
<path d="M 22, 90 h 5"/>
|
||||||
|
<path d="M 22, 53 h 5"/>
|
||||||
|
<path d="M 22, 15 h 5"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<text transform="matrix(1 0 0 1 54 256)">Shoe</text>
|
||||||
|
<text transform="matrix(1 0 0 1 142 256)">Car</text>
|
||||||
|
<text transform="matrix(1 0 0 1 211 256)">Travel</text>
|
||||||
|
<text transform="matrix(1 0 0 1 285 256)">Computer</text>
|
||||||
|
|
||||||
|
<text transform="matrix(1 0 0 1 13 247)"><tspan x="0" y="0">0</tspan></text>
|
||||||
|
<text transform="matrix(1 0 0 1 6 209)"><tspan x="0" y="0">10</tspan></text>
|
||||||
|
<text transform="matrix(1 0 0 1 6 171)"><tspan x="0" y="0">20</tspan></text>
|
||||||
|
<text transform="matrix(1 0 0 1 6 134)"><tspan x="0" y="0">30</tspan></text>
|
||||||
|
<text transform="matrix(1 0 0 1 6 96)"><tspan x="0" y="0">40</tspan></text>
|
||||||
|
<text transform="matrix(1 0 0 1 6 60)"><tspan x="0" y="0">50</tspan></text>
|
||||||
|
<text transform="matrix(1 0 0 1 6 22)"><tspan x="0" y="0">60</tspan></text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g id="ShoeBar">
|
||||||
|
<path style="fill:#8686E0;" d="M 86,240 v -37 l 15 -15 v 37 l -15,15 z"/>
|
||||||
|
<path style="fill:#5B5B97;" d="M 86,203 h -39 l 15 -15 h 39 l -15,15 z"/>
|
||||||
|
<path style="fill:#7575C3;" d="M 47,203 v 37 h 39 v -37 H 47 z"/>
|
||||||
|
</g>
|
||||||
|
<g id="CarBar">
|
||||||
|
<path style="fill:#8686E0;" d="M 165,240 v -74 l 15 -15 v 74 l -15,15 z"/>
|
||||||
|
<path style="fill:#5B5B97;" d="M 165,166 h -39 l 15 -15 h 39 l -15,15 z"/>
|
||||||
|
<path style="fill:#7575C3;" d="M 126,166 v 74 h 39 v -74 h -39 z"/>
|
||||||
|
</g>
|
||||||
|
<g id="TravelBar">
|
||||||
|
<path style="fill:#8686E0;" d="M 244,240 v -37 l 15 -15 v 37 l -15,15 z"/>
|
||||||
|
<path style="fill:#5B5B97;" d="M 244,203 h -39 l 15 -15 h 39 l -15,15 z"/>
|
||||||
|
<path style="fill:#7575C3;" d="M 205,203 v 37 h 39 v -37 h -39 z"/>
|
||||||
|
</g>
|
||||||
|
<g id="ComputerBar">
|
||||||
|
<path style="fill:#8686E0;" d="M 323,240 v -224 l 15 -15 v 224 l -15,15 z"/>
|
||||||
|
<path style="fill:#5B5B97;" d="M 323, 16 h -39 l 15 -15 h 39 l -15,15 z"/>
|
||||||
|
<path style="fill:#7575C3;" d="M 284, 16 v 224 h 39 v -224 h -39 z"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- ============================================================= -->
|
||||||
|
<!-- Batik sample mark -->
|
||||||
|
<!-- ============================================================= -->
|
||||||
|
<!--
|
||||||
|
<use xlink:href="batikLogo.svg#Batik_Tag_Box" />
|
||||||
|
-->
|
||||||
|
|
||||||
|
</svg>
|
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 318 KiB |
After Width: | Height: | Size: 84 KiB |
|
@ -0,0 +1,72 @@
|
||||||
|
<!-- Taken from https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/ -->
|
||||||
|
<svg height="100" width="200" viewBox="190 40 10 200" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
|
||||||
|
<title>Bench, Forest Gump</title>
|
||||||
|
|
||||||
|
<defs>
|
||||||
|
<g id="post">
|
||||||
|
<rect width="20" height="150" fill="rgb(208,196,192)"/>
|
||||||
|
<path d="M0,0 l3-3 h20 l-3,3 Z" fill="rgb(156,147,144)" />
|
||||||
|
<path d="M20,0 l3-3 v150 l-3,3 Z" fill="rgb(104,98,96)" />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g id="foot">
|
||||||
|
<rect width="20" height="10" fill="rgb(208,196,192)"/>
|
||||||
|
<path d="M0,0 l30-30 h20 l-30,30 Z" fill="rgb(156,147,144)" />
|
||||||
|
<path d="M20,0 l30-30 v10 l-30,30 Z" fill="rgb(104,98,96)" />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g id="back">
|
||||||
|
<rect width="300" height="10" fill="rgb(120,60,30)" />
|
||||||
|
<path d="M0,0 l3-3 h300 l-3,3 Z" fill="rgb(90,45,20)" />
|
||||||
|
<path d="M300,0 l3-3 v10 l-3,3 Z" fill="rgb(60,30,10)" />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g id="seat">
|
||||||
|
<rect width="300" height="5" fill="rgb(120,60,30)" />
|
||||||
|
<path d="M0,0 l5-5 h300 l-5,5 Z" fill="rgb(90,45,20)" />
|
||||||
|
<path d="M300,0 l5-5 v5 l-5,5 Z" fill="rgb(60,30,10)" />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g id="box">
|
||||||
|
<title>Box of Chocolates</title>
|
||||||
|
|
||||||
|
<rect width="45" height="15" fill="rgb(240,240,255)"/>
|
||||||
|
<path d="M0,0 l25-25 h45 l-25,25 Z" fill="rgb(220,220,240)" />
|
||||||
|
<path d="M45,0 l25-25 v15 l-25,25 Z" fill="rgb(200,200,220)" />
|
||||||
|
|
||||||
|
<rect x="20" width="5" height="15" fill="rgb(255,0,0)"/>
|
||||||
|
<path d="M20,0 l25-25 h5 l-25,25 Z" fill="rgb(255,32,32)" />
|
||||||
|
<path d="M10,-10 l5-5 h45 l-5,5 Z" fill="rgb(255,32,32)" />
|
||||||
|
<path d="M55,-10 l5-5 v15 l-5,5 Z" fill="rgb(255,64,64)" />
|
||||||
|
|
||||||
|
<path d="M35,-10 s-40,-30 0,-6 M35,-10 s60,-30 0,-6"
|
||||||
|
fill="none" stroke="red" stroke-width="2" />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<g>
|
||||||
|
<title>Bench</title>
|
||||||
|
<use xlink:href="#foot" x="56.5" y="213.5"/>
|
||||||
|
<use xlink:href="#foot" x="276.5" y="213.5"/>
|
||||||
|
|
||||||
|
<use xlink:href="#post" x="70" y="50"/>
|
||||||
|
<use xlink:href="#post" x="290" y="50"/>
|
||||||
|
|
||||||
|
<use xlink:href="#foot" x="40" y="160"/>
|
||||||
|
<use xlink:href="#foot" x="260" y="160"/>
|
||||||
|
|
||||||
|
<use xlink:href="#back" x="40" y="60"/>
|
||||||
|
<use xlink:href="#back" x="40" y="80"/>
|
||||||
|
<use xlink:href="#back" x="40" y="100"/>
|
||||||
|
|
||||||
|
<use xlink:href="#seat" x="40" y="140"/>
|
||||||
|
<use xlink:href="#seat" x="30" y="148"/>
|
||||||
|
<use xlink:href="#seat" x="20" y="156"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g>
|
||||||
|
<use xlink:href="#box" x="225" y="140"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
|
@ -0,0 +1,4 @@
|
||||||
|
<!-- Taken from https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/ -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<path d="M50,30c9-22 42-24 48,0c5,40-40,40-48,65c-8-25-54-25-48-65c 6-24 39-22 48,0 z" fill="#F00" stroke="#000"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 262 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg version="1.1" class="highcharts-root" style="font-family:Arial;font-size:13px;color:#595959;font-weight:normal;font-style:normal;text-decoration:none;fill:#595959;" xmlns="http://www.w3.org/2000/svg" width="613" height="380" viewBox="0 0 613 380"><desc>Created with Highcharts x.x.x</desc><defs><linearGradient x1="0" y1="0" x2="1" y2="0" id="highcharts-ryky3gk-804"><stop offset="0" stop-color="#7ace05" stop-opacity="1"></stop><stop offset="0.5" stop-color="#ffde0f" stop-opacity="1"></stop><stop offset="1" stop-color="#ee2744" stop-opacity="1"></stop></linearGradient><clipPath id="highcharts-ryky3gk-805"><rect x="0" y="0" width="613" height="380" fill="none"></rect></clipPath><clipPath id="highcharts-ryky3gk-806"><rect x="0" y="0" width="613" height="380" fill="none"></rect></clipPath><pattern id="highcharts-default-pattern-0" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 0 L 10 10 M 9 -1 L 11 1 M -1 9 L 1 11" stroke="#7cb5ec" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-1" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 10 L 10 0 M -1 1 L 1 -1 M 9 11 L 11 9" stroke="#434348" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-2" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 3 0 L 3 10 M 8 0 L 8 10" stroke="#90ed7d" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-3" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 3 L 10 3 M 0 8 L 10 8" stroke="#f7a35c" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-4" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 3 L 5 3 L 5 0 M 5 10 L 5 7 L 10 7" stroke="#8085e9" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-5" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 3 3 L 8 3 L 8 8 L 3 8 Z" stroke="#f15c80" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-6" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 5 5 m -4 0 a 4 4 0 1 1 8 0 a 4 4 0 1 1 -8 0" stroke="#e4d354" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-7" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 10 3 L 5 3 L 5 0 M 5 10 L 5 7 L 0 7" stroke="#2b908f" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-8" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 2 5 L 5 2 L 8 5 L 5 8 Z" stroke="#f45b5b" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-9" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 0 L 5 10 L 10 0" stroke="#91e8e1" stroke-width="2"></path></pattern></defs><rect fill="rgb(0,0,0)" class="highcharts-background" x="0" y="0" width="613" height="380" rx="0" ry="0" fill-opacity="0.0"></rect><rect fill="url(#highcharts-ryky3gk-804)" class="highcharts-plot-background" x="0" y="0" width="613" height="380"></rect><g class="highcharts-label highcharts-no-data" transform="translate(247,180)"><text x="3" data-z-index="1" style="font-size:12px;font-weight:bold;color:#666666;fill:#666666;" y="15"><tspan>No data to display</tspan></text></g><g class="highcharts-pane-group" data-z-index="0"></g><rect fill="none" class="highcharts-plot-border" data-z-index="1" x="0" y="0" width="613" height="380"></rect><g class="highcharts-series-group" data-z-index="3"></g><text x="307" text-anchor="middle" class="highcharts-title" data-z-index="4" style="color:#595959;font-size:16px;font-family:Arial;font-weight:normal;font-style:normal;text-decoration:none;fill:#595959;" y="12"></text><text x="307" text-anchor="middle" class="highcharts-subtitle" data-z-index="4" style="color:#666666;fill:#666666;" y="15"></text></svg>
|
After Width: | Height: | Size: 3.8 KiB |
|
@ -0,0 +1,6 @@
|
||||||
|
<!-- Taken from https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/ -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 102">
|
||||||
|
<radialGradient id="jsongrad" cx="65" cy="90" r="100" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#EEF"/><stop offset="1"/></radialGradient>
|
||||||
|
<path d="M61,02 A 49,49 0,0,0 39,98 C 9,79 10,24 45,25 C 72,24 65,75 50,75 C 93,79 91,21 62,02" id="jsonswirl" fill="url(#jsongrad)"/>
|
||||||
|
<use xlink:href="#jsonswirl" transform="rotate(180 50,50)"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 547 B |
|
@ -0,0 +1,58 @@
|
||||||
|
<svg version="1.1" class="highcharts-root" style="font-family:Arial;font-size:13px;color:#595959;font-weight:normal;font-style:normal;text-decoration:none;fill:#595959;" xmlns="http://www.w3.org/2000/svg" width="700" height="100" viewBox="0 0 700 100">
|
||||||
|
<desc>Created with Highcharts x.x.x</desc>
|
||||||
|
<defs>
|
||||||
|
<linearGradient x1="0" y1="1" x2="1" y2="0" id="highcharts-pjta544-5786">
|
||||||
|
<stop offset="0" stop-color="#000000" stop-opacity="1"></stop>
|
||||||
|
<stop offset="0.5" stop-color="#ffffff" stop-opacity="1"></stop>
|
||||||
|
<stop offset="1" stop-color="#000000" stop-opacity="1"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
<clipPath id="highcharts-pjta544-5787">
|
||||||
|
<rect x="0" y="0" width="700" height="100" fill="none"></rect>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="highcharts-pjta544-5788">
|
||||||
|
<rect x="0" y="0" width="700" height="100" fill="none"></rect>
|
||||||
|
</clipPath>
|
||||||
|
<pattern id="highcharts-default-pattern-0" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0">
|
||||||
|
<path d="M 0 0 L 10 10 M 9 -1 L 11 1 M -1 9 L 1 11" stroke="#7cb5ec" stroke-width="2"></path>
|
||||||
|
</pattern>
|
||||||
|
<pattern id="highcharts-default-pattern-1" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0">
|
||||||
|
<path d="M 0 10 L 10 0 M -1 1 L 1 -1 M 9 11 L 11 9" stroke="#434348" stroke-width="2"></path>
|
||||||
|
</pattern>
|
||||||
|
<pattern id="highcharts-default-pattern-2" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0">
|
||||||
|
<path d="M 3 0 L 3 10 M 8 0 L 8 10" stroke="#90ed7d" stroke-width="2"></path>
|
||||||
|
</pattern>
|
||||||
|
<pattern id="highcharts-default-pattern-3" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0">
|
||||||
|
<path d="M 0 3 L 10 3 M 0 8 L 10 8" stroke="#f7a35c" stroke-width="2"></path>
|
||||||
|
</pattern>
|
||||||
|
<pattern id="highcharts-default-pattern-4" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0">
|
||||||
|
<path d="M 0 3 L 5 3 L 5 0 M 5 10 L 5 7 L 10 7" stroke="#8085e9" stroke-width="2"></path>
|
||||||
|
</pattern>
|
||||||
|
<pattern id="highcharts-default-pattern-5" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0">
|
||||||
|
<path d="M 3 3 L 8 3 L 8 8 L 3 8 Z" stroke="#f15c80" stroke-width="2"></path>
|
||||||
|
</pattern>
|
||||||
|
<pattern id="highcharts-default-pattern-6" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0">
|
||||||
|
<path d="M 5 5 m -4 0 a 4 4 0 1 1 8 0 a 4 4 0 1 1 -8 0" stroke="#e4d354" stroke-width="2"></path>
|
||||||
|
</pattern>
|
||||||
|
<pattern id="highcharts-default-pattern-7" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0">
|
||||||
|
<path d="M 10 3 L 5 3 L 5 0 M 5 10 L 5 7 L 0 7" stroke="#2b908f" stroke-width="2"></path>
|
||||||
|
</pattern>
|
||||||
|
<pattern id="highcharts-default-pattern-8" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0">
|
||||||
|
<path d="M 2 5 L 5 2 L 8 5 L 5 8 Z" stroke="#f45b5b" stroke-width="2"></path>
|
||||||
|
</pattern>
|
||||||
|
<pattern id="highcharts-default-pattern-9" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0">
|
||||||
|
<path d="M 0 0 L 5 10 L 10 0" stroke="#91e8e1" stroke-width="2"></path>
|
||||||
|
</pattern>
|
||||||
|
</defs>
|
||||||
|
<rect fill="rgb(0,0,0)" class="highcharts-background" x="0" y="0" width="700" height="100" rx="0" ry="0" fill-opacity="0.0"></rect>
|
||||||
|
<rect fill="url(#highcharts-pjta544-5786)" class="highcharts-plot-background" x="0" y="0" width="700" height="100"></rect>
|
||||||
|
<g class="highcharts-label highcharts-no-data" transform="translate(291,40)">
|
||||||
|
<text x="3" data-z-index="1" style="font-size:12px;font-weight:bold;color:#666666;fill:#666666;" y="15">
|
||||||
|
<tspan>No data to display</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
<g class="highcharts-pane-group" data-z-index="0"></g>
|
||||||
|
<rect fill="none" class="highcharts-plot-border" data-z-index="1" x="0" y="0" width="700" height="100"></rect>
|
||||||
|
<g class="highcharts-series-group" data-z-index="3"></g>
|
||||||
|
<text x="350" text-anchor="middle" class="highcharts-title" data-z-index="4" style="color:#595959;font-size:16px;font-family:Arial;font-weight:normal;font-style:normal;text-decoration:none;fill:#595959;" y="12"></text>
|
||||||
|
<text x="350" text-anchor="middle" class="highcharts-subtitle" data-z-index="4" style="color:#666666;fill:#666666;" y="15"></text>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg version="1.1" class="highcharts-root" style="font-family:Arial;font-size:13px;color:#595959;font-weight:normal;font-style:normal;text-decoration:none;fill:#595959;" xmlns="http://www.w3.org/2000/svg" width="700" height="800" viewBox="0 0 700 800"><desc>Created with Highcharts x.x.x</desc><defs><linearGradient x1="0" y1="1" x2="1" y2="0" id="highcharts-1uhggho-2021"><stop offset="0" stop-color="#000000" stop-opacity="1"></stop><stop offset="0.5" stop-color="#ffffff" stop-opacity="1"></stop><stop offset="1" stop-color="#000000" stop-opacity="1"></stop></linearGradient><clipPath id="highcharts-1uhggho-2022"><rect x="0" y="0" width="700" height="800" fill="none"></rect></clipPath><clipPath id="highcharts-1uhggho-2023"><rect x="0" y="0" width="700" height="800" fill="none"></rect></clipPath><pattern id="highcharts-default-pattern-0" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 0 L 10 10 M 9 -1 L 11 1 M -1 9 L 1 11" stroke="#7cb5ec" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-1" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 10 L 10 0 M -1 1 L 1 -1 M 9 11 L 11 9" stroke="#434348" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-2" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 3 0 L 3 10 M 8 0 L 8 10" stroke="#90ed7d" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-3" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 3 L 10 3 M 0 8 L 10 8" stroke="#f7a35c" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-4" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 3 L 5 3 L 5 0 M 5 10 L 5 7 L 10 7" stroke="#8085e9" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-5" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 3 3 L 8 3 L 8 8 L 3 8 Z" stroke="#f15c80" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-6" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 5 5 m -4 0 a 4 4 0 1 1 8 0 a 4 4 0 1 1 -8 0" stroke="#e4d354" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-7" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 10 3 L 5 3 L 5 0 M 5 10 L 5 7 L 0 7" stroke="#2b908f" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-8" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 2 5 L 5 2 L 8 5 L 5 8 Z" stroke="#f45b5b" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-9" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 0 L 5 10 L 10 0" stroke="#91e8e1" stroke-width="2"></path></pattern></defs><rect fill="rgb(0,0,0)" class="highcharts-background" x="0" y="0" width="700" height="800" rx="0" ry="0" fill-opacity="0.0"></rect><rect fill="url(#highcharts-1uhggho-2021)" class="highcharts-plot-background" x="0" y="0" width="700" height="800"></rect><g class="highcharts-label highcharts-no-data" transform="translate(291,390)"><text x="3" data-z-index="1" style="font-size:12px;font-weight:bold;color:#666666;fill:#666666;" y="15"><tspan>No data to display</tspan></text></g><g class="highcharts-pane-group" data-z-index="0"></g><rect fill="none" class="highcharts-plot-border" data-z-index="1" x="0" y="0" width="700" height="800"></rect><g class="highcharts-series-group" data-z-index="3"></g><text x="350" text-anchor="middle" class="highcharts-title" data-z-index="4" style="color:#595959;font-size:16px;font-family:Arial;font-weight:normal;font-style:normal;text-decoration:none;fill:#595959;" y="12"></text><text x="350" text-anchor="middle" class="highcharts-subtitle" data-z-index="4" style="color:#666666;fill:#666666;" y="15"></text></svg>
|
After Width: | Height: | Size: 3.8 KiB |
|
@ -0,0 +1,12 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="main-svg" width="1163" height="545" style="" viewBox="0 0 1163 545">
|
||||||
|
<defs id="defs-52f0ea">
|
||||||
|
<g class="clips">
|
||||||
|
<clipPath class="axesclip" id="clip52f0eax">
|
||||||
|
<rect x="80" y="0" width="1003" height="545"></rect>
|
||||||
|
</clipPath>
|
||||||
|
</g>
|
||||||
|
</defs>
|
||||||
|
<g class="shapelayer">
|
||||||
|
<path data-index="0" fill-rule="evenodd" d="M50.91,250H109.85V100H200.91Z" clip-path="url('#clip52f0eax')" style="opacity: 0.4; stroke: rgb(0, 0, 0); stroke-opacity: 0; fill: rgb(255, 182, 193); fill-opacity: 1; stroke-width: 0px;"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 619 B |
After Width: | Height: | Size: 186 KiB |
After Width: | Height: | Size: 391 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg version="1.1" class="highcharts-root" style="font-family:Arial;font-size:13px;color:#595959;font-weight:normal;font-style:normal;text-decoration:none;fill:#595959;" xmlns="http://www.w3.org/2000/svg" width="400" height="400" viewBox="0 0 400 400"><desc>Created with Highcharts x.x.x</desc><defs><linearGradient x1="0" y1="1" x2="1" y2="0" id="highcharts-1uhggho-2025"><stop offset="0" stop-color="#0e32e1" stop-opacity="1"></stop><stop offset="0.5" stop-color="#fff7c3" stop-opacity="1"></stop><stop offset="1" stop-color="#ee2724" stop-opacity="1"></stop></linearGradient><clipPath id="highcharts-1uhggho-2026"><rect x="0" y="0" width="400" height="400" fill="none"></rect></clipPath><clipPath id="highcharts-1uhggho-2027"><rect x="0" y="0" width="400" height="400" fill="none"></rect></clipPath><pattern id="highcharts-default-pattern-0" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 0 L 10 10 M 9 -1 L 11 1 M -1 9 L 1 11" stroke="#7cb5ec" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-1" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 10 L 10 0 M -1 1 L 1 -1 M 9 11 L 11 9" stroke="#434348" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-2" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 3 0 L 3 10 M 8 0 L 8 10" stroke="#90ed7d" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-3" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 3 L 10 3 M 0 8 L 10 8" stroke="#f7a35c" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-4" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 3 L 5 3 L 5 0 M 5 10 L 5 7 L 10 7" stroke="#8085e9" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-5" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 3 3 L 8 3 L 8 8 L 3 8 Z" stroke="#f15c80" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-6" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 5 5 m -4 0 a 4 4 0 1 1 8 0 a 4 4 0 1 1 -8 0" stroke="#e4d354" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-7" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 10 3 L 5 3 L 5 0 M 5 10 L 5 7 L 0 7" stroke="#2b908f" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-8" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 2 5 L 5 2 L 8 5 L 5 8 Z" stroke="#f45b5b" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-9" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 0 L 5 10 L 10 0" stroke="#91e8e1" stroke-width="2"></path></pattern></defs><rect fill="rgb(0,0,0)" class="highcharts-background" x="0" y="0" width="400" height="400" rx="0" ry="0" fill-opacity="0.0"></rect><rect fill="url(#highcharts-1uhggho-2025)" class="highcharts-plot-background" x="0" y="0" width="400" height="400"></rect><g class="highcharts-label highcharts-no-data" transform="translate(141,190)"><text x="3" data-z-index="1" style="font-size:12px;font-weight:bold;color:#666666;fill:#666666;" y="15"><tspan>No data to display</tspan></text></g><g class="highcharts-pane-group" data-z-index="0"></g><rect fill="none" class="highcharts-plot-border" data-z-index="1" x="0" y="0" width="400" height="400"></rect><g class="highcharts-series-group" data-z-index="3"></g><text x="200" text-anchor="middle" class="highcharts-title" data-z-index="4" style="color:#595959;font-size:16px;font-family:Arial;font-weight:normal;font-style:normal;text-decoration:none;fill:#595959;" y="12"></text><text x="200" text-anchor="middle" class="highcharts-subtitle" data-z-index="4" style="color:#666666;fill:#666666;" y="15"></text></svg>
|
After Width: | Height: | Size: 3.8 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg version="1.1" class="highcharts-root" style="font-family:Arial;font-size:13px;color:#595959;font-weight:normal;font-style:normal;text-decoration:none;fill:#595959;" xmlns="http://www.w3.org/2000/svg" width="100" height="700" viewBox="0 0 100 700"><desc>Created with Highcharts x.x.x</desc><defs><linearGradient x1="0" y1="0" x2="1" y2="1" id="highcharts-2kkdslw-75"><stop offset="0" stop-color="#000000" stop-opacity="1"></stop><stop offset="0.5" stop-color="#ffffff" stop-opacity="1"></stop><stop offset="1" stop-color="#000000" stop-opacity="1"></stop></linearGradient><clipPath id="highcharts-2kkdslw-76"><rect x="0" y="0" width="100" height="700" fill="none"></rect></clipPath><clipPath id="highcharts-2kkdslw-77"><rect x="0" y="0" width="100" height="700" fill="none"></rect></clipPath><pattern id="highcharts-default-pattern-0" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 0 L 10 10 M 9 -1 L 11 1 M -1 9 L 1 11" stroke="#7cb5ec" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-1" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 10 L 10 0 M -1 1 L 1 -1 M 9 11 L 11 9" stroke="#434348" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-2" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 3 0 L 3 10 M 8 0 L 8 10" stroke="#90ed7d" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-3" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 3 L 10 3 M 0 8 L 10 8" stroke="#f7a35c" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-4" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 3 L 5 3 L 5 0 M 5 10 L 5 7 L 10 7" stroke="#8085e9" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-5" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 3 3 L 8 3 L 8 8 L 3 8 Z" stroke="#f15c80" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-6" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 5 5 m -4 0 a 4 4 0 1 1 8 0 a 4 4 0 1 1 -8 0" stroke="#e4d354" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-7" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 10 3 L 5 3 L 5 0 M 5 10 L 5 7 L 0 7" stroke="#2b908f" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-8" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 2 5 L 5 2 L 8 5 L 5 8 Z" stroke="#f45b5b" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-9" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 0 L 5 10 L 10 0" stroke="#91e8e1" stroke-width="2"></path></pattern></defs><rect fill="rgb(0,0,0)" class="highcharts-background" x="0" y="0" width="100" height="700" rx="0" ry="0" fill-opacity="0.0"></rect><rect fill="url(#highcharts-2kkdslw-75)" class="highcharts-plot-background" x="0" y="0" width="100" height="700"></rect><g class="highcharts-label highcharts-no-data" transform="translate(-9,340)"><text x="3" data-z-index="1" style="font-size:12px;font-weight:bold;color:#666666;fill:#666666;" y="15"><tspan>No data to display</tspan></text></g><g class="highcharts-pane-group" data-z-index="0"></g><rect fill="none" class="highcharts-plot-border" data-z-index="1" x="0" y="0" width="100" height="700"></rect><g class="highcharts-series-group" data-z-index="3"></g><text x="50" text-anchor="middle" class="highcharts-title" data-z-index="4" style="color:#595959;font-size:16px;font-family:Arial;font-weight:normal;font-style:normal;text-decoration:none;fill:#595959;" y="12"></text><text x="50" text-anchor="middle" class="highcharts-subtitle" data-z-index="4" style="color:#666666;fill:#666666;" y="15"></text></svg>
|
After Width: | Height: | Size: 3.8 KiB |
|
@ -0,0 +1,2 @@
|
||||||
|
<svg version="1.1" class="highcharts-root" style="font-family:Arial;font-size:13px;color:#595959;font-weight:normal;font-style:normal;text-decoration:none;fill:#595959;" xmlns="http://www.w3.org/2000/svg" width="100" height="700" viewBox="0 0 100 700"><desc>Created with Highcharts x.x.x</desc><defs><linearGradient x1="0" y1="1" x2="1" y2="0" id="highcharts-2exmcqc-458"><stop offset="0" stop-color="#000000" stop-opacity="1"></stop><stop offset="0.5" stop-color="#ffffff" stop-opacity="1"></stop><stop offset="1" stop-color="#000000" stop-opacity="1"></stop></linearGradient><clipPath id="highcharts-2exmcqc-459"><rect x="0" y="0" width="100" height="700" fill="none"></rect></clipPath><clipPath id="highcharts-2exmcqc-460"><rect x="0" y="0" width="100" height="700" fill="none"></rect></clipPath><pattern id="highcharts-default-pattern-0" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 0 L 10 10 M 9 -1 L 11 1 M -1 9 L 1 11" stroke="#7cb5ec" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-1" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 10 L 10 0 M -1 1 L 1 -1 M 9 11 L 11 9" stroke="#434348" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-2" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 3 0 L 3 10 M 8 0 L 8 10" stroke="#90ed7d" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-3" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 3 L 10 3 M 0 8 L 10 8" stroke="#f7a35c" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-4" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 3 L 5 3 L 5 0 M 5 10 L 5 7 L 10 7" stroke="#8085e9" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-5" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 3 3 L 8 3 L 8 8 L 3 8 Z" stroke="#f15c80" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-6" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 5 5 m -4 0 a 4 4 0 1 1 8 0 a 4 4 0 1 1 -8 0" stroke="#e4d354" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-7" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 10 3 L 5 3 L 5 0 M 5 10 L 5 7 L 0 7" stroke="#2b908f" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-8" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 2 5 L 5 2 L 8 5 L 5 8 Z" stroke="#f45b5b" stroke-width="2"></path></pattern><pattern id="highcharts-default-pattern-9" patternUnits="userSpaceOnUse" width="10" height="10" x="0" y="0"><path d="M 0 0 L 5 10 L 10 0" stroke="#91e8e1" stroke-width="2"></path></pattern></defs><rect fill="rgb(0,0,0)" class="highcharts-background" x="0" y="0" width="100" height="700" rx="0" ry="0" fill-opacity="0.0"></rect><rect fill="url(#highcharts-2exmcqc-458)" class="highcharts-plot-background" x="0" y="0" width="100" height="700"></rect><g class="highcharts-label highcharts-no-data" transform="translate(-9,340)"><text x="3" data-z-index="1" style="font-size:12px;font-weight:bold;color:#666666;fill:#666666;" y="15"><tspan>No data to display</tspan></text></g><g class="highcharts-pane-group" data-z-index="0"></g><rect fill="none" class="highcharts-plot-border" data-z-index="1" x="0" y="0" width="100" height="700"></rect><g class="highcharts-series-group" data-z-index="3"></g><text x="50" text-anchor="middle" class="highcharts-title" data-z-index="4" style="color:#595959;font-size:16px;font-family:Arial;font-weight:normal;font-style:normal;text-decoration:none;fill:#595959;" y="12"></text><text x="50" text-anchor="middle" class="highcharts-subtitle" data-z-index="4" style="color:#666666;fill:#666666;" y="15"></text></svg>
|
||||||
|
|
After Width: | Height: | Size: 3.8 KiB |
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<svg width="900" height="512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<style type="text/css">text { fill: gray; font-family: Avenir, Arial, Helvetica, sans-serif; }</style>
|
||||||
|
<defs>
|
||||||
|
<pattern id="twitterhandle" patternUnits="userSpaceOnUse" width="400" height="200">
|
||||||
|
<text y="30" font-size="40" id="name">TEST WATERMARK</text>
|
||||||
|
</pattern>
|
||||||
|
<pattern xlink:href="#twitterhandle">
|
||||||
|
<text y="120" x="200" font-size="30" id="occupation">test watermark</text>
|
||||||
|
</pattern>
|
||||||
|
<pattern id="combo" xlink:href="#twitterhandle" patternTransform="translate(10,10) rotate(35)">
|
||||||
|
<use xlink:href="#name" />
|
||||||
|
<use xlink:href="#occupation" />
|
||||||
|
</pattern>
|
||||||
|
</defs>
|
||||||
|
<rect width="100%" height="100%" fill="url(#combo)" />
|
||||||
|
|
||||||
|
<rect width="100%" height="100%" fill-opacity="0" stroke="rgb(255,240,255)" stroke-width="10"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 914 B |
|
@ -318,24 +318,4 @@ public class IndentCharacters {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
Pattern PATTERN = Pattern//
|
|
||||||
.compile("^-(!)|^([ ]*)-(-)(\\{(\\d*)(em|pt)?\\})?|^([ ]*)-(\\+)(\\{(.)?:(\\d*)(em|pt)?\\})?|^([ ]*)-(#)(\\{((?!:).)?(.+)?:((\\d*))((em|pt))?\\})?");
|
|
||||||
Matcher matcher = PATTERN.matcher(" -#{d:3em}");
|
|
||||||
System.out.println("matches: " + matcher.find());
|
|
||||||
if (!matcher.matches()) {
|
|
||||||
System.err.println("exit");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
System.out.println("start: " + matcher.start());
|
|
||||||
System.out.println("end: " + matcher.end());
|
|
||||||
System.out.println("groups: " + matcher.groupCount());
|
|
||||||
for (int i = 0; i < matcher.groupCount(); i++) {
|
|
||||||
System.out.println("group " + i + ": '" + matcher.group(i) + "'");
|
|
||||||
}
|
|
||||||
// 2 - -> 1: blanks, 4: size, 5: unit
|
|
||||||
// 7 + -> 6: blanks, 9: sign, 10: size, 11: unit
|
|
||||||
// 11 # -> 12: blanks, 15: number-sign, 16: size, 18: unit
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import java.util.regex.Pattern;
|
||||||
*/
|
*/
|
||||||
public class AnnotationCharacters {
|
public class AnnotationCharacters {
|
||||||
|
|
||||||
private final static List<AnnotationControlCharacterFactory<?>> FACTORIES = new CopyOnWriteArrayList<AnnotationControlCharacterFactory<?>>();
|
private final static List<AnnotationControlCharacterFactory<?>> FACTORIES = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
register(new HyperlinkControlCharacterFactory());
|
register(new HyperlinkControlCharacterFactory());
|
||||||
|
@ -218,10 +218,6 @@ public class AnnotationCharacters {
|
||||||
private static Float defaultBaselineOffsetScale;
|
private static Float defaultBaselineOffsetScale;
|
||||||
private final UnderlineAnnotation line;
|
private final UnderlineAnnotation line;
|
||||||
|
|
||||||
protected UnderlineControlCharacter() {
|
|
||||||
this(null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected UnderlineControlCharacter(String baselineOffsetScaleValue,
|
protected UnderlineControlCharacter(String baselineOffsetScaleValue,
|
||||||
String lineWeightValue) {
|
String lineWeightValue) {
|
||||||
super("UNDERLINE", UnderlineControlCharacterFactory.TO_ESCAPE);
|
super("UNDERLINE", UnderlineControlCharacterFactory.TO_ESCAPE);
|
||||||
|
@ -285,8 +281,8 @@ public class AnnotationCharacters {
|
||||||
extends ControlCharacter {
|
extends ControlCharacter {
|
||||||
|
|
||||||
protected AnnotationControlCharacter(final String description,
|
protected AnnotationControlCharacter(final String description,
|
||||||
final String charaterToEscape) {
|
final String characterToEscape) {
|
||||||
super(description, charaterToEscape);
|
super(description, characterToEscape);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -300,25 +296,4 @@ public class AnnotationCharacters {
|
||||||
public abstract Class<T> getAnnotationType();
|
public abstract Class<T> getAnnotationType();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
Pattern PATTERN = Pattern
|
|
||||||
.compile("(?<!\\\\)(\\\\\\\\)*(__(\\{(-?\\d+(\\.\\d*)?)?\\:(-?\\d+(\\.\\d*)?)?\\})?)");
|
|
||||||
Matcher matcher = PATTERN.matcher("__");
|
|
||||||
System.out.println("matches: " + matcher.find());
|
|
||||||
if (!matcher.matches()) {
|
|
||||||
System.err.println("exit");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
System.out.println("start: " + matcher.start());
|
|
||||||
System.out.println("end: " + matcher.end());
|
|
||||||
System.out.println("groups: " + matcher.groupCount());
|
|
||||||
for (int i = 0; i < matcher.groupCount(); i++) {
|
|
||||||
System.out.println("group " + i + ": '" + matcher.group(i) + "'");
|
|
||||||
}
|
|
||||||
// 2 - -> 1: blanks, 4: size, 5: unit
|
|
||||||
// 7 + -> 6: blanks, 9: sign, 10: size, 11: unit
|
|
||||||
// 11 # -> 12: blanks, 15: number-sign, 16: size, 18: unit
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,3 +3,4 @@ include 'io-vector'
|
||||||
include 'chart'
|
include 'chart'
|
||||||
include 'barcode'
|
include 'barcode'
|
||||||
include 'layout-pdfbox'
|
include 'layout-pdfbox'
|
||||||
|
include 'graphics2d-pdfbox'
|