You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ms...@apache.org on 2016/05/22 15:04:38 UTC
svn commit: r1745066 [1/3] - in
/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox:
contentstream/PDAbstractContentStream.java pdmodel/PDPageContentStream.java
pdmodel/interactive/annotation/PDAppearanceContentStream.java
Author: msahyoun
Date: Sun May 22 15:04:38 2016
New Revision: 1745066
URL: http://svn.apache.org/viewvc?rev=1745066&view=rev
Log:
PDFBOX-3353: create abstract class for shared content stream writing functionality.
Added:
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/contentstream/PDAbstractContentStream.java (with props)
Modified:
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPageContentStream.java
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceContentStream.java
Added: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/contentstream/PDAbstractContentStream.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/contentstream/PDAbstractContentStream.java?rev=1745066&view=auto
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/contentstream/PDAbstractContentStream.java (added)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/contentstream/PDAbstractContentStream.java Sun May 22 15:04:38 2016
@@ -0,0 +1,1479 @@
+/*
+ * 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.contentstream;
+
+import java.awt.Color;
+import java.awt.geom.AffineTransform;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.NumberFormat;
+import java.util.Locale;
+import java.util.Stack;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdfwriter.COSWriter;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.documentinterchange.markedcontent.PDPropertyList;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceN;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
+import org.apache.pdfbox.pdmodel.graphics.color.PDPattern;
+import org.apache.pdfbox.pdmodel.graphics.color.PDSeparation;
+import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
+import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
+import org.apache.pdfbox.pdmodel.graphics.image.PDInlineImage;
+import org.apache.pdfbox.pdmodel.graphics.shading.PDShading;
+import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
+import org.apache.pdfbox.util.Charsets;
+import org.apache.pdfbox.util.Matrix;
+
+/**
+ * Provides the ability to write to a content stream.
+ *
+ * @author Ben Litchfield
+ */
+public abstract class PDAbstractContentStream implements Closeable
+{
+
+ /**
+ * This is to choose what to do with the stream: overwrite, append or prepend.
+ */
+ public static enum AppendMode
+ {
+ /**
+ * Overwrite the existing page content streams.
+ */
+ OVERWRITE,
+ /**
+ * Append the content stream after all existing page content streams.
+ */
+ APPEND,
+ /**
+ * Insert before all other page content streams.
+ */
+ PREPEND;
+
+ public boolean isOverwrite()
+ {
+ return this == OVERWRITE;
+ }
+
+ public boolean isPrepend()
+ {
+ return this == PREPEND;
+ }
+ }
+
+ private OutputStream output;
+ private PDResources resources;
+
+ private boolean inTextMode = false;
+ private final Stack<PDFont> fontStack = new Stack<PDFont>();
+
+ private final Stack<PDColorSpace> nonStrokingColorSpaceStack = new Stack<PDColorSpace>();
+ private final Stack<PDColorSpace> strokingColorSpaceStack = new Stack<PDColorSpace>();
+
+ // number format
+ private final NumberFormat formatDecimal = NumberFormat.getNumberInstance(Locale.US);
+
+ /**
+ * Create a new appearance stream.
+ *
+ * @throws IOException If there is an error writing to the page contents.
+ */
+ public PDAbstractContentStream() throws IOException
+ {
+ }
+
+ /**
+ * Create a new appearance stream.
+ *
+ * @param doc The document the page is part of.
+ * @param appearance The appearance stream to write to.
+ * @throws IOException If there is an error writing to the page contents.
+ */
+ public PDAbstractContentStream(PDAppearanceStream appearance) throws IOException
+ {
+ this (appearance, appearance.getStream().createOutputStream());
+ }
+
+ /**
+ * Create a new appearance stream. Note that this is not actually a "page" content stream.
+ *
+ * @param doc The document the appearance is part of.
+ * @param appearance The appearance stream to add to.
+ * @param outputStream The appearances output stream to write to.
+ * @throws IOException If there is an error writing to the page contents.
+ */
+ public PDAbstractContentStream(PDAppearanceStream appearance, OutputStream outputStream)
+ throws IOException
+ {
+ output = outputStream;
+ this.resources = appearance.getResources();
+
+ formatDecimal.setMaximumFractionDigits(4);
+ formatDecimal.setGroupingUsed(false);
+ }
+
+ public void setOutput(OutputStream outputStream)
+ {
+ this.output = outputStream;
+ }
+
+ public PDResources getResources()
+ {
+ return resources;
+ }
+
+ public void setResources(PDResources resources)
+ {
+ this.resources = resources;
+ }
+
+ public OutputStream getOutput()
+ {
+ return this.output;
+ }
+
+ public Stack<PDColorSpace> getStrokingColorSpaceStack()
+ {
+ return strokingColorSpaceStack;
+ }
+
+ public Stack<PDColorSpace> getNonStrokingColorSpaceStack()
+ {
+ return nonStrokingColorSpaceStack;
+ }
+
+
+ public boolean isInTextMode()
+ {
+ return inTextMode;
+ }
+
+ /**
+ * 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("BT");
+ 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("ET");
+ 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.setElementAt(font, fontStack.size() - 1);
+ }
+
+ writeOperand(resources.add(font));
+ writeOperand(fontSize);
+ writeOperator("Tf");
+ }
+
+ /**
+ * Shows the given text at the location specified by the current text matrix.
+ *
+ * @param text The Unicode text to show.
+ * @throws IOException If an io exception occurs.
+ */
+ public void showText(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();
+
+ // Unicode code points to keep when subsetting
+ if (font.willBeSubset())
+ {
+ for (int offset = 0; offset < text.length(); )
+ {
+ int codePoint = text.codePointAt(offset);
+ font.addToSubset(codePoint);
+ offset += Character.charCount(codePoint);
+ }
+ }
+
+ COSWriter.writeString(font.encode(text), output);
+ write(" ");
+
+ writeOperator("Tj");
+ }
+
+ /**
+ * 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(double leading) throws IOException
+ {
+ writeOperand((float) leading);
+ writeOperator("TL");
+ }
+
+ /**
+ * 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("T*");
+ }
+
+ /**
+ * 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("Td");
+ }
+
+ /**
+ * 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("Tm");
+ }
+
+ /**
+ * 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("Do");
+
+ 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("Do");
+
+ 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("BI");
+
+ sb.append("\n /W ");
+ sb.append(inlineImage.getWidth());
+
+ sb.append("\n /H ");
+ sb.append(inlineImage.getHeight());
+
+ sb.append("\n /CS ");
+ sb.append("/");
+ sb.append(inlineImage.getColorSpace().getName());
+
+ if (inlineImage.getDecode() != null && inlineImage.getDecode().size() > 0)
+ {
+ sb.append("\n /D ");
+ sb.append("[");
+ for (COSBase base : inlineImage.getDecode())
+ {
+ sb.append(((COSNumber) base).intValue());
+ sb.append(" ");
+ }
+ sb.append("]");
+ }
+
+ if (inlineImage.isStencil())
+ {
+ sb.append("\n /IM true");
+ }
+
+ sb.append("\n /BPC ");
+ sb.append(inlineImage.getBitsPerComponent());
+
+ // image dictionary
+ write(sb.toString());
+ writeLine();
+
+ // binary data
+ writeOperator("ID");
+ writeBytes(inlineImage.getData());
+ writeLine();
+ writeOperator("EI");
+
+ 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("Do");
+ }
+
+ /**
+ * The cm operator. Concatenates the given matrix with the CTM.
+ *
+ * @param matrix the transformation matrix
+ * @throws IOException If there is an error writing to the stream.
+ */
+ public void transform(Matrix matrix) throws IOException
+ {
+ writeAffineTransform(matrix.createAffineTransform());
+ writeOperator("cm");
+ }
+
+ /**
+ * q operator. Saves the current graphics state.
+ * @throws IOException If an error occurs while writing to the stream.
+ */
+ public void saveGraphicsState() throws IOException
+ {
+ if (!fontStack.isEmpty())
+ {
+ fontStack.push(fontStack.peek());
+ }
+ if (!strokingColorSpaceStack.isEmpty())
+ {
+ strokingColorSpaceStack.push(strokingColorSpaceStack.peek());
+ }
+ if (!nonStrokingColorSpaceStack.isEmpty())
+ {
+ nonStrokingColorSpaceStack.push(nonStrokingColorSpaceStack.peek());
+ }
+ writeOperator("q");
+ }
+
+ /**
+ * Q operator. Restores the current graphics state.
+ * @throws IOException If an error occurs while writing to the stream.
+ */
+ public void restoreGraphicsState() throws IOException
+ {
+ if (!fontStack.isEmpty())
+ {
+ fontStack.pop();
+ }
+ if (!strokingColorSpaceStack.isEmpty())
+ {
+ strokingColorSpaceStack.pop();
+ }
+ if (!nonStrokingColorSpaceStack.isEmpty())
+ {
+ nonStrokingColorSpaceStack.pop();
+ }
+ writeOperator("Q");
+ }
+
+ protected COSName getName(PDColorSpace colorSpace) throws IOException
+ {
+ if (colorSpace instanceof PDDeviceGray ||
+ colorSpace instanceof PDDeviceRGB ||
+ colorSpace instanceof PDDeviceCMYK)
+ {
+ return COSName.getPDFName(colorSpace.getName());
+ }
+ else
+ {
+ return resources.add(colorSpace);
+ }
+ }
+
+ public void setStrokingColor(float[] components) throws IOException
+ {
+ for (float value : components)
+ {
+ writeOperand(value);
+ }
+
+ int numComponents = components.length;
+ switch(numComponents)
+ {
+ case 1:
+ writeOperator("G");
+ break;
+ case 3:
+ writeOperator("RG");
+ break;
+ case 4:
+ writeOperator("K");
+ }
+ }
+
+ /**
+ * 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("CS");
+ 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("SCN");
+ }
+ else
+ {
+ writeOperator("SC");
+ }
+ }
+
+ /**
+ * 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..255.
+ *
+ * @param r The red value
+ * @param g The green value.
+ * @param b The blue value.
+ * @throws IOException If an IO error occurs while writing to the stream.
+ * @throws IllegalArgumentException If the parameters are invalid.
+ */
+ public void setStrokingColor(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));
+ }
+ writeOperand(r / 255f);
+ writeOperand(g / 255f);
+ writeOperand(b / 255f);
+ writeOperator("RG");
+ setStrokingColorSpaceStack(PDDeviceRGB.INSTANCE);
+ }
+
+ /**
+ * 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("K");
+ 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(double g) throws IOException
+ {
+ if (isOutsideOneInterval(g))
+ {
+ throw new IllegalArgumentException("Parameter must be within 0..1, but is " + g);
+ }
+ writeOperand((float) g);
+ writeOperator("G");
+ 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("cs");
+ 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("scn");
+ }
+ else
+ {
+ writeOperator("sc");
+ }
+ }
+
+ /**
+ * 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..255.
+ *
+ * @param r The red value.
+ * @param g The green value.
+ * @param b The blue value.
+ * @throws IOException If an IO error occurs while writing to the stream.
+ * @throws IllegalArgumentException If the parameters are invalid.
+ */
+ public void setNonStrokingColor(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));
+ }
+ writeOperand(r / 255f);
+ writeOperand(g / 255f);
+ writeOperand(b / 255f);
+ writeOperator("rg");
+ setNonStrokingColorSpaceStack(PDDeviceRGB.INSTANCE);
+ }
+
+ /**
+ * Set the non-stroking color in the DeviceCMYK color space. Range is 0..255.
+ *
+ * @param c The cyan value.
+ * @param m The magenta value.
+ * @param y The yellow value.
+ * @param k The black value.
+ * @throws IOException If an IO error occurs while writing to the stream.
+ * @throws IllegalArgumentException If the parameters are invalid.
+ */
+ public void setNonStrokingColor(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 DeviceRGB color space. Range is 0..1.
+ *
+ * @param c The cyan value.
+ * @param m The magenta value.
+ * @param y The yellow value.
+ * @param k The black value.
+ * @throws IOException If an IO error occurs while writing to the stream.
+ */
+ public void setNonStrokingColor(double c, double m, double y, double 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((float) c);
+ writeOperand((float) m);
+ writeOperand((float) y);
+ writeOperand((float) k);
+ writeOperator("k");
+ 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.
+ */
+ 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(double g) throws IOException
+ {
+ if (isOutsideOneInterval(g))
+ {
+ throw new IllegalArgumentException("Parameter must be within 0..1, but is " + g);
+ }
+ writeOperand((float) g);
+ writeOperator("g");
+ 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("re");
+ }
+
+ /**
+ * 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("c");
+ }
+
+ /**
+ * Append a cubic Bézier curve to the current path. The curve extends from the current point to
+ * the point (x3, y3), using the current point and (x2, y2) as the Bézier control points.
+ *
+ * @param x2 x coordinate of the point 2
+ * @param y2 y coordinate of the point 2
+ * @param x3 x coordinate of the point 3
+ * @param y3 y coordinate of the point 3
+ * @throws IllegalStateException If the method was called within a text block.
+ * @throws IOException If the content stream could not be written.
+ */
+ public void curveTo2(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("v");
+ }
+
+ /**
+ * Append a cubic Bézier curve to the current path. The curve extends from the current point to
+ * the point (x3, y3), using (x1, y1) and (x3, y3) as the Bézier control points.
+ *
+ * @param x1 x coordinate of the point 1
+ * @param y1 y coordinate of the point 1
+ * @param x3 x coordinate of the point 3
+ * @param y3 y coordinate of the point 3
+ * @throws IOException If the content stream could not be written.
+ * @throws IllegalStateException If the method was called within a text block.
+ */
+ public void curveTo1(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("y");
+ }
+
+ /**
+ * Move the current position to the given coordinates.
+ *
+ * @param x The x coordinate.
+ * @param y The y coordinate.
+ * @throws IOException If the content stream could not be written.
+ * @throws IllegalStateException If the method was called within a text block.
+ */
+ public void moveTo(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("m");
+ }
+
+ /**
+ * Draw a line from the current position to the given coordinates.
+ *
+ * @param x The x coordinate.
+ * @param y The y coordinate.
+ * @throws IOException If the content stream could not be written.
+ * @throws IllegalStateException If the method was called within a text block.
+ */
+ public void lineTo(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("l");
+ }
+
+ /**
+ * Stroke the path.
+ *
+ * @throws IOException If the content stream could not be written
+ * @throws IllegalStateException If the method was called within a text block.
+ */
+ public void stroke() throws IOException
+ {
+ if (inTextMode)
+ {
+ throw new IllegalStateException("Error: stroke is not allowed within a text block.");
+ }
+ writeOperator("S");
+ }
+
+ /**
+ * Close and stroke the path.
+ *
+ * @throws IOException If the content stream could not be written
+ * @throws IllegalStateException If the method was called within a text block.
+ */
+ public void closeAndStroke() throws IOException
+ {
+ if (inTextMode)
+ {
+ throw new IllegalStateException("Error: closeAndStroke is not allowed within a text block.");
+ }
+ writeOperator("s");
+ }
+
+ /**
+ * 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("f");
+ }
+
+ /**
+ * Fills the path using the even-odd winding rule.
+ *
+ * @throws IOException If the content stream could not be written
+ * @throws IllegalStateException If the method was called within a text block.
+ */
+ public void fillEvenOdd() throws IOException
+ {
+ if (inTextMode)
+ {
+ throw new IllegalStateException("Error: fillEvenOdd is not allowed within a text block.");
+ }
+ writeOperator("f*");
+ }
+
+ /**
+ * 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("B");
+ }
+
+ /**
+ * 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("B*");
+ }
+
+ /**
+ * 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("b");
+ }
+
+ /**
+ * 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("b*");
+ }
+
+ /**
+ * 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("sh");
+ }
+
+ /**
+ * 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("h");
+ }
+
+ /**
+ * Intersects the current clipping path with the current path, using the nonzero rule.
+ *
+ * @throws IOException If the content stream could not be written
+ * @throws IllegalStateException If the method was called within a text block.
+ */
+ public void clip() throws IOException
+ {
+ if (inTextMode)
+ {
+ throw new IllegalStateException("Error: clip is not allowed within a text block.");
+ }
+ writeOperator("W");
+
+ // end path without filling or stroking
+ writeOperator("n");
+ }
+
+ /**
+ * Intersects the current clipping path with the current path, using the even-odd rule.
+ *
+ * @throws IOException If the content stream could not be written
+ * @throws IllegalStateException If the method was called within a text block.
+ */
+ public void clipEvenOdd() throws IOException
+ {
+ if (inTextMode)
+ {
+ throw new IllegalStateException("Error: clipEvenOdd is not allowed within a text block.");
+ }
+ writeOperator("W*");
+
+ // end path without filling or stroking
+ writeOperator("n");
+ }
+
+ /**
+ * Set line width to the given value.
+ *
+ * @param lineWidth The width which is used for drwaing.
+ * @throws IOException If the content stream could not be written
+ * @throws IllegalStateException If the method was called within a text block.
+ */
+ public void setLineWidth(float lineWidth) throws IOException
+ {
+ if (inTextMode)
+ {
+ throw new IllegalStateException("Error: setLineWidth is not allowed within a text block.");
+ }
+ writeOperand(lineWidth);
+ writeOperator("w");
+ }
+
+ /**
+ * Set the line join style.
+ *
+ * @param lineJoinStyle 0 for miter join, 1 for round join, and 2 for bevel join.
+ * @throws IOException If the content stream could not be written.
+ * @throws IllegalStateException If the method was called within a text block.
+ * @throws IllegalArgumentException If the parameter is not a valid line join style.
+ */
+ public void setLineJoinStyle(int lineJoinStyle) throws IOException
+ {
+ if (inTextMode)
+ {
+ throw new IllegalStateException("Error: setLineJoinStyle is not allowed within a text block.");
+ }
+ if (lineJoinStyle >= 0 && lineJoinStyle <= 2)
+ {
+ writeOperand(lineJoinStyle);
+ writeOperator("j");
+ }
+ else
+ {
+ throw new IllegalArgumentException("Error: unknown value for line join style");
+ }
+ }
+
+ /**
+ * Set the line cap style.
+ *
+ * @param lineCapStyle 0 for butt cap, 1 for round cap, and 2 for projecting square cap.
+ * @throws IOException If the content stream could not be written.
+ * @throws IllegalStateException If the method was called within a text block.
+ * @throws IllegalArgumentException If the parameter is not a valid line cap style.
+ */
+ public void setLineCapStyle(int lineCapStyle) throws IOException
+ {
+ if (inTextMode)
+ {
+ throw new IllegalStateException("Error: setLineCapStyle is not allowed within a text block.");
+ }
+ if (lineCapStyle >= 0 && lineCapStyle <= 2)
+ {
+ writeOperand(lineCapStyle);
+ writeOperator("J");
+ }
+ else
+ {
+ throw new IllegalArgumentException("Error: unknown value for line cap style");
+ }
+ }
+
+ /**
+ * Set the line dash pattern.
+ *
+ * @param pattern The pattern array
+ * @param phase The phase of the pattern
+ * @throws IOException If the content stream could not be written.
+ * @throws IllegalStateException If the method was called within a text block.
+ */
+ public void setLineDashPattern(float[] pattern, float phase) throws IOException
+ {
+ if (inTextMode)
+ {
+ throw new IllegalStateException("Error: setLineDashPattern is not allowed within a text block.");
+ }
+ write("[");
+ for (float value : pattern)
+ {
+ writeOperand(value);
+ }
+ write("] ");
+ writeOperand(phase);
+ writeOperator("d");
+ }
+
+ /**
+ * 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("BMC");
+ }
+
+ /**
+ * 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);
+ writeOperand(resources.add(propertyList));
+ writeOperator("BDC");
+ }
+
+ /**
+ * End a marked content sequence.
+ *
+ * @throws IOException If the content stream could not be written
+ */
+ public void endMarkedContent() throws IOException
+ {
+ writeOperator("EMC");
+ }
+
+ /**
+ * 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("gs");
+ }
+
+ /**
+ * 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");
+ }
+ output.write('%');
+ output.write(comment.getBytes(Charsets.US_ASCII));
+ output.write('\n');
+ }
+
+ /**
+ * Writes a real real to the content stream.
+ */
+ protected void writeOperand(float real) throws IOException
+ {
+ write(formatDecimal.format(real));
+ output.write(' ');
+ }
+
+ /**
+ * Writes a real number to the content stream.
+ */
+ protected void writeOperand(int integer) throws IOException
+ {
+ write(formatDecimal.format(integer));
+ output.write(' ');
+ }
+
+ /**
+ * Writes a COSName to the content stream.
+ */
+ protected void writeOperand(COSName name) throws IOException
+ {
+ name.writePDF(output);
+ output.write(' ');
+ }
+
+ /**
+ * Writes a string to the content stream as ASCII.
+ */
+ protected void writeOperator(String text) throws IOException
+ {
+ output.write(text.getBytes(Charsets.US_ASCII));
+ output.write('\n');
+ }
+
+ /**
+ * Writes a string to the content stream as ASCII.
+ */
+ protected void write(String text) throws IOException
+ {
+ output.write(text.getBytes(Charsets.US_ASCII));
+ }
+
+ /**
+ * Writes a string to the content stream as ASCII.
+ */
+ protected void writeLine() throws IOException
+ {
+ output.write('\n');
+ }
+
+ /**
+ * Writes binary data to the content stream.
+ */
+ protected void writeBytes(byte[] data) throws IOException
+ {
+ output.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
+ {
+ output.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.setElementAt(colorSpace, strokingColorSpaceStack.size() - 1);
+ }
+ }
+
+ protected void setNonStrokingColorSpaceStack(PDColorSpace colorSpace)
+ {
+ if (nonStrokingColorSpaceStack.isEmpty())
+ {
+ nonStrokingColorSpaceStack.add(colorSpace);
+ }
+ else
+ {
+ nonStrokingColorSpaceStack.setElementAt(colorSpace, nonStrokingColorSpaceStack.size() - 1);
+ }
+ }
+}
Propchange: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/contentstream/PDAbstractContentStream.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/contentstream/PDAbstractContentStream.java
------------------------------------------------------------------------------
svn:mime-type = text/plain