Author: msahyoun Date: Sun May 22 06:48:34 2016 New Revision: 1745013 URL: http://svn.apache.org/viewvc?rev=1745013&view=rev Log: PDFBOX-3353: initial support to generate a default appearance stream for annotations
Added: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceContentStream.java (with props) pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDAppearanceHandler.java (with props) pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDSquareAppearanceHandler.java (with props) pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDSquareAnnotationTest.java (with props) Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationSquareCircle.java Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationSquareCircle.java URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationSquareCircle.java?rev=1745013&r1=1745012&r2=1745013&view=diff ============================================================================== --- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationSquareCircle.java (original) +++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationSquareCircle.java Sun May 22 06:48:34 2016 @@ -22,15 +22,18 @@ import org.apache.pdfbox.cos.COSDictiona import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.graphics.color.PDColor; +import org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDAppearanceHandler; +import org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDSquareAppearanceHandler; /** - * This is the class that represents a rectangular or eliptical annotation Introduced in PDF 1.3 specification . + * This is the class that represents a rectangular or elliptical annotation introduced in PDF 1.3 specification . * * @author Paul King */ public class PDAnnotationSquareCircle extends PDAnnotationMarkup { + private PDAppearanceHandler squareAppearanceHandler; /** * Constant for a Rectangular type of annotation. */ @@ -62,6 +65,36 @@ public class PDAnnotationSquareCircle ex } /** + * Set a custom appearance handler for generating the annotations appearance streams. + * + * @param squareAppearanceHandler + */ + public void setCustomSquareAppearanceHandler(PDAppearanceHandler squareAppearanceHandler) + { + this.squareAppearanceHandler = squareAppearanceHandler; + } + + public void constructAppearances() + { + if (getSubtype() == SUB_TYPE_SQUARE) + { + if (squareAppearanceHandler == null) + { + PDSquareAppearanceHandler appearanceHandler = new PDSquareAppearanceHandler(this); + appearanceHandler.generateAppearanceStreams(); + } + else + { + squareAppearanceHandler.generateAppearanceStreams(); + } + } + else if (getSubtype() == SUB_TYPE_SQUARE) + { + + } + } + + /** * This will set interior color of the drawn area color is in DeviceRGB colo rspace. * * @param ic color in the DeviceRGB color space. Added: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceContentStream.java URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceContentStream.java?rev=1745013&view=auto ============================================================================== --- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceContentStream.java (added) +++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceContentStream.java Sun May 22 06:48:34 2016 @@ -0,0 +1,1434 @@ +/* + * 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.interactive.annotation; + +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 page content stream. + * + * @author Ben Litchfield + */ +public final class PDAppearanceContentStream 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. + * + * @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 PDAppearanceContentStream(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 PDAppearanceContentStream(PDAppearanceStream appearance, OutputStream outputStream) + throws IOException + { + output = outputStream; + this.resources = appearance.getResources(); + + formatDecimal.setMaximumFractionDigits(4); + formatDecimal.setGroupingUsed(false); + } + + /** + * Begin some text operations. + * + * @throws IOException If there is an error writing to the stream or if you attempt to + * nest beginText calls. + * @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"); + } + + private 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. + */ + private void writeOperand(float real) throws IOException + { + write(formatDecimal.format(real)); + output.write(' '); + } + + /** + * Writes a real number to the content stream. + */ + private void writeOperand(int integer) throws IOException + { + write(formatDecimal.format(integer)); + output.write(' '); + } + + /** + * Writes a COSName to the content stream. + */ + private void writeOperand(COSName name) throws IOException + { + name.writePDF(output); + output.write(' '); + } + + /** + * Writes a string to the content stream as ASCII. + */ + private 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. + */ + private void write(String text) throws IOException + { + output.write(text.getBytes(Charsets.US_ASCII)); + } + + /** + * Writes a string to the content stream as ASCII. + */ + private void writeLine() throws IOException + { + output.write('\n'); + } + + /** + * Writes binary data to the content stream. + */ + private 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(); + } + + private boolean isOutside255Interval(int val) + { + return val < 0 || val > 255; + } + + private boolean isOutsideOneInterval(double val) + { + return val < 0 || val > 1; + } + + private void setStrokingColorSpaceStack(PDColorSpace colorSpace) + { + if (strokingColorSpaceStack.isEmpty()) + { + strokingColorSpaceStack.add(colorSpace); + } + else + { + strokingColorSpaceStack.setElementAt(colorSpace, strokingColorSpaceStack.size() - 1); + } + } + + private 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/pdmodel/interactive/annotation/PDAppearanceContentStream.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceContentStream.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDAppearanceHandler.java URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDAppearanceHandler.java?rev=1745013&view=auto ============================================================================== --- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDAppearanceHandler.java (added) +++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDAppearanceHandler.java Sun May 22 06:48:34 2016 @@ -0,0 +1,177 @@ +/* + * 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.interactive.annotation.handlers; + +import org.apache.pdfbox.cos.COSStream; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.graphics.color.PDColor; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceEntry; + +/** + * Generic handler to generate the fields appearance. + * + * Individual handler will provide specific implementations for different field + * types. + * + */ +public abstract class PDAppearanceHandler +{ + + private PDAnnotation annotation; + + public PDAppearanceHandler(PDAnnotation annotation) + { + this.annotation = annotation; + + } + + public void generateAppearanceStreams() + { + if (annotation.getRectangle() != null) + { + generateNormalAppearance(); + generateRolloverAppearance(); + generateDownAppearance(); + } + } + + public abstract void generateNormalAppearance(); + + public abstract void generateRolloverAppearance(); + + public abstract void generateDownAppearance(); + + PDAnnotation getAnnotation() + { + return annotation; + } + + /** + * Get the line with of the border. + * + * Get the width of the line used to draw a border around the annotation. + * This may either be specified by the annotation dictionaries Border + * setting or by the W entry in the BS border style dictionary. If both are + * missing the default width is 1. + * + * @return the line width + */ + // TODO: according to the PDF spec the use of the BS entry is annotation + // specific + // so we will leave that to be implemented by individual handlers. + // If at the end all annotations support the BS entry this can be handled + // here and removed from the individual handlers. + abstract float getLineWidth(); + + PDColor getColor() + { + return annotation.getColor(); + } + + PDRectangle getRectangle() + { + return annotation.getRectangle(); + } + + /** + * Get the annotations appearance dictionary. + * + * <p>This will get the annotations appearance dictionary. If this is + * not existent an empty appearance dictionary will be created. + * + * @return the annotations appearance dictionary + */ + PDAppearanceDictionary getAppearance() + { + PDAppearanceDictionary appearanceDictionary = annotation.getAppearance(); + if (appearanceDictionary == null) + { + appearanceDictionary = new PDAppearanceDictionary(); + annotation.setAppearance(appearanceDictionary); + } + return appearanceDictionary; + } + + /** + * Get the annotations normal appearance. + * + * <p>This will get the annotations normal appearance. If this is + * not existent an empty appearance entry will be created. + * + * @return the appearance entry representing the normal appearance. + */ + PDAppearanceEntry getNormalAppearance() + { + PDAppearanceDictionary appearanceDictionary = getAppearance(); + PDAppearanceEntry appearanceEntry = appearanceDictionary.getNormalAppearance(); + + if (appearanceEntry.isSubDictionary()) + { + appearanceEntry = new PDAppearanceEntry(new COSStream()); + appearanceDictionary.setNormalAppearance(appearanceEntry); + } + + return appearanceEntry; + } + + /** + * Get the annotations down appearance. + * + * <p>This will get the annotations down appearance. If this is + * not existent an empty appearance entry will be created. + * + * @return the appearance entry representing the down appearance. + */ + PDAppearanceEntry getDownAppearance() + { + PDAppearanceDictionary appearanceDictionary = getAppearance(); + PDAppearanceEntry appearanceEntry = appearanceDictionary.getDownAppearance(); + + if (appearanceEntry.isSubDictionary()) + { + appearanceEntry = new PDAppearanceEntry(new COSStream()); + appearanceDictionary.setDownAppearance(appearanceEntry); + } + + return appearanceEntry; + } + + /** + * Get the annotations rollover appearance. + * + * <p>This will get the annotations rollover appearance. If this is + * not existent an empty appearance entry will be created. + * + * @return the appearance entry representing the rollover appearance. + */ + PDAppearanceEntry getRolloverAppearance() + { + PDAppearanceDictionary appearanceDictionary = getAppearance(); + PDAppearanceEntry appearanceEntry = appearanceDictionary.getDownAppearance(); + + if (appearanceEntry.isSubDictionary()) + { + appearanceEntry = new PDAppearanceEntry(new COSStream()); + appearanceDictionary.setRolloverAppearance(appearanceEntry); + } + + return appearanceEntry; + } +} Propchange: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDAppearanceHandler.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDAppearanceHandler.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDSquareAppearanceHandler.java URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDSquareAppearanceHandler.java?rev=1745013&view=auto ============================================================================== --- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDSquareAppearanceHandler.java (added) +++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDSquareAppearanceHandler.java Sun May 22 06:48:34 2016 @@ -0,0 +1,114 @@ +/* + * 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.interactive.annotation.handlers; + + +import java.awt.geom.AffineTransform; +import java.io.IOException; + +import org.apache.pdfbox.cos.COSArray; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationSquareCircle; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceContentStream; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceEntry; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary; + +/** + * Handler to generate the square annotations appearance. + * + */ +public class PDSquareAppearanceHandler extends PDAppearanceHandler +{ + public PDSquareAppearanceHandler(PDAnnotation annotation) + { + super(annotation); + } + + @Override + public void generateNormalAppearance() + { + PDAppearanceEntry appearanceEntry = getNormalAppearance(); + PDAppearanceStream appearanceStream = appearanceEntry.getAppearanceStream(); + try + { + PDAppearanceContentStream contentStream = new PDAppearanceContentStream(appearanceStream); + PDRectangle bbox = getRectangle(); + appearanceStream.setBBox(bbox); + AffineTransform transform = AffineTransform.getTranslateInstance(-bbox.getLowerLeftX(), -bbox.getLowerLeftY()); + appearanceStream.setMatrix(transform); + contentStream.setStrokingColor(getColor().getComponents()); + contentStream.addRect(bbox.getLowerLeftX()+1f, bbox.getLowerLeftY()+1f, bbox.getWidth()-2f, bbox.getHeight()-2f); + + contentStream.stroke(); + contentStream.close(); + } catch (IOException e) + { + e.printStackTrace(); + } + } + + @Override + public void generateRolloverAppearance() + { + // TODO to be implemented + } + + @Override + public void generateDownAppearance() + { + // TODO to be implemented + } + + /** + * Get the line with of the border. + * + * Get the width of the line used to draw a border around + * the annotation. This may either be specified by the annotation + * dictionaries Border setting or by the W entry in the BS border + * style dictionary. If both are missing the default width is 1. + * + * @return the line width + */ + // TODO: according to the PDF spec the use of the BS entry is annotation specific + // so we will leave that to be implemented by individual handlers. + // If at the end all annotations support the BS entry this can be handled + // here and removed from the individual handlers. + public float getLineWidth() + { + PDAnnotationSquareCircle annotation = (PDAnnotationSquareCircle) getAnnotation(); + + PDBorderStyleDictionary bs = annotation.getBorderStyle(); + + if (bs != null) + { + return bs.getWidth(); + } + else + { + COSArray borderCharacteristics = annotation.getBorder(); + if (borderCharacteristics != null && borderCharacteristics.size() >= 3) + { + return borderCharacteristics.getInt(3); + } + } + + return 1; + } +} Propchange: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDSquareAppearanceHandler.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDSquareAppearanceHandler.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDSquareAnnotationTest.java URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDSquareAnnotationTest.java?rev=1745013&view=auto ============================================================================== --- pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDSquareAnnotationTest.java (added) +++ pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDSquareAnnotationTest.java Sun May 22 06:48:34 2016 @@ -0,0 +1,172 @@ +/* + * 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.interactive.annotation; + +import org.apache.pdfbox.contentstream.operator.Operator; +import org.apache.pdfbox.cos.COSFloat; +import org.apache.pdfbox.cos.COSInteger; +import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.pdfparser.PDFStreamParser; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.common.PDStream; +import org.apache.pdfbox.pdmodel.graphics.color.PDColor; +import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB; +import org.apache.pdfbox.util.Matrix; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +/** + * Test for the PDAnnotation classes. + * + */ +public class PDSquareAnnotationTest +{ + + // delta for comparing equality of float values + private static final double DELTA = 1e-4; + + // the location of the annotation + static PDRectangle rectangle; + + private static final File OUT_DIR = new File("target/test-output"); + private static final String NAME_OF_PDF = "PDSquareAnnotationTest.pdf"; + + @Before + public void setUp() throws IOException + { + rectangle = new PDRectangle(); + rectangle.setLowerLeftX(91.5958f); + rectangle.setLowerLeftY(741.91f); + rectangle.setUpperRightX(113.849f); + rectangle.setUpperRightY(757.078f); + } + + @Test + public void createDefaultSquareAnnotation() + { + PDAnnotation annotation = new PDAnnotationSquareCircle(PDAnnotationSquareCircle.SUB_TYPE_SQUARE); + assertEquals(COSName.ANNOT, annotation.getCOSObject().getItem(COSName.TYPE)); + assertEquals(PDAnnotationSquareCircle.SUB_TYPE_SQUARE, + annotation.getCOSObject().getNameAsString(COSName.SUBTYPE)); + } + + @Test + public void createWithAppearance() throws IOException + { + // the width of the <nnotations border + final int borderWidth = 1; + + PDDocument document = new PDDocument(); + PDPage page = new PDPage(); + document.addPage(page); + List<PDAnnotation> annotations = page.getAnnotations(); + + PDAnnotationSquareCircle annotation = new PDAnnotationSquareCircle(PDAnnotationSquareCircle.SUB_TYPE_SQUARE); + + PDBorderStyleDictionary borderThin = new PDBorderStyleDictionary(); + borderThin.setWidth(borderWidth); + + PDColor red = new PDColor(new float[] { 1, 0, 0 }, PDDeviceRGB.INSTANCE); + annotation.setContents("Square Annotation"); + annotation.setColor(red); + annotation.setBorderStyle(borderThin); + + + annotation.setRectangle(rectangle); + + annotation.constructAppearances(); + annotations.add(annotation); + + // store for validation test + File file = new File(OUT_DIR, NAME_OF_PDF); + document.save(file); + document.close(); + } + + @Test + public void validateAppearance() throws IOException + { + // the width of the <nnotations border + final int borderWidth = 1; + + File file = new File(OUT_DIR, NAME_OF_PDF); + PDDocument document = PDDocument.load(file); + PDPage page = document.getPage(0); + List<PDAnnotation> annotations = page.getAnnotations(); + + PDAnnotationSquareCircle annotation = (PDAnnotationSquareCircle) annotations.get(0); + + PDRectangle rectangle = new PDRectangle(); + rectangle.setLowerLeftX(91.5958f); + rectangle.setLowerLeftY(741.91f); + rectangle.setUpperRightX(113.849f); + rectangle.setUpperRightY(757.078f); + + // test the correct setting of the appearance stream + assertNotNull("Appearance dictionary shall not be null", annotation.getAppearance()); + assertNotNull("Normal appearance shall not be null", annotation.getAppearance().getNormalAppearance()); + PDAppearanceStream appearanceStream = annotation.getAppearance().getNormalAppearance().getAppearanceStream(); + assertNotNull("Appearance stream shall not be null", appearanceStream); + assertEquals(rectangle.getLowerLeftX(), appearanceStream.getBBox().getLowerLeftX(), DELTA); + assertEquals(rectangle.getLowerLeftY(), appearanceStream.getBBox().getLowerLeftY(), DELTA); + assertEquals(rectangle.getWidth(), appearanceStream.getBBox().getWidth(), DELTA); + assertEquals(rectangle.getHeight(), appearanceStream.getBBox().getHeight(), DELTA); + + Matrix matrix = appearanceStream.getMatrix(); + assertNotNull("Matrix shall not be null", matrix); + + // should have been translated to a 0 origin + assertEquals(-rectangle.getLowerLeftX(), matrix.getTranslateX(), DELTA); + assertEquals(-rectangle.getLowerLeftY(), matrix.getTranslateY(), DELTA); + + // test the content of the apperance stream + PDStream contentStream = appearanceStream.getContentStream(); + assertNotNull("Content stream shall not be null", contentStream); + PDFStreamParser parser = new PDFStreamParser(appearanceStream); + parser.parse(); + List<Object> tokens = parser.getTokens(); + + // the samples content stream should contain 10 tokens + assertEquals(10, tokens.size()); + + // setting of the stroking color + assertEquals(1, ((COSInteger) tokens.get(0)).intValue()); + assertEquals(0, ((COSInteger) tokens.get(1)).intValue()); + assertEquals(0, ((COSInteger) tokens.get(2)).intValue()); + assertEquals("RG", ((Operator) tokens.get(3)).getName()); + + // setting of the rectangle for the border + // it shall be inset by the border width + assertEquals(rectangle.getLowerLeftX() + borderWidth, ((COSFloat) tokens.get(4)).floatValue(), DELTA); + assertEquals(rectangle.getLowerLeftY() + borderWidth, ((COSFloat) tokens.get(5)).floatValue(), DELTA); + assertEquals(rectangle.getWidth() - 2 * borderWidth, ((COSFloat) tokens.get(6)).floatValue(), DELTA); + assertEquals(rectangle.getHeight() - 2 * borderWidth, ((COSFloat) tokens.get(7)).floatValue(), DELTA); + assertEquals("re", ((Operator) tokens.get(8)).getName()); + assertEquals("S", ((Operator) tokens.get(9)).getName()); + + document.close(); + } +} Propchange: pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDSquareAnnotationTest.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDSquareAnnotationTest.java ------------------------------------------------------------------------------ svn:mime-type = text/plain