/* * Copyright 2006-2016 ICEsoft Technologies Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package org.icepdf.core.pobjects.annotations; import org.icepdf.core.pobjects.*; import org.icepdf.core.pobjects.graphics.Shapes; import org.icepdf.core.pobjects.graphics.commands.*; import org.icepdf.core.util.Library; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.logging.Logger; /** * Circle annotations (PDF 1.3) shall display, respectively, a * rectangle or an ellipse on the page. When opened, they shall display a * pop-up window containing the text of the associated note. The rectangle or * ellipse shall be inscribed within the annotation rectangle defined by the * annotation dictionary’s Rect entry (see Table 168). * <br> * Figure 63 shows two annotations, each with a border width of 18 points. Despite * the names square and circle, the width and height of the annotation rectangle * need not be equal. Table 177 shows the annotation dictionary entries specific * to these types of annotations. * * @since 5.0 */ public class CircleAnnotation extends MarkupAnnotation { private static final Logger logger = Logger.getLogger(CircleAnnotation.class.toString()); /** * (Optional; PDF 1.4) An array of numbers in the range 0.0 to 1.0 specifying * the interior color that shall be used to fill the annotation’s line endings * (see Table 176). The number of array elements shall determine the colour * space in which the colour is defined: * 0 - No colour; transparent * 1 - DeviceGray * 3 - DeviceRGB * 4 - DeviceCMYK */ public static final Name IC_KEY = new Name("IC"); // state properties for generate the content stream and shapes representation. // of the annnotation state. private Color fillColor; private boolean isFillColor; private Rectangle rectangle; public CircleAnnotation(Library l, HashMap h) { super(l, h); } public void init() throws InterruptedException { super.init(); // parse out interior colour, specific to link annotations. fillColor = Color.WHITE; // we default to black but probably should be null java.util.List C = (java.util.List) getObject(IC_KEY); // parse thought rgb colour. if (C != null && C.size() >= 3) { float red = ((Number) C.get(0)).floatValue(); float green = ((Number) C.get(1)).floatValue(); float blue = ((Number) C.get(2)).floatValue(); red = Math.max(0.0f, Math.min(1.0f, red)); green = Math.max(0.0f, Math.min(1.0f, green)); blue = Math.max(0.0f, Math.min(1.0f, blue)); fillColor = new Color(red, green, blue); isFillColor = true; } // try and generate an appearance stream. resetNullAppearanceStream(); } /** * Gets an instance of a CircleAnnotation that has valid Object Reference. * * @param library document library * @param rect bounding rectangle in user space * @return new CircleAnnotation Instance. */ public static CircleAnnotation getInstance(Library library, Rectangle rect) { // state manager StateManager stateManager = library.getStateManager(); // create a new entries to hold the annotation properties HashMap<Name, Object> entries = new HashMap<Name, Object>(); // set default link annotation values. entries.put(Dictionary.TYPE_KEY, Annotation.TYPE_VALUE); entries.put(Dictionary.SUBTYPE_KEY, Annotation.SUBTYPE_CIRCLE); // coordinates if (rect != null) { entries.put(Annotation.RECTANGLE_KEY, PRectangle.getPRectangleVector(rect)); } else { entries.put(Annotation.RECTANGLE_KEY, new Rectangle(10, 10, 50, 100)); } // create the new instance CircleAnnotation circleAnnotation = null; try { circleAnnotation = new CircleAnnotation(library, entries); circleAnnotation.init(); circleAnnotation.setPObjectReference(stateManager.getNewReferencNumber()); circleAnnotation.setNew(true); // set default flags. circleAnnotation.setFlag(Annotation.FLAG_READ_ONLY, false); circleAnnotation.setFlag(Annotation.FLAG_NO_ROTATE, false); circleAnnotation.setFlag(Annotation.FLAG_NO_ZOOM, false); circleAnnotation.setFlag(Annotation.FLAG_PRINT, true); } catch (InterruptedException e) { Thread.currentThread().interrupt(); logger.fine("CircletAnnotation initialization interrupted."); } return circleAnnotation; } /** * Resets the annotations appearance stream. */ public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform) { // grab the current appearance stream as we'll be updating the shapes. Appearance appearance = appearances.get(currentAppearance); AppearanceState appearanceState = appearance.getSelectedAppearanceState(); // clean identity matrix (nothing fancy) and new empty shapes. appearanceState.setMatrix(new AffineTransform()); appearanceState.setShapes(new Shapes()); // grab references so so we can bass them to the update appearance method. AffineTransform matrix = appearanceState.getMatrix(); Shapes shapes = appearanceState.getShapes(); // we paint everythign in annotation space which is relative to the bbox. Rectangle2D bbox = appearanceState.getBbox(); bbox.setRect(0, 0, bbox.getWidth(), bbox.getHeight()); // setup the AP stream. setModifiedDate(PDate.formatDateTime(new Date())); // refresh /rect entry to match bbox of the appearance stream. rectangle = getUserSpaceRectangle().getBounds(); // check the stroke width if (borderStyle.getStrokeWidth() == 0) { borderStyle.setStrokeWidth(1); } BasicStroke stroke = getBorderStyleStroke(); int strokeWidth = (int) stroke.getLineWidth(); // setup the space for the AP content stream. Rectangle rectangleToDraw = new Rectangle( strokeWidth, strokeWidth, (int) userSpaceRectangle.getWidth() - strokeWidth * 2, (int) userSpaceRectangle.getHeight() - strokeWidth * 2); Ellipse2D.Double circle = new Ellipse2D.Double( strokeWidth, strokeWidth, rectangleToDraw.getWidth(), rectangleToDraw.getHeight()); // add external graphics state for transparency support. shapes.add(new GraphicsStateCmd(EXT_GSTATE_NAME)); shapes.add(new AlphaDrawCmd( AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity))); shapes.add(new StrokeDrawCmd(stroke)); shapes.add(new ShapeDrawCmd(circle)); if (isFillColor) { shapes.add(new ColorDrawCmd(fillColor)); shapes.add(new FillDrawCmd()); } if (borderStyle.getStrokeWidth() > 0) { shapes.add(new ColorDrawCmd(color)); shapes.add(new DrawDrawCmd()); } shapes.add(new AlphaDrawCmd( AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f))); // update the appearance stream // create/update the appearance stream of the xObject. Form form = updateAppearanceStream(shapes, bbox, matrix, PostScriptEncoder.generatePostScript(shapes.getShapes())); generateExternalGraphicsState(form, opacity); } public Color getFillColor() { return fillColor; } public void setFillColor(Color fillColor) { this.fillColor = fillColor; float[] compArray = new float[3]; this.fillColor.getColorComponents(compArray); java.util.List<Float> colorValues = new ArrayList<Float>(compArray.length); for (float comp : compArray) { colorValues.add(comp); } entries.put(IC_KEY, colorValues); } public Rectangle getRectangle() { return rectangle; } public void setRectangle(Rectangle rectangle) { this.rectangle = rectangle; } public boolean isFillColor() { return isFillColor; } public void setFillColor(boolean fillColor) { isFillColor = fillColor; if (!isFillColor) { entries.remove(IC_KEY); } } }