Skip to content

Commit d97d182

Browse files
BlackEgoistiText-CI
authored andcommitted
Add shorthand text decoration support
DEVSIX-3933
1 parent e83df62 commit d97d182

File tree

10 files changed

+519
-17
lines changed

10 files changed

+519
-17
lines changed

styled-xml-parser/src/main/java/com/itextpdf/styledxmlparser/css/CommonCssConstants.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,21 @@ public class CommonCssConstants {
573573
*/
574574
public static final String TEXT_DECORATION = "text-decoration";
575575

576+
/**
577+
* The Constant TEXT_DECORATION_LINE.
578+
*/
579+
public static final String TEXT_DECORATION_LINE = "text-decoration-line";
580+
581+
/**
582+
* The Constant TEXT_DECORATION_STYLE.
583+
*/
584+
public static final String TEXT_DECORATION_STYLE = "text-decoration-style";
585+
586+
/**
587+
* The Constant TEXT_DECORATION_COLOR.
588+
*/
589+
public static final String TEXT_DECORATION_COLOR = "text-decoration-color";
590+
576591
/**
577592
* The Constant TEXT_INDENT.
578593
*/
@@ -675,6 +690,11 @@ public class CommonCssConstants {
675690
*/
676691
public static final String AUTO = "auto";
677692

693+
/**
694+
* The Constant BLINK.
695+
*/
696+
public static final String BLINK = "blink";
697+
678698
/**
679699
* The Constant BOLD.
680700
*/
@@ -859,6 +879,11 @@ public class CommonCssConstants {
859879
*/
860880
public static final String LIGHTER = "lighter";
861881

882+
/**
883+
* The Constant value LINE_THROUGH.
884+
*/
885+
public static final String LINE_THROUGH = "line-through";
886+
862887
/**
863888
* The Constant LOCAL.
864889
*/
@@ -954,6 +979,11 @@ public class CommonCssConstants {
954979
*/
955980
public static final String OUTSET = "outset";
956981

982+
/**
983+
* The Constant value OVERLINE.
984+
*/
985+
public static final String OVERLINE= "overline";
986+
957987
/**
958988
* The Constant PADDING_BOX.
959989
*/
@@ -1109,6 +1139,11 @@ public class CommonCssConstants {
11091139
*/
11101140
public static final String TRANSPARENT = "transparent";
11111141

1142+
/**
1143+
* The Constant value UNDERLINE
1144+
*/
1145+
public static final String UNDERLINE = "underline";
1146+
11121147
/**
11131148
* The Constant UPPER_ALPHA.
11141149
*/
@@ -1129,6 +1164,11 @@ public class CommonCssConstants {
11291164
*/
11301165
public static final String VISIBLE = "visible";
11311166

1167+
/**
1168+
* The Constant value WAVY.
1169+
*/
1170+
public static final String WAVY = "wavy";
1171+
11321172
/**
11331173
* The Constant X_LARGE.
11341174
*/

styled-xml-parser/src/main/java/com/itextpdf/styledxmlparser/css/resolve/CssDefaults.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,10 @@ public class CssDefaults {
136136

137137
defaultValues.put(CommonCssConstants.TEXT_ALIGN, CommonCssConstants.START);
138138
defaultValues.put(CommonCssConstants.TEXT_DECORATION, CommonCssConstants.NONE);
139+
defaultValues.put(CommonCssConstants.TEXT_DECORATION_LINE, CommonCssConstants.NONE);
140+
defaultValues.put(CommonCssConstants.TEXT_DECORATION_STYLE, CommonCssConstants.SOLID);
141+
defaultValues.put(CommonCssConstants.TEXT_DECORATION_COLOR, CommonCssConstants.CURRENTCOLOR);
139142
defaultValues.put(CommonCssConstants.TEXT_TRANSFORM, CommonCssConstants.NONE);
140-
defaultValues.put(CommonCssConstants.TEXT_DECORATION, CommonCssConstants.NONE);
141143

142144
defaultValues.put(CommonCssConstants.WHITE_SPACE, CommonCssConstants.NORMAL);
143145
defaultValues.put(CommonCssConstants.WIDTH, CommonCssConstants.AUTO);

styled-xml-parser/src/main/java/com/itextpdf/styledxmlparser/css/resolve/shorthand/ShorthandResolverFactory.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ This file is part of the iText (R) project.
6060
import com.itextpdf.styledxmlparser.css.resolve.shorthand.impl.MarginShorthandResolver;
6161
import com.itextpdf.styledxmlparser.css.resolve.shorthand.impl.OutlineShorthandResolver;
6262
import com.itextpdf.styledxmlparser.css.resolve.shorthand.impl.PaddingShorthandResolver;
63+
import com.itextpdf.styledxmlparser.css.resolve.shorthand.impl.TextDecorationShorthandResolver;
6364

6465
import java.util.HashMap;
6566
import java.util.Map;
@@ -88,7 +89,7 @@ public class ShorthandResolverFactory {
8889
shorthandResolvers.put(CommonCssConstants.MARGIN, new MarginShorthandResolver());
8990
shorthandResolvers.put(CommonCssConstants.OUTLINE, new OutlineShorthandResolver());
9091
shorthandResolvers.put(CommonCssConstants.PADDING, new PaddingShorthandResolver());
91-
// TODO text-decoration is a shorthand in CSS3, however it is not yet supported in any major browsers
92+
shorthandResolvers.put(CommonCssConstants.TEXT_DECORATION, new TextDecorationShorthandResolver());
9293
}
9394

9495
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.itextpdf.styledxmlparser.css.resolve.shorthand.impl;
2+
3+
import com.itextpdf.styledxmlparser.css.CommonCssConstants;
4+
import com.itextpdf.styledxmlparser.css.CssDeclaration;
5+
import com.itextpdf.styledxmlparser.css.resolve.shorthand.IShorthandResolver;
6+
7+
import java.util.ArrayList;
8+
import java.util.Arrays;
9+
import java.util.HashSet;
10+
import java.util.List;
11+
import java.util.Set;
12+
13+
public class TextDecorationShorthandResolver implements IShorthandResolver {
14+
15+
private static final Set<String> TEXT_DECORATION_LINE_VALUES = new HashSet<>(Arrays.asList(
16+
CommonCssConstants.UNDERLINE, CommonCssConstants.OVERLINE, CommonCssConstants.LINE_THROUGH,
17+
CommonCssConstants.BLINK
18+
));
19+
20+
private static final Set<String> TEXT_DECORATION_STYLE_VALUES = new HashSet<>(Arrays.asList(
21+
CommonCssConstants.SOLID, CommonCssConstants.DOUBLE, CommonCssConstants.DOTTED,
22+
CommonCssConstants.DASHED, CommonCssConstants.WAVY
23+
));
24+
25+
@Override
26+
public List<CssDeclaration> resolveShorthand(String shorthandExpression) {
27+
if (CommonCssConstants.INITIAL.equals(shorthandExpression) || CommonCssConstants.INHERIT
28+
.equals(shorthandExpression)) {
29+
return Arrays.asList(
30+
new CssDeclaration(CommonCssConstants.TEXT_DECORATION_LINE, shorthandExpression),
31+
new CssDeclaration(CommonCssConstants.TEXT_DECORATION_STYLE, shorthandExpression),
32+
new CssDeclaration(CommonCssConstants.TEXT_DECORATION_COLOR, shorthandExpression));
33+
}
34+
35+
//regexp for separating line by spaces that are not inside the parentheses, so rgb()
36+
// and hsl() color declarations are parsed correctly
37+
String[] props = shorthandExpression.split("\\s+(?![^\\(]*\\))");
38+
39+
List<String> textDecorationLineValues = new ArrayList<>();
40+
String textDecorationStyleValue = null;
41+
String textDecorationColorValue = null;
42+
43+
for (String value : props) {
44+
//For text-decoration-line attributes several attributes may be present at once.
45+
//However when "none" attribute is present, all the other attributes become invalid
46+
if (TEXT_DECORATION_LINE_VALUES.contains(value)
47+
|| CommonCssConstants.NONE.equals(value)) {
48+
textDecorationLineValues.add(value);
49+
} else if (TEXT_DECORATION_STYLE_VALUES.contains(value)) {
50+
textDecorationStyleValue = value;
51+
} else if (!value.isEmpty()) {
52+
textDecorationColorValue = value;
53+
}
54+
}
55+
56+
List<CssDeclaration> resolvedDecl = new ArrayList<>();
57+
if (textDecorationLineValues.isEmpty()) {
58+
resolvedDecl.add(new CssDeclaration(CommonCssConstants.TEXT_DECORATION_LINE, CommonCssConstants.INITIAL));
59+
} else {
60+
StringBuilder resultLine = new StringBuilder();
61+
for (String line : textDecorationLineValues) {
62+
resultLine.append(line).append(" ");
63+
}
64+
resolvedDecl.add(new CssDeclaration(CommonCssConstants.TEXT_DECORATION_LINE, resultLine.toString().trim()));
65+
}
66+
67+
resolvedDecl.add(new CssDeclaration(CommonCssConstants.TEXT_DECORATION_STYLE,
68+
textDecorationStyleValue == null ? CommonCssConstants.INITIAL : textDecorationStyleValue));
69+
resolvedDecl.add(new CssDeclaration(CommonCssConstants.TEXT_DECORATION_COLOR,
70+
textDecorationColorValue == null ? CommonCssConstants.INITIAL : textDecorationColorValue));
71+
return resolvedDecl;
72+
}
73+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package com.itextpdf.styledxmlparser.util;
2+
3+
import com.itextpdf.io.util.DecimalFormatUtil;
4+
import com.itextpdf.styledxmlparser.css.CommonCssConstants;
5+
import com.itextpdf.styledxmlparser.css.resolve.CssPropertyMerger;
6+
import com.itextpdf.styledxmlparser.css.resolve.IStyleInheritance;
7+
import com.itextpdf.styledxmlparser.css.util.CssUtils;
8+
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
import java.util.Map;
12+
import java.util.Set;
13+
14+
public final class StyleUtil {
15+
16+
private StyleUtil() {
17+
18+
}
19+
20+
/**
21+
* List to store the properties whose value can depend on parent or element font-size
22+
*/
23+
private static final List<String> fontSizeDependentPercentage = new ArrayList<String>(2);
24+
25+
static {
26+
fontSizeDependentPercentage.add(CommonCssConstants.FONT_SIZE);
27+
fontSizeDependentPercentage.add(CommonCssConstants.LINE_HEIGHT);
28+
}
29+
30+
/**
31+
* Merge parent CSS declarations.
32+
*
33+
* @param styles the styles map
34+
* @param styleProperty the CSS property
35+
* @param parentPropValue the parent properties value
36+
* @param inheritanceRules set of inheritance rules
37+
*
38+
* @return a map of updated styles after merging parent and child style declarations
39+
*/
40+
public static Map<String, String> mergeParentStyleDeclaration(Map<String, String> styles, String styleProperty, String parentPropValue, String parentFontSizeString, Set<IStyleInheritance> inheritanceRules) {
41+
String childPropValue = styles.get(styleProperty);
42+
if ((childPropValue == null && checkInheritance(styleProperty, inheritanceRules)) || CommonCssConstants.INHERIT.equals(childPropValue)) {
43+
if (valueIsOfMeasurement(parentPropValue, CommonCssConstants.EM)
44+
|| valueIsOfMeasurement(parentPropValue, CommonCssConstants.EX)
45+
|| valueIsOfMeasurement(parentPropValue, CommonCssConstants.PERCENTAGE) && fontSizeDependentPercentage.contains(styleProperty)) {
46+
float absoluteParentFontSize = CssUtils.parseAbsoluteLength(parentFontSizeString);
47+
// Format to 4 decimal places to prevent differences between Java and C#
48+
styles.put(styleProperty, DecimalFormatUtil
49+
.formatNumber(CssUtils.parseRelativeValue(parentPropValue, absoluteParentFontSize),
50+
"0.####") + CommonCssConstants.PT);
51+
} else {
52+
styles.put(styleProperty, parentPropValue);
53+
}
54+
} else if (CommonCssConstants.TEXT_DECORATION_LINE.equals(styleProperty)
55+
&& !CommonCssConstants.INLINE_BLOCK.equals(styles.get(CommonCssConstants.DISPLAY))) {
56+
// Note! This property is formally not inherited, but the browsers behave very similar to inheritance here.
57+
// Text decorations on inline boxes are drawn across the entire element,
58+
// going across any descendant elements without paying any attention to their presence.
59+
// Also, when, for example, parent element has text-decoration:underline, and the child text-decoration:overline,
60+
// then the text in the child will be both overline and underline. This is why the declarations are merged
61+
// See TextDecorationTest#textDecoration01Test
62+
styles.put(styleProperty, CssPropertyMerger.mergeTextDecoration(childPropValue, parentPropValue));
63+
}
64+
65+
return styles;
66+
}
67+
68+
/**
69+
* Check all inheritance rule-sets to see if the passed property is inheritable
70+
*
71+
* @param styleProperty property identifier to check
72+
* @param inheritanceRules a set of inheritance rules
73+
* @return True if the property is inheritable by one of the rule-sets,
74+
* false if it is not marked as inheritable in all rule-sets
75+
*/
76+
private static boolean checkInheritance(String styleProperty, Set<IStyleInheritance> inheritanceRules) {
77+
for (IStyleInheritance inheritanceRule : inheritanceRules) {
78+
if (inheritanceRule.isInheritable(styleProperty)) {
79+
return true;
80+
}
81+
}
82+
return false;
83+
}
84+
85+
/**
86+
* Check to see if the passed value is a measurement of the type based on the passed measurement symbol string
87+
*
88+
* @param value string containing value to check
89+
* @param measurement measurement symbol (e.g. % for relative, px for pixels)
90+
* @return True if the value is numerical and ends with the measurement symbol, false otherwise
91+
*/
92+
private static boolean valueIsOfMeasurement(String value, String measurement) {
93+
if (value == null) {
94+
return false;
95+
}
96+
return value.endsWith(measurement) && CssUtils
97+
.isNumericValue(value.substring(0, value.length() - measurement.length()).trim());
98+
}
99+
}

0 commit comments

Comments
 (0)