/* * 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.svg; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.Image; import java.awt.Paint; import java.awt.PaintContext; import java.awt.Rectangle; import java.awt.Shape; import java.awt.Stroke; import java.awt.color.ColorSpace; import java.awt.geom.AffineTransform; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.DataBuffer; import java.awt.image.DirectColorModel; import java.awt.image.ImageObserver; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import java.awt.image.renderable.RenderableImage; import java.io.IOException; import java.io.OutputStream; import java.io.StringWriter; import java.util.List; import java.util.Map; import org.apache.batik.ext.awt.LinearGradientPaint; import org.apache.batik.ext.awt.MultipleGradientPaint; import org.apache.batik.ext.awt.RadialGradientPaint; import org.apache.batik.ext.awt.RenderingHintsKeyExt; import org.apache.batik.gvt.GraphicsNode; import org.apache.batik.gvt.PatternPaint; import org.apache.xmlgraphics.image.GraphicsConstants; import org.apache.xmlgraphics.image.loader.ImageInfo; import org.apache.xmlgraphics.image.loader.ImageSize; import org.apache.xmlgraphics.image.loader.impl.ImageRawCCITTFax; import org.apache.xmlgraphics.image.loader.impl.ImageRawJPEG; import org.apache.xmlgraphics.image.loader.impl.ImageRendered; import org.apache.xmlgraphics.java2d.AbstractGraphics2D; import org.apache.xmlgraphics.java2d.GraphicContext; import org.apache.xmlgraphics.java2d.GraphicsConfigurationWithTransparency; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.FontSetup; import org.apache.fop.pdf.BitmapImage; import org.apache.fop.pdf.PDFAnnotList; import org.apache.fop.pdf.PDFColor; import org.apache.fop.pdf.PDFColorHandler; import org.apache.fop.pdf.PDFConformanceException; import org.apache.fop.pdf.PDFDeviceColorSpace; import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFGState; import org.apache.fop.pdf.PDFImage; import org.apache.fop.pdf.PDFImageXObject; import org.apache.fop.pdf.PDFLink; import org.apache.fop.pdf.PDFNumber; import org.apache.fop.pdf.PDFPaintingState; import org.apache.fop.pdf.PDFPattern; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFResources; import org.apache.fop.pdf.PDFText; import org.apache.fop.pdf.PDFXObject; import org.apache.fop.render.pdf.ImageRawCCITTFaxAdapter; import org.apache.fop.render.pdf.ImageRawJPEGAdapter; import org.apache.fop.render.pdf.ImageRenderedAdapter; /** *
PDF Graphics 2D. * Used for drawing into a pdf document as if it is a graphics object. * This takes a pdf document and draws into it.
* *This work was authored by Keiron Liddle (keiron@aftexsw.com).
* * @see org.apache.batik.ext.awt.g2d.AbstractGraphics2D */ public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHandler { private static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform(); /** The number of decimal places. */ private static final int DEC = 8; /** Convenience constant for full opacity */ static final int OPAQUE = 255; /** * the PDF Document being created */ protected PDFDocument pdfDoc; /** * The current resource context for adding fonts, patterns etc. */ protected PDFResourceContext resourceContext; /** * The PDF reference of the current page. */ protected String pageRef; /** * The PDF painting state */ protected PDFPaintingState paintingState; /** the PDF color handler */ protected PDFColorHandler colorHandler; /** * The PDF graphics state level that this svg is being drawn into. */ protected int baseLevel = 0; /** * The count of natively handled images added to document so they receive * unique keys. */ protected int nativeCount = 0; /** * The current font information. */ protected FontInfo fontInfo; /** * The override font state used when drawing text and the font cannot be * set using java fonts. */ protected Font ovFontState = null; /** * the current stream to add PDF commands to */ protected StringWriter currentStream = new StringWriter(); /** * the current (internal) font name */ protected String currentFontName; /** * the current font size in millipoints */ protected float currentFontSize; /** * The output stream for the pdf document. * If this is set then it can progressively output * the pdf document objects to reduce memory. * Especially with images. */ protected OutputStream outputStream = null; /** * Create a new PDFGraphics2D with the given pdf document info. * This is used to create a Graphics object for use inside an already * existing document. * * @param textAsShapes if true then draw text as shapes * @param fi the current font information * @param doc the pdf document for creating pdf objects * @param page the current resource context or page * @param pref the PDF reference of the current page * @param font the current font name * @param size the current font size */ public PDFGraphics2D(boolean textAsShapes, FontInfo fi, PDFDocument doc, PDFResourceContext page, String pref, String font, float size) { this(textAsShapes); pdfDoc = doc; this.colorHandler = new PDFColorHandler(doc.getResources()); resourceContext = page; currentFontName = font; currentFontSize = size; fontInfo = fi; pageRef = pref; paintingState = new PDFPaintingState(); } /** * Create a new PDFGraphics2D. * * @param textAsShapes true if drawing text as shapes */ protected PDFGraphics2D(boolean textAsShapes) { super(textAsShapes); } /** * This constructor supports the create method. * This is not implemented properly. * * @param g the PDF graphics to make a copy of */ public PDFGraphics2D(PDFGraphics2D g) { super(g); this.pdfDoc = g.pdfDoc; this.colorHandler = g.colorHandler; this.resourceContext = g.resourceContext; this.currentFontName = g.currentFontName; this.currentFontSize = g.currentFontSize; this.fontInfo = g.fontInfo; this.pageRef = g.pageRef; this.paintingState = g.paintingState; this.currentStream = g.currentStream; this.nativeCount = g.nativeCount; this.outputStream = g.outputStream; this.ovFontState = g.ovFontState; } /** * Creates a newGraphics
object that is
* a copy of this Graphics
object.
* @return a new graphics context that is a copy of
* this graphics context.
*/
@Override
public Graphics create() {
return new PDFGraphics2D(this);
}
/**
* Central handler for IOExceptions for this class.
* @param ioe IOException to handle
*/
protected void handleIOException(IOException ioe) {
//TODO Surely, there's a better way to do this.
ioe.printStackTrace();
}
/**
* This method is used by PDFDocumentGraphics2D to prepare a new page if
* necessary.
*/
protected void preparePainting() {
//nop, used by PDFDocumentGraphics2D
}
/**
* Set the PDF state to use when starting to draw
* into the PDF graphics.
*
* @param state the PDF state
*/
public void setPaintingState(PDFPaintingState state) {
paintingState = state;
baseLevel = paintingState.getStackLevel();
}
/**
* Set the output stream that this PDF document is
* being drawn to. This is so that it can progressively
* use the PDF document to output data such as images.
* This results in a significant saving on memory.
*
* @param os the output stream that is being used for the PDF document
*/
public void setOutputStream(OutputStream os) {
outputStream = os;
}
/**
* Get the string containing all the commands written into this
* Graphics.
* @return the string containing the PDF markup
*/
public String getString() {
return currentStream.toString();
}
/**
* Get the string buffer from the currentStream, containing all
* the commands written into this Graphics so far.
* @return the StringBuffer containing the PDF markup
*/
public StringBuffer getBuffer() {
return currentStream.getBuffer();
}
/**
* Gets the PDF reference of the current page.
* @return the PDF reference of the current page
*/
public String getPageReference() {
return this.pageRef;
}
/**
* Set the Graphics context.
* @param c the graphics context to use
*/
public void setGraphicContext(GraphicContext c) {
gc = c;
setPrivateHints();
}
private void setPrivateHints() {
setRenderingHint(RenderingHintsKeyExt.KEY_AVOID_TILE_PAINTING,
RenderingHintsKeyExt.VALUE_AVOID_TILE_PAINTING_ON);
}
/**
* Set the override font state for drawing text.
* This is used by the PDF text painter so that it can temporarily
* set the font state when a java font cannot be used.
* The next drawString will use this font state.
*
* @param infont the font state to use
*/
public void setOverrideFontState(Font infont) {
ovFontState = infont;
}
/**
* Restore the PDF graphics state to the starting state level.
*/
/* seems not to be used
public void restorePDFState() {
for (int count = graphicsState.getStackLevel(); count > baseLevel; count--) {
currentStream.write("Q\n");
}
graphicsState.restoreLevel(baseLevel);
}*/
private void concatMatrix(double[] matrix) {
currentStream.write(PDFNumber.doubleOut(matrix[0], DEC) + " "
+ PDFNumber.doubleOut(matrix[1], DEC) + " "
+ PDFNumber.doubleOut(matrix[2], DEC) + " "
+ PDFNumber.doubleOut(matrix[3], DEC) + " "
+ PDFNumber.doubleOut(matrix[4], DEC) + " "
+ PDFNumber.doubleOut(matrix[5], DEC) + " cm\n");
}
private void concatMatrix(AffineTransform transform) {
if (!transform.isIdentity()) {
double[] matrix = new double[6];
transform.getMatrix(matrix);
concatMatrix(matrix);
}
}
/**
* This is mainly used for shading patterns which use the document-global coordinate system
* instead of the local one.
* @return the transformation matrix that established the basic user space for this document
*/
protected AffineTransform getBaseTransform() {
AffineTransform at = new AffineTransform(paintingState.getTransform());
return at;
}
/**
* This is a pdf specific method used to add a link to the
* pdf document.
*
* @param bounds the bounds of the link in user coordinates
* @param trans the transform of the current drawing position
* @param dest the PDF destination
* @param linkType the type of link, internal or external
*/
public void addLink(Rectangle2D bounds, AffineTransform trans, String dest, int linkType) {
if (!pdfDoc.getProfile().isAnnotationAllowed()) {
return;
}
preparePainting();
AffineTransform at = getTransform();
Shape b = at.createTransformedShape(bounds);
b = trans.createTransformedShape(b);
if (b != null) {
Rectangle rect = b.getBounds();
if (linkType != PDFLink.EXTERNAL) {
String pdfdest = "/FitR " + dest;
resourceContext.addAnnotation(
pdfDoc.getFactory().makeLink(rect, getPageReference(), pdfdest));
} else {
resourceContext.addAnnotation(
pdfDoc.getFactory().makeLink(rect, dest, linkType, 0));
}
}
}
/**
* Add a natively handled image directly to the PDF document.
* This is used by the PDFImageElementBridge to draw a natively handled image
* (like JPEG or CCITT images)
* directly into the PDF document rather than converting the image into
* a bitmap and increasing the size.
*
* @param image the image to draw
* @param x the x position
* @param y the y position
* @param width the width to draw the image
* @param height the height to draw the image
*/
public void addNativeImage(org.apache.xmlgraphics.image.loader.Image image, float x, float y,
float width, float height) {
preparePainting();
String key = image.getInfo().getOriginalURI();
if (key == null) {
// Need to include hash code as when invoked from FO you
// may have several 'independent' PDFGraphics2D so the
// count is not enough.
key = "__AddNative_" + hashCode() + "_" + nativeCount;
nativeCount++;
}
PDFImage pdfImage;
if (image instanceof ImageRawJPEG) {
pdfImage = new ImageRawJPEGAdapter((ImageRawJPEG)image, key);
} else if (image instanceof ImageRawCCITTFax) {
pdfImage = new ImageRawCCITTFaxAdapter((ImageRawCCITTFax)image, key);
} else {
throw new IllegalArgumentException(
"Unsupported Image subclass: " + image.getClass().getName());
}
PDFXObject xObject = this.pdfDoc.addImage(resourceContext, pdfImage);
flushPDFDocument();
AffineTransform at = new AffineTransform();
at.translate(x, y);
useXObject(xObject, at, width, height);
}
private void flushPDFDocument() {
if (outputStream != null) {
try {
this.pdfDoc.output(outputStream);
} catch (IOException ioe) {
// ignore exception, will be thrown again later
}
}
}
/**
* Draws as much of the specified image as is currently available.
* The image is drawn with its top-left corner at
* (x, y) in this graphics context's coordinate
* space. Transparent pixels in the image do not affect whatever
* pixels are already there.
* * This method returns immediately in all cases, even if the * complete image has not yet been loaded, and it has not been dithered * and converted for the current output device. *
* If the image has not yet been completely loaded, then
* drawImage
returns false
. As more of
* the image becomes available, the process that draws the image notifies
* the specified image observer.
* @param img the specified image to be drawn.
* @param x the x coordinate.
* @param y the y coordinate.
* @param observer object to be notified as more of
* the image is converted.
* @return true if the image was drawn
* @see java.awt.Image
* @see java.awt.image.ImageObserver
* @see java.awt.image.ImageObserver#imageUpdate(java.awt.Image, int, int, int, int, int)
*/
@Override
public boolean drawImage(Image img, int x, int y,
ImageObserver observer) {
preparePainting();
int width = img.getWidth(observer);
int height = img.getHeight(observer);
if (width == -1 || height == -1) {
return false;
}
return drawImage(img, x, y, width, height, observer);
}
private BufferedImage buildBufferedImage(Dimension size) {
return new BufferedImage(size.width, size.height,
BufferedImage.TYPE_INT_ARGB);
}
/** {@inheritDoc} */
@Override
public boolean drawImage(Image img, int x, int y, int width, int height,
ImageObserver observer) {
preparePainting();
// first we look to see if we've already added this image to
// the pdf document. If so, we just reuse the reference;
// otherwise we have to build a FopImage and add it to the pdf
// document
String key = "TempImage:" + img.toString();
PDFXObject xObject = pdfDoc.getXObject(key);
if (xObject == null) {
// OK, have to build and add a PDF image
Dimension size = new Dimension(width, height);
BufferedImage buf = buildBufferedImage(size);
java.awt.Graphics2D g = buf.createGraphics();
g.setComposite(AlphaComposite.SrcOver);
g.setBackground(new Color(1, 1, 1, 0));
g.setPaint(new Color(1, 1, 1, 0));
g.fillRect(0, 0, width, height);
int imageWidth = buf.getWidth();
int imageHeight = buf.getHeight();
g.clip(new Rectangle(0, 0, imageWidth, imageHeight));
g.setComposite(gc.getComposite());
boolean drawn = g.drawImage(img, 0, 0, imageWidth, imageHeight, observer);
if (!drawn) {
return false;
}
g.dispose();
xObject = addRenderedImage(key, buf);
} else {
resourceContext.getPDFResources().addXObject(xObject);
}
AffineTransform at = new AffineTransform();
at.translate(x, y);
useXObject(xObject, at, width, height);
return true;
}
/**
* Disposes of this graphics context and releases
* any system resources that it is using.
* A Graphics
object cannot be used after
* dispose
has been called.
*
* When a Java program runs, a large number of Graphics
* objects can be created within a short time frame.
* Although the finalization process of the garbage collector
* also disposes of the same system resources, it is preferable
* to manually free the associated resources by calling this
* method rather than to rely on a finalization process which
* may not run to completion for a long period of time.
*
* Graphics objects which are provided as arguments to the
*
* When drawing operations are performed, pixels which are the
* current color are changed to the specified color, and vice versa.
*
* Pixels that are of colors other than those two colors are changed
* in an unpredictable but reversible manner; if the same figure is
* drawn twice, then all pixels are restored to their original values.
* @param c1 the XOR alternation color
*/
@Override
public void setXORMode(Color c1) {
//NYI
}
/**
* Copies an area of the component by a distance specified by
* paint
and update
methods
* of components are automatically released by the system when
* those methods return. For efficiency, programmers should
* call dispose
when finished using
* a Graphics
object only if it was created
* directly from a component or another Graphics
object.
* @see java.awt.Graphics#finalize
* @see java.awt.Component#paint
* @see java.awt.Component#update
* @see java.awt.Component#getGraphics
* @see java.awt.Graphics#create
*/
@Override
public void dispose() {
pdfDoc = null;
fontInfo = null;
currentStream = null;
currentFontName = null;
}
/**
* Strokes the outline of a Shape
using the settings of the
* current Graphics2D
context. The rendering attributes
* applied include the Clip
, Transform
,
* Paint
, Composite
and
* Stroke
attributes.
* @param s the Shape
to be rendered
* @see #setStroke
* @see #setPaint
* @see java.awt.Graphics#setColor
* @see #transform
* @see #setTransform
* @see #clip
* @see #setClip
* @see #setComposite
*/
@Override
public void draw(Shape s) {
preparePainting();
//Transparency shortcut
Color c;
c = getColor();
if (c.getAlpha() == 0) {
return;
}
AffineTransform trans = getTransform();
double[] tranvals = new double[6];
trans.getMatrix(tranvals);
Shape imclip = getClip();
boolean newClip = paintingState.checkClip(imclip);
boolean newTransform = paintingState.checkTransform(trans)
&& !trans.isIdentity();
if (newClip || newTransform) {
saveGraphicsState();
if (newTransform) {
concatMatrix(tranvals);
}
if (newClip) {
writeClip(imclip);
}
}
applyAlpha(OPAQUE, c.getAlpha());
c = getColor();
applyColor(c, false);
c = getBackground();
applyColor(c, true);
Paint paint = getPaint();
if (paintingState.setPaint(paint)) {
if (!applyPaint(paint, false)) {
// Stroke the shape and use it to 'clip'
// the paint contents.
Shape ss = getStroke().createStrokedShape(s);
applyUnknownPaint(paint, ss);
if (newClip || newTransform) {
restoreGraphicsState();
}
return;
}
}
applyStroke(getStroke());
PathIterator iter = s.getPathIterator(IDENTITY_TRANSFORM);
processPathIterator(iter);
doDrawing(false, true, false);
if (newClip || newTransform) {
restoreGraphicsState();
}
}
/*
// in theory we could set the clip using these methods
// it doesn't seem to improve the file sizes much
// and makes everything more complicated
Shape lastClip = null;
public void clip(Shape cl) {
super.clip(cl);
Shape newClip = getClip();
if (newClip == null || lastClip == null
|| !(new Area(newClip).equals(new Area(lastClip)))) {
graphicsState.setClip(newClip);
writeClip(newClip);
}
lastClip = newClip;
}
public void setClip(Shape cl) {
super.setClip(cl);
Shape newClip = getClip();
if (newClip == null || lastClip == null
|| !(new Area(newClip).equals(new Area(lastClip)))) {
for (int count = graphicsState.getStackLevel(); count > baseLevel; count--) {
currentStream.write("Q\n");
}
graphicsState.restoreLevel(baseLevel);
currentStream.write("q\n");
graphicsState.push();
if (newClip != null) {
graphicsState.setClip(newClip);
}
writeClip(newClip);
}
lastClip = newClip;
}
*/
/**
* Set the clipping shape for future PDF drawing in the current graphics state.
* This sets creates and writes a clipping shape that will apply
* to future drawings in the current graphics state.
*
* @param s the clipping shape
*/
protected void writeClip(Shape s) {
if (s == null) {
return;
}
PathIterator iter = s.getPathIterator(IDENTITY_TRANSFORM);
if (iter.isDone()) {
// no segments available. Not worth doing anything
return;
}
preparePainting();
processPathIterator(iter);
// clip area
currentStream.write("W\n");
currentStream.write("n\n");
}
/**
* Apply the java Color to PDF.
* This converts the java colour to a PDF colour and
* sets it for the next drawing.
*
* @param col the java colour
* @param fill true if the colour will be used for filling
*/
protected void applyColor(Color col, boolean fill) {
preparePainting();
//TODO Handle this in PDFColorHandler by automatically converting the color.
//This won't work properly anyway after the redesign of ColorExt
if (col.getColorSpace().getType() == ColorSpace.TYPE_CMYK) {
if (pdfDoc.getProfile().getPDFAMode().isPart1()) {
//See PDF/A-1, ISO 19005:1:2005(E), 6.2.3.3
//FOP is currently restricted to DeviceRGB if PDF/A-1 is active.
throw new PDFConformanceException(
"PDF/A-1 does not allow mixing DeviceRGB and DeviceCMYK.");
}
}
boolean doWrite = false;
if (fill) {
if (paintingState.setBackColor(col)) {
doWrite = true;
}
} else {
if (paintingState.setColor(col)) {
doWrite = true;
}
}
if (doWrite) {
StringBuffer sb = new StringBuffer();
colorHandler.establishColor(sb, col, fill);
currentStream.write(sb.toString());
}
}
/**
* Apply the java paint to the PDF.
* This takes the java paint sets up the appropraite PDF commands
* for the drawing with that paint.
* Currently this supports the gradients and patterns from batik.
*
* @param paint the paint to convert to PDF
* @param fill true if the paint should be set for filling
* @return true if the paint is handled natively, false if the paint should be rasterized
*/
protected boolean applyPaint(Paint paint, boolean fill) {
preparePainting();
if (paint instanceof Color) {
return true;
}
// convert java.awt.GradientPaint to LinearGradientPaint to avoid rasterization
if (paint instanceof GradientPaint) {
GradientPaint gpaint = (GradientPaint) paint;
paint = new LinearGradientPaint(
(float) gpaint.getPoint1().getX(),
(float) gpaint.getPoint1().getY(),
(float) gpaint.getPoint2().getX(),
(float) gpaint.getPoint2().getY(),
new float[] {0, 1},
new Color[] {gpaint.getColor1(), gpaint.getColor2()},
gpaint.isCyclic() ? LinearGradientPaint.REPEAT : LinearGradientPaint.NO_CYCLE);
}
if (paint instanceof LinearGradientPaint) {
LinearGradientPaint gp = (LinearGradientPaint)paint;
// This code currently doesn't support 'repeat'.
// For linear gradients it is possible to construct
// a 'tile' that is repeated with a PDF pattern, but
// it would be very tricky as you would have to rotate
// the coordinate system so the repeat was axially
// aligned. At this point I'm just going to rasterize it.
MultipleGradientPaint.CycleMethodEnum cycle = gp.getCycleMethod();
if (cycle != MultipleGradientPaint.NO_CYCLE) {
return false;
}
Color[] cols = gp.getColors();
float[] fractions = gp.getFractions();
// Build proper transform from gradient space to page space
// ('Patterns' don't get userspace transform).
AffineTransform transform;
transform = new AffineTransform(getBaseTransform());
transform.concatenate(getTransform());
transform.concatenate(gp.getTransform());
ListString
,
* using the current Font
and Paint
attributes
* in the Graphics2D
context.
* The baseline of the first character is at position
* (x, y) in the User Space.
* The rendering attributes applied include the Clip
,
* Transform
, Paint
, Font
and
* Composite
attributes. For characters in script systems
* such as Hebrew and Arabic, the glyphs can be rendered from right to
* left, in which case the coordinate supplied is the location of the
* leftmost character on the baseline.
* @param s the String
to be rendered
* @param x the coordinate where the String
* should be rendered
* @param y the coordinate where the String
* should be rendered
* @see #setPaint
* @see java.awt.Graphics#setColor
* @see java.awt.Graphics#setFont
* @see #setTransform
* @see #setComposite
* @see #setClip
*/
@Override
public void drawString(String s, float x, float y) {
preparePainting();
Font fontState;
AffineTransform fontTransform = null;
if (ovFontState == null) {
java.awt.Font gFont = getFont();
fontTransform = gFont.getTransform();
fontState = fontInfo.getFontInstanceForAWTFont(gFont);
} else {
fontState = fontInfo.getFontInstance(
ovFontState.getFontTriplet(), ovFontState.getFontSize());
ovFontState = null;
}
updateCurrentFont(fontState);
saveGraphicsState();
Color c = getColor();
applyColor(c, true);
applyPaint(getPaint(), true);
applyAlpha(c.getAlpha(), OPAQUE);
MapGraphics2D
context's current Paint
. The
* iterator must specify a font
* for each character. The baseline of the
* first character is at position (x, y) in the
* User Space.
* The rendering attributes applied include the Clip
,
* Transform
, Paint
, and
* Composite
attributes.
* For characters in script systems such as Hebrew and Arabic,
* the glyphs can be rendered from right to left, in which case the
* coordinate supplied is the location of the leftmost character
* on the baseline.
* @param iterator the iterator whose text is to be rendered
* @param x the coordinate where the iterator's text is to be
* rendered
* @param y the coordinate where the iterator's text is to be
* rendered
* @see #setPaint
* @see java.awt.Graphics#setColor
* @see #setTransform
* @see #setComposite
* @see #setClip
*//* TODO Reimplement for higher efficiency similar to the way it was done in PDFTextPainter
public void drawString(AttributedCharacterIterator iterator, float x,
float y) {
preparePainting();
Font fontState = null;
Shape imclip = getClip();
writeClip(imclip);
Color c = getColor();
applyColor(c, true);
applyPaint(getPaint(), true);
boolean fill = true;
boolean stroke = false;
if (true) {
Stroke currentStroke = getStroke();
stroke = true;
applyStroke(currentStroke);
applyColor(c, false);
applyPaint(getPaint(), false);
}
currentStream.write("BT\n");
// set text rendering mode:
// 0 - fill, 1 - stroke, 2 - fill then stroke
int textr = 0;
if (fill && stroke) {
textr = 2;
} else if (stroke) {
textr = 1;
}
currentStream.write(textr + " Tr\n");
AffineTransform trans = getTransform();
trans.translate(x, y);
double[] vals = new double[6];
trans.getMatrix(vals);
for (char ch = iterator.first(); ch != CharacterIterator.DONE;
ch = iterator.next()) {
//Map attr = iterator.getAttributes();
String name = fontState.getFontName();
int size = fontState.getFontSize();
if ((!name.equals(this.currentFontName))
|| (size != this.currentFontSize)) {
this.currentFontName = name;
this.currentFontSize = size;
currentStream.write("/" + name + " " + (size / 1000)
+ " Tf\n");
}
currentStream.write(PDFNumber.doubleOut(vals[0], DEC) + " "
+ PDFNumber.doubleOut(vals[1], DEC) + " "
+ PDFNumber.doubleOut(vals[2], DEC) + " "
+ PDFNumber.doubleOut(vals[3], DEC) + " "
+ PDFNumber.doubleOut(vals[4], DEC) + " "
+ PDFNumber.doubleOut(vals[5], DEC) + " Tm (" + ch
+ ") Tj\n");
}
currentStream.write("ET\n");
}*/
/**
* Fills the interior of a Shape
using the settings of the
* Graphics2D
context. The rendering attributes applied
* include the Clip
, Transform
,
* Paint
, and Composite
.
* @param s the Shape
to be filled
* @see #setPaint
* @see java.awt.Graphics#setColor
* @see #transform
* @see #setTransform
* @see #setComposite
* @see #clip
* @see #setClip
*/
@Override
public void fill(Shape s) {
preparePainting();
//Transparency shortcut
Color c;
c = getBackground();
if (c.getAlpha() == 0) {
c = getColor();
if (c.getAlpha() == 0) {
return;
}
}
AffineTransform trans = getTransform();
double[] tranvals = new double[6];
trans.getMatrix(tranvals);
Shape imclip = getClip();
boolean newClip = paintingState.checkClip(imclip);
boolean newTransform = paintingState.checkTransform(trans)
&& !trans.isIdentity();
if (newClip || newTransform) {
saveGraphicsState();
if (newTransform) {
concatMatrix(tranvals);
}
if (newClip) {
writeClip(imclip);
}
}
applyAlpha(c.getAlpha(), OPAQUE);
c = getColor();
applyColor(c, true);
c = getBackground();
applyColor(c, false);
Paint paint = getPaint();
if (paintingState.setPaint(paint)) {
if (!applyPaint(paint, true)) {
// Use the shape to 'clip' the paint contents.
applyUnknownPaint(paint, s);
if (newClip || newTransform) {
restoreGraphicsState();
}
return;
}
}
if (s instanceof Rectangle2D) {
Rectangle2D rect = (Rectangle2D)s;
currentStream.write(PDFNumber.doubleOut(rect.getMinX(), DEC) + " "
+ PDFNumber.doubleOut(rect.getMinY(), DEC) + " ");
currentStream.write(PDFNumber.doubleOut(rect.getWidth(), DEC) + " "
+ PDFNumber.doubleOut(rect.getHeight(), DEC) + " re ");
doDrawing(true, false, false);
} else {
PathIterator iter = s.getPathIterator(IDENTITY_TRANSFORM);
processPathIterator(iter);
doDrawing(true, false,
iter.getWindingRule() == PathIterator.WIND_EVEN_ODD);
}
if (newClip || newTransform) {
restoreGraphicsState();
}
}
void saveGraphicsState() {
currentStream.write("q\n");
paintingState.save();
}
void restoreGraphicsState() {
currentStream.write("Q\n");
paintingState.restore();
}
/** Checks whether the use of transparency is allowed. */
protected void checkTransparencyAllowed() {
pdfDoc.getProfile().verifyTransparencyAllowed("Java2D graphics");
}
/**
* Processes a path iterator generating the necessary painting operations.
* @param iter PathIterator to process
*/
public void processPathIterator(PathIterator iter) {
double lastX = 0.0;
double lastY = 0.0;
while (!iter.isDone()) {
double[] vals = new double[6];
int type = iter.currentSegment(vals);
switch (type) {
case PathIterator.SEG_CUBICTO:
lastX = vals[4];
lastY = vals[5];
currentStream.write(PDFNumber.doubleOut(vals[0], DEC) + " "
+ PDFNumber.doubleOut(vals[1], DEC) + " "
+ PDFNumber.doubleOut(vals[2], DEC) + " "
+ PDFNumber.doubleOut(vals[3], DEC) + " "
+ PDFNumber.doubleOut(vals[4], DEC) + " "
+ PDFNumber.doubleOut(vals[5], DEC) + " c\n");
break;
case PathIterator.SEG_LINETO:
lastX = vals[0];
lastY = vals[1];
currentStream.write(PDFNumber.doubleOut(vals[0], DEC) + " "
+ PDFNumber.doubleOut(vals[1], DEC) + " l\n");
break;
case PathIterator.SEG_MOVETO:
lastX = vals[0];
lastY = vals[1];
currentStream.write(PDFNumber.doubleOut(vals[0], DEC) + " "
+ PDFNumber.doubleOut(vals[1], DEC) + " m\n");
break;
case PathIterator.SEG_QUADTO:
double controlPointAX = lastX + ((2.0 / 3.0) * (vals[0] - lastX));
double controlPointAY = lastY + ((2.0 / 3.0) * (vals[1] - lastY));
double controlPointBX = vals[2] + ((2.0 / 3.0) * (vals[0] - vals[2]));
double controlPointBY = vals[3] + ((2.0 / 3.0) * (vals[1] - vals[3]));
currentStream.write(PDFNumber.doubleOut(controlPointAX, DEC) + " "
+ PDFNumber.doubleOut(controlPointAY, DEC) + " "
+ PDFNumber.doubleOut(controlPointBX, DEC) + " "
+ PDFNumber.doubleOut(controlPointBY, DEC) + " "
+ PDFNumber.doubleOut(vals[2], DEC) + " "
+ PDFNumber.doubleOut(vals[3], DEC) + " c\n");
lastX = vals[2];
lastY = vals[3];
break;
case PathIterator.SEG_CLOSE:
currentStream.write("h\n");
break;
default:
break;
}
iter.next();
}
}
/**
* Do the PDF drawing command.
* This does the PDF drawing command according to fill
* stroke and winding rule.
*
* @param fill true if filling the path
* @param stroke true if stroking the path
* @param nonzero true if using the non-zero winding rule
*/
protected void doDrawing(boolean fill, boolean stroke, boolean nonzero) {
preparePainting();
if (fill) {
if (stroke) {
if (nonzero) {
currentStream.write("B*\n");
} else {
currentStream.write("B\n");
}
} else {
if (nonzero) {
currentStream.write("f*\n");
} else {
currentStream.write("f\n");
}
}
} else {
// if (stroke)
currentStream.write("S\n");
}
}
/**
* Returns the device configuration associated with this
* Graphics2D
.
*
* @return the PDF graphics configuration
*/
@Override
public GraphicsConfiguration getDeviceConfiguration() {
return new GraphicsConfigurationWithTransparency();
}
/**
* Used to create proper font metrics
*/
private Graphics2D fmg;
{
BufferedImage bi = new BufferedImage(1, 1,
BufferedImage.TYPE_INT_ARGB);
fmg = bi.createGraphics();
}
/**
* Gets the font metrics for the specified font.
* @return the font metrics for the specified font.
* @param f the specified font
* @see java.awt.Graphics#getFont
* @see java.awt.FontMetrics
* @see java.awt.Graphics#getFontMetrics()
*/
@Override
public java.awt.FontMetrics getFontMetrics(java.awt.Font f) {
return fmg.getFontMetrics(f);
}
/**
* Sets the paint mode of this graphics context to alternate between
* this graphics context's current color and the new specified color.
* This specifies that logical pixel operations are performed in the
* XOR mode, which alternates pixels between the current color and
* a specified XOR color.
* dx
and dy
. From the point specified
* by x
and y
, this method
* copies downwards and to the right. To copy an area of the
* component to the left or upwards, specify a negative value for
* dx
or dy
.
* If a portion of the source rectangle lies outside the bounds
* of the component, or is obscured by another window or component,
* copyArea
will be unable to copy the associated
* pixels. The area that is omitted can be refreshed by calling
* the component's paint
method.
* @param x the x coordinate of the source rectangle.
* @param y the y coordinate of the source rectangle.
* @param width the width of the source rectangle.
* @param height the height of the source rectangle.
* @param dx the horizontal distance to copy the pixels.
* @param dy the vertical distance to copy the pixels.
*/
@Override
public void copyArea(int x, int y, int width, int height, int dx,
int dy) {
//NYI
}
}