diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/Page.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/Page.java index 17b04fca1..35d1c534f 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/Page.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/Page.java @@ -812,10 +812,12 @@ public Shape getPageShape(int boundary, float userRotation, float userZoom) { * the method @link{#createAnnotation} for creating new annotations. * * @param newAnnotation annotation object to add + * @param isNew annotation is new and should be added to stateManager, otherwise change will be part of the document + * but not yet added to the stateManager as the change was likely a missing content stream or popup. * @return reference to annotation that was added. */ @SuppressWarnings("unchecked") - public Annotation addAnnotation(Annotation newAnnotation) { + public Annotation addAnnotation(Annotation newAnnotation, boolean isNew) { // make sure the page annotations have been initialized. if (annotations == null) { @@ -839,20 +841,18 @@ public Annotation addAnnotation(Annotation newAnnotation) { // update annots dictionary with new annotations reference, annotations.add(newAnnotation.getPObjectReference()); // add the page as state change - stateManager.addChange( - new PObject(this, this.getPObjectReference())); + stateManager.addChange(new PObject(this, this.getPObjectReference()), isNew); } else if (isAnnotAReference && annotations != null) { // get annots array from page // update annots dictionary with new annotations reference, annotations.add(newAnnotation.getPObjectReference()); // add the annotations reference dictionary as state has changed stateManager.addChange( - new PObject(annotations, library.getObjectReference( - entries, ANNOTS_KEY))); + new PObject(annotations, library.getObjectReference(entries, ANNOTS_KEY)), isNew); } // we need to add the a new annots reference else { - List annotsVector = new ArrayList(4); + List annotsVector = new ArrayList<>(4); annotsVector.add(newAnnotation.getPObjectReference()); // create a new Dictionary of annotations using an external reference @@ -866,8 +866,8 @@ public Annotation addAnnotation(Annotation newAnnotation) { // add the page and the new dictionary to the state change stateManager.addChange( - new PObject(this, this.getPObjectReference())); - stateManager.addChange(annotsPObject); + new PObject(this, this.getPObjectReference()), isNew); + stateManager.addChange(annotsPObject, isNew); this.annotations = new ArrayList<>(); } @@ -883,7 +883,7 @@ public Annotation addAnnotation(Annotation newAnnotation) { library.addObject(newAnnotation, newAnnotation.getPObjectReference()); // finally add the new annotations to the state manager - stateManager.addChange(new PObject(newAnnotation, newAnnotation.getPObjectReference())); + stateManager.addChange(new PObject(newAnnotation, newAnnotation.getPObjectReference()), isNew); // return to caller for further manipulations. return newAnnotation; diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/StateManager.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/StateManager.java index 765da1925..03c8fdf9a 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/StateManager.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/StateManager.java @@ -35,7 +35,7 @@ public class StateManager { Logger.getLogger(StateManager.class.getName()); // a list is all we might need. - private final HashMap changes; + private final HashMap changes; // access to xref size and next revision number. private final PTrailer trailer; @@ -80,7 +80,18 @@ public Reference getNewReferenceNumber() { * @param pObject object to add to cache. */ public void addChange(PObject pObject) { - changes.put(pObject.getReference(), pObject); + addChange(pObject, true); + } + + /** + * Add a new PObject containing changed data to the cache. + * + * @param pObject object to add to cache. + * @param isNew new indicates a new object that should be saved when isChanged() is called. If false the object + * was added but because the object wasn't present for rendering and was created by the core library. + */ + public void addChange(PObject pObject, boolean isNew) { + changes.put(pObject.getReference(), new Change(pObject, isNew)); int objectNumber = pObject.getReference().getObjectNumber(); // check the reference numbers synchronized (this) { @@ -121,10 +132,25 @@ public void removeChange(PObject pObject) { } /** - * @return If there are any changes + * @return If there are any changes from objects that were manipulated by user interaction + */ + public boolean isChange() { + Collection changeValues = changes.values(); + Change[] changeArray = changeValues.toArray(new Change[0]); + for (Change change : changeArray) { + if (change.isNew) { + return true; + } + } + return false; + } + + /** + * @return If there are any changes that end up in the state manager form user interactions or annotations + * needing to create missing content streams or popups. */ - public boolean isChanged() { - return !changes.isEmpty(); + public boolean isNoChange() { + return changes.isEmpty(); } /** @@ -139,22 +165,11 @@ public int getChangedSize() { /** * @return An Iterator<PObject> for all the changes objects, sorted */ - public Iterator iteratorSortedByObjectNumber() { - Collection coll = changes.values(); -/* - * This code allows me to force an object to be treated as modified, - * so I can debug how we write out that kind of object, before we - * add a ui to actually edit it. -Reference ref = new Reference(10,0); -Object ob = trailer.getLibrary().getObject(ref); -logger.severe("Object 10: " + ob + " ob.class: " + ob.getClass().getName()); -java.util.HashSet hs = new java.util.HashSet(coll); -hs.add(new PObject(ob, ref)); -coll = hs; -*/ - PObject[] arr = coll.toArray(new PObject[coll.size()]); + public Iterator iteratorSortedByObjectNumber() { + Collection coll = changes.values(); + Change[] arr = coll.toArray(new Change[0]); Arrays.sort(arr, new PObjectComparatorByReferenceObjectNumber()); - List sortedList = Arrays.asList(arr); + List sortedList = Arrays.asList(arr); return sortedList.iterator(); } @@ -164,16 +179,16 @@ public PTrailer getTrailer() { private static class PObjectComparatorByReferenceObjectNumber - implements Comparator { - public int compare(PObject a, PObject b) { + implements Comparator { + public int compare(Change a, Change b) { if (a == null && b == null) return 0; else if (a == null) return -1; else if (b == null) return 1; - Reference ar = a.getReference(); - Reference br = b.getReference(); + Reference ar = a.pObject.getReference(); + Reference br = b.pObject.getReference(); if (ar == null && br == null) return 0; else if (ar == null) @@ -189,5 +204,23 @@ else if (aron > bron) return 0; } } + + /** + * Wrapper class of a pObject and how it was created. The newFlag differentiates if the object was created + * by a user action vs the core library creating an object that isn't in the source file but needed for rendering. + */ + public class Change { + protected PObject pObject; + protected boolean isNew; + + public Change(PObject pObject, boolean isNew) { + this.pObject = pObject; + this.isNew = isNew; + } + + public PObject getPObject() { + return pObject; + } + } } diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/AbstractWidgetAnnotation.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/AbstractWidgetAnnotation.java index 5c0bf8796..24791a45f 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/AbstractWidgetAnnotation.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/AbstractWidgetAnnotation.java @@ -1,410 +1,410 @@ -/* - * Copyright 2006-2019 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.Name; -import org.icepdf.core.pobjects.Resources; -import org.icepdf.core.pobjects.acroform.FieldDictionary; -import org.icepdf.core.pobjects.acroform.InteractiveForm; -import org.icepdf.core.util.ColorUtil; -import org.icepdf.core.util.Defs; -import org.icepdf.core.util.Library; - -import java.awt.*; -import java.awt.geom.AffineTransform; -import java.awt.geom.Rectangle2D; -import java.util.HashMap; -import java.util.StringTokenizer; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Abstract base class for Widget annotations types, button, choice and text. - * - * @since 5.1 - */ -public abstract class AbstractWidgetAnnotation extends Annotation { - - /** - * Indicates that the annotation has no highlight effect. - */ - public static final Name HIGHLIGHT_NONE = new Name("N"); - - protected static final Logger logger = - Logger.getLogger(AbstractWidgetAnnotation.class.toString()); - - /** - * Transparency value used to simulate text highlighting. - */ - protected static float highlightAlpha = 0.1f; - - // text selection colour - protected static Color highlightColor; - - private boolean enableHighlightedWidget; - - static { - // sets the background colour of the annotation highlight - try { - String color = Defs.sysProperty( - "org.icepdf.core.views.page.annotation.widget.highlight.color", "#CC00FF"); - int colorValue = ColorUtil.convertColor(color); - highlightColor = - new Color(colorValue >= 0 ? colorValue : - Integer.parseInt("0077FF", 16)); - } catch (NumberFormatException e) { - if (logger.isLoggable(Level.WARNING)) { - logger.warning("Error reading widget highlight colour."); - } - } - - try { - highlightAlpha = (float) Defs.doubleProperty( - "org.icepdf.core.views.page.annotation.widget.highlight.alpha", 0.1f); - } catch (NumberFormatException e) { - if (logger.isLoggable(Level.WARNING)) { - logger.warning("Error reading widget highlight alpha."); - } - } - } - - protected Name highlightMode; - - public AbstractWidgetAnnotation(Library l, HashMap h) { - super(l, h); - Object possibleName = getObject(LinkAnnotation.HIGHLIGHT_MODE_KEY); - if (possibleName instanceof Name) { - Name name = (Name) possibleName; - if (HIGHLIGHT_NONE.equals(name.getName())) { - highlightMode = HIGHLIGHT_NONE; - } else if (LinkAnnotation.HIGHLIGHT_OUTLINE.equals(name.getName())) { - highlightMode = LinkAnnotation.HIGHLIGHT_OUTLINE; - } else if (LinkAnnotation.HIGHLIGHT_PUSH.equals(name.getName())) { - highlightMode = LinkAnnotation.HIGHLIGHT_PUSH; - } - } else { - highlightMode = LinkAnnotation.HIGHLIGHT_INVERT; - } - - } - - @Override - public void init() throws InterruptedException { - super.init(); - // check to make sure the field value matches the content stream. - InteractiveForm interactiveForm = library.getCatalog().getInteractiveForm(); - if (interactiveForm != null && interactiveForm.needAppearances()) { - resetAppearanceStream(new AffineTransform()); - } - // todo check if we have content value but no appearance stream. - } - - public abstract void reset(); - - @Override - public abstract void resetAppearanceStream(double dx, double dy, AffineTransform pageSpace); - - @Override - protected void renderAppearanceStream(Graphics2D g) { - - Appearance appearance = appearances.get(currentAppearance); - if (appearance != null) { - AppearanceState appearanceState = appearance.getSelectedAppearanceState(); - if (appearanceState != null && - appearanceState.getShapes() != null) { - // render the main annotation content - super.renderAppearanceStream(g); - } - } - // check the highlight widgetAnnotation field and if true we draw a light background colour to mark - // the widgets on a page. - if (enableHighlightedWidget && - !(getFieldDictionary() != null && getFieldDictionary().isReadOnly())) { - AffineTransform preHighLightTransform = g.getTransform(); - g.setColor(highlightColor); - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, highlightAlpha)); - g.fill(getBbox() != null ? getBbox() : getRectangle()); - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)); - g.setTransform(preHighLightTransform); - } - } - - private Rectangle2D getRectangle() { - Rectangle2D origRect = getBbox() != null ? getBbox() : getUserSpaceRectangle(); - Rectangle2D.Float jrect = new Rectangle2D.Float(0, 0, - (float) origRect.getWidth(), (float) origRect.getHeight()); - return jrect; - } - - public abstract T getFieldDictionary(); - - /** - * Generally immediately after the BMC there is a rectangle that defines the actual size of the annotation. If - * found we can use this to make many assumptions and regenerate the content stream. - * - * @param markedContent content stream of the marked content. - * @return a rectangle either way, if the q # # # # re isn't found then we use the bbox as a potential bound. - */ - protected Rectangle2D.Float findBoundRectangle(String markedContent) { - int selectionStart = markedContent.indexOf("q") + 1; - int selectionEnd = markedContent.indexOf("re"); - if (selectionStart < selectionEnd && selectionEnd > 0) { - String potentialNumbers = markedContent.substring(selectionStart, selectionEnd); - float[] points = parseRectanglePoints(potentialNumbers); - if (points != null) { - return new Rectangle2D.Float(points[0], points[1], points[2], points[3]); - } - } - // default to the bounding box. - Rectangle2D bbox = getBbox(); - return new Rectangle2D.Float(1, 1, (float) bbox.getWidth(), (float) bbox.getHeight()); - } - - /** - * Finds a rectangle in the marked content. - * - * @param markedContent content to search for a rectangle. - * @return rectangle if found, otherwise bbox is used. - */ - protected Rectangle2D.Float findRectangle(String markedContent) { - int selectionEnd = markedContent.indexOf("re"); - if (selectionEnd >= 0) { - String potentialNumbers = markedContent.substring(0, selectionEnd); - float[] points = parseRectanglePoints(potentialNumbers); - if (points != null) { - return new Rectangle2D.Float(points[0], points[1], points[2], points[3]); - } - // default to the bounding box. - Rectangle2D bbox = getBbox(); - return new Rectangle2D.Float(1, 1, (float) bbox.getWidth(), (float) bbox.getHeight()); - } else { - return null; - } - } - - /** - * Get the line height as specified by Th or the font size. - * - * @param defaultAppearance searchable stream - * @return line height, or 13.87 if no reasonable approximation can be found. - */ - protected double getLineHeight(String defaultAppearance) { - if (defaultAppearance != null && checkAppearance(defaultAppearance)) { - String sub = defaultAppearance.substring(0, defaultAppearance.indexOf("Tf")); - StringTokenizer toker = new StringTokenizer(sub); - while (toker.hasMoreTokens()) { - Object obj = toker.nextElement(); - if (obj instanceof String) { - try { - double tmp = Double.parseDouble((String) obj); - tmp *= 1.15; - if (tmp > 0) { - return tmp; - } - } catch (NumberFormatException e) { - // intentionally blank. - } - } - } - } - return 13.87; - } - - protected double getFontSize(String content) { - // try and find text size - double size = 12; - - if (content != null) { - Pattern pattern = Pattern.compile("\\d+(\\.\\d+)?\\s+Tf"); - Matcher matcher = pattern.matcher(content); - if (matcher.find()) { - String fontDef = content.substring(matcher.start(), matcher.end()); - fontDef = fontDef.split(" ")[0]; - try { - size = Double.parseDouble(fontDef); - } catch (NumberFormatException e) { - // ignore and move on - } - if (size < 2) { - size = 12; - } - } - } - return size; - } - - /** - * Encodes the given cotents string into a valid postscript string that is literal encoded. - * - * @param content current content stream to append literal string to. - * @param contents string to be encoded into '(...)' literal format. - * @return original content stream with contents encoded in the literal string format. - */ - protected StringBuilder encodeLiteralString(StringBuilder content, String contents) { - String[] lines = contents.split("\n|\r|\f"); - for (String line : lines) { - content.append('(').append(line.replaceAll("(?=[()\\\\])", "\\\\") - .replaceAll("ÿ", "")).append(")' "); - } - return content; - } - - /** - * Encodes the given contents string into a valid postscript hex string. - * - * @param content current content stream to append literal string to. - * @param contents string to be encoded into '<...></...>' hex format. - * @return original content stream with contents encoded in the hex string format. - */ - protected StringBuilder encodeHexString(StringBuilder content, String contents) { - String[] lines = contents.split("\n|\r|\f"); - for (String line : lines) { - char[] chars = line.toCharArray(); - StringBuilder hex = new StringBuilder(); - for (char aChar : chars) { - hex.append(Integer.toHexString((int) aChar)); - } - content.append('<').append(hex).append(">' "); - } - return content; - } - - /** - * Utility to try and determine if the appearance is valid. - * - * @param appearance appearance ot test. - * @return true if valid, false otherwise. - */ - protected boolean checkAppearance(String appearance) { - // example of a bad appearance, /TiBo 0 Tf 0 g - // size is zero and the font can't be found. - StringTokenizer toker = new StringTokenizer(appearance); - if (toker.hasMoreTokens()) { - String fontName = toker.nextToken().substring(1); - String fontSize = toker.nextToken(); - Appearance appearance1 = appearances.get(currentAppearance); - AppearanceState appearanceState = appearance1.getSelectedAppearanceState(); - org.icepdf.core.pobjects.fonts.Font font = null; - Resources resources = appearanceState.getResources(); - if (resources != null) { - font = resources.getFont(new Name(fontName)); - } - return !(font == null || library.getInteractiveFormFont(fontName) == null || - fontSize.equals("0")); - } - return false; - } - - - /** - * The selection rectangle if present will help define the line height of the text. If not present we can use - * the default value 13.87 later which seems to be very common in the samples. - * - * @param markedContent content to look for "rg # # # # re". - * @return selection rectangle, null if not found. - */ - protected Rectangle2D.Float findSelectionRectangle(String markedContent) { - int selectionStart = markedContent.indexOf("rg") + 2; - int selectionEnd = markedContent.lastIndexOf("re"); - if (selectionStart < selectionEnd && selectionEnd > 0) { - String potentialNumbers = markedContent.substring(selectionStart, selectionEnd); - float[] points = parseRectanglePoints(potentialNumbers); - if (points != null) { - return new Rectangle2D.Float(points[0], points[1], points[2], points[3]); - } - } - return null; - } - - /** - * Simple utility to write Rectangle2D.Float in postscript. - * - * @param rect Rectangle2D.Float to convert to postscript. Null value with throw null pointer exception. - * @return postscript representation of the rect. - */ - protected String generateRectangle(Rectangle2D.Float rect) { - return rect.x + " " + rect.y + " " + rect.width + " " + rect.height + " re "; - } - - /** - * Converts a given string of four numbers into an array of floats. If a conversion error is encountered - * null value is returned. - * - * @param potentialNumbers space separated string of four numbers. - * @return list of four numbers, null if string can not be converted. - */ - protected float[] parseRectanglePoints(String potentialNumbers) { - StringTokenizer toker = new StringTokenizer(potentialNumbers); - float[] points = new float[4]; - int max = toker.countTokens(); - Object[] tokens = new Object[max]; - for (int i = 0; i < max; i++) { - tokens[i] = toker.nextElement(); - } - boolean notFound = false; - for (int i = 3, j = 0; j < 4; j++, i--) { - try { - points[j] = Float.parseFloat((String) tokens[max - i - 1]); - } catch (NumberFormatException e) { - notFound = true; - } - } - if (!notFound) { - return points; - } else { - return null; - } - } - - /** - * Set the static highlight color used to highlight widget annotations. - * - * @param highlightColor colour of - */ - public static void setHighlightColor(Color highlightColor) { - AbstractWidgetAnnotation.highlightColor = highlightColor; - } - - /** - * Set enable highlight on an individual widget. - * - * @param enableHighlightedWidget true to enable highlight mode, otherwise false. - */ - public void setEnableHighlightedWidget(boolean enableHighlightedWidget) { - this.enableHighlightedWidget = enableHighlightedWidget; - } - - /** - * Set the static alpha value uses to paint a color over a widget annotation. - * - * @param highlightAlpha highlight alpha value between zero and one. - */ - public static void setHighlightAlpha(float highlightAlpha) { - AbstractWidgetAnnotation.highlightAlpha = highlightAlpha; - } - - /** - * Is enable highlight enabled. - * - * @return return true if highlight is enabled, false otherwise. - */ - public boolean isEnableHighlightedWidget() { - return enableHighlightedWidget; - } -} +/* + * Copyright 2006-2019 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.Name; +import org.icepdf.core.pobjects.Resources; +import org.icepdf.core.pobjects.acroform.FieldDictionary; +import org.icepdf.core.pobjects.acroform.InteractiveForm; +import org.icepdf.core.util.ColorUtil; +import org.icepdf.core.util.Defs; +import org.icepdf.core.util.Library; + +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.util.HashMap; +import java.util.StringTokenizer; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Abstract base class for Widget annotations types, button, choice and text. + * + * @since 5.1 + */ +public abstract class AbstractWidgetAnnotation extends Annotation { + + /** + * Indicates that the annotation has no highlight effect. + */ + public static final Name HIGHLIGHT_NONE = new Name("N"); + + protected static final Logger logger = + Logger.getLogger(AbstractWidgetAnnotation.class.toString()); + + /** + * Transparency value used to simulate text highlighting. + */ + protected static float highlightAlpha = 0.1f; + + // text selection colour + protected static Color highlightColor; + + private boolean enableHighlightedWidget; + + static { + // sets the background colour of the annotation highlight + try { + String color = Defs.sysProperty( + "org.icepdf.core.views.page.annotation.widget.highlight.color", "#CC00FF"); + int colorValue = ColorUtil.convertColor(color); + highlightColor = + new Color(colorValue >= 0 ? colorValue : + Integer.parseInt("0077FF", 16)); + } catch (NumberFormatException e) { + if (logger.isLoggable(Level.WARNING)) { + logger.warning("Error reading widget highlight colour."); + } + } + + try { + highlightAlpha = (float) Defs.doubleProperty( + "org.icepdf.core.views.page.annotation.widget.highlight.alpha", 0.1f); + } catch (NumberFormatException e) { + if (logger.isLoggable(Level.WARNING)) { + logger.warning("Error reading widget highlight alpha."); + } + } + } + + protected Name highlightMode; + + public AbstractWidgetAnnotation(Library l, HashMap h) { + super(l, h); + Object possibleName = getObject(LinkAnnotation.HIGHLIGHT_MODE_KEY); + if (possibleName instanceof Name) { + Name name = (Name) possibleName; + if (HIGHLIGHT_NONE.equals(name.getName())) { + highlightMode = HIGHLIGHT_NONE; + } else if (LinkAnnotation.HIGHLIGHT_OUTLINE.equals(name.getName())) { + highlightMode = LinkAnnotation.HIGHLIGHT_OUTLINE; + } else if (LinkAnnotation.HIGHLIGHT_PUSH.equals(name.getName())) { + highlightMode = LinkAnnotation.HIGHLIGHT_PUSH; + } + } else { + highlightMode = LinkAnnotation.HIGHLIGHT_INVERT; + } + + } + + @Override + public void init() throws InterruptedException { + super.init(); + // check to make sure the field value matches the content stream. + InteractiveForm interactiveForm = library.getCatalog().getInteractiveForm(); + if (interactiveForm != null && interactiveForm.needAppearances()) { + resetAppearanceStream(new AffineTransform()); + } + // todo check if we have content value but no appearance stream. + } + + public abstract void reset(); + + @Override + public abstract void resetAppearanceStream(double dx, double dy, AffineTransform pageSpace, boolean isNew); + + @Override + protected void renderAppearanceStream(Graphics2D g) { + + Appearance appearance = appearances.get(currentAppearance); + if (appearance != null) { + AppearanceState appearanceState = appearance.getSelectedAppearanceState(); + if (appearanceState != null && + appearanceState.getShapes() != null) { + // render the main annotation content + super.renderAppearanceStream(g); + } + } + // check the highlight widgetAnnotation field and if true we draw a light background colour to mark + // the widgets on a page. + if (enableHighlightedWidget && + !(getFieldDictionary() != null && getFieldDictionary().isReadOnly())) { + AffineTransform preHighLightTransform = g.getTransform(); + g.setColor(highlightColor); + g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, highlightAlpha)); + g.fill(getBbox() != null ? getBbox() : getRectangle()); + g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)); + g.setTransform(preHighLightTransform); + } + } + + private Rectangle2D getRectangle() { + Rectangle2D origRect = getBbox() != null ? getBbox() : getUserSpaceRectangle(); + Rectangle2D.Float jrect = new Rectangle2D.Float(0, 0, + (float) origRect.getWidth(), (float) origRect.getHeight()); + return jrect; + } + + public abstract T getFieldDictionary(); + + /** + * Generally immediately after the BMC there is a rectangle that defines the actual size of the annotation. If + * found we can use this to make many assumptions and regenerate the content stream. + * + * @param markedContent content stream of the marked content. + * @return a rectangle either way, if the q # # # # re isn't found then we use the bbox as a potential bound. + */ + protected Rectangle2D.Float findBoundRectangle(String markedContent) { + int selectionStart = markedContent.indexOf("q") + 1; + int selectionEnd = markedContent.indexOf("re"); + if (selectionStart < selectionEnd && selectionEnd > 0) { + String potentialNumbers = markedContent.substring(selectionStart, selectionEnd); + float[] points = parseRectanglePoints(potentialNumbers); + if (points != null) { + return new Rectangle2D.Float(points[0], points[1], points[2], points[3]); + } + } + // default to the bounding box. + Rectangle2D bbox = getBbox(); + return new Rectangle2D.Float(1, 1, (float) bbox.getWidth(), (float) bbox.getHeight()); + } + + /** + * Finds a rectangle in the marked content. + * + * @param markedContent content to search for a rectangle. + * @return rectangle if found, otherwise bbox is used. + */ + protected Rectangle2D.Float findRectangle(String markedContent) { + int selectionEnd = markedContent.indexOf("re"); + if (selectionEnd >= 0) { + String potentialNumbers = markedContent.substring(0, selectionEnd); + float[] points = parseRectanglePoints(potentialNumbers); + if (points != null) { + return new Rectangle2D.Float(points[0], points[1], points[2], points[3]); + } + // default to the bounding box. + Rectangle2D bbox = getBbox(); + return new Rectangle2D.Float(1, 1, (float) bbox.getWidth(), (float) bbox.getHeight()); + } else { + return null; + } + } + + /** + * Get the line height as specified by Th or the font size. + * + * @param defaultAppearance searchable stream + * @return line height, or 13.87 if no reasonable approximation can be found. + */ + protected double getLineHeight(String defaultAppearance) { + if (defaultAppearance != null && checkAppearance(defaultAppearance)) { + String sub = defaultAppearance.substring(0, defaultAppearance.indexOf("Tf")); + StringTokenizer toker = new StringTokenizer(sub); + while (toker.hasMoreTokens()) { + Object obj = toker.nextElement(); + if (obj instanceof String) { + try { + double tmp = Double.parseDouble((String) obj); + tmp *= 1.15; + if (tmp > 0) { + return tmp; + } + } catch (NumberFormatException e) { + // intentionally blank. + } + } + } + } + return 13.87; + } + + protected double getFontSize(String content) { + // try and find text size + double size = 12; + + if (content != null) { + Pattern pattern = Pattern.compile("\\d+(\\.\\d+)?\\s+Tf"); + Matcher matcher = pattern.matcher(content); + if (matcher.find()) { + String fontDef = content.substring(matcher.start(), matcher.end()); + fontDef = fontDef.split(" ")[0]; + try { + size = Double.parseDouble(fontDef); + } catch (NumberFormatException e) { + // ignore and move on + } + if (size < 2) { + size = 12; + } + } + } + return size; + } + + /** + * Encodes the given cotents string into a valid postscript string that is literal encoded. + * + * @param content current content stream to append literal string to. + * @param contents string to be encoded into '(...)' literal format. + * @return original content stream with contents encoded in the literal string format. + */ + protected StringBuilder encodeLiteralString(StringBuilder content, String contents) { + String[] lines = contents.split("\n|\r|\f"); + for (String line : lines) { + content.append('(').append(line.replaceAll("(?=[()\\\\])", "\\\\") + .replaceAll("ÿ", "")).append(")' "); + } + return content; + } + + /** + * Encodes the given contents string into a valid postscript hex string. + * + * @param content current content stream to append literal string to. + * @param contents string to be encoded into '<...></...>' hex format. + * @return original content stream with contents encoded in the hex string format. + */ + protected StringBuilder encodeHexString(StringBuilder content, String contents) { + String[] lines = contents.split("\n|\r|\f"); + for (String line : lines) { + char[] chars = line.toCharArray(); + StringBuilder hex = new StringBuilder(); + for (char aChar : chars) { + hex.append(Integer.toHexString((int) aChar)); + } + content.append('<').append(hex).append(">' "); + } + return content; + } + + /** + * Utility to try and determine if the appearance is valid. + * + * @param appearance appearance ot test. + * @return true if valid, false otherwise. + */ + protected boolean checkAppearance(String appearance) { + // example of a bad appearance, /TiBo 0 Tf 0 g + // size is zero and the font can't be found. + StringTokenizer toker = new StringTokenizer(appearance); + if (toker.hasMoreTokens()) { + String fontName = toker.nextToken().substring(1); + String fontSize = toker.nextToken(); + Appearance appearance1 = appearances.get(currentAppearance); + AppearanceState appearanceState = appearance1.getSelectedAppearanceState(); + org.icepdf.core.pobjects.fonts.Font font = null; + Resources resources = appearanceState.getResources(); + if (resources != null) { + font = resources.getFont(new Name(fontName)); + } + return !(font == null || library.getInteractiveFormFont(fontName) == null || + fontSize.equals("0")); + } + return false; + } + + + /** + * The selection rectangle if present will help define the line height of the text. If not present we can use + * the default value 13.87 later which seems to be very common in the samples. + * + * @param markedContent content to look for "rg # # # # re". + * @return selection rectangle, null if not found. + */ + protected Rectangle2D.Float findSelectionRectangle(String markedContent) { + int selectionStart = markedContent.indexOf("rg") + 2; + int selectionEnd = markedContent.lastIndexOf("re"); + if (selectionStart < selectionEnd && selectionEnd > 0) { + String potentialNumbers = markedContent.substring(selectionStart, selectionEnd); + float[] points = parseRectanglePoints(potentialNumbers); + if (points != null) { + return new Rectangle2D.Float(points[0], points[1], points[2], points[3]); + } + } + return null; + } + + /** + * Simple utility to write Rectangle2D.Float in postscript. + * + * @param rect Rectangle2D.Float to convert to postscript. Null value with throw null pointer exception. + * @return postscript representation of the rect. + */ + protected String generateRectangle(Rectangle2D.Float rect) { + return rect.x + " " + rect.y + " " + rect.width + " " + rect.height + " re "; + } + + /** + * Converts a given string of four numbers into an array of floats. If a conversion error is encountered + * null value is returned. + * + * @param potentialNumbers space separated string of four numbers. + * @return list of four numbers, null if string can not be converted. + */ + protected float[] parseRectanglePoints(String potentialNumbers) { + StringTokenizer toker = new StringTokenizer(potentialNumbers); + float[] points = new float[4]; + int max = toker.countTokens(); + Object[] tokens = new Object[max]; + for (int i = 0; i < max; i++) { + tokens[i] = toker.nextElement(); + } + boolean notFound = false; + for (int i = 3, j = 0; j < 4; j++, i--) { + try { + points[j] = Float.parseFloat((String) tokens[max - i - 1]); + } catch (NumberFormatException e) { + notFound = true; + } + } + if (!notFound) { + return points; + } else { + return null; + } + } + + /** + * Set the static highlight color used to highlight widget annotations. + * + * @param highlightColor colour of + */ + public static void setHighlightColor(Color highlightColor) { + AbstractWidgetAnnotation.highlightColor = highlightColor; + } + + /** + * Set enable highlight on an individual widget. + * + * @param enableHighlightedWidget true to enable highlight mode, otherwise false. + */ + public void setEnableHighlightedWidget(boolean enableHighlightedWidget) { + this.enableHighlightedWidget = enableHighlightedWidget; + } + + /** + * Set the static alpha value uses to paint a color over a widget annotation. + * + * @param highlightAlpha highlight alpha value between zero and one. + */ + public static void setHighlightAlpha(float highlightAlpha) { + AbstractWidgetAnnotation.highlightAlpha = highlightAlpha; + } + + /** + * Is enable highlight enabled. + * + * @return return true if highlight is enabled, false otherwise. + */ + public boolean isEnableHighlightedWidget() { + return enableHighlightedWidget; + } +} diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/Annotation.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/Annotation.java index 1c75fd355..82928b2df 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/Annotation.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/Annotation.java @@ -1853,7 +1853,8 @@ public Form getOrGenerateAppearanceForm() { * @param rawBytes raw bytes of string data making up the content stream. * @return new Form object with updated appearance stream. */ - public Form updateAppearanceStream(Shapes shapes, Rectangle2D bbox, AffineTransform matrix, byte[] rawBytes) { + public Form updateAppearanceStream(Shapes shapes, Rectangle2D bbox, AffineTransform matrix, byte[] rawBytes, + boolean isNew) { // update the appearance stream // create/update the appearance stream of the xObject. StateManager stateManager = library.getStateManager(); @@ -1978,10 +1979,23 @@ public void syncBBoxToUserSpaceRectangle(Rectangle2D bbox) { (float) tBbox.getWidth(), (float) tBbox.getHeight())); } - public abstract void resetAppearanceStream(double dx, double dy, AffineTransform pageSpace); + /** + * Reset the appearance stream given the specified location and page space transform. + * @param dx coord-x + * @param dy coord-y + * @param pageSpace page space transform + * @param isNew marks the reset as happening because of a + */ + public abstract void resetAppearanceStream(double dx, double dy, AffineTransform pageSpace, boolean isNew); + +// public abstract void resetAppearanceStream(double dx, double dy, AffineTransform pageSpace); + + public void resetAppearanceStream(AffineTransform pageSpace, boolean isNew) { + resetAppearanceStream(0, 0, pageSpace, isNew); + } public void resetAppearanceStream(AffineTransform pageSpace) { - resetAppearanceStream(0, 0, pageSpace); + resetAppearanceStream(0, 0, pageSpace, false); } public Shapes getShapes() { diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/ButtonWidgetAnnotation.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/ButtonWidgetAnnotation.java index a06a8d9bc..9b3416c93 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/ButtonWidgetAnnotation.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/ButtonWidgetAnnotation.java @@ -1,149 +1,150 @@ -/* - * Copyright 2006-2019 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.Name; -import org.icepdf.core.pobjects.PObject; -import org.icepdf.core.pobjects.StateManager; -import org.icepdf.core.pobjects.acroform.ButtonFieldDictionary; -import org.icepdf.core.pobjects.acroform.FieldDictionary; -import org.icepdf.core.util.Library; - -import java.awt.geom.AffineTransform; -import java.util.HashMap; - -/** - * Represents a Acroform Button widget and manages the appearance streams - * for the various appearance states. - * - * @since 5.1 - */ -public class ButtonWidgetAnnotation extends AbstractWidgetAnnotation { - - private ButtonFieldDictionary fieldDictionary; - - protected Name originalAppearance; - - public ButtonWidgetAnnotation(Library l, HashMap h) { - super(l, h); - fieldDictionary = new ButtonFieldDictionary(library, entries); - } - - public ButtonWidgetAnnotation(Annotation widgetAnnotation){ - super(widgetAnnotation.getLibrary(), widgetAnnotation.getEntries()); - fieldDictionary = new ButtonFieldDictionary(library, entries); - // copy over the reference number. - setPObjectReference(widgetAnnotation.getPObjectReference()); - } - - /** - * Button appearance streams are fixed, all that is done in this method is appearance selected state - * is set and the change persisted to the StateManager. - * @param dx current location of the annotation - * @param dy current location of the annotation - * @param pageSpace current page space. - */ - public void resetAppearanceStream(double dx, double dy, AffineTransform pageSpace) { - // update the appearanceState in the state manager so the change will persist. - Appearance appearance = appearances.get(currentAppearance); - if (appearance != null) { - appearance.updateAppearanceDictionary(entries); - } - // add this annotation to the state manager. - StateManager stateManager = library.getStateManager(); - stateManager.addChange(new PObject(this, this.getPObjectReference())); - - Name selectedName = appearance.getSelectedName(); - // check boxes will have a V entry which - if (getFieldDictionary().hasFieldValue()){ - // update the value - entries.put(FieldDictionary.V_KEY, selectedName); - // object already in state manager, nothing further to to. - } // radio buttons will store the value in the parent. - else if (getFieldDictionary().getParent() != null && - getFieldDictionary().getParent().hasFieldValue()){ - // update the value - //getFieldDictionary().getParent().getEntries().put(FieldDictionary.V_KEY, selectedName); - // add to state manager. - stateManager.addChange(new PObject(getFieldDictionary().getParent(), - getFieldDictionary().getParent().getPObjectReference())); - } - - if (originalAppearance == null){ - originalAppearance = currentAppearance; - } - } - - public void reset() { - Object oldValue = fieldDictionary.getFieldValue(); - Object defaultFieldValue = fieldDictionary.getDefaultFieldValue(); - FieldDictionary parentFaultFieldValue = fieldDictionary.getParent(); - if (defaultFieldValue != null) { - // apply the default value - changeSupport.firePropertyChange("valueFieldReset", oldValue, defaultFieldValue); - }else if (parentFaultFieldValue != null) { - // apply the default value - changeSupport.firePropertyChange("valueFieldReset", oldValue, - parentFaultFieldValue.getDefaultFieldValue()); - }else{ - // otherwise we remove the key - changeSupport.firePropertyChange("valueFieldReset", oldValue, ""); - } - } - - public void turnOff() { - Appearance appearance = appearances.get(currentAppearance); - if (appearance != null && appearance.hasAlternativeAppearance()) { - appearance.setSelectedName(appearance.getOffName()); - } - } - - public boolean isOn() { - Appearance appearance = appearances.get(currentAppearance); - Name selectedNormalAppearance = appearance.getSelectedName(); - return !selectedNormalAppearance.equals(appearance.getOffName()); - } - - public Name toggle() { - Appearance appearance = appearances.get(currentAppearance); - Name selectedNormalAppearance = appearance.getSelectedName(); - if (appearance.hasAlternativeAppearance()) { - if (!selectedNormalAppearance.equals(appearance.getOffName())) { - appearance.setSelectedName(appearance.getOffName()); - } else { - appearance.setSelectedName(appearance.getOnName()); - } - } - return appearance.getSelectedName(); - } - - - public void turnOn() { - // first check if there are more then one normal streams. - Appearance appearance = appearances.get(currentAppearance); - if (appearance.hasAlternativeAppearance()) { - appearance.setSelectedName(appearance.getOnName()); - } - } - - @Override - public ButtonFieldDictionary getFieldDictionary() { - return fieldDictionary; - } -} - - +/* + * Copyright 2006-2019 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.Name; +import org.icepdf.core.pobjects.PObject; +import org.icepdf.core.pobjects.StateManager; +import org.icepdf.core.pobjects.acroform.ButtonFieldDictionary; +import org.icepdf.core.pobjects.acroform.FieldDictionary; +import org.icepdf.core.util.Library; + +import java.awt.geom.AffineTransform; +import java.util.HashMap; + +/** + * Represents a Acroform Button widget and manages the appearance streams + * for the various appearance states. + * + * @since 5.1 + */ +public class ButtonWidgetAnnotation extends AbstractWidgetAnnotation { + + private ButtonFieldDictionary fieldDictionary; + + protected Name originalAppearance; + + public ButtonWidgetAnnotation(Library l, HashMap h) { + super(l, h); + fieldDictionary = new ButtonFieldDictionary(library, entries); + } + + public ButtonWidgetAnnotation(Annotation widgetAnnotation){ + super(widgetAnnotation.getLibrary(), widgetAnnotation.getEntries()); + fieldDictionary = new ButtonFieldDictionary(library, entries); + // copy over the reference number. + setPObjectReference(widgetAnnotation.getPObjectReference()); + } + + /** + * Button appearance streams are fixed, all that is done in this method is appearance selected state + * is set and the change persisted to the StateManager. + * + * @param dx current location of the annotation + * @param dy current location of the annotation + * @param pageSpace current page space. + */ + public void resetAppearanceStream(double dx, double dy, AffineTransform pageSpace, boolean isNew) { + // update the appearanceState in the state manager so the change will persist. + Appearance appearance = appearances.get(currentAppearance); + if (appearance != null) { + appearance.updateAppearanceDictionary(entries); + } + // add this annotation to the state manager. + StateManager stateManager = library.getStateManager(); + stateManager.addChange(new PObject(this, this.getPObjectReference())); + + Name selectedName = appearance.getSelectedName(); + // check boxes will have a V entry which + if (getFieldDictionary().hasFieldValue()){ + // update the value + entries.put(FieldDictionary.V_KEY, selectedName); + // object already in state manager, nothing further to to. + } // radio buttons will store the value in the parent. + else if (getFieldDictionary().getParent() != null && + getFieldDictionary().getParent().hasFieldValue()){ + // update the value + //getFieldDictionary().getParent().getEntries().put(FieldDictionary.V_KEY, selectedName); + // add to state manager. + stateManager.addChange(new PObject(getFieldDictionary().getParent(), + getFieldDictionary().getParent().getPObjectReference()), isNew); + } + + if (originalAppearance == null){ + originalAppearance = currentAppearance; + } + } + + public void reset() { + Object oldValue = fieldDictionary.getFieldValue(); + Object defaultFieldValue = fieldDictionary.getDefaultFieldValue(); + FieldDictionary parentFaultFieldValue = fieldDictionary.getParent(); + if (defaultFieldValue != null) { + // apply the default value + changeSupport.firePropertyChange("valueFieldReset", oldValue, defaultFieldValue); + }else if (parentFaultFieldValue != null) { + // apply the default value + changeSupport.firePropertyChange("valueFieldReset", oldValue, + parentFaultFieldValue.getDefaultFieldValue()); + }else{ + // otherwise we remove the key + changeSupport.firePropertyChange("valueFieldReset", oldValue, ""); + } + } + + public void turnOff() { + Appearance appearance = appearances.get(currentAppearance); + if (appearance != null && appearance.hasAlternativeAppearance()) { + appearance.setSelectedName(appearance.getOffName()); + } + } + + public boolean isOn() { + Appearance appearance = appearances.get(currentAppearance); + Name selectedNormalAppearance = appearance.getSelectedName(); + return !selectedNormalAppearance.equals(appearance.getOffName()); + } + + public Name toggle() { + Appearance appearance = appearances.get(currentAppearance); + Name selectedNormalAppearance = appearance.getSelectedName(); + if (appearance.hasAlternativeAppearance()) { + if (!selectedNormalAppearance.equals(appearance.getOffName())) { + appearance.setSelectedName(appearance.getOffName()); + } else { + appearance.setSelectedName(appearance.getOnName()); + } + } + return appearance.getSelectedName(); + } + + + public void turnOn() { + // first check if there are more then one normal streams. + Appearance appearance = appearances.get(currentAppearance); + if (appearance.hasAlternativeAppearance()) { + appearance.setSelectedName(appearance.getOnName()); + } + } + + @Override + public ButtonFieldDictionary getFieldDictionary() { + return fieldDictionary; + } +} + + diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/ChoiceWidgetAnnotation.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/ChoiceWidgetAnnotation.java index 72da6b12f..22b5252b8 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/ChoiceWidgetAnnotation.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/ChoiceWidgetAnnotation.java @@ -1,409 +1,409 @@ -/* - * Copyright 2006-2019 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.acroform.ChoiceFieldDictionary; -import org.icepdf.core.pobjects.acroform.FieldDictionary; -import org.icepdf.core.pobjects.graphics.Shapes; -import org.icepdf.core.pobjects.graphics.text.LineText; -import org.icepdf.core.pobjects.graphics.text.WordText; -import org.icepdf.core.util.Library; - -import java.awt.geom.AffineTransform; -import java.awt.geom.Rectangle2D; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.StringTokenizer; -import java.util.logging.Level; - -import static org.icepdf.core.pobjects.acroform.ChoiceFieldDictionary.ChoiceFieldType; - -/** - * Represents a Acroform Choice widget and manages the appearance streams - * for the various appearance states. This class can generate a postscript - * stream that represents it current state. - * - * @since 5.1 - */ -public class ChoiceWidgetAnnotation extends AbstractWidgetAnnotation { - - private ChoiceFieldDictionary fieldDictionary; - - public ChoiceWidgetAnnotation(Library l, HashMap h) { - super(l, h); - fieldDictionary = new ChoiceFieldDictionary(library, entries); - } - - /** - * Some choices lists are lacking the /opt key so we need to do our best to generate the list from the shapes. - * - * @return list of potential options. - */ - public ArrayList generateChoices() { - Shapes shapes = getShapes(); - if (shapes != null) { - ArrayList options = new ArrayList<>(); - String tmp; - ArrayList pageLines = shapes.getPageText().getPageLines(); - for (LineText lines : pageLines) { - for (WordText word : lines.getWords()) { - tmp = word.toString(); - if (!(tmp.equals("") || tmp.equals(" "))) { - options.add(fieldDictionary.buildChoiceOption(tmp, tmp)); - } - } - } - return options; - } - return new ArrayList<>(); - } - - /** - * Resets the appearance stream for this instance using the current state. The mark content section of the stream - * is found and the edit it make to best of our ability. - * - * @param dx x offset of the annotation - * @param dy y offset of the annotation - * @param pageTransform current page transform. - */ - public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform) { - ChoiceFieldType choiceFieldType = - fieldDictionary.getChoiceFieldType(); - - // get at the original postscript as well alter the marked content - Appearance appearance = appearances.get(currentAppearance); - AppearanceState appearanceState = appearance.getSelectedAppearanceState(); - Rectangle2D bbox = appearanceState.getBbox(); - AffineTransform matrix = appearanceState.getMatrix(); - String currentContentStream = appearanceState.getOriginalContentStream(); - - // alterations vary by choice type. - if (choiceFieldType == ChoiceFieldType.CHOICE_COMBO || - choiceFieldType == ChoiceFieldType.CHOICE_EDITABLE_COMBO) { - // relatively straight forward replace with new selected value. - if (currentContentStream != null) { - currentContentStream = buildChoiceComboContents(currentContentStream); - } else { - // todo no stream and we will need to build one. - currentContentStream = ""; - } - } else { - // build out the complex choice list content stream - if (currentContentStream != null) { - currentContentStream = buildChoiceListContents(currentContentStream); - } else { - // todo no stream and we will need to build one. - currentContentStream = ""; - } - } - // finally create the shapes from the altered stream. - if (currentContentStream != null) { - appearanceState.setContentStream(currentContentStream.getBytes()); - } - - // some widgets don't have AP dictionaries in such a case we need to create the form object - // and build out the default properties. - Form appearanceStream = getOrGenerateAppearanceForm(); - - if (appearanceStream != null) { - // update the content stream with the new stream data. - appearanceStream.setRawBytes(currentContentStream.getBytes()); - // add the appearance stream - StateManager stateManager = library.getStateManager(); - stateManager.addChange(new PObject(appearanceStream, appearanceStream.getPObjectReference())); - // add an AP entry for the - HashMap appearanceRefs = new HashMap<>(); - appearanceRefs.put(APPEARANCE_STREAM_NORMAL_KEY, appearanceStream.getPObjectReference()); - entries.put(APPEARANCE_STREAM_KEY, appearanceRefs); - Rectangle2D formBbox = new Rectangle2D.Float(0, 0, - (float) bbox.getWidth(), (float) bbox.getHeight()); - appearanceStream.setAppearance(null, matrix, formBbox); - // add link to resources on forum, if no resources exist. - if (library.getResources(appearanceStream.getEntries(), Form.RESOURCES_KEY) == null) { - appearanceStream.getEntries().put(Form.RESOURCES_KEY, - library.getCatalog().getInteractiveForm().getResources().getEntries()); - } - // add the annotation as changed as T entry has also been updated to reflect teh changed content. - stateManager.addChange(new PObject(this, this.getPObjectReference())); - - // compress the form object stream. - if (false && compressAppearanceStream) { - appearanceStream.getEntries().put(Stream.FILTER_KEY, new Name("FlateDecode")); - } else { - appearanceStream.getEntries().remove(Stream.FILTER_KEY); - } - try { - appearanceStream.init(); - } catch (InterruptedException e) { - logger.log(Level.WARNING, "Could not initialized ChoiceWidgetAnnotation", e); - } - } - } - - - public void reset() { - Object oldValue = fieldDictionary.getFieldValue(); - Object tmp = fieldDictionary.getDefaultFieldValue(); - if (tmp == null) { - FieldDictionary parentFieldDictionary = fieldDictionary.getParent(); - if (parentFieldDictionary != null) { - tmp = parentFieldDictionary.getDefaultFieldValue(); - } - } - if (tmp != null) { - // apply the default value - fieldDictionary.setFieldValue(tmp, getPObjectReference()); - changeSupport.firePropertyChange("valueFieldReset", oldValue, tmp); - } else { - // otherwise we remove the key - fieldDictionary.getEntries().remove(FieldDictionary.V_KEY); - fieldDictionary.setIndexes(null); - // check the parent as well. - FieldDictionary parentFieldDictionary = fieldDictionary.getParent(); - if (parentFieldDictionary != null) { - parentFieldDictionary.getEntries().remove(FieldDictionary.V_KEY); - if (parentFieldDictionary instanceof ChoiceFieldDictionary) { - ((ChoiceFieldDictionary) parentFieldDictionary).setIndexes(null); - } - } - changeSupport.firePropertyChange("valueFieldReset", oldValue, null); - } - } - - @Override - public ChoiceFieldDictionary getFieldDictionary() { - return fieldDictionary; - } - - public String buildChoiceComboContents(String currentContentStream) { - ArrayList choices = fieldDictionary.getOptions(); - // double check we have some choices to work with. - if (choices == null) { - // generate them from the content stream. - choices = generateChoices(); - fieldDictionary.setOptions(choices); - } - String selectedField = (String) fieldDictionary.getFieldValue(); - int btStart = currentContentStream.indexOf("BT"); - int btEnd = currentContentStream.lastIndexOf("ET"); - int bmcStart = currentContentStream.indexOf("BMC"); - int bmcEnd = currentContentStream.lastIndexOf("EMC"); - // grab the pre post marked content postscript. - String preBmc = btStart >= 0 ? currentContentStream.substring(0, btStart + 2) : - currentContentStream.substring(0, bmcStart + 3); - String postEmc = btEnd >= 0 ? currentContentStream.substring(btEnd) : - currentContentStream.substring(0, bmcEnd + 3); - - // marked content which we will use to try and find some data points. - //String markedContent = currentContentStream.substring(bmcStart, bmcEnd); - - // check for a bounding box definition - //Rectangle2D.Float bounds = findBoundRectangle(markedContent); - - // finally build out the new content stream - StringBuilder content = new StringBuilder(); - // apply font - if (fieldDictionary.getDefaultAppearance() != null) { - String markedContent = fieldDictionary.getDefaultAppearance(); - Page page = getPage(); - markedContent = fieldDictionary.generateDefaultAppearance(markedContent, - page != null ? page.getResources() : null); - content.append(markedContent).append(' '); - } else { // common font and colour layout for most form elements. - content.append("/Helv 12 Tf 0 g "); - } - // apply the text offset, 4 is just a generic padding. - content.append(4).append(' ').append(4).append(" Td "); - // hex encode the text so that we better handle character codes > 127 - content = encodeHexString(content, selectedField).append(" Tj "); - // build the final content stream. - if (btStart >= 0) { - currentContentStream = preBmc + "\n" + content + "\n" + postEmc; - } else { - currentContentStream = preBmc + " BT\n" + content + "\n ET EMC"; - } - - return currentContentStream; - } - - public String buildChoiceListContents(String currentContentStream) { - - ArrayList choices = fieldDictionary.getOptions(); - // double check we have some choices to work with. - if (choices == null) { - // generate them from the content stream. - choices = generateChoices(); - fieldDictionary.setOptions(choices); - } - ArrayList selections = fieldDictionary.getIndexes(); - // mark the indexes of the mark content. - int bmcStart = currentContentStream.indexOf("BMC") + 3; - int bmcEnd = currentContentStream.indexOf("EMC"); - // grab the pre post marked content postscript. - String preBmc = currentContentStream.substring(0, bmcStart); - String postEmc = currentContentStream.substring(bmcEnd); - // marked content which we will use to try and find some data points. - String markedContent = currentContentStream.substring(bmcStart, bmcEnd); - - // check for a bounding box definition - Rectangle2D.Float bounds = findBoundRectangle(markedContent); - - // check to see if there is a selection box colour defined. - float[] selectionColor = findSelectionColour(markedContent); - - // and finally look for a previous selection box, this can be null, no default value - Rectangle2D.Float selectionRectangle = findSelectionRectangle(markedContent); - float lineHeight = 13.87f; - if (selectionRectangle != null) { - lineHeight = selectionRectangle.height; - } - - // we need to plot out where the opt text is going to go as well as the background colour and text colour - // for any selected items. So we update the choices model to reflect the current selection state. - boolean isSelection = false; - if (selections != null) { - for (int i = 0, max = choices.size(); i < max; i++) { - for (int selection : selections) { - if (selection == i) { - choices.get(i).setIsSelected(true); - isSelection = true; - } else { - choices.get(i).setIsSelected(false); - } - } - } - } - // figure out offset range to insure a single selection is always visible - int startIndex = 0, endIndex = choices.size(); - if (selections != null && selections.size() == 1) { - int numberLines = (int) Math.floor(bounds.height / lineHeight); - // check if list is smaller then number of lines - int selectedIndex = selections.get(0); - if (choices.size() < numberLines) { - // nothing to do. - } else if (selectedIndex < numberLines) { - endIndex = numberLines + 1; - } - // check for bottom out range - else if (endIndex - selectedIndex <= numberLines) { - startIndex = endIndex - numberLines; - } - // else mid range just need to start the index. - else { - startIndex = selectedIndex; - endIndex = numberLines + 1; - - } - // we have a single line - if (startIndex > endIndex) { - endIndex = startIndex + 1; - } - } - - // finally build out the new content stream - StringBuilder content = new StringBuilder(); - // bounding rectangle. - content.append("q ").append(generateRectangle(bounds)).append("W n "); - // apply selection highlight background. - if (isSelection) { - // apply colour - content.append(selectionColor[0]).append(' ').append(selectionColor[1]).append(' ') - .append(selectionColor[2]).append(" rg "); - // apply selection - Rectangle2D.Float firstSelection; - if (selectionRectangle == null) { - firstSelection = new Rectangle2D.Float(bounds.x, bounds.y + bounds.height - lineHeight, bounds.width, lineHeight); - } else { - firstSelection = new Rectangle2D.Float(selectionRectangle.x, bounds.y + bounds.height - lineHeight, - selectionRectangle.width, lineHeight); - } - ChoiceFieldDictionary.ChoiceOption choice; - for (int i = startIndex; i < endIndex; i++) { - choice = choices.get(i); - // check if a selection rectangle was defined, if not we might have a custom style and we - // avoid the selection background (only have one test case for this) - if (choice.isSelected() && selectionRectangle != null) { - content.append(generateRectangle(firstSelection)).append("f "); - } - firstSelection.y -= lineHeight; - } - } - // apply the ext. - content.append("BT "); - // apply font - if (fieldDictionary.getDefaultAppearance() != null) { - content.append(fieldDictionary.getDefaultAppearance()); - } else { // common font and colour layout for most form elements. - content.append("/Helv 12 Tf 0 g "); - } - // apply the line height - content.append(lineHeight).append(" TL "); - // apply the text offset, 4 is just a generic padding. - content.append(4).append(' ').append(bounds.height + 4).append(" Td "); - // print out text - ChoiceFieldDictionary.ChoiceOption choice; - for (int i = startIndex; i < endIndex; i++) { - choice = choices.get(i); - if (choice.isSelected() && selectionRectangle != null) { - content.append("1 g "); - } else { - content.append("0 g "); - } - content.append('(').append(choice.getLabel()).append(")' "); - } - content.append("ET Q"); - // build the final content stream. - currentContentStream = preBmc + "\n" + content + "\n" + postEmc; - return currentContentStream; - } - - /** - * The selection colour is generally defined in DeviceRGB and occurs after the bounding box has been defined. - * This utility method tries to parse out the colour information and return it in float[3]. If the data can't - * be found then we return the default colour of new float[]{0.03922f, 0.14118f, 0.41569f}. - * - * @param markedContent content to look for colour info. - * @return found colour data or new float[]{0.03922f, 0.14118f, 0.41569f}. - */ - private float[] findSelectionColour(String markedContent) { - int selectionStart = markedContent.indexOf("n") + 1; - int selectionEnd = markedContent.lastIndexOf("rg"); - if (selectionStart < selectionEnd && selectionEnd > 0) { - String potentialNumbers = markedContent.substring(selectionStart, selectionEnd); - StringTokenizer toker = new StringTokenizer(potentialNumbers); - float[] points = new float[3]; - int i = 0; - while (toker.hasMoreTokens()) { - try { - float tmp = Float.parseFloat(toker.nextToken()); - points[i] = tmp; - i++; - } catch (NumberFormatException e) { - break; - } - } - if (i == 3) { - return points; - } - } - // default selection colour. - return new float[]{0.03922f, 0.14118f, 0.41569f}; - } - -} - +/* + * Copyright 2006-2019 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.acroform.ChoiceFieldDictionary; +import org.icepdf.core.pobjects.acroform.FieldDictionary; +import org.icepdf.core.pobjects.graphics.Shapes; +import org.icepdf.core.pobjects.graphics.text.LineText; +import org.icepdf.core.pobjects.graphics.text.WordText; +import org.icepdf.core.util.Library; + +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.StringTokenizer; +import java.util.logging.Level; + +import static org.icepdf.core.pobjects.acroform.ChoiceFieldDictionary.ChoiceFieldType; + +/** + * Represents a Acroform Choice widget and manages the appearance streams + * for the various appearance states. This class can generate a postscript + * stream that represents it current state. + * + * @since 5.1 + */ +public class ChoiceWidgetAnnotation extends AbstractWidgetAnnotation { + + private ChoiceFieldDictionary fieldDictionary; + + public ChoiceWidgetAnnotation(Library l, HashMap h) { + super(l, h); + fieldDictionary = new ChoiceFieldDictionary(library, entries); + } + + /** + * Some choices lists are lacking the /opt key so we need to do our best to generate the list from the shapes. + * + * @return list of potential options. + */ + public ArrayList generateChoices() { + Shapes shapes = getShapes(); + if (shapes != null) { + ArrayList options = new ArrayList<>(); + String tmp; + ArrayList pageLines = shapes.getPageText().getPageLines(); + for (LineText lines : pageLines) { + for (WordText word : lines.getWords()) { + tmp = word.toString(); + if (!(tmp.equals("") || tmp.equals(" "))) { + options.add(fieldDictionary.buildChoiceOption(tmp, tmp)); + } + } + } + return options; + } + return new ArrayList<>(); + } + + /** + * Resets the appearance stream for this instance using the current state. The mark content section of the stream + * is found and the edit it make to best of our ability. + * + * @param dx x offset of the annotation + * @param dy y offset of the annotation + * @param pageTransform current page transform. + */ + public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform, boolean isNew) { + ChoiceFieldType choiceFieldType = + fieldDictionary.getChoiceFieldType(); + + // get at the original postscript as well alter the marked content + Appearance appearance = appearances.get(currentAppearance); + AppearanceState appearanceState = appearance.getSelectedAppearanceState(); + Rectangle2D bbox = appearanceState.getBbox(); + AffineTransform matrix = appearanceState.getMatrix(); + String currentContentStream = appearanceState.getOriginalContentStream(); + + // alterations vary by choice type. + if (choiceFieldType == ChoiceFieldType.CHOICE_COMBO || + choiceFieldType == ChoiceFieldType.CHOICE_EDITABLE_COMBO) { + // relatively straight forward replace with new selected value. + if (currentContentStream != null) { + currentContentStream = buildChoiceComboContents(currentContentStream); + } else { + // todo no stream and we will need to build one. + currentContentStream = ""; + } + } else { + // build out the complex choice list content stream + if (currentContentStream != null) { + currentContentStream = buildChoiceListContents(currentContentStream); + } else { + // todo no stream and we will need to build one. + currentContentStream = ""; + } + } + // finally create the shapes from the altered stream. + if (currentContentStream != null) { + appearanceState.setContentStream(currentContentStream.getBytes()); + } + + // some widgets don't have AP dictionaries in such a case we need to create the form object + // and build out the default properties. + Form appearanceStream = getOrGenerateAppearanceForm(); + + if (appearanceStream != null) { + // update the content stream with the new stream data. + appearanceStream.setRawBytes(currentContentStream.getBytes()); + // add the appearance stream + StateManager stateManager = library.getStateManager(); + stateManager.addChange(new PObject(appearanceStream, appearanceStream.getPObjectReference())); + // add an AP entry for the + HashMap appearanceRefs = new HashMap<>(); + appearanceRefs.put(APPEARANCE_STREAM_NORMAL_KEY, appearanceStream.getPObjectReference()); + entries.put(APPEARANCE_STREAM_KEY, appearanceRefs); + Rectangle2D formBbox = new Rectangle2D.Float(0, 0, + (float) bbox.getWidth(), (float) bbox.getHeight()); + appearanceStream.setAppearance(null, matrix, formBbox); + // add link to resources on forum, if no resources exist. + if (library.getResources(appearanceStream.getEntries(), Form.RESOURCES_KEY) == null) { + appearanceStream.getEntries().put(Form.RESOURCES_KEY, + library.getCatalog().getInteractiveForm().getResources().getEntries()); + } + // add the annotation as changed as T entry has also been updated to reflect teh changed content. + stateManager.addChange(new PObject(this, this.getPObjectReference())); + + // compress the form object stream. + if (false && compressAppearanceStream) { + appearanceStream.getEntries().put(Stream.FILTER_KEY, new Name("FlateDecode")); + } else { + appearanceStream.getEntries().remove(Stream.FILTER_KEY); + } + try { + appearanceStream.init(); + } catch (InterruptedException e) { + logger.log(Level.WARNING, "Could not initialized ChoiceWidgetAnnotation", e); + } + } + } + + + public void reset() { + Object oldValue = fieldDictionary.getFieldValue(); + Object tmp = fieldDictionary.getDefaultFieldValue(); + if (tmp == null) { + FieldDictionary parentFieldDictionary = fieldDictionary.getParent(); + if (parentFieldDictionary != null) { + tmp = parentFieldDictionary.getDefaultFieldValue(); + } + } + if (tmp != null) { + // apply the default value + fieldDictionary.setFieldValue(tmp, getPObjectReference()); + changeSupport.firePropertyChange("valueFieldReset", oldValue, tmp); + } else { + // otherwise we remove the key + fieldDictionary.getEntries().remove(FieldDictionary.V_KEY); + fieldDictionary.setIndexes(null); + // check the parent as well. + FieldDictionary parentFieldDictionary = fieldDictionary.getParent(); + if (parentFieldDictionary != null) { + parentFieldDictionary.getEntries().remove(FieldDictionary.V_KEY); + if (parentFieldDictionary instanceof ChoiceFieldDictionary) { + ((ChoiceFieldDictionary) parentFieldDictionary).setIndexes(null); + } + } + changeSupport.firePropertyChange("valueFieldReset", oldValue, null); + } + } + + @Override + public ChoiceFieldDictionary getFieldDictionary() { + return fieldDictionary; + } + + public String buildChoiceComboContents(String currentContentStream) { + ArrayList choices = fieldDictionary.getOptions(); + // double check we have some choices to work with. + if (choices == null) { + // generate them from the content stream. + choices = generateChoices(); + fieldDictionary.setOptions(choices); + } + String selectedField = (String) fieldDictionary.getFieldValue(); + int btStart = currentContentStream.indexOf("BT"); + int btEnd = currentContentStream.lastIndexOf("ET"); + int bmcStart = currentContentStream.indexOf("BMC"); + int bmcEnd = currentContentStream.lastIndexOf("EMC"); + // grab the pre post marked content postscript. + String preBmc = btStart >= 0 ? currentContentStream.substring(0, btStart + 2) : + currentContentStream.substring(0, bmcStart + 3); + String postEmc = btEnd >= 0 ? currentContentStream.substring(btEnd) : + currentContentStream.substring(0, bmcEnd + 3); + + // marked content which we will use to try and find some data points. + //String markedContent = currentContentStream.substring(bmcStart, bmcEnd); + + // check for a bounding box definition + //Rectangle2D.Float bounds = findBoundRectangle(markedContent); + + // finally build out the new content stream + StringBuilder content = new StringBuilder(); + // apply font + if (fieldDictionary.getDefaultAppearance() != null) { + String markedContent = fieldDictionary.getDefaultAppearance(); + Page page = getPage(); + markedContent = fieldDictionary.generateDefaultAppearance(markedContent, + page != null ? page.getResources() : null); + content.append(markedContent).append(' '); + } else { // common font and colour layout for most form elements. + content.append("/Helv 12 Tf 0 g "); + } + // apply the text offset, 4 is just a generic padding. + content.append(4).append(' ').append(4).append(" Td "); + // hex encode the text so that we better handle character codes > 127 + content = encodeHexString(content, selectedField).append(" Tj "); + // build the final content stream. + if (btStart >= 0) { + currentContentStream = preBmc + "\n" + content + "\n" + postEmc; + } else { + currentContentStream = preBmc + " BT\n" + content + "\n ET EMC"; + } + + return currentContentStream; + } + + public String buildChoiceListContents(String currentContentStream) { + + ArrayList choices = fieldDictionary.getOptions(); + // double check we have some choices to work with. + if (choices == null) { + // generate them from the content stream. + choices = generateChoices(); + fieldDictionary.setOptions(choices); + } + ArrayList selections = fieldDictionary.getIndexes(); + // mark the indexes of the mark content. + int bmcStart = currentContentStream.indexOf("BMC") + 3; + int bmcEnd = currentContentStream.indexOf("EMC"); + // grab the pre post marked content postscript. + String preBmc = currentContentStream.substring(0, bmcStart); + String postEmc = currentContentStream.substring(bmcEnd); + // marked content which we will use to try and find some data points. + String markedContent = currentContentStream.substring(bmcStart, bmcEnd); + + // check for a bounding box definition + Rectangle2D.Float bounds = findBoundRectangle(markedContent); + + // check to see if there is a selection box colour defined. + float[] selectionColor = findSelectionColour(markedContent); + + // and finally look for a previous selection box, this can be null, no default value + Rectangle2D.Float selectionRectangle = findSelectionRectangle(markedContent); + float lineHeight = 13.87f; + if (selectionRectangle != null) { + lineHeight = selectionRectangle.height; + } + + // we need to plot out where the opt text is going to go as well as the background colour and text colour + // for any selected items. So we update the choices model to reflect the current selection state. + boolean isSelection = false; + if (selections != null) { + for (int i = 0, max = choices.size(); i < max; i++) { + for (int selection : selections) { + if (selection == i) { + choices.get(i).setIsSelected(true); + isSelection = true; + } else { + choices.get(i).setIsSelected(false); + } + } + } + } + // figure out offset range to insure a single selection is always visible + int startIndex = 0, endIndex = choices.size(); + if (selections != null && selections.size() == 1) { + int numberLines = (int) Math.floor(bounds.height / lineHeight); + // check if list is smaller then number of lines + int selectedIndex = selections.get(0); + if (choices.size() < numberLines) { + // nothing to do. + } else if (selectedIndex < numberLines) { + endIndex = numberLines + 1; + } + // check for bottom out range + else if (endIndex - selectedIndex <= numberLines) { + startIndex = endIndex - numberLines; + } + // else mid range just need to start the index. + else { + startIndex = selectedIndex; + endIndex = numberLines + 1; + + } + // we have a single line + if (startIndex > endIndex) { + endIndex = startIndex + 1; + } + } + + // finally build out the new content stream + StringBuilder content = new StringBuilder(); + // bounding rectangle. + content.append("q ").append(generateRectangle(bounds)).append("W n "); + // apply selection highlight background. + if (isSelection) { + // apply colour + content.append(selectionColor[0]).append(' ').append(selectionColor[1]).append(' ') + .append(selectionColor[2]).append(" rg "); + // apply selection + Rectangle2D.Float firstSelection; + if (selectionRectangle == null) { + firstSelection = new Rectangle2D.Float(bounds.x, bounds.y + bounds.height - lineHeight, bounds.width, lineHeight); + } else { + firstSelection = new Rectangle2D.Float(selectionRectangle.x, bounds.y + bounds.height - lineHeight, + selectionRectangle.width, lineHeight); + } + ChoiceFieldDictionary.ChoiceOption choice; + for (int i = startIndex; i < endIndex; i++) { + choice = choices.get(i); + // check if a selection rectangle was defined, if not we might have a custom style and we + // avoid the selection background (only have one test case for this) + if (choice.isSelected() && selectionRectangle != null) { + content.append(generateRectangle(firstSelection)).append("f "); + } + firstSelection.y -= lineHeight; + } + } + // apply the ext. + content.append("BT "); + // apply font + if (fieldDictionary.getDefaultAppearance() != null) { + content.append(fieldDictionary.getDefaultAppearance()); + } else { // common font and colour layout for most form elements. + content.append("/Helv 12 Tf 0 g "); + } + // apply the line height + content.append(lineHeight).append(" TL "); + // apply the text offset, 4 is just a generic padding. + content.append(4).append(' ').append(bounds.height + 4).append(" Td "); + // print out text + ChoiceFieldDictionary.ChoiceOption choice; + for (int i = startIndex; i < endIndex; i++) { + choice = choices.get(i); + if (choice.isSelected() && selectionRectangle != null) { + content.append("1 g "); + } else { + content.append("0 g "); + } + content.append('(').append(choice.getLabel()).append(")' "); + } + content.append("ET Q"); + // build the final content stream. + currentContentStream = preBmc + "\n" + content + "\n" + postEmc; + return currentContentStream; + } + + /** + * The selection colour is generally defined in DeviceRGB and occurs after the bounding box has been defined. + * This utility method tries to parse out the colour information and return it in float[3]. If the data can't + * be found then we return the default colour of new float[]{0.03922f, 0.14118f, 0.41569f}. + * + * @param markedContent content to look for colour info. + * @return found colour data or new float[]{0.03922f, 0.14118f, 0.41569f}. + */ + private float[] findSelectionColour(String markedContent) { + int selectionStart = markedContent.indexOf("n") + 1; + int selectionEnd = markedContent.lastIndexOf("rg"); + if (selectionStart < selectionEnd && selectionEnd > 0) { + String potentialNumbers = markedContent.substring(selectionStart, selectionEnd); + StringTokenizer toker = new StringTokenizer(potentialNumbers); + float[] points = new float[3]; + int i = 0; + while (toker.hasMoreTokens()) { + try { + float tmp = Float.parseFloat(toker.nextToken()); + points[i] = tmp; + i++; + } catch (NumberFormatException e) { + break; + } + } + if (i == 3) { + return points; + } + } + // default selection colour. + return new float[]{0.03922f, 0.14118f, 0.41569f}; + } + +} + diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/CircleAnnotation.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/CircleAnnotation.java index 17809ae0c..65085582a 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/CircleAnnotation.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/CircleAnnotation.java @@ -139,7 +139,7 @@ public static CircleAnnotation getInstance(Library library, /** * Resets the annotations appearance stream. */ - public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform) { + public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform, boolean isNew) { // grab the current appearance stream as we'll be updating the shapes. Appearance appearance = appearances.get(currentAppearance); AppearanceState appearanceState = appearance.getSelectedAppearanceState(); @@ -200,7 +200,7 @@ public void resetAppearanceStream(double dx, double dy, AffineTransform pageTran // update the appearance stream // create/update the appearance stream of the xObject. Form form = updateAppearanceStream(shapes, bbox, matrix, - PostScriptEncoder.generatePostScript(shapes.getShapes())); + PostScriptEncoder.generatePostScript(shapes.getShapes()), isNew); generateExternalGraphicsState(form, opacity); } diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/FreeTextAnnotation.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/FreeTextAnnotation.java index ba069c387..8661214a1 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/FreeTextAnnotation.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/FreeTextAnnotation.java @@ -398,7 +398,7 @@ public void render(Graphics2D origG, int renderHintType, float totalRotation, fl } @Override - public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform) { + public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform, boolean isNew) { Appearance appearance = appearances.get(currentAppearance); AppearanceState appearanceState = appearance.getSelectedAppearanceState(); @@ -532,7 +532,7 @@ public void resetAppearanceStream(double dx, double dy, AffineTransform pageTran // create/update the appearance stream of the xObject. StateManager stateManager = library.getStateManager(); Form form = updateAppearanceStream(shapes, bbox, matrix, - PostScriptEncoder.generatePostScript(shapes.getShapes())); + PostScriptEncoder.generatePostScript(shapes.getShapes()), isNew); generateExternalGraphicsState(form, opacity); if (form != null) { diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/GenericAnnotation.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/GenericAnnotation.java index 871ba8fec..98b2f576c 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/GenericAnnotation.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/GenericAnnotation.java @@ -1,45 +1,45 @@ -/* - * Copyright 2006-2019 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.util.Library; - -import java.awt.geom.AffineTransform; -import java.util.HashMap; - -/** - * Generic annotation that instantiated when an annotation subtype is not - * recognized. - * - * @since 5.0 - */ -public class GenericAnnotation extends Annotation { - - /** - * Creates a new instance of an Annotation. - * - * @param l document library. - * @param h dictionary entries. - */ - public GenericAnnotation(Library l, HashMap h) { - super(l, h); - } - - @Override - public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform) { - - } -} +/* + * Copyright 2006-2019 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.util.Library; + +import java.awt.geom.AffineTransform; +import java.util.HashMap; + +/** + * Generic annotation that instantiated when an annotation subtype is not + * recognized. + * + * @since 5.0 + */ +public class GenericAnnotation extends Annotation { + + /** + * Creates a new instance of an Annotation. + * + * @param l document library. + * @param h dictionary entries. + */ + public GenericAnnotation(Library l, HashMap h) { + super(l, h); + } + + @Override + public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform, boolean isNew) { + + } +} diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/InkAnnotation.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/InkAnnotation.java index 4984aa388..cc2f1b711 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/InkAnnotation.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/InkAnnotation.java @@ -178,7 +178,7 @@ public static InkAnnotation getInstance(Library library, /** * Resets the annotations appearance stream. */ - public void resetAppearanceStream(double dx, double dy, AffineTransform pageSpace) { + public void resetAppearanceStream(double dx, double dy, AffineTransform pageSpace, boolean isNew) { // setup clean shapes Appearance appearance = appearances.get(currentAppearance); @@ -234,7 +234,7 @@ public void resetAppearanceStream(double dx, double dy, AffineTransform pageSpac // mark the change. StateManager stateManager = library.getStateManager(); - stateManager.addChange(new PObject(this, this.getPObjectReference())); + stateManager.addChange(new PObject(this, this.getPObjectReference()), isNew); } diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/LineAnnotation.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/LineAnnotation.java index 1d4cc30c7..0c33abb50 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/LineAnnotation.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/LineAnnotation.java @@ -548,7 +548,7 @@ public synchronized void init() throws InterruptedException { /** * Resets the annotations appearance stream. */ - public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform) { + public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform, boolean isNew) { // nothing to reset, creating new annotation. if (startOfLine == null || endOfLine == null) { @@ -648,7 +648,7 @@ public void resetAppearanceStream(double dx, double dy, AffineTransform pageTran // mark the change. StateManager stateManager = library.getStateManager(); - stateManager.addChange(new PObject(this, this.getPObjectReference())); + stateManager.addChange(new PObject(this, this.getPObjectReference()), isNew); } public Point2D getStartOfLine() { diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/LinkAnnotation.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/LinkAnnotation.java index 367103aab..23473225e 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/LinkAnnotation.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/LinkAnnotation.java @@ -210,7 +210,7 @@ public Destination getDestination() { } @Override - public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform) { + public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform, boolean isNew) { } } diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/PolyAnnotation.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/PolyAnnotation.java index 92cb2729c..bd3d5e06e 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/PolyAnnotation.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/PolyAnnotation.java @@ -88,7 +88,7 @@ public static boolean isPolyAnnotation(Name subType) { } @Override - public void resetAppearanceStream(double dx, double dy, AffineTransform pageSpace) { + public void resetAppearanceStream(double dx, double dy, AffineTransform pageSpace, boolean isNew) { } } diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/PopupAnnotation.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/PopupAnnotation.java index b4e7490a2..f2a2c9d99 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/PopupAnnotation.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/PopupAnnotation.java @@ -120,7 +120,7 @@ public static PopupAnnotation getInstance(Library library, } @Override - public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform) { + public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform, boolean isNew) { } diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/SignatureWidgetAnnotation.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/SignatureWidgetAnnotation.java index 45de62428..53d5af2fd 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/SignatureWidgetAnnotation.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/SignatureWidgetAnnotation.java @@ -1,84 +1,84 @@ -package org.icepdf.core.pobjects.annotations; - -import org.icepdf.core.pobjects.acroform.FieldDictionary; -import org.icepdf.core.pobjects.acroform.SignatureDictionary; -import org.icepdf.core.pobjects.acroform.SignatureFieldDictionary; -import org.icepdf.core.pobjects.acroform.SignatureHandler; -import org.icepdf.core.pobjects.acroform.signature.SignatureValidator; -import org.icepdf.core.util.Library; - -import java.awt.geom.AffineTransform; -import java.util.HashMap; -import java.util.logging.Logger; - -/** - * A digital signature (PDF 1.3) may be used to authenticate the identity of a user and the document's contents. It - * stores information about the signer and the state of the document when it was signed. The signature may be purely - * mathematical, such as a public/private-key encrypted document digest, or it may be a biometric form of identification, - * such as a handwritten signature, fingerprint, or retinal scan. The specific form of authentication used shall be - * implemented by a special software module called a signature handler. Signature handlers shall be identified in - * accordance with the rules defined in Annex E. - *
- * NOTE 2
- * The entries in the signature dictionary can be conceptualized as being in different dictionaries; they are in one - * dictionary for historical and cryptographic reasons. The categories are signature properties (R, M, Name, Reason, - * Location, Prop_Build, Prop_AuthTime, and Prop_AuthType); key information (Cert and portions of Contents when the - * signature value is a PKCS#7 object); reference (Reference and ByteRange); and signature value (Contents when the - * signature value is a PKCS#1 object). - */ -public class SignatureWidgetAnnotation extends AbstractWidgetAnnotation { - - private static final Logger logger = - Logger.getLogger(SignatureWidgetAnnotation.class.toString()); - - // signature field dictionary, - private SignatureFieldDictionary fieldDictionary; - - // signatures value holds all the signature info for signing. - private SignatureDictionary signatureDictionary; - - private SignatureValidator signatureValidator; - - public SignatureWidgetAnnotation(Library l, HashMap h) { - super(l, h); - fieldDictionary = new SignatureFieldDictionary(library, entries); - - HashMap valueDict = library.getDictionary(entries, FieldDictionary.V_KEY); - signatureDictionary = new SignatureDictionary(library, valueDict); - - } - - public SignatureValidator getSignatureValidator() { - if (signatureValidator == null) { - SignatureHandler signatureHandler = fieldDictionary.getLibrary().getSignatureHandler(); - signatureValidator = signatureHandler.validateSignature(fieldDictionary); - } - return signatureValidator; - } - - public SignatureWidgetAnnotation(Annotation widgetAnnotation) { - super(widgetAnnotation.getLibrary(), widgetAnnotation.getEntries()); - fieldDictionary = new SignatureFieldDictionary(library, entries); - // copy over the reference number. - setPObjectReference(widgetAnnotation.getPObjectReference()); - } - - public SignatureDictionary getSignatureDictionary() { - return signatureDictionary; - } - - @Override - public void reset() { - - } - - @Override - public void resetAppearanceStream(double dx, double dy, AffineTransform pageSpace) { - - } - - @Override - public SignatureFieldDictionary getFieldDictionary() { - return fieldDictionary; - } -} +package org.icepdf.core.pobjects.annotations; + +import org.icepdf.core.pobjects.acroform.FieldDictionary; +import org.icepdf.core.pobjects.acroform.SignatureDictionary; +import org.icepdf.core.pobjects.acroform.SignatureFieldDictionary; +import org.icepdf.core.pobjects.acroform.SignatureHandler; +import org.icepdf.core.pobjects.acroform.signature.SignatureValidator; +import org.icepdf.core.util.Library; + +import java.awt.geom.AffineTransform; +import java.util.HashMap; +import java.util.logging.Logger; + +/** + * A digital signature (PDF 1.3) may be used to authenticate the identity of a user and the document's contents. It + * stores information about the signer and the state of the document when it was signed. The signature may be purely + * mathematical, such as a public/private-key encrypted document digest, or it may be a biometric form of identification, + * such as a handwritten signature, fingerprint, or retinal scan. The specific form of authentication used shall be + * implemented by a special software module called a signature handler. Signature handlers shall be identified in + * accordance with the rules defined in Annex E. + *
+ * NOTE 2
+ * The entries in the signature dictionary can be conceptualized as being in different dictionaries; they are in one + * dictionary for historical and cryptographic reasons. The categories are signature properties (R, M, Name, Reason, + * Location, Prop_Build, Prop_AuthTime, and Prop_AuthType); key information (Cert and portions of Contents when the + * signature value is a PKCS#7 object); reference (Reference and ByteRange); and signature value (Contents when the + * signature value is a PKCS#1 object). + */ +public class SignatureWidgetAnnotation extends AbstractWidgetAnnotation { + + private static final Logger logger = + Logger.getLogger(SignatureWidgetAnnotation.class.toString()); + + // signature field dictionary, + private SignatureFieldDictionary fieldDictionary; + + // signatures value holds all the signature info for signing. + private SignatureDictionary signatureDictionary; + + private SignatureValidator signatureValidator; + + public SignatureWidgetAnnotation(Library l, HashMap h) { + super(l, h); + fieldDictionary = new SignatureFieldDictionary(library, entries); + + HashMap valueDict = library.getDictionary(entries, FieldDictionary.V_KEY); + signatureDictionary = new SignatureDictionary(library, valueDict); + + } + + public SignatureValidator getSignatureValidator() { + if (signatureValidator == null) { + SignatureHandler signatureHandler = fieldDictionary.getLibrary().getSignatureHandler(); + signatureValidator = signatureHandler.validateSignature(fieldDictionary); + } + return signatureValidator; + } + + public SignatureWidgetAnnotation(Annotation widgetAnnotation) { + super(widgetAnnotation.getLibrary(), widgetAnnotation.getEntries()); + fieldDictionary = new SignatureFieldDictionary(library, entries); + // copy over the reference number. + setPObjectReference(widgetAnnotation.getPObjectReference()); + } + + public SignatureDictionary getSignatureDictionary() { + return signatureDictionary; + } + + @Override + public void reset() { + + } + + @Override + public void resetAppearanceStream(double dx, double dy, AffineTransform pageSpace, boolean isNew) { + + } + + @Override + public SignatureFieldDictionary getFieldDictionary() { + return fieldDictionary; + } +} diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/SquareAnnotation.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/SquareAnnotation.java index 3e7f1fb31..f9109f741 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/SquareAnnotation.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/SquareAnnotation.java @@ -137,7 +137,7 @@ public static SquareAnnotation getInstance(Library library, /** * Resets the annotations appearance stream. */ - public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform) { + public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform, boolean isNew) { // grab the current appearance stream as we'll be updating the shapes. Appearance appearance = appearances.get(currentAppearance); AppearanceState appearanceState = appearance.getSelectedAppearanceState(); @@ -188,7 +188,7 @@ public void resetAppearanceStream(double dx, double dy, AffineTransform pageTran // update the appearance stream // create/update the appearance stream of the xObject. Form form = updateAppearanceStream(shapes, bbox, matrix, - PostScriptEncoder.generatePostScript(shapes.getShapes())); + PostScriptEncoder.generatePostScript(shapes.getShapes()), isNew); generateExternalGraphicsState(form, opacity); } diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/TextAnnotation.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/TextAnnotation.java index b07a5045e..df6fdbcb0 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/TextAnnotation.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/TextAnnotation.java @@ -203,7 +203,7 @@ public static TextAnnotation getInstance(Library library, /** * Resets the annotations appearance stream. */ - public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform) { + public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform, boolean isNew) { // setup the context Appearance appearance = appearances.get(currentAppearance); AppearanceState appearanceState = appearance.getSelectedAppearanceState(); @@ -271,7 +271,7 @@ public void resetAppearanceStream(double dx, double dy, AffineTransform pageTran MessageFormat formatter = new MessageFormat(iconContentString); iconContentString = formatter.format(colorArgument); - Form form = updateAppearanceStream(null, bbox, matrix, null); + Form form = updateAppearanceStream(null, bbox, matrix, null, isNew); generateExternalGraphicsState(form, opacity); // parse the shapes and assign to this instance try { @@ -285,7 +285,7 @@ public void resetAppearanceStream(double dx, double dy, AffineTransform pageTran // update the appearance stream // create/update the appearance stream of the xObject. - form = updateAppearanceStream(shapes, bbox, matrix, iconContentString.getBytes()); + form = updateAppearanceStream(shapes, bbox, matrix, iconContentString.getBytes(), isNew); // generateExternalGraphicsState(form, opacity); if (form != null) { appearanceState.setShapes(shapes); diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/TextMarkupAnnotation.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/TextMarkupAnnotation.java index ed94e8e24..868406668 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/TextMarkupAnnotation.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/TextMarkupAnnotation.java @@ -246,7 +246,7 @@ public static boolean isTextMarkupAnnotation(Name subType) { /** * Resets the annotations appearance stream. */ - public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform) { + public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform, boolean isNew) { // check if we have anything to reset. if (markupBounds == null) { @@ -330,7 +330,7 @@ public void resetAppearanceStream(double dx, double dy, AffineTransform pageTran // update the appearance stream // create/update the appearance stream of the xObject. Form form = updateAppearanceStream(shapes, bbox, matrix, - PostScriptEncoder.generatePostScript(shapes.getShapes())); + PostScriptEncoder.generatePostScript(shapes.getShapes()), isNew); generateExternalGraphicsState(form, opacity); } diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/TextWidgetAnnotation.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/TextWidgetAnnotation.java index 11b6e9cba..af432d256 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/TextWidgetAnnotation.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/TextWidgetAnnotation.java @@ -1,224 +1,224 @@ -/* - * Copyright 2006-2019 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.acroform.FieldDictionary; -import org.icepdf.core.pobjects.acroform.TextFieldDictionary; -import org.icepdf.core.pobjects.acroform.VariableTextFieldDictionary; -import org.icepdf.core.pobjects.fonts.FontFile; -import org.icepdf.core.pobjects.fonts.FontManager; -import org.icepdf.core.util.Library; - -import java.awt.geom.AffineTransform; -import java.awt.geom.Rectangle2D; -import java.util.HashMap; -import java.util.logging.Level; - -/** - * Text field (field type Text) is a box or space for text fill-in data typically - * entered from a keyboard. The text may be restricted to a single line or may - * be permitted to span multiple lines, depending on the setting of the Multi line - * flag in the field dictionary’s Ff entry. Table 228 shows the flags pertaining - * to this type of field. A text field shall have a field type of Text. A conforming - * PDF file, and a conforming processor shall obey the usage guidelines as - * defined by the big flags below. - * - * @since 5.1 - */ -public class TextWidgetAnnotation extends AbstractWidgetAnnotation { - - protected FontFile fontFile; - - private TextFieldDictionary fieldDictionary; - - public TextWidgetAnnotation(Library l, HashMap h) { - super(l, h); - fieldDictionary = new TextFieldDictionary(library, entries); - fontFile = fieldDictionary.getFont() != null ? fieldDictionary.getFont().getFont() : null; - if (fontFile == null) { - fontFile = FontManager.getInstance().initialize().getInstance( - fieldDictionary.getFontName().toString(), 0); - } - } - - public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform) { - - // we won't touch password fields, we'll used the original display - TextFieldDictionary.TextFieldType textFieldType = fieldDictionary.getTextFieldType(); - if (textFieldType == TextFieldDictionary.TextFieldType.TEXT_PASSWORD) { - // nothing to do, let the password comp handle the look. - } else { - // get at the original postscript as well alter the marked content - Appearance appearance = appearances.get(currentAppearance); - AppearanceState appearanceState = appearance.getSelectedAppearanceState(); - Rectangle2D bbox = appearanceState.getBbox(); - // putting in identity, as we a trump any cm in the annotation stream. - AffineTransform matrix = new AffineTransform();//appearanceState.getMatrix(); - String currentContentStream = appearanceState.getOriginalContentStream(); - currentContentStream = buildTextWidgetContents(currentContentStream); - - // finally create the shapes from the altered stream. - if (currentContentStream != null) { - appearanceState.setContentStream(currentContentStream.getBytes()); - } - - // some widgets don't have AP dictionaries in such a case we need to create the form object - // and build out the default properties. - Form appearanceStream = getOrGenerateAppearanceForm(); - - if (appearanceStream != null) { - // update the content stream with the new stream data. - appearanceStream.setRawBytes(currentContentStream.getBytes()); - // add the appearance stream - StateManager stateManager = library.getStateManager(); - stateManager.addChange(new PObject(appearanceStream, appearanceStream.getPObjectReference())); - // add an AP entry for the - HashMap appearanceRefs = new HashMap<>(); - appearanceRefs.put(APPEARANCE_STREAM_NORMAL_KEY, appearanceStream.getPObjectReference()); - entries.put(APPEARANCE_STREAM_KEY, appearanceRefs); - Rectangle2D formBbox = new Rectangle2D.Float( - (float) bbox.getX(), (float) bbox.getY(), (float) bbox.getWidth(), (float) bbox.getHeight()); - appearanceStream.setAppearance(null, matrix, formBbox); - // add link to resources on forum, if no resources exist. - if (library.getResources(appearanceStream.getEntries(), Form.RESOURCES_KEY) == null && - library.getCatalog().getInteractiveForm().getResources() != null) { - appearanceStream.getEntries().put(Form.RESOURCES_KEY, - library.getCatalog().getInteractiveForm().getResources().getEntries()); - } else { - // need to find some resources, try adding the parent page. - Page page = getPage(); - if (page != null && page.getResources() != null) { - appearanceStream.getEntries().put(Form.RESOURCES_KEY, page.getResources().getEntries()); - } - } - // add the annotation as changed as T entry has also been updated to reflect teh changed content. - stateManager.addChange(new PObject(this, this.getPObjectReference())); - - // compress the form object stream. - if (compressAppearanceStream) { - appearanceStream.getEntries().put(Stream.FILTER_KEY, new Name("FlateDecode")); - } else { - appearanceStream.getEntries().remove(Stream.FILTER_KEY); - } - try { - appearanceStream.init(); - } catch (InterruptedException e) { - logger.log(Level.WARNING, "Could not initialized TextWidgetAnnotation", e); - } - } - } - } - - public String buildTextWidgetContents(String currentContentStream) { - - // text widgets can be null, in this case we setup the default so we can add our own data. - if (currentContentStream == null || currentContentStream.equals("")) { - currentContentStream = " /Tx BMC q BT ET Q EMC"; - } - String contents = (String) fieldDictionary.getFieldValue(); -// int btStart = currentContentStream.indexOf("BT") + 2; -// int etEnd = currentContentStream.lastIndexOf("ET"); - int btStart = currentContentStream.indexOf("BMC") + 3; - int etEnd = currentContentStream.lastIndexOf("EMC"); - - String preBt = ""; - String postEt = ""; - String markedContent = ""; - if (btStart >= 0 && etEnd >= 0) { - // grab the pre post marked content postscript. - preBt = currentContentStream.substring(0, btStart) + " BT "; - postEt = "ET " + currentContentStream.substring(etEnd); - // marked content which we will use to try and find some data points. - markedContent = currentContentStream.substring(btStart, etEnd); - } else { - preBt = "/Tx BMC q BT "; - postEt = " ET Q EMC "; - } - - // check for a bounding box definition - Rectangle2D.Float bounds = findRectangle(preBt); - boolean isfourthQuadrant = false; - if (bounds != null && bounds.getHeight() < 0) { - isfourthQuadrant = true; - } - - // finally build out the new content stream - StringBuilder content = new StringBuilder(); - // calculate line light - double lineHeight = getLineHeight(fieldDictionary.getDefaultAppearance()); - - // apply the default appearance. - Page parentPage = getPage(); - content.append(generateDefaultAppearance(markedContent, - parentPage != null?parentPage.getResources():null, fieldDictionary)); - if (fieldDictionary.getDefaultAppearance() == null) { - lineHeight = getFontSize(markedContent); - } - - // apply the text offset, 4 is just a generic padding. - if (!isfourthQuadrant) { - double height = getBbox().getHeight(); - double size = fieldDictionary.getSize(); - content.append(lineHeight).append(" TL "); - // todo rework taking into account multi line height. - double hOffset = Math.ceil(size + (height - size)); - content.append(2).append(' ').append(hOffset).append(" Td "); - } else { - content.append(2).append(' ').append(2).append(" Td "); - } - // encode the text so it can be properly encoded in PDF string format - // hex encode the text so that we better handle character codes > 127 - content = encodeHexString(content, contents); - - // build the final content stream. - currentContentStream = preBt + content + postEt; - return currentContentStream; - } - - - public void reset() { - // set the fields value (V) to the default value defined by the DV key. - Object oldValue = fieldDictionary.getFieldValue(); - Object tmp = fieldDictionary.getDefaultFieldValue(); - if (tmp != null) { - // apply the default value - fieldDictionary.setFieldValue(fieldDictionary.getDefaultFieldValue(), getPObjectReference()); - changeSupport.firePropertyChange("valueFieldReset", oldValue, fieldDictionary.getFieldValue()); - } else { - // otherwise we remove the key - fieldDictionary.getEntries().remove(FieldDictionary.V_KEY); - fieldDictionary.setFieldValue("", getPObjectReference()); - if (changeSupport != null) { - changeSupport.firePropertyChange("valueFieldReset", oldValue, ""); - } - } - } - - @Override - public TextFieldDictionary getFieldDictionary() { - return fieldDictionary; - } - - public String generateDefaultAppearance(String content, Resources resources, - VariableTextFieldDictionary variableTextFieldDictionary) { - if (variableTextFieldDictionary != null) { - return variableTextFieldDictionary.generateDefaultAppearance(content, resources); - } - return null; - } -} +/* + * Copyright 2006-2019 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.acroform.FieldDictionary; +import org.icepdf.core.pobjects.acroform.TextFieldDictionary; +import org.icepdf.core.pobjects.acroform.VariableTextFieldDictionary; +import org.icepdf.core.pobjects.fonts.FontFile; +import org.icepdf.core.pobjects.fonts.FontManager; +import org.icepdf.core.util.Library; + +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.util.HashMap; +import java.util.logging.Level; + +/** + * Text field (field type Text) is a box or space for text fill-in data typically + * entered from a keyboard. The text may be restricted to a single line or may + * be permitted to span multiple lines, depending on the setting of the Multi line + * flag in the field dictionary’s Ff entry. Table 228 shows the flags pertaining + * to this type of field. A text field shall have a field type of Text. A conforming + * PDF file, and a conforming processor shall obey the usage guidelines as + * defined by the big flags below. + * + * @since 5.1 + */ +public class TextWidgetAnnotation extends AbstractWidgetAnnotation { + + protected FontFile fontFile; + + private TextFieldDictionary fieldDictionary; + + public TextWidgetAnnotation(Library l, HashMap h) { + super(l, h); + fieldDictionary = new TextFieldDictionary(library, entries); + fontFile = fieldDictionary.getFont() != null ? fieldDictionary.getFont().getFont() : null; + if (fontFile == null) { + fontFile = FontManager.getInstance().initialize().getInstance( + fieldDictionary.getFontName().toString(), 0); + } + } + + public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform, boolean isNew) { + + // we won't touch password fields, we'll used the original display + TextFieldDictionary.TextFieldType textFieldType = fieldDictionary.getTextFieldType(); + if (textFieldType == TextFieldDictionary.TextFieldType.TEXT_PASSWORD) { + // nothing to do, let the password comp handle the look. + } else { + // get at the original postscript as well alter the marked content + Appearance appearance = appearances.get(currentAppearance); + AppearanceState appearanceState = appearance.getSelectedAppearanceState(); + Rectangle2D bbox = appearanceState.getBbox(); + // putting in identity, as we a trump any cm in the annotation stream. + AffineTransform matrix = new AffineTransform();//appearanceState.getMatrix(); + String currentContentStream = appearanceState.getOriginalContentStream(); + currentContentStream = buildTextWidgetContents(currentContentStream); + + // finally create the shapes from the altered stream. + if (currentContentStream != null) { + appearanceState.setContentStream(currentContentStream.getBytes()); + } + + // some widgets don't have AP dictionaries in such a case we need to create the form object + // and build out the default properties. + Form appearanceStream = getOrGenerateAppearanceForm(); + + if (appearanceStream != null) { + // update the content stream with the new stream data. + appearanceStream.setRawBytes(currentContentStream.getBytes()); + // add the appearance stream + StateManager stateManager = library.getStateManager(); + stateManager.addChange(new PObject(appearanceStream, appearanceStream.getPObjectReference())); + // add an AP entry for the + HashMap appearanceRefs = new HashMap<>(); + appearanceRefs.put(APPEARANCE_STREAM_NORMAL_KEY, appearanceStream.getPObjectReference()); + entries.put(APPEARANCE_STREAM_KEY, appearanceRefs); + Rectangle2D formBbox = new Rectangle2D.Float( + (float) bbox.getX(), (float) bbox.getY(), (float) bbox.getWidth(), (float) bbox.getHeight()); + appearanceStream.setAppearance(null, matrix, formBbox); + // add link to resources on forum, if no resources exist. + if (library.getResources(appearanceStream.getEntries(), Form.RESOURCES_KEY) == null && + library.getCatalog().getInteractiveForm().getResources() != null) { + appearanceStream.getEntries().put(Form.RESOURCES_KEY, + library.getCatalog().getInteractiveForm().getResources().getEntries()); + } else { + // need to find some resources, try adding the parent page. + Page page = getPage(); + if (page != null && page.getResources() != null) { + appearanceStream.getEntries().put(Form.RESOURCES_KEY, page.getResources().getEntries()); + } + } + // add the annotation as changed as T entry has also been updated to reflect teh changed content. + stateManager.addChange(new PObject(this, this.getPObjectReference())); + + // compress the form object stream. + if (compressAppearanceStream) { + appearanceStream.getEntries().put(Stream.FILTER_KEY, new Name("FlateDecode")); + } else { + appearanceStream.getEntries().remove(Stream.FILTER_KEY); + } + try { + appearanceStream.init(); + } catch (InterruptedException e) { + logger.log(Level.WARNING, "Could not initialized TextWidgetAnnotation", e); + } + } + } + } + + public String buildTextWidgetContents(String currentContentStream) { + + // text widgets can be null, in this case we setup the default so we can add our own data. + if (currentContentStream == null || currentContentStream.equals("")) { + currentContentStream = " /Tx BMC q BT ET Q EMC"; + } + String contents = (String) fieldDictionary.getFieldValue(); +// int btStart = currentContentStream.indexOf("BT") + 2; +// int etEnd = currentContentStream.lastIndexOf("ET"); + int btStart = currentContentStream.indexOf("BMC") + 3; + int etEnd = currentContentStream.lastIndexOf("EMC"); + + String preBt = ""; + String postEt = ""; + String markedContent = ""; + if (btStart >= 0 && etEnd >= 0) { + // grab the pre post marked content postscript. + preBt = currentContentStream.substring(0, btStart) + " BT "; + postEt = "ET " + currentContentStream.substring(etEnd); + // marked content which we will use to try and find some data points. + markedContent = currentContentStream.substring(btStart, etEnd); + } else { + preBt = "/Tx BMC q BT "; + postEt = " ET Q EMC "; + } + + // check for a bounding box definition + Rectangle2D.Float bounds = findRectangle(preBt); + boolean isfourthQuadrant = false; + if (bounds != null && bounds.getHeight() < 0) { + isfourthQuadrant = true; + } + + // finally build out the new content stream + StringBuilder content = new StringBuilder(); + // calculate line light + double lineHeight = getLineHeight(fieldDictionary.getDefaultAppearance()); + + // apply the default appearance. + Page parentPage = getPage(); + content.append(generateDefaultAppearance(markedContent, + parentPage != null?parentPage.getResources():null, fieldDictionary)); + if (fieldDictionary.getDefaultAppearance() == null) { + lineHeight = getFontSize(markedContent); + } + + // apply the text offset, 4 is just a generic padding. + if (!isfourthQuadrant) { + double height = getBbox().getHeight(); + double size = fieldDictionary.getSize(); + content.append(lineHeight).append(" TL "); + // todo rework taking into account multi line height. + double hOffset = Math.ceil(size + (height - size)); + content.append(2).append(' ').append(hOffset).append(" Td "); + } else { + content.append(2).append(' ').append(2).append(" Td "); + } + // encode the text so it can be properly encoded in PDF string format + // hex encode the text so that we better handle character codes > 127 + content = encodeHexString(content, contents); + + // build the final content stream. + currentContentStream = preBt + content + postEt; + return currentContentStream; + } + + + public void reset() { + // set the fields value (V) to the default value defined by the DV key. + Object oldValue = fieldDictionary.getFieldValue(); + Object tmp = fieldDictionary.getDefaultFieldValue(); + if (tmp != null) { + // apply the default value + fieldDictionary.setFieldValue(fieldDictionary.getDefaultFieldValue(), getPObjectReference()); + changeSupport.firePropertyChange("valueFieldReset", oldValue, fieldDictionary.getFieldValue()); + } else { + // otherwise we remove the key + fieldDictionary.getEntries().remove(FieldDictionary.V_KEY); + fieldDictionary.setFieldValue("", getPObjectReference()); + if (changeSupport != null) { + changeSupport.firePropertyChange("valueFieldReset", oldValue, ""); + } + } + } + + @Override + public TextFieldDictionary getFieldDictionary() { + return fieldDictionary; + } + + public String generateDefaultAppearance(String content, Resources resources, + VariableTextFieldDictionary variableTextFieldDictionary) { + if (variableTextFieldDictionary != null) { + return variableTextFieldDictionary.generateDefaultAppearance(content, resources); + } + return null; + } +} diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/WidgetAnnotation.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/WidgetAnnotation.java index ad8aa12c0..bd9f179ed 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/WidgetAnnotation.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/WidgetAnnotation.java @@ -1,59 +1,59 @@ -/* - * Copyright 2006-2019 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.acroform.FieldDictionary; -import org.icepdf.core.util.Library; - -import java.awt.geom.AffineTransform; -import java.util.HashMap; - -/** - * Interactive forms (see 12.7, "Interactive Forms") use widget annotations (PDF 1.2) - * to represent the appearance of fields and to manage user interactions. As a - * convenience, when a field has only a single associated widget annotation, the - * contents of the field dictionary (12.7.3, "Field Dictionaries") and the - * annotation dictionary may be merged into a single dictionary containing - * entries that pertain to both a field and an annotation. - * - * @since 5.0 - */ -public class WidgetAnnotation extends AbstractWidgetAnnotation { - - - private FieldDictionary fieldDictionary; - - - public WidgetAnnotation(Library l, HashMap h) { - super(l, h); - fieldDictionary = new FieldDictionary(library, entries); - } - - public void resetAppearanceStream(double dx, double dy, AffineTransform pageSpace) { - - } - - @Override - public void reset() { - - } - - @Override - public FieldDictionary getFieldDictionary() { - return fieldDictionary; - } -} +/* + * Copyright 2006-2019 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.acroform.FieldDictionary; +import org.icepdf.core.util.Library; + +import java.awt.geom.AffineTransform; +import java.util.HashMap; + +/** + * Interactive forms (see 12.7, "Interactive Forms") use widget annotations (PDF 1.2) + * to represent the appearance of fields and to manage user interactions. As a + * convenience, when a field has only a single associated widget annotation, the + * contents of the field dictionary (12.7.3, "Field Dictionaries") and the + * annotation dictionary may be merged into a single dictionary containing + * entries that pertain to both a field and an annotation. + * + * @since 5.0 + */ +public class WidgetAnnotation extends AbstractWidgetAnnotation { + + + private FieldDictionary fieldDictionary; + + + public WidgetAnnotation(Library l, HashMap h) { + super(l, h); + fieldDictionary = new FieldDictionary(library, entries); + } + + public void resetAppearanceStream(double dx, double dy, AffineTransform pageSpace, boolean isNew) { + + } + + @Override + public void reset() { + + } + + @Override + public FieldDictionary getFieldDictionary() { + return fieldDictionary; + } +} diff --git a/core/core-awt/src/main/java/org/icepdf/core/util/updater/IncrementalUpdater.java b/core/core-awt/src/main/java/org/icepdf/core/util/updater/IncrementalUpdater.java index a8344fcfc..bd51ae821 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/util/updater/IncrementalUpdater.java +++ b/core/core-awt/src/main/java/org/icepdf/core/util/updater/IncrementalUpdater.java @@ -30,7 +30,7 @@ public long appendIncrementalUpdate( StateManager stateManager = document.getStateManager(); PTrailer trailer = stateManager.getTrailer(); - if (!stateManager.isChanged()) { + if (stateManager.isNoChange()) { return 0L; } @@ -40,9 +40,9 @@ public long appendIncrementalUpdate( BaseWriter writer = new BaseWriter(trailer, securityManager, output, documentLength); writer.initializeWriters(); writer.writeNewLine(); - Iterator changes = stateManager.iteratorSortedByObjectNumber(); + Iterator changes = stateManager.iteratorSortedByObjectNumber(); while (changes.hasNext()) { - PObject pobject = changes.next(); + PObject pobject = changes.next().getPObject(); writer.writePObject(pobject); } diff --git a/examples/annotation/creation/src/main/java/org/icepdf/os/examples/annotation/creation/NewAnnotationPrePageLoad.java b/examples/annotation/creation/src/main/java/org/icepdf/os/examples/annotation/creation/NewAnnotationPrePageLoad.java index 03f0cc397..7be1061b8 100644 --- a/examples/annotation/creation/src/main/java/org/icepdf/os/examples/annotation/creation/NewAnnotationPrePageLoad.java +++ b/examples/annotation/creation/src/main/java/org/icepdf/os/examples/annotation/creation/NewAnnotationPrePageLoad.java @@ -161,7 +161,7 @@ public void run() { // add the action to the annotation linkAnnotation.addAction(action); // add it to the page. - page.addAnnotation(linkAnnotation); + page.addAnnotation(linkAnnotation, true); } } // removed the search highlighting diff --git a/logging.properties b/logging.properties index 5772e9db9..4e4838c81 100644 --- a/logging.properties +++ b/logging.properties @@ -21,11 +21,11 @@ java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter # Set the default logging level for the root logger #.level = OFF #.level = ALL -.level=FINER -#.level = INFO +#.level=FINER +.level=INFO # Set the default logging level for new ConsoleHandler instances -java.util.logging.ConsoleHandler.level=FINER -#java.util.logging.ConsoleHandler.level = INFO +#java.util.logging.ConsoleHandler.level=FINER +java.util.logging.ConsoleHandler.level=INFO # Set the default logging level for new FileHandler instances java.util.logging.FileHandler.level=OFF org.icepdf.core.util.parser.content.level=INFO diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/MyAnnotationCallback.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/MyAnnotationCallback.java index 5f574d37e..d1d21991e 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/MyAnnotationCallback.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/MyAnnotationCallback.java @@ -181,7 +181,7 @@ public void newAnnotation(PageViewComponent pageComponent, Document document = documentViewController.getDocument(); PageTree pageTree = document.getPageTree(); Page page = pageTree.getPage(pageComponent.getPageIndex()); - page.addAnnotation(annotationComponent.getAnnotation()); + page.addAnnotation(annotationComponent.getAnnotation(), !annotationComponent.isSynthetic()); // no we have let the pageComponent now about it. ((PageViewComponentImpl) pageComponent).addAnnotation(annotationComponent); diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingController.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingController.java index 521fb5564..1f98ceb58 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingController.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingController.java @@ -3491,7 +3491,7 @@ protected void saveFileChecks(String originalFileName, File file) { fileOutputStream, 4096 * 2); // We want 'save as' or 'save a copy to always occur - if (!document.getStateManager().isChanged()) { + if (document.getStateManager().isNoChange()) { // save as copy document.writeToOutputStream(buf); } else { @@ -3619,7 +3619,7 @@ public boolean saveChangesDialog() { // check if document changes have been made, if so ask the user if they // want to save the changes. if (document != null) { - boolean documentChanges = document.getStateManager().isChanged(); + boolean documentChanges = document.getStateManager().isChange(); if (documentChanges) { MessageFormat formatter = new MessageFormat( diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/tools/TextAnnotationHandler.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/tools/TextAnnotationHandler.java index 5a596e926..2fde49942 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/tools/TextAnnotationHandler.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/tools/TextAnnotationHandler.java @@ -156,7 +156,8 @@ public TextAnnotation createTextAnnotationInstance(Library library, Rectangle bb public static PopupAnnotation createPopupAnnotation(Library library, Rectangle bbox, MarkupAnnotation parent, - AffineTransform pageSpace) { + AffineTransform pageSpace, + boolean isNew) { // text annotation are special as the annotation has fixed size. PopupAnnotation popupAnnotation = (PopupAnnotation) AnnotationFactory.buildAnnotation( @@ -165,15 +166,14 @@ public static PopupAnnotation createPopupAnnotation(Library library, Rectangle b bbox); // save the annotation StateManager stateManager = library.getStateManager(); - stateManager.addChange(new PObject(popupAnnotation, - popupAnnotation.getPObjectReference())); + stateManager.addChange(new PObject(popupAnnotation, popupAnnotation.getPObjectReference()), isNew); library.addObject(popupAnnotation, popupAnnotation.getPObjectReference()); // setup up some default values popupAnnotation.setOpen(true); popupAnnotation.setParent(parent); parent.setPopupAnnotation(popupAnnotation); - popupAnnotation.resetAppearanceStream(0, 0, pageSpace); + popupAnnotation.resetAppearanceStream(0, 0, pageSpace, isNew); return popupAnnotation; } diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/AnnotationComponent.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/AnnotationComponent.java index a13441a2a..d2dd596cd 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/AnnotationComponent.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/AnnotationComponent.java @@ -1,144 +1,148 @@ -/* - * Copyright 2006-2019 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.ri.common.views; - -import org.icepdf.core.pobjects.Document; -import org.icepdf.core.pobjects.annotations.Annotation; - -/** - * AnnotationComponent interfaces. Outlines two main methods needed for - * management and state saving but avoids having to load the Swing/awt libraries - * unless necessary. - * - * @since 4.0 - */ -public interface AnnotationComponent { - - /** - * Gets wrapped annotation object. - * - * @return annotation that this component wraps. - */ - Annotation getAnnotation(); - - /** - * Refresh the annotations bounds rectangle. This method insures that - * the bounds have been correctly adjusted for the current page transformation - * In a none visual representation this method may not have to do anything. - */ - void refreshDirtyBounds(); - - /** - * Refreshed the annotation rectangle by inverting the components current - * bounds with the current page transformation. - */ - void refreshAnnotationRect(); - - /** - * Component has focus. - * - * @return true if has focus, false otherwise. - */ - boolean hasFocus(); - - /** - * Component is editable, contents can be updated in ui - * @return true if has editable, false otherwise. - */ - boolean isEditable(); - - /** - * Component is editable, contents can be updated in ui - * @return true if show invisible border, false otherwise. - */ - boolean isShowInvisibleBorder(); - - /** - * Component highlight/select border is draw on mouse over. - * @return true if is rollover, false otherwise. - */ - boolean isRollover(); - - /** - * Component is movable. - * @return true if movable, false otherwise. - */ - boolean isMovable(); - - /** - * Component is resizable. - * @return true if resizable, false otherwise. - */ - boolean isResizable(); - - /** - * border has defined style. - * - * @return true if has border style, false otherwise. - */ - boolean isBorderStyle(); - - /** - * Annotation is in a selected state. Used for drawing a highlighted state. - * - * @return is selected - */ - boolean isSelected(); - - /** - * Document that annotation belows too. - * - * @return document parent. - */ - Document getDocument(); - - /** - * Page index that annotation component resides on. - * - * @return page index of parent page - */ - int getPageIndex(); - - /** - * Sets the selected state - * - * @param selected selected state. - */ - void setSelected(boolean selected); - - /** - * Repaints this component - */ - void repaint(); - - /** - * Rest the annotation appearance stream. - */ - void resetAppearanceShapes(); - - /** - * Gets the parent page view that displays this component. - * - * @return parent component. - */ - PageViewComponent getPageViewComponent(); - - /** - * Dispose this component resources. - */ - void dispose(); - -} +/* + * Copyright 2006-2019 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.ri.common.views; + +import org.icepdf.core.pobjects.Document; +import org.icepdf.core.pobjects.annotations.Annotation; + +/** + * AnnotationComponent interfaces. Outlines two main methods needed for + * management and state saving but avoids having to load the Swing/awt libraries + * unless necessary. + * + * @since 4.0 + */ +public interface AnnotationComponent { + + /** + * Gets wrapped annotation object. + * + * @return annotation that this component wraps. + */ + Annotation getAnnotation(); + + /** + * Refresh the annotations bounds rectangle. This method insures that + * the bounds have been correctly adjusted for the current page transformation + * In a none visual representation this method may not have to do anything. + */ + void refreshDirtyBounds(); + + /** + * Refreshed the annotation rectangle by inverting the components current + * bounds with the current page transformation. + */ + void refreshAnnotationRect(); + + /** + * Component has focus. + * + * @return true if has focus, false otherwise. + */ + boolean hasFocus(); + + /** + * Component is editable, contents can be updated in ui + * @return true if has editable, false otherwise. + */ + boolean isEditable(); + + /** + * Component is editable, contents can be updated in ui + * @return true if show invisible border, false otherwise. + */ + boolean isShowInvisibleBorder(); + + /** + * Component highlight/select border is draw on mouse over. + * @return true if is rollover, false otherwise. + */ + boolean isRollover(); + + /** + * Component is movable. + * @return true if movable, false otherwise. + */ + boolean isMovable(); + + /** + * Component is resizable. + * @return true if resizable, false otherwise. + */ + boolean isResizable(); + + /** + * border has defined style. + * + * @return true if has border style, false otherwise. + */ + boolean isBorderStyle(); + + /** + * Annotation is in a selected state. Used for drawing a highlighted state. + * + * @return is selected + */ + boolean isSelected(); + + /** + * Document that annotation belows too. + * + * @return document parent. + */ + Document getDocument(); + + /** + * Page index that annotation component resides on. + * + * @return page index of parent page + */ + int getPageIndex(); + + /** + * Sets the selected state + * + * @param selected selected state. + */ + void setSelected(boolean selected); + + /** + * Repaints this component + */ + void repaint(); + + /** + * Rest the annotation appearance stream. + */ + void resetAppearanceShapes(); + + /** + * Gets the parent page view that displays this component. + * + * @return parent component. + */ + PageViewComponent getPageViewComponent(); + + /** + * Dispose this component resources. + */ + void dispose(); + + boolean isSynthetic(); + + void setSynthetic(boolean synthetic); + +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewControllerImpl.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewControllerImpl.java index d596c6c14..f23c3c0ac 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewControllerImpl.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/DocumentViewControllerImpl.java @@ -1331,6 +1331,8 @@ public void addNewAnnotation(AnnotationComponent annotationComponent) { public void updateAnnotation(AnnotationComponent annotationComponent) { if (documentViewModel != null && annotationComponent != null) { + // user initiated change, make sure to store the change + annotationComponent.setSynthetic(false); if (annotationCallback != null) { annotationCallback.updateAnnotation(annotationComponent); } @@ -1345,6 +1347,8 @@ public void updateAnnotation(AnnotationComponent annotationComponent) { public void updatedSummaryAnnotation(AnnotationComponent annotationComponent) { if (documentViewModel != null && annotationComponent != null) { + // user initiated change, make sure to store the change + annotationComponent.setSynthetic(false); if (annotationCallback != null) { annotationCallback.updateAnnotation(annotationComponent); } diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/AbstractAnnotationComponent.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/AbstractAnnotationComponent.java index fa7833bde..e2c79b5d6 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/AbstractAnnotationComponent.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/AbstractAnnotationComponent.java @@ -93,6 +93,9 @@ public abstract class AbstractAnnotationComponent extends } } + // created for rendering only, not created by the user, set when state manager shouldn't record the change + protected boolean isSynthetic; + public static final int resizeBoxSize = 4; // reusable border @@ -585,7 +588,7 @@ public void mouseReleased(MouseEvent mouseEvent) { dy = endOfMousePress.getY() - startOfMousePress.getY(); } - annotation.resetAppearanceStream(dx, -dy, getToPageSpaceTransform()); + annotation.resetAppearanceStream(dx, -dy, getToPageSpaceTransform(), true); // fire new bounds change event, let the listener handle // how to deal with the bound change. @@ -709,4 +712,12 @@ public boolean isResizable() { public boolean isShowInvisibleBorder() { return isShowInvisibleBorder; } + + public boolean isSynthetic() { + return isSynthetic; + } + + public void setSynthetic(boolean synthetic) { + isSynthetic = synthetic; + } } \ No newline at end of file diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/AnnotationState.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/AnnotationState.java index 89c16f278..cca478a78 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/AnnotationState.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/AnnotationState.java @@ -1,132 +1,132 @@ -/* - * Copyright 2006-2019 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.ri.common.views.annotations; - -import org.icepdf.core.Memento; -import org.icepdf.core.pobjects.Document; -import org.icepdf.core.pobjects.Page; -import org.icepdf.core.pobjects.PageTree; -import org.icepdf.core.pobjects.annotations.Annotation; -import org.icepdf.core.pobjects.annotations.BorderStyle; -import org.icepdf.ri.common.views.AnnotationComponent; - -import java.awt.*; -import java.awt.geom.Rectangle2D; - -/** - * Stores state parameters for annotation objects to be used in conjunction - * with a care taker as part of the memento pattern. - * - * @since 4.0 - */ -public class AnnotationState implements Memento { - - // annotation bounding rectangle in user space. - protected Rectangle2D.Float userSpaceRectangle; - - // original rectangle reference. - protected AnnotationComponent annotationComponent; - - /** - * Stores the annotation state associated with the AnnotationComponents - * annotation object. When a new instance of this object is created - * the annotation's proeprties are saved. - * - * @param annotationComponent annotation component who's state will be stored. - */ - public AnnotationState(AnnotationComponent annotationComponent) { - // reference to component so we can apply the state parameters if - // restore() is called. - this.annotationComponent = annotationComponent; - } - - - public void apply(AnnotationState applyState) { - - // store user space rectangle SpaceRectangle. - Rectangle2D.Float rect = applyState.userSpaceRectangle; - if (rect != null) { - userSpaceRectangle = new Rectangle2D.Float(rect.x, rect.y, - rect.width, rect.height); - } - - // apply the new state to the annotation and schedule a sync - restore(); - - } - - /** - * Restores the AnnotationComponents state to the state stored during the - * construction of this object. - */ - public void restore() { - if (annotationComponent != null && - annotationComponent.getAnnotation() != null) { - // get reference to annotation - Annotation annotation = annotationComponent.getAnnotation(); - - restore(annotation); - - // update the document with current state. - synchronizeState(); - } - } - - /** - * Restores the annotation state in this instance to the Annotation - * specified as a param. This method is ment to bue used in - * - * @param annotation annotation to retore state to. - */ - public void restore(Annotation annotation) { - // create a new Border style entry as an inline dictionary - if (annotation.getBorderStyle() == null) { - annotation.setBorderStyle(new BorderStyle()); - } - - // apply old user rectangle - annotation.setUserSpaceRectangle(userSpaceRectangle); - } - - public void synchronizeState() { - // update the document with this change. - int pageIndex = annotationComponent.getPageIndex(); - Document document = annotationComponent.getDocument(); - Annotation annotation = annotationComponent.getAnnotation(); - PageTree pageTree = document.getPageTree(); - Page page = pageTree.getPage(pageIndex); - // state behind draw state. - if (!annotation.isDeleted()) { - page.updateAnnotation(annotation); - // refresh bounds for any resizes - annotationComponent.refreshDirtyBounds(); - } - // special case for an undelete as we need to to make the component - // visible again. - else { - // mark it as not deleted - annotation.setDeleted(false); - // re-add it to the page - page.addAnnotation(annotation); - // finally update the pageComponent so we can see it again. - ((Component) annotationComponent).setVisible(true); - // refresh bounds for any resizes - annotationComponent.refreshDirtyBounds(); - } - } - - -} +/* + * Copyright 2006-2019 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.ri.common.views.annotations; + +import org.icepdf.core.Memento; +import org.icepdf.core.pobjects.Document; +import org.icepdf.core.pobjects.Page; +import org.icepdf.core.pobjects.PageTree; +import org.icepdf.core.pobjects.annotations.Annotation; +import org.icepdf.core.pobjects.annotations.BorderStyle; +import org.icepdf.ri.common.views.AnnotationComponent; + +import java.awt.*; +import java.awt.geom.Rectangle2D; + +/** + * Stores state parameters for annotation objects to be used in conjunction + * with a care taker as part of the memento pattern. + * + * @since 4.0 + */ +public class AnnotationState implements Memento { + + // annotation bounding rectangle in user space. + protected Rectangle2D.Float userSpaceRectangle; + + // original rectangle reference. + protected AnnotationComponent annotationComponent; + + /** + * Stores the annotation state associated with the AnnotationComponents + * annotation object. When a new instance of this object is created + * the annotation's proeprties are saved. + * + * @param annotationComponent annotation component who's state will be stored. + */ + public AnnotationState(AnnotationComponent annotationComponent) { + // reference to component so we can apply the state parameters if + // restore() is called. + this.annotationComponent = annotationComponent; + } + + + public void apply(AnnotationState applyState) { + + // store user space rectangle SpaceRectangle. + Rectangle2D.Float rect = applyState.userSpaceRectangle; + if (rect != null) { + userSpaceRectangle = new Rectangle2D.Float(rect.x, rect.y, + rect.width, rect.height); + } + + // apply the new state to the annotation and schedule a sync + restore(); + + } + + /** + * Restores the AnnotationComponents state to the state stored during the + * construction of this object. + */ + public void restore() { + if (annotationComponent != null && + annotationComponent.getAnnotation() != null) { + // get reference to annotation + Annotation annotation = annotationComponent.getAnnotation(); + + restore(annotation); + + // update the document with current state. + synchronizeState(); + } + } + + /** + * Restores the annotation state in this instance to the Annotation + * specified as a param. This method is ment to bue used in + * + * @param annotation annotation to retore state to. + */ + public void restore(Annotation annotation) { + // create a new Border style entry as an inline dictionary + if (annotation.getBorderStyle() == null) { + annotation.setBorderStyle(new BorderStyle()); + } + + // apply old user rectangle + annotation.setUserSpaceRectangle(userSpaceRectangle); + } + + public void synchronizeState() { + // update the document with this change. + int pageIndex = annotationComponent.getPageIndex(); + Document document = annotationComponent.getDocument(); + Annotation annotation = annotationComponent.getAnnotation(); + PageTree pageTree = document.getPageTree(); + Page page = pageTree.getPage(pageIndex); + // state behind draw state. + if (!annotation.isDeleted()) { + page.updateAnnotation(annotation); + // refresh bounds for any resizes + annotationComponent.refreshDirtyBounds(); + } + // special case for an undelete as we need to to make the component + // visible again. + else { + // mark it as not deleted + annotation.setDeleted(false); + // re-add it to the page + page.addAnnotation(annotation, true); + // finally update the pageComponent so we can see it again. + ((Component) annotationComponent).setVisible(true); + // refresh bounds for any resizes + annotationComponent.refreshDirtyBounds(); + } + } + + +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/InkAnnotationComponent.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/InkAnnotationComponent.java index 92d147d44..cd4387392 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/InkAnnotationComponent.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/InkAnnotationComponent.java @@ -1,72 +1,72 @@ -/* - * Copyright 2006-2019 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.ri.common.views.annotations; - -import org.icepdf.core.pobjects.annotations.InkAnnotation; -import org.icepdf.ri.common.utility.annotation.properties.InkAnnotationPanel; -import org.icepdf.ri.common.views.AbstractPageViewComponent; -import org.icepdf.ri.common.views.DocumentViewController; - -import java.awt.*; -import java.awt.event.MouseEvent; - -/** - * The InkAnnotationComponent encapsulates a InkAnnotation objects. It - * also provides basic editing functionality such as resizing, moving and change - * the border color and style. - *
- * The Viewer RI implementation contains a InkAnnotationPanel class which - * can edit the various properties of this component. - * - * @see InkAnnotationPanel - * @since 5.0 - */ -@SuppressWarnings("serial") -public class InkAnnotationComponent extends MarkupAnnotationComponent { - - - public InkAnnotationComponent(InkAnnotation annotation, DocumentViewController documentViewController, - AbstractPageViewComponent pageViewComponent) { - super(annotation, documentViewController, pageViewComponent); - isShowInvisibleBorder = false; - isResizable = false; - } - - @Override - public void resetAppearanceShapes() { - super.resetAppearanceShapes(); - refreshAnnotationRect(); - annotation.resetAppearanceStream(dx, dy, getToPageSpaceTransform()); - } - - @Override - public void mouseReleased(MouseEvent mouseEvent) { - wasResized = false; - super.mouseReleased(mouseEvent); - } - - @Override - public void mouseDragged(MouseEvent me) { - super.mouseDragged(me); - dy *= -1; - resetAppearanceShapes(); - } - - @Override - public void paintComponent(Graphics g) { - - } -} +/* + * Copyright 2006-2019 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.ri.common.views.annotations; + +import org.icepdf.core.pobjects.annotations.InkAnnotation; +import org.icepdf.ri.common.utility.annotation.properties.InkAnnotationPanel; +import org.icepdf.ri.common.views.AbstractPageViewComponent; +import org.icepdf.ri.common.views.DocumentViewController; + +import java.awt.*; +import java.awt.event.MouseEvent; + +/** + * The InkAnnotationComponent encapsulates a InkAnnotation objects. It + * also provides basic editing functionality such as resizing, moving and change + * the border color and style. + *
+ * The Viewer RI implementation contains a InkAnnotationPanel class which + * can edit the various properties of this component. + * + * @see InkAnnotationPanel + * @since 5.0 + */ +@SuppressWarnings("serial") +public class InkAnnotationComponent extends MarkupAnnotationComponent { + + + public InkAnnotationComponent(InkAnnotation annotation, DocumentViewController documentViewController, + AbstractPageViewComponent pageViewComponent) { + super(annotation, documentViewController, pageViewComponent); + isShowInvisibleBorder = false; + isResizable = false; + } + + @Override + public void resetAppearanceShapes() { + super.resetAppearanceShapes(); + refreshAnnotationRect(); + annotation.resetAppearanceStream(dx, dy, getToPageSpaceTransform(), true); + } + + @Override + public void mouseReleased(MouseEvent mouseEvent) { + wasResized = false; + super.mouseReleased(mouseEvent); + } + + @Override + public void mouseDragged(MouseEvent me) { + super.mouseDragged(me); + dy *= -1; + resetAppearanceShapes(); + } + + @Override + public void paintComponent(Graphics g) { + + } +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/LineAnnotationComponent.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/LineAnnotationComponent.java index 80fda4836..59f0ef764 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/LineAnnotationComponent.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/LineAnnotationComponent.java @@ -1,74 +1,74 @@ -/* - * Copyright 2006-2019 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.ri.common.views.annotations; - -import org.icepdf.core.pobjects.annotations.LineAnnotation; -import org.icepdf.ri.common.utility.annotation.properties.LineAnnotationPanel; -import org.icepdf.ri.common.views.AbstractPageViewComponent; -import org.icepdf.ri.common.views.DocumentViewController; - -import java.awt.*; -import java.awt.event.MouseEvent; - -/** - * The LineAnnotationComponent encapsulates a LineAnnotation objects. It - * also provides basic editing functionality such as resizing, moving and change - * the border color and style. The start and end line cab can also be changed - * to one of the name types defined in the LineAnnotation class. - *
- * The Viewer RI implementation contains a LineAnnotationPanel class which - * can edit the various properties of this component. - * - * @see LineAnnotationPanel - * @since 5.0 - */ -@SuppressWarnings("serial") -public class LineAnnotationComponent extends MarkupAnnotationComponent { - - - public LineAnnotationComponent(LineAnnotation annotation, DocumentViewController documentViewController, - AbstractPageViewComponent pageViewComponent) { - super(annotation, documentViewController, pageViewComponent); - isRollover = false; - isResizable = false; - isShowInvisibleBorder = false; - } - - @Override - public void paintComponent(Graphics g) { - - } - - @Override - public void mouseReleased(MouseEvent mouseEvent) { - wasResized = false; - super.mouseReleased(mouseEvent); - } - - @Override - public void resetAppearanceShapes() { - super.resetAppearanceShapes(); - refreshAnnotationRect(); - annotation.resetAppearanceStream(dx, dy, getToPageSpaceTransform()); - } - - @Override - public void mouseDragged(MouseEvent me) { - super.mouseDragged(me); - dy *= -1; - resetAppearanceShapes(); - } -} +/* + * Copyright 2006-2019 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.ri.common.views.annotations; + +import org.icepdf.core.pobjects.annotations.LineAnnotation; +import org.icepdf.ri.common.utility.annotation.properties.LineAnnotationPanel; +import org.icepdf.ri.common.views.AbstractPageViewComponent; +import org.icepdf.ri.common.views.DocumentViewController; + +import java.awt.*; +import java.awt.event.MouseEvent; + +/** + * The LineAnnotationComponent encapsulates a LineAnnotation objects. It + * also provides basic editing functionality such as resizing, moving and change + * the border color and style. The start and end line cab can also be changed + * to one of the name types defined in the LineAnnotation class. + *
+ * The Viewer RI implementation contains a LineAnnotationPanel class which + * can edit the various properties of this component. + * + * @see LineAnnotationPanel + * @since 5.0 + */ +@SuppressWarnings("serial") +public class LineAnnotationComponent extends MarkupAnnotationComponent { + + + public LineAnnotationComponent(LineAnnotation annotation, DocumentViewController documentViewController, + AbstractPageViewComponent pageViewComponent) { + super(annotation, documentViewController, pageViewComponent); + isRollover = false; + isResizable = false; + isShowInvisibleBorder = false; + } + + @Override + public void paintComponent(Graphics g) { + + } + + @Override + public void mouseReleased(MouseEvent mouseEvent) { + wasResized = false; + super.mouseReleased(mouseEvent); + } + + @Override + public void resetAppearanceShapes() { + super.resetAppearanceShapes(); + refreshAnnotationRect(); + annotation.resetAppearanceStream(dx, dy, getToPageSpaceTransform(), true); + } + + @Override + public void mouseDragged(MouseEvent me) { + super.mouseDragged(me); + dy *= -1; + resetAppearanceShapes(); + } +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/MarkupAnnotationComponent.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/MarkupAnnotationComponent.java index 7d66ae269..b4c5d6440 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/MarkupAnnotationComponent.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/MarkupAnnotationComponent.java @@ -160,9 +160,10 @@ public PopupAnnotationComponent createPopupAnnotationComponent(boolean isNew) { } PopupAnnotation popupAnnotation = null; if (annotation != null && annotation.getPopupAnnotation() == null) { + popupAnnotation = TextAnnotationHandler.createPopupAnnotation( documentViewController.getDocument().getPageTree().getLibrary(), - tBbox, annotation, getToPageSpaceTransform()); + tBbox, annotation, getToPageSpaceTransform(), isNew); annotation.setPopupAnnotation(popupAnnotation); } else if (annotation != null) { popupAnnotation = annotation.getPopupAnnotation(); @@ -177,6 +178,8 @@ public PopupAnnotationComponent createPopupAnnotationComponent(boolean isNew) { comp.setBounds(bBox); // resets user space rectangle to match bbox converted to page space comp.refreshAnnotationRect(); + // not new, which means the popup wasn't part of the document, we don't want to save it at this time + comp.setSynthetic(!isNew); // add them to the container, using absolute positioning. documentViewController.addNewAnnotation(comp); @@ -226,10 +229,9 @@ public void togglePopupAnnotationVisibility() { popupComponent.setBounds(popupBounds); } } - // no markupAnnotation so we need to create one and display for - // the addition comments. + // no markupAnnotation so we need to create one and display for the addition comments. else { - // convert bbox and start and end line points. + // user initiated change, so we'll mark the popup state as changed and queue it for saving. createPopupAnnotationComponent(true); } }