Java tutorial
package fi.nls.oskari.printout.printing; /* * 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. */ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.pdfbox.cos.COSArray; import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSString; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDResources; import org.apache.pdfbox.pdmodel.common.COSStreamArray; import org.apache.pdfbox.pdmodel.common.PDStream; import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.graphics.color.*; import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject; import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage; import java.awt.*; import java.awt.color.ColorSpace; import java.awt.geom.AffineTransform; import java.awt.geom.PathIterator; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.text.NumberFormat; import java.util.ArrayList; import java.util.List; import java.util.Locale; /** * This class is a convenience for creating page content streams. You MUST call * close() when you are finished with this object. * * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a> * */ public class PDPageContentStream { /** * Log instance. */ private static final Log LOG = LogFactory.getLog(PDPageContentStream.class); private PDPage page; private OutputStream output; private boolean inTextMode = false; private PDResources resources; private PDColorSpace currentStrokingColorSpace = new PDDeviceGray(); private PDColorSpace currentNonStrokingColorSpace = new PDDeviceGray(); // cached storage component for getting color values private float[] colorComponents = new float[4]; private NumberFormat formatDecimal = NumberFormat.getNumberInstance(Locale.US); private static final String ISO8859 = "ISO-8859-1"; private static byte[] getISOBytes(final String s) { try { return s.getBytes(ISO8859); } catch (final UnsupportedEncodingException ex) { throw new IllegalStateException(ex); } } private static final byte[] BEGIN_TEXT = getISOBytes("BT\n"); private static final byte[] END_TEXT = getISOBytes("ET\n"); private static final byte[] SET_FONT = getISOBytes("Tf\n"); private static final byte[] MOVE_TEXT_POSITION = getISOBytes("Td\n"); private static final byte[] SET_TEXT_MATRIX = getISOBytes("Tm\n"); private static final byte[] SHOW_TEXT = getISOBytes("Tj\n"); private static final byte[] SAVE_GRAPHICS_STATE = getISOBytes("q\n"); private static final byte[] RESTORE_GRAPHICS_STATE = getISOBytes("Q\n"); private static final byte[] CONCATENATE_MATRIX = getISOBytes("cm\n"); private static final byte[] XOBJECT_DO = getISOBytes("Do\n"); private static final byte[] RG_STROKING = getISOBytes("RG\n"); private static final byte[] RG_NON_STROKING = getISOBytes("rg\n"); private static final byte[] K_STROKING = getISOBytes("K\n"); private static final byte[] K_NON_STROKING = getISOBytes("k\n"); private static final byte[] G_STROKING = getISOBytes("G\n"); private static final byte[] G_NON_STROKING = getISOBytes("g\n"); private static final byte[] RECTANGLE = getISOBytes("re\n"); private static final byte[] FILL_NON_ZERO = getISOBytes("f\n"); private static final byte[] FILL_EVEN_ODD = getISOBytes("f*\n"); private static final byte[] LINE_TO = getISOBytes("l\n"); private static final byte[] MOVE_TO = getISOBytes("m\n"); private static final byte[] CLOSE_STROKE = getISOBytes("s\n"); private static final byte[] STROKE = getISOBytes("S\n"); private static final byte[] LINE_WIDTH = getISOBytes("w\n"); private static final byte[] LINE_JOIN_STYLE = getISOBytes("j\n"); private static final byte[] LINE_CAP_STYLE = getISOBytes("J\n"); private static final byte[] LINE_DASH_PATTERN = getISOBytes("d\n"); private static final byte[] CLOSE_SUBPATH = getISOBytes("h\n"); private static final byte[] CLIP_PATH_NON_ZERO = getISOBytes("W\n"); private static final byte[] CLIP_PATH_EVEN_ODD = getISOBytes("W*\n"); private static final byte[] NOP = getISOBytes("n\n"); private static final byte[] BEZIER_312 = getISOBytes("c\n"); private static final byte[] BEZIER_32 = getISOBytes("v\n"); private static final byte[] BEZIER_313 = getISOBytes("y\n"); private static final byte[] BMC = getISOBytes("BMC\n"); private static final byte[] BDC = getISOBytes("BDC\n"); private static final byte[] EMC = getISOBytes("EMC\n"); private static final byte[] SET_STROKING_COLORSPACE = getISOBytes("CS\n"); private static final byte[] SET_NON_STROKING_COLORSPACE = getISOBytes("cs\n"); private static final byte[] SET_STROKING_COLOR_SIMPLE = getISOBytes("SC\n"); private static final byte[] SET_STROKING_COLOR_COMPLEX = getISOBytes("SCN\n"); private static final byte[] SET_NON_STROKING_COLOR_SIMPLE = getISOBytes("sc\n"); private static final byte[] SET_NON_STROKING_COLOR_COMPLEX = getISOBytes("scn\n"); private static final byte[] OPENING_BRACKET = getISOBytes("["); private static final byte[] CLOSING_BRACKET = getISOBytes("]"); private static final int SPACE = 32; /** * Create a new PDPage content stream. * * @param document * The document the page is part of. * @param sourcePage * The page to write the contents to. * @throws IOException * If there is an error writing to the page contents. */ public PDPageContentStream(PDDocument document, PDPage sourcePage) throws IOException { this(document, sourcePage, false, true); } /** * Create a new PDPage content stream. * * @param document * The document the page is part of. * @param sourcePage * The page to write the contents to. * @param appendContent * Indicates whether content will be overwritten. If false all * previous content is deleted. * @param compress * Tell if the content stream should compress the page contents. * @throws IOException * If there is an error writing to the page contents. */ public PDPageContentStream(PDDocument document, PDPage sourcePage, boolean appendContent, boolean compress) throws IOException { this(document, sourcePage, appendContent, compress, false); } /** * Create a new PDPage content stream. * * @param document * The document the page is part of. * @param sourcePage * The page to write the contents to. * @param appendContent * Indicates whether content will be overwritten. If false all * previous content is deleted. * @param compress * Tell if the content stream should compress the page contents. * @param resetContext * Tell if the graphic context should be reseted. * @throws IOException * If there is an error writing to the page contents. */ public PDPageContentStream(PDDocument document, PDPage sourcePage, boolean appendContent, boolean compress, boolean resetContext) throws IOException { page = sourcePage; resources = page.getResources(); if (resources == null) { resources = new PDResources(); page.setResources(resources); } // Get the pdstream from the source page instead of creating a new one PDStream contents = sourcePage.getContents(); boolean hasContent = contents != null; // If request specifies the need to append to the document if (appendContent && hasContent) { // Create a pdstream to append new content PDStream contentsToAppend = new PDStream(document); // This will be the resulting COSStreamArray after existing and new // streams are merged COSStreamArray compoundStream = null; // If contents is already an array, a new stream is simply appended // to it if (contents.getStream() instanceof COSStreamArray) { compoundStream = (COSStreamArray) contents.getStream(); compoundStream.appendStream(contentsToAppend.getStream()); } else { // Creates the COSStreamArray and adds the current stream plus a // new one to it COSArray newArray = new COSArray(); newArray.add(contents.getCOSObject()); newArray.add(contentsToAppend.getCOSObject()); compoundStream = new COSStreamArray(newArray); } if (compress) { List<COSName> filters = new ArrayList<COSName>(); filters.add(COSName.FLATE_DECODE); contentsToAppend.setFilters(filters); } if (resetContext) { // create a new stream to encapsulate the existing stream PDStream saveGraphics = new PDStream(document); output = saveGraphics.createOutputStream(); // save the initial/unmodified graphics context saveGraphicsState(); close(); // ? if (compress) { List<COSName> filters = new ArrayList<COSName>(); filters.add(COSName.FLATE_DECODE); saveGraphics.setFilters(filters); } // insert the new stream at the beginning compoundStream.insertCOSStream(saveGraphics); } // Sets the compoundStream as page contents sourcePage.setContents(new PDStream(compoundStream)); output = contentsToAppend.createOutputStream(); if (resetContext) { // restore the initial/unmodified graphics context restoreGraphicsState(); } } else { if (hasContent) { LOG.warn("You are overwriting an existing content, you should use the append mode"); } contents = new PDStream(document); if (compress) { List<COSName> filters = new ArrayList<COSName>(); filters.add(COSName.FLATE_DECODE); contents.setFilters(filters); } sourcePage.setContents(contents); output = contents.createOutputStream(); } formatDecimal.setMaximumFractionDigits(10); formatDecimal.setGroupingUsed(false); } /** * Begin some text operations. * * @throws IOException * If there is an error writing to the stream or if you attempt * to nest beginText calls. */ public void beginText() throws IOException { if (inTextMode) { throw new IOException("Error: Nested beginText() calls are not allowed."); } appendRawCommands(BEGIN_TEXT); inTextMode = true; } /** * End some text operations. * * @throws IOException * If there is an error writing to the stream or if you attempt * to nest endText calls. */ public void endText() throws IOException { if (!inTextMode) { throw new IOException("Error: You must call beginText() before calling endText."); } appendRawCommands(END_TEXT); inTextMode = false; } /** * Set the font to draw text with. * * @param font * The font to use. * @param fontSize * The font size to draw the text. * @throws IOException * If there is an error writing the font information. */ public void setFont(PDFont font, float fontSize) throws IOException { String fontMapping = resources.addFont(font); appendRawCommands("/"); appendRawCommands(fontMapping); appendRawCommands(SPACE); appendRawCommands(fontSize); appendRawCommands(SPACE); appendRawCommands(SET_FONT); } /** * Draw an image at the x,y coordinates, with the default size of the image. * * @param image * The image to draw. * @param x * The x-coordinate to draw the image. * @param y * The y-coordinate to draw the image. * * @throws IOException * If there is an error writing to the stream. */ public void drawImage(PDXObjectImage image, float x, float y) throws IOException { drawXObject(image, x, y, image.getWidth(), image.getHeight()); } /** * Draw an xobject(form or image) at the x,y coordinates and a certain width * and height. * * @param xobject * The xobject to draw. * @param x * The x-coordinate to draw the image. * @param y * The y-coordinate to draw the image. * @param width * The width of the image to draw. * @param height * The height of the image to draw. * * @throws IOException * If there is an error writing to the stream. */ public void drawXObject(PDXObject xobject, float x, float y, float width, float height) throws IOException { AffineTransform transform = new AffineTransform(width, 0, 0, height, x, y); drawXObject(xobject, transform); } /** * Draw an xobject(form or image) using the given {@link AffineTransform} to * position the xobject. * * @param xobject * The xobject to draw. * @param transform * the transformation matrix * @throws IOException * If there is an error writing to the stream. */ public void drawXObject(PDXObject xobject, AffineTransform transform) throws IOException { if (inTextMode) { throw new IOException("Error: drawXObject is not allowed within a text block."); } String xObjectPrefix = null; if (xobject instanceof PDXObjectImage) { xObjectPrefix = "Im"; } else { xObjectPrefix = "Form"; } String objMapping = resources.addXObject(xobject, xObjectPrefix); saveGraphicsState(); appendRawCommands(SPACE); concatenate2CTM(transform); appendRawCommands(SPACE); appendRawCommands("/"); appendRawCommands(objMapping); appendRawCommands(SPACE); appendRawCommands(XOBJECT_DO); restoreGraphicsState(); } /** * The Td operator. A current text matrix will be replaced with a new one (1 * 0 0 1 x y). * * @param x * The x coordinate. * @param y * The y coordinate. * @throws IOException * If there is an error writing to the stream. */ public void moveTextPositionByAmount(float x, float y) throws IOException { if (!inTextMode) { throw new IOException("Error: must call beginText() before moveTextPositionByAmount"); } appendRawCommands(x); appendRawCommands(SPACE); appendRawCommands(y); appendRawCommands(SPACE); appendRawCommands(MOVE_TEXT_POSITION); } /** * The Tm operator. Sets the text matrix to the given values. A current text * matrix will be replaced with the new one. * * @param a * The a value of the matrix. * @param b * The b value of the matrix. * @param c * The c value of the matrix. * @param d * The d value of the matrix. * @param e * The e value of the matrix. * @param f * The f value of the matrix. * @throws IOException * If there is an error writing to the stream. */ public void setTextMatrix(double a, double b, double c, double d, double e, double f) throws IOException { if (!inTextMode) { throw new IOException("Error: must call beginText() before setTextMatrix"); } appendRawCommands(a); appendRawCommands(SPACE); appendRawCommands(b); appendRawCommands(SPACE); appendRawCommands(c); appendRawCommands(SPACE); appendRawCommands(d); appendRawCommands(SPACE); appendRawCommands(e); appendRawCommands(SPACE); appendRawCommands(f); appendRawCommands(SPACE); appendRawCommands(SET_TEXT_MATRIX); } /** * The Tm operator. Sets the text matrix to the given values. A current text * matrix will be replaced with the new one. * * @param matrix * the transformation matrix * @throws IOException * If there is an error writing to the stream. */ public void setTextMatrix(AffineTransform matrix) throws IOException { if (!inTextMode) { throw new IOException("Error: must call beginText() before setTextMatrix"); } appendMatrix(matrix); appendRawCommands(SET_TEXT_MATRIX); } /** * The Tm operator. Sets the text matrix to the given scaling and * translation values. A current text matrix will be replaced with the new * one. * * @param sx * The scaling factor in x-direction. * @param sy * The scaling factor in y-direction. * @param tx * The translation value in x-direction. * @param ty * The translation value in y-direction. * @throws IOException * If there is an error writing to the stream. */ public void setTextScaling(double sx, double sy, double tx, double ty) throws IOException { setTextMatrix(sx, 0, 0, sy, tx, ty); } /** * The Tm operator. Sets the text matrix to the given translation values. A * current text matrix will be replaced with the new one. * * @param tx * The translation value in x-direction. * @param ty * The translation value in y-direction. * @throws IOException * If there is an error writing to the stream. */ public void setTextTranslation(double tx, double ty) throws IOException { setTextMatrix(1, 0, 0, 1, tx, ty); } /** * The Tm operator. Sets the text matrix to the given rotation and * translation values. A current text matrix will be replaced with the new * one. * * @param angle * The angle used for the counterclockwise rotation in radians. * @param tx * The translation value in x-direction. * @param ty * The translation value in y-direction. * @throws IOException * If there is an error writing to the stream. */ public void setTextRotation(double angle, double tx, double ty) throws IOException { double angleCos = Math.cos(angle); double angleSin = Math.sin(angle); setTextMatrix(angleCos, angleSin, -angleSin, angleCos, tx, ty); } /** * The Cm operator. Concatenates the current transformation matrix with the * given values. * * @param a * The a value of the matrix. * @param b * The b value of the matrix. * @param c * The c value of the matrix. * @param d * The d value of the matrix. * @param e * The e value of the matrix. * @param f * The f value of the matrix. * @throws IOException * If there is an error writing to the stream. */ public void concatenate2CTM(double a, double b, double c, double d, double e, double f) throws IOException { appendRawCommands(a); appendRawCommands(SPACE); appendRawCommands(b); appendRawCommands(SPACE); appendRawCommands(c); appendRawCommands(SPACE); appendRawCommands(d); appendRawCommands(SPACE); appendRawCommands(e); appendRawCommands(SPACE); appendRawCommands(f); appendRawCommands(SPACE); appendRawCommands(CONCATENATE_MATRIX); } /** * The Cm operator. Concatenates the current transformation matrix with the * given {@link AffineTransform}. * * @param at * the transformation matrix * @throws IOException * If there is an error writing to the stream. */ public void concatenate2CTM(AffineTransform at) throws IOException { appendMatrix(at); appendRawCommands(CONCATENATE_MATRIX); } /** * This will draw a string at the current location on the screen. * * @param text * The text to draw. * @throws IOException * If an io exception occurs. */ public void drawString(String text) throws IOException { if (!inTextMode) { throw new IOException("Error: must call beginText() before drawString"); } COSString string = new COSString(text); ByteArrayOutputStream buffer = new ByteArrayOutputStream(); string.writePDF(buffer); appendRawCommands(buffer.toByteArray()); appendRawCommands(SPACE); appendRawCommands(SHOW_TEXT); } /** * Set the stroking color space. This will add the colorspace to the * PDResources if necessary. * * @param colorSpace * The colorspace to write. * @throws IOException * If there is an error writing the colorspace. */ public void setStrokingColorSpace(PDColorSpace colorSpace) throws IOException { currentStrokingColorSpace = colorSpace; writeColorSpace(colorSpace); appendRawCommands(SET_STROKING_COLORSPACE); } /** * Set the stroking color space. This will add the colorspace to the * PDResources if necessary. * * @param colorSpace * The colorspace to write. * @throws IOException * If there is an error writing the colorspace. */ public void setNonStrokingColorSpace(PDColorSpace colorSpace) throws IOException { currentNonStrokingColorSpace = colorSpace; writeColorSpace(colorSpace); appendRawCommands(SET_NON_STROKING_COLORSPACE); } private void writeColorSpace(PDColorSpace colorSpace) throws IOException { COSName key = null; if (colorSpace instanceof PDDeviceGray || colorSpace instanceof PDDeviceRGB || colorSpace instanceof PDDeviceCMYK) { key = COSName.getPDFName(colorSpace.getName()); } else { COSDictionary colorSpaces = (COSDictionary) resources.getCOSDictionary() .getDictionaryObject(COSName.COLORSPACE); if (colorSpaces == null) { colorSpaces = new COSDictionary(); resources.getCOSDictionary().setItem(COSName.COLORSPACE, colorSpaces); } key = colorSpaces.getKeyForValue(colorSpace.getCOSObject()); if (key == null) { int counter = 0; String csName = "CS"; while (colorSpaces.containsValue(csName + counter)) { counter++; } key = COSName.getPDFName(csName + counter); colorSpaces.setItem(key, colorSpace); } } key.writePDF(output); appendRawCommands(SPACE); } /** * Set the color components of current stroking colorspace. * * @param components * The components to set for the current color. * @throws IOException * If there is an error while writing to the stream. */ public void setStrokingColor(float[] components) throws IOException { for (int i = 0; i < components.length; i++) { appendRawCommands(components[i]); appendRawCommands(SPACE); } if (currentStrokingColorSpace instanceof PDSeparation || currentStrokingColorSpace instanceof PDPattern || currentStrokingColorSpace instanceof PDDeviceN || currentStrokingColorSpace instanceof PDICCBased) { appendRawCommands(SET_STROKING_COLOR_COMPLEX); } else { appendRawCommands(SET_STROKING_COLOR_SIMPLE); } } /** * Set the stroking color, specified as RGB. * * @param color * The color to set. * @throws IOException * If an IO error occurs while writing to the stream. */ public void setStrokingColor(Color color) throws IOException { ColorSpace colorSpace = color.getColorSpace(); if (colorSpace.getType() == ColorSpace.TYPE_RGB) { setStrokingColor(color.getRed(), color.getGreen(), color.getBlue()); } else if (colorSpace.getType() == ColorSpace.TYPE_GRAY) { color.getColorComponents(colorComponents); setStrokingColor(colorComponents[0]); } else if (colorSpace.getType() == ColorSpace.TYPE_CMYK) { color.getColorComponents(colorComponents); setStrokingColor(colorComponents[0], colorComponents[1], colorComponents[2], colorComponents[3]); } else { throw new IOException("Error: unknown colorspace:" + colorSpace); } } /** * Set the non stroking color, specified as RGB. * * @param color * The color to set. * @throws IOException * If an IO error occurs while writing to the stream. */ public void setNonStrokingColor(Color color) throws IOException { ColorSpace colorSpace = color.getColorSpace(); if (colorSpace.getType() == ColorSpace.TYPE_RGB) { setNonStrokingColor(color.getRed(), color.getGreen(), color.getBlue()); } else if (colorSpace.getType() == ColorSpace.TYPE_GRAY) { color.getColorComponents(colorComponents); setNonStrokingColor(colorComponents[0]); } else if (colorSpace.getType() == ColorSpace.TYPE_CMYK) { color.getColorComponents(colorComponents); setNonStrokingColor(colorComponents[0], colorComponents[1], colorComponents[2], colorComponents[3]); } else { throw new IOException("Error: unknown colorspace:" + colorSpace); } } /** * Set the stroking color, specified as RGB, 0-255. * * @param r * The red value. * @param g * The green value. * @param b * The blue value. * @throws IOException * If an IO error occurs while writing to the stream. */ public void setStrokingColor(int r, int g, int b) throws IOException { appendRawCommands(r / 255d); appendRawCommands(SPACE); appendRawCommands(g / 255d); appendRawCommands(SPACE); appendRawCommands(b / 255d); appendRawCommands(SPACE); appendRawCommands(RG_STROKING); } /** * Set the stroking color, specified as CMYK, 0-255. * * @param c * The cyan value. * @param m * The magenta value. * @param y * The yellow value. * @param k * The black value. * @throws IOException * If an IO error occurs while writing to the stream. */ public void setStrokingColor(int c, int m, int y, int k) throws IOException { appendRawCommands(c / 255d); appendRawCommands(SPACE); appendRawCommands(m / 255d); appendRawCommands(SPACE); appendRawCommands(y / 255d); appendRawCommands(SPACE); appendRawCommands(k / 255d); appendRawCommands(SPACE); appendRawCommands(K_STROKING); } /** * Set the stroking color, specified as CMYK, 0.0-1.0. * * @param c * The cyan value. * @param m * The magenta value. * @param y * The yellow value. * @param k * The black value. * @throws IOException * If an IO error occurs while writing to the stream. */ public void setStrokingColor(double c, double m, double y, double k) throws IOException { appendRawCommands(c); appendRawCommands(SPACE); appendRawCommands(m); appendRawCommands(SPACE); appendRawCommands(y); appendRawCommands(SPACE); appendRawCommands(k); appendRawCommands(SPACE); appendRawCommands(K_STROKING); } /** * Set the stroking color, specified as grayscale, 0-255. * * @param g * The gray value. * @throws IOException * If an IO error occurs while writing to the stream. */ public void setStrokingColor(int g) throws IOException { appendRawCommands(g / 255d); appendRawCommands(SPACE); appendRawCommands(G_STROKING); } /** * Set the stroking color, specified as Grayscale 0.0-1.0. * * @param g * The gray value. * @throws IOException * If an IO error occurs while writing to the stream. */ public void setStrokingColor(double g) throws IOException { appendRawCommands(g); appendRawCommands(SPACE); appendRawCommands(G_STROKING); } /** * Set the color components of current non stroking colorspace. * * @param components * The components to set for the current color. * @throws IOException * If there is an error while writing to the stream. */ public void setNonStrokingColor(float[] components) throws IOException { for (int i = 0; i < components.length; i++) { appendRawCommands(components[i]); appendRawCommands(SPACE); } if (currentNonStrokingColorSpace instanceof PDSeparation || currentNonStrokingColorSpace instanceof PDPattern || currentNonStrokingColorSpace instanceof PDDeviceN || currentNonStrokingColorSpace instanceof PDICCBased) { appendRawCommands(SET_NON_STROKING_COLOR_COMPLEX); } else { appendRawCommands(SET_NON_STROKING_COLOR_SIMPLE); } } /** * Set the non stroking color, specified as RGB, 0-255. * * @param r * The red value. * @param g * The green value. * @param b * The blue value. * @throws IOException * If an IO error occurs while writing to the stream. */ public void setNonStrokingColor(int r, int g, int b) throws IOException { appendRawCommands(r / 255d); appendRawCommands(SPACE); appendRawCommands(g / 255d); appendRawCommands(SPACE); appendRawCommands(b / 255d); appendRawCommands(SPACE); appendRawCommands(RG_NON_STROKING); } /** * Set the non stroking color, specified as CMYK, 0-255. * * @param c * The cyan value. * @param m * The magenta value. * @param y * The yellow value. * @param k * The black value. * @throws IOException * If an IO error occurs while writing to the stream. */ public void setNonStrokingColor(int c, int m, int y, int k) throws IOException { appendRawCommands(c / 255d); appendRawCommands(SPACE); appendRawCommands(m / 255d); appendRawCommands(SPACE); appendRawCommands(y / 255d); appendRawCommands(SPACE); appendRawCommands(k / 255d); appendRawCommands(SPACE); appendRawCommands(K_NON_STROKING); } /** * Set the non stroking color, specified as CMYK, 0.0-1.0. * * @param c * The cyan value. * @param m * The magenta value. * @param y * The yellow value. * @param k * The black value. * @throws IOException * If an IO error occurs while writing to the stream. */ public void setNonStrokingColor(double c, double m, double y, double k) throws IOException { appendRawCommands(c); appendRawCommands(SPACE); appendRawCommands(m); appendRawCommands(SPACE); appendRawCommands(y); appendRawCommands(SPACE); appendRawCommands(k); appendRawCommands(SPACE); appendRawCommands(K_NON_STROKING); } /** * Set the non stroking color, specified as grayscale, 0-255. * * @param g * The gray value. * @throws IOException * If an IO error occurs while writing to the stream. */ public void setNonStrokingColor(int g) throws IOException { appendRawCommands(g / 255d); appendRawCommands(SPACE); appendRawCommands(G_NON_STROKING); } /** * Set the non stroking color, specified as Grayscale 0.0-1.0. * * @param g * The gray value. * @throws IOException * If an IO error occurs while writing to the stream. */ public void setNonStrokingColor(double g) throws IOException { appendRawCommands(g); appendRawCommands(SPACE); appendRawCommands(G_NON_STROKING); } /** * Add a rectangle to the current path. * * @param x * The lower left x coordinate. * @param y * The lower left y coordinate. * @param width * The width of the rectangle. * @param height * The height of the rectangle. * @throws IOException * If there is an error while drawing on the screen. */ public void addRect(float x, float y, float width, float height) throws IOException { if (inTextMode) { throw new IOException("Error: addRect is not allowed within a text block."); } appendRawCommands(x); appendRawCommands(SPACE); appendRawCommands(y); appendRawCommands(SPACE); appendRawCommands(width); appendRawCommands(SPACE); appendRawCommands(height); appendRawCommands(SPACE); appendRawCommands(RECTANGLE); } /** * Draw a rectangle on the page using the current non stroking color. * * @param x * The lower left x coordinate. * @param y * The lower left y coordinate. * @param width * The width of the rectangle. * @param height * The height of the rectangle. * @throws IOException * If there is an error while drawing on the screen. */ public void fillRect(float x, float y, float width, float height) throws IOException { if (inTextMode) { throw new IOException("Error: fillRect is not allowed within a text block."); } addRect(x, y, width, height); fill(PathIterator.WIND_NON_ZERO); } /** * Append a cubic Bzier curve to the current path. The curve extends from * the current point to the point (x3 , y3 ), using (x1 , y1 ) and (x2 , y2 * ) as the Bzier control points * * @param x1 * x coordinate of the point 1 * @param y1 * y coordinate of the point 1 * @param x2 * x coordinate of the point 2 * @param y2 * y coordinate of the point 2 * @param x3 * x coordinate of the point 3 * @param y3 * y coordinate of the point 3 * @throws IOException * If there is an error while adding the . */ public void addBezier312(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException { if (inTextMode) { throw new IOException("Error: addBezier312 is not allowed within a text block."); } appendRawCommands(x1); appendRawCommands(SPACE); appendRawCommands(y1); appendRawCommands(SPACE); appendRawCommands(x2); appendRawCommands(SPACE); appendRawCommands(y2); appendRawCommands(SPACE); appendRawCommands(x3); appendRawCommands(SPACE); appendRawCommands(y3); appendRawCommands(SPACE); appendRawCommands(BEZIER_312); } /** * Append a cubic Bzier curve to the current path. The curve extends from * the current point to the point (x3 , y3 ), using the current point and * (x2 , y2 ) as the Bzier control points * * @param x2 * x coordinate of the point 2 * @param y2 * y coordinate of the point 2 * @param x3 * x coordinate of the point 3 * @param y3 * y coordinate of the point 3 * @throws IOException * If there is an error while adding the . */ public void addBezier32(float x2, float y2, float x3, float y3) throws IOException { if (inTextMode) { throw new IOException("Error: addBezier32 is not allowed within a text block."); } appendRawCommands(x2); appendRawCommands(SPACE); appendRawCommands(y2); appendRawCommands(SPACE); appendRawCommands(x3); appendRawCommands(SPACE); appendRawCommands(y3); appendRawCommands(SPACE); appendRawCommands(BEZIER_32); } /** * Append a cubic Bzier curve to the current path. The curve extends from * the current point to the point (x3 , y3 ), using (x1 , y1 ) and (x3 , y3 * ) as the Bzier control points * * @param x1 * x coordinate of the point 1 * @param y1 * y coordinate of the point 1 * @param x3 * x coordinate of the point 3 * @param y3 * y coordinate of the point 3 * @throws IOException * If there is an error while adding the . */ public void addBezier31(float x1, float y1, float x3, float y3) throws IOException { if (inTextMode) { throw new IOException("Error: addBezier31 is not allowed within a text block."); } appendRawCommands(x1); appendRawCommands(SPACE); appendRawCommands(y1); appendRawCommands(SPACE); appendRawCommands(x3); appendRawCommands(SPACE); appendRawCommands(y3); appendRawCommands(SPACE); appendRawCommands(BEZIER_313); } /** * Add a line to the given coordinate. * * @param x * The x coordinate. * @param y * The y coordinate. * @throws IOException * If there is an error while adding the line. */ public void moveTo(float x, float y) throws IOException { if (inTextMode) { throw new IOException("Error: moveTo is not allowed within a text block."); } appendRawCommands(x); appendRawCommands(SPACE); appendRawCommands(y); appendRawCommands(SPACE); appendRawCommands(MOVE_TO); } /** * Add a move to the given coordinate. * * @param x * The x coordinate. * @param y * The y coordinate. * @throws IOException * If there is an error while adding the line. */ public void lineTo(float x, float y) throws IOException { if (inTextMode) { throw new IOException("Error: lineTo is not allowed within a text block."); } appendRawCommands(x); appendRawCommands(SPACE); appendRawCommands(y); appendRawCommands(SPACE); appendRawCommands(LINE_TO); } /** * add a line to the current path. * * @param xStart * The start x coordinate. * @param yStart * The start y coordinate. * @param xEnd * The end x coordinate. * @param yEnd * The end y coordinate. * @throws IOException * If there is an error while adding the line. */ public void addLine(float xStart, float yStart, float xEnd, float yEnd) throws IOException { if (inTextMode) { throw new IOException("Error: addLine is not allowed within a text block."); } // moveTo moveTo(xStart, yStart); // lineTo lineTo(xEnd, yEnd); } /** * Draw a line on the page using the current non stroking color and the * current line width. * * @param xStart * The start x coordinate. * @param yStart * The start y coordinate. * @param xEnd * The end x coordinate. * @param yEnd * The end y coordinate. * @throws IOException * If there is an error while drawing on the screen. */ public void drawLine(float xStart, float yStart, float xEnd, float yEnd) throws IOException { if (inTextMode) { throw new IOException("Error: drawLine is not allowed within a text block."); } addLine(xStart, yStart, xEnd, yEnd); // stroke stroke(); } /** * Add a polygon to the current path. * * @param x * x coordinate of each points * @param y * y coordinate of each points * @throws IOException * If there is an error while drawing on the screen. */ public void addPolygon(float[] x, float[] y) throws IOException { if (inTextMode) { throw new IOException("Error: addPolygon is not allowed within a text block."); } if (x.length != y.length) { throw new IOException("Error: some points are missing coordinate"); } for (int i = 0; i < x.length; i++) { if (i == 0) { moveTo(x[i], y[i]); } else { lineTo(x[i], y[i]); } } closeSubPath(); } /** * Draw a polygon on the page using the current non stroking color. * * @param x * x coordinate of each points * @param y * y coordinate of each points * @throws IOException * If there is an error while drawing on the screen. */ public void drawPolygon(float[] x, float[] y) throws IOException { if (inTextMode) { throw new IOException("Error: drawPolygon is not allowed within a text block."); } addPolygon(x, y); stroke(); } /** * Draw and fill a polygon on the page using the current non stroking color. * * @param x * x coordinate of each points * @param y * y coordinate of each points * @throws IOException * If there is an error while drawing on the screen. */ public void fillPolygon(float[] x, float[] y) throws IOException { if (inTextMode) { throw new IOException("Error: fillPolygon is not allowed within a text block."); } addPolygon(x, y); fill(PathIterator.WIND_NON_ZERO); } /** * Stroke the path. * * @throws IOException * If there is an error while stroking the path. */ public void stroke() throws IOException { if (inTextMode) { throw new IOException("Error: stroke is not allowed within a text block."); } appendRawCommands(STROKE); } /** * Close and stroke the path. * * @throws IOException * If there is an error while closing and stroking the path. */ public void closeAndStroke() throws IOException { if (inTextMode) { throw new IOException("Error: closeAndStroke is not allowed within a text block."); } appendRawCommands(CLOSE_STROKE); } /** * Fill the path. * * @param windingRule * the winding rule to be used for filling * * @throws IOException * If there is an error while filling the path. */ public void fill(int windingRule) throws IOException { if (inTextMode) { throw new IOException("Error: fill is not allowed within a text block."); } if (windingRule == PathIterator.WIND_NON_ZERO) { appendRawCommands(FILL_NON_ZERO); } else if (windingRule == PathIterator.WIND_EVEN_ODD) { appendRawCommands(FILL_EVEN_ODD); } else { throw new IOException("Error: unknown value for winding rule"); } } /** * Close subpath. * * @throws IOException * If there is an error while closing the subpath. */ public void closeSubPath() throws IOException { if (inTextMode) { throw new IOException("Error: closeSubPath is not allowed within a text block."); } appendRawCommands(CLOSE_SUBPATH); } /** * Clip path. * * @param windingRule * the winding rule to be used for clipping * * @throws IOException * If there is an error while clipping the path. */ public void clipPath(int windingRule) throws IOException { if (inTextMode) { throw new IOException("Error: clipPath is not allowed within a text block."); } if (windingRule == PathIterator.WIND_NON_ZERO) { appendRawCommands(CLIP_PATH_NON_ZERO); appendRawCommands(NOP); } else if (windingRule == PathIterator.WIND_EVEN_ODD) { appendRawCommands(CLIP_PATH_EVEN_ODD); appendRawCommands(NOP); } else { throw new IOException("Error: unknown value for winding rule"); } } /** * Set linewidth to the given value. * * @param lineWidth * The width which is used for drwaing. * @throws IOException * If there is an error while drawing on the screen. */ public void setLineWidth(float lineWidth) throws IOException { if (inTextMode) { throw new IOException("Error: setLineWidth is not allowed within a text block."); } appendRawCommands(lineWidth); appendRawCommands(SPACE); appendRawCommands(LINE_WIDTH); } /** * Set the line join style. * * @param lineJoinStyle * 0 for miter join, 1 for round join, and 2 for bevel join. * @throws IOException * If there is an error while writing to the stream. */ public void setLineJoinStyle(int lineJoinStyle) throws IOException { if (inTextMode) { throw new IOException("Error: setLineJoinStyle is not allowed within a text block."); } if (lineJoinStyle >= 0 && lineJoinStyle <= 2) { appendRawCommands(Integer.toString(lineJoinStyle)); appendRawCommands(SPACE); appendRawCommands(LINE_JOIN_STYLE); } else { throw new IOException("Error: unknown value for line join style"); } } /** * Set the line cap style. * * @param lineCapStyle * 0 for butt cap, 1 for round cap, and 2 for projecting square * cap. * @throws IOException * If there is an error while writing to the stream. */ public void setLineCapStyle(int lineCapStyle) throws IOException { if (inTextMode) { throw new IOException("Error: setLineCapStyle is not allowed within a text block."); } if (lineCapStyle >= 0 && lineCapStyle <= 2) { appendRawCommands(Integer.toString(lineCapStyle)); appendRawCommands(SPACE); appendRawCommands(LINE_CAP_STYLE); } else { throw new IOException("Error: unknown value for line cap style"); } } /** * Set the line dash pattern. * * @param pattern * The pattern array * @param phase * The phase of the pattern * @throws IOException * If there is an error while writing to the stream. */ public void setLineDashPattern(float[] pattern, float phase) throws IOException { if (inTextMode) { throw new IOException("Error: setLineDashPattern is not allowed within a text block."); } appendRawCommands(OPENING_BRACKET); for (float value : pattern) { appendRawCommands(value); appendRawCommands(SPACE); } appendRawCommands(CLOSING_BRACKET); appendRawCommands(SPACE); appendRawCommands(phase); appendRawCommands(SPACE); appendRawCommands(LINE_DASH_PATTERN); } /** * Begin a marked content sequence. * * @param tag * the tag * @throws IOException * if an I/O error occurs */ public void beginMarkedContentSequence(COSName tag) throws IOException { appendCOSName(tag); appendRawCommands(SPACE); appendRawCommands(BMC); } /** * Begin a marked content sequence with a reference to an entry in the page * resources' Properties dictionary. * * @param tag * the tag * @param propsName * the properties reference * @throws IOException * if an I/O error occurs */ public void beginMarkedContentSequence(COSName tag, COSName propsName) throws IOException { appendCOSName(tag); appendRawCommands(SPACE); appendCOSName(propsName); appendRawCommands(SPACE); appendRawCommands(BDC); } /** * End a marked content sequence. * * @throws IOException * if an I/O error occurs */ public void endMarkedContentSequence() throws IOException { appendRawCommands(EMC); } /** * q operator. Saves the current graphics state. * * @throws IOException * If an error occurs while writing to the stream. */ public void saveGraphicsState() throws IOException { appendRawCommands(SAVE_GRAPHICS_STATE); } /** * Q operator. Restores the current graphics state. * * @throws IOException * If an error occurs while writing to the stream. */ public void restoreGraphicsState() throws IOException { appendRawCommands(RESTORE_GRAPHICS_STATE); } /** * This will append raw commands to the content stream. * * @param commands * The commands to append to the stream. * @throws IOException * If an error occurs while writing to the stream. */ public void appendRawCommands(String commands) throws IOException { appendRawCommands(commands.getBytes("ISO-8859-1")); } /** * This will append raw commands to the content stream. * * @param commands * The commands to append to the stream. * @throws IOException * If an error occurs while writing to the stream. */ public void appendRawCommands(byte[] commands) throws IOException { output.write(commands); } /** * This will append raw commands to the content stream. * * @param data * Append a raw byte to the stream. * * @throws IOException * If an error occurs while writing to the stream. */ public void appendRawCommands(int data) throws IOException { output.write(data); } /** * This will append raw commands to the content stream. * * @param data * Append a formatted double value to the stream. * * @throws IOException * If an error occurs while writing to the stream. */ public void appendRawCommands(double data) throws IOException { appendRawCommands(formatDecimal.format(data)); } /** * This will append raw commands to the content stream. * * @param data * Append a formatted float value to the stream. * * @throws IOException * If an error occurs while writing to the stream. */ public void appendRawCommands(float data) throws IOException { appendRawCommands(formatDecimal.format(data)); } /** * This will append a {@link COSName} to the content stream. * * @param name * the name * @throws IOException * If an error occurs while writing to the stream. */ public void appendCOSName(COSName name) throws IOException { name.writePDF(output); } private void appendMatrix(AffineTransform transform) throws IOException { double[] values = new double[6]; transform.getMatrix(values); for (double v : values) { appendRawCommands(v); appendRawCommands(SPACE); } } /** * Close the content stream. This must be called when you are done with this * object. * * @throws IOException * If the underlying stream has a problem being written to. */ public void close() throws IOException { output.close(); currentNonStrokingColorSpace = null; currentStrokingColorSpace = null; page = null; resources = null; } public PDResources getResources() { return resources; } public void setResources(PDResources resources) { this.resources = resources; } }