Java tutorial
/* * Copyright 2017 Emmeran Seehuber * Licensed 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. */ package de.rototor.pdfbox.graphics2d; import de.rototor.pdfbox.graphics2d.IPdfBoxGraphics2DDrawControl.IDrawControlEnv; import de.rototor.pdfbox.graphics2d.IPdfBoxGraphics2DFontTextDrawer.IFontTextDrawerEnv; import de.rototor.pdfbox.graphics2d.IPdfBoxGraphics2DPaintApplier.IPaintEnv; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSStream; 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 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.interactive.annotation.PDAppearanceStream; import org.apache.pdfbox.util.Matrix; import java.awt.*; import java.awt.RenderingHints.Key; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.font.TextAttribute; import java.awt.font.TextLayout; import java.awt.geom.*; import java.awt.image.*; import java.awt.image.renderable.RenderableImage; import java.io.IOException; import java.text.AttributedCharacterIterator; import java.text.AttributedString; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Graphics 2D Adapter for PDFBox. */ public class PdfBoxGraphics2D extends Graphics2D { private final PDFormXObject xFormObject; private final Graphics2D calcGfx; private final PDPageContentStream contentStream; private BufferedImage calcImage; private PDDocument document; private final AffineTransform baseTransform; private AffineTransform transform = new AffineTransform(); private IPdfBoxGraphics2DImageEncoder imageEncoder = new PdfBoxGraphics2DLosslessImageEncoder(); private IPdfBoxGraphics2DColorMapper colorMapper = new PdfBoxGraphics2DColorMapper(); private IPdfBoxGraphics2DPaintApplier paintApplier = new PdfBoxGraphics2DPaintApplier(); private IPdfBoxGraphics2DFontTextDrawer fontTextDrawer = new PdfBoxGraphics2DFontTextDrawer(); private IPdfBoxGraphics2DDrawControl drawControl = PdfBoxGraphics2DDrawControlDefault.INSTANCE; private Paint paint; private Stroke stroke; private Color xorColor; private Font font; private Composite composite; private Shape clipShape; private Color backgroundColor; private final CopyInfo copyInfo; private final PDRectangle bbox; /** * Set a new color mapper. * * @param colorMapper * the color mapper which maps Color to PDColor. */ @SuppressWarnings({ "unused" }) public void setColorMapper(IPdfBoxGraphics2DColorMapper colorMapper) { this.colorMapper = colorMapper; } /** * Set a new image encoder * * @param imageEncoder * the image encoder, which encodes a image as PDImageXForm. */ @SuppressWarnings({ "unused" }) public void setImageEncoder(IPdfBoxGraphics2DImageEncoder imageEncoder) { this.imageEncoder = imageEncoder; } /** * Set a new paint applier. You should always derive your custom paint applier * from the {@link IPdfBoxGraphics2DPaintApplier} and just extend the paint * mapping for custom paint. * <p> * If the paint you map is a paint from a standard library and you can implement * the mapping using reflection please feel free to send a pull request to * extend the default paint mapper. * * @param paintApplier * the paint applier responsible for mapping the paint correctly */ @SuppressWarnings("unused") public void setPaintApplier(IPdfBoxGraphics2DPaintApplier paintApplier) { this.paintApplier = paintApplier; } /** * Set a new draw control. This allows you to influence fill() and draw() * operations. drawString() is only influence if the text is drawn as vector * shape. * * @param drawControl * the draw control */ @SuppressWarnings({ "unused", "WeakerAccess" }) public void setDrawControl(IPdfBoxGraphics2DDrawControl drawControl) { this.drawControl = drawControl; } /** * Create a PDfBox Graphics2D. This size is used for the BBox of the XForm. So * everything drawn outside the rectangle (0x0)-(pixelWidth,pixelHeight) will be * clipped. * <p> * Note: pixelWidth and pixelHeight only define the size of the coordinate space * within this Graphics2D. They do not affect how big the XForm is finally * displayed in the PDF. * * @param document * The document the graphics should be used to create a XForm in. * @param pixelWidth * the width in pixel of the drawing area. * @param pixelHeight * the height in pixel of the drawing area. * @throws IOException * if something goes wrong with writing into the content stream of * the {@link PDDocument}. */ public PdfBoxGraphics2D(PDDocument document, int pixelWidth, int pixelHeight) throws IOException { this(document, new PDRectangle(pixelWidth, pixelHeight)); } /** * Create a PDfBox Graphics2D. This size is used for the BBox of the XForm. So * everything drawn outside the rectangle (0x0)-(pixelWidth,pixelHeight) will be * clipped. * <p> * Note: pixelWidth and pixelHeight only define the size of the coordinate space * within this Graphics2D. They do not affect how big the XForm is finally * displayed in the PDF. * * @param document * The document the graphics should be used to create a XForm in. * @param pixelWidth * the width in pixel of the drawing area. * @param pixelHeight * the height in pixel of the drawing area. * @throws IOException * if something goes wrong with writing into the content stream of * the {@link PDDocument}. */ public PdfBoxGraphics2D(PDDocument document, float pixelWidth, float pixelHeight) throws IOException { this(document, new PDRectangle(pixelWidth, pixelHeight)); } /** * Set an optional text drawer. By default, all text is vectorized and drawn * using vector shapes. To embed fonts into a PDF file it is necessary to have * the underlying TTF file. The java.awt.Font class does not provide that. The * FontTextDrawer must perform the java.awt.Font <=> PDFont mapping and * also must perform the text layout. If it can not map the text or font * correctly, the font drawing falls back to vectoring the text. * * @param fontTextDrawer * The text drawer, which can draw text using fonts */ @SuppressWarnings("WeakerAccess") public void setFontTextDrawer(IPdfBoxGraphics2DFontTextDrawer fontTextDrawer) { this.fontTextDrawer = fontTextDrawer; } private int saveCounter = 0; private final List<CopyInfo> copyList = new ArrayList<CopyInfo>(); private static class CopyInfo { PdfBoxGraphics2D sourceGfx; PdfBoxGraphics2D copy; } /** * @param document * The document the graphics should be used to create a XForm in. * @param bbox * Bounding Box of the graphics * @throws IOException * when something goes wrong with writing into the content stream of * the {@link PDDocument}. */ public PdfBoxGraphics2D(PDDocument document, PDRectangle bbox) throws IOException { this(document, bbox, null); } /* * @internal */ PdfBoxGraphics2D(PDDocument document, PDRectangle bbox, PdfBoxGraphics2D parentGfx) throws IOException { this.document = document; this.bbox = bbox; PDAppearanceStream appearance = new PDAppearanceStream(document); xFormObject = appearance; xFormObject.setResources(new PDResources()); xFormObject.setBBox(bbox); contentStream = new PDPageContentStream(document, appearance, xFormObject.getStream().createOutputStream(COSName.FLATE_DECODE)); contentStreamSaveState(); if (parentGfx != null) { this.colorMapper = parentGfx.colorMapper; this.fontTextDrawer = parentGfx.fontTextDrawer; this.imageEncoder = parentGfx.imageEncoder; this.paintApplier = parentGfx.paintApplier; } baseTransform = new AffineTransform(); baseTransform.translate(0, bbox.getHeight()); baseTransform.scale(1, -1); calcImage = new BufferedImage(100, 100, BufferedImage.TYPE_4BYTE_ABGR); calcGfx = calcImage.createGraphics(); font = calcGfx.getFont(); copyInfo = null; } /** * @return the PDAppearanceStream which resulted in this graphics */ @SuppressWarnings("WeakerAccess") public PDFormXObject getXFormObject() { if (document != null) throw new IllegalStateException("You can only get the XformObject after you disposed the Graphics2D!"); if (copyInfo != null) throw new IllegalStateException("You can not get the Xform stream from the copy"); return xFormObject; } private PdfBoxGraphics2D(PdfBoxGraphics2D gfx) throws IOException { CopyInfo info = new CopyInfo(); info.copy = this; info.sourceGfx = gfx; gfx.copyList.add(info); this.copyInfo = info; this.document = gfx.document; this.bbox = gfx.bbox; this.xFormObject = gfx.xFormObject; this.contentStream = gfx.contentStream; this.baseTransform = gfx.baseTransform; this.transform = (AffineTransform) gfx.transform.clone(); this.calcGfx = gfx.calcGfx; this.calcImage = gfx.calcImage; this.font = gfx.font; this.stroke = gfx.stroke; this.paint = gfx.paint; this.clipShape = gfx.clipShape; this.backgroundColor = gfx.backgroundColor; this.colorMapper = gfx.colorMapper; this.fontTextDrawer = gfx.fontTextDrawer; this.imageEncoder = gfx.imageEncoder; this.paintApplier = gfx.paintApplier; this.drawControl = gfx.drawControl; this.composite = gfx.composite; this.renderingHints = new HashMap<Key, Object>(gfx.renderingHints); this.xorColor = gfx.xorColor; this.saveCounter = 0; contentStreamSaveState(); } /** * Sometimes the users of {@link #create()} don't correctly {@link #dispose()} * the child graphics they create. And you may not always be able to fix this * uses, as it may be in some 3rdparty library. In this case this method can * help you. It will cleanup all dangling child graphics. The child graphics can * not be used after that. This method is a workaround for a buggy old code. You * should only use it if you have to. <br> * <p> * Note: You can only call this method on the "main" graphics, not on a child * created with {@link #create()} */ @SuppressWarnings("WeakerAccess") public void disposeDanglingChildGraphics() { if (copyInfo != null) throw new IllegalStateException("Don't call disposeDanglingChildGraphics() on a child!"); disposeCopies(copyList); } private static void disposeCopies(List<CopyInfo> cl) { while (cl.size() > 0) { CopyInfo copyInfo = cl.get(0); disposeCopies(copyInfo.copy.copyList); copyInfo.copy.dispose(); } } public void dispose() { if (copyInfo != null) { copyInfo.sourceGfx.copyList.remove(copyInfo); try { contentStreamRestoreState(); } catch (IOException e) { throwException(e); } if (this.saveCounter != 0) throw new IllegalStateException("Copy - SaveCounter should be 0, but is " + this.saveCounter); return; } if (copyList.size() > 0) /* * When not all copies created by create() are disposed(), the resulting PDF * content stream will be invalid, as the save/restore context commands (q/Q) * are not balanced. You should always dispose() a graphics context when you are * done with it. */ throw new IllegalStateException( "Not all PdfGraphics2D copies were destroyed! Please ensure that all create() calls get a matching dispose() on the returned copies. Also consider using disposeDanglingChildGraphics()"); try { contentStreamRestoreState(); contentStream.close(); } catch (IOException e) { throwException(e); } if (this.saveCounter != 0) throw new IllegalStateException("SaveCounter should be 0, but is " + this.saveCounter); document = null; calcGfx.dispose(); calcImage.flush(); calcImage = null; } private final IDrawControlEnv drawControlEnv = new IDrawControlEnv() { @Override public Paint getPaint() { return paint; } @Override public PdfBoxGraphics2D getGraphics() { return PdfBoxGraphics2D.this; } }; public void draw(Shape s) { checkNoCopyActive(); /* * Don't try to draw with no paint, just ignore that. */ if (paint == null) return; try { contentStreamSaveState(); Shape shapeToDraw = drawControl.transformShapeBeforeDraw(s, drawControlEnv); if (shapeToDraw != null) { walkShape(shapeToDraw); PDShading pdShading = applyPaint(); if (pdShading != null) applyShadingAsColor(pdShading); if (stroke instanceof BasicStroke) { BasicStroke basicStroke = (BasicStroke) this.stroke; // Cap Style maps 1:1 between Java and PDF Spec contentStream.setLineCapStyle(basicStroke.getEndCap()); // Line Join Style maps 1:1 between Java and PDF Spec contentStream.setLineJoinStyle(basicStroke.getLineJoin()); if (basicStroke.getMiterLimit() > 0) { // Also Miter maps 1:1 between Java and PDF Spec // (NB: set the miter-limit only if value is > 0) contentStream.setMiterLimit(basicStroke.getMiterLimit()); } AffineTransform tf = new AffineTransform(); tf.concatenate(baseTransform); tf.concatenate(transform); double scaleX = tf.getScaleX(); contentStream.setLineWidth((float) Math.abs(basicStroke.getLineWidth() * scaleX)); float[] dashArray = basicStroke.getDashArray(); if (dashArray != null) { for (int i = 0; i < dashArray.length; i++) dashArray[i] = (float) Math.abs(dashArray[i] * scaleX); contentStream.setLineDashPattern(dashArray, (float) Math.abs(basicStroke.getDashPhase() * scaleX)); } } contentStream.stroke(); } drawControl.afterShapeDraw(s, drawControlEnv); contentStreamRestoreState(); } catch (IOException e) { throwException(e); } } public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) { BufferedImage img1 = op.filter(img, null); drawImage(img1, new AffineTransform(1f, 0f, 0f, 1f, x, y), null); } public void drawRenderedImage(RenderedImage img, AffineTransform xform) { WritableRaster data = img.copyData(null); drawImage(new BufferedImage(img.getColorModel(), data, false, null), xform, null); } public void drawRenderableImage(RenderableImage img, AffineTransform xform) { drawRenderedImage(img.createDefaultRendering(), xform); } public void drawString(String str, int x, int y) { drawString(str, (float) x, (float) y); } public void drawString(String str, float x, float y) { AttributedString attributedString = new AttributedString(str); attributedString.addAttribute(TextAttribute.FONT, font); drawString(attributedString.getIterator(), x, y); } public void drawString(AttributedCharacterIterator iterator, int x, int y) { drawString(iterator, (float) x, (float) y); } public boolean drawImage(Image img, int x, int y, ImageObserver observer) { return drawImage(img, x, y, img.getWidth(observer), img.getHeight(observer), observer); } public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) { AffineTransform tf = new AffineTransform(); tf.translate(x, y); tf.scale((float) width / img.getWidth(null), (float) height / img.getHeight(null)); return drawImage(img, tf, observer); } public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) { return drawImage(img, x, y, img.getWidth(observer), img.getHeight(observer), bgcolor, observer); } public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) { try { if (bgcolor != null) { contentStream.setNonStrokingColor(colorMapper.mapColor(contentStream, bgcolor)); walkShape(new Rectangle(x, y, width, height)); contentStream.fill(); } return drawImage(img, x, y, img.getWidth(observer), img.getHeight(observer), observer); } catch (IOException e) { throwException(e); return false; } } public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer) { return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy2, sx2, sy2, null, observer); } public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) { checkNoCopyActive(); AffineTransform tf = new AffineTransform(); tf.concatenate(baseTransform); tf.concatenate(transform); // Sometimes the xform can be null if (xform != null) tf.concatenate((AffineTransform) xform.clone()); PDImageXObject pdImage = imageEncoder.encodeImage(document, contentStream, img); try { contentStreamSaveState(); int imgHeight = img.getHeight(obs); tf.translate(0, imgHeight); tf.scale(1, -1); contentStream.transform(new Matrix(tf)); Object keyInterpolation = renderingHints.get(RenderingHints.KEY_INTERPOLATION); if (RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR.equals(keyInterpolation)) pdImage.setInterpolate(false); contentStream.drawImage(pdImage, 0, 0, img.getWidth(obs), imgHeight); contentStreamRestoreState(); } catch (IOException e) { throwException(e); } return true; } public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer) { try { contentStreamSaveState(); int width = dx2 - dx1; int height = dy2 - dy1; /* * Set the clipping */ walkShape(new Rectangle2D.Double(dx1, dy1, width, height)); contentStream.clip(); /* * Maybe fill the background color */ if (bgcolor != null) { contentStream.setNonStrokingColor(colorMapper.mapColor(contentStream, bgcolor)); walkShape(new Rectangle(dx1, dy1, width, height)); contentStream.fill(); } /* * Build the transform for the image */ AffineTransform tf = new AffineTransform(); tf.translate(dx1, dy1); float imgWidth = img.getWidth(observer); float imgHeight = img.getHeight(observer); tf.scale((float) width / imgWidth, (float) height / imgHeight); tf.translate(-sx1, -sy1); tf.scale((sx2 - sx1) / imgWidth, (sy2 - sy1) / imgHeight); drawImage(img, tf, observer); contentStreamRestoreState(); return true; } catch (IOException e) { throwException(e); return false; } } private void drawStringUsingShapes(AttributedCharacterIterator iterator, float x, float y) { Stroke originalStroke = stroke; Paint originalPaint = paint; TextLayout textLayout = new TextLayout(iterator, getFontRenderContext()); textLayout.draw(this, x, y); paint = originalPaint; stroke = originalStroke; } public void drawString(AttributedCharacterIterator iterator, float x, float y) { /* * Don't try to draw with no paint, just ignore that. */ if (paint == null) return; try { contentStreamSaveState(); /* * If we can draw the text using fonts, we do this */ if (fontTextDrawer.canDrawText((AttributedCharacterIterator) iterator.clone(), fontDrawerEnv)) { drawStringUsingText(iterator, x, y); } else { /* * Otherwise we fall back to draw using shapes. This works always */ drawStringUsingShapes(iterator, x, y); } contentStreamRestoreState(); } catch (IOException e) { throwException(e); } catch (FontFormatException e) { throwException(e); } } private void drawStringUsingText(AttributedCharacterIterator iterator, float x, float y) throws IOException, FontFormatException { contentStreamSaveState(); AffineTransform tf = new AffineTransform(baseTransform); tf.concatenate(transform); tf.translate(x, y); contentStream.transform(new Matrix(tf)); fontTextDrawer.drawText(iterator, fontDrawerEnv); contentStreamRestoreState(); } private void contentStreamSaveState() throws IOException { saveCounter++; contentStream.saveGraphicsState(); } private void contentStreamRestoreState() throws IOException { if (saveCounter == 0) throw new IllegalStateException("Internal save/restore state error. Should never happen."); saveCounter--; contentStream.restoreGraphicsState(); } private final IFontTextDrawerEnv fontDrawerEnv = new IPdfBoxGraphics2DFontTextDrawer.IFontTextDrawerEnv() { @Override public PDDocument getDocument() { return document; } @Override public PDPageContentStream getContentStream() { return contentStream; } @Override public Font getFont() { return font; } @Override public Paint getPaint() { return paint; } @Override public void applyPaint(Paint paint) throws IOException { PDShading pdShading = PdfBoxGraphics2D.this.applyPaint(paint); if (pdShading != null) applyShadingAsColor(pdShading); } @Override public FontRenderContext getFontRenderContext() { return PdfBoxGraphics2D.this.getFontRenderContext(); } @Override public PDRectangle getGraphicsBBox() { return bbox; } @Override public PDResources getResources() { return xFormObject.getResources(); } }; public void drawGlyphVector(GlyphVector g, float x, float y) { checkNoCopyActive(); AffineTransform transformOrig = (AffineTransform) transform.clone(); transform.translate(x, y); fill(g.getOutline()); transform = transformOrig; } public void fill(Shape s) { checkNoCopyActive(); /* * Don't try to draw with no paint, just ignore that. */ if (paint == null) return; try { contentStreamSaveState(); Shape shapeToFill = drawControl.transformShapeBeforeFill(s, drawControlEnv); if (shapeToFill != null) { boolean useEvenOdd = walkShape(shapeToFill); PDShading shading = applyPaint(); if (shading != null) { /* * NB: the shading fill doesn't work with shapes with zero or negative * dimensions (width and/or height): in these cases a normal fill is used */ Rectangle2D r2d = s.getBounds2D(); if ((r2d.getWidth() <= 0) || (r2d.getHeight() <= 0)) { /* * But we apply the shading as color, we usually want to avoid that because it * creates another nested XForm for that ... */ applyShadingAsColor(shading); fill(useEvenOdd); } else { clip(useEvenOdd); contentStream.shadingFill(shading); } } else { fill(useEvenOdd); } } drawControl.afterShapeFill(s, drawControlEnv); contentStreamRestoreState(); } catch (IOException e) { throwException(e); } } private void fill(boolean useEvenOdd) throws IOException { if (useEvenOdd) contentStream.fillEvenOdd(); else contentStream.fill(); } private void applyShadingAsColor(PDShading shading) throws IOException { /* * If the paint has a shading we must create a tiling pattern and set that as * stroke color... */ PDTilingPattern pattern = new PDTilingPattern(); pattern.setPaintType(PDTilingPattern.PAINT_COLORED); pattern.setTilingType(PDTilingPattern.TILING_CONSTANT_SPACING_FASTER_TILING); PDRectangle anchorRect = bbox; pattern.setBBox(anchorRect); pattern.setXStep(anchorRect.getWidth()); pattern.setYStep(anchorRect.getHeight()); PDAppearanceStream appearance = new PDAppearanceStream(this.document); appearance.setResources(pattern.getResources()); appearance.setBBox(pattern.getBBox()); PDPageContentStream imageContentStream = new PDPageContentStream(document, appearance, ((COSStream) pattern.getCOSObject()).createOutputStream()); imageContentStream.addRect(0, 0, anchorRect.getWidth(), anchorRect.getHeight()); imageContentStream.clip(); imageContentStream.shadingFill(shading); imageContentStream.close(); PDColorSpace patternCS1 = new PDPattern(null); COSName tilingPatternName = xFormObject.getResources().add(pattern); PDColor patternColor = new PDColor(tilingPatternName, patternCS1); contentStream.setNonStrokingColor(patternColor); contentStream.setStrokingColor(patternColor); } private PDShading applyPaint() throws IOException { return applyPaint(paint); } private final IPaintEnv paintEnv = new IPaintEnv() { @Override public IPdfBoxGraphics2DColorMapper getColorMapper() { return colorMapper; } @Override public IPdfBoxGraphics2DImageEncoder getImageEncoder() { return imageEncoder; } @Override public PDDocument getDocument() { return document; } @Override public PDResources getResources() { return xFormObject.getResources(); } @Override public Composite getComposite() { return PdfBoxGraphics2D.this.getComposite(); } @Override public PdfBoxGraphics2D getGraphics2D() { return PdfBoxGraphics2D.this; } @Override public Color getXORMode() { return xorColor; } }; private PDShading applyPaint(Paint paintToApply) throws IOException { AffineTransform tf = new AffineTransform(baseTransform); tf.concatenate(transform); return paintApplier.applyPaint(paintToApply, contentStream, tf, paintEnv); } public boolean hit(Rectangle rect, Shape s, boolean onStroke) { return false; } public GraphicsConfiguration getDeviceConfiguration() { return null; } public void setComposite(Composite comp) { composite = comp; } public void setPaint(Paint paint) { this.paint = paint; } public void setStroke(Stroke stroke) { this.stroke = stroke; } private Map<RenderingHints.Key, Object> renderingHints = new HashMap<RenderingHints.Key, Object>(); public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue) { renderingHints.put(hintKey, hintValue); } public Object getRenderingHint(RenderingHints.Key hintKey) { return renderingHints.get(hintKey); } public void setRenderingHints(Map<?, ?> hints) { hints.clear(); addRenderingHints(hints); } @SuppressWarnings("unchecked") public void addRenderingHints(Map<?, ?> hints) { renderingHints.putAll((Map<? extends RenderingHints.Key, ?>) hints); } public RenderingHints getRenderingHints() { return new RenderingHints(renderingHints); } /** * Creates a copy of this graphics object. Please call {@link #dispose()} always * on the copy after you have finished drawing with it. <br> * <br> * Never draw both in this copy and its parent graphics at the same time, as * they all write to the same content stream. This will create a broken PDF * content stream. You should get an {@link java.lang.IllegalStateException} if * you do so, but better just don't try. <br> * <br> * The copy allows you to have different transforms, paints, etc. than the * parent graphics context without affecting the parent. You may also call * create() on a copy, but always remember to call {@link #dispose()} in reverse * order. * * @return a copy of this Graphics. */ public PdfBoxGraphics2D create() { try { return new PdfBoxGraphics2D(this); } catch (IOException e) { throw new RuntimeException(e); } } public PdfBoxGraphics2D create(int x, int y, int width, int height) { return (PdfBoxGraphics2D) super.create(x, y, width, height); } public void translate(int x, int y) { transform.translate(x, y); } public Color getColor() { if (paint instanceof Color) return (Color) paint; return null; } public void setColor(Color color) { this.paint = color; } public void setPaintMode() { xorColor = null; } /** * XOR Mode is currently not implemented as it's not possible in PDF. This mode * is ignored. * * @param c1 * the XORMode Color */ public void setXORMode(Color c1) { xorColor = c1; } public Font getFont() { return font; } public void setFont(Font font) { this.font = font; } public FontMetrics getFontMetrics(Font f) { return calcGfx.getFontMetrics(f); } public Rectangle getClipBounds() { Shape clip = getClip(); if (clip != null) return clip.getBounds(); return null; } public void clipRect(int x, int y, int width, int height) { Rectangle2D rect = new Rectangle2D.Double(x, y, width, height); clip(rect); } public void setClip(int x, int y, int width, int height) { setClip(new Rectangle(x, y, width, height)); } public Shape getClip() { try { return transform.createInverse().createTransformedShape(clipShape); } catch (NoninvertibleTransformException e) { return null; } } public void setClip(Shape clip) { checkNoCopyActive(); this.clipShape = transform.createTransformedShape(clip); /* * Clip on the content stream */ try { contentStreamRestoreState(); contentStreamSaveState(); /* * clip can be null, only set a clipping if not null */ if (clip != null) { clip(walkShape(clip)); } } catch (IOException e) { throwException(e); } } private void clip(boolean useEvenOdd) throws IOException { if (useEvenOdd) contentStream.clipEvenOdd(); else contentStream.clip(); } /** * Float#isFinite() is JDK 8+. We just copied the trivial implementation here. * When we require JDK 8+ we can just drop this method and replace it bei * Float#isFinite() */ private static boolean isFinite(float f) { return Math.abs(f) <= Float.MAX_VALUE; } /** * @return true when all required values are finite */ private static boolean isFinite(float[] coords, int count) { for (int i = 0; i < count; i++) if (!isFinite(coords[i])) return false; return true; } /** * Walk the path and return true if we need to use the even odd winding rule. * * @return true if we need to use the even odd winding rule */ private boolean walkShape(Shape clip) throws IOException { checkNoCopyActive(); AffineTransform tf = new AffineTransform(baseTransform); tf.concatenate(transform); PathIterator pi = clip.getPathIterator(tf); float[] coords = new float[6]; while (!pi.isDone()) { int segment = pi.currentSegment(coords); switch (segment) { case PathIterator.SEG_MOVETO: if (isFinite(coords, 2)) contentStream.moveTo(coords[0], coords[1]); break; case PathIterator.SEG_LINETO: if (isFinite(coords, 2)) contentStream.lineTo(coords[0], coords[1]); break; case PathIterator.SEG_QUADTO: if (isFinite(coords, 4)) contentStream.curveTo1(coords[0], coords[1], coords[2], coords[3]); break; case PathIterator.SEG_CUBICTO: if (isFinite(coords, 6)) contentStream.curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); break; case PathIterator.SEG_CLOSE: contentStream.closePath(); break; } pi.next(); } return pi.getWindingRule() == PathIterator.WIND_EVEN_ODD; } private void checkNoCopyActive() { /* * As long as a copy is in use you are not allowed to do anything here */ if (copyList.size() > 0) throw new IllegalStateException("Don't use the main context as long as a copy is active!"); } private void throwException(Exception e) { throw new RuntimeException(e); } public void copyArea(int x, int y, int width, int height, int dx, int dy) { /* * Sorry, cant do that :( */ throw new IllegalStateException("copyArea() not possible!"); } public void drawLine(int x1, int y1, int x2, int y2) { draw(new Line2D.Double(x1, y1, x2, y2)); } public void fillRect(int x, int y, int width, int height) { fill(new Rectangle(x, y, width, height)); } public void clearRect(int x, int y, int width, int height) { Paint p = paint; paint = backgroundColor; fillRect(x, y, width, height); paint = p; } public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { draw(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight)); } public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { fill(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight)); } public void drawOval(int x, int y, int width, int height) { draw(new Ellipse2D.Double(x, y, width, height)); } public void fillOval(int x, int y, int width, int height) { fill(new Ellipse2D.Double(x, y, width, height)); } public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) { draw(new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.OPEN)); } public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) { fill(new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.PIE)); } public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) { Path2D.Double path = new Path2D.Double(); path.moveTo(xPoints[0], yPoints[0]); for (int i = 1; i < nPoints; i++) path.lineTo(xPoints[i], yPoints[i]); draw(path); } public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) { draw(new Polygon(xPoints, yPoints, nPoints)); } public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) { fill(new Polygon(xPoints, yPoints, nPoints)); } public void translate(double tx, double ty) { checkNoCopyActive(); transform.translate(tx, ty); } public void rotate(double theta) { checkNoCopyActive(); transform.rotate(theta); } public void rotate(double theta, double x, double y) { checkNoCopyActive(); transform.rotate(theta, x, y); } public void scale(double sx, double sy) { checkNoCopyActive(); transform.scale(sx, sy); } public void shear(double shx, double shy) { checkNoCopyActive(); transform.shear(shx, shy); } public void transform(AffineTransform Tx) { checkNoCopyActive(); transform.concatenate(Tx); } public void setTransform(AffineTransform Tx) { checkNoCopyActive(); transform = new AffineTransform(); transform.concatenate(Tx); } public AffineTransform getTransform() { return (AffineTransform) transform.clone(); } public Paint getPaint() { return paint; } public Composite getComposite() { return composite; } public void setBackground(Color color) { backgroundColor = color; } public Color getBackground() { return backgroundColor; } public Stroke getStroke() { return stroke; } public void clip(Shape shape) { Shape clip = getClip(); if (clip == null) setClip(shape); else { Area area = new Area(clip); area.intersect(new Area(shape)); setClip(area); } } public FontRenderContext getFontRenderContext() { calcGfx.addRenderingHints(renderingHints); return calcGfx.getFontRenderContext(); } }