All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.helger.pdflayout4.pdfbox.PDPageContentStreamExt Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2014-2021 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * 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 com.helger.pdflayout4.pdfbox;

import java.awt.Color;
import java.awt.geom.AffineTransform;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.text.NumberFormat;
import java.util.Locale;

import javax.annotation.Nonnull;
import javax.annotation.concurrent.NotThreadSafe;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSNumber;
import org.apache.pdfbox.pdfwriter.COSWriter;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentHelper;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.pdfbox.pdmodel.documentinterchange.markedcontent.PDPropertyList;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceN;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
import org.apache.pdfbox.pdmodel.graphics.color.PDPattern;
import org.apache.pdfbox.pdmodel.graphics.color.PDSeparation;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.graphics.image.PDInlineImage;
import org.apache.pdfbox.pdmodel.graphics.shading.PDShading;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
import org.apache.pdfbox.util.Charsets;
import org.apache.pdfbox.util.Matrix;
import org.apache.pdfbox.util.NumberFormatUtil;

import com.helger.commons.annotation.CodingStyleguideUnaware;
import com.helger.commons.collection.NonBlockingStack;

/**
 * Provides the ability to write to a page content stream.
* Extensions/changes for this project: *
    *
  • Speed up in text drawing
  • *
  • Removed all deprecated methods
  • *
  • Allowing to prepend content
  • *
* * @author Ben Litchfield */ @CodingStyleguideUnaware @NotThreadSafe public final class PDPageContentStreamExt implements Closeable { private static final Log LOGGER = LogFactory.getLog (PDPageContentStreamExt.class); private final PDDocument m_aDoc; protected OutputStream m_aOS; private PDResources m_aResources; private boolean m_bInTextMode = false; private final NonBlockingStack m_aFontStack = new NonBlockingStack <> (); private final NonBlockingStack m_aNonStrokingColorSpaceStack = new NonBlockingStack <> (); private final NonBlockingStack m_aStrokingColorSpaceStack = new NonBlockingStack <> (); // number format private final NumberFormat m_aFormatDecimal = NumberFormat.getNumberInstance (Locale.US); private final byte [] m_aFormatBuffer = new byte [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 PDPageContentStreamExt (final PDDocument document, final PDPage sourcePage) throws IOException { this (document, sourcePage, PDPageContentStream.AppendMode.OVERWRITE, 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 PDPageContentStreamExt (final PDDocument document, final PDPage sourcePage, final PDPageContentStream.AppendMode appendContent, final boolean compress) throws IOException { this (document, sourcePage, appendContent, compress, false); } /** * Create a new PDPage content stream. * * @param aDoc * 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 PDPageContentStreamExt (final PDDocument aDoc, final PDPage sourcePage, final PDPageContentStream.AppendMode appendContent, final boolean compress, final boolean resetContext) throws IOException { m_aDoc = aDoc; final COSName filter = compress ? COSName.FLATE_DECODE : null; // If request specifies the need to append to the document if (!appendContent.isOverwrite () && sourcePage.hasContents ()) { // Create a stream to append new content final PDStream contentsToAppend = new PDStream (aDoc); // Add new stream to contents array final COSBase contents = sourcePage.getCOSObject ().getDictionaryObject (COSName.CONTENTS); COSArray array; if (contents instanceof COSArray) { // If contents is already an array, a new stream is simply appended to // it array = (COSArray) contents; } else { // Creates a new array and adds the current stream plus a new one to it array = new COSArray (); array.add (contents); } if (appendContent.isPrepend ()) array.add (0, contentsToAppend.getCOSObject ()); else array.add (contentsToAppend); // save the initial/unmodified graphics context if (resetContext) { // create a new stream to encapsulate the existing stream final PDStream saveGraphics = new PDStream (aDoc); m_aOS = saveGraphics.createOutputStream (filter); // save the initial/unmodified graphics context saveGraphicsState (); close (); // insert the new stream at the beginning array.add (0, saveGraphics.getCOSObject ()); } // Sets the compoundStream as page contents sourcePage.getCOSObject ().setItem (COSName.CONTENTS, array); m_aOS = contentsToAppend.createOutputStream (filter); // restore the initial/unmodified graphics context if (resetContext) { restoreGraphicsState (); } } else { if (sourcePage.hasContents ()) { LOGGER.warn ("You are overwriting an existing content, you should use the append mode"); } final PDStream contents = new PDStream (aDoc); sourcePage.setContents (contents); m_aOS = contents.createOutputStream (filter); } // this has to be done here, as the resources will be set to null when // resetting the content stream m_aResources = sourcePage.getResources (); if (m_aResources == null) { m_aResources = new PDResources (); sourcePage.setResources (m_aResources); } // configure NumberFormat m_aFormatDecimal.setMaximumFractionDigits (5); m_aFormatDecimal.setGroupingUsed (false); } /** * Create a new appearance stream. Note that this is not actually a "page" * content stream. * * @param doc * The document the page is part of. * @param appearance * The appearance stream to write to. * @throws IOException * If there is an error writing to the page contents. */ public PDPageContentStreamExt (final PDDocument doc, final PDAppearanceStream appearance) throws IOException { this (doc, appearance, appearance.getStream ().createOutputStream ()); } /** * Create a new appearance stream. Note that this is not actually a "page" * content stream. * * @param aDoc * The document the appearance is part of. * @param appearance * The appearance stream to add to. * @param outputStream * The appearances output stream to write to. */ public PDPageContentStreamExt (final PDDocument aDoc, final PDAppearanceStream appearance, final OutputStream outputStream) { m_aDoc = aDoc; m_aOS = outputStream; m_aResources = appearance.getResources (); m_aFormatDecimal.setMaximumFractionDigits (4); m_aFormatDecimal.setGroupingUsed (false); } @Nonnull final OutputStream getOutput () { return m_aOS; } /** * Begin some text operations. * * @throws IOException * If there is an error writing to the stream or if you attempt to * nest beginText calls. * @throws IllegalStateException * If the method was not allowed to be called at this time. */ public void beginText () throws IOException { if (m_bInTextMode) { throw new IllegalStateException ("Error: Nested beginText() calls are not allowed."); } writeOperator ((byte) 'B', (byte) 'T'); m_bInTextMode = true; } /** * End some text operations. * * @throws IOException * If there is an error writing to the stream or if you attempt to * nest endText calls. * @throws IllegalStateException * If the method was not allowed to be called at this time. */ public void endText () throws IOException { if (!m_bInTextMode) { throw new IllegalStateException ("Error: You must call beginText() before calling endText."); } writeOperator ((byte) 'E', (byte) 'T'); m_bInTextMode = false; } /** * Set the font and font size 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 (final PDFont font, final float fontSize) throws IOException { if (m_aFontStack.isEmpty ()) m_aFontStack.add (font); else m_aFontStack.set (m_aFontStack.size () - 1, font); PDDocumentHelper.handleFontSubset (m_aDoc, font); writeOperand (m_aResources.add (font)); writeOperand (fontSize); writeOperator ((byte) 'T', (byte) 'f'); } /** * Set the character spacing. The value shall be added to the horizontal or * vertical component of the glyph's displacement, depending on the writing * mode. * * @param spacing * character spacing * @throws IOException * If the content stream could not be written. */ public void setCharacterSpacing (final float spacing) throws IOException { writeOperand (spacing); writeOperator ((byte) 'T', (byte) 'c'); } /** * Shows the given text at the location specified by the current text matrix. * * @param text * The Unicode text to show. * @throws IOException * If an io exception occurs. */ public void showText (final String text) throws IOException { if (!m_bInTextMode) throw new IllegalStateException ("Must call beginText() before showText()"); if (m_aFontStack.isEmpty ()) throw new IllegalStateException ("Must call setFont() before showText()"); final PDFont font = m_aFontStack.peek (); // Unicode code points to keep when subsetting if (font.willBeSubset ()) { for (int offset = 0; offset < text.length ();) { final int codePoint = text.codePointAt (offset); font.addToSubset (codePoint); offset += Character.charCount (codePoint); } } COSWriter.writeString (font.encode (text), m_aOS); write ((byte) ' '); writeOperator ((byte) 'T', (byte) 'j'); } /** * Sets the text leading. * * @param leading * The leading in unscaled text units. * @throws IOException * If there is an error writing to the stream. */ public void setLeading (final double leading) throws IOException { writeOperand ((float) leading); writeOperator ((byte) 'T', (byte) 'L'); } /** * Move to the start of the next line of text. Requires the leading (see * {@link #setLeading}) to have been set. * * @throws IOException * If there is an error writing to the stream. */ public void newLine () throws IOException { if (!m_bInTextMode) throw new IllegalStateException ("Must call beginText() before newLine()"); writeOperator ((byte) 'T', (byte) '*'); } /** * The Td operator. Move to the start of the next line, offset from the start * of the current line by (tx, ty). * * @param tx * The x translation. * @param ty * The y translation. * @throws IOException * If there is an error writing to the stream. * @throws IllegalStateException * If the method was not allowed to be called at this time. */ public void newLineAtOffset (final float tx, final float ty) throws IOException { if (!m_bInTextMode) throw new IllegalStateException ("Error: must call beginText() before newLineAtOffset()"); writeOperand (tx); writeOperand (ty); writeOperator ((byte) 'T', (byte) 'd'); } /** * 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. * @throws IllegalStateException * If the method was not allowed to be called at this time. */ public void setTextMatrix (final Matrix matrix) throws IOException { if (!m_bInTextMode) throw new IllegalStateException ("Error: must call beginText() before setTextMatrix"); writeAffineTransform (matrix.createAffineTransform ()); writeOperator ((byte) 'T', (byte) 'm'); } /** * 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 (final PDImageXObject image, final float x, final float y) throws IOException { drawImage (image, x, y, image.getWidth (), image.getHeight ()); } /** * Draw an image at the x,y coordinates, with the given size. * * @param image * The image to draw. * @param x * The x-coordinate to draw the image. * @param y * The y-coordinate to draw the image. * @param width * The width to draw the image. * @param height * The height to draw the image. * @throws IOException * If there is an error writing to the stream. * @throws IllegalStateException * If the method was called within a text block. */ public void drawImage (final PDImageXObject image, final float x, final float y, final float width, final float height) throws IOException { if (m_bInTextMode) throw new IllegalStateException ("Error: drawImage is not allowed within a text block."); saveGraphicsState (); final AffineTransform transform = new AffineTransform (width, 0, 0, height, x, y); transform (new Matrix (transform)); writeOperand (m_aResources.add (image)); writeOperator ((byte) 'D', (byte) 'o'); restoreGraphicsState (); } /** * Draw an inline image at the x,y coordinates, with the default size of the * image. * * @param inlineImage * The inline image to draw. * @param x * The x-coordinate to draw the inline image. * @param y * The y-coordinate to draw the inline image. * @throws IOException * If there is an error writing to the stream. */ public void drawImage (final PDInlineImage inlineImage, final float x, final float y) throws IOException { drawImage (inlineImage, x, y, inlineImage.getWidth (), inlineImage.getHeight ()); } /** * Draw an inline image at the x,y coordinates and a certain width and height. * * @param inlineImage * The inline image to draw. * @param x * The x-coordinate to draw the inline image. * @param y * The y-coordinate to draw the inline image. * @param width * The width of the inline image to draw. * @param height * The height of the inline image to draw. * @throws IOException * If there is an error writing to the stream. * @throws IllegalStateException * If the method was called within a text block. */ public void drawImage (final PDInlineImage inlineImage, final float x, final float y, final float width, final float height) throws IOException { if (m_bInTextMode) throw new IllegalStateException ("Error: drawImage is not allowed within a text block."); saveGraphicsState (); transform (new Matrix (width, 0, 0, height, x, y)); // create the image dictionary final StringBuilder sb = new StringBuilder (); sb.append ("BI"); sb.append ("\n /W "); sb.append (inlineImage.getWidth ()); sb.append ("\n /H "); sb.append (inlineImage.getHeight ()); sb.append ("\n /CS "); sb.append ("/"); sb.append (inlineImage.getColorSpace ().getName ()); if (inlineImage.getDecode () != null && inlineImage.getDecode ().size () > 0) { sb.append ("\n /D "); sb.append ("["); for (final COSBase base : inlineImage.getDecode ()) { sb.append (((COSNumber) base).intValue ()); sb.append (" "); } sb.append ("]"); } if (inlineImage.isStencil ()) { sb.append ("\n /IM true"); } sb.append ("\n /BPC "); sb.append (inlineImage.getBitsPerComponent ()); // image dictionary write (sb.toString ()); writeLine (); // binary data writeOperator ((byte) 'I', (byte) 'D'); writeBytes (inlineImage.getData ()); writeLine (); writeOperator ((byte) 'E', (byte) 'I'); restoreGraphicsState (); } /** * Draws the given Form XObject at the current location. * * @param form * Form XObject * @throws IOException * if the content stream could not be written * @throws IllegalStateException * If the method was called within a text block. */ public void drawForm (final PDFormXObject form) throws IOException { if (m_bInTextMode) throw new IllegalStateException ("Error: drawForm is not allowed within a text block."); writeOperand (m_aResources.add (form)); writeOperator ((byte) 'D', (byte) 'o'); } /** * The cm operator. Concatenates the given matrix with the CTM. * * @param matrix * the transformation matrix * @throws IOException * If there is an error writing to the stream. */ public void transform (final Matrix matrix) throws IOException { writeAffineTransform (matrix.createAffineTransform ()); writeOperator ((byte) 'c', (byte) 'm'); } /** * q operator. Saves the current graphics state. * * @throws IOException * If an error occurs while writing to the stream. */ public void saveGraphicsState () throws IOException { if (!m_aFontStack.isEmpty ()) m_aFontStack.push (m_aFontStack.peek ()); if (!m_aStrokingColorSpaceStack.isEmpty ()) m_aStrokingColorSpaceStack.push (m_aStrokingColorSpaceStack.peek ()); if (!m_aNonStrokingColorSpaceStack.isEmpty ()) m_aNonStrokingColorSpaceStack.push (m_aNonStrokingColorSpaceStack.peek ()); writeOperator ((byte) 'q'); } /** * Q operator. Restores the current graphics state. * * @throws IOException * If an error occurs while writing to the stream. */ public void restoreGraphicsState () throws IOException { if (!m_aFontStack.isEmpty ()) m_aFontStack.pop (); if (!m_aStrokingColorSpaceStack.isEmpty ()) m_aStrokingColorSpaceStack.pop (); if (!m_aNonStrokingColorSpaceStack.isEmpty ()) m_aNonStrokingColorSpaceStack.pop (); writeOperator ((byte) 'Q'); } private COSName getName (final PDColorSpace colorSpace) { if (colorSpace instanceof PDDeviceGray || colorSpace instanceof PDDeviceRGB || colorSpace instanceof PDDeviceCMYK) return COSName.getPDFName (colorSpace.getName ()); return m_aResources.add (colorSpace); } /** * Sets the stroking color and, if necessary, the stroking color space. * * @param color * Color in a specific color space. * @throws IOException * If an IO error occurs while writing to the stream. */ public void setStrokingColor (@Nonnull final PDColor color) throws IOException { if (m_aStrokingColorSpaceStack.isEmpty () || m_aStrokingColorSpaceStack.peek () != color.getColorSpace ()) { writeOperand (getName (color.getColorSpace ())); writeOperator ((byte) 'C', (byte) 'S'); if (m_aStrokingColorSpaceStack.isEmpty ()) m_aStrokingColorSpaceStack.add (color.getColorSpace ()); else m_aStrokingColorSpaceStack.set (m_aNonStrokingColorSpaceStack.size () - 1, color.getColorSpace ()); } for (final float value : color.getComponents ()) writeOperand (value); if (color.getColorSpace () instanceof PDPattern) writeOperand (color.getPatternName ()); if (color.getColorSpace () instanceof PDPattern || color.getColorSpace () instanceof PDSeparation || color.getColorSpace () instanceof PDDeviceN || color.getColorSpace () instanceof PDICCBased) { writeOperator ((byte) 'S', (byte) 'C', (byte) 'N'); } else { writeOperator ((byte) 'S', (byte) 'C'); } } /** * Set the stroking color using an AWT color. Conversion uses the default sRGB * color space. * * @param color * The color to set. * @throws IOException * If an IO error occurs while writing to the stream. */ public void setStrokingColor (@Nonnull final Color color) throws IOException { final float [] components = new float [] { color.getRed () / 255f, color.getGreen () / 255f, color.getBlue () / 255f }; final PDColor pdColor = new PDColor (components, PDDeviceRGB.INSTANCE); setStrokingColor (pdColor); } /** * Set the stroking color in the DeviceRGB color space. Range is 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. * @throws IllegalArgumentException * If the parameters are invalid. */ public void setStrokingColor (final int r, final int g, final int b) throws IOException { if (_isOutside255Interval (r) || _isOutside255Interval (g) || _isOutside255Interval (b)) throw new IllegalArgumentException ("Parameters must be within 0..255, but are (" + r + "," + g + "," + b + ")"); writeOperand (r / 255f); writeOperand (g / 255f); writeOperand (b / 255f); writeOperator ((byte) 'R', (byte) 'G'); } /** * Set the stroking color in the DeviceCMYK color space. Range is 0..1 * * @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. * @throws IllegalArgumentException * If the parameters are invalid. */ public void setStrokingColor (final float c, final float m, final float y, final float k) throws IOException { if (_isOutsideOneInterval (c) || _isOutsideOneInterval (m) || _isOutsideOneInterval (y) || _isOutsideOneInterval (k)) { throw new IllegalArgumentException ("Parameters must be within 0..1, but are (" + c + "," + m + "," + y + "," + k + ")"); } writeOperand (c); writeOperand (m); writeOperand (y); writeOperand (k); writeOperator ((byte) 'K'); } /** * Set the stroking color in the DeviceGray color space. Range is 0..1. * * @param g * The gray value. * @throws IOException * If an IO error occurs while writing to the stream. * @throws IllegalArgumentException * If the parameter is invalid. */ public void setStrokingColor (final double g) throws IOException { if (_isOutsideOneInterval (g)) throw new IllegalArgumentException ("Parameter must be within 0..1, but is " + g); writeOperand ((float) g); writeOperator ((byte) 'G'); } /** * Sets the non-stroking color and, if necessary, the non-stroking color * space. * * @param color * Color in a specific color space. * @throws IOException * If an IO error occurs while writing to the stream. */ public void setNonStrokingColor (@Nonnull final PDColor color) throws IOException { if (m_aNonStrokingColorSpaceStack.isEmpty () || m_aNonStrokingColorSpaceStack.peek () != color.getColorSpace ()) { writeOperand (getName (color.getColorSpace ())); writeOperator ((byte) 'c', (byte) 's'); if (m_aNonStrokingColorSpaceStack.isEmpty ()) m_aNonStrokingColorSpaceStack.add (color.getColorSpace ()); else m_aNonStrokingColorSpaceStack.set (m_aNonStrokingColorSpaceStack.size () - 1, color.getColorSpace ()); } for (final float value : color.getComponents ()) writeOperand (value); if (color.getColorSpace () instanceof PDPattern) writeOperand (color.getPatternName ()); if (color.getColorSpace () instanceof PDPattern || color.getColorSpace () instanceof PDSeparation || color.getColorSpace () instanceof PDDeviceN || color.getColorSpace () instanceof PDICCBased) { writeOperator ((byte) 's', (byte) 'c', (byte) 'n'); } else { writeOperator ((byte) 's', (byte) 'c'); } } /** * Set the non-stroking color using an AWT color. Conversion uses the default * sRGB color space. * * @param color * The color to set. * @throws IOException * If an IO error occurs while writing to the stream. */ public void setNonStrokingColor (@Nonnull final Color color) throws IOException { final float [] components = new float [] { color.getRed () / 255f, color.getGreen () / 255f, color.getBlue () / 255f }; final PDColor pdColor = new PDColor (components, PDDeviceRGB.INSTANCE); setNonStrokingColor (pdColor); } /** * Set the non-stroking color in the DeviceRGB color space. Range is 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. * @throws IllegalArgumentException * If the parameters are invalid. */ public void setNonStrokingColor (final int r, final int g, final int b) throws IOException { if (_isOutside255Interval (r) || _isOutside255Interval (g) || _isOutside255Interval (b)) throw new IllegalArgumentException ("Parameters must be within 0..255, but are (" + r + "," + g + "," + b + ")"); writeOperand (r / 255f); writeOperand (g / 255f); writeOperand (b / 255f); writeOperator ((byte) 'r', (byte) 'g'); } /** * Set the non-stroking color in the DeviceCMYK color space. Range is 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. * @throws IllegalArgumentException * If the parameters are invalid. */ public void setNonStrokingColor (final int c, final int m, final int y, final int k) throws IOException { if (_isOutside255Interval (c) || _isOutside255Interval (m) || _isOutside255Interval (y) || _isOutside255Interval (k)) { throw new IllegalArgumentException ("Parameters must be within 0..255, but are (" + c + "," + m + "," + y + "," + k + ")"); } setNonStrokingColor (c / 255f, m / 255f, y / 255f, k / 255f); } /** * Set the non-stroking color in the DeviceRGB color space. Range is 0..1. * * @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 (final double c, final double m, final double y, final double k) throws IOException { if (_isOutsideOneInterval (c) || _isOutsideOneInterval (m) || _isOutsideOneInterval (y) || _isOutsideOneInterval (k)) { throw new IllegalArgumentException ("Parameters must be within 0..1, but are (" + c + "," + m + "," + y + "," + k + ")"); } writeOperand ((float) c); writeOperand ((float) m); writeOperand ((float) y); writeOperand ((float) k); writeOperator ((byte) 'k'); } /** * Set the non-stroking color in the DeviceGray color space. Range is 0..255. * * @param g * The gray value. * @throws IOException * If an IO error occurs while writing to the stream. * @throws IllegalArgumentException * If the parameter is invalid. */ public void setNonStrokingColor (final int g) throws IOException { if (_isOutside255Interval (g)) throw new IllegalArgumentException ("Parameter must be within 0..255, but is " + g); setNonStrokingColor (g / 255f); } /** * Set the non-stroking color in the DeviceGray color space. Range is 0..1. * * @param g * The gray value. * @throws IOException * If an IO error occurs while writing to the stream. * @throws IllegalArgumentException * If the parameter is invalid. */ public void setNonStrokingColor (final double g) throws IOException { if (_isOutsideOneInterval (g)) throw new IllegalArgumentException ("Parameter must be within 0..1, but is " + g); writeOperand ((float) g); writeOperator ((byte) 'g'); } /** * 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 the content stream could not be written. * @throws IllegalStateException * If the method was called within a text block. */ public void addRect (final float x, final float y, final float width, final float height) throws IOException { if (m_bInTextMode) throw new IllegalStateException ("Error: addRect is not allowed within a text block."); writeOperand (x); writeOperand (y); writeOperand (width); writeOperand (height); writeOperator ((byte) 'r', (byte) 'e'); } /** * Append a cubic Bézier 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 * Bézier 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 the content stream could not be written. * @throws IllegalStateException * If the method was called within a text block. */ public void curveTo (final float x1, final float y1, final float x2, final float y2, final float x3, final float y3) throws IOException { if (m_bInTextMode) throw new IllegalStateException ("Error: curveTo is not allowed within a text block."); writeOperand (x1); writeOperand (y1); writeOperand (x2); writeOperand (y2); writeOperand (x3); writeOperand (y3); writeOperator ((byte) 'c'); } /** * Append a cubic Bézier 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 Bézier 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 IllegalStateException * If the method was called within a text block. * @throws IOException * If the content stream could not be written. */ public void curveTo2 (final float x2, final float y2, final float x3, final float y3) throws IOException { if (m_bInTextMode) throw new IllegalStateException ("Error: curveTo2 is not allowed within a text block."); writeOperand (x2); writeOperand (y2); writeOperand (x3); writeOperand (y3); writeOperator ((byte) 'v'); } /** * Append a cubic Bézier 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 * Bézier 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 the content stream could not be written. * @throws IllegalStateException * If the method was called within a text block. */ public void curveTo1 (final float x1, final float y1, final float x3, final float y3) throws IOException { if (m_bInTextMode) throw new IllegalStateException ("Error: curveTo1 is not allowed within a text block."); writeOperand (x1); writeOperand (y1); writeOperand (x3); writeOperand (y3); writeOperator ((byte) 'y'); } /** * Move the current position to the given coordinates. * * @param x * The x coordinate. * @param y * The y coordinate. * @throws IOException * If the content stream could not be written. * @throws IllegalStateException * If the method was called within a text block. */ public void moveTo (final float x, final float y) throws IOException { if (m_bInTextMode) throw new IllegalStateException ("Error: moveTo is not allowed within a text block."); writeOperand (x); writeOperand (y); writeOperator ((byte) 'm'); } /** * Draw a line from the current position to the given coordinates. * * @param x * The x coordinate. * @param y * The y coordinate. * @throws IOException * If the content stream could not be written. * @throws IllegalStateException * If the method was called within a text block. */ public void lineTo (final float x, final float y) throws IOException { if (m_bInTextMode) throw new IllegalStateException ("Error: lineTo is not allowed within a text block."); writeOperand (x); writeOperand (y); writeOperator ((byte) 'l'); } /** * Stroke the path. * * @throws IOException * If the content stream could not be written * @throws IllegalStateException * If the method was called within a text block. */ public void stroke () throws IOException { if (m_bInTextMode) throw new IllegalStateException ("Error: stroke is not allowed within a text block."); writeOperator ((byte) 'S'); } /** * Close and stroke the path. * * @throws IOException * If the content stream could not be written * @throws IllegalStateException * If the method was called within a text block. */ public void closeAndStroke () throws IOException { if (m_bInTextMode) throw new IllegalStateException ("Error: closeAndStroke is not allowed within a text block."); writeOperator ((byte) 's'); } /** * Fills the path using the nonzero winding rule. * * @throws IOException * If the content stream could not be written * @throws IllegalStateException * If the method was called within a text block. */ public void fill () throws IOException { if (m_bInTextMode) throw new IllegalStateException ("Error: fill is not allowed within a text block."); writeOperator ((byte) 'f'); } /** * Fills the path using the even-odd winding rule. * * @throws IOException * If the content stream could not be written * @throws IllegalStateException * If the method was called within a text block. */ public void fillEvenOdd () throws IOException { if (m_bInTextMode) { throw new IllegalStateException ("Error: fill is not allowed within a text block."); } writeOperator ((byte) 'f', (byte) '*'); } /** * Fills the clipping area with the given shading. * * @param shading * Shading resource * @throws IOException * If the content stream could not be written * @throws IllegalStateException * If the method was called within a text block. */ public void shadingFill (final PDShading shading) throws IOException { if (m_bInTextMode) { throw new IllegalStateException ("Error: shadingFill is not allowed within a text block."); } writeOperand (m_aResources.add (shading)); writeOperator ((byte) 's', (byte) 'h'); } /** * Closes the current subpath. * * @throws IOException * If the content stream could not be written * @throws IllegalStateException * If the method was called within a text block. */ public void closePath () throws IOException { if (m_bInTextMode) { throw new IllegalStateException ("Error: closePath is not allowed within a text block."); } writeOperator ((byte) 'h'); } /** * Intersects the current clipping path with the current path, using the * nonzero rule. * * @throws IOException * If the content stream could not be written * @throws IllegalStateException * If the method was called within a text block. */ public void clip () throws IOException { if (m_bInTextMode) { throw new IllegalStateException ("Error: clip is not allowed within a text block."); } writeOperator ((byte) 'W'); // end path without filling or stroking writeOperator ((byte) 'n'); } /** * Intersects the current clipping path with the current path, using the * even-odd rule. * * @throws IOException * If the content stream could not be written * @throws IllegalStateException * If the method was called within a text block. */ public void clipEvenOdd () throws IOException { if (m_bInTextMode) { throw new IllegalStateException ("Error: clipEvenOdd is not allowed within a text block."); } writeOperator ((byte) 'W', (byte) '*'); // end path without filling or stroking writeOperator ((byte) 'n'); } /** * Set line width to the given value. * * @param lineWidth * The width which is used for drawing. * @throws IOException * If the content stream could not be written * @throws IllegalStateException * If the method was called within a text block. */ public void setLineWidth (final float lineWidth) throws IOException { if (m_bInTextMode) { throw new IllegalStateException ("Error: setLineWidth is not allowed within a text block."); } writeOperand (lineWidth); writeOperator ((byte) 'w'); } /** * Set the line join style. * * @param lineJoinStyle * 0 for miter join, 1 for round join, and 2 for bevel join. * @throws IOException * If the content stream could not be written. * @throws IllegalStateException * If the method was called within a text block. * @throws IllegalArgumentException * If the parameter is not a valid line join style. */ public void setLineJoinStyle (final int lineJoinStyle) throws IOException { if (m_bInTextMode) { throw new IllegalStateException ("Error: setLineJoinStyle is not allowed within a text block."); } if (lineJoinStyle >= 0 && lineJoinStyle <= 2) { writeOperand (lineJoinStyle); writeOperator ((byte) 'j'); } else { throw new IllegalArgumentException ("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 the content stream could not be written. * @throws IllegalStateException * If the method was called within a text block. * @throws IllegalArgumentException * If the parameter is not a valid line cap style. */ public void setLineCapStyle (final int lineCapStyle) throws IOException { if (m_bInTextMode) { throw new IllegalStateException ("Error: setLineCapStyle is not allowed within a text block."); } if (lineCapStyle >= 0 && lineCapStyle <= 2) { writeOperand (lineCapStyle); writeOperator ((byte) 'J'); } else { throw new IllegalArgumentException ("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 the content stream could not be written. * @throws IllegalStateException * If the method was called within a text block. */ public void setLineDashPattern (final float [] pattern, final float phase) throws IOException { if (m_bInTextMode) { throw new IllegalStateException ("Error: setLineDashPattern is not allowed within a text block."); } write ((byte) '['); for (final float value : pattern) { writeOperand (value); } write ((byte) ']', (byte) ' '); writeOperand (phase); writeOperator ((byte) 'd'); } /** * Begin a marked content sequence. * * @param tag * the tag * @throws IOException * If the content stream could not be written */ public void beginMarkedContent (final COSName tag) throws IOException { writeOperand (tag); writeOperator ((byte) 'B', (byte) 'M', (byte) 'C'); } /** * Begin a marked content sequence with a reference to an entry in the page * resources' Properties dictionary. * * @param tag * the tag * @param propertyList * property list * @throws IOException * If the content stream could not be written */ public void beginMarkedContent (final COSName tag, final PDPropertyList propertyList) throws IOException { writeOperand (tag); writeOperand (m_aResources.add (propertyList)); writeOperator ((byte) 'B', (byte) 'D', (byte) 'C'); } /** * End a marked content sequence. * * @throws IOException * If the content stream could not be written */ public void endMarkedContent () throws IOException { writeOperator ((byte) 'E', (byte) 'M', (byte) 'C'); } /** * Set an extended graphics state. * * @param state * The extended graphics state. * @throws IOException * If the content stream could not be written. */ public void setGraphicsStateParameters (final PDExtendedGraphicsState state) throws IOException { writeOperand (m_aResources.add (state)); writeOperator ((byte) 'g', (byte) 's'); } /** * Writes a real real to the content stream. * * @param real * the value to be written * @throws IOException * In case of IO error */ protected void writeOperand (final float real) throws IOException { final int byteCount = NumberFormatUtil.formatFloatFast (real, m_aFormatDecimal.getMaximumFractionDigits (), m_aFormatBuffer); if (byteCount == -1) { // Fast formatting failed write (m_aFormatDecimal.format (real)); } else { m_aOS.write (m_aFormatBuffer, 0, byteCount); } m_aOS.write (' '); } /** * Writes a real number to the content stream. * * @param integer * the value to be written * @throws IOException * In case of IO error */ protected void writeOperand (final int integer) throws IOException { write (m_aFormatDecimal.format (integer)); m_aOS.write (' '); } /** * Writes a COSName to the content stream. * * @param name * the value to be written * @throws IOException * In case of IO error */ protected void writeOperand (final COSName name) throws IOException { name.writePDF (m_aOS); m_aOS.write (' '); } /** * Writes a string to the content stream as ASCII. * * @param text * the value to be written * @throws IOException * In case of IO error */ protected final void writeOperator (final byte... text) throws IOException { m_aOS.write (text); m_aOS.write ('\n'); } /** * Writes a string to the content stream as ASCII. * * @param text * the value to be written * @throws IOException * In case of IO error */ private void write (final String text) throws IOException { write (text.getBytes (Charsets.US_ASCII)); } /** * Writes a string to the content stream as ASCII. * * @param text * the value to be written * @throws IOException * In case of IO error */ protected void write (final byte... text) throws IOException { m_aOS.write (text); } /** * Writes a string to the content stream as ASCII. * * @throws IOException * In case of IO error */ protected void writeLine () throws IOException { m_aOS.write ('\n'); } /** * Writes binary data to the content stream. * * @param data * the value to be written * @throws IOException * In case of IO error */ protected void writeBytes (final byte [] data) throws IOException { m_aOS.write (data); } /** * Writes an AffineTransform to the content stream as an array. * * @param transform * the transform to use * @throws IOException * In case of IO error */ private void writeAffineTransform (final AffineTransform transform) throws IOException { final double [] values = new double [6]; transform.getMatrix (values); for (final double v : values) { writeOperand ((float) v); } } /** * 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. */ @Override public void close () throws IOException { m_aOS.close (); } private static boolean _isOutside255Interval (final int val) { return val < 0 || val > 255; } private static boolean _isOutsideOneInterval (final double val) { return val < 0 || val > 1; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy