/* * Copyright 2006-2017 ICEsoft Technologies Canada Corp. * * 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.GeneralPath; import java.awt.geom.PathIterator; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.logging.Logger; /** * An ink annotation (PDF 1.3) represents a freehand "scribble" composed of one * or more disjoint paths. When opened, it shall display a pop-up window * containing the text of the associated note. Table 182 shows the annotation * dictionary entries specific to this type of annotation. * * @since 5.0 */ public class InkAnnotation extends MarkupAnnotation { private static final Logger logger = Logger.getLogger(InkAnnotation.class.toString()); /** * (Required) An array of n arrays, each representing a stroked path. Each * array shall be a series of alternating horizontal and vertical coordinates * in default user space, specifying points along the path. When drawn, the * points shall be connected by straight lines or curves in an * implementation-dependent way */ public static final Name INK_LIST_KEY = new Name("InkList"); protected Shape inkPath; public InkAnnotation(Library l, HashMap h) { super(l, h); } @SuppressWarnings("unchecked") public void init() throws InterruptedException{ super.init(); // look for an ink list List<List<Number>> inkLists = library.getArray(entries, INK_LIST_KEY); GeneralPath inkPaths = new GeneralPath(); if (inkLists != null) { inkPath = new GeneralPath(); for (List<Number> inkList : inkLists) { GeneralPath inkPath = null; for (int i = 0, max = inkList.size() - 1; i < max; i += 2) { if (inkPath == null) { inkPath = new GeneralPath(); inkPath.moveTo(inkList.get(i).floatValue(), inkList.get(i + 1).floatValue()); } else { inkPath.lineTo(inkList.get(i).floatValue(), inkList.get(i + 1).floatValue()); } } inkPaths.append(inkPath, false); } } this.inkPath = inkPaths; if (!hasAppearanceStream() && inkPath != null) { Object tmp = getObject(RECTANGLE_KEY); Rectangle2D.Float rectangle = null; if (tmp instanceof List) { rectangle = library.getRectangle(entries, RECTANGLE_KEY); } if (rectangle != null) { setBBox(rectangle.getBounds()); } resetAppearanceStream(new AffineTransform()); } // try and generate an appearance stream. resetNullAppearanceStream(); } /** * Converts the ink path back to an array of points. * * @param inkPath path to translate to an array * @return an array of an array of points. */ private List<List<Float>> convertPathToArray(Shape inkPath) { List<List<Float>> inkLists = new ArrayList<List<Float>>(); List<Float> segment = null; if (inkPath != null) { PathIterator pathIterator = inkPath.getPathIterator(null); float[] inkSegment = new float[6]; int segmentType; while (!pathIterator.isDone()) { segmentType = pathIterator.currentSegment(inkSegment); if (segmentType == PathIterator.SEG_MOVETO) { segment = new ArrayList<Float>(); segment.add(inkSegment[0]); segment.add(inkSegment[1]); inkLists.add(segment); } else if (segmentType == PathIterator.SEG_LINETO) { segment.add(inkSegment[0]); segment.add(inkSegment[1]); } pathIterator.next(); } } return inkLists; } /** * Gets an instance of a InkAnnotation that has valid Object Reference. * * @param library document library * @param rect bounding rectangle in user space * @return new InkAnnotation Instance. */ public static InkAnnotation 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_INK); // 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 InkAnnotation inkAnnotation = null; try { inkAnnotation = new InkAnnotation(library, entries); inkAnnotation.setPObjectReference(stateManager.getNewReferencNumber()); inkAnnotation.init(); inkAnnotation.setNew(true); // set default flags. inkAnnotation.setFlag(Annotation.FLAG_READ_ONLY, false); inkAnnotation.setFlag(Annotation.FLAG_NO_ROTATE, false); inkAnnotation.setFlag(Annotation.FLAG_NO_ZOOM, false); inkAnnotation.setFlag(Annotation.FLAG_PRINT, true); } catch (InterruptedException e) { Thread.currentThread().interrupt(); logger.fine("Ink annotation instance creation was interrupted"); } return inkAnnotation; } /** * Resets the annotations appearance stream. */ public void resetAppearanceStream(double dx, double dy, AffineTransform pageSpace) { // setup clean shapes Appearance appearance = appearances.get(currentAppearance); AppearanceState appearanceState = appearance.getSelectedAppearanceState(); appearanceState.setMatrix(new AffineTransform()); appearanceState.setShapes(new Shapes()); // update the circle for any dx/dy moves. AffineTransform af = new AffineTransform(); af.setToTranslation(dx * pageSpace.getScaleX(), -dy * pageSpace.getScaleY()); inkPath = af.createTransformedShape(inkPath); entries.put(INK_LIST_KEY, convertPathToArray(inkPath)); Rectangle2D bbox = appearanceState.getBbox(); bbox.setRect(userSpaceRectangle.x, userSpaceRectangle.y, userSpaceRectangle.width, userSpaceRectangle.height); setUserSpaceRectangle(userSpaceRectangle); // setup the AP stream. setModifiedDate(PDate.formatDateTime(new Date())); // save the stroke. if (borderStyle.getStrokeWidth() == 0) { borderStyle.setStrokeWidth(1); } Stroke stroke = getBorderStyleStroke(); Shapes shapes = appearanceState.getShapes(); // setup the space for the AP content stream. 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 ColorDrawCmd(color)); shapes.add(new ShapeDrawCmd(inkPath)); shapes.add(new DrawDrawCmd()); shapes.add(new AlphaDrawCmd( AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f))); // remove appearance stream if it exists on an existing edit. entries.remove(APPEARANCE_STREAM_KEY); // we don't write out an appearance stream for ink annotation, we just regenerate it from properties // mark the change. StateManager stateManager = library.getStateManager(); stateManager.addChange(new PObject(this, this.getPObjectReference())); } public Shape getInkPath() { return inkPath; } public void setInkPath(Shape inkPath) { this.inkPath = inkPath; } }