This Enhancement Request is not yet accepted so it's internaly referenced in Java Bug Database as JI-9073933.
This extends the previous closed JDK-4895924 issue see table below.
The present CSS implementation accepts 3 digits or 6 digits hex coded Color as following :
With the CSS 1 convention : #rgb must be interpreted as #rrggbb opaque Color.
Only the 6 highest digits are parsed.
When 1, 2, 4 and 5 digits, they are supposed represent the lowest digits. The missing digits to reach 6 digits are replaced with 0 (This is not a CSS recommendation)
Then #f0a is interpreted as #ff00aa opaque Color (Correct).
And #f00a is interpreted as #00f00a opaque Color (Wrong).
I request to reach CSS Color Level 4 recommendation which accepts 3, 4, 6 and 8 digits.
With the following CSS C L 4 conventions:
#rgb must be interpreted as #rrggbb opaque Color.
#rgba must be interpreted as #rrggbbaa Color with an aa Alpha value.
#rrggbb must be interpreted as #rrggbb opaque Color.
#rrggbbaa must be interpreted as #rrggbbaa Color with an aa Alpha value.
only a maximum of 8 highest digits in the string are parsed. Strings with length = 1, 2, 5, 7 or wrong digits return null to avoid to return a wrong color.
For example : #f00a will be interpreted as #ff0000aa.
I chose a strict interpretation of the recommendation.
Impacts of these modifications.
This PR modifies the CSS.java CopyRight date.
This PR modifies CSS.javahexToColor method
by adding two hex format code,
by treating only 2, 3, 4 and 8 digits well formated hex code, all other strings return null.
Refactoring rgb method will be treated in another PR. rgba is a legacy format, it would be better to not mention it in documentation even if CSS.java takes it in charge. In fact rgb could be reached trough rgba.
Done : Performance impact is not evaluated. Enhancements must be at least without performance cost.
As suggested by a main-line reviewer, chartAt replace substring methods.
As suggested by a main-line reviewer, try to improve tests with < rather than ==. In fact, try to mix his proposition in my test flow.
Optimized version using Map : Main-line discussions lead to this new approach.
Previous performance results were not very good.
Debate was stuck by minor performance considerations.
This new proposal seems to solve all difficulties.
Performance results are 6 to 50 times better than previous implementions .
The codes before this PR ran in 230ms.
Previous codes ran in 1 200ms to 1800 ms with String + formatted + %n$s usage.
They ran in 350ms to 380ms with String + formatted + %s usage.
And in 100ms to 110ms if we replace String + format with a string concatenation.
The code below with one Map gives the same results (including number format validation) in 36ms.
Since we are controlling notation length we
can bypass some controls,
directly generate the color value,
without generate a new string,
and reject a wrong number format without generate any exception.
Done : Problem with charAt out of bounds possible ! Not when using integrated parsing.
static final Color hexToColor(String digits) {
// CSS level 4
// - defines color hex code as #[2 digits Red][2 digits Green][2 digits Blue][2 digits Alpha]. With digit 0 ... f.
// - allows, webpage passes 3, 4, 6 or 8 digit color code.
// - 3 digits #[R][G][B] ........ represents #[RR][GG][BB]FF
// - 4 digits #[R][G][B][A] ..... represents #[RR][GG][BB][AA]
// - 6 digits #[RR][GG][BB] ..... represents #[RR][GG][BB]FF
// - 8 digits #[RR][GG][BB][AA] . represents #[RR][GG][BB][AA]
//
// Becareful ! In java.awt.Color hex #[2 digits Alpha][2 digits Red][2 digits Green][2 digits Blue]
// As this method is declared in CSS.java, it must treat only format CSS Level 4.
//
// According notes below the current OpenJDK implementation is
// - 3 digits #[R][G][B] represents #[RR][GG][BB]FF
// - 6 digits #[R][G][B] represents #[RR][GG][BB]FF
//
// Some webpage passe 3 digit color code as in #fff which is
// decoded as #000FFF resulting in blue background.
// As per https://www.w3.org/TR/CSS1/#color-units,
// The three-digit RGB notation (#rgb) is converted into six-digit form
// (#rrggbb) by replicating digits, not by adding zeros.
// This makes sure that white (#ffffff) can be specified with the short notation
// (#fff) and removes any dependencies on the color depth of the display.
final int st = digits.startsWith("#") ? 1 : 0;
final byte[] idseq = digit.get(Integer.valueOf(digits.length() - st));
if (idseq == null) {
// Rejects string argument with a wrong number length.
return null;
}
// Only 3, 4, 6 and 8 digits notations.
// Parses the string argument and build color value
final long value = parseHex(st, digits, idseq);
return (value == -1 ? null : new Color((int)value, true));
}
private static final long parseHex(int st, String digits, byte[] idseq) {
int dv;
long value = 0;
for (byte i : idseq) {
value *= 16;
dv = 15;
if (i != -1) {
if ((dv = Character.digit(digits.charAt(st + i), 16)) < 0) {
// Rejects string argument with not a valid digit in the radix-16
return -1;
}
}
value += dv;
}
return value;
}
// Index of the Digit in the Sequence -1 means, let the default value.
private static Map<Integer, byte[]> digit =
Map.ofEntries(
Map.entry(Integer.valueOf(3), new byte[]{-1, -1, 0, 0, 1, 1, 2, 2}),
Map.entry(Integer.valueOf(4), new byte[]{3, 3, 0, 0, 1, 1, 2, 2}),
Map.entry(Integer.valueOf(6), new byte[]{-1, -1, 0, 1, 2, 3, 4, 5}),
Map.entry(Integer.valueOf(8), new byte[]{6, 7, 0, 1, 2, 3, 4, 5})
);
Notes :
It compiles.
It runs and pass the test, see results below (Custom Pars.).
static final Color hexToColor(String digits) {
// CSS level 4
// - defines color hex code as #[2 digits Red][2 digits Green][2 digits Blue][2 digits Alpha]. With digit 0 ... f.
// - allows, webpage passes 3, 4, 6 or 8 digit color code.
// - 3 digits #[R][G][B] ........ represents #[RR][GG][BB]FF
// - 4 digits #[R][G][B][A] ..... represents #[RR][GG][BB][AA]
// - 6 digits #[RR][GG][BB] ..... represents #[RR][GG][BB]FF
// - 8 digits #[RR][GG][BB][AA] . represents #[RR][GG][BB][AA]
//
// Becareful ! In java.awt.Color hex #[2 digits Alpha][2 digits Red][2 digits Green][2 digits Blue]
// As this method is declared in CSS.java, it must treat only format CSS Level 4.
//
// According notes below the current OpenJDK implementation is
// - 3 digits #[R][G][B] represents #[RR][GG][BB]FF
// - 6 digits #[R][G][B] represents #[RR][GG][BB]FF
//
// Some webpage passe 3 digit color code as in #fff which is
// decoded as #000FFF resulting in blue background.
// As per https://www.w3.org/TR/CSS1/#color-units,
// The three-digit RGB notation (#rgb) is converted into six-digit form
// (#rrggbb) by replicating digits, not by adding zeros.
// This makes sure that white (#ffffff) can be specified with the short notation
// (#fff) and removes any dependencies on the color depth of the display.
final int st = digits.startsWith("#") ? 1 : 0;
final byte[] idseq = digit.get(Integer.valueOf(digits.length() - st));
if (idseq == null) {
// Rejects string argument with a wrong number length.
return null;
}
// Only 3, 4, 6 and 8 digits notations.
// Parses the string argument and build color value
final long value = parseHex(st, digits, idseq);
return (value == -1 ? null : new Color((int)value, true));
}
private static final long parseHex(int st, String digits, byte[] idseq) {
int dv;
long value = 0;
for (byte i : idseq) {
dv = 15;
if (i != -1 && (dv = Character.digit(digits.charAt(st + i), 16)) < 0) {
// Rejects string argument with not a valid digit in the radix-16
return -1;
}
value = value * 16 + dv;
}
return value;
}
// Index of the Digit in the Sequence -1 means, let the default value.
private static Map<Integer, byte[]> digit =
Map.ofEntries(
Map.entry(Integer.valueOf(3), new byte[]{-1, -1, 0, 0, 1, 1, 2, 2}),
Map.entry(Integer.valueOf(4), new byte[]{3, 3, 0, 0, 1, 1, 2, 2}),
Map.entry(Integer.valueOf(6), new byte[]{-1, -1, 0, 1, 2, 3, 4, 5}),
Map.entry(Integer.valueOf(8), new byte[]{6, 7, 0, 1, 2, 3, 4, 5})
);
Notes :
Simplification of the code.
static final Color hexToColor(String digits) {
// CSS level 4
// - defines color hex code as #[2 digits Red][2 digits Green][2 digits Blue][2 digits Alpha]. With digit 0 ... f.
// - allows, webpage passes 3, 4, 6 or 8 digit color code.
// - 3 digits #[R][G][B] ........ represents #[RR][GG][BB]FF
// - 4 digits #[R][G][B][A] ..... represents #[RR][GG][BB][AA]
// - 6 digits #[RR][GG][BB] ..... represents #[RR][GG][BB]FF
// - 8 digits #[RR][GG][BB][AA] . represents #[RR][GG][BB][AA]
//
// Becareful ! In java.awt.Color hex #[2 digits Alpha][2 digits Red][2 digits Green][2 digits Blue]
// As this method is declared in CSS.java, it must treat only format CSS Level 4.
//
// According notes below the current OpenJDK implementation is
// - 3 digits #[R][G][B] represents #[RR][GG][BB]FF
// - 6 digits #[R][G][B] represents #[RR][GG][BB]FF
//
// Some webpage passe 3 digit color code as in #fff which is
// decoded as #000FFF resulting in blue background.
// As per https://www.w3.org/TR/CSS1/#color-units,
// The three-digit RGB notation (#rgb) is converted into six-digit form
// (#rrggbb) by replicating digits, not by adding zeros.
// This makes sure that white (#ffffff) can be specified with the short notation
// (#fff) and removes any dependencies on the color depth of the display.
final int st = digits.startsWith("#") ? 1 : 0;
final byte[] idseq = digit.get(Integer.valueOf(digits.length() - st));
if (idseq == null) {
// Rejects string argument with a wrong number length.
return null;
}
// Only 3, 4, 6 and 8 digits notations.
// Parses the string argument and build color value
final long value = parseHex(st, digits, idseq);
return (value == -1 ? null : new Color((int)value, true));
}
private static final long parseHex(int st, String digits, byte[] idseq) {
int dv;
long value = 0;
for (byte i : idseq) {
if ((dv = i) != 15 && (dv = Character.digit(digits.charAt(st + i), 16)) < 0) {
// Rejects string argument with not a valid digit in the radix-16
return -1;
}
value = (value << 4) + dv;
}
return value;
}
// Index/Value of the Digit in the Sequence -1 means, let the default value.
private static Map<Integer, byte[]> digit =
Map.ofEntries(
// 0-7 Index of the digit value in the sequence.
// 15 value of the digit.
Map.entry(Integer.valueOf(3), new byte[]{15, 15, 0, 0, 1, 1, 2, 2}),
Map.entry(Integer.valueOf(4), new byte[]{3, 3, 0, 0, 1, 1, 2, 2}),
Map.entry(Integer.valueOf(6), new byte[]{15, 15, 0, 1, 2, 3, 4, 5}),
Map.entry(Integer.valueOf(8), new byte[]{6, 7, 0, 1, 2, 3, 4, 5})
);
Notes :
Replaces -1 by 15.
And * 16 by << 4.
static final Color hexToColor(String digits) {
// CSS level 4
// - defines color hex code as #[2 digits Red][2 digits Green][2 digits Blue][2 digits Alpha]. With digit 0 ... f.
// - allows, webpage passes 3, 4, 6 or 8 digit color code.
// - 3 digits #[R][G][B] ........ represents #[RR][GG][BB]FF
// - 4 digits #[R][G][B][A] ..... represents #[RR][GG][BB][AA]
// - 6 digits #[RR][GG][BB] ..... represents #[RR][GG][BB]FF
// - 8 digits #[RR][GG][BB][AA] . represents #[RR][GG][BB][AA]
//
// Becareful ! In java.awt.Color hex #[2 digits Alpha][2 digits Red][2 digits Green][2 digits Blue]
// As this method is declared in CSS.java, it must treat only format CSS Level 4.
//
// According notes below the current OpenJDK implementation is
// - 3 digits #[R][G][B] represents #[RR][GG][BB]FF
// - 6 digits #[R][G][B] represents #[RR][GG][BB]FF
//
// Some webpage passe 3 digit color code as in #fff which is
// decoded as #000FFF resulting in blue background.
// As per https://www.w3.org/TR/CSS1/#color-units,
// The three-digit RGB notation (#rgb) is converted into six-digit form
// (#rrggbb) by replicating digits, not by adding zeros.
// This makes sure that white (#ffffff) can be specified with the short notation
// (#fff) and removes any dependencies on the color depth of the display.
final byte st = digits.startsWith("#") ? (byte)1 : (byte)0;
final byte[] idseq = digit.get(Integer.valueOf(digits.length() - st));
if (idseq == null) {
// Rejects string argument with a wrong number length.
return null;
}
// Only 3, 4, 6 and 8 digits notations.
// Parses the string argument and build color value
final int value = parseHex(st, digits, idseq);
return (value == -2 ? null : new Color(value, true));
}
private static final int parseHex(byte st, String digits, byte[] idseq) {
int dv;
int value = 0;
for (byte i : idseq) {
if ((dv = i) != 15 && (dv = Character.digit(digits.charAt(i + st), 16)) < 0) {
// Rejects string argument with not a valid digit in the radix-16
return -2;
}
value = dv | value << 4;
}
return value;
}
// Index of the Digit Value or simply the Digit Value.
private static Map<Integer, byte[]> digit =
Map.ofEntries(
// From 0 to 7, index of the digit value in the sequence.
// 15, the default digit value.
Map.entry(Integer.valueOf(3), new byte[]{15, 15, 0, 0, 1, 1, 2, 2}),
Map.entry(Integer.valueOf(4), new byte[]{3, 3, 0, 0, 1, 1, 2, 2}),
Map.entry(Integer.valueOf(6), new byte[]{15, 15, 0, 1, 2, 3, 4, 5}),
Map.entry(Integer.valueOf(8), new byte[]{6, 7, 0, 1, 2, 3, 4, 5})
);
Notes :
Replaces long by int.
Replaces + by |.
static final Color hexToColor(String digits) {
// CSS level 4
// - defines color hex code as #[2 digits Red][2 digits Green][2 digits Blue][2 digits Alpha]. With digit 0 ... f.
// - allows, webpage passes 3, 4, 6 or 8 digit color code.
// - 3 digits #[R][G][B] ........ represents #[RR][GG][BB]FF
// - 4 digits #[R][G][B][A] ..... represents #[RR][GG][BB][AA]
// - 6 digits #[RR][GG][BB] ..... represents #[RR][GG][BB]FF
// - 8 digits #[RR][GG][BB][AA] . represents #[RR][GG][BB][AA]
//
// Becareful ! In java.awt.Color hex #[2 digits Alpha][2 digits Red][2 digits Green][2 digits Blue]
// As this method is declared in CSS.java, it must treat only format CSS Level 4.
//
// According notes below the current OpenJDK implementation is
// - 3 digits #[R][G][B] represents #[RR][GG][BB]FF
// - 6 digits #[R][G][B] represents #[RR][GG][BB]FF
//
// Some webpage passe 3 digit color code as in #fff which is
// decoded as #000FFF resulting in blue background.
// As per https://www.w3.org/TR/CSS1/#color-units,
// The three-digit RGB notation (#rgb) is converted into six-digit form
// (#rrggbb) by replicating digits, not by adding zeros.
// This makes sure that white (#ffffff) can be specified with the short notation
// (#fff) and removes any dependencies on the color depth of the display.
final byte st = digits.startsWith("#") ? (byte)1 : (byte)0;
final byte[] idseq = digit.get(Integer.valueOf(digits.length() - st));
if (idseq == null) {
// Rejects string argument with a wrong number length.
return null;
}
// Only 3, 4, 6 and 8 digits notations.
// Parses the string argument and build color value.
int dv;
int value = 0;
for (byte i : idseq) {
if ((dv = i) != 15 && (dv = Character.digit(digits.charAt(i + st), 16)) < 0) {
// Rejects string argument with not a valid digit in the radix-16
return null;
}
value = dv | value << 4;
}
return new Color(value, true);
}
// Index of the Digit Value or simply the Digit Value.
private static Map<Integer, byte[]> digit =
Map.ofEntries(
// From 0 to 7, index of the digit value in the sequence.
// 15, the default digit value.
Map.entry(Integer.valueOf(3), new byte[]{15, 15, 0, 0, 1, 1, 2, 2}),
Map.entry(Integer.valueOf(4), new byte[]{3, 3, 0, 0, 1, 1, 2, 2}),
Map.entry(Integer.valueOf(6), new byte[]{15, 15, 0, 1, 2, 3, 4, 5}),
Map.entry(Integer.valueOf(8), new byte[]{6, 7, 0, 1, 2, 3, 4, 5})
);
Notes :
Back to an integrated parsing process rather than using a parsing method.
static final Color hexToColor(String digits) {
// CSS level 4
// - defines color hex code as #[2 digits Red][2 digits Green][2 digits Blue][2 digits Alpha]. With digit 0 ... f.
// - allows, webpage passes 3, 4, 6 or 8 digit color code.
// - 3 digits #[R][G][B] ........ represents #[RR][GG][BB]FF
// - 4 digits #[R][G][B][A] ..... represents #[RR][GG][BB][AA]
// - 6 digits #[RR][GG][BB] ..... represents #[RR][GG][BB]FF
// - 8 digits #[RR][GG][BB][AA] . represents #[RR][GG][BB][AA]
//
// Becareful ! In java.awt.Color hex #[2 digits Alpha][2 digits Red][2 digits Green][2 digits Blue]
// As this method is declared in CSS.java, it must treat only format CSS Level 4.
//
// According notes below the current OpenJDK implementation is
// - 3 digits #[R][G][B] represents #[RR][GG][BB]FF
// - 6 digits #[R][G][B] represents #[RR][GG][BB]FF
//
// Some webpage passes 3 digit color code as in #fff which is
// decoded as #000FFF resulting in blue background.
// As per https://www.w3.org/TR/CSS1/#color-units,
// The three-digit RGB notation (#rgb) is converted into six-digit form
// (#rrggbb) by replicating digits, not by adding zeros.
// This makes sure that white (#ffffff) can be specified with the short notation
// (#fff) and removes any dependencies on the color depth of the display.
final byte[] idseq = digits.startsWith("#") ?
digitl.get(Long.valueOf(digits.length())):
digitl.get(Long.valueOf(-digits.length()));
if (idseq == null) {
// Rejects string argument with a wrong number length.
return null;
}
// Only 3, 4, 6 and 8 digits notations.
// Parses the string argument and build color value.
int dv;
int value = 0;
for (byte i : idseq) {
if ((dv = i) != 15 && (dv = Character.digit(digits.charAt(i), 16)) < 0) {
// Rejects string argument with not a valid digit in the radix-16.
return null;
}
value = dv | value << 4;
}
return new Color(value, true);
}
// Index of the Digit Value or simply the Digit Value. # not prefixed.
private static Map<Long, byte[]> digitl =
Map.ofEntries(
// From 1 to 8, index of the digit value in the sequence, when # prefixed
// From 0 to 7, index of the digit value in the sequence, when # not prefixed
// 15, the default digit value.
Map.entry(Long.valueOf(4), new byte[]{15, 15, 1, 1, 2, 2, 3, 3}),
Map.entry(Long.valueOf(5), new byte[]{4, 4, 1, 1, 2, 2, 3, 3}),
Map.entry(Long.valueOf(7), new byte[]{15, 15, 1, 2, 3, 4, 5, 6}),
Map.entry(Long.valueOf(9), new byte[]{7, 8, 1, 2, 3, 4, 6, 6}),
Map.entry(Long.valueOf(-3), new byte[]{15, 15, 0, 0, 1, 1, 2, 2}),
Map.entry(Long.valueOf(-4), new byte[]{3, 3, 0, 0, 1, 1, 2, 2}),
Map.entry(Long.valueOf(-6), new byte[]{15, 15, 0, 1, 2, 3, 4, 5}),
Map.entry(Long.valueOf(-8), new byte[]{6, 7, 0, 1, 2, 3, 4, 5})
);
Notes :
Maps the two case prefixed and not prefixed.
Becareful some Long operations are not atomic. Then retry to use string. First times, performances seemed no good, test again !
/**
* Convert a "#FFF", "#FFFF", "#FFFFFF" or "#FFFFFFFF" hex string to a Color.
* If the color specification is bad, an attempt will be made to fix it up.
*/
static final Color hexToColor(String digits) {
// CSS Color level 4 allows webpage passes 3, 4, 6 or 8 digit color codes.
// - 3 digits #[R][G][B] ........ represents #[RR][GG][BB]FF
// - 4 digits #[R][G][B][A] ..... represents #[RR][GG][BB][AA]
// - 6 digits #[RR][GG][BB] ..... represents #[RR][GG][BB]FF
// - 8 digits #[RR][GG][BB][AA] . represents #[RR][GG][BB][AA]
final byte[] iseq = digits.startsWith("#") ?
iseqmap.get(Integer.valueOf(digits.length())):
iseqmap.get(Integer.valueOf(-digits.length()));
if (iseq == null) {
// Rejects string argument with a wrong number length.
return null;
}
// Only 3, 4, 6 and 8 digits notations.
// Parses the string argument and build color value.
int dv;
int value = 0;
for (byte i : iseq) {
if ((dv = -i) != 15 && (dv = Character.digit(digits.charAt(i), 16)) < 0) {
// Rejects string argument with not a valid digit in the radix-16.
return null;
}
value = dv | value << 4;
}
return new Color(value, true);
}
// Map of Index Sequences. Index -15 means, use the default value 15.
private static final Map<Integer, byte[]> iseqmap =
Map.ofEntries(
// Positive key, for # prefixed string, is associated with index from 1 to 8.
// Negative key, for not # prefixed string, is associated with index from 0 to 7.
Map.entry(Integer.valueOf(4), new byte[]{-15, -15, 1, 1, 2, 2, 3, 3}),
Map.entry(Integer.valueOf(5), new byte[]{4, 4, 1, 1, 2, 2, 3, 3}),
Map.entry(Integer.valueOf(7), new byte[]{-15, -15, 1, 2, 3, 4, 5, 6}),
Map.entry(Integer.valueOf(9), new byte[]{7, 8, 1, 2, 3, 4, 6, 6}),
Map.entry(Integer.valueOf(-3), new byte[]{-15, -15, 0, 0, 1, 1, 2, 2}),
Map.entry(Integer.valueOf(-4), new byte[]{3, 3, 0, 0, 1, 1, 2, 2}),
Map.entry(Integer.valueOf(-6), new byte[]{-15, -15, 0, 1, 2, 3, 4, 5}),
Map.entry(Integer.valueOf(-8), new byte[]{6, 7, 0, 1, 2, 3, 4, 5})
);
Notes :
Maps the two case prefixed and not prefixed.
Back to Integer.
This method is too strict ! Test with "#1fe56980 ", "#1fe56980 ". Made changes to be able to parse this kind of string. Unnecessary, all leading and trailing spaces have been removed during the parsing process.
Should I declare idseq as final for the same reasons a, b, c were declared final in the previous version.
The code to test the present implementation.
Done : This is not a regression test. The regression test has been published.
Done : Test all values red, green, blue and alpha, but only for this repository, main line stream preferred a more simple one.
public static void main(String[] args) {
StringBuilder result = new StringBuilder();
boolean passed = true;
int red, green, blue, alpha;
Color color;
StyleSheet styleSheet = new StyleSheet();
// #rgba Should be interpreted as #rrggbbaa according CSS Color Level 4.
// Then expecting r=255 g=17 b=34 a=170
color = styleSheet.stringToColor("#f12a");
alpha = color.getAlpha();
result.append(" Result for #f00a -> expected r=255 g=17 b=34 a=170 -> %s Alpha : %s".formatted(color.toString(), alpha));
if (alpha != 170) {
passed = false;
}
// In #rrggbbaa last two digits should be interpreted as Alpha value according CSS Color Level 4.
// Then expecting r=255 g=17 b=34 a=170
color = styleSheet.stringToColor("#ff1122aa");
alpha = color.getAlpha();
result.append(" Result for #ff1122aa -> expected r=255 g=17 b=34 a=170 -> %s Alpha : %s\n".formatted(color.toString(), alpha));
if (alpha != 170) {
passed = false;
}
result.insert(0, passed ? "Succeed " : "Failed :");
System.out.println(result);
}
}
Implementation evaluations : according the number of tests to reach a case
|Nb of digits|Original Code|Binary tree|Sequential + duplicate| Duplicate only|
|----|---|---|---|----|
|0|4|2|1|5|
|1|4|2|1|5|
|2|4|2|1|5|
|**3**|**1**|**3**|**3**|**2**|
|**4**|**2**|**3**|**3**|**3**|
|5|4|3|2|5|
|**6**|**3**|**3**|**3**|**3**|
|7|4|3|3|5|
|**8**|**4**|**3**|**4**|**5**|
|9|4|3|4|5|
|...|4|3|4|5|
### Test results :
[scientificwaredev@localhost openjdk-compile-tests]$ ./ce_jdk_unpatched_test_Hex3468DigitsColorInCSS
Failed : Result for #f00a -> expected r=255 g=17 b=34 a=170 -> java.awt.Color[r=0,g=241,b=42] Alpha : 255 Result for #ff1122aa -> expected r=255 g=17 b=34 a=170 -> java.awt.Color[r=255,g=17,b=34] Alpha : 255
[scientificwaredev@localhost openjdk-compile-tests]$ ./ce_jdk_patched_test_Hex3468DigitsColorInCSS
Succeed Result for #f00a -> expected r=255 g=17 b=34 a=170 -> java.awt.Color[r=255,g=17,b=34] Alpha : 170 Result for #ff1122aa -> expected r=255 g=17 b=34 a=170 -> java.awt.Color[r=255,g=17,b=34] Alpha : 170
### Regression test
https://github.com/scientificware/jdk/blob/24040cf664ecdeb2678796761331edfc60a6b897/test/jdk/javax/swing/text/html/CSS/Hex3468DigitsColor.java#L36-L92
[scientificwaredev@localhost openjdk-compile-tests]$ ./ce_jdk_unpatched_test_Hex3468DigitsColor
Exception in thread "main" java.lang.RuntimeException: Failed : Test for #f00a, expected r=255 but r=0 found, expected g=17 but g=241 found, expected b=34 but b=42 found, expected a=170 but a=255 found
Test for #ff1122aa, expected r=255 but r=0 found, expected g=17 but g=241 found, expected b=34 but b=42 found, expected a=170 but a=255 found
at Hex3468DigitsColor.main(Hex3468DigitsColor.java:90)
### About performances
- Enhancements must be at least without performance cost.
- What is tested :
- 1 000 occurences of :
- a set of 1 600 valid color notations, equi-distributed (Not Real WWW).
- and 1 wrong color notation.
|Run|Nb Tests|First Imp. subString (ms)|Sugg Imp. char %n$s (ms)|Sugg. Imp. char %n$s dis. mis comb. 3 & 4 (ms)|Bin. Tree (ms)|Bin. Tree (ms)|Bin. Tree (ms)|Bin. Tree (ms)|dis. mis comb. 3 & 4 (ms)|Custom Pars. 1 Map (ms)|Before PR (ms)|
|----|----|----|----|----|----|----|----|----|----|----|----|
|format `%s`||:heavy_check_mark:||||:heavy_check_mark:||||||
|format `%n$s`|||:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|||||||
|`""+r+...`|||||||:heavy_check_mark:||:heavy_check_mark:|||
|bit rotation||||||||:heavy_check_mark:||||
|hex Pattern||:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:||||
|||||||||:3rd_place_medal:|:2nd_place_medal:|:1st_place_medal:|||
|1|1 601 000|351|1 790|1 825|1 860|374|178|180|110|36|229|
|2|1 601 000|346|1 253|1 297|1 913|385|177|180|110|35|233|
|3|1 601 000|365|1 783|1 763|1 887|369|179|185|108|36|228|
|4|1 601 000|346|1 824|1 698|1 820|375|178|184|109|36|230|
|5|1 601 000|364|1 767|1 823|1 958|375|180|178|111|37|229|
### CSS Level 4 details
- defines color hex code as `#[2 digits Red][2 digits Green][2 digits Blue][2 digits Alpha]`. With digit 0 ... f.
- allows, webpage passes 3, 4, 6 or 8 digit color code.
- `3 digits #[R][G][B]` ........ represents `#[RR][GG][BB]FF`
- 4 digits `#[R][G][B][A]` ..... represents `#[RR][GG][BB][AA]`
- 6 digits `#[RR][GG][BB]` ..... represents `#[RR][GG][BB]FF`
- 8 digits `#[RR][GG][BB][AA]` . represents `#[RR][GG][BB][AA]`
Becareful ! In java.awt.Color hex `#[2 digits Alpha][2 digits Red][2 digits Green][2 digits Blue]`
Since this method is defined in CSS.java, it must only take in charge CSS Level 4 color format.
As explained in the code, the current OpenJDK implementation follows [CSS1 Recommandation](https://www.w3.org/TR/CSS1/#color-units)
- 3 digits `#[R][G][B]` represents `#[RR][GG][BB]FF`
- 6 digits `#[R][G][B]` represents `#[RR][GG][BB]FF`
[JDK-4895924 : Strings in format #rgb not handled by Color.decode() (affects CSS / Swing)](https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4895924)
[JDK-4895924 : Strings in format #rgb not handled by Color.decode() (affects CSS / Swing)](https://bugs.openjdk.org/browse/JDK-4895924)
----
|Id.|Title|Java Bug Data Base|OpenJDK Bug System|Status|
|----|----|----|----|----|
|JDK-4895924|Strings in format #rgb not handled by Color.decode() (affects CSS / Swing)|[Report](https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4895924)|[Details](https://bugs.openjdk.org/browse/JDK-4895924)|:closed_lock_with_key:|
|JDK-8149631|rgb(...) CSS color values are not parsed properly|[Report](https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8149631)|[Details](https://bugs.openjdk.org/browse/JDK-8149631)|:closed_lock_with_key:|
The purpose of this issue is to add CSS 4 and 8 digits hex coded Color in CSS.java.
This is referenced in Java Bug Database as
This is tracked in JBS as
Related Pull Request
<rgb()>
and<rgba()>
function behavioursAdds the 4 and 8 digits color hex notations to CSS.java, as described in : CSS Color Module Level 4 W3C Candidate Recommendation Snapshot, 5 July 2022 4.1 The
<Color>
syntax 6.2 The RGB Hexadecimal Notations:#RRGGBB
Status
This Enhancement Request is not yet accepted so it's internaly referenced in Java Bug Database as JI-9073933.This extends the previous closed JDK-4895924 issue see table below.
The present CSS implementation accepts 3 digits or 6 digits hex coded Color as following :
#rgb
must be interpreted as#rrggbb
opaque Color.#f0a
is interpreted as#ff00aa
opaque Color (Correct).#f00a
is interpreted as#00f00a
opaque Color (Wrong).I request to reach CSS Color Level 4 recommendation which accepts 3, 4, 6 and 8 digits.
#rgb
must be interpreted as#rrggbb
opaque Color.#rgba
must be interpreted as#rrggbbaa
Color with an aa Alpha value.#rrggbb
must be interpreted as#rrggbb
opaque Color.#rrggbbaa
must be interpreted as#rrggbbaa
Color with an aa Alpha value.#f00a
will be interpreted as#ff0000aa
.Impacts of these modifications.
CSS.java
hexToColor
methodrgb
method will be treated in another PR.rgba
is a legacy format, it would be better to not mention it in documentation even if CSS.java takes it in charge. In fact rgb could be reached troughrgba
.Integer.rotateRight(int i, int distance)
method +Color(int rgba, boolean hasalpha)
constructor. Test will determine which is the best. Conclusion, none gain in time, only https://github.com/scientificware/jdk/blob/50672004a47885bfc74c0fd189d4f1184a57ad08/src/java.desktop/share/classes/javax/swing/text/html/CSS.java#L1354-L1411Details :
()
multiple white-space (according to Java) occurences are replaced by only one space character.rgb
orrgba
and(
are removed.Implementation examples
First proposition https://github.com/scientificware/jdk/blob/7d7f49fe8a3f88e7fba8dcb68bda30ef891c6598/src/java.desktop/share/classes/javax/swing/text/html/CSS.java#L1354-L1412 Notes :
chartAt
replacesubstring
methods.<
rather than==
. In fact, try to mix his proposition in my test flow.Optimized version using
Map
: Main-line discussions lead to this new approach.String
+formatted
+%n$s
usage.String
+formatted
+%s
usage.String
+format
with a string concatenation.charAt
out of bounds possible ! Not when using integrated parsing.Notes :
Notes :
Notes :
* 16
by<< 4
.Notes :
long
byint
.+
by|
.Notes :
Notes :
Long
operations are not atomic. Then retry to use string. First times, performances seemed no good, test again !Notes :
This method is too strict ! Test with "#1fe56980 ", "#1fe56980 ". Made changes to be able to parse this kind of string.Unnecessary, all leading and trailing spaces have been removed during the parsing process.Should I declareidseq
asfinal
for the same reasonsa
,b
,c
were declaredfinal
in the previous version.The code to test the present implementation.
public class Hex3468DigitsColorInCSS {
}
[scientificwaredev@localhost openjdk-compile-tests]$ ./ce_jdk_unpatched_test_Hex3468DigitsColorInCSS Failed : Result for #f00a -> expected r=255 g=17 b=34 a=170 -> java.awt.Color[r=0,g=241,b=42] Alpha : 255 Result for #ff1122aa -> expected r=255 g=17 b=34 a=170 -> java.awt.Color[r=255,g=17,b=34] Alpha : 255
[scientificwaredev@localhost openjdk-compile-tests]$ ./ce_jdk_patched_test_Hex3468DigitsColorInCSS Succeed Result for #f00a -> expected r=255 g=17 b=34 a=170 -> java.awt.Color[r=255,g=17,b=34] Alpha : 170 Result for #ff1122aa -> expected r=255 g=17 b=34 a=170 -> java.awt.Color[r=255,g=17,b=34] Alpha : 170
[scientificwaredev@localhost openjdk-compile-tests]$ ./ce_jdk_unpatched_test_Hex3468DigitsColor Exception in thread "main" java.lang.RuntimeException: Failed : Test for #f00a, expected r=255 but r=0 found, expected g=17 but g=241 found, expected b=34 but b=42 found, expected a=170 but a=255 found Test for #ff1122aa, expected r=255 but r=0 found, expected g=17 but g=241 found, expected b=34 but b=42 found, expected a=170 but a=255 found at Hex3468DigitsColor.main(Hex3468DigitsColor.java:90)
[scientificwaredev@localhost openjdk-compile-tests]$ ./ce_jdk_patched_test_Hex3468DigitsColor