Author: tilman
Date: Mon Sep 29 08:27:49 2025
New Revision: 1928831

Log:
PDFBOX-5660: optimize, as suggested by Valery Bokov; closes #267

Modified:
   
pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDAbstractContentStream.java

Modified: 
pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDAbstractContentStream.java
==============================================================================
--- 
pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDAbstractContentStream.java
     Mon Sep 29 06:04:15 2025        (r1928830)
+++ 
pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDAbstractContentStream.java
     Mon Sep 29 08:27:49 2025        (r1928831)
@@ -1,1674 +1,1676 @@
-/*
- * 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.
- */
-package org.apache.pdfbox.pdmodel;
-
-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.ArrayDeque;
-import java.util.Deque;
-import java.util.Locale;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.pdfbox.contentstream.operator.OperatorName;
-import org.apache.pdfbox.cos.COSArray;
-import org.apache.pdfbox.cos.COSBase;
-import org.apache.pdfbox.cos.COSDictionary;
-import org.apache.pdfbox.cos.COSName;
-import org.apache.pdfbox.cos.COSNumber;
-import org.apache.pdfbox.pdfwriter.COSWriter;
-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.graphics.state.RenderingMode;
-import org.apache.pdfbox.util.Charsets;
-import org.apache.pdfbox.util.Matrix;
-import org.apache.pdfbox.util.NumberFormatUtil;
-
-/**
- * Provides the ability to write to a content stream.
- *
- * @author Ben Litchfield
- */
-abstract class PDAbstractContentStream implements Closeable
-{
-    private static final Log LOG = 
LogFactory.getLog(PDAbstractContentStream.class);
-
-    protected final PDDocument document; // may be null
-
-    protected final OutputStream outputStream;
-    protected final PDResources resources;
-
-    protected boolean inTextMode = false;
-    protected final Deque<PDFont> fontStack = new ArrayDeque<PDFont>();
-
-    protected final Deque<PDColorSpace> nonStrokingColorSpaceStack = new 
ArrayDeque<PDColorSpace>();
-    protected final Deque<PDColorSpace> strokingColorSpaceStack = new 
ArrayDeque<PDColorSpace>();
-
-    // number format
-    private final NumberFormat formatDecimal = 
NumberFormat.getNumberInstance(Locale.US);
-    private final byte[] formatBuffer = new byte[32];
-
-    /**
-     * Create a new appearance stream.
-     *
-     * @param document may be null
-     * @param outputStream The appearances output stream to write to.
-     * @param resources The resources to use
-     */
-    PDAbstractContentStream(PDDocument document, OutputStream outputStream, 
PDResources resources)
-    {
-        this.document = document;
-        this.outputStream = outputStream;
-        this.resources = resources;
-
-        formatDecimal.setMaximumFractionDigits(4);
-        formatDecimal.setGroupingUsed(false);
-    }
-
-    /**
-     * Sets the maximum number of digits allowed for fractional numbers.
-     * 
-     * @see NumberFormat#setMaximumFractionDigits(int)
-     * @param fractionDigitsNumber
-     */
-    protected void setMaximumFractionDigits(int fractionDigitsNumber)
-    {
-        formatDecimal.setMaximumFractionDigits(fractionDigitsNumber);
-    }
-
-    /**
-     * 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 (inTextMode)
-        {
-            throw new IllegalStateException("Error: Nested beginText() calls 
are not allowed.");
-        }
-        writeOperator(OperatorName.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.
-     * @throws IllegalStateException If the method was not allowed to be 
called at this time.
-     */
-    public void endText() throws IOException
-    {
-        if (!inTextMode)
-        {
-            throw new IllegalStateException("Error: You must call beginText() 
before calling endText.");
-        }
-        writeOperator(OperatorName.END_TEXT);
-        inTextMode = 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(PDFont font, float fontSize) throws IOException
-    {
-        if (fontStack.isEmpty())
-        {
-            fontStack.add(font);
-        }
-        else
-        {
-            fontStack.pop();
-            fontStack.push(font);
-        }
-
-        // keep track of fonts which are configured for subsetting
-        if (font.willBeSubset())
-        {
-            if (document != null)
-            {
-                document.getFontsToSubset().add(font);
-            }
-            else
-            {
-                LOG.warn("Using the subsetted font '" + font.getName() +
-                        "' without a PDDocument context; call subset() before 
saving");
-            }
-        }
-
-        writeOperand(resources.add(font));
-        writeOperand(fontSize);
-        writeOperator(OperatorName.SET_FONT_AND_SIZE);
-    }
-
-    /**
-     * Shows the given text at the location specified by the current text 
matrix with the given
-     * interspersed positioning. This allows the user to efficiently position 
each glyph or sequence
-     * of glyphs.
-     *
-     * @param textWithPositioningArray An array consisting of String and Float 
types. Each String is
-     * output to the page using the current text matrix. Using the default 
coordinate system, each
-     * interspersed number adjusts the current text matrix by translating to 
the left or down for
-     * horizontal and vertical text respectively. The number is expressed in 
thousands of a text
-     * space unit, and may be negative.
-     *
-     * @throws IOException if an io exception occurs.
-     */
-    public void showTextWithPositioning(Object[] textWithPositioningArray) 
throws IOException
-    {
-        write("[");
-        for (Object obj : textWithPositioningArray)
-        {
-            if (obj instanceof String)
-            {
-                showTextInternal((String) obj);
-            }
-            else if (obj instanceof Float)
-            {
-                writeOperand((Float) obj);
-            }
-            else
-            {
-                throw new IllegalArgumentException("Argument must consist of 
array of Float and String types");
-            }
-        }
-        write("] ");
-        writeOperator(OperatorName.SHOW_TEXT_ADJUSTED);
-    }
-
-    /**
-     * 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.
-     * @throws IllegalArgumentException if a character isn't supported by the 
current font
-     */
-    public void showText(String text) throws IOException
-    {
-        showTextInternal(text);
-        write(" ");
-        writeOperator(OperatorName.SHOW_TEXT);
-    }
-
-    /**
-     * Outputs a string using the correct encoding and subsetting as required.
-     *
-     * @param text The Unicode text to show.
-     * 
-     * @throws IOException If an io exception occurs.
-     */
-    protected void showTextInternal(String text) throws IOException
-    {
-        if (!inTextMode)
-        {
-            throw new IllegalStateException("Must call beginText() before 
showText()");
-        }
-
-        if (fontStack.isEmpty())
-        {
-            throw new IllegalStateException("Must call setFont() before 
showText()");
-        }
-
-        PDFont font = fontStack.peek();
-
-        // complex text layout
-        byte[] encodedText = null;
-
-        if (encodedText == null)
-        {
-            encodedText = font.encode(text);
-        }
-
-        // Unicode code points to keep when subsetting
-        if (font.willBeSubset())
-        {
-            int offset = 0;
-            while (offset < text.length())
-            {
-                int codePoint = text.codePointAt(offset);
-                font.addToSubset(codePoint);
-                offset += Character.charCount(codePoint);
-            }
-        }
-
-        COSWriter.writeString(encodedText, outputStream);
-    }
-
-    /**
-     * 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(float leading) throws IOException
-    {
-        writeOperand(leading);
-        writeOperator(OperatorName.SET_TEXT_LEADING);
-    }
-
-    /**
-     * 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 (!inTextMode)
-        {
-            throw new IllegalStateException("Must call beginText() before 
newLine()");
-        }
-        writeOperator(OperatorName.NEXT_LINE);
-    }
-
-    /**
-     * 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(float tx, float ty) throws IOException
-    {
-        if (!inTextMode)
-        {
-            throw new IllegalStateException("Error: must call beginText() 
before newLineAtOffset()");
-        }
-        writeOperand(tx);
-        writeOperand(ty);
-        writeOperator(OperatorName.MOVE_TEXT);
-    }
-
-    /**
-     * 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(Matrix matrix) throws IOException
-    {
-        if (!inTextMode)
-        {
-            throw new IllegalStateException("Error: must call beginText() 
before setTextMatrix");
-        }
-        writeAffineTransform(matrix.createAffineTransform());
-        writeOperator(OperatorName.SET_MATRIX);
-    }
-
-    /**
-     * 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(PDImageXObject image, float x, 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(PDImageXObject image, float x, float y, float width, 
float height) throws IOException
-    {
-        if (inTextMode)
-        {
-            throw new IllegalStateException("Error: drawImage is not allowed 
within a text block.");
-        }
-
-        saveGraphicsState();
-
-        AffineTransform transform = new AffineTransform(width, 0, 0, height, 
x, y);
-        transform(new Matrix(transform));
-
-        writeOperand(resources.add(image));
-        writeOperator(OperatorName.DRAW_OBJECT);
-
-        restoreGraphicsState();
-    }
-
-    /**
-     * Draw an image at the origin with the given transformation matrix.
-     *
-     * @param image The image to draw.
-     * @param matrix The transformation matrix to apply to 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(PDImageXObject image, Matrix matrix) throws 
IOException
-    {
-        if (inTextMode)
-        {
-            throw new IllegalStateException("Error: drawImage is not allowed 
within a text block.");
-        }
-
-        saveGraphicsState();
-
-        AffineTransform transform = matrix.createAffineTransform();
-        transform(new Matrix(transform));
-
-        writeOperand(resources.add(image));
-        writeOperator(OperatorName.DRAW_OBJECT);
-
-        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(PDInlineImage inlineImage, float x, 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(PDInlineImage inlineImage, float x, float y, float 
width, float height) throws IOException
-    {
-        if (inTextMode)
-        {
-            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
-        StringBuilder sb = new StringBuilder();
-        sb.append(OperatorName.BEGIN_INLINE_IMAGE);
-
-        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());
-
-        COSArray decodeArray = inlineImage.getDecode();
-        if (decodeArray != null && decodeArray.size() > 0)
-        {
-            sb.append("\n /D ");
-            sb.append('[');
-            for (COSBase base : decodeArray)
-            {
-                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(OperatorName.BEGIN_INLINE_IMAGE_DATA);
-        writeBytes(inlineImage.getData());
-        writeLine();
-        writeOperator(OperatorName.END_INLINE_IMAGE);
-
-        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(PDFormXObject form) throws IOException
-    {
-        if (inTextMode)
-        {
-            throw new IllegalStateException("Error: drawForm is not allowed 
within a text block.");
-        }
-
-        writeOperand(resources.add(form));
-        writeOperator(OperatorName.DRAW_OBJECT);
-    }
-
-    /**
-     * The cm operator. Concatenates the given matrix with the current 
transformation matrix (CTM),
-     * which maps user space coordinates used within a PDF content stream into 
output device
-     * coordinates. More details on coordinates can be found in the PDF 32000 
specification, 8.3.2
-     * Coordinate Spaces.
-     *
-     * @param matrix the transformation matrix
-     * @throws IOException If there is an error writing to the stream.
-     */
-    public void transform(Matrix matrix) throws IOException
-    {
-        if (inTextMode)
-        {
-            throw new IllegalStateException("Error: Modifying the current 
transformation matrix is not allowed within text objects.");
-        }
-
-        writeAffineTransform(matrix.createAffineTransform());
-        writeOperator(OperatorName.CONCAT);
-    }
-
-    /**
-     * q operator. Saves the current graphics state.
-     * @throws IOException If an error occurs while writing to the stream.
-     */
-    public void saveGraphicsState() throws IOException
-    {
-        if (inTextMode)
-        {
-            throw new IllegalStateException("Error: Saving the graphics state 
is not allowed within text objects.");
-        }
-
-        if (!fontStack.isEmpty())
-        {
-            fontStack.push(fontStack.peek());
-        }
-        if (!strokingColorSpaceStack.isEmpty())
-        {
-            strokingColorSpaceStack.push(strokingColorSpaceStack.peek());
-        }
-        if (!nonStrokingColorSpaceStack.isEmpty())
-        {
-            nonStrokingColorSpaceStack.push(nonStrokingColorSpaceStack.peek());
-        }
-        writeOperator(OperatorName.SAVE);
-    }
-
-    /**
-     * Q operator. Restores the current graphics state.
-     * @throws IOException If an error occurs while writing to the stream.
-     */
-    public void restoreGraphicsState() throws IOException
-    {
-        if (inTextMode)
-        {
-            throw new IllegalStateException("Error: Restoring the graphics 
state is not allowed within text objects.");
-        }
-
-        if (!fontStack.isEmpty())
-        {
-            fontStack.pop();
-        }
-        if (!strokingColorSpaceStack.isEmpty())
-        {
-            strokingColorSpaceStack.pop();
-        }
-        if (!nonStrokingColorSpaceStack.isEmpty())
-        {
-            nonStrokingColorSpaceStack.pop();
-        }
-        writeOperator(OperatorName.RESTORE);
-    }
-
-    protected COSName getName(PDColorSpace colorSpace)
-    {
-        if (colorSpace instanceof PDDeviceGray ||
-            colorSpace instanceof PDDeviceRGB ||
-            colorSpace instanceof PDDeviceCMYK)
-        {
-            return COSName.getPDFName(colorSpace.getName());
-        }
-        else
-        {
-            return resources.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(PDColor color) throws IOException
-    {
-        if (strokingColorSpaceStack.isEmpty() ||
-            strokingColorSpaceStack.peek() != color.getColorSpace())
-        {
-            writeOperand(getName(color.getColorSpace()));
-            writeOperator(OperatorName.STROKING_COLORSPACE);
-            setStrokingColorSpaceStack(color.getColorSpace());
-        }
-
-        for (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(OperatorName.STROKING_COLOR_N);
-        }
-        else
-        {
-            writeOperator(OperatorName.STROKING_COLOR);
-        }
-    }
-
-    /**
-     * 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(Color color) throws IOException
-    {
-        float[] components = new float[] {
-                color.getRed() / 255f, color.getGreen() / 255f, 
color.getBlue() / 255f };
-        PDColor pdColor = new PDColor(components, PDDeviceRGB.INSTANCE);
-        setStrokingColor(pdColor);
-    }
-
-    /**
-     * Set the stroking color in the DeviceRGB color space. Range is 0..1.
-     *
-     * @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(float r, float g, float b) throws IOException
-    {
-        if (isOutsideOneInterval(r) || isOutsideOneInterval(g) || 
isOutsideOneInterval(b))
-        {
-            throw new IllegalArgumentException("Parameters must be within 
0..1, but are "
-                    + String.format("(%.2f,%.2f,%.2f)", r, g, b));
-        }
-        writeOperand(r);
-        writeOperand(g);
-        writeOperand(b);
-        writeOperator(OperatorName.STROKING_COLOR_RGB);
-        setStrokingColorSpaceStack(PDDeviceRGB.INSTANCE);
-    }
-
-    /**
-     * 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.
-     * @deprecated use
-     * {@link #setStrokingColor(float, float, float) setStrokingColor(r/255f, 
g/255f, b/255f)}
-     */
-    @Deprecated
-    public void setStrokingColor(int r, int g, int b) throws IOException
-    {
-        if (isOutside255Interval(r) || isOutside255Interval(g) || 
isOutside255Interval(b))
-        {
-            throw new IllegalArgumentException("Parameters must be within 
0..255, but are "
-                    + String.format("(%d,%d,%d)", r, g, b));
-        }
-        setStrokingColor(r / 255f, g / 255f, b / 255f);
-    }
-
-    /**
-     * 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(float c, float m, float y, float k) throws 
IOException
-    {
-        if (isOutsideOneInterval(c) || isOutsideOneInterval(m) || 
isOutsideOneInterval(y) || isOutsideOneInterval(k))
-        {
-            throw new IllegalArgumentException("Parameters must be within 
0..1, but are "
-                    + String.format("(%.2f,%.2f,%.2f,%.2f)", c, m, y, k));
-        }
-        writeOperand(c);
-        writeOperand(m);
-        writeOperand(y);
-        writeOperand(k);
-        writeOperator(OperatorName.STROKING_COLOR_CMYK);
-        setStrokingColorSpaceStack(PDDeviceCMYK.INSTANCE);
-    }
-
-    /**
-     * 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(float g) throws IOException
-    {
-        if (isOutsideOneInterval(g))
-        {
-            throw new IllegalArgumentException("Parameter must be within 0..1, 
but is " + g);
-        }
-        writeOperand(g);
-        writeOperator(OperatorName.STROKING_COLOR_GRAY);
-        setStrokingColorSpaceStack(PDDeviceGray.INSTANCE);
-    }
-
-    /**
-     * 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(PDColor color) throws IOException
-    {
-        if (nonStrokingColorSpaceStack.isEmpty() ||
-            nonStrokingColorSpaceStack.peek() != color.getColorSpace())
-        {
-            writeOperand(getName(color.getColorSpace()));
-            writeOperator(OperatorName.NON_STROKING_COLORSPACE);
-            setNonStrokingColorSpaceStack(color.getColorSpace());
-        }
-
-        for (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(OperatorName.NON_STROKING_COLOR_N);
-        }
-        else
-        {
-            writeOperator(OperatorName.NON_STROKING_COLOR);
-        }
-    }
-
-    /**
-     * 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(Color color) throws IOException
-    {
-        float[] components = new float[] {
-                color.getRed() / 255f, color.getGreen() / 255f, 
color.getBlue() / 255f };
-        PDColor pdColor = new PDColor(components, PDDeviceRGB.INSTANCE);
-        setNonStrokingColor(pdColor);
-    }
-
-    /**
-     * Set the non-stroking color in the DeviceRGB color space. Range is 0..1.
-     *
-     * @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(float r, float g, float b) throws 
IOException
-    {
-        if (isOutsideOneInterval(r) || isOutsideOneInterval(g) || 
isOutsideOneInterval(b))
-        {
-            throw new IllegalArgumentException("Parameters must be within 
0..1, but are "
-                    + String.format("(%.2f,%.2f,%.2f)", r, g, b));
-        }
-        writeOperand(r);
-        writeOperand(g);
-        writeOperand(b);
-        writeOperator(OperatorName.NON_STROKING_RGB);
-        setNonStrokingColorSpaceStack(PDDeviceRGB.INSTANCE);
-    }
-
-    /**
-     * 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.
-     * @deprecated use
-     * {@link #setNonStrokingColor(float, float, float) 
setNonStrokingColor(r/255f, g/255f, b/255f)}
-     */
-    @Deprecated
-    public void setNonStrokingColor(int r, int g, int b) throws IOException
-    {
-        if (isOutside255Interval(r) || isOutside255Interval(g) || 
isOutside255Interval(b))
-        {
-            throw new IllegalArgumentException("Parameters must be within 
0..255, but are "
-                    + String.format("(%d,%d,%d)", r, g, b));
-        }
-        setNonStrokingColor(r / 255f, g / 255f, b / 255f);
-    }
-
-    /**
-     * 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.
-     * @deprecated Use {@link #setStrokingColor(float, float, float, float) 
setStrokingColor(c/255f, m/255f, y/255f, k/255f)} instead.
-     */
-    @Deprecated
-    public void setNonStrokingColor(int c, int m, int y, int k) throws 
IOException
-    {
-        if (isOutside255Interval(c) || isOutside255Interval(m) || 
isOutside255Interval(y) || isOutside255Interval(k))
-        {
-            throw new IllegalArgumentException("Parameters must be within 
0..255, but are "
-                    + String.format("(%d,%d,%d,%d)", c, m, y, k));
-        }
-        setNonStrokingColor(c / 255f, m / 255f, y / 255f, k / 255f);
-    }
-
-    /**
-     * Set the non-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.
-     */
-    public void setNonStrokingColor(float c, float m, float y, float k) throws 
IOException
-    {
-        if (isOutsideOneInterval(c) || isOutsideOneInterval(m) || 
isOutsideOneInterval(y) || isOutsideOneInterval(k))
-        {
-            throw new IllegalArgumentException("Parameters must be within 
0..1, but are "
-                    + String.format("(%.2f,%.2f,%.2f,%.2f)", c, m, y, k));
-        }
-        writeOperand(c);
-        writeOperand(m);
-        writeOperand(y);
-        writeOperand(k);
-        writeOperator(OperatorName.NON_STROKING_CMYK);
-        setNonStrokingColorSpaceStack(PDDeviceCMYK.INSTANCE);
-    }
-
-    /**
-     * 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.
-     * @deprecated use {@link #setNonStrokingColor(float) 
setNonStrokingColor(g/255f)}
-     */
-    public void setNonStrokingColor(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(float g) throws IOException
-    {
-        if (isOutsideOneInterval(g))
-        {
-            throw new IllegalArgumentException("Parameter must be within 0..1, 
but is " + g);
-        }
-        writeOperand(g);
-        writeOperator(OperatorName.NON_STROKING_GRAY);
-        setNonStrokingColorSpaceStack(PDDeviceGray.INSTANCE);
-    }
-
-    /**
-     * 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(float x, float y, float width, float height) throws 
IOException
-    {
-        if (inTextMode)
-        {
-            throw new IllegalStateException("Error: addRect is not allowed 
within a text block.");
-        }
-        writeOperand(x);
-        writeOperand(y);
-        writeOperand(width);
-        writeOperand(height);
-        writeOperator(OperatorName.APPEND_RECT);
-    }
-
-    /**
-     * 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(float x1, float y1, float x2, float y2, float x3, 
float y3) throws IOException
-    {
-        if (inTextMode)
-        {
-            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(OperatorName.CURVE_TO);
-    }
-
-    /**
-     * 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(float x2, float y2, float x3, float y3) throws 
IOException
-    {
-        if (inTextMode)
-        {
-            throw new IllegalStateException("Error: curveTo2 is not allowed 
within a text block.");
-        }
-        writeOperand(x2);
-        writeOperand(y2);
-        writeOperand(x3);
-        writeOperand(y3);
-        writeOperator(OperatorName.CURVE_TO_REPLICATE_INITIAL_POINT);
-    }
-
-    /**
-     * 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(float x1, float y1, float x3, float y3) throws 
IOException
-    {
-        if (inTextMode)
-        {
-            throw new IllegalStateException("Error: curveTo1 is not allowed 
within a text block.");
-        }
-        writeOperand(x1);
-        writeOperand(y1);
-        writeOperand(x3);
-        writeOperand(y3);
-        writeOperator(OperatorName.CURVE_TO_REPLICATE_FINAL_POINT);
-    }
-
-    /**
-     * 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(float x, float y) throws IOException
-    {
-        if (inTextMode)
-        {
-            throw new IllegalStateException("Error: moveTo is not allowed 
within a text block.");
-        }
-        writeOperand(x);
-        writeOperand(y);
-        writeOperator(OperatorName.MOVE_TO);
-    }
-
-    /**
-     * 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(float x, float y) throws IOException
-    {
-        if (inTextMode)
-        {
-            throw new IllegalStateException("Error: lineTo is not allowed 
within a text block.");
-        }
-        writeOperand(x);
-        writeOperand(y);
-        writeOperator(OperatorName.LINE_TO);
-    }
-
-    /**
-     * 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 (inTextMode)
-        {
-            throw new IllegalStateException("Error: stroke is not allowed 
within a text block.");
-        }
-        writeOperator(OperatorName.STROKE_PATH);
-    }
-
-    /**
-     * 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 (inTextMode)
-        {
-            throw new IllegalStateException("Error: closeAndStroke is not 
allowed within a text block.");
-        }
-        writeOperator(OperatorName.CLOSE_AND_STROKE);
-    }
-
-    /**
-     * Fills the path using the nonzero winding number 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 (inTextMode)
-        {
-            throw new IllegalStateException("Error: fill is not allowed within 
a text block.");
-        }
-        writeOperator(OperatorName.FILL_NON_ZERO);
-    }
-
-    /**
-     * 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 (inTextMode)
-        {
-            throw new IllegalStateException("Error: fillEvenOdd is not allowed 
within a text block.");
-        }
-        writeOperator(OperatorName.FILL_EVEN_ODD);
-    }
-
-    /**
-     * Fill and then stroke the path, using the nonzero winding number rule to 
determine the region
-     * to fill. This shall produce the same result as constructing two 
identical path objects,
-     * painting the first with {@link #fill() } and the second with {@link 
#stroke() }.
-     *
-     * @throws IOException If the content stream could not be written
-     * @throws IllegalStateException If the method was called within a text 
block.
-     */
-    public void fillAndStroke() throws IOException
-    {
-        if (inTextMode)
-        {
-            throw new IllegalStateException("Error: fillAndStroke is not 
allowed within a text block.");
-        }
-        writeOperator(OperatorName.FILL_NON_ZERO_AND_STROKE);
-    }
-
-    /**
-     * Fill and then stroke the path, using the even-odd rule to determine the 
region to
-     * fill. This shall produce the same result as constructing two identical 
path objects, painting
-     * the first with {@link #fillEvenOdd() } and the second with {@link 
#stroke() }.
-     *
-     * @throws IOException If the content stream could not be written
-     * @throws IllegalStateException If the method was called within a text 
block.
-     */
-    public void fillAndStrokeEvenOdd() throws IOException
-    {
-        if (inTextMode)
-        {
-            throw new IllegalStateException("Error: fillAndStrokeEvenOdd is 
not allowed within a text block.");
-        }
-        writeOperator(OperatorName.FILL_EVEN_ODD_AND_STROKE);
-    }
-
-    /**
-     * Close, fill, and then stroke the path, using the nonzero winding number 
rule to determine the
-     * region to fill. This shall have the same effect as the sequence {@link 
#closePath() }
-     * and then {@link #fillAndStroke() }.
-     *
-     * @throws IOException If the content stream could not be written
-     * @throws IllegalStateException If the method was called within a text 
block.
-     */
-    public void closeAndFillAndStroke() throws IOException
-    {
-        if (inTextMode)
-        {
-            throw new IllegalStateException("Error: closeAndFillAndStroke is 
not allowed within a text block.");
-        }
-        writeOperator(OperatorName.CLOSE_FILL_NON_ZERO_AND_STROKE);
-    }
-
-    /**
-     * Close, fill, and then stroke the path, using the even-odd rule to 
determine the region to
-     * fill. This shall have the same effect as the sequence {@link 
#closePath() }
-     * and then {@link #fillAndStrokeEvenOdd() }.
-     *
-     * @throws IOException If the content stream could not be written
-     * @throws IllegalStateException If the method was called within a text 
block.
-     */
-    public void closeAndFillAndStrokeEvenOdd() throws IOException
-    {
-        if (inTextMode)
-        {
-            throw new IllegalStateException("Error: 
closeAndFillAndStrokeEvenOdd is not allowed within a text block.");
-        }
-        writeOperator(OperatorName.CLOSE_FILL_EVEN_ODD_AND_STROKE);
-    }
-
-    /**
-     * 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(PDShading shading) throws IOException
-    {
-        if (inTextMode)
-        {
-            throw new IllegalStateException("Error: shadingFill is not allowed 
within a text block.");
-        }
-
-        writeOperand(resources.add(shading));
-        writeOperator(OperatorName.SHADING_FILL);
-    }
-
-    /**
-     * 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 (inTextMode)
-        {
-            throw new IllegalStateException("Error: closePath is not allowed 
within a text block.");
-        }
-        writeOperator(OperatorName.CLOSE_PATH);
-    }
-
-    /**
-     * 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 (inTextMode)
-        {
-            throw new IllegalStateException("Error: clip is not allowed within 
a text block.");
-        }
-        writeOperator(OperatorName.CLIP_NON_ZERO);
-        
-        // end path without filling or stroking
-        writeOperator(OperatorName.ENDPATH);
-    }
-
-    /**
-     * 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 (inTextMode)
-        {
-            throw new IllegalStateException("Error: clipEvenOdd is not allowed 
within a text block.");
-        }
-        writeOperator(OperatorName.CLIP_EVEN_ODD);
-        
-        // end path without filling or stroking
-        writeOperator(OperatorName.ENDPATH);
-    }
-
-    /**
-     * 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
-     */
-    public void setLineWidth(float lineWidth) throws IOException
-    {
-        writeOperand(lineWidth);
-        writeOperator(OperatorName.SET_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 the content stream could not be written.
-     * @throws IllegalArgumentException If the parameter is not a valid line 
join style.
-     */
-    public void setLineJoinStyle(int lineJoinStyle) throws IOException
-    {
-        if (lineJoinStyle >= 0 && lineJoinStyle <= 2)
-        {
-            writeOperand(lineJoinStyle);
-            writeOperator(OperatorName.SET_LINE_JOINSTYLE);
-        }
-        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 IllegalArgumentException If the parameter is not a valid line 
cap style.
-     */
-    public void setLineCapStyle(int lineCapStyle) throws IOException
-    {
-        if (lineCapStyle >= 0 && lineCapStyle <= 2)
-        {
-            writeOperand(lineCapStyle);
-            writeOperator(OperatorName.SET_LINE_CAPSTYLE);
-        }
-        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.
-     */
-    public void setLineDashPattern(float[] pattern, float phase) throws 
IOException
-    {
-        write("[");
-        for (float value : pattern)
-        {
-            writeOperand(value);
-        }
-        write("] ");
-        writeOperand(phase);
-        writeOperator(OperatorName.SET_LINE_DASHPATTERN);
-    }
-
-    /**
-     * Set the miter limit.
-     *
-     * @param miterLimit the new miter limit.
-     * @throws IOException If the content stream could not be written.
-     * @throws IllegalArgumentException If the parameter is \u2264 0.
-     */
-    public void setMiterLimit(float miterLimit) throws IOException
-    {
-        if (miterLimit <= 0.0)
-        {
-            throw new IllegalArgumentException("A miter limit <= 0 is invalid 
and will not render in Acrobat Reader");
-        }
-        writeOperand(miterLimit);
-        writeOperator(OperatorName.SET_LINE_MITERLIMIT);
-    }
-
-    /**
-     * Begin a marked content sequence.
-     *
-     * @param tag the tag
-     * @throws IOException If the content stream could not be written
-     */
-    public void beginMarkedContent(COSName tag) throws IOException
-    {
-        writeOperand(tag);
-        writeOperator(OperatorName.BEGIN_MARKED_CONTENT);
-    }
-
-    /**
-     * Begin a marked content sequence with a reference to the marked content 
identifier (MCID).
-     *
-     * @param tag the tag to be added to the content stream
-     * @param mcid the marked content identifier (MCID)
-     * @throws IOException If the content stream could not be written
-     */
-    public void beginMarkedContent(COSName tag, int mcid) throws IOException
-    {
-        if (mcid < 0)
-        {
-            throw new IllegalArgumentException("mcid should not be negative");
-        }
-        writeOperand(tag);
-        write("<</MCID " + mcid + ">> ");
-        writeOperator(OperatorName.BEGIN_MARKED_CONTENT_SEQ);
-    }
-
-    /**
-     * 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(COSName tag, PDPropertyList propertyList) 
throws IOException
-    {
-        writeOperand(tag);
-
-        COSDictionary dict = propertyList.getCOSObject();
-        if (dict.getInt(COSName.MCID) > -1 && dict.size() == 1)
-        {
-            // PDFBOX-5890: use simplified notation if there's only an MCID
-            write("<</MCID " + dict.getInt(COSName.MCID) + ">> ");
-        }
-        else
-        {
-            writeOperand(resources.add(propertyList));
-        }
-
-        writeOperator(OperatorName.BEGIN_MARKED_CONTENT_SEQ);
-    }
-
-    /**
-     * End a marked content sequence.
-     *
-     * @throws IOException If the content stream could not be written
-     */
-    public void endMarkedContent() throws IOException
-    {
-        writeOperator(OperatorName.END_MARKED_CONTENT);
-    }
-
-    /**
-     * Set an extended graphics state.
-     * 
-     * @param state The extended graphics state.
-     * @throws IOException If the content stream could not be written.
-     */
-    public void setGraphicsStateParameters(PDExtendedGraphicsState state) 
throws IOException
-    {
-        writeOperand(resources.add(state));
-        writeOperator(OperatorName.SET_GRAPHICS_STATE_PARAMS);
-    }
-
-    /**
-     * Write a comment line.
-     *
-     * @param comment
-     * @throws IOException If the content stream could not be written.
-     * @throws IllegalArgumentException If the comment contains a newline. 
This is not allowed,
-     * because the next line could be ordinary PDF content.
-     */
-    public void addComment(String comment) throws IOException
-    {
-        if (comment.indexOf('\n') >= 0 || comment.indexOf('\r') >= 0)
-        {
-            throw new IllegalArgumentException("comment should not include a 
newline");
-        }
-        outputStream.write('%');
-        outputStream.write(comment.getBytes(Charsets.US_ASCII));
-        outputStream.write('\n');
-    }
-
-    /**
-     * Writes a real number to the content stream.
-     * @param real
-     * @throws java.io.IOException
-     * @throws IllegalArgumentException if the parameter is not a finite number
-     */
-    protected void writeOperand(float real) throws IOException
-    {
-        if (Float.isInfinite(real) || Float.isNaN(real))
-        {
-            throw new IllegalArgumentException(real + " is not a finite 
number");
-        }
-
-        int byteCount = NumberFormatUtil.formatFloatFast(real, 
formatDecimal.getMaximumFractionDigits(), formatBuffer);
-
-        if (byteCount == -1)
-        {
-            //Fast formatting failed
-            write(formatDecimal.format(real));
-        }
-        else
-        {
-            outputStream.write(formatBuffer, 0, byteCount);
-        }
-        outputStream.write(' ');
-    }
-
-    /**
-     * Writes an integer number to the content stream.
-     * @param integer
-     * @throws java.io.IOException
-     */
-    protected void writeOperand(int integer) throws IOException
-    {
-        write(formatDecimal.format(integer));
-        outputStream.write(' ');
-    }
-
-    /**
-     * Writes a COSName to the content stream.
-     * @param name
-     * @throws java.io.IOException
-     */
-    protected void writeOperand(COSName name) throws IOException
-    {
-        name.writePDF(outputStream);
-        outputStream.write(' ');
-    }
-
-    /**
-     * Writes a string to the content stream as ASCII.
-     * @param text
-     * @throws java.io.IOException
-     */
-    protected void writeOperator(String text) throws IOException
-    {
-        outputStream.write(text.getBytes(Charsets.US_ASCII));
-        outputStream.write('\n');
-    }
-
-    /**
-     * Writes a string to the content stream as ASCII.
-     * @param text
-     * @throws java.io.IOException
-     */
-    protected void write(String text) throws IOException
-    {
-        outputStream.write(text.getBytes(Charsets.US_ASCII));
-    }
-
-    /**
-     * Writes a byte[] to the content stream.
-     * @param data
-     * @throws java.io.IOException
-     */
-    protected void write(byte[] data) throws IOException
-    {
-        outputStream.write(data);
-    }
-    
-    /**
-     * Writes a newline to the content stream as ASCII.
-     * @throws java.io.IOException
-     */
-    protected void writeLine() throws IOException
-    {
-        outputStream.write('\n');
-    }
-
-    /**
-     * Writes binary data to the content stream.
-     * @param data
-     * @throws java.io.IOException
-     */
-    protected void writeBytes(byte[] data) throws IOException
-    {
-        outputStream.write(data);
-    }
-
-    /**
-     * Writes an AffineTransform to the content stream as an array.
-     */
-    private void writeAffineTransform(AffineTransform transform) throws 
IOException
-    {
-        double[] values = new double[6];
-        transform.getMatrix(values);
-        for (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
-    {
-        if (inTextMode)
-        {
-            LOG.warn("You did not call endText(), some viewers won't display 
your text");
-        }
-        outputStream.close();
-    }
-
-    protected boolean isOutside255Interval(int val)
-    {
-        return val < 0 || val > 255;
-    }
-
-    private boolean isOutsideOneInterval(double val)
-    {
-        return val < 0 || val > 1;
-    }
-
-    protected void setStrokingColorSpaceStack(PDColorSpace colorSpace)
-    {
-        if (strokingColorSpaceStack.isEmpty())
-        {
-            strokingColorSpaceStack.add(colorSpace);
-        }
-        else
-        {
-            strokingColorSpaceStack.pop();
-            strokingColorSpaceStack.push(colorSpace);
-        }
-    }
-
-    protected void setNonStrokingColorSpaceStack(PDColorSpace colorSpace)
-    {
-        if (nonStrokingColorSpaceStack.isEmpty())
-        {
-            nonStrokingColorSpaceStack.add(colorSpace);
-        }
-        else
-        {
-            nonStrokingColorSpaceStack.pop();
-            nonStrokingColorSpaceStack.push(colorSpace);
-        }
-    }
-
-    /**
-     * 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(float spacing) throws IOException
-    {
-        writeOperand(spacing);
-        writeOperator(OperatorName.SET_CHAR_SPACING);
-    }
-
-    /**
-     * Set the word spacing. The value shall be added to the horizontal or 
vertical component of the
-     * ASCII SPACE character, depending on the writing mode.
-     * <p>
-     * This will have an effect only with Type1 and TrueType fonts, not with 
Type0 fonts. The PDF
-     * specification tells why: "Word spacing shall be applied to every 
occurrence of the
-     * single-byte character code 32 in a string when using a simple font or a 
composite font that
-     * defines code 32 as a single-byte code. It shall not apply to 
occurrences of the byte value 32
-     * in multiple-byte codes."
-     *
-     * @param spacing word spacing
-     * @throws IOException If the content stream could not be written.
-     */
-    public void setWordSpacing(float spacing) throws IOException
-    {
-        writeOperand(spacing);
-        writeOperator(OperatorName.SET_WORD_SPACING);
-    }
-
-    /**
-     * Set the horizontal scaling to scale / 100.
-     *
-     * @param scale number specifying the percentage of the normal width. 
Default value: 100 (normal
-     * width).
-     * @throws IOException If the content stream could not be written.
-     */
-    public void setHorizontalScaling(float scale) throws IOException
-    {
-        writeOperand(scale);
-        writeOperator(OperatorName.SET_TEXT_HORIZONTAL_SCALING);
-    }
-
-    /**
-     * Set the text rendering mode. This determines whether showing text shall 
cause glyph outlines
-     * to be stroked, filled, used as a clipping boundary, or some combination 
of the three.
-     *
-     * @param rm The text rendering mode.
-     * @throws IOException If the content stream could not be written.
-     */
-    public void setRenderingMode(RenderingMode rm) throws IOException
-    {
-        writeOperand(rm.intValue());
-        writeOperator(OperatorName.SET_TEXT_RENDERINGMODE);
-    }
-
-    /**
-     * Set the text rise value, i.e. move the baseline up or down. This is 
useful for drawing
-     * superscripts or subscripts.
-     *
-     * @param rise Specifies the distance, in unscaled text space units, to 
move the baseline up or
-     * down from its default location. 0 restores the default location.
-     * @throws IOException
-     */
-    public void setTextRise(float rise) throws IOException
-    {
-        writeOperand(rise);
-        writeOperator(OperatorName.SET_TEXT_RISE);
-    }
-
-}
+/*
+ * 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.
+ */
+package org.apache.pdfbox.pdmodel;
+
+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.ArrayDeque;
+import java.util.Deque;
+import java.util.Locale;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.contentstream.operator.OperatorName;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdfwriter.COSWriter;
+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.graphics.state.RenderingMode;
+import org.apache.pdfbox.util.Charsets;
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.NumberFormatUtil;
+
+/**
+ * Provides the ability to write to a content stream.
+ *
+ * @author Ben Litchfield
+ */
+abstract class PDAbstractContentStream implements Closeable
+{
+    private static final Log LOG = 
LogFactory.getLog(PDAbstractContentStream.class);
+
+    protected final PDDocument document; // may be null
+
+    protected final OutputStream outputStream;
+    protected final PDResources resources;
+
+    protected boolean inTextMode = false;
+    protected final Deque<PDFont> fontStack = new ArrayDeque<PDFont>();
+
+    protected final Deque<PDColorSpace> nonStrokingColorSpaceStack = new 
ArrayDeque<PDColorSpace>();
+    protected final Deque<PDColorSpace> strokingColorSpaceStack = new 
ArrayDeque<PDColorSpace>();
+
+    // number format
+    private final NumberFormat formatDecimal = 
NumberFormat.getNumberInstance(Locale.US);
+    private final byte[] formatBuffer = new byte[32];
+
+    /**
+     * Create a new appearance stream.
+     *
+     * @param document may be null
+     * @param outputStream The appearances output stream to write to.
+     * @param resources The resources to use
+     */
+    PDAbstractContentStream(PDDocument document, OutputStream outputStream, 
PDResources resources)
+    {
+        this.document = document;
+        this.outputStream = outputStream;
+        this.resources = resources;
+
+        formatDecimal.setMaximumFractionDigits(4);
+        formatDecimal.setGroupingUsed(false);
+    }
+
+    /**
+     * Sets the maximum number of digits allowed for fractional numbers.
+     * 
+     * @see NumberFormat#setMaximumFractionDigits(int)
+     * @param fractionDigitsNumber
+     */
+    protected void setMaximumFractionDigits(int fractionDigitsNumber)
+    {
+        formatDecimal.setMaximumFractionDigits(fractionDigitsNumber);
+    }
+
+    /**
+     * 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 (inTextMode)
+        {
+            throw new IllegalStateException("Error: Nested beginText() calls 
are not allowed.");
+        }
+        writeOperator(OperatorName.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.
+     * @throws IllegalStateException If the method was not allowed to be 
called at this time.
+     */
+    public void endText() throws IOException
+    {
+        if (!inTextMode)
+        {
+            throw new IllegalStateException("Error: You must call beginText() 
before calling endText.");
+        }
+        writeOperator(OperatorName.END_TEXT);
+        inTextMode = 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(PDFont font, float fontSize) throws IOException
+    {
+        if (fontStack.isEmpty())
+        {
+            fontStack.add(font);
+        }
+        else
+        {
+            fontStack.pop();
+            fontStack.push(font);
+        }
+
+        // keep track of fonts which are configured for subsetting
+        if (font.willBeSubset())
+        {
+            if (document != null)
+            {
+                document.getFontsToSubset().add(font);
+            }
+            else
+            {
+                LOG.warn("Using the subsetted font '" + font.getName() +
+                        "' without a PDDocument context; call subset() before 
saving");
+            }
+        }
+
+        writeOperand(resources.add(font));
+        writeOperand(fontSize);
+        writeOperator(OperatorName.SET_FONT_AND_SIZE);
+    }
+
+    /**
+     * Shows the given text at the location specified by the current text 
matrix with the given
+     * interspersed positioning. This allows the user to efficiently position 
each glyph or sequence
+     * of glyphs.
+     *
+     * @param textWithPositioningArray An array consisting of String and Float 
types. Each String is
+     * output to the page using the current text matrix. Using the default 
coordinate system, each
+     * interspersed number adjusts the current text matrix by translating to 
the left or down for
+     * horizontal and vertical text respectively. The number is expressed in 
thousands of a text
+     * space unit, and may be negative.
+     *
+     * @throws IOException if an io exception occurs.
+     */
+    public void showTextWithPositioning(Object[] textWithPositioningArray) 
throws IOException
+    {
+        write("[");
+        for (Object obj : textWithPositioningArray)
+        {
+            if (obj instanceof String)
+            {
+                showTextInternal((String) obj);
+            }
+            else if (obj instanceof Float)
+            {
+                writeOperand((Float) obj);
+            }
+            else
+            {
+                throw new IllegalArgumentException("Argument must consist of 
array of Float and String types");
+            }
+        }
+        write("] ");
+        writeOperator(OperatorName.SHOW_TEXT_ADJUSTED);
+    }
+
+    /**
+     * 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.
+     * @throws IllegalArgumentException if a character isn't supported by the 
current font
+     */
+    public void showText(String text) throws IOException
+    {
+        showTextInternal(text);
+        write(" ");
+        writeOperator(OperatorName.SHOW_TEXT);
+    }
+
+    /**
+     * Outputs a string using the correct encoding and subsetting as required.
+     *
+     * @param text The Unicode text to show.
+     * 
+     * @throws IOException If an io exception occurs.
+     */
+    protected void showTextInternal(String text) throws IOException
+    {
+        if (!inTextMode)
+        {
+            throw new IllegalStateException("Must call beginText() before 
showText()");
+        }
+
+        if (fontStack.isEmpty())
+        {
+            throw new IllegalStateException("Must call setFont() before 
showText()");
+        }
+
+        PDFont font = fontStack.peek();
+
+        // complex text layout
+        byte[] encodedText = null;
+
+        if (encodedText == null)
+        {
+            encodedText = font.encode(text);
+        }
+
+        // Unicode code points to keep when subsetting
+        if (font.willBeSubset())
+        {
+            int offset = 0;
+            while (offset < text.length())
+            {
+                int codePoint = text.codePointAt(offset);
+                font.addToSubset(codePoint);
+                offset += Character.charCount(codePoint);
+            }
+        }
+
+        COSWriter.writeString(encodedText, outputStream);
+    }
+
+    /**
+     * 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(float leading) throws IOException
+    {
+        writeOperand(leading);
+        writeOperator(OperatorName.SET_TEXT_LEADING);
+    }
+
+    /**
+     * 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 (!inTextMode)
+        {
+            throw new IllegalStateException("Must call beginText() before 
newLine()");
+        }
+        writeOperator(OperatorName.NEXT_LINE);
+    }
+
+    /**
+     * 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(float tx, float ty) throws IOException
+    {
+        if (!inTextMode)
+        {
+            throw new IllegalStateException("Error: must call beginText() 
before newLineAtOffset()");
+        }
+        writeOperand(tx);
+        writeOperand(ty);
+        writeOperator(OperatorName.MOVE_TEXT);
+    }
+
+    /**
+     * 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(Matrix matrix) throws IOException
+    {
+        if (!inTextMode)
+        {
+            throw new IllegalStateException("Error: must call beginText() 
before setTextMatrix");
+        }
+        writeAffineTransform(matrix.createAffineTransform());
+        writeOperator(OperatorName.SET_MATRIX);
+    }
+
+    /**
+     * 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(PDImageXObject image, float x, 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(PDImageXObject image, float x, float y, float width, 
float height) throws IOException
+    {
+        if (inTextMode)
+        {
+            throw new IllegalStateException("Error: drawImage is not allowed 
within a text block.");
+        }
+
+        saveGraphicsState();
+
+        AffineTransform transform = new AffineTransform(width, 0, 0, height, 
x, y);
+        transform(new Matrix(transform));
+
+        writeOperand(resources.add(image));
+        writeOperator(OperatorName.DRAW_OBJECT);
+
+        restoreGraphicsState();
+    }
+
+    /**
+     * Draw an image at the origin with the given transformation matrix.
+     *
+     * @param image The image to draw.
+     * @param matrix The transformation matrix to apply to 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(PDImageXObject image, Matrix matrix) throws 
IOException
+    {
+        if (inTextMode)
+        {
+            throw new IllegalStateException("Error: drawImage is not allowed 
within a text block.");
+        }
+
+        saveGraphicsState();
+
+        AffineTransform transform = matrix.createAffineTransform();
+        transform(new Matrix(transform));
+
+        writeOperand(resources.add(image));
+        writeOperator(OperatorName.DRAW_OBJECT);
+
+        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(PDInlineImage inlineImage, float x, 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(PDInlineImage inlineImage, float x, float y, float 
width, float height) throws IOException
+    {
+        if (inTextMode)
+        {
+            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
+        StringBuilder sb = new StringBuilder();
+        sb.append(OperatorName.BEGIN_INLINE_IMAGE);
+
+        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());
+
+        COSArray decodeArray = inlineImage.getDecode();
+        if (decodeArray != null && decodeArray.size() > 0)
+        {
+            sb.append("\n /D ");
+            sb.append('[');
+            for (COSBase base : decodeArray)
+            {
+                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(OperatorName.BEGIN_INLINE_IMAGE_DATA);
+        writeBytes(inlineImage.getData());
+        writeLine();
+        writeOperator(OperatorName.END_INLINE_IMAGE);
+
+        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(PDFormXObject form) throws IOException
+    {
+        if (inTextMode)
+        {
+            throw new IllegalStateException("Error: drawForm is not allowed 
within a text block.");
+        }
+
+        writeOperand(resources.add(form));
+        writeOperator(OperatorName.DRAW_OBJECT);
+    }
+
+    /**
+     * The cm operator. Concatenates the given matrix with the current 
transformation matrix (CTM),
+     * which maps user space coordinates used within a PDF content stream into 
output device
+     * coordinates. More details on coordinates can be found in the PDF 32000 
specification, 8.3.2
+     * Coordinate Spaces.
+     *
+     * @param matrix the transformation matrix
+     * @throws IOException If there is an error writing to the stream.
+     */
+    public void transform(Matrix matrix) throws IOException
+    {
+        if (inTextMode)
+        {
+            throw new IllegalStateException("Error: Modifying the current 
transformation matrix is not allowed within text objects.");
+        }
+
+        writeAffineTransform(matrix.createAffineTransform());
+        writeOperator(OperatorName.CONCAT);
+    }
+
+    /**
+     * q operator. Saves the current graphics state.
+     * @throws IOException If an error occurs while writing to the stream.
+     */
+    public void saveGraphicsState() throws IOException
+    {
+        if (inTextMode)
+        {
+            throw new IllegalStateException("Error: Saving the graphics state 
is not allowed within text objects.");
+        }
+
+        if (!fontStack.isEmpty())
+        {
+            fontStack.push(fontStack.peek());
+        }
+        if (!strokingColorSpaceStack.isEmpty())
+        {
+            strokingColorSpaceStack.push(strokingColorSpaceStack.peek());
+        }
+        if (!nonStrokingColorSpaceStack.isEmpty())
+        {
+            nonStrokingColorSpaceStack.push(nonStrokingColorSpaceStack.peek());
+        }
+        writeOperator(OperatorName.SAVE);
+    }
+
+    /**
+     * Q operator. Restores the current graphics state.
+     * @throws IOException If an error occurs while writing to the stream.
+     */
+    public void restoreGraphicsState() throws IOException
+    {
+        if (inTextMode)
+        {
+            throw new IllegalStateException("Error: Restoring the graphics 
state is not allowed within text objects.");
+        }
+
+        if (!fontStack.isEmpty())
+        {
+            fontStack.pop();
+        }
+        if (!strokingColorSpaceStack.isEmpty())
+        {
+            strokingColorSpaceStack.pop();
+        }
+        if (!nonStrokingColorSpaceStack.isEmpty())
+        {
+            nonStrokingColorSpaceStack.pop();
+        }
+        writeOperator(OperatorName.RESTORE);
+    }
+
+    protected COSName getName(PDColorSpace colorSpace)
+    {
+        if (colorSpace instanceof PDDeviceGray ||
+            colorSpace instanceof PDDeviceRGB ||
+            colorSpace instanceof PDDeviceCMYK)
+        {
+            return COSName.getPDFName(colorSpace.getName());
+        }
+        else
+        {
+            return resources.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(PDColor color) throws IOException
+    {
+        PDColorSpace colorSpace = color.getColorSpace();
+        if (strokingColorSpaceStack.isEmpty() ||
+            strokingColorSpaceStack.peek() != colorSpace)
+        {
+            writeOperand(getName(colorSpace));
+            writeOperator(OperatorName.STROKING_COLORSPACE);
+            setStrokingColorSpaceStack(colorSpace);
+        }
+
+        for (float value : color.getComponents())
+        {
+            writeOperand(value);
+        }
+
+        if (colorSpace instanceof PDPattern)
+        {
+            writeOperand(color.getPatternName());
+        }
+
+        if (colorSpace instanceof PDPattern ||
+            colorSpace instanceof PDSeparation ||
+            colorSpace instanceof PDDeviceN ||
+            colorSpace instanceof PDICCBased)
+        {
+            writeOperator(OperatorName.STROKING_COLOR_N);
+        }
+        else
+        {
+            writeOperator(OperatorName.STROKING_COLOR);
+        }
+    }
+
+    /**
+     * 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(Color color) throws IOException
+    {
+        float[] components = new float[] {
+                color.getRed() / 255f, color.getGreen() / 255f, 
color.getBlue() / 255f };
+        PDColor pdColor = new PDColor(components, PDDeviceRGB.INSTANCE);
+        setStrokingColor(pdColor);
+    }
+
+    /**
+     * Set the stroking color in the DeviceRGB color space. Range is 0..1.
+     *
+     * @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(float r, float g, float b) throws IOException
+    {
+        if (isOutsideOneInterval(r) || isOutsideOneInterval(g) || 
isOutsideOneInterval(b))
+        {
+            throw new IllegalArgumentException("Parameters must be within 
0..1, but are "
+                    + String.format("(%.2f,%.2f,%.2f)", r, g, b));
+        }
+        writeOperand(r);
+        writeOperand(g);
+        writeOperand(b);
+        writeOperator(OperatorName.STROKING_COLOR_RGB);
+        setStrokingColorSpaceStack(PDDeviceRGB.INSTANCE);
+    }
+
+    /**
+     * 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.
+     * @deprecated use
+     * {@link #setStrokingColor(float, float, float) setStrokingColor(r/255f, 
g/255f, b/255f)}
+     */
+    @Deprecated
+    public void setStrokingColor(int r, int g, int b) throws IOException
+    {
+        if (isOutside255Interval(r) || isOutside255Interval(g) || 
isOutside255Interval(b))
+        {
+            throw new IllegalArgumentException("Parameters must be within 
0..255, but are "
+                    + String.format("(%d,%d,%d)", r, g, b));
+        }
+        setStrokingColor(r / 255f, g / 255f, b / 255f);
+    }
+
+    /**
+     * 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(float c, float m, float y, float k) throws 
IOException
+    {
+        if (isOutsideOneInterval(c) || isOutsideOneInterval(m) || 
isOutsideOneInterval(y) || isOutsideOneInterval(k))
+        {
+            throw new IllegalArgumentException("Parameters must be within 
0..1, but are "
+                    + String.format("(%.2f,%.2f,%.2f,%.2f)", c, m, y, k));
+        }
+        writeOperand(c);
+        writeOperand(m);
+        writeOperand(y);
+        writeOperand(k);
+        writeOperator(OperatorName.STROKING_COLOR_CMYK);
+        setStrokingColorSpaceStack(PDDeviceCMYK.INSTANCE);
+    }
+
+    /**
+     * 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(float g) throws IOException
+    {
+        if (isOutsideOneInterval(g))
+        {
+            throw new IllegalArgumentException("Parameter must be within 0..1, 
but is " + g);
+        }
+        writeOperand(g);
+        writeOperator(OperatorName.STROKING_COLOR_GRAY);
+        setStrokingColorSpaceStack(PDDeviceGray.INSTANCE);
+    }
+
+    /**
+     * 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(PDColor color) throws IOException
+    {
+        PDColorSpace colorSpace = color.getColorSpace();
+        if (nonStrokingColorSpaceStack.isEmpty() ||
+            nonStrokingColorSpaceStack.peek() != colorSpace)
+        {
+            writeOperand(getName(colorSpace));
+            writeOperator(OperatorName.NON_STROKING_COLORSPACE);
+            setNonStrokingColorSpaceStack(colorSpace);
+        }
+
+        for (float value : color.getComponents())
+        {
+            writeOperand(value);
+        }
+
+        if (colorSpace instanceof PDPattern)
+        {
+            writeOperand(color.getPatternName());
+        }
+
+        if (colorSpace instanceof PDPattern ||
+            colorSpace instanceof PDSeparation ||
+            colorSpace instanceof PDDeviceN ||
+            colorSpace instanceof PDICCBased)
+        {
+            writeOperator(OperatorName.NON_STROKING_COLOR_N);
+        }
+        else
+        {
+            writeOperator(OperatorName.NON_STROKING_COLOR);
+        }
+    }
+
+    /**
+     * 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(Color color) throws IOException
+    {
+        float[] components = new float[] {
+                color.getRed() / 255f, color.getGreen() / 255f, 
color.getBlue() / 255f };
+        PDColor pdColor = new PDColor(components, PDDeviceRGB.INSTANCE);
+        setNonStrokingColor(pdColor);
+    }
+
+    /**
+     * Set the non-stroking color in the DeviceRGB color space. Range is 0..1.
+     *
+     * @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(float r, float g, float b) throws 
IOException
+    {
+        if (isOutsideOneInterval(r) || isOutsideOneInterval(g) || 
isOutsideOneInterval(b))
+        {
+            throw new IllegalArgumentException("Parameters must be within 
0..1, but are "
+                    + String.format("(%.2f,%.2f,%.2f)", r, g, b));
+        }
+        writeOperand(r);
+        writeOperand(g);
+        writeOperand(b);
+        writeOperator(OperatorName.NON_STROKING_RGB);
+        setNonStrokingColorSpaceStack(PDDeviceRGB.INSTANCE);
+    }
+
+    /**
+     * 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.
+     * @deprecated use
+     * {@link #setNonStrokingColor(float, float, float) 
setNonStrokingColor(r/255f, g/255f, b/255f)}
+     */
+    @Deprecated
+    public void setNonStrokingColor(int r, int g, int b) throws IOException
+    {
+        if (isOutside255Interval(r) || isOutside255Interval(g) || 
isOutside255Interval(b))
+        {
+            throw new IllegalArgumentException("Parameters must be within 
0..255, but are "
+                    + String.format("(%d,%d,%d)", r, g, b));
+        }
+        setNonStrokingColor(r / 255f, g / 255f, b / 255f);
+    }
+
+    /**
+     * 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.
+     * @deprecated Use {@link #setStrokingColor(float, float, float, float) 
setStrokingColor(c/255f, m/255f, y/255f, k/255f)} instead.
+     */
+    @Deprecated
+    public void setNonStrokingColor(int c, int m, int y, int k) throws 
IOException
+    {
+        if (isOutside255Interval(c) || isOutside255Interval(m) || 
isOutside255Interval(y) || isOutside255Interval(k))
+        {
+            throw new IllegalArgumentException("Parameters must be within 
0..255, but are "
+                    + String.format("(%d,%d,%d,%d)", c, m, y, k));
+        }
+        setNonStrokingColor(c / 255f, m / 255f, y / 255f, k / 255f);
+    }
+
+    /**
+     * Set the non-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.
+     */
+    public void setNonStrokingColor(float c, float m, float y, float k) throws 
IOException
+    {
+        if (isOutsideOneInterval(c) || isOutsideOneInterval(m) || 
isOutsideOneInterval(y) || isOutsideOneInterval(k))
+        {
+            throw new IllegalArgumentException("Parameters must be within 
0..1, but are "
+                    + String.format("(%.2f,%.2f,%.2f,%.2f)", c, m, y, k));
+        }
+        writeOperand(c);
+        writeOperand(m);
+        writeOperand(y);
+        writeOperand(k);
+        writeOperator(OperatorName.NON_STROKING_CMYK);
+        setNonStrokingColorSpaceStack(PDDeviceCMYK.INSTANCE);
+    }
+
+    /**
+     * 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.
+     * @deprecated use {@link #setNonStrokingColor(float) 
setNonStrokingColor(g/255f)}
+     */
+    public void setNonStrokingColor(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(float g) throws IOException
+    {
+        if (isOutsideOneInterval(g))
+        {
+            throw new IllegalArgumentException("Parameter must be within 0..1, 
but is " + g);
+        }
+        writeOperand(g);
+        writeOperator(OperatorName.NON_STROKING_GRAY);
+        setNonStrokingColorSpaceStack(PDDeviceGray.INSTANCE);
+    }
+
+    /**
+     * 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(float x, float y, float width, float height) throws 
IOException
+    {
+        if (inTextMode)
+        {
+            throw new IllegalStateException("Error: addRect is not allowed 
within a text block.");
+        }
+        writeOperand(x);
+        writeOperand(y);
+        writeOperand(width);
+        writeOperand(height);
+        writeOperator(OperatorName.APPEND_RECT);
+    }
+
+    /**
+     * 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(float x1, float y1, float x2, float y2, float x3, 
float y3) throws IOException
+    {
+        if (inTextMode)
+        {
+            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(OperatorName.CURVE_TO);
+    }
+
+    /**
+     * 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(float x2, float y2, float x3, float y3) throws 
IOException
+    {
+        if (inTextMode)
+        {
+            throw new IllegalStateException("Error: curveTo2 is not allowed 
within a text block.");
+        }
+        writeOperand(x2);
+        writeOperand(y2);
+        writeOperand(x3);
+        writeOperand(y3);
+        writeOperator(OperatorName.CURVE_TO_REPLICATE_INITIAL_POINT);
+    }
+
+    /**
+     * 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(float x1, float y1, float x3, float y3) throws 
IOException
+    {
+        if (inTextMode)
+        {
+            throw new IllegalStateException("Error: curveTo1 is not allowed 
within a text block.");
+        }
+        writeOperand(x1);
+        writeOperand(y1);
+        writeOperand(x3);
+        writeOperand(y3);
+        writeOperator(OperatorName.CURVE_TO_REPLICATE_FINAL_POINT);
+    }
+
+    /**
+     * 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(float x, float y) throws IOException
+    {
+        if (inTextMode)
+        {
+            throw new IllegalStateException("Error: moveTo is not allowed 
within a text block.");
+        }
+        writeOperand(x);
+        writeOperand(y);
+        writeOperator(OperatorName.MOVE_TO);
+    }
+
+    /**
+     * 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(float x, float y) throws IOException
+    {
+        if (inTextMode)
+        {
+            throw new IllegalStateException("Error: lineTo is not allowed 
within a text block.");
+        }
+        writeOperand(x);
+        writeOperand(y);
+        writeOperator(OperatorName.LINE_TO);
+    }
+
+    /**
+     * 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 (inTextMode)
+        {
+            throw new IllegalStateException("Error: stroke is not allowed 
within a text block.");
+        }
+        writeOperator(OperatorName.STROKE_PATH);
+    }
+
+    /**
+     * 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 (inTextMode)
+        {
+            throw new IllegalStateException("Error: closeAndStroke is not 
allowed within a text block.");
+        }
+        writeOperator(OperatorName.CLOSE_AND_STROKE);
+    }
+
+    /**
+     * Fills the path using the nonzero winding number 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 (inTextMode)
+        {
+            throw new IllegalStateException("Error: fill is not allowed within 
a text block.");
+        }
+        writeOperator(OperatorName.FILL_NON_ZERO);
+    }
+
+    /**
+     * 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 (inTextMode)
+        {
+            throw new IllegalStateException("Error: fillEvenOdd is not allowed 
within a text block.");
+        }
+        writeOperator(OperatorName.FILL_EVEN_ODD);
+    }
+
+    /**
+     * Fill and then stroke the path, using the nonzero winding number rule to 
determine the region
+     * to fill. This shall produce the same result as constructing two 
identical path objects,
+     * painting the first with {@link #fill() } and the second with {@link 
#stroke() }.
+     *
+     * @throws IOException If the content stream could not be written
+     * @throws IllegalStateException If the method was called within a text 
block.
+     */
+    public void fillAndStroke() throws IOException
+    {
+        if (inTextMode)
+        {
+            throw new IllegalStateException("Error: fillAndStroke is not 
allowed within a text block.");
+        }
+        writeOperator(OperatorName.FILL_NON_ZERO_AND_STROKE);
+    }
+
+    /**
+     * Fill and then stroke the path, using the even-odd rule to determine the 
region to
+     * fill. This shall produce the same result as constructing two identical 
path objects, painting
+     * the first with {@link #fillEvenOdd() } and the second with {@link 
#stroke() }.
+     *
+     * @throws IOException If the content stream could not be written
+     * @throws IllegalStateException If the method was called within a text 
block.
+     */
+    public void fillAndStrokeEvenOdd() throws IOException
+    {
+        if (inTextMode)
+        {
+            throw new IllegalStateException("Error: fillAndStrokeEvenOdd is 
not allowed within a text block.");
+        }
+        writeOperator(OperatorName.FILL_EVEN_ODD_AND_STROKE);
+    }
+
+    /**
+     * Close, fill, and then stroke the path, using the nonzero winding number 
rule to determine the
+     * region to fill. This shall have the same effect as the sequence {@link 
#closePath() }
+     * and then {@link #fillAndStroke() }.
+     *
+     * @throws IOException If the content stream could not be written
+     * @throws IllegalStateException If the method was called within a text 
block.
+     */
+    public void closeAndFillAndStroke() throws IOException
+    {
+        if (inTextMode)
+        {
+            throw new IllegalStateException("Error: closeAndFillAndStroke is 
not allowed within a text block.");
+        }
+        writeOperator(OperatorName.CLOSE_FILL_NON_ZERO_AND_STROKE);
+    }
+
+    /**
+     * Close, fill, and then stroke the path, using the even-odd rule to 
determine the region to
+     * fill. This shall have the same effect as the sequence {@link 
#closePath() }
+     * and then {@link #fillAndStrokeEvenOdd() }.
+     *
+     * @throws IOException If the content stream could not be written
+     * @throws IllegalStateException If the method was called within a text 
block.
+     */
+    public void closeAndFillAndStrokeEvenOdd() throws IOException
+    {
+        if (inTextMode)
+        {
+            throw new IllegalStateException("Error: 
closeAndFillAndStrokeEvenOdd is not allowed within a text block.");
+        }
+        writeOperator(OperatorName.CLOSE_FILL_EVEN_ODD_AND_STROKE);
+    }
+
+    /**
+     * 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(PDShading shading) throws IOException
+    {
+        if (inTextMode)
+        {
+            throw new IllegalStateException("Error: shadingFill is not allowed 
within a text block.");
+        }
+
+        writeOperand(resources.add(shading));
+        writeOperator(OperatorName.SHADING_FILL);
+    }
+
+    /**
+     * 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 (inTextMode)
+        {
+            throw new IllegalStateException("Error: closePath is not allowed 
within a text block.");
+        }
+        writeOperator(OperatorName.CLOSE_PATH);
+    }
+
+    /**
+     * 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 (inTextMode)
+        {
+            throw new IllegalStateException("Error: clip is not allowed within 
a text block.");
+        }
+        writeOperator(OperatorName.CLIP_NON_ZERO);
+        
+        // end path without filling or stroking
+        writeOperator(OperatorName.ENDPATH);
+    }
+
+    /**
+     * 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 (inTextMode)
+        {
+            throw new IllegalStateException("Error: clipEvenOdd is not allowed 
within a text block.");
+        }
+        writeOperator(OperatorName.CLIP_EVEN_ODD);
+        
+        // end path without filling or stroking
+        writeOperator(OperatorName.ENDPATH);
+    }
+
+    /**
+     * 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
+     */
+    public void setLineWidth(float lineWidth) throws IOException
+    {
+        writeOperand(lineWidth);
+        writeOperator(OperatorName.SET_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 the content stream could not be written.
+     * @throws IllegalArgumentException If the parameter is not a valid line 
join style.
+     */
+    public void setLineJoinStyle(int lineJoinStyle) throws IOException
+    {
+        if (lineJoinStyle >= 0 && lineJoinStyle <= 2)
+        {
+            writeOperand(lineJoinStyle);
+            writeOperator(OperatorName.SET_LINE_JOINSTYLE);
+        }
+        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 IllegalArgumentException If the parameter is not a valid line 
cap style.
+     */
+    public void setLineCapStyle(int lineCapStyle) throws IOException
+    {
+        if (lineCapStyle >= 0 && lineCapStyle <= 2)
+        {
+            writeOperand(lineCapStyle);
+            writeOperator(OperatorName.SET_LINE_CAPSTYLE);
+        }
+        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.
+     */
+    public void setLineDashPattern(float[] pattern, float phase) throws 
IOException
+    {
+        write("[");
+        for (float value : pattern)
+        {
+            writeOperand(value);
+        }
+        write("] ");
+        writeOperand(phase);
+        writeOperator(OperatorName.SET_LINE_DASHPATTERN);
+    }
+
+    /**
+     * Set the miter limit.
+     *
+     * @param miterLimit the new miter limit.
+     * @throws IOException If the content stream could not be written.
+     * @throws IllegalArgumentException If the parameter is \u2264 0.
+     */
+    public void setMiterLimit(float miterLimit) throws IOException
+    {
+        if (miterLimit <= 0.0)
+        {
+            throw new IllegalArgumentException("A miter limit <= 0 is invalid 
and will not render in Acrobat Reader");
+        }
+        writeOperand(miterLimit);
+        writeOperator(OperatorName.SET_LINE_MITERLIMIT);
+    }
+
+    /**
+     * Begin a marked content sequence.
+     *
+     * @param tag the tag
+     * @throws IOException If the content stream could not be written
+     */
+    public void beginMarkedContent(COSName tag) throws IOException
+    {
+        writeOperand(tag);
+        writeOperator(OperatorName.BEGIN_MARKED_CONTENT);
+    }
+
+    /**
+     * Begin a marked content sequence with a reference to the marked content 
identifier (MCID).
+     *
+     * @param tag the tag to be added to the content stream
+     * @param mcid the marked content identifier (MCID)
+     * @throws IOException If the content stream could not be written
+     */
+    public void beginMarkedContent(COSName tag, int mcid) throws IOException
+    {
+        if (mcid < 0)
+        {
+            throw new IllegalArgumentException("mcid should not be negative");
+        }
+        writeOperand(tag);
+        write("<</MCID " + mcid + ">> ");
+        writeOperator(OperatorName.BEGIN_MARKED_CONTENT_SEQ);
+    }
+
+    /**
+     * 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(COSName tag, PDPropertyList propertyList) 
throws IOException
+    {
+        writeOperand(tag);
+
+        COSDictionary dict = propertyList.getCOSObject();
+        if (dict.getInt(COSName.MCID) > -1 && dict.size() == 1)
+        {
+            // PDFBOX-5890: use simplified notation if there's only an MCID
+            write("<</MCID " + dict.getInt(COSName.MCID) + ">> ");
+        }
+        else
+        {
+            writeOperand(resources.add(propertyList));
+        }
+
+        writeOperator(OperatorName.BEGIN_MARKED_CONTENT_SEQ);
+    }
+
+    /**
+     * End a marked content sequence.
+     *
+     * @throws IOException If the content stream could not be written
+     */
+    public void endMarkedContent() throws IOException
+    {
+        writeOperator(OperatorName.END_MARKED_CONTENT);
+    }
+
+    /**
+     * Set an extended graphics state.
+     * 
+     * @param state The extended graphics state.
+     * @throws IOException If the content stream could not be written.
+     */
+    public void setGraphicsStateParameters(PDExtendedGraphicsState state) 
throws IOException
+    {
+        writeOperand(resources.add(state));
+        writeOperator(OperatorName.SET_GRAPHICS_STATE_PARAMS);
+    }
+
+    /**
+     * Write a comment line.
+     *
+     * @param comment
+     * @throws IOException If the content stream could not be written.
+     * @throws IllegalArgumentException If the comment contains a newline. 
This is not allowed,
+     * because the next line could be ordinary PDF content.
+     */
+    public void addComment(String comment) throws IOException
+    {
+        if (comment.indexOf('\n') >= 0 || comment.indexOf('\r') >= 0)
+        {
+            throw new IllegalArgumentException("comment should not include a 
newline");
+        }
+        outputStream.write('%');
+        outputStream.write(comment.getBytes(Charsets.US_ASCII));
+        outputStream.write('\n');
+    }
+
+    /**
+     * Writes a real number to the content stream.
+     * @param real
+     * @throws java.io.IOException
+     * @throws IllegalArgumentException if the parameter is not a finite number
+     */
+    protected void writeOperand(float real) throws IOException
+    {
+        if (Float.isInfinite(real) || Float.isNaN(real))
+        {
+            throw new IllegalArgumentException(real + " is not a finite 
number");
+        }
+
+        int byteCount = NumberFormatUtil.formatFloatFast(real, 
formatDecimal.getMaximumFractionDigits(), formatBuffer);
+
+        if (byteCount == -1)
+        {
+            //Fast formatting failed
+            write(formatDecimal.format(real));
+        }
+        else
+        {
+            outputStream.write(formatBuffer, 0, byteCount);
+        }
+        outputStream.write(' ');
+    }
+
+    /**
+     * Writes an integer number to the content stream.
+     * @param integer
+     * @throws java.io.IOException
+     */
+    protected void writeOperand(int integer) throws IOException
+    {
+        write(formatDecimal.format(integer));
+        outputStream.write(' ');
+    }
+
+    /**
+     * Writes a COSName to the content stream.
+     * @param name
+     * @throws java.io.IOException
+     */
+    protected void writeOperand(COSName name) throws IOException
+    {
+        name.writePDF(outputStream);
+        outputStream.write(' ');
+    }
+
+    /**
+     * Writes a string to the content stream as ASCII.
+     * @param text
+     * @throws java.io.IOException
+     */
+    protected void writeOperator(String text) throws IOException
+    {
+        outputStream.write(text.getBytes(Charsets.US_ASCII));
+        outputStream.write('\n');
+    }
+
+    /**
+     * Writes a string to the content stream as ASCII.
+     * @param text
+     * @throws java.io.IOException
+     */
+    protected void write(String text) throws IOException
+    {
+        outputStream.write(text.getBytes(Charsets.US_ASCII));
+    }
+
+    /**
+     * Writes a byte[] to the content stream.
+     * @param data
+     * @throws java.io.IOException
+     */
+    protected void write(byte[] data) throws IOException
+    {
+        outputStream.write(data);
+    }
+    
+    /**
+     * Writes a newline to the content stream as ASCII.
+     * @throws java.io.IOException
+     */
+    protected void writeLine() throws IOException
+    {
+        outputStream.write('\n');
+    }
+
+    /**
+     * Writes binary data to the content stream.
+     * @param data
+     * @throws java.io.IOException
+     */
+    protected void writeBytes(byte[] data) throws IOException
+    {
+        outputStream.write(data);
+    }
+
+    /**
+     * Writes an AffineTransform to the content stream as an array.
+     */
+    private void writeAffineTransform(AffineTransform transform) throws 
IOException
+    {
+        double[] values = new double[6];
+        transform.getMatrix(values);
+        for (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
+    {
+        if (inTextMode)
+        {
+            LOG.warn("You did not call endText(), some viewers won't display 
your text");
+        }
+        outputStream.close();
+    }
+
+    protected boolean isOutside255Interval(int val)
+    {
+        return val < 0 || val > 255;
+    }
+
+    private boolean isOutsideOneInterval(double val)
+    {
+        return val < 0 || val > 1;
+    }
+
+    protected void setStrokingColorSpaceStack(PDColorSpace colorSpace)
+    {
+        if (strokingColorSpaceStack.isEmpty())
+        {
+            strokingColorSpaceStack.add(colorSpace);
+        }
+        else
+        {
+            strokingColorSpaceStack.pop();
+            strokingColorSpaceStack.push(colorSpace);
+        }
+    }
+
+    protected void setNonStrokingColorSpaceStack(PDColorSpace colorSpace)
+    {
+        if (nonStrokingColorSpaceStack.isEmpty())
+        {
+            nonStrokingColorSpaceStack.add(colorSpace);
+        }
+        else
+        {
+            nonStrokingColorSpaceStack.pop();
+            nonStrokingColorSpaceStack.push(colorSpace);
+        }
+    }
+
+    /**
+     * 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(float spacing) throws IOException
+    {
+        writeOperand(spacing);
+        writeOperator(OperatorName.SET_CHAR_SPACING);
+    }
+
+    /**
+     * Set the word spacing. The value shall be added to the horizontal or 
vertical component of the
+     * ASCII SPACE character, depending on the writing mode.
+     * <p>
+     * This will have an effect only with Type1 and TrueType fonts, not with 
Type0 fonts. The PDF
+     * specification tells why: "Word spacing shall be applied to every 
occurrence of the
+     * single-byte character code 32 in a string when using a simple font or a 
composite font that
+     * defines code 32 as a single-byte code. It shall not apply to 
occurrences of the byte value 32
+     * in multiple-byte codes."
+     *
+     * @param spacing word spacing
+     * @throws IOException If the content stream could not be written.
+     */
+    public void setWordSpacing(float spacing) throws IOException
+    {
+        writeOperand(spacing);
+        writeOperator(OperatorName.SET_WORD_SPACING);
+    }
+
+    /**
+     * Set the horizontal scaling to scale / 100.
+     *
+     * @param scale number specifying the percentage of the normal width. 
Default value: 100 (normal
+     * width).
+     * @throws IOException If the content stream could not be written.
+     */
+    public void setHorizontalScaling(float scale) throws IOException
+    {
+        writeOperand(scale);
+        writeOperator(OperatorName.SET_TEXT_HORIZONTAL_SCALING);
+    }
+
+    /**
+     * Set the text rendering mode. This determines whether showing text shall 
cause glyph outlines
+     * to be stroked, filled, used as a clipping boundary, or some combination 
of the three.
+     *
+     * @param rm The text rendering mode.
+     * @throws IOException If the content stream could not be written.
+     */
+    public void setRenderingMode(RenderingMode rm) throws IOException
+    {
+        writeOperand(rm.intValue());
+        writeOperator(OperatorName.SET_TEXT_RENDERINGMODE);
+    }
+
+    /**
+     * Set the text rise value, i.e. move the baseline up or down. This is 
useful for drawing
+     * superscripts or subscripts.
+     *
+     * @param rise Specifies the distance, in unscaled text space units, to 
move the baseline up or
+     * down from its default location. 0 restores the default location.
+     * @throws IOException
+     */
+    public void setTextRise(float rise) throws IOException
+    {
+        writeOperand(rise);
+        writeOperator(OperatorName.SET_TEXT_RISE);
+    }
+
+}

Reply via email to