From cd3e4c03661f770ebeefcd3637d56589243ac0a9 Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Fri, 24 May 2024 16:30:30 +0000 Subject: [PATCH] 8326734: text-decoration applied to lost when mixed with or 8325620: HTMLReader uses ConvertAction instead of specified CharacterAction for , , Reviewed-by: honkar, prr --- .../classes/javax/swing/text/html/CSS.java | 14 +- .../javax/swing/text/html/HTMLDocument.java | 71 ++++---- .../swing/text/html/MuxingAttributeSet.java | 36 ++-- .../javax/swing/text/html/StyleSheet.java | 75 ++++++++- .../html/HTMLDocument/HTMLStrikeOnly.java | 135 +++++++++++++++ .../html/HTMLDocument/HTMLTextDecoration.java | 157 ++++++++++++++++++ .../html/HTMLDocument/HTMLUnderlineOnly.java | 129 ++++++++++++++ .../HTMLDocument/HTMLUnderlineStrike.java | 2 +- 8 files changed, 563 insertions(+), 56 deletions(-) create mode 100644 test/jdk/javax/swing/text/html/HTMLDocument/HTMLStrikeOnly.java create mode 100644 test/jdk/javax/swing/text/html/HTMLDocument/HTMLTextDecoration.java create mode 100644 test/jdk/javax/swing/text/html/HTMLDocument/HTMLUnderlineOnly.java diff --git a/src/java.desktop/share/classes/javax/swing/text/html/CSS.java b/src/java.desktop/share/classes/javax/swing/text/html/CSS.java index 2b96b28dfbb..595799926a3 100644 --- a/src/java.desktop/share/classes/javax/swing/text/html/CSS.java +++ b/src/java.desktop/share/classes/javax/swing/text/html/CSS.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -843,6 +843,18 @@ public class CSS implements Serializable { return r != null ? r : conv.parseCssValue(key.getDefaultValue()); } + static Object mergeTextDecoration(String value) { + boolean underline = value.contains("underline"); + boolean strikeThrough = value.contains("line-through"); + if (!underline && !strikeThrough) { + return null; + } + String newValue = underline && strikeThrough + ? "underline,line-through" + : (underline ? "underline" : "line-through"); + return new StringValue().parseCssValue(newValue); + } + /** * Maps from a StyleConstants to a CSS Attribute. */ diff --git a/src/java.desktop/share/classes/javax/swing/text/html/HTMLDocument.java b/src/java.desktop/share/classes/javax/swing/text/html/HTMLDocument.java index 2b70e625325..c382b4e056a 100644 --- a/src/java.desktop/share/classes/javax/swing/text/html/HTMLDocument.java +++ b/src/java.desktop/share/classes/javax/swing/text/html/HTMLDocument.java @@ -2499,7 +2499,7 @@ public class HTMLDocument extends DefaultStyledDocument { tagMap.put(HTML.Tag.SCRIPT, ha); tagMap.put(HTML.Tag.SELECT, fa); tagMap.put(HTML.Tag.SMALL, ca); - tagMap.put(HTML.Tag.SPAN, ca); + tagMap.put(HTML.Tag.SPAN, new ConvertSpanAction()); tagMap.put(HTML.Tag.STRIKE, conv); tagMap.put(HTML.Tag.S, conv); tagMap.put(HTML.Tag.STRONG, ca); @@ -3423,11 +3423,43 @@ public class HTMLDocument extends DefaultStyledDocument { if (styleAttributes != null) { charAttr.addAttributes(styleAttributes); } + + convertAttributes(t, attr); } public void end(HTML.Tag t) { popCharacterStyle(); } + + /** + * Converts HTML tags to CSS attributes. + * @param t the current HTML tag + * @param attr the attributes of the HTML tag + */ + void convertAttributes(HTML.Tag t, MutableAttributeSet attr) { + } + } + + final class ConvertSpanAction extends CharacterAction { + @Override + void convertAttributes(HTML.Tag t, MutableAttributeSet attr) { + Object newDecoration = attr.getAttribute(CSS.Attribute.TEXT_DECORATION); + Object previousDecoration = + charAttrStack.peek() + .getAttribute(CSS.Attribute.TEXT_DECORATION); + + if (newDecoration != null + && !"none".equals(newDecoration.toString()) + && previousDecoration != null + && !"none".equals(previousDecoration.toString())) { + StyleSheet sheet = getStyleSheet(); + sheet.addCSSAttribute(charAttr, + CSS.Attribute.TEXT_DECORATION, + CSS.mergeTextDecoration(newDecoration + "," + + previousDecoration) + .toString()); + } + } } /** @@ -3435,35 +3467,9 @@ public class HTMLDocument extends DefaultStyledDocument { * mappings that have a corresponding StyleConstants * and CSS mapping. The conversion is to CSS attributes. */ - class ConvertAction extends TagAction { - - public void start(HTML.Tag t, MutableAttributeSet attr) { - pushCharacterStyle(); - if (!foundInsertTag) { - // Note that the third argument should really be based off - // inParagraph and impliedP. If we're wrong (that is - // insertTagDepthDelta shouldn't be changed), we'll end up - // removing an extra EndSpec, which won't matter anyway. - boolean insert = canInsertTag(t, attr, false); - if (foundInsertTag) { - if (!inParagraph) { - inParagraph = impliedP = true; - } - } - if (!insert) { - return; - } - } - if (attr.isDefined(IMPLIED)) { - attr.removeAttribute(IMPLIED); - } - if (styleAttributes != null) { - charAttr.addAttributes(styleAttributes); - } - // We also need to add attr, otherwise we lose custom - // attributes, including class/id for style lookups, and - // further confuse style lookup (doesn't have tag). - charAttr.addAttribute(t, attr.copyAttributes()); + final class ConvertAction extends CharacterAction { + @Override + void convertAttributes(HTML.Tag t, MutableAttributeSet attr) { StyleSheet sheet = getStyleSheet(); if (t == HTML.Tag.B) { sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_WEIGHT, "bold"); @@ -3504,11 +3510,6 @@ public class HTMLDocument extends DefaultStyledDocument { } } } - - public void end(HTML.Tag t) { - popCharacterStyle(); - } - } class AnchorAction extends CharacterAction { diff --git a/src/java.desktop/share/classes/javax/swing/text/html/MuxingAttributeSet.java b/src/java.desktop/share/classes/javax/swing/text/html/MuxingAttributeSet.java index 9d423dcded9..4d6ae0e0ae1 100644 --- a/src/java.desktop/share/classes/javax/swing/text/html/MuxingAttributeSet.java +++ b/src/java.desktop/share/classes/javax/swing/text/html/MuxingAttributeSet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,9 +24,16 @@ */ package javax.swing.text.html; -import javax.swing.text.*; import java.io.Serializable; -import java.util.*; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.stream.Collectors; + +import javax.swing.text.AttributeSet; +import javax.swing.text.MutableAttributeSet; +import javax.swing.text.SimpleAttributeSet; /** * An implementation of AttributeSet that can multiplex @@ -196,15 +203,24 @@ class MuxingAttributeSet implements AttributeSet, Serializable { * @see AttributeSet#getAttribute */ public Object getAttribute(Object key) { - AttributeSet[] as = getAttributes(); - int n = as.length; - for (int i = 0; i < n; i++) { - Object o = as[i].getAttribute(key); - if (o != null) { - return o; + final AttributeSet[] as = getAttributes(); + final int n = as.length; + if (key != CSS.Attribute.TEXT_DECORATION) { + for (int i = 0; i < n; i++) { + Object o = as[i].getAttribute(key); + if (o != null) { + return o; + } } + return null; } - return null; + + String values = Arrays.stream(as) + .map(a -> a.getAttribute(key)) + .filter(Objects::nonNull) + .map(Object::toString) + .collect(Collectors.joining(",")); + return CSS.mergeTextDecoration(values); } /** diff --git a/src/java.desktop/share/classes/javax/swing/text/html/StyleSheet.java b/src/java.desktop/share/classes/javax/swing/text/html/StyleSheet.java index fd3c75829e8..0790060f5e8 100644 --- a/src/java.desktop/share/classes/javax/swing/text/html/StyleSheet.java +++ b/src/java.desktop/share/classes/javax/swing/text/html/StyleSheet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,17 +24,53 @@ */ package javax.swing.text.html; -import sun.swing.SwingUtilities2; -import java.util.*; -import java.awt.*; -import java.io.*; -import java.net.*; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.Serializable; +import java.io.StringReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.EmptyStackException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Stack; +import java.util.StringTokenizer; +import java.util.Vector; + import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.UIManager; -import javax.swing.border.*; +import javax.swing.border.BevelBorder; +import javax.swing.border.Border; import javax.swing.event.ChangeListener; -import javax.swing.text.*; +import javax.swing.text.AttributeSet; +import javax.swing.text.Document; +import javax.swing.text.Element; +import javax.swing.text.MutableAttributeSet; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.Style; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyleContext; +import javax.swing.text.StyledDocument; +import javax.swing.text.View; + +import sun.swing.SwingUtilities2; /** * Support for defining the visual characteristics of @@ -2817,10 +2853,31 @@ public class StyleSheet extends StyleContext { return doGetAttribute(key); } + /** + * Merges the current value of the 'text-decoration' property + * with the value from parent. + */ + private Object getTextDecoration(Object value) { + AttributeSet parent = getResolveParent(); + if (parent == null) { + return value; + } + + Object parentValue = parent.getAttribute(CSS.Attribute.TEXT_DECORATION); + return parentValue == null + ? value + : CSS.mergeTextDecoration(value + "," + parentValue); + } + Object doGetAttribute(Object key) { Object retValue = super.getAttribute(key); if (retValue != null) { - return retValue; + if (key != CSS.Attribute.TEXT_DECORATION) { + return retValue; + } else { + // Merge current value with parent + return getTextDecoration(retValue); + } } if (key == CSS.Attribute.FONT_SIZE) { diff --git a/test/jdk/javax/swing/text/html/HTMLDocument/HTMLStrikeOnly.java b/test/jdk/javax/swing/text/html/HTMLDocument/HTMLStrikeOnly.java new file mode 100644 index 00000000000..5ec32f74d40 --- /dev/null +++ b/test/jdk/javax/swing/text/html/HTMLDocument/HTMLStrikeOnly.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; +import javax.swing.JEditorPane; +import javax.swing.text.View; +import javax.swing.text.html.CSS; + +/* + * @test + * @bug 8326734 + * @summary Tests different combinations of setting 'line-through' + * @run main HTMLStrikeOnly + */ +public class HTMLStrikeOnly { + private static final String HTML = """ + + + + + line-through + + + +

line-through?

+

line-through?

+

line-through?

+

line-through?

+ +

line-through?

+

line-through?

+

line-through?

+

line-through?

+ +

line-through?

+

line-through?

+

line-through?

+ +

line-through

+

line-through

+

line-through

+

line-through

+ + + """; + public static void main(String[] args) { + final JEditorPane html = new JEditorPane("text/html", HTML); + html.setEditable(false); + + final Dimension size = html.getPreferredSize(); + html.setSize(size); + + BufferedImage image = new BufferedImage(size.width, size.height, + BufferedImage.TYPE_INT_RGB); + Graphics g = image.createGraphics(); + // Paint the editor pane to ensure all views are created + html.paint(g); + g.dispose(); + + int errorCount = 0; + String firstError = null; + + System.out.println("----- Views -----"); + final View bodyView = html.getUI() + .getRootView(html) + .getView(1) + .getView(1); + for (int i = 0; i < bodyView.getViewCount(); i++) { + View pView = bodyView.getView(i); + View contentView = getContentView(pView); + + String decoration = + contentView.getAttributes() + .getAttribute(CSS.Attribute.TEXT_DECORATION) + .toString(); + + System.out.println(i + ": " + decoration); + if (!decoration.contains("line-through") + || decoration.contains("underline")) { + errorCount++; + if (firstError == null) { + firstError = "Line " + i + ": " + decoration; + } + } + } + + if (errorCount > 0) { + saveImage(image); + throw new RuntimeException(errorCount + " error(s) found, " + + "the first one: " + firstError); + } + } + + private static View getContentView(View parent) { + View view = parent.getView(0); + return view.getViewCount() > 0 + ? getContentView(view) + : view; + } + + private static void saveImage(BufferedImage image) { + try { + ImageIO.write(image, "png", + new File("html.png")); + } catch (IOException ignored) { } + } +} diff --git a/test/jdk/javax/swing/text/html/HTMLDocument/HTMLTextDecoration.java b/test/jdk/javax/swing/text/html/HTMLDocument/HTMLTextDecoration.java new file mode 100644 index 00000000000..a8868d05a5a --- /dev/null +++ b/test/jdk/javax/swing/text/html/HTMLDocument/HTMLTextDecoration.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; +import javax.swing.JEditorPane; +import javax.swing.text.View; +import javax.swing.text.html.CSS; + +/* + * @test + * @bug 8323801 8326734 + * @summary Tests different combination of 'underline' and 'line-through'; + * the text should render with both 'underline' and 'line-through'. + * @run main HTMLTextDecoration + */ +public final class HTMLTextDecoration { + private static final String HTML = """ + + + + + underline + line-through text + + + +

underline + line-through?

+

underline + line-through?

+

underline + line-through?

+ +

underline + line-through?

+

underline + line-through?

+

underline + line-through?

+ +

underline + line-through?

+

underline + line-through?

+ +

underline + line-through?

+

underline + line-through?

+

underline + line-through?

+ +

underline + line-through?

+

underline + line-through?

+ +

underline + line-through?

+

underline + line-through?

+

underline + line-through?

+ +

underline + line-through?

+

underline + line-through?

+ +
underline + line-through?
+
underline + line-through?
+
underline + line-through?
+ +
underline + line-through?
+
underline + line-through?
+ +

underline + line-through?

+

underline + line-through?

+ +
underline + line-through?
+
underline + line-through?
+ + + """; + + public static void main(String[] args) { + final JEditorPane html = new JEditorPane("text/html", HTML); + html.setEditable(false); + + final Dimension size = html.getPreferredSize(); + html.setSize(size); + + BufferedImage image = new BufferedImage(size.width, size.height, + BufferedImage.TYPE_INT_RGB); + Graphics g = image.createGraphics(); + // Paint the editor pane to ensure all views are created + html.paint(g); + g.dispose(); + + int errorCount = 0; + String firstError = null; + + System.out.println("----- Views -----"); + final View bodyView = html.getUI() + .getRootView(html) + .getView(1) + .getView(1); + for (int i = 0; i < bodyView.getViewCount(); i++) { + View pView = bodyView.getView(i); + View contentView = getContentView(pView); + + String decoration = + contentView.getAttributes() + .getAttribute(CSS.Attribute.TEXT_DECORATION) + .toString(); + + System.out.println(i + ": " + decoration); + if (!decoration.contains("underline") + || !decoration.contains("line-through")) { + errorCount++; + if (firstError == null) { + firstError = "Line " + i + ": " + decoration; + } + } + } + + if (errorCount > 0) { + saveImage(image); + throw new RuntimeException(errorCount + " error(s) found, " + + "the first one: " + firstError); + } + } + + private static View getContentView(View parent) { + View view = parent.getView(0); + return view.getViewCount() > 0 + ? getContentView(view) + : view; + } + + private static void saveImage(BufferedImage image) { + try { + ImageIO.write(image, "png", + new File("html.png")); + } catch (IOException ignored) { } + } +} diff --git a/test/jdk/javax/swing/text/html/HTMLDocument/HTMLUnderlineOnly.java b/test/jdk/javax/swing/text/html/HTMLDocument/HTMLUnderlineOnly.java new file mode 100644 index 00000000000..e142203b458 --- /dev/null +++ b/test/jdk/javax/swing/text/html/HTMLDocument/HTMLUnderlineOnly.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; +import javax.swing.JEditorPane; +import javax.swing.text.View; +import javax.swing.text.html.CSS; + +/* + * @test + * @bug 8326734 + * @summary Tests different combinations of setting 'underline' + * @run main HTMLUnderlineOnly + */ +public class HTMLUnderlineOnly { + private static final String HTML = """ + + + + + underline + + + +

underline?

+

underline?

+ +

underline?

+

underline?

+ +

underline?

+

underline?

+ +

underline

+

underline

+

underline

+ + + """; + public static void main(String[] args) { + final JEditorPane html = new JEditorPane("text/html", HTML); + html.setEditable(false); + + final Dimension size = html.getPreferredSize(); + html.setSize(size); + + BufferedImage image = new BufferedImage(size.width, size.height, + BufferedImage.TYPE_INT_RGB); + Graphics g = image.createGraphics(); + // Paint the editor pane to ensure all views are created + html.paint(g); + g.dispose(); + + int errorCount = 0; + String firstError = null; + + System.out.println("----- Views -----"); + final View bodyView = html.getUI() + .getRootView(html) + .getView(1) + .getView(1); + for (int i = 0; i < bodyView.getViewCount(); i++) { + View pView = bodyView.getView(i); + View contentView = getContentView(pView); + + String decoration = + contentView.getAttributes() + .getAttribute(CSS.Attribute.TEXT_DECORATION) + .toString(); + + System.out.println(i + ": " + decoration); + if (!decoration.contains("underline") + || decoration.contains("line-through")) { + errorCount++; + if (firstError == null) { + firstError = "Line " + i + ": " + decoration; + } + } + } + + if (errorCount > 0) { + saveImage(image); + throw new RuntimeException(errorCount + " error(s) found, " + + "the first one: " + firstError); + } + } + + private static View getContentView(View parent) { + View view = parent.getView(0); + return view.getViewCount() > 0 + ? getContentView(view) + : view; + } + + private static void saveImage(BufferedImage image) { + try { + ImageIO.write(image, "png", + new File("html.png")); + } catch (IOException ignored) { } + } +} diff --git a/test/jdk/javax/swing/text/html/HTMLDocument/HTMLUnderlineStrike.java b/test/jdk/javax/swing/text/html/HTMLDocument/HTMLUnderlineStrike.java index 5061321ea43..79aa5c81281 100644 --- a/test/jdk/javax/swing/text/html/HTMLDocument/HTMLUnderlineStrike.java +++ b/test/jdk/javax/swing/text/html/HTMLDocument/HTMLUnderlineStrike.java @@ -31,7 +31,7 @@ import javax.swing.text.html.HTMLEditorKit; /* * @test - * @bug 8323801 + * @bug 8323801 8326734 * @summary Tests that '' produce underlined and struck-through text */ public final class HTMLUnderlineStrike {