From 416b29b3195a97d1f42c105f18de11966bab9634 Mon Sep 17 00:00:00 2001 From: Patrick Corless Date: Sun, 7 May 2023 22:10:13 -0600 Subject: [PATCH] GH-173 printable popup annotations (#265) --- .../core/pobjects/MarkupGlueAnnotation.java | 63 +++++++++ .../java/org/icepdf/core/pobjects/Page.java | 52 +++++++- .../core/pobjects/annotations/Annotation.java | 5 +- .../annotations/MarkupAnnotation.java | 23 ++++ .../annotations/MarkupGluePainter.java | 124 ++++++++++++++++++ .../pobjects/annotations/PopupAnnotation.java | 117 +++++++++++++++++ logging.properties | 8 +- .../annotations/MarkupGlueComponent.java | 113 +++------------- .../annotations/PopupAnnotationComponent.java | 28 +--- 9 files changed, 404 insertions(+), 129 deletions(-) create mode 100644 core/core-awt/src/main/java/org/icepdf/core/pobjects/MarkupGlueAnnotation.java create mode 100644 core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/MarkupGluePainter.java diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/MarkupGlueAnnotation.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/MarkupGlueAnnotation.java new file mode 100644 index 000000000..50bd964e8 --- /dev/null +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/MarkupGlueAnnotation.java @@ -0,0 +1,63 @@ +package org.icepdf.core.pobjects; + +import org.icepdf.core.pobjects.annotations.Annotation; +import org.icepdf.core.pobjects.annotations.MarkupAnnotation; +import org.icepdf.core.pobjects.annotations.MarkupGluePainter; +import org.icepdf.core.pobjects.annotations.PopupAnnotation; +import org.icepdf.core.util.Library; + +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; + +public class MarkupGlueAnnotation extends Annotation { + + protected MarkupAnnotation markupAnnotation; + protected PopupAnnotation popupAnnotation; + + public MarkupGlueAnnotation(Library l, DictionaryEntries h) { + super(l, h); + } + + public MarkupGlueAnnotation(Library l, MarkupAnnotation markupAnnotation, PopupAnnotation popupAnnotation) { + super(l, new DictionaryEntries()); + this.markupAnnotation = markupAnnotation; + this.popupAnnotation = popupAnnotation; + } + + protected void renderAppearanceStream(Graphics2D g2d) { + if (this.popupAnnotation == null || this.markupAnnotation == null) return; + + GraphicsConfiguration graphicsConfiguration = g2d.getDeviceConfiguration(); + boolean isPrintingAllowed = this.markupAnnotation.getFlagPrint(); + if (graphicsConfiguration.getDevice().getType() == GraphicsDevice.TYPE_PRINTER && + this.popupAnnotation.isOpen() && isPrintingAllowed) { + AffineTransform oldTransform = g2d.getTransform(); + new MarkupGluePainter(markupAnnotation, popupAnnotation, this).paint(g2d); + g2d.setTransform(oldTransform); + } + } + + public Rectangle2D.Float getUserSpaceRectangle() { + // make sure we always update this to get the correct clip during painting + if (this.markupAnnotation != null && this.popupAnnotation != null) { + Rectangle rect = this.markupAnnotation.getUserSpaceRectangle().getBounds().union( + this.popupAnnotation.getUserSpaceRectangle().getBounds()); + userSpaceRectangle = new Rectangle2D.Float(rect.x, rect.y, rect.width, rect.height); + } + return userSpaceRectangle; + } + + public boolean allowPrintNormalMode() { + return allowScreenOrPrintRenderingOrInteraction() && this.markupAnnotation.getFlagPrint(); + } + + @Override + public void resetAppearanceStream(double dx, double dy, AffineTransform pageSpace, boolean isNew) { + + } + + public MarkupAnnotation getMarkupAnnotation() { + return markupAnnotation; + } +} 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 7a499b37f..0112bfe8a 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 @@ -353,6 +353,9 @@ else if (annotObj instanceof DictionaryEntries) { // HashMap lacks "Type"->"Anno // add any found annotations to the vector. annotations.add(a); } + // create synthetic annotation to paint the glue between a markup annotation and the popup + // this is only used for print purposes. A similar pattern is also used in the Viewer RI + createPrintableMarkupAnnotationGlue(a); } } catch (IllegalStateException e) { Annotation finalA = a; @@ -809,6 +812,24 @@ public Shape getPageShape(int boundary, float userRotation, float userZoom) { return path.createTransformedShape(at); } + private void createPrintableMarkupAnnotationGlue(Annotation annotation) { + // create synthetic annotation to paint the glue between a markup annotation and the popup + // this is only used for print purposes. A similar pattern is also used in the Viewer RI + if (annotation instanceof PopupAnnotation) { + PopupAnnotation popupAnnotation = (PopupAnnotation) annotation; + MarkupAnnotation markupAnnotation = popupAnnotation.getParent(); + // insert glue before popup is painted, so we don't over paint + Annotation annot; + for (int i = 0; i < annotations.size(); i++) { + annot = annotations.get(i); + if (annot instanceof PopupAnnotation && annot.equals(popupAnnotation)) { + annotations.add(i, new MarkupGlueAnnotation(library, markupAnnotation, popupAnnotation)); + break; + } + } + } + } + /** * Adds an annotation that was previously added to the document. It is * assumed that the annotation has a valid object reference. This @@ -816,8 +837,8 @@ 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. + * @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") @@ -883,6 +904,9 @@ public Annotation addAnnotation(Annotation newAnnotation, boolean isNew) { // add the annotations to the parsed annotations list this.annotations.add(newAnnotation); + // add visual glue for markup annotation + createPrintableMarkupAnnotationGlue(newAnnotation); + // add the new annotations to the library library.addObject(newAnnotation, newAnnotation.getPObjectReference()); @@ -971,6 +995,30 @@ else if (annot.isNew()) { if (annotations != null) { annotations.remove(annot); } + // todo clean up orphaned popup annotations + // remove any corresponding popup annotation. + if (annot instanceof MarkupAnnotation) { + MarkupAnnotation markupAnnotation = (MarkupAnnotation) annot; + for (Annotation annotation : annotations) { + if (annotation instanceof PopupAnnotation && + annotation.equals(markupAnnotation.getPopupAnnotation())) { + annotations.remove(annotation); + break; + } + } + } + // remove any markupGlue so that it doesn't get painted. Glue is never added to the document, it created + // dynamically for print purposes. + if (annot instanceof MarkupAnnotation) { + MarkupAnnotation markupAnnotation = (MarkupAnnotation) annot; + for (Annotation annotation : annotations) { + if (annotation instanceof MarkupGlueAnnotation && + ((MarkupGlueAnnotation) annotation).getMarkupAnnotation().equals(markupAnnotation)) { + annotations.remove(annotation); + break; + } + } + } // finally remove it from the library to free up the memory library.removeObject(annot.getPObjectReference()); } 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 ad71819ca..908074347 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 @@ -1308,7 +1308,10 @@ public void render(Graphics2D origG, int renderHintType, origG.setRenderingHints(grh.getRenderingHints(renderHintType)); origG.setTransform(at); Shape preAppearanceStreamClip = origG.getClip(); - origG.clip(deriveDrawingRectangle()); + Rectangle2D.Float derivedClip = deriveDrawingRectangle(); + if (derivedClip != null) { + origG.clip(deriveDrawingRectangle()); + } renderAppearanceStream(origG); diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/MarkupAnnotation.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/MarkupAnnotation.java index 5a36ed625..9f59164e4 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/MarkupAnnotation.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/MarkupAnnotation.java @@ -19,7 +19,11 @@ import org.icepdf.core.pobjects.graphics.GraphicsState; import org.icepdf.core.util.Library; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; import java.util.HashSet; +import java.util.Locale; import java.util.Set; /** @@ -301,6 +305,25 @@ public PDate getCreationDate() { return creationDate; } + /** + * Format the creation date using the given FormatStyle + * @param formatStyle date output style used by DateTimeFormatter + * @return formatted creation date if available, empty String otherwise + */ + public String getFormattedCreationDate(FormatStyle formatStyle) { + LocalDateTime creationDate = getCreationDate().asLocalDateTime(); + if (creationDate == null) return ""; + DateTimeFormatter formatter = DateTimeFormatter + .ofLocalizedDateTime(formatStyle) + .withLocale(Locale.getDefault()); + return creationDate.format(formatter); + } + + public String getFormattedTitleText() { + String titleText = getTitleText(); + return titleText != null ? titleText : ""; + } + public boolean isInReplyTo() { return library.getObject(entries, IRT_KEY) != null; } diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/MarkupGluePainter.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/MarkupGluePainter.java new file mode 100644 index 000000000..de7d97688 --- /dev/null +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/annotations/MarkupGluePainter.java @@ -0,0 +1,124 @@ +package org.icepdf.core.pobjects.annotations; + +import org.icepdf.core.pobjects.MarkupGlueAnnotation; + +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.geom.GeneralPath; + +/** + * MarkupGluePainter allows for a visual associating between a markup annotation, and it's popup annotation + * when open. This painter code is called from the component used in the Viewer RI as well as painting popups + * for printing purposes. + * + * @since 7.1 + */ +public class MarkupGluePainter { + + protected MarkupAnnotation markupAnnotation; + protected PopupAnnotation popupAnnotation; + + protected MarkupGlueAnnotation markupGlueAnnotation; + + public MarkupGluePainter(MarkupAnnotation markupAnnotation, PopupAnnotation popupAnnotation, + MarkupGlueAnnotation markupGlueAnnotation) { + this.markupAnnotation = markupAnnotation; + this.popupAnnotation = popupAnnotation; + this.markupGlueAnnotation = markupGlueAnnotation; + } + + public void paint(Graphics g) { + if (popupAnnotation.isOpen()) { + Rectangle popupBounds = popupAnnotation.getUserSpaceRectangle().getBounds(); + Rectangle markupBounds = markupAnnotation.getUserSpaceRectangle().getBounds(); + Rectangle glueBounds = markupGlueAnnotation.getUserSpaceRectangle().getBounds(); + MarkupGluePainter.paintGlue( + g, markupBounds, popupBounds, glueBounds, markupAnnotation.getColor()); + } + } + + public static void paintGlue(Graphics g, Rectangle markupBounds, Rectangle popupBounds, Rectangle glueBounds, + Color color) { + Graphics2D g2d = (Graphics2D) g; + g2d.setColor(color); + g2d.setStroke(new BasicStroke(1)); + GeneralPath path = new GeneralPath(); + path.moveTo(0, 0); + + // in order to draw the curvy shape we need to determine which of the 8 surrounding regions + // the popup is relative to the markup annotation. + int popupX = popupBounds.x; + int popupY = popupBounds.y; + int popupXC = (int) popupBounds.getCenterX(); + int popupYC = (int) popupBounds.getCenterY(); + int popupW = popupBounds.width; + int popupH = popupBounds.height; + + int markupXC = (int) markupBounds.getCenterX(); + int markupYC = (int) markupBounds.getCenterY(); + + float angle = (float) Math.toDegrees(Math.atan2(markupYC - popupYC, markupXC - popupXC)); + if (angle < 0) { + angle += 360; + } + // N + if (angle >= 67.5 && angle < 112.5) { + path.moveTo(markupXC, markupYC); + path.quadTo(popupXC, popupY + popupH, popupX, popupY + popupH); + path.lineTo(popupX + popupW, popupY + popupH); + path.quadTo(popupXC, popupY + popupH, markupXC, markupYC); + } + // NE + else if (angle >= 112.5 && angle < 157.5) { + path.moveTo(markupXC, markupYC); + path.quadTo(popupX, popupY + popupH, popupX, popupYC); + path.lineTo(popupXC, popupY + popupH); + path.quadTo(popupX, popupY + popupH, markupXC, markupYC); + } + // E + else if (angle >= 157.5 && angle < 202.5) { + path.moveTo(markupXC, markupYC); + path.quadTo(popupX, popupYC, popupX, popupY); + path.lineTo(popupX, popupY + popupH); + path.quadTo(popupX, popupYC, markupXC, markupYC); + } + // SE + else if (angle >= 202.5 && angle < 247.5) { + path.moveTo(markupXC, markupYC); + path.quadTo(popupX, popupY, popupXC, popupY); + path.lineTo(popupX, popupYC); + path.quadTo(popupX, popupY, markupXC, markupYC); + } + // S + else if (angle >= 247.5 && angle < 292.5) { + path.moveTo(markupXC, markupYC); + path.quadTo(popupXC, popupY, popupX, popupY); + path.lineTo(popupX + popupW, popupY); + path.quadTo(popupXC, popupY, markupXC, markupYC); + } else if (angle >= 292.5 && angle < 315) { + path.moveTo(markupXC, markupYC); + path.quadTo(popupX + popupW, popupY, popupXC, popupY); + path.lineTo(popupX + popupW, popupYC); + path.quadTo(popupX + popupW, popupY, markupXC, markupYC); + } + // W + else if (angle >= 315 || angle < 22.5) { + path.moveTo(markupXC, markupYC); + path.quadTo(popupX + popupW, popupYC, popupX + popupW, popupY); + path.lineTo(popupX + popupW, popupY + popupH); + path.quadTo(popupX + popupW, popupYC, markupXC, markupYC); + } + // NW + else if (angle >= 22.5 && angle < 67.5) { + path.moveTo(markupXC, markupYC); + path.quadTo(popupX + popupW, popupY + popupH, popupX + popupW, popupYC); + path.lineTo(popupXC, popupY + popupH); + path.quadTo(popupX + popupW, popupY + popupH, markupXC, markupYC); + } + // translate to this components space. + path.transform(new AffineTransform(1, 0, 0, 1, -glueBounds.x, -glueBounds.y)); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f)); + g2d.fill(path); + } +} 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 fac18023a..e308f47df 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 @@ -18,8 +18,12 @@ import org.icepdf.core.pobjects.*; import org.icepdf.core.util.Library; +import javax.swing.*; import java.awt.*; import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.time.format.FormatStyle; +import java.util.HashMap; import java.util.logging.Logger; /** @@ -61,10 +65,17 @@ public class PopupAnnotation extends Annotation { protected MarkupAnnotation parent; + protected JPanel popupPaintablesPanel; + private boolean resetPopupPaintables = true; + public PopupAnnotation(Library library, DictionaryEntries dictionaryEntries) { super(library, dictionaryEntries); } + public synchronized void init() throws InterruptedException { + super.init(); + } + /** * Gets an instance of a PopupAnnotation that has valid Object Reference. * @@ -111,6 +122,112 @@ public static PopupAnnotation getInstance(Library library, return popupAnnotation; } + @Override + protected void renderAppearanceStream(Graphics2D g2d) { + GraphicsConfiguration graphicsConfiguration = g2d.getDeviceConfiguration(); + boolean isPrintingAllowed = getParent().getFlagPrint(); + if (graphicsConfiguration.getDevice().getType() == GraphicsDevice.TYPE_PRINTER && + isOpen() && + isPrintingAllowed) { + if (resetPopupPaintables) { + buildPopupPaintables(); + } + paintPopupPaintables(g2d); + } + } + + public boolean allowPrintNormalMode() { + boolean isParentPrintable = parent != null && parent.getFlagPrint(); + return allowScreenOrPrintRenderingOrInteraction() && isParentPrintable; + } + + private void paintPopupPaintables(Graphics2D g2d) { + + Rectangle2D.Float popupBounds = getUserSpaceRectangle(); + + AffineTransform oldTransform = g2d.getTransform(); + g2d.scale(1, -1); + g2d.translate(0, -popupBounds.getBounds().getSize().height); + + popupPaintablesPanel.invalidate(); + popupPaintablesPanel.revalidate(); + popupPaintablesPanel.print(g2d); + + g2d.setTransform(oldTransform); + } + + /** + * Builds a JPanel representing the popup annotation that can be printed. + */ + private void buildPopupPaintables() { + + Rectangle2D.Float popupBounds = getUserSpaceRectangle(); + Rectangle popupBoundsNormalized = new Rectangle2D.Double(0, 0, popupBounds.width, popupBounds.height).getBounds(); + + popupPaintablesPanel = new JPanel(); + + Color color = getParent().getColor(); + Color contrastColor = calculateContrastHighLowColor(color.getRGB()); + popupPaintablesPanel.setBackground(color); + popupPaintablesPanel.setBounds(popupBoundsNormalized); + popupPaintablesPanel.setSize(popupBoundsNormalized.getSize()); + popupPaintablesPanel.setPreferredSize(popupBoundsNormalized.getSize()); + + MarkupAnnotation markupAnnotation = getParent(); + // user + String title = markupAnnotation.getFormattedTitleText(); + JLabel titleLabel = new JLabel(title); + titleLabel.setForeground(contrastColor); + popupPaintablesPanel.add(titleLabel); + + // creation date + JLabel creationLabel = new JLabel(); + creationLabel.setText(markupAnnotation.getFormattedCreationDate(FormatStyle.MEDIUM)); + creationLabel.setForeground(contrastColor); + popupPaintablesPanel.add(creationLabel); + + // text area + String contents = getParent() != null ? getParent().getContents() : ""; + JTextArea textArea = new JTextArea(contents); + textArea.setFont(new JLabel().getFont()); + textArea.setWrapStyleWord(true); + textArea.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(color), + BorderFactory.createEmptyBorder(2, 2, 2, 2))); + popupPaintablesPanel.add(textArea); + + // basic layout + Component[] components = popupPaintablesPanel.getComponents(); + int yOffset = 0; + int padding = 2; + for (Component comp : components) { + Dimension size = comp.getPreferredSize(); + comp.setSize(size); + comp.setPreferredSize(size); + comp.setLocation(padding, yOffset); + yOffset += comp.getHeight() + padding; + } + // stretch text area. + textArea.setSize((int) (popupBounds.width - (padding * 2)), (int) (popupBounds.height + textArea.getHeight() - yOffset)); + resetPopupPaintables = false; + } + + public Color calculateContrastHighLowColor(int rgb) { + int tolerance = 120; + if ((rgb & 0xFF) <= tolerance && + (rgb >> 8 & 0xFF) <= tolerance || + (rgb >> 16 & 0xFF) <= tolerance) { + return Color.WHITE; + } else { + return Color.BLACK; + } + + } + + public void setContents(String content) { + super.setString(CONTENTS_KEY, content); + resetPopupPaintables = true; + } + @Override public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform, boolean isNew) { diff --git a/logging.properties b/logging.properties index 4e4838c81..d79029cf3 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/views/annotations/MarkupGlueComponent.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/MarkupGlueComponent.java index 854ca7b32..325fbff5d 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/MarkupGlueComponent.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/MarkupGlueComponent.java @@ -15,10 +15,10 @@ */ package org.icepdf.ri.common.views.annotations; +import org.icepdf.core.pobjects.annotations.MarkupGluePainter; + import javax.swing.*; import java.awt.*; -import java.awt.geom.AffineTransform; -import java.awt.geom.GeneralPath; /** * MarkupGlueComponent allows for a visual associating between a markup annotation and it's popup annotation @@ -36,9 +36,9 @@ public MarkupGlueComponent(MarkupAnnotationComponent markupAnnotationComponent, this.popupAnnotationComponent = popupAnnotationComponent; if (popupAnnotationComponent != null) { Rectangle bound = markupAnnotationComponent.getBounds().union(popupAnnotationComponent.getBounds()); - setBounds(markupAnnotationComponent.getBounds().union(popupAnnotationComponent.getBounds())); - setPreferredSize(new Dimension(bound.width, bound.height)); - setSize(new Dimension(bound.width, bound.height)); + setBounds(bound); + setPreferredSize(bound.getSize()); + setSize(bound.getSize()); } } @@ -49,100 +49,17 @@ public MarkupAnnotationComponent getMarkupAnnotationComponent() { @Override protected void paintComponent(Graphics g) { super.paintComponent(g); + if (popupAnnotationComponent.getAnnotation().isOpen()) { + Rectangle popupBounds = popupAnnotationComponent.getBounds(); + Rectangle markupBounds = markupAnnotationComponent.getBounds(); + Rectangle glueBounds = markupAnnotationComponent.getBounds().union(popupAnnotationComponent.getBounds()); + setBounds(glueBounds); + setPreferredSize(glueBounds.getSize()); + setSize(glueBounds.getSize()); - Rectangle popupBounds = popupAnnotationComponent.getBounds(); - Rectangle markupBounds = markupAnnotationComponent.getBounds(); - setBounds(markupAnnotationComponent.getBounds().union(popupAnnotationComponent.getBounds())); - Rectangle bound = markupAnnotationComponent.getBounds().union(popupAnnotationComponent.getBounds()); - setBounds(markupAnnotationComponent.getBounds().union(popupAnnotationComponent.getBounds())); - setPreferredSize(new Dimension(bound.width, bound.height)); - setSize(new Dimension(bound.width, bound.height)); - - Rectangle bounds = getBounds(); - Graphics2D g2d = (Graphics2D) g; - - if (popupAnnotationComponent.isVisible()) { - g2d.setColor(markupAnnotationComponent.getAnnotation().getColor()); - g2d.setStroke(new BasicStroke(1)); - GeneralPath path = new GeneralPath(); - path.moveTo(0, 0); - - // in order to draw the curvy shape we need to determine which of the 8 surrounding regions - // the popup is relative to the markup annotation. - int popupX = popupBounds.x; - int popupY = popupBounds.y; - int popupXC = (int) popupBounds.getCenterX(); - int popupYC = (int) popupBounds.getCenterY(); - int popupW = popupBounds.width; - int popupH = popupBounds.height; - - int markupXC = (int) markupBounds.getCenterX(); - int markupYC = (int) markupBounds.getCenterY(); - - float angle = (float) Math.toDegrees(Math.atan2(markupYC - popupYC, markupXC - popupXC)); - if (angle < 0) { - angle += 360; - } - - // N - if (angle >= 67.5 && angle < 112.5) { - path.moveTo(markupXC, markupYC); - path.quadTo(popupXC, popupY + popupH, popupX, popupY + popupH); - path.lineTo(popupX + popupW, popupY + popupH); - path.quadTo(popupXC, popupY + popupH, markupXC, markupYC); - } - // NE - else if (angle >= 112.5 && angle < 157.5) { - path.moveTo(markupXC, markupYC); - path.quadTo(popupX, popupY + popupH, popupX, popupYC); - path.lineTo(popupXC, popupY + popupH); - path.quadTo(popupX, popupY + popupH, markupXC, markupYC); - } - // E - else if (angle >= 157.5 && angle < 202.5) { - path.moveTo(markupXC, markupYC); - path.quadTo(popupX, popupYC, popupX, popupY); - path.lineTo(popupX, popupY + popupH); - path.quadTo(popupX, popupYC, markupXC, markupYC); - } - // SE - else if (angle >= 202.5 && angle < 247.5) { - path.moveTo(markupXC, markupYC); - path.quadTo(popupX, popupY, popupXC, popupY); - path.lineTo(popupX, popupYC); - path.quadTo(popupX, popupY, markupXC, markupYC); - } - // S - else if (angle >= 247.5 && angle < 292.5) { - path.moveTo(markupXC, markupYC); - path.quadTo(popupXC, popupY, popupX, popupY); - path.lineTo(popupX + popupW, popupY); - path.quadTo(popupXC, popupY, markupXC, markupYC); - } else if (angle >= 292.5 && angle < 315) { - path.moveTo(markupXC, markupYC); - path.quadTo(popupX + popupW, popupY, popupXC, popupY); - path.lineTo(popupX + popupW, popupYC); - path.quadTo(popupX + popupW, popupY, markupXC, markupYC); - } - // W - else if (angle >= 315 || angle < 22.5) { - path.moveTo(markupXC, markupYC); - path.quadTo(popupX + popupW, popupYC, popupX + popupW, popupY); - path.lineTo(popupX + popupW, popupY + popupH); - path.quadTo(popupX + popupW, popupYC, markupXC, markupYC); - } - // NW - else if (angle >= 22.5 && angle < 67.5) { - path.moveTo(markupXC, markupYC); - path.quadTo(popupX + popupW, popupY + popupH, popupX + popupW, popupYC); - path.lineTo(popupXC, popupY + popupH); - path.quadTo(popupX + popupW, popupY + popupH, markupXC, markupYC); - } - // translate to this components space. - path.transform(new AffineTransform(1, 0, 0, 1, -bounds.x, -bounds.y)); - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f)); - g2d.fill(path); + MarkupGluePainter.paintGlue( + g, markupBounds, popupBounds, glueBounds, + markupAnnotationComponent.getAnnotation().getColor()); } } } diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/PopupAnnotationComponent.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/PopupAnnotationComponent.java index 5c449c01d..0dfca677b 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/PopupAnnotationComponent.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/annotations/PopupAnnotationComponent.java @@ -50,8 +50,6 @@ import java.io.File; import java.io.IOException; import java.text.MessageFormat; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.List; import java.util.*; @@ -350,8 +348,7 @@ private void buildGUI() { refreshCreationLabel(); // title, user name. String title = selectedMarkupAnnotation != null ? - selectedMarkupAnnotation.getTitleText() != null ? - selectedMarkupAnnotation.getTitleText() : "" : ""; + selectedMarkupAnnotation.getFormattedTitleText() : ""; titleLabel = new JLabel(title); // Setup color appearance values. @@ -762,13 +759,8 @@ public void refreshPopupState() { } private void refreshCreationLabel() { - if (selectedMarkupAnnotation != null && - selectedMarkupAnnotation.getCreationDate() != null && creationLabel != null) { - LocalDateTime creationDate = selectedMarkupAnnotation.getCreationDate().asLocalDateTime(); - DateTimeFormatter formatter = DateTimeFormatter - .ofLocalizedDateTime(FormatStyle.MEDIUM) - .withLocale(Locale.getDefault()); - creationLabel.setText(creationDate.format(formatter)); + if (selectedMarkupAnnotation != null && creationLabel != null) { + creationLabel.setText(selectedMarkupAnnotation.getFormattedCreationDate(FormatStyle.MEDIUM)); } } @@ -1075,7 +1067,7 @@ protected AnnotationComponent findAnnotationComponent(Annotation annotation) { } protected void resetComponentColors() { - Color contrastColor = calculateContrastHighLowColor(popupBackgroundColor.getRGB()); + Color contrastColor = annotation.calculateContrastHighLowColor(popupBackgroundColor.getRGB()); minimizeButton.setForeground(contrastColor); minimizeButton.setBackground(popupBackgroundColor); minimizeButton.setBackground(popupBackgroundColor); @@ -1205,18 +1197,6 @@ protected Color calculateContrastColor(int rgb) { } - protected Color calculateContrastHighLowColor(int rgb) { - int tolerance = 120; - if ((rgb & 0xFF) <= tolerance && - (rgb >> 8 & 0xFF) <= tolerance || - (rgb >> 16 & 0xFF) <= tolerance) { - return Color.WHITE; - } else { - return Color.BLACK; - } - - } - public void changeFontSize(float changeValue) { final Font areaFont = textArea.getFont(); final Font titleFont = titleLabel.getFont();