blob: ef2f6b213e66657a530ed240fbbc5bfb58dac2ce [file] [log] [blame]
/*
* 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.
*/
/* $Id$ */
package org.apache.fop.render.pdf.pdfbox;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.PaintContext;
import java.awt.Rectangle;
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.awt.image.ColorModel;
import java.awt.image.DataBufferInt;
import java.awt.image.ImageObserver;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URI;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSInteger;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSStream;
import org.apache.pdfbox.pdmodel.common.function.PDFunction;
import org.apache.pdfbox.pdmodel.common.function.PDFunctionType0;
import org.apache.pdfbox.pdmodel.common.function.PDFunctionType2;
import org.apache.pdfbox.pdmodel.common.function.PDFunctionType3;
import org.apache.pdfbox.pdmodel.graphics.shading.AxialShadingContext;
import org.apache.pdfbox.pdmodel.graphics.shading.AxialShadingPaint;
import org.apache.pdfbox.pdmodel.graphics.shading.RadialShadingContext;
import org.apache.pdfbox.pdmodel.graphics.shading.RadialShadingPaint;
import org.apache.pdfbox.pdmodel.graphics.shading.ShadingPaint;
import org.apache.pdfbox.util.Matrix;
import org.apache.xmlgraphics.image.loader.ImageInfo;
import org.apache.xmlgraphics.image.loader.ImageSize;
import org.apache.xmlgraphics.io.TempResourceURIGenerator;
import org.apache.xmlgraphics.java2d.ps.PSGraphics2D;
import org.apache.xmlgraphics.ps.PSGenerator;
import org.apache.xmlgraphics.ps.PSResource;
import org.apache.fop.pdf.PDFDeviceColorSpace;
import org.apache.fop.render.gradient.Function;
import org.apache.fop.render.gradient.GradientMaker;
import org.apache.fop.render.gradient.GradientMaker.DoubleFormatter;
import org.apache.fop.render.gradient.Pattern;
import org.apache.fop.render.gradient.Shading;
import org.apache.fop.render.ps.Gradient;
import org.apache.fop.render.ps.PSDocumentHandler;
import org.apache.fop.render.ps.PSImageUtils;
public class PSPDFGraphics2D extends PSGraphics2D {
private boolean clearRect;
public PSPDFGraphics2D(boolean textAsShapes) {
super(textAsShapes);
}
public PSPDFGraphics2D(PSGraphics2D g) {
super(g);
}
public PSPDFGraphics2D(boolean textAsShapes, PSGenerator gen) {
super(textAsShapes, gen);
}
public void clearRect(int x, int y, int width, int height) {
if (clearRect) {
super.clearRect(x, y, width, height);
}
clearRect = true;
}
private final GradientMaker.DoubleFormatter doubleFormatter = new DoubleFormatter() {
public String formatDouble(double d) {
return getPSGenerator().formatDouble(d);
}
};
protected void applyPaint(Paint paint, boolean fill) {
preparePainting();
if (paint instanceof AxialShadingPaint || paint instanceof RadialShadingPaint) {
PaintContext paintContext = paint.createContext(null, new Rectangle(), null, new AffineTransform(),
getRenderingHints());
int deviceColorSpace = PDFDeviceColorSpace.DEVICE_RGB;
if (paint instanceof AxialShadingPaint) {
try {
AxialShadingContext asc = (AxialShadingContext) paintContext;
float[] fCoords = asc.getCoords();
transformCoords(fCoords, (ShadingPaint) paint, true);
PDFunction function = asc.getFunction();
Function targetFT = getFunction(function);
if (targetFT != null) {
if (targetFT.getFunctions().size() == 5
&& targetFT.getFunctions().get(0).getFunctionType() == 0) {
return;
}
List<Double> dCoords = floatArrayToDoubleList(fCoords);
PDFDeviceColorSpace colSpace = new PDFDeviceColorSpace(deviceColorSpace);
Shading shading = new Shading(2, colSpace, dCoords, targetFT);
Pattern pattern = new Pattern(2, shading, null);
gen.write(Gradient.outputPattern(pattern, doubleFormatter));
}
} catch (IOException ioe) {
handleIOException(ioe);
}
} else if (paint instanceof RadialShadingPaint) {
try {
RadialShadingContext rsc = (RadialShadingContext) paintContext;
float[] fCoords = rsc.getCoords();
transformCoords(fCoords, (ShadingPaint) paint, false);
PDFunction function = rsc.getFunction();
Function targetFT3 = getFunction(function);
List<Double> dCoords = floatArrayToDoubleList(fCoords);
PDFDeviceColorSpace colSpace = new PDFDeviceColorSpace(deviceColorSpace);
Shading shading = new Shading(3, colSpace, dCoords, targetFT3);
Pattern pattern = new Pattern(2, shading, null);
gen.write(Gradient.outputPattern(pattern, doubleFormatter));
} catch (IOException ioe) {
handleIOException(ioe);
}
}
} else if (paint.getClass().getSimpleName().equals("TilingPaint")) {
TexturePaint texturePaint = (TexturePaint) getField(paint, "paint");
Matrix matrix = (Matrix) getField(paint, "patternMatrix");
Rectangle2D rect = getTransformedRect(matrix, texturePaint.getAnchorRect());
texturePaint = new TexturePaint(texturePaint.getImage(), rect);
super.applyPaint(texturePaint, fill);
}
}
private static Object getField(final Paint paint, final String field) {
return AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
try {
Field f = paint.getClass().getDeclaredField(field);
f.setAccessible(true);
return f.get(paint);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
});
}
private static Rectangle2D getTransformedRect(Matrix matrix, Rectangle2D anchorRect) {
double x = anchorRect.getX();
double y = anchorRect.getY();
double width = anchorRect.getWidth();
double height = anchorRect.getHeight();
AffineTransform at = matrix.createAffineTransform();
Point2D p1 = new Point2D.Double(x, y);
Point2D p2 = new Point2D.Double(x + width, y + height);
at.transform(p1, p1);
at.transform(p2, p2);
Rectangle2D rectangle = new Rectangle2D.Float(
(float) Math.min(p1.getX(), p2.getX()),
(float) Math.min(p1.getY(), p2.getY()),
(float) Math.abs(width),
(float) Math.abs(height));
return rectangle;
}
private void transformCoords(float[] coords, ShadingPaint paint, boolean axialShading) {
Matrix ctm = paint.getMatrix();
AffineTransform at = ctm.createAffineTransform();
if (axialShading) {
at.transform(coords, 0, coords, 0, 2);
} else {
at.transform(coords, 0, coords, 0, 1);
at.transform(coords, 3, coords, 3, 1);
coords[2] *= ctm.getScalingFactorX();
coords[5] *= ctm.getScalingFactorX();
}
}
protected static Function getFunction(PDFunction f) throws IOException {
if (f instanceof PDFunctionType3) {
PDFunctionType3 sourceFT3 = (PDFunctionType3) f;
float[] bounds = sourceFT3.getBounds().toFloatArray();
COSArray sourceFunctions = sourceFT3.getFunctions();
List<Function> targetFunctions = new ArrayList<Function>();
for (int j = 0; j < sourceFunctions.size(); j++) {
targetFunctions.add(getFunction(PDFunction.create(sourceFunctions.get(j))));
}
return new Function(null, null, targetFunctions, toList(bounds), null);
} else if (f instanceof PDFunctionType2) {
PDFunctionType2 sourceFT2 = (PDFunctionType2) f;
double interpolation = (double)sourceFT2.getN();
float[] c0 = sourceFT2.getC0().toFloatArray();
float[] c1 = sourceFT2.getC1().toFloatArray();
return new Function(null, null, c0, c1, interpolation);
} else if (f instanceof PDFunctionType0) {
COSDictionary s = f.getCOSObject();
assert s instanceof COSStream;
COSStream stream = (COSStream) s;
COSArray domain = (COSArray) s.getDictionaryObject(COSName.DOMAIN);
COSArray range = (COSArray) s.getDictionaryObject(COSName.RANGE);
int bits = ((COSInteger)s.getDictionaryObject(COSName.BITS_PER_SAMPLE)).intValue();
COSArray size = (COSArray) s.getDictionaryObject(COSName.SIZE);
COSArray encode = getEncode(s);
byte[] x = IOUtils.toByteArray(stream.getUnfilteredStream());
for (byte y : x) {
if (y != 0) {
return new Function(floatArrayToDoubleList(domain.toFloatArray()),
floatArrayToDoubleList(range.toFloatArray()),
floatArrayToDoubleList(encode.toFloatArray()),
x,
bits,
toList(size)
);
}
}
return null;
}
throw new IOException("Unsupported " + f.toString());
}
private static COSArray getEncode(COSDictionary s) {
COSArray encode = (COSArray) s.getDictionaryObject(COSName.ENCODE);
if (encode == null) {
encode = new COSArray();
COSArray size = (COSArray) s.getDictionaryObject(COSName.SIZE);
int sizeValuesSize = size.size();
for (int i = 0; i < sizeValuesSize; i++) {
encode.add(COSInteger.ZERO);
encode.add(COSInteger.get(size.getInt(i) - 1));
}
}
return encode;
}
private static List<Float> toList(float[] array) {
List<Float> list = new ArrayList<Float>(array.length);
for (float f : array) {
list.add(f);
}
return list;
}
private static List<Integer> toList(COSArray array) {
List<Integer> list = new ArrayList<Integer>();
for (COSBase i : array) {
list.add(((COSInteger)i).intValue());
}
return list;
}
private static List<Double> floatArrayToDoubleList(float[] floatArray) {
List<Double> doubleList = new ArrayList<Double>();
for (float f : floatArray) {
doubleList.add((double) f);
}
return doubleList;
}
@Override
public boolean drawImage(Image img, int x1, int y1, ImageObserver observer) {
Color mask = null;
ColorModel cm = ((BufferedImage)img).getColorModel();
if (cm.hasAlpha()) {
mask = Color.WHITE;
}
if (gen instanceof PSDocumentHandler.FOPPSGenerator) {
PSDocumentHandler.FOPPSGenerator fopGen = (PSDocumentHandler.FOPPSGenerator)gen;
PSDocumentHandler handler = fopGen.getHandler();
if (handler.getPSUtil().isOptimizeResources()) {
try {
final int width = img.getWidth(observer);
final int height = img.getHeight(observer);
if (width == -1 || height == -1) {
return false;
}
BufferedImage buf = getImage(width, height, img, observer);
if (buf == null) {
return false;
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataBufferInt db = (DataBufferInt) buf.getRaster().getDataBuffer();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeInt(width);
dos.writeInt(height);
for (int i : db.getData()) {
dos.writeInt(i);
}
String format = DataBufferInt.class.getName();
int hash = Arrays.hashCode(bos.toByteArray());
URI uri = fopGen.getImages().get(hash);
if (uri == null) {
uri = new TempResourceURIGenerator("img" + hash + "." + format).generate();
fopGen.getImages().put(hash, uri);
BufferedOutputStream outputStream = fopGen.getTempStream(uri);
outputStream.write(bos.toByteArray());
outputStream.close();
}
PSResource form = handler.getFormForImage(uri.toASCIIString());
ImageInfo info = new ImageInfo(uri.toASCIIString(), "image/" + format);
ImageSize size = new ImageSize(width, height, handler.getUserAgent().getTargetResolution());
size.calcSizeFromPixels();
info.setSize(size);
float res = handler.getUserAgent().getSourceResolution() / 72;
Rectangle rect =
new Rectangle(0, 0, (int)(size.getWidthMpt() * res), (int)(size.getHeightMpt() * res));
gen.saveGraphicsState();
gen.concatMatrix(getTransform());
writeClip(getClip());
PSImageUtils.drawForm(form, info, rect, gen);
gen.restoreGraphicsState();
} catch (IOException e) {
throw new RuntimeException(e);
}
return true;
}
}
return super.drawImage(img, x1, y1, observer, mask);
}
private BufferedImage getImage(int width, int height, Image img, ImageObserver observer) {
Dimension size = new Dimension(width, height);
BufferedImage buf = buildBufferedImage(size);
Graphics2D g = buf.createGraphics();
g.setComposite(AlphaComposite.SrcOver);
g.setBackground(new Color(1, 1, 1, 0));
g.fillRect(0, 0, width, height);
g.clip(new Rectangle(0, 0, buf.getWidth(), buf.getHeight()));
if (!g.drawImage(img, 0, 0, observer)) {
return null;
}
g.dispose();
return buf;
}
}