From ce3ce607aec6d5382181ee218758cb59dcf659f0 Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Thu, 7 Nov 2024 16:30:31 +0100 Subject: [PATCH 1/8] LibWeb: Load otf fonts, used by wpt tests --- Libraries/LibWeb/CSS/StyleComputer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/LibWeb/CSS/StyleComputer.cpp b/Libraries/LibWeb/CSS/StyleComputer.cpp index ac8e688d89e..ccdfbf29e99 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -260,7 +260,7 @@ ErrorOr> FontLoader::try_load_font() mime_type = MimeSniff::Resource::sniff(resource()->encoded_data(), Web::MimeSniff::SniffingConfiguration { .sniffing_context = Web::MimeSniff::SniffingContext::Font }); } if (mime_type.has_value()) { - if (mime_type->essence() == "font/ttf"sv || mime_type->essence() == "application/x-font-ttf"sv) { + if (mime_type->essence() == "font/ttf"sv || mime_type->essence() == "application/x-font-ttf"sv || mime_type->essence() == "font/otf"sv) { if (auto result = Gfx::Typeface::try_load_from_externally_owned_memory(resource()->encoded_data()); !result.is_error()) { return result; } From 2b316d16ce369b1662a04da0037fcc4ec7fd34ce Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Sat, 9 Nov 2024 19:02:04 +0100 Subject: [PATCH 2/8] WPT: Import /css/css-fonts/font-variant-*.html tests --- .prettierignore | 1 + .../LibWeb/Ref/data/font-variant-features.css | 63 +++++ .../LibWeb/Ref/data/font-variant-features.js | 218 ++++++++++++++++++ Tests/LibWeb/Ref/data/gsubtest-features.js | 85 +++++++ Tests/LibWeb/Ref/data/gsubtest-lookup3.otf | Bin 0 -> 289724 bytes .../css/css-fonts/font-variant-caps-ref.html | 21 ++ .../font-variant-east-asian-ref.html | 21 ++ .../css-fonts/font-variant-ligatures-ref.html | 21 ++ .../css-fonts/font-variant-numeric-ref.html | 21 ++ .../css-fonts/font-variant-position-ref.html | 21 ++ .../css/css-fonts/font-variant-caps.html | 24 ++ .../css-fonts/font-variant-east-asian.html | 24 ++ .../css/css-fonts/font-variant-ligatures.html | 24 ++ .../css/css-fonts/font-variant-numeric.html | 24 ++ .../css/css-fonts/font-variant-position.html | 24 ++ UI/Headless/Test.cpp | 1 + 16 files changed, 593 insertions(+) create mode 100644 Tests/LibWeb/Ref/data/font-variant-features.css create mode 100644 Tests/LibWeb/Ref/data/font-variant-features.js create mode 100644 Tests/LibWeb/Ref/data/gsubtest-features.js create mode 100644 Tests/LibWeb/Ref/data/gsubtest-lookup3.otf create mode 100644 Tests/LibWeb/Ref/expected/wpt-import/css/css-fonts/font-variant-caps-ref.html create mode 100644 Tests/LibWeb/Ref/expected/wpt-import/css/css-fonts/font-variant-east-asian-ref.html create mode 100644 Tests/LibWeb/Ref/expected/wpt-import/css/css-fonts/font-variant-ligatures-ref.html create mode 100644 Tests/LibWeb/Ref/expected/wpt-import/css/css-fonts/font-variant-numeric-ref.html create mode 100644 Tests/LibWeb/Ref/expected/wpt-import/css/css-fonts/font-variant-position-ref.html create mode 100644 Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-caps.html create mode 100644 Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-east-asian.html create mode 100644 Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-ligatures.html create mode 100644 Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-numeric.html create mode 100644 Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-position.html diff --git a/.prettierignore b/.prettierignore index 1c285bdbfbd..36ed47f769d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -8,5 +8,6 @@ Libraries/LibJS/Tests/modules/top-level-dispose.mjs Libraries/LibJS/Tests/using-declaration.js Libraries/LibJS/Tests/using-for-loops.js +Tests/LibWeb/Ref/data Tests/LibWeb/Ref/input/wpt-import Tests/LibWeb/Text/input/wpt-import diff --git a/Tests/LibWeb/Ref/data/font-variant-features.css b/Tests/LibWeb/Ref/data/font-variant-features.css new file mode 100644 index 00000000000..b3dc28307e0 --- /dev/null +++ b/Tests/LibWeb/Ref/data/font-variant-features.css @@ -0,0 +1,63 @@ +body { margin: 10px; } + +@font-face { + font-family: gsub-test; + src: url(gsubtest-lookup3.otf); +} + +td.prop { + font-family: Menlo, monospace; + font-weight: normal; + text-align: left; + font-size: 80%; +} + +td.features { + font-family: gsub-test; +} + +.invalid { + color: red; +} + +@font-feature-values gsub-test { + @styleset { ok-alt-a: 1 3 5; ok-alt-b: 19; } + @character-variant { ok-1: 78 2; } + @character-variant { ok-3: 23; } + @character-variant { not-good: 0 2; } + @annotation { ok-4: 1; } + @annotation { bogus-font-doesnt-support: 23; } + @annotation { circled: 1; } + @character-variant { multi-def: 4; } + @annotation { multi-def: 3; } + @styleset { multi-def2: 3 4 5; } + @styleset { MULTI-def2: 2 6; } + @styleset { out-of-bounds1: 0; out-of-bounds2: 100; } +} + +@font-feature-values bogus-family { + @styleset { bogus: 3 4 7; } +} + +@font-feature-values GSUB-tEsT { + @styleset { mixed-case: 3 4 7; } +} + +@font-feature-values gSuB-tEsT { + @styleset { 3blah: 1 3; } +} + +@font-feature-values gSuB-tEsT { + @styleset { moxie: 14; } + @styleset { 3blah: 1; } +} + +@font-feature-values gSUB-TeST { + @styleset { moxie2: 14; } + @bongo { blah: 1; } +} + +@font-feature-values gSUB-TEst { + @bongo { blah2: 1; } + @styleset { moxie3: 14; } +} diff --git a/Tests/LibWeb/Ref/data/font-variant-features.js b/Tests/LibWeb/Ref/data/font-variant-features.js new file mode 100644 index 00000000000..3b022d52c0a --- /dev/null +++ b/Tests/LibWeb/Ref/data/font-variant-features.js @@ -0,0 +1,218 @@ + +// data associated with gsubtest test font for testing font features + +// prefix +gPrefix = ""; + +// equivalent properties +// setting prop: value should match the specific feature settings listed +// +// each of these tests evaluate whether a given feature is enabled as required +// and also whether features that shouldn't be enabled are or not. +var gPropertyData = [ + // font-variant-caps + // valid values + { prop: "font-variant-caps", value: "normal", features: {"smcp": 0} }, + { prop: "font-variant-caps", value: "small-caps", features: {"smcp": 1, "c2sc": 0} }, + { prop: "font-variant-caps", value: "all-small-caps", features: {"smcp": 1, "c2sc": 1, "pcap": 0} }, + { prop: "font-variant-caps", value: "petite-caps", features: {"pcap": 1, "smcp": 0} }, + { prop: "font-variant-caps", value: "all-petite-caps", features: {"c2pc": 1, "pcap": 1, "smcp": 0} }, + { prop: "font-variant-caps", value: "titling-caps", features: {"titl": 1, "smcp": 0} }, + { prop: "font-variant-caps", value: "unicase", features: {"unic": 1, "titl": 0} }, + + // invalid values + { prop: "font-variant-caps", value: "normal small-caps", features: {"smcp": 0}, invalid: true }, + { prop: "font-variant-caps", value: "small-caps potato", features: {"smcp": 0}, invalid: true }, + { prop: "font-variant-caps", value: "small-caps petite-caps", features: {"smcp": 0, "pcap": 0}, invalid: true }, + { prop: "font-variant-caps", value: "small-caps all-small-caps", features: {"smcp": 0, "c2sc": 0}, invalid: true }, + { prop: "font-variant-caps", value: "small-cap", features: {"smcp": 0}, invalid: true }, + + // font-variant-east-asian + // valid values + { prop: "font-variant-east-asian", value: "jis78", features: {"jp78": 1, "jp04": 0} }, + { prop: "font-variant-east-asian", value: "jis83", features: {"jp83": 1, "jp04": 0} }, + { prop: "font-variant-east-asian", value: "jis90", features: {"jp90": 1, "jp04": 0} }, + { prop: "font-variant-east-asian", value: "jis04", features: {"jp04": 1, "jp78": 0} }, + { prop: "font-variant-east-asian", value: "simplified", features: {"smpl": 1, "jp04": 0} }, + { prop: "font-variant-east-asian", value: "traditional", features: {"trad": 1, "jp04": 0} }, + { prop: "font-variant-east-asian", value: "full-width", features: {"fwid": 1, "jp04": 0} }, + { prop: "font-variant-east-asian", value: "proportional-width", features: {"pwid": 1, "jp04": 0} }, + { prop: "font-variant-east-asian", value: "ruby", features: {"ruby": 1, "jp04": 0} }, + { prop: "font-variant-east-asian", value: "jis78 full-width", features: {"jp78": 1, "fwid": 1, "jp83": 0} }, + { prop: "font-variant-east-asian", value: "jis78 full-width ruby", features: {"jp78": 1, "fwid": 1, "jp83": 0, "ruby": 1} }, + { prop: "font-variant-east-asian", value: "simplified proportional-width", features: {"smpl": 1, "pwid": 1, "jp83": 0} }, + { prop: "font-variant-east-asian", value: "ruby simplified", features: {"ruby": 1, "smpl": 1, "trad": 0} }, + + // invalid values + { prop: "font-variant-east-asian", value: "ruby normal", features: {"ruby": 0}, invalid: true }, + { prop: "font-variant-east-asian", value: "jis90 jis04", features: {"jp90": 0, "jp04": 0}, invalid: true }, + { prop: "font-variant-east-asian", value: "simplified traditional", features: {"smpl": 0, "trad": 0}, invalid: true }, + { prop: "font-variant-east-asian", value: "full-width proportional-width", features: {"fwid": 0, "pwid": 0}, invalid: true }, + { prop: "font-variant-east-asian", value: "ruby simplified ruby", features: {"ruby": 0, "smpl": 0, "jp04": 0}, invalid: true }, + { prop: "font-variant-east-asian", value: "jis78 ruby simplified", features: {"ruby": 0, "smpl": 0, "jp78": 0}, invalid: true }, + + // font-variant-ligatures + // valid values + { prop: "font-variant-ligatures", value: "normal", features: {"liga": 1, "dlig": 0} }, + { prop: "font-variant-ligatures", value: "common-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0, "calt": 1} }, + { prop: "font-variant-ligatures", value: "no-common-ligatures", features: {"liga": 0, "clig": 0, "dlig": 0, "hlig": 0, "calt": 1} }, + { prop: "font-variant-ligatures", value: "discretionary-ligatures", features: {"liga": 1, "clig": 1, "dlig": 1, "hlig": 0, "calt": 1} }, + { prop: "font-variant-ligatures", value: "no-discretionary-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0, "calt": 1} }, + { prop: "font-variant-ligatures", value: "historical-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 1, "calt": 1} }, + { prop: "font-variant-ligatures", value: "no-historical-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0, "calt": 1} }, + { prop: "font-variant-ligatures", value: "contextual", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0, "calt": 1} }, + { prop: "font-variant-ligatures", value: "no-contextual", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0, "calt": 0} }, + { prop: "font-variant-ligatures", value: "common-ligatures no-discretionary-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0, "calt": 1} }, + { prop: "font-variant-ligatures", value: "historical-ligatures no-common-ligatures", features: {"clig": 0, "liga": 0, "dlig": 0, "hlig": 1, "calt": 1} }, + { prop: "font-variant-ligatures", value: "no-historical-ligatures discretionary-ligatures", features: {"liga": 1, "clig": 1, "dlig": 1, "hlig": 0, "calt": 1} }, + { prop: "font-variant-ligatures", value: "common-ligatures no-discretionary-ligatures historical-ligatures no-contextual", features: {"clig": 1, "dlig": 0, "hlig": 1, "liga": 1, "calt": 0} }, + + // invalid values + { prop: "font-variant-ligatures", value: "common-ligatures normal", features: {"liga": 1, "clig": 1, "dlig": 0}, invalid: true }, + { prop: "font-variant-ligatures", value: "common-ligatures no-common-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0}, invalid: true }, + { prop: "font-variant-ligatures", value: "common-ligatures common-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0}, invalid: true }, + { prop: "font-variant-ligatures", value: "no-historical-ligatures historical-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0}, invalid: true }, + { prop: "font-variant-ligatures", value: "no-contextual contextual", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0}, invalid: true }, + { prop: "font-variant-ligatures", value: "no-discretionary-ligatures discretionary-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0}, invalid: true }, + { prop: "font-variant-ligatures", value: "common-ligatures no-discretionary-ligatures no-common-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0}, invalid: true }, + + // font-variant-numeric + // valid values + { prop: "font-variant-numeric", value: "normal", features: {"lnum": 0, "tnum": 0, "pnum": 0, "onum": 0} }, + { prop: "font-variant-numeric", value: "lining-nums", features: {"lnum": 1, "onum": 0, "pnum": 0} }, + { prop: "font-variant-numeric", value: "oldstyle-nums", features: {"lnum": 0, "onum": 1, "pnum": 0} }, + { prop: "font-variant-numeric", value: "proportional-nums", features: {"lnum": 0, "onum": 0, "pnum": 1, "tnum": 0} }, + { prop: "font-variant-numeric", value: "proportional-nums oldstyle-nums", features: {"lnum": 0, "onum": 1, "pnum": 1, "tnum": 0} }, + { prop: "font-variant-numeric", value: "tabular-nums", features: {"tnum": 1, "onum": 0, "pnum": 0} }, + { prop: "font-variant-numeric", value: "diagonal-fractions", features: {"frac": 1, "afrc": 0, "pnum": 0} }, + { prop: "font-variant-numeric", value: "stacked-fractions", features: {"frac": 0, "afrc": 1, "pnum": 0} }, + { prop: "font-variant-numeric", value: "slashed-zero", features: {"zero": 1, "pnum": 0} }, + { prop: "font-variant-numeric", value: "ordinal", features: {"ordn": 1, "pnum": 0} }, + { prop: "font-variant-numeric", value: "lining-nums diagonal-fractions", features: {"frac": 1, "afrc": 0, "lnum": 1} }, + { prop: "font-variant-numeric", value: "tabular-nums stacked-fractions", features: {"frac": 0, "afrc": 1, "tnum": 1} }, + { prop: "font-variant-numeric", value: "tabular-nums slashed-zero stacked-fractions", features: {"frac": 0, "afrc": 1, "tnum": 1, "zero": 1} }, + { prop: "font-variant-numeric", value: "proportional-nums slashed-zero diagonal-fractions oldstyle-nums ordinal", features: {"frac": 1, "afrc": 0, "tnum": 0, "pnum": 1, "onum": 1, "ordn": 1, "zero": 1} }, + + // invalid values + { prop: "font-variant-numeric", value: "lining-nums normal", features: {"lnum": 0, "onum": 0}, invalid: true }, + { prop: "font-variant-numeric", value: "lining-nums oldstyle-nums", features: {"lnum": 0, "onum": 0}, invalid: true }, + { prop: "font-variant-numeric", value: "lining-nums normal slashed-zero ordinal", features: {"lnum": 0, "onum": 0, "zero": 0}, invalid: true }, + { prop: "font-variant-numeric", value: "proportional-nums tabular-nums", features: {"pnum": 0, "tnum": 0}, invalid: true }, + { prop: "font-variant-numeric", value: "diagonal-fractions stacked-fractions", features: {"frac": 0, "afrc": 0}, invalid: true }, + { prop: "font-variant-numeric", value: "slashed-zero diagonal-fractions slashed-zero", features: {"frac": 0, "afrc": 0, "zero": 0}, invalid: true }, + { prop: "font-variant-numeric", value: "lining-nums slashed-zero diagonal-fractions oldstyle-nums", features: {"frac": 0, "afrc": 0, "zero": 0, "onum": 0}, invalid: true }, + + // font-variant-position + // valid values + { prop: "font-variant-position", value: "normal", features: {"subs": 0, "sups": 0} }, + { prop: "font-variant-position", value: "super", features: {"subs": 0, "sups": 1} }, + { prop: "font-variant-position", value: "sub", features: {"subs": 1, "sups": 0} }, + + // invalid values + { prop: "font-variant-position", value: "super sub", features: {"subs": 0, "sups": 0}, invalid: true }, +]; + +// note: the code below requires an array "gFeatures" from : +// support/fonts/gsubtest-features.js + +// The font defines feature lookups for all OpenType features for a +// specific set of PUA codepoints, as listed in the gFeatures array. +// Using these codepoints and feature combinations, tests can be +// constructed to detect when certain features are enabled or not. + +// return a created table containing tests for a given property +// +// Ex: { prop: "font-variant-ligatures", value: "common-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0} } +// +// This means that for the property 'font-variant-ligatures' with the value 'common-ligatures', the features listed should +// either be explicitly enabled or disabled. + +// propData is the prop/value list with corresponding feature assertions +// whichProp is either "all" or a specific subproperty (i.e. "font-variant-position") +// isRef is true when this is the reference +// debug outputs the prop/value pair along with the tests + +function createFeatureTestTable(propData, whichProp, isRef, debug) +{ + var table = document.createElement("table"); + + if (typeof(isRef) == "undefined") { + isRef = false; + } + + if (typeof(debug) == "undefined") { + debug = false; + } + + var doAll = (whichProp == "all"); + for (var i in propData) { + var data = propData[i]; + + if (!doAll && data.prop != whichProp) continue; + + var row = document.createElement("tr"); + var invalid = false; + if ("invalid" in data) { + invalid = true; + row.className = "invalid"; + } + + var cell = document.createElement("td"); + cell.className = "prop"; + var styledecl = gPrefix + data.prop + ": " + data.value + ";"; + cell.innerHTML = styledecl; + row.appendChild(cell); + if (debug) { + table.appendChild(row); + } + + row = document.createElement("tr"); + if (invalid) { + row.className = "invalid"; + } + + cell = document.createElement("td"); + cell.className = "features"; + if (!isRef) { + cell.style.cssText = styledecl; + } + + for (var f in data.features) { + var feature = data.features[f]; + + var cp, unsupported = "F".charCodeAt(0); + var basecp = gFeatures[f]; + + if (typeof(basecp) == "undefined") { + cp = unsupported; + } else { + switch(feature) { + case 0: + cp = basecp; + break; + case 1: + cp = basecp + 1; + break; + case 2: + cp = basecp + 2; + break; + case 3: + cp = basecp + 3; + break; + default: + cp = basecp + 1; + break; + } + } + + var span = document.createElement("span"); + span.innerHTML = (isRef ? "P " : "&#x" + cp.toString(16) + "; "); + span.title = f + "=" + feature; + cell.appendChild(span); + } + row.appendChild(cell); + table.appendChild(row); + } + + return table; +} diff --git a/Tests/LibWeb/Ref/data/gsubtest-features.js b/Tests/LibWeb/Ref/data/gsubtest-features.js new file mode 100644 index 00000000000..52b2e2e2499 --- /dev/null +++ b/Tests/LibWeb/Ref/data/gsubtest-features.js @@ -0,0 +1,85 @@ + +/* This file is autogenerated by makegsubfonts.py */ + +/* + Features defined in gsubtest fonts with associated base + codepoints for each feature: + + cp = codepoint for feature featX + + cp default PASS + cp featX=1 FAIL + cp featX=2 FAIL + + cp+1 default FAIL + cp+1 featX=1 PASS + cp+1 featX=2 FAIL + + cp+2 default FAIL + cp+2 featX=1 FAIL + cp+2 featX=2 PASS + +*/ + +var gFeatures = { + "MWL1": 0xe000, "NUM2": 0xe004, "PRIV": 0xe008, "T3ST": 0xe00c, + "TPSP": 0xe010, "abvf": 0xe014, "abvm": 0xe018, "abvs": 0xe01c, + "afrc": 0xe020, "akhn": 0xe024, "blwf": 0xe028, "blwm": 0xe02c, + "blws": 0xe030, "c2pc": 0xe034, "c2sc": 0xe038, "calt": 0xe03c, + "case": 0xe040, "ccmp": 0xe044, "cfar": 0xe048, "cjct": 0xe04c, + "clig": 0xe050, "cpct": 0xe054, "cpsp": 0xe058, "cswh": 0xe05c, + "curs": 0xe060, "cv00": 0xe064, "cv01": 0xe068, "cv02": 0xe06c, + "cv03": 0xe070, "cv04": 0xe074, "cv05": 0xe078, "cv06": 0xe07c, + "cv07": 0xe080, "cv08": 0xe084, "cv09": 0xe088, "cv10": 0xe08c, + "cv11": 0xe090, "cv12": 0xe094, "cv13": 0xe098, "cv14": 0xe09c, + "cv15": 0xe0a0, "cv16": 0xe0a4, "cv17": 0xe0a8, "cv18": 0xe0ac, + "cv19": 0xe0b0, "cv20": 0xe0b4, "cv21": 0xe0b8, "cv22": 0xe0bc, + "cv23": 0xe0c0, "cv24": 0xe0c4, "cv25": 0xe0c8, "cv26": 0xe0cc, + "cv27": 0xe0d0, "cv28": 0xe0d4, "cv29": 0xe0d8, "cv30": 0xe0dc, + "cv31": 0xe0e0, "cv32": 0xe0e4, "cv33": 0xe0e8, "cv34": 0xe0ec, + "cv35": 0xe0f0, "cv36": 0xe0f4, "cv37": 0xe0f8, "cv38": 0xe0fc, + "cv39": 0xe100, "cv40": 0xe104, "cv41": 0xe108, "cv42": 0xe10c, + "cv43": 0xe110, "cv44": 0xe114, "cv45": 0xe118, "cv46": 0xe11c, + "cv47": 0xe120, "cv48": 0xe124, "cv49": 0xe128, "cv50": 0xe12c, + "cv51": 0xe130, "cv52": 0xe134, "cv53": 0xe138, "cv54": 0xe13c, + "cv55": 0xe140, "cv56": 0xe144, "cv57": 0xe148, "cv58": 0xe14c, + "cv59": 0xe150, "cv60": 0xe154, "cv61": 0xe158, "cv62": 0xe15c, + "cv63": 0xe160, "cv64": 0xe164, "cv65": 0xe168, "cv66": 0xe16c, + "cv67": 0xe170, "cv68": 0xe174, "cv69": 0xe178, "cv70": 0xe17c, + "cv71": 0xe180, "cv72": 0xe184, "cv73": 0xe188, "cv74": 0xe18c, + "cv75": 0xe190, "cv76": 0xe194, "cv77": 0xe198, "cv78": 0xe19c, + "cv79": 0xe1a0, "cv80": 0xe1a4, "cv81": 0xe1a8, "cv82": 0xe1ac, + "cv83": 0xe1b0, "cv84": 0xe1b4, "cv85": 0xe1b8, "cv86": 0xe1bc, + "cv87": 0xe1c0, "cv88": 0xe1c4, "cv89": 0xe1c8, "cv90": 0xe1cc, + "cv91": 0xe1d0, "cv92": 0xe1d4, "cv93": 0xe1d8, "cv94": 0xe1dc, + "cv95": 0xe1e0, "cv96": 0xe1e4, "cv97": 0xe1e8, "cv98": 0xe1ec, + "cv99": 0xe1f0, "dist": 0xe1f4, "dlig": 0xe1f8, "dnom": 0xe1fc, + "expt": 0xe200, "falt": 0xe204, "fin2": 0xe208, "fin3": 0xe20c, + "fina": 0xe210, "frac": 0xe214, "fwid": 0xe218, "half": 0xe21c, + "haln": 0xe220, "halt": 0xe224, "hist": 0xe228, "hkna": 0xe22c, + "hlig": 0xe230, "hngl": 0xe234, "hojo": 0xe238, "hwid": 0xe23c, + "init": 0xe240, "isol": 0xe244, "ital": 0xe248, "jalt": 0xe24c, + "jp04": 0xe250, "jp78": 0xe254, "jp83": 0xe258, "jp90": 0xe25c, + "kern": 0xe260, "lfbd": 0xe264, "liga": 0xe268, "ljmo": 0xe26c, + "lnum": 0xe270, "locl": 0xe274, "ltra": 0xe278, "ltrm": 0xe27c, + "mark": 0xe280, "med2": 0xe284, "medi": 0xe288, "mgrk": 0xe28c, + "mkmk": 0xe290, "mset": 0xe294, "nalt": 0xe298, "nlck": 0xe29c, + "nukt": 0xe2a0, "numr": 0xe2a4, "onum": 0xe2a8, "opbd": 0xe2ac, + "ordn": 0xe2b0, "ornm": 0xe2b4, "palt": 0xe2b8, "pcap": 0xe2bc, + "pkna": 0xe2c0, "pnum": 0xe2c4, "pref": 0xe2c8, "pres": 0xe2cc, + "pstf": 0xe2d0, "psts": 0xe2d4, "pwid": 0xe2d8, "qwid": 0xe2dc, + "rand": 0xe2e0, "rkrf": 0xe2e4, "rlig": 0xe2e8, "rphf": 0xe2ec, + "rtbd": 0xe2f0, "rtla": 0xe2f4, "rtlm": 0xe2f8, "ruby": 0xe2fc, + "salt": 0xe300, "sinf": 0xe304, "size": 0xe308, "smcp": 0xe30c, + "smpl": 0xe310, "ss00": 0xe314, "ss01": 0xe318, "ss02": 0xe31c, + "ss03": 0xe320, "ss04": 0xe324, "ss05": 0xe328, "ss06": 0xe32c, + "ss07": 0xe330, "ss08": 0xe334, "ss09": 0xe338, "ss10": 0xe33c, + "ss11": 0xe340, "ss12": 0xe344, "ss13": 0xe348, "ss14": 0xe34c, + "ss15": 0xe350, "ss16": 0xe354, "ss17": 0xe358, "ss18": 0xe35c, + "ss19": 0xe360, "ss20": 0xe364, "ss21": 0xe368, "subs": 0xe36c, + "sups": 0xe370, "swsh": 0xe374, "titl": 0xe378, "tjmo": 0xe37c, + "tnam": 0xe380, "tnum": 0xe384, "trad": 0xe388, "twid": 0xe38c, + "unic": 0xe390, "valt": 0xe394, "vatu": 0xe398, "vert": 0xe39c, + "vhal": 0xe3a0, "vjmo": 0xe3a4, "vkna": 0xe3a8, "vkrn": 0xe3ac, + "vpal": 0xe3b0, "vrt2": 0xe3b4, "zero": 0xe3b8 +}; diff --git a/Tests/LibWeb/Ref/data/gsubtest-lookup3.otf b/Tests/LibWeb/Ref/data/gsubtest-lookup3.otf new file mode 100644 index 0000000000000000000000000000000000000000..86d79a0d8f07a3ab149999ec8a36b6e2cacbadc0 GIT binary patch literal 289724 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 + + +CSS Test: feature value matching for font-variant-caps + + + + + + + +
+ + + diff --git a/Tests/LibWeb/Ref/expected/wpt-import/css/css-fonts/font-variant-east-asian-ref.html b/Tests/LibWeb/Ref/expected/wpt-import/css/css-fonts/font-variant-east-asian-ref.html new file mode 100644 index 00000000000..a7d528537f3 --- /dev/null +++ b/Tests/LibWeb/Ref/expected/wpt-import/css/css-fonts/font-variant-east-asian-ref.html @@ -0,0 +1,21 @@ + + + +CSS Test: feature value matching for font-variant-east-asian + + + + + + + +
+ + + diff --git a/Tests/LibWeb/Ref/expected/wpt-import/css/css-fonts/font-variant-ligatures-ref.html b/Tests/LibWeb/Ref/expected/wpt-import/css/css-fonts/font-variant-ligatures-ref.html new file mode 100644 index 00000000000..5cd9dd765de --- /dev/null +++ b/Tests/LibWeb/Ref/expected/wpt-import/css/css-fonts/font-variant-ligatures-ref.html @@ -0,0 +1,21 @@ + + + +CSS Test: feature value matching for font-variant-ligatures + + + + + + + +
+ + + diff --git a/Tests/LibWeb/Ref/expected/wpt-import/css/css-fonts/font-variant-numeric-ref.html b/Tests/LibWeb/Ref/expected/wpt-import/css/css-fonts/font-variant-numeric-ref.html new file mode 100644 index 00000000000..52f17eee451 --- /dev/null +++ b/Tests/LibWeb/Ref/expected/wpt-import/css/css-fonts/font-variant-numeric-ref.html @@ -0,0 +1,21 @@ + + + +CSS Test: feature value matching for font-variant-numeric + + + + + + + +
+ + + diff --git a/Tests/LibWeb/Ref/expected/wpt-import/css/css-fonts/font-variant-position-ref.html b/Tests/LibWeb/Ref/expected/wpt-import/css/css-fonts/font-variant-position-ref.html new file mode 100644 index 00000000000..130902ec6b4 --- /dev/null +++ b/Tests/LibWeb/Ref/expected/wpt-import/css/css-fonts/font-variant-position-ref.html @@ -0,0 +1,21 @@ + + + +CSS Test: feature value matching for font-variant-position + + + + + + + +
+ + + diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-caps.html b/Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-caps.html new file mode 100644 index 00000000000..9d0070216b5 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-caps.html @@ -0,0 +1,24 @@ + + + +CSS Test: feature value matching for font-variant-caps + + + + + + + + + + +
+ + + diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-east-asian.html b/Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-east-asian.html new file mode 100644 index 00000000000..846bf4e77ed --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-east-asian.html @@ -0,0 +1,24 @@ + + + +CSS Test: feature value matching for font-variant-east-asian + + + + + + + + + + +
+ + + diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-ligatures.html b/Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-ligatures.html new file mode 100644 index 00000000000..aee8ae8ee8b --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-ligatures.html @@ -0,0 +1,24 @@ + + + +CSS Test: feature value matching for font-variant-ligatures + + + + + + + + + + +
+ + + diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-numeric.html b/Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-numeric.html new file mode 100644 index 00000000000..eaae3b29dec --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-numeric.html @@ -0,0 +1,24 @@ + + + +CSS Test: feature value matching for font-variant-numeric + + + + + + + + + + +
+ + + diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-position.html b/Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-position.html new file mode 100644 index 00000000000..f5a2abe7f44 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-position.html @@ -0,0 +1,24 @@ + + + +CSS Test: feature value matching for font-variant-position + + + + + + + + + + +
+ + + diff --git a/UI/Headless/Test.cpp b/UI/Headless/Test.cpp index ab6b6d43555..8d3b529207c 100644 --- a/UI/Headless/Test.cpp +++ b/UI/Headless/Test.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include From 0269bdbe6037c518618c8100ade27a062a82b4fb Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Thu, 7 Nov 2024 16:27:04 +0100 Subject: [PATCH 3/8] LibWeb: Add font-variant-* css properties --- Libraries/LibWeb/CSS/Enums.json | 59 ++++++++++++++++++++++++- Libraries/LibWeb/CSS/Keywords.json | 31 ++++++++++++++ Libraries/LibWeb/CSS/Properties.json | 64 +++++++++++++++++++++++++++- 3 files changed, 151 insertions(+), 3 deletions(-) diff --git a/Libraries/LibWeb/CSS/Enums.json b/Libraries/LibWeb/CSS/Enums.json index e3e2fed8684..c58a8bc2fbc 100644 --- a/Libraries/LibWeb/CSS/Enums.json +++ b/Libraries/LibWeb/CSS/Enums.json @@ -221,9 +221,64 @@ "fallback", "optional" ], - "font-variant": [ + "font-variant-alternates": [ "normal", - "small-caps" + "historical-forms" + ], + "font-variant-caps": [ + "normal", + "small-caps", + "all-small-caps", + "petite-caps", + "all-petite-caps", + "unicase", + "titling-caps" + ], + "font-variant-east-asian": [ + "normal", + "ruby", + "jis78", + "jis83", + "jis90", + "jis04", + "simplified", + "traditional", + "full-width", + "proportional-width" + ], + "font-variant-emoji": [ + "normal", + "text", + "emoji", + "unicode" + ], + "font-variant-ligatures": [ + "normal", + "none", + "common-ligatures", + "no-common-ligatures", + "discretionary-ligatures", + "no-discretionary-ligatures", + "historical-ligatures", + "no-historical-ligatures", + "contextual", + "no-contextual" + ], + "font-variant-numeric": [ + "normal", + "ordinal", + "slashed-zero", + "lining-nums", + "oldstyle-nums", + "proportional-nums", + "tabular-nums", + "diagonal-fractions", + "stacked-fractions" + ], + "font-variant-position": [ + "normal", + "sub", + "super" ], "font-width": [ "ultra-condensed", diff --git a/Libraries/LibWeb/CSS/Keywords.json b/Libraries/LibWeb/CSS/Keywords.json index 177358a2893..a459a06ae21 100644 --- a/Libraries/LibWeb/CSS/Keywords.json +++ b/Libraries/LibWeb/CSS/Keywords.json @@ -67,7 +67,9 @@ "additive", "alias", "all", + "all-petite-caps", "all-scroll", + "all-small-caps", "alpha", "alternate", "alternate-reverse", @@ -114,6 +116,7 @@ "collapse", "column", "column-reverse", + "common-ligatures", "compact", "condensed", "contain", @@ -121,6 +124,7 @@ "content-box", "contents", "context-menu", + "contextual", "copy", "cover", "crisp-edges", @@ -133,7 +137,9 @@ "decimal", "decimal-leading-zero", "default", + "diagonal-fractions", "disc", + "discretionary-ligatures", "disclosure-closed", "disclosure-open", "distribute", @@ -147,6 +153,7 @@ "ease-out", "ellipsis", "embed", + "emoji", "enabled", "end", "evenodd", @@ -185,6 +192,8 @@ "high-quality", "highlight", "highlighttext", + "historical-forms", + "historical-ligatures", "horizontal-tb", "hover", "inactiveborder", @@ -213,6 +222,10 @@ "isolate", "isolate-override", "italic", + "jis04", + "jis78", + "jis83", + "jis90", "jump-both", "jump-end", "jump-none", @@ -229,6 +242,7 @@ "lighter", "line-through", "linear", + "lining-nums", "linktext", "list-item", "listbox", @@ -262,8 +276,12 @@ "nearest", "nesw-resize", "no-close-quote", + "no-common-ligatures", + "no-contextual", + "no-discretionary-ligatures", "no-drop", "no-open-quote", + "no-historical-ligatures", "no-preference", "no-repeat", "none", @@ -276,12 +294,14 @@ "nwse-resize", "oblique", "off", + "oldstyle-nums", "on", "opaque", "open-quote", "optimizequality", "optimizespeed", "optional", + "ordinal", "outset", "outside", "overline", @@ -289,6 +309,7 @@ "padding-box", "paged", "paused", + "petite-caps", "pixelated", "plaintext", "pointer", @@ -299,6 +320,8 @@ "progress", "progress-bar", "progressive", + "proportional-nums", + "proportional-width", "push-button", "radio", "rec2020", @@ -342,6 +365,8 @@ "serif", "sideways-lr", "sideways-rl", + "simplified", + "slashed-zero", "slider-horizontal", "slow", "small", @@ -357,6 +382,7 @@ "square-button", "srgb", "stable", + "stacked-fractions", "standalone", "standard", "start", @@ -378,6 +404,7 @@ "table-header-group", "table-row", "table-row-group", + "tabular-nums", "text", "text-bottom", "text-top", @@ -385,6 +412,8 @@ "textfield", "thick", "thin", + "titling-caps", + "traditional", "threeddarkshadow", "threedface", "threedhighlight", @@ -399,6 +428,8 @@ "ultra-condensed", "ultra-expanded", "underline", + "unicase", + "unicode", "unsafe", "unset", "up", diff --git a/Libraries/LibWeb/CSS/Properties.json b/Libraries/LibWeb/CSS/Properties.json index 2212af2dfc2..0d42e57f7c8 100644 --- a/Libraries/LibWeb/CSS/Properties.json +++ b/Libraries/LibWeb/CSS/Properties.json @@ -1247,7 +1247,69 @@ "inherited": true, "initial": "normal", "valid-types": [ - "font-variant" + "font-variant-alternates", + "font-variant-caps", + "font-variant-east-asian", + "font-variant-emoji", + "font-variant-ligatures", + "font-variant-numeric", + "font-variant-position" + ] + }, + "font-variant-alternates": { + "animation-type": "discrete", + "inherited": true, + "initial": "normal", + "valid-types": [ + "font-variant-alternates" + ] + }, + "font-variant-caps": { + "animation-type": "discrete", + "inherited": true, + "initial": "normal", + "valid-types": [ + "font-variant-caps" + ] + }, + "font-variant-east-asian": { + "animation-type": "discrete", + "inherited": true, + "initial": "normal", + "valid-types": [ + "font-variant-east-asian" + ] + }, + "font-variant-emoji": { + "animation-type": "discrete", + "inherited": true, + "initial": "normal", + "valid-types": [ + "font-variant-emoji" + ] + }, + "font-variant-ligatures": { + "animation-type": "discrete", + "inherited": true, + "initial": "normal", + "valid-types": [ + "font-variant-ligatures" + ] + }, + "font-variant-numeric": { + "animation-type": "discrete", + "inherited": true, + "initial": "normal", + "valid-types": [ + "font-variant-numeric" + ] + }, + "font-variant-position": { + "animation-type": "discrete", + "inherited": true, + "initial": "normal", + "valid-types": [ + "font-variant-position" ] }, "font-variation-settings": { From 164dd0546773d04567a96fe5917a8626fa86c384 Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Thu, 7 Nov 2024 16:28:02 +0100 Subject: [PATCH 4/8] LibWeb: Parse font-variant properties --- Libraries/LibWeb/CSS/Parser/Parser.cpp | 473 +++++++++++++++++++++++++ Libraries/LibWeb/CSS/Parser/Parser.h | 6 + 2 files changed, 479 insertions(+) diff --git a/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Libraries/LibWeb/CSS/Parser/Parser.cpp index 5d8ff1db63e..ce9eaac03ff 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -5903,6 +5903,455 @@ RefPtr Parser::parse_font_variation_settings_value(TokenStream Parser::parse_font_variant(TokenStream& tokens) +{ + // 6.11 https://drafts.csswg.org/css-fonts/#propdef-font-variant + // normal | none | + // [ [ || || || ] + // || [ small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps ] || + // [ FIXME: stylistic() || + // historical-forms || + // FIXME: styleset(#) || + // FIXME: character-variant(#) || + // FIXME: swash() || + // FIXME: ornaments() || + // FIXME: annotation() ] || + // [ || || || + // ordinal || slashed-zero ] || [ || || ruby ] || + // [ sub | super ] || [ text | emoji | unicode ] ] + + bool has_caps = false; + bool has_alternates = false; + bool has_common_ligatures = false; + bool has_discretionary_ligatures = false; + bool has_historical_ligatures = false; + bool has_contextual = false; + bool has_numeric_figures = false; + bool has_numeric_spacing = false; + bool has_numeric_fractions = false; + bool has_numeric_ordinals = false; + bool has_numeric_slashed_zero = false; + bool has_east_asian_variant = false; + bool has_east_asian_width = false; + bool has_east_asian_ruby = false; + bool has_position = false; + Keyword alternates_value = Keyword::Normal; + Keyword caps_value = Keyword::Normal; + Keyword position_value = Keyword::Normal; + StyleValueVector east_asian_values; + StyleValueVector ligatures_values; + StyleValueVector numeric_values; + StyleValueVector position_values; + + // normal + if (auto parsed_value = parse_all_as_single_keyword_value(tokens, Keyword::Normal)) { + /* do nothing */ + // none + } else if (auto parsed_value = parse_all_as_single_keyword_value(tokens, Keyword::None)) { + // FIXME + } else { + + while (tokens.has_next_token()) { + auto maybe_value = parse_keyword_value(tokens); + if (!maybe_value) + break; + auto value = maybe_value.release_nonnull(); + if (!value->is_keyword()) { + // FIXME: alternate functions such as stylistic() + return nullptr; + } + auto keyword = value->to_keyword(); + + switch (keyword) { + // = [ common-ligatures | no-common-ligatures ] + case Keyword::CommonLigatures: + case Keyword::NoCommonLigatures: + if (has_common_ligatures) + return nullptr; + ligatures_values.append(value); + has_common_ligatures = true; + break; + // = [ discretionary-ligatures | no-discretionary-ligatures ] + case Keyword::DiscretionaryLigatures: + case Keyword::NoDiscretionaryLigatures: + if (has_discretionary_ligatures) + return nullptr; + ligatures_values.append(value); + has_discretionary_ligatures = true; + break; + // = [ historical-ligatures | no-historical-ligatures ] + case Keyword::HistoricalLigatures: + case Keyword::NoHistoricalLigatures: + if (has_historical_ligatures) + return nullptr; + ligatures_values.append(value); + has_historical_ligatures = true; + break; + // = [ contextual | no-contextual ] + case Keyword::Contextual: + case Keyword::NoContextual: + if (has_contextual) + return nullptr; + ligatures_values.append(value); + has_contextual = true; + break; + // historical-forms + case Keyword::HistoricalForms: + if (has_alternates) + return nullptr; + alternates_value = keyword; + has_alternates = true; + break; + // [ small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps ] + case Keyword::SmallCaps: + case Keyword::AllSmallCaps: + case Keyword::PetiteCaps: + case Keyword::AllPetiteCaps: + case Keyword::Unicase: + case Keyword::TitlingCaps: + if (has_caps) + return nullptr; + caps_value = keyword; + has_caps = true; + break; + // = [ lining-nums | oldstyle-nums ] + case Keyword::LiningNums: + case Keyword::OldstyleNums: + if (has_numeric_figures) + return nullptr; + numeric_values.append(value); + has_numeric_figures = true; + break; + // = [ proportional-nums | tabular-nums ] + case Keyword::ProportionalNums: + case Keyword::TabularNums: + if (has_numeric_spacing) + return nullptr; + numeric_values.append(value); + has_numeric_spacing = true; + break; + // = [ diagonal-fractions | stacked-fractions] + case Keyword::DiagonalFractions: + case Keyword::StackedFractions: + if (has_numeric_fractions) + return nullptr; + numeric_values.append(value); + has_numeric_fractions = true; + break; + // ordinal + case Keyword::Ordinal: + if (has_numeric_ordinals) + return nullptr; + numeric_values.append(value); + has_numeric_ordinals = true; + break; + case Keyword::SlashedZero: + if (has_numeric_slashed_zero) + return nullptr; + numeric_values.append(value); + has_numeric_slashed_zero = true; + break; + // = [ jis78 | jis83 | jis90 | jis04 | simplified | traditional ] + case Keyword::Jis78: + case Keyword::Jis83: + case Keyword::Jis90: + case Keyword::Jis04: + case Keyword::Simplified: + case Keyword::Traditional: + if (has_east_asian_variant) + return nullptr; + east_asian_values.append(value); + has_east_asian_variant = true; + break; + // = [ full-width | proportional-width ] + case Keyword::FullWidth: + case Keyword::ProportionalWidth: + if (has_east_asian_width) + return nullptr; + east_asian_values.append(value); + has_east_asian_width = true; + break; + // ruby + case Keyword::Ruby: + if (has_east_asian_ruby) + return nullptr; + east_asian_values.append(value); + has_east_asian_ruby = true; + break; + // sub | super + case Keyword::Sub: + case Keyword::Super: + if (has_position) + return nullptr; + position_value = keyword; + has_position = true; + break; + default: + break; + } + } + } + + if (ligatures_values.is_empty()) + ligatures_values.append(CSSKeywordValue::create(Keyword::Normal)); + if (numeric_values.is_empty()) + numeric_values.append(CSSKeywordValue::create(Keyword::Normal)); + if (east_asian_values.is_empty()) + east_asian_values.append(CSSKeywordValue::create(Keyword::Normal)); + + return ShorthandStyleValue::create(PropertyID::FontVariant, + { PropertyID::FontVariantAlternates, + PropertyID::FontVariantCaps, + PropertyID::FontVariantEastAsian, + PropertyID::FontVariantLigatures, + PropertyID::FontVariantNumeric, + PropertyID::FontVariantPosition }, + { + CSSKeywordValue::create(alternates_value), + CSSKeywordValue::create(caps_value), + StyleValueList::create(move(east_asian_values), StyleValueList::Separator::Space), + StyleValueList::create(move(ligatures_values), StyleValueList::Separator::Space), + StyleValueList::create(move(numeric_values), StyleValueList::Separator::Space), + CSSKeywordValue::create(position_value), + }); +} + +RefPtr Parser::parse_font_variant_alternates_value(TokenStream& tokens) +{ + // 6.8 https://drafts.csswg.org/css-fonts/#font-variant-alternates-prop + // normal | + // [ FIXME: stylistic() || + // historical-forms || + // FIXME: styleset(#) || + // FIXME: character-variant(#) || + // FIXME: swash() || + // FIXME: ornaments() || + // FIXME: annotation() ] + + // normal + if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal)) + return normal; + + // historical-forms + // FIXME: Support this together with other values when we parse them. + if (auto historical_forms = parse_all_as_single_keyword_value(tokens, Keyword::HistoricalForms)) + return historical_forms; + + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-variant-alternate: parsing {} not implemented.", tokens.next_token().to_debug_string()); + return nullptr; +} + +RefPtr Parser::parse_font_variant_caps_value(TokenStream& tokens) +{ + // https://drafts.csswg.org/css-fonts/#propdef-font-variant-caps + // normal | small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps + + bool has_token = false; + while (tokens.has_next_token()) { + if (has_token) + return nullptr; + auto maybe_value = parse_keyword_value(tokens); + if (!maybe_value) + break; + auto value = maybe_value.release_nonnull(); + auto font_variant = keyword_to_font_variant_caps(value->to_keyword()); + if (font_variant.has_value()) { + return value; + } + return nullptr; + } + + return nullptr; +} + +RefPtr Parser::parse_font_variant_east_asian_value(TokenStream& tokens) +{ + // 6.10 https://drafts.csswg.org/css-fonts/#propdef-font-variant-east-asian + // normal | [ || || ruby ] + // = [ jis78 | jis83 | jis90 | jis04 | simplified | traditional ] + // = [ full-width | proportional-width ] + + StyleValueVector value_list; + bool has_ruby = false; + bool has_variant = false; + bool has_width = false; + + // normal | ... + if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal)) { + value_list.append(normal.release_nonnull()); + } else { + while (tokens.has_next_token()) { + auto maybe_value = parse_keyword_value(tokens); + if (!maybe_value) + break; + auto value = maybe_value.release_nonnull(); + auto font_variant = keyword_to_font_variant_east_asian(value->to_keyword()); + if (!font_variant.has_value()) { + return nullptr; + } + auto keyword = value->to_keyword(); + if (keyword == Keyword::Ruby) { + if (has_ruby) + return nullptr; + has_ruby = true; + } else if (keyword == Keyword::FullWidth || keyword == Keyword::ProportionalWidth) { + if (has_width) + return nullptr; + has_width = true; + } else { + if (has_variant) + return nullptr; + has_variant = true; + } + value_list.append(value); + } + } + if (value_list.is_empty()) + return nullptr; + + return StyleValueList::create(move(value_list), StyleValueList::Separator::Space); +} + +RefPtr Parser::parse_font_variant_ligatures_value(TokenStream& tokens) +{ + // 6.4 https://drafts.csswg.org/css-fonts/#propdef-font-variant-ligatures + // normal | none | [ || || || ] + // = [ common-ligatures | no-common-ligatures ] + // = [ discretionary-ligatures | no-discretionary-ligatures ] + // = [ historical-ligatures | no-historical-ligatures ] + // = [ contextual | no-contextual ] + + StyleValueVector value_list; + bool has_common_ligatures = false; + bool has_discretionary_ligatures = false; + bool has_historical_ligatures = false; + bool has_contextual = false; + + // normal | ... + if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal)) { + value_list.append(normal.release_nonnull()); + // none | ... + } else if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None)) { + value_list.append(none.release_nonnull()); + } else { + while (tokens.has_next_token()) { + auto maybe_value = parse_keyword_value(tokens); + if (!maybe_value) + break; + auto value = maybe_value.release_nonnull(); + switch (value->to_keyword()) { + // = [ common-ligatures | no-common-ligatures ] + case Keyword::CommonLigatures: + case Keyword::NoCommonLigatures: + if (has_common_ligatures) + return nullptr; + has_common_ligatures = true; + break; + // = [ discretionary-ligatures | no-discretionary-ligatures ] + case Keyword::DiscretionaryLigatures: + case Keyword::NoDiscretionaryLigatures: + if (has_discretionary_ligatures) + return nullptr; + has_discretionary_ligatures = true; + break; + // = [ historical-ligatures | no-historical-ligatures ] + case Keyword::HistoricalLigatures: + case Keyword::NoHistoricalLigatures: + if (has_historical_ligatures) + return nullptr; + has_historical_ligatures = true; + break; + // = [ contextual | no-contextual ] + case Keyword::Contextual: + case Keyword::NoContextual: + if (has_contextual) + return nullptr; + has_contextual = true; + break; + default: + return nullptr; + } + value_list.append(value); + } + } + + if (value_list.is_empty()) + return nullptr; + + return StyleValueList::create(move(value_list), StyleValueList::Separator::Space); +} + +RefPtr Parser::parse_font_variant_numeric_value(TokenStream& tokens) +{ + // 6.7 https://drafts.csswg.org/css-fonts/#propdef-font-variant-numeric + // normal | [ || || || ordinal || slashed-zero] + // = [ lining-nums | oldstyle-nums ] + // = [ proportional-nums | tabular-nums ] + // = [ diagonal-fractions | stacked-fractions ] + + StyleValueVector value_list; + bool has_figures = false; + bool has_spacing = false; + bool has_fractions = false; + bool has_ordinals = false; + bool has_slashed_zero = false; + + // normal | ... + if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal)) { + value_list.append(normal.release_nonnull()); + } else { + while (tokens.has_next_token()) { + auto maybe_value = parse_keyword_value(tokens); + if (!maybe_value) + break; + auto value = maybe_value.release_nonnull(); + switch (value->to_keyword()) { + // ... || ordinal + case Keyword::Ordinal: + if (has_ordinals) + return nullptr; + has_ordinals = true; + break; + // ... || slashed-zero + case Keyword::SlashedZero: + if (has_slashed_zero) + return nullptr; + has_slashed_zero = true; + break; + // = [ lining-nums | oldstyle-nums ] + case Keyword::LiningNums: + case Keyword::OldstyleNums: + if (has_figures) + return nullptr; + has_figures = true; + break; + // = [ proportional-nums | tabular-nums ] + case Keyword::ProportionalNums: + case Keyword::TabularNums: + if (has_spacing) + return nullptr; + has_spacing = true; + break; + // = [ diagonal-fractions | stacked-fractions ] + case Keyword::DiagonalFractions: + case Keyword::StackedFractions: + if (has_fractions) + return nullptr; + has_fractions = true; + break; + default: + return nullptr; + } + value_list.append(value); + } + } + + if (value_list.is_empty()) + return nullptr; + + return StyleValueList::create(move(value_list), StyleValueList::Separator::Space); +} + Vector Parser::parse_as_font_face_src() { return parse_font_face_src(m_token_stream); @@ -7789,6 +8238,30 @@ Parser::ParseErrorOr> Parser::parse_css_value(Prope if (auto parsed_value = parse_font_variation_settings_value(tokens); parsed_value && !tokens.has_next_token()) return parsed_value.release_nonnull(); return ParseError::SyntaxError; + case PropertyID::FontVariant: + if (auto parsed_value = parse_font_variant(tokens); parsed_value && !tokens.has_next_token()) + return parsed_value.release_nonnull(); + return ParseError::SyntaxError; + case PropertyID::FontVariantAlternates: + if (auto parsed_value = parse_font_variant_alternates_value(tokens); parsed_value && !tokens.has_next_token()) + return parsed_value.release_nonnull(); + return ParseError::SyntaxError; + case PropertyID::FontVariantCaps: + if (auto parsed_value = parse_font_variant_caps_value(tokens); parsed_value && !tokens.has_next_token()) + return parsed_value.release_nonnull(); + return ParseError::SyntaxError; + case PropertyID::FontVariantEastAsian: + if (auto parsed_value = parse_font_variant_east_asian_value(tokens); parsed_value && !tokens.has_next_token()) + return parsed_value.release_nonnull(); + return ParseError::SyntaxError; + case PropertyID::FontVariantLigatures: + if (auto parsed_value = parse_font_variant_ligatures_value(tokens); parsed_value && !tokens.has_next_token()) + return parsed_value.release_nonnull(); + return ParseError::SyntaxError; + case PropertyID::FontVariantNumeric: + if (auto parsed_value = parse_font_variant_numeric_value(tokens); parsed_value && !tokens.has_next_token()) + return parsed_value.release_nonnull(); + return ParseError::SyntaxError; case PropertyID::GridArea: if (auto parsed_value = parse_grid_area_shorthand_value(tokens); parsed_value && !tokens.has_next_token()) return parsed_value.release_nonnull(); diff --git a/Libraries/LibWeb/CSS/Parser/Parser.h b/Libraries/LibWeb/CSS/Parser/Parser.h index dd8476c68ad..6f01457a57d 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Libraries/LibWeb/CSS/Parser/Parser.h @@ -322,6 +322,12 @@ private: RefPtr parse_font_language_override_value(TokenStream&); RefPtr parse_font_feature_settings_value(TokenStream&); RefPtr parse_font_variation_settings_value(TokenStream&); + RefPtr parse_font_variant(TokenStream&); + RefPtr parse_font_variant_alternates_value(TokenStream&); + RefPtr parse_font_variant_caps_value(TokenStream&); + RefPtr parse_font_variant_east_asian_value(TokenStream&); + RefPtr parse_font_variant_ligatures_value(TokenStream&); + RefPtr parse_font_variant_numeric_value(TokenStream&); RefPtr parse_list_style_value(TokenStream&); RefPtr parse_math_depth_value(TokenStream&); RefPtr parse_overflow_value(TokenStream&); From 0a3193f0b6a0e5b3fd670b210ea95c46f9cc49c7 Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Mon, 11 Nov 2024 22:49:02 +0100 Subject: [PATCH 5/8] LibWeb: Style font-variant properties --- Libraries/LibGfx/CMakeLists.txt | 1 + Libraries/LibGfx/Font/FontVariant.cpp | 176 ++++++++ Libraries/LibGfx/Font/FontVariant.h | 90 +++++ Libraries/LibGfx/TextLayout.h | 10 + Libraries/LibWeb/CSS/CSSStyleValue.cpp | 161 ++++++++ Libraries/LibWeb/CSS/CSSStyleValue.h | 8 + Libraries/LibWeb/CSS/ComputedValues.h | 22 +- Libraries/LibWeb/CSS/StyleComputer.cpp | 7 + Libraries/LibWeb/CSS/StyleProperties.cpp | 42 +- Libraries/LibWeb/CSS/StyleProperties.h | 7 +- .../CSS/StyleValues/ShorthandStyleValue.cpp | 54 +++ ...upported-properties-and-default-values.txt | 16 +- ...eclaration-has-indexed-property-getter.txt | 376 +++++++++--------- .../css/getComputedStyle-print-all.txt | 9 +- 14 files changed, 781 insertions(+), 198 deletions(-) create mode 100644 Libraries/LibGfx/Font/FontVariant.cpp create mode 100644 Libraries/LibGfx/Font/FontVariant.h diff --git a/Libraries/LibGfx/CMakeLists.txt b/Libraries/LibGfx/CMakeLists.txt index 4db9903c17b..43db12dd5fb 100644 --- a/Libraries/LibGfx/CMakeLists.txt +++ b/Libraries/LibGfx/CMakeLists.txt @@ -16,6 +16,7 @@ set(SOURCES Font/Font.cpp Font/FontData.cpp Font/FontDatabase.cpp + Font/FontVariant.cpp Font/PathFontProvider.cpp Font/ScaledFont.cpp Font/ScaledFontSkia.cpp diff --git a/Libraries/LibGfx/Font/FontVariant.cpp b/Libraries/LibGfx/Font/FontVariant.cpp new file mode 100644 index 00000000000..53a26a51100 --- /dev/null +++ b/Libraries/LibGfx/Font/FontVariant.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2024, Johan Dahlin + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace Gfx { + +StringView font_variant_alternates_to_string(FontVariantAlternates value) +{ + if (value.normal && value.historical_forms) + return "normal historical-forms"sv; + if (value.normal) + return "normal"sv; + if (value.historical_forms) + return "historical-forms"sv; + return {}; +} + +StringView font_variant_ligatures_to_string(FontVariantLigatures ligatures) +{ + if (ligatures.normal) + return "normal"sv; + if (ligatures.none) + return "none"sv; + + Vector values; + switch (ligatures.common) { + case FontVariantLigatures::Common::Common: + values.append("common-ligatures"sv); + break; + case FontVariantLigatures::Common::NoCommon: + values.append("no-common-ligatures"sv); + break; + case FontVariantLigatures::Common::Unset: + break; + } + + switch (ligatures.discretionary) { + case FontVariantLigatures::Discretionary::Discretionary: + values.append("discretionary-ligatures"sv); + break; + case FontVariantLigatures::Discretionary::NoDiscretionary: + values.append("no-discretionary-ligatures"sv); + break; + case FontVariantLigatures::Discretionary::Unset: + break; + } + + switch (ligatures.historical) { + case FontVariantLigatures::Historical::Historical: + values.append("historical-ligatures"sv); + break; + case FontVariantLigatures::Historical::NoHistorical: + values.append("no-historical-ligatures"sv); + break; + case FontVariantLigatures::Historical::Unset: + break; + } + + switch (ligatures.contextual) { + case FontVariantLigatures::Contextual::Contextual: + values.append("contextual"sv); + break; + case FontVariantLigatures::Contextual::NoContextual: + values.append("no-contextual"sv); + break; + case FontVariantLigatures::Contextual::Unset: + break; + } + + StringBuilder builder; + builder.join(' ', values); + return MUST(builder.to_string()); +} + +StringView font_variant_east_asian_to_string(FontVariantEastAsian value) +{ + Vector values; + + switch (value.variant) { + case FontVariantEastAsian::Variant::Unset: + break; + case FontVariantEastAsian::Variant::Jis78: + values.append("jis78"sv); + break; + case FontVariantEastAsian::Variant::Jis83: + values.append("jis83"sv); + break; + case FontVariantEastAsian::Variant::Jis90: + values.append("jis90"sv); + break; + case FontVariantEastAsian::Variant::Jis04: + values.append("jis04"sv); + break; + case FontVariantEastAsian::Variant::Simplified: + values.append("simplified"sv); + break; + case FontVariantEastAsian::Variant::Traditional: + values.append("traditional"sv); + break; + } + + switch (value.width) { + case FontVariantEastAsian::Width::Unset: + break; + case FontVariantEastAsian::Width::FullWidth: + values.append("full-width"sv); + break; + case FontVariantEastAsian::Width::Proportional: + values.append("proportional-width"sv); + break; + } + + if (value.ruby) + values.append("ruby"sv); + + StringBuilder builder; + builder.join(' ', values); + return MUST(builder.to_string()); +} + +StringView font_variant_numeric_to_string(FontVariantNumeric value) +{ + Vector values; + + if (value.normal) + values.append("normal"sv); + if (value.ordinal) + values.append("ordinal"sv); + if (value.slashed_zero) + values.append("slashed-zero"sv); + + switch (value.figure) { + case FontVariantNumeric::Figure::Unset: + break; + case FontVariantNumeric::Figure::Lining: + values.append("lining-nums"sv); + break; + case FontVariantNumeric::Figure::Oldstyle: + values.append("oldstyle-nums"sv); + break; + } + + switch (value.spacing) { + case FontVariantNumeric::Spacing::Unset: + break; + case FontVariantNumeric::Spacing::Proportional: + values.append("proportional-nums"sv); + break; + case FontVariantNumeric::Spacing::Tabular: + values.append("tabular-nums"sv); + break; + } + + switch (value.fraction) { + case FontVariantNumeric::Fraction::Unset: + break; + case FontVariantNumeric::Fraction::Diagonal: + values.append("diagonal-fractions"sv); + break; + case FontVariantNumeric::Fraction::Stacked: + values.append("stacked-fractions"sv); + break; + } + + StringBuilder builder; + builder.join(' ', values); + return MUST(builder.to_string()); +} + +} diff --git a/Libraries/LibGfx/Font/FontVariant.h b/Libraries/LibGfx/Font/FontVariant.h new file mode 100644 index 00000000000..b6ea9888ac5 --- /dev/null +++ b/Libraries/LibGfx/Font/FontVariant.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +#pragma once + +namespace Gfx { + +using FontFeatureName = StringView; + +class FontVariantAlternates { +public: + bool normal { false }; + bool historical_forms { false }; +}; + +class FontVariantEastAsian { +public: + enum class Variant { Unset, + Jis78, + Jis83, + Jis90, + Jis04, + Simplified, + Traditional }; + enum class Width { Unset, + Proportional, + FullWidth }; + + bool normal = false; + bool ruby = false; + Variant variant { Variant::Unset }; + Width width { Width::Unset }; +}; + +class FontVariantLigatures { +public: + enum class Common { Unset, + Common, + NoCommon }; + enum class Discretionary { Unset, + Discretionary, + NoDiscretionary }; + enum class Historical { Unset, + Historical, + NoHistorical }; + enum class Contextual { Unset, + Contextual, + NoContextual }; + + bool normal = false; + bool none = false; + Common common { Common::Unset }; + Discretionary discretionary { Discretionary::Unset }; + Historical historical { Historical::Unset }; + Contextual contextual { Contextual::Unset }; +}; + +class FontVariantNumeric { +public: + enum class Figure { Unset, + Lining, + Oldstyle }; + enum class Spacing { Unset, + Proportional, + Tabular }; + enum class Fraction { Unset, + Diagonal, + Stacked }; + + bool normal = false; + bool ordinal = false; + bool slashed_zero = false; + Figure figure { Figure::Unset }; + Spacing spacing { Spacing::Unset }; + Fraction fraction { Fraction::Unset }; +}; + +extern StringView font_variant_alternates_to_string(FontVariantAlternates); +extern StringView font_variant_east_asian_to_string(FontVariantEastAsian); +extern StringView font_variant_ligatures_to_string(FontVariantLigatures); +extern StringView font_variant_numeric_to_string(FontVariantNumeric); + +} diff --git a/Libraries/LibGfx/TextLayout.h b/Libraries/LibGfx/TextLayout.h index 8cbba66b902..3ca2a4c792d 100644 --- a/Libraries/LibGfx/TextLayout.h +++ b/Libraries/LibGfx/TextLayout.h @@ -30,6 +30,16 @@ struct DrawGlyph { } }; +/* ABI compatible with harfbuzz' hb_feature_t */ +typedef struct ShapeFeature { + u32 tag; + u32 value; + unsigned int start; + unsigned int end; +} ShapeFeature; + +using ShapeFeatures = Vector; + class GlyphRun : public RefCounted { public: enum class TextType { diff --git a/Libraries/LibWeb/CSS/CSSStyleValue.cpp b/Libraries/LibWeb/CSS/CSSStyleValue.cpp index 78b5b0a220d..2795031f625 100644 --- a/Libraries/LibWeb/CSS/CSSStyleValue.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleValue.cpp @@ -481,4 +481,165 @@ int CSSStyleValue::to_font_width() const return width; } +Gfx::FontVariantAlternates CSSStyleValue::to_font_variant_alternates() const +{ + VERIFY(is_keyword()); + switch (as_keyword().keyword()) { + case Keyword::Normal: + return Gfx::FontVariantAlternates { .normal = true }; + case Keyword::HistoricalForms: + return Gfx::FontVariantAlternates { .historical_forms = true }; + default: + VERIFY_NOT_REACHED(); + } +} + +Optional CSSStyleValue::to_font_variant_caps() const +{ + VERIFY(is_keyword()); + return keyword_to_font_variant_caps(as_keyword().keyword()); +} + +Gfx::FontVariantEastAsian CSSStyleValue::to_font_variant_east_asian() const +{ + VERIFY(is_value_list()); + auto& list = as_value_list(); + Gfx::FontVariantEastAsian east_asian {}; + for (auto& value : list.values()) { + VERIFY(value->is_keyword()); + switch (value->as_keyword().keyword()) { + case Keyword::Normal: + east_asian.normal = true; + return east_asian; + case Keyword::Jis78: + east_asian.variant = Gfx::FontVariantEastAsian::Variant::Jis78; + break; + case Keyword::Jis83: + east_asian.variant = Gfx::FontVariantEastAsian::Variant::Jis83; + break; + case Keyword::Jis90: + east_asian.variant = Gfx::FontVariantEastAsian::Variant::Jis90; + break; + case Keyword::Jis04: + east_asian.variant = Gfx::FontVariantEastAsian::Variant::Jis04; + break; + case Keyword::Simplified: + east_asian.variant = Gfx::FontVariantEastAsian::Variant::Simplified; + break; + case Keyword::Traditional: + east_asian.variant = Gfx::FontVariantEastAsian::Variant::Traditional; + break; + case Keyword::FullWidth: + east_asian.width = Gfx::FontVariantEastAsian::Width::FullWidth; + break; + case Keyword::ProportionalWidth: + east_asian.width = Gfx::FontVariantEastAsian::Width::Proportional; + break; + case Keyword::Ruby: + east_asian.ruby = true; + break; + default: + VERIFY_NOT_REACHED(); + break; + } + } + return east_asian; +} + +Gfx::FontVariantLigatures CSSStyleValue::to_font_variant_ligatures() const +{ + VERIFY(is_value_list()); + auto& list = as_value_list(); + Gfx::FontVariantLigatures ligatures; + + for (auto& value : list.values()) { + VERIFY(value->is_keyword()); + switch (value->as_keyword().keyword()) { + case Keyword::Normal: + ligatures.normal = true; + return ligatures; + case Keyword::None: + ligatures.none = true; + return ligatures; + case Keyword::CommonLigatures: + ligatures.common = Gfx::FontVariantLigatures::Common::Common; + break; + case Keyword::NoCommonLigatures: + ligatures.common = Gfx::FontVariantLigatures::Common::NoCommon; + break; + case Keyword::DiscretionaryLigatures: + ligatures.discretionary = Gfx::FontVariantLigatures::Discretionary::Discretionary; + break; + case Keyword::NoDiscretionaryLigatures: + ligatures.discretionary = Gfx::FontVariantLigatures::Discretionary::NoDiscretionary; + break; + case Keyword::HistoricalLigatures: + ligatures.historical = Gfx::FontVariantLigatures::Historical::Historical; + break; + case Keyword::NoHistoricalLigatures: + ligatures.historical = Gfx::FontVariantLigatures::Historical::NoHistorical; + break; + case Keyword::Contextual: + ligatures.contextual = Gfx::FontVariantLigatures::Contextual::Contextual; + break; + case Keyword::NoContextual: + ligatures.contextual = Gfx::FontVariantLigatures::Contextual::NoContextual; + break; + default: + VERIFY_NOT_REACHED(); + break; + } + } + return ligatures; +} + +Gfx::FontVariantNumeric CSSStyleValue::to_font_variant_numeric() const +{ + VERIFY(is_value_list()); + auto& list = as_value_list(); + Gfx::FontVariantNumeric numeric {}; + for (auto& value : list.values()) { + VERIFY(value->is_keyword()); + switch (value->as_keyword().keyword()) { + case Keyword::Normal: + numeric.normal = true; + return numeric; + case Keyword::Ordinal: + numeric.ordinal = true; + break; + case Keyword::SlashedZero: + numeric.slashed_zero = true; + break; + case Keyword::OldstyleNums: + numeric.figure = Gfx::FontVariantNumeric::Figure::Oldstyle; + break; + case Keyword::LiningNums: + numeric.figure = Gfx::FontVariantNumeric::Figure::Lining; + break; + case Keyword::ProportionalNums: + numeric.spacing = Gfx::FontVariantNumeric::Spacing::Proportional; + break; + case Keyword::TabularNums: + numeric.spacing = Gfx::FontVariantNumeric::Spacing::Tabular; + break; + case Keyword::DiagonalFractions: + numeric.fraction = Gfx::FontVariantNumeric::Fraction::Diagonal; + break; + case Keyword::StackedFractions: + numeric.fraction = Gfx::FontVariantNumeric::Fraction::Stacked; + break; + default: + VERIFY_NOT_REACHED(); + break; + } + } + return numeric; +} + +Optional CSSStyleValue::to_font_variant_position() const +{ + VERIFY(is_keyword()); + return keyword_to_font_variant_position(as_keyword().keyword()); +} + } diff --git a/Libraries/LibWeb/CSS/CSSStyleValue.h b/Libraries/LibWeb/CSS/CSSStyleValue.h index 312b5d6a8bc..3c2c953519e 100644 --- a/Libraries/LibWeb/CSS/CSSStyleValue.h +++ b/Libraries/LibWeb/CSS/CSSStyleValue.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -104,6 +105,7 @@ public: Edge, FilterValueList, Flex, + FontVariant, Frequency, GridAutoFlow, GridTemplateArea, @@ -355,6 +357,12 @@ public: [[nodiscard]] int to_font_weight() const; [[nodiscard]] int to_font_slope() const; [[nodiscard]] int to_font_width() const; + [[nodiscard]] Gfx::FontVariantAlternates to_font_variant_alternates() const; + [[nodiscard]] Optional to_font_variant_caps() const; + [[nodiscard]] Gfx::FontVariantEastAsian to_font_variant_east_asian() const; + [[nodiscard]] Gfx::FontVariantLigatures to_font_variant_ligatures() const; + [[nodiscard]] Gfx::FontVariantNumeric to_font_variant_numeric() const; + [[nodiscard]] Optional to_font_variant_position() const; virtual bool equals(CSSStyleValue const& other) const = 0; diff --git a/Libraries/LibWeb/CSS/ComputedValues.h b/Libraries/LibWeb/CSS/ComputedValues.h index cc67aa2517b..e7c81f61b4f 100644 --- a/Libraries/LibWeb/CSS/ComputedValues.h +++ b/Libraries/LibWeb/CSS/ComputedValues.h @@ -97,7 +97,6 @@ public: static AspectRatio aspect_ratio() { return AspectRatio { true, {} }; } static CSSPixels font_size() { return 16; } static int font_weight() { return 400; } - static CSS::FontVariant font_variant() { return CSS::FontVariant::Normal; } static CSSPixels line_height() { return 0; } static CSS::Float float_() { return CSS::Float::None; } static CSS::Length border_spacing() { return CSS::Length::make_px(0); } @@ -511,7 +510,12 @@ public: Gfx::FontCascadeList const& font_list() const { return *m_inherited.font_list; } CSSPixels font_size() const { return m_inherited.font_size; } int font_weight() const { return m_inherited.font_weight; } - CSS::FontVariant font_variant() const { return m_inherited.font_variant; } + Gfx::FontVariantAlternates font_variant_alternates() const { return m_inherited.font_variant_alternates; } + FontVariantCaps font_variant_caps() const { return m_inherited.font_variant_caps; } + Gfx::FontVariantEastAsian font_variant_east_asian() const { return m_inherited.font_variant_east_asian; } + Gfx::FontVariantLigatures font_variant_ligatures() const { return m_inherited.font_variant_ligatures; } + Gfx::FontVariantNumeric font_variant_numeric() const { return m_inherited.font_variant_numeric; } + FontVariantPosition font_variant_position() const { return m_inherited.font_variant_position; } Optional font_language_override() const { return m_inherited.font_language_override; } Optional> font_feature_settings() const { return m_inherited.font_feature_settings; } Optional> font_variation_settings() const { return m_inherited.font_variation_settings; } @@ -545,7 +549,12 @@ protected: RefPtr font_list {}; CSSPixels font_size { InitialValues::font_size() }; int font_weight { InitialValues::font_weight() }; - CSS::FontVariant font_variant { InitialValues::font_variant() }; + Gfx::FontVariantAlternates font_variant_alternates { .normal = true }; + FontVariantCaps font_variant_caps { FontVariantCaps::Normal }; + Gfx::FontVariantEastAsian font_variant_east_asian { .normal = true }; + Gfx::FontVariantLigatures font_variant_ligatures { .normal = true }; + Gfx::FontVariantNumeric font_variant_numeric { .normal = true }; + FontVariantPosition font_variant_position { FontVariantPosition::Normal }; Optional font_language_override; Optional> font_feature_settings; Optional> font_variation_settings; @@ -717,7 +726,12 @@ public: void set_font_list(NonnullRefPtr font_list) { m_inherited.font_list = move(font_list); } void set_font_size(CSSPixels font_size) { m_inherited.font_size = font_size; } void set_font_weight(int font_weight) { m_inherited.font_weight = font_weight; } - void set_font_variant(CSS::FontVariant font_variant) { m_inherited.font_variant = font_variant; } + void set_font_variant_alternates(Gfx::FontVariantAlternates font_variant_alternates) { m_inherited.font_variant_alternates = font_variant_alternates; } + void set_font_variant_caps(FontVariantCaps font_variant_caps) { m_inherited.font_variant_caps = font_variant_caps; } + void set_font_variant_east_asian(Gfx::FontVariantEastAsian font_variant_east_asian) { m_inherited.font_variant_east_asian = font_variant_east_asian; } + void set_font_variant_ligatures(Gfx::FontVariantLigatures font_variant_ligatures) { m_inherited.font_variant_ligatures = font_variant_ligatures; } + void set_font_variant_numeric(Gfx::FontVariantNumeric font_variant_numeric) { m_inherited.font_variant_numeric = font_variant_numeric; } + void set_font_variant_position(FontVariantPosition font_variant_position) { m_inherited.font_variant_position = font_variant_position; } void set_font_language_override(Optional font_language_override) { m_inherited.font_language_override = font_language_override; } void set_font_feature_settings(Optional> value) { m_inherited.font_feature_settings = move(value); } void set_font_variation_settings(Optional> value) { m_inherited.font_variation_settings = move(value); } diff --git a/Libraries/LibWeb/CSS/StyleComputer.cpp b/Libraries/LibWeb/CSS/StyleComputer.cpp index ccdfbf29e99..c6095eb2a78 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -2111,6 +2111,13 @@ void StyleComputer::compute_font(StyleProperties& style, DOM::Element const* ele compute_defaulted_property_value(style, element, CSS::PropertyID::FontStyle, pseudo_element); compute_defaulted_property_value(style, element, CSS::PropertyID::FontWeight, pseudo_element); compute_defaulted_property_value(style, element, CSS::PropertyID::LineHeight, pseudo_element); + compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariant, pseudo_element); + compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariantAlternates, pseudo_element); + compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariantCaps, pseudo_element); + compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariantEastAsian, pseudo_element); + compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariantLigatures, pseudo_element); + compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariantNumeric, pseudo_element); + compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariantPosition, pseudo_element); auto const& font_family = style.property(CSS::PropertyID::FontFamily); auto const& font_size = style.property(CSS::PropertyID::FontSize); diff --git a/Libraries/LibWeb/CSS/StyleProperties.cpp b/Libraries/LibWeb/CSS/StyleProperties.cpp index 3d1efb25652..de4158d64e6 100644 --- a/Libraries/LibWeb/CSS/StyleProperties.cpp +++ b/Libraries/LibWeb/CSS/StyleProperties.cpp @@ -1143,12 +1143,6 @@ Variant StyleProperties::vertical_ali VERIFY_NOT_REACHED(); } -Optional StyleProperties::font_variant() const -{ - auto const& value = property(CSS::PropertyID::FontVariant); - return keyword_to_font_variant(value.to_keyword()); -} - Optional StyleProperties::font_language_override() const { auto const& value = property(CSS::PropertyID::FontLanguageOverride); @@ -1157,6 +1151,42 @@ Optional StyleProperties::font_language_override() const return {}; } +Gfx::FontVariantAlternates StyleProperties::font_variant_alternates() const +{ + auto const& value = property(CSS::PropertyID::FontVariantAlternates); + return value.to_font_variant_alternates(); +} + +Optional StyleProperties::font_variant_caps() const +{ + auto const& value = property(CSS::PropertyID::FontVariantCaps); + return value.to_font_variant_caps(); +} + +Gfx::FontVariantEastAsian StyleProperties::font_variant_east_asian() const +{ + auto const& value = property(CSS::PropertyID::FontVariantEastAsian); + return value.to_font_variant_east_asian(); +} + +Gfx::FontVariantLigatures StyleProperties::font_variant_ligatures() const +{ + auto const& value = property(CSS::PropertyID::FontVariantLigatures); + return value.to_font_variant_ligatures(); +} + +Gfx::FontVariantNumeric StyleProperties::font_variant_numeric() const +{ + auto const& value = property(CSS::PropertyID::FontVariantNumeric); + return value.to_font_variant_numeric(); +} + +Optional StyleProperties::font_variant_position() const +{ + auto const& value = property(CSS::PropertyID::FontVariantPosition); + return value.to_font_variant_position(); +} + Optional> StyleProperties::font_feature_settings() const { auto const& value = property(PropertyID::FontFeatureSettings); diff --git a/Libraries/LibWeb/CSS/StyleProperties.h b/Libraries/LibWeb/CSS/StyleProperties.h index 7551757df5f..016eded3680 100644 --- a/Libraries/LibWeb/CSS/StyleProperties.h +++ b/Libraries/LibWeb/CSS/StyleProperties.h @@ -149,7 +149,12 @@ public: Optional box_sizing() const; Optional pointer_events() const; Variant vertical_align() const; - Optional font_variant() const; + Gfx::FontVariantAlternates font_variant_alternates() const; + Optional font_variant_caps() const; + Gfx::FontVariantEastAsian font_variant_east_asian() const; + Gfx::FontVariantLigatures font_variant_ligatures() const; + Gfx::FontVariantNumeric font_variant_numeric() const; + Optional font_variant_position() const; Optional font_language_override() const; Optional> font_feature_settings() const; Optional> font_variation_settings() const; diff --git a/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp index ef3b1d37b82..d1571236bd7 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp @@ -119,6 +119,60 @@ String ShorthandStyleValue::to_string() const longhand(PropertyID::FontSize)->to_string(), longhand(PropertyID::LineHeight)->to_string(), longhand(PropertyID::FontFamily)->to_string())); + case PropertyID::FontVariant: { + Vector values; + auto alternates = longhand(PropertyID::FontVariantAlternates)->to_font_variant_alternates(); + if (alternates.historical_forms) + values.append("historical-forms"sv); + auto caps_or_null = longhand(PropertyID::FontVariantCaps)->to_font_variant_caps(); + if (caps_or_null.has_value() && caps_or_null.value() != CSS::FontVariantCaps::Normal) { + values.append(CSS::to_string(caps_or_null.release_value())); + } + auto east_asian = longhand(PropertyID::FontVariantEastAsian)->to_font_variant_east_asian(); + if (!east_asian.normal) { + if (east_asian.variant != Gfx::FontVariantEastAsian::Variant::Unset) + values.append(Gfx::font_variant_east_asian_to_string(east_asian)); + if (east_asian.width != Gfx::FontVariantEastAsian::Width::Unset) + values.append(Gfx::font_variant_east_asian_to_string(east_asian)); + } + auto ligatures = longhand(PropertyID::FontVariantLigatures)->to_font_variant_ligatures(); + if (!ligatures.normal && !ligatures.none) { + if (ligatures.common != Gfx::FontVariantLigatures::Common::Unset) + values.append(Gfx::font_variant_ligatures_to_string(ligatures)); + if (ligatures.discretionary != Gfx::FontVariantLigatures::Discretionary::Unset) { + values.append(Gfx::font_variant_ligatures_to_string(ligatures)); + } + if (ligatures.historical != Gfx::FontVariantLigatures::Historical::Unset) { + values.append(Gfx::font_variant_ligatures_to_string(ligatures)); + } + if (ligatures.contextual != Gfx::FontVariantLigatures::Contextual::Unset) { + values.append(Gfx::font_variant_ligatures_to_string(ligatures)); + } + } + auto numeric = longhand(PropertyID::FontVariantNumeric)->to_font_variant_numeric(); + if (!numeric.normal) { + if (numeric.ordinal) + values.append("ordinal"sv); + if (numeric.slashed_zero) + values.append("slashed-zero"sv); + if (numeric.figure != Gfx::FontVariantNumeric::Figure::Unset) + values.append(Gfx::font_variant_numeric_to_string(numeric)); + if (numeric.spacing != Gfx::FontVariantNumeric::Spacing::Unset) + values.append(Gfx::font_variant_numeric_to_string(numeric)); + if (numeric.fraction != Gfx::FontVariantNumeric::Fraction::Unset) + values.append(Gfx::font_variant_numeric_to_string(numeric)); + } + auto position_or_null = longhand(PropertyID::FontVariantPosition)->to_font_variant_position(); + if (position_or_null.has_value() && position_or_null.value() != CSS::FontVariantPosition::Normal) { + values.append(CSS::to_string(position_or_null.release_value())); + } + StringBuilder builder; + if (values.is_empty()) + builder.append("normal"sv); + else + builder.join(' ', values); + return MUST(builder.to_string()); + } case PropertyID::GridArea: { auto& row_start = longhand(PropertyID::GridRowStart)->as_grid_track_placement(); auto& column_start = longhand(PropertyID::GridColumnStart)->as_grid_track_placement(); diff --git a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt b/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt index 567376a2b1f..d583063e2aa 100644 --- a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt +++ b/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt @@ -1,6 +1,6 @@ All supported properties and their default values exposed from CSSStyleDeclaration from getComputedStyle: 'cssText': '' -'length': '203' +'length': '210' 'parentRule': 'null' 'cssFloat': 'none' 'WebkitAlignContent': 'normal' @@ -305,6 +305,20 @@ All supported properties and their default values exposed from CSSStyleDeclarati 'font-style': 'normal' 'fontVariant': 'normal' 'font-variant': 'normal' +'fontVariantAlternates': 'normal' +'font-variant-alternates': 'normal' +'fontVariantCaps': 'normal' +'font-variant-caps': 'normal' +'fontVariantEastAsian': 'normal' +'font-variant-east-asian': 'normal' +'fontVariantEmoji': 'normal' +'font-variant-emoji': 'normal' +'fontVariantLigatures': 'normal' +'font-variant-ligatures': 'normal' +'fontVariantNumeric': 'normal' +'font-variant-numeric': 'normal' +'fontVariantPosition': 'normal' +'font-variant-position': 'normal' 'fontVariationSettings': 'normal' 'font-variation-settings': 'normal' 'fontWeight': '400' diff --git a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-has-indexed-property-getter.txt b/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-has-indexed-property-getter.txt index 37e322d0855..3358b9e442b 100644 --- a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-has-indexed-property-getter.txt +++ b/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-has-indexed-property-getter.txt @@ -18,191 +18,197 @@ All properties associated with getComputedStyle(document.body): "15": "font-size", "16": "font-style", "17": "font-variant", - "18": "font-variation-settings", - "19": "font-weight", - "20": "font-width", - "21": "image-rendering", - "22": "letter-spacing", - "23": "line-height", - "24": "list-style-image", - "25": "list-style-position", - "26": "list-style-type", - "27": "math-depth", - "28": "math-shift", - "29": "math-style", - "30": "pointer-events", - "31": "quotes", - "32": "stroke", - "33": "stroke-dashoffset", - "34": "stroke-linecap", - "35": "stroke-linejoin", - "36": "stroke-miterlimit", - "37": "stroke-opacity", - "38": "stroke-width", - "39": "tab-size", - "40": "text-align", - "41": "text-anchor", - "42": "text-decoration-line", - "43": "text-indent", - "44": "text-justify", - "45": "text-shadow", - "46": "text-transform", - "47": "visibility", - "48": "white-space", - "49": "word-break", - "50": "word-spacing", - "51": "word-wrap", - "52": "writing-mode", - "53": "align-content", - "54": "align-items", - "55": "align-self", - "56": "animation-delay", - "57": "animation-direction", - "58": "animation-duration", - "59": "animation-fill-mode", - "60": "animation-iteration-count", - "61": "animation-name", - "62": "animation-play-state", - "63": "animation-timing-function", - "64": "appearance", - "65": "aspect-ratio", - "66": "backdrop-filter", - "67": "background-attachment", - "68": "background-clip", - "69": "background-color", - "70": "background-image", - "71": "background-origin", - "72": "background-position-x", - "73": "background-position-y", - "74": "background-repeat", - "75": "background-size", - "76": "border-bottom-color", - "77": "border-bottom-left-radius", - "78": "border-bottom-right-radius", - "79": "border-bottom-style", - "80": "border-bottom-width", - "81": "border-left-color", - "82": "border-left-style", - "83": "border-left-width", - "84": "border-right-color", - "85": "border-right-style", - "86": "border-right-width", - "87": "border-top-color", - "88": "border-top-left-radius", - "89": "border-top-right-radius", - "90": "border-top-style", - "91": "border-top-width", - "92": "bottom", - "93": "box-shadow", - "94": "box-sizing", - "95": "clear", - "96": "clip", - "97": "clip-path", - "98": "column-count", - "99": "column-gap", - "100": "column-span", - "101": "column-width", - "102": "content", - "103": "content-visibility", - "104": "counter-increment", - "105": "counter-reset", - "106": "counter-set", - "107": "cx", - "108": "cy", - "109": "display", - "110": "filter", - "111": "flex-basis", - "112": "flex-direction", - "113": "flex-grow", - "114": "flex-shrink", - "115": "flex-wrap", - "116": "float", - "117": "grid-auto-columns", - "118": "grid-auto-flow", - "119": "grid-auto-rows", - "120": "grid-column-end", - "121": "grid-column-start", - "122": "grid-row-end", - "123": "grid-row-start", - "124": "grid-template-areas", - "125": "grid-template-columns", - "126": "grid-template-rows", - "127": "height", - "128": "inline-size", - "129": "inset-block-end", - "130": "inset-block-start", - "131": "inset-inline-end", - "132": "inset-inline-start", - "133": "justify-content", - "134": "justify-items", - "135": "justify-self", - "136": "left", - "137": "margin-block-end", - "138": "margin-block-start", - "139": "margin-bottom", - "140": "margin-inline-end", - "141": "margin-inline-start", - "142": "margin-left", - "143": "margin-right", - "144": "margin-top", - "145": "mask", - "146": "mask-image", - "147": "mask-type", - "148": "max-height", - "149": "max-inline-size", - "150": "max-width", - "151": "min-height", - "152": "min-inline-size", - "153": "min-width", - "154": "object-fit", - "155": "object-position", - "156": "opacity", - "157": "order", - "158": "outline-color", - "159": "outline-offset", - "160": "outline-style", - "161": "outline-width", - "162": "overflow-x", - "163": "overflow-y", - "164": "padding-block-end", - "165": "padding-block-start", - "166": "padding-bottom", - "167": "padding-inline-end", - "168": "padding-inline-start", - "169": "padding-left", - "170": "padding-right", - "171": "padding-top", - "172": "position", - "173": "r", - "174": "right", - "175": "rotate", - "176": "row-gap", - "177": "rx", - "178": "ry", - "179": "scrollbar-gutter", - "180": "scrollbar-width", - "181": "stop-color", - "182": "stop-opacity", - "183": "table-layout", - "184": "text-decoration-color", - "185": "text-decoration-style", - "186": "text-decoration-thickness", - "187": "text-overflow", - "188": "top", - "189": "transform", - "190": "transform-box", - "191": "transform-origin", - "192": "transition-delay", - "193": "transition-duration", - "194": "transition-property", - "195": "transition-timing-function", - "196": "unicode-bidi", - "197": "user-select", - "198": "vertical-align", - "199": "width", - "200": "x", - "201": "y", - "202": "z-index" + "18": "font-variant-alternates", + "19": "font-variant-caps", + "20": "font-variant-east-asian", + "21": "font-variant-emoji", + "22": "font-variant-ligatures", + "23": "font-variant-numeric", + "24": "font-variant-position", + "25": "font-variation-settings", + "26": "font-weight", + "27": "font-width", + "28": "image-rendering", + "29": "letter-spacing", + "30": "line-height", + "31": "list-style-image", + "32": "list-style-position", + "33": "list-style-type", + "34": "math-depth", + "35": "math-shift", + "36": "math-style", + "37": "pointer-events", + "38": "quotes", + "39": "stroke", + "40": "stroke-linecap", + "41": "stroke-linejoin", + "42": "stroke-miterlimit", + "43": "stroke-opacity", + "44": "stroke-width", + "45": "tab-size", + "46": "text-align", + "47": "text-anchor", + "48": "text-decoration-line", + "49": "text-indent", + "50": "text-justify", + "51": "text-shadow", + "52": "text-transform", + "53": "visibility", + "54": "white-space", + "55": "word-break", + "56": "word-spacing", + "57": "word-wrap", + "58": "writing-mode", + "59": "align-content", + "60": "align-items", + "61": "align-self", + "62": "animation-delay", + "63": "animation-direction", + "64": "animation-duration", + "65": "animation-fill-mode", + "66": "animation-iteration-count", + "67": "animation-name", + "68": "animation-play-state", + "69": "animation-timing-function", + "70": "appearance", + "71": "aspect-ratio", + "72": "backdrop-filter", + "73": "background-attachment", + "74": "background-clip", + "75": "background-color", + "76": "background-image", + "77": "background-origin", + "78": "background-position-x", + "79": "background-position-y", + "80": "background-repeat", + "81": "background-size", + "82": "border-bottom-color", + "83": "border-bottom-left-radius", + "84": "border-bottom-right-radius", + "85": "border-bottom-style", + "86": "border-bottom-width", + "87": "border-left-color", + "88": "border-left-style", + "89": "border-left-width", + "90": "border-right-color", + "91": "border-right-style", + "92": "border-right-width", + "93": "border-top-color", + "94": "border-top-left-radius", + "95": "border-top-right-radius", + "96": "border-top-style", + "97": "border-top-width", + "98": "bottom", + "99": "box-shadow", + "100": "box-sizing", + "101": "clear", + "102": "clip", + "103": "clip-path", + "104": "column-count", + "105": "column-gap", + "106": "column-span", + "107": "column-width", + "108": "content", + "109": "content-visibility", + "110": "counter-increment", + "111": "counter-reset", + "112": "counter-set", + "113": "cx", + "114": "cy", + "115": "display", + "116": "filter", + "117": "flex-basis", + "118": "flex-direction", + "119": "flex-grow", + "120": "flex-shrink", + "121": "flex-wrap", + "122": "float", + "123": "grid-auto-columns", + "124": "grid-auto-flow", + "125": "grid-auto-rows", + "126": "grid-column-end", + "127": "grid-column-start", + "128": "grid-row-end", + "129": "grid-row-start", + "130": "grid-template-areas", + "131": "grid-template-columns", + "132": "grid-template-rows", + "133": "height", + "134": "inline-size", + "135": "inset-block-end", + "136": "inset-block-start", + "137": "inset-inline-end", + "138": "inset-inline-start", + "139": "justify-content", + "140": "justify-items", + "141": "justify-self", + "142": "left", + "143": "margin-block-end", + "144": "margin-block-start", + "145": "margin-bottom", + "146": "margin-inline-end", + "147": "margin-inline-start", + "148": "margin-left", + "149": "margin-right", + "150": "margin-top", + "151": "mask", + "152": "mask-image", + "153": "mask-type", + "154": "max-height", + "155": "max-inline-size", + "156": "max-width", + "157": "min-height", + "158": "min-inline-size", + "159": "min-width", + "160": "object-fit", + "161": "object-position", + "162": "opacity", + "163": "order", + "164": "outline-color", + "165": "outline-offset", + "166": "outline-style", + "167": "outline-width", + "168": "overflow-x", + "169": "overflow-y", + "170": "padding-block-end", + "171": "padding-block-start", + "172": "padding-bottom", + "173": "padding-inline-end", + "174": "padding-inline-start", + "175": "padding-left", + "176": "padding-right", + "177": "padding-top", + "178": "position", + "179": "r", + "180": "right", + "181": "rotate", + "182": "row-gap", + "183": "rx", + "184": "ry", + "185": "scrollbar-gutter", + "186": "scrollbar-width", + "187": "stop-color", + "188": "stop-opacity", + "189": "table-layout", + "190": "text-decoration-color", + "191": "text-decoration-style", + "192": "text-decoration-thickness", + "193": "text-overflow", + "194": "top", + "195": "transform", + "196": "transform-box", + "197": "transform-origin", + "198": "transition-delay", + "199": "transition-duration", + "200": "transition-property", + "201": "transition-timing-function", + "202": "unicode-bidi", + "203": "user-select", + "204": "vertical-align", + "205": "width", + "206": "x", + "207": "y", + "208": "z-index" } All properties associated with document.body.style by default: {} diff --git a/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt b/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt index 4bfd10393a5..b71f2531324 100644 --- a/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt +++ b/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt @@ -16,6 +16,13 @@ font-language-override: normal font-size: 16px font-style: normal font-variant: normal +font-variant-alternates: normal +font-variant-caps: normal +font-variant-east-asian: normal +font-variant-emoji: normal +font-variant-ligatures: normal +font-variant-numeric: normal +font-variant-position: normal font-variation-settings: normal font-weight: 400 font-width: normal @@ -125,7 +132,7 @@ grid-row-start: auto grid-template-areas: none grid-template-columns: auto grid-template-rows: auto -height: 2159px +height: 2261px inline-size: auto inset-block-end: auto inset-block-start: auto From 98183c716498d6d36c48981677f350f4667971e8 Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Thu, 7 Nov 2024 16:36:11 +0100 Subject: [PATCH 6/8] LibWeb/LibGfx: Add ShapeFeatures argument to shape_text() --- Libraries/LibGfx/Font/ScaledFont.cpp | 6 +++--- Libraries/LibGfx/TextLayout.cpp | 10 ++++++---- Libraries/LibGfx/TextLayout.h | 4 ++-- Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp | 2 +- Libraries/LibWeb/Layout/InlineLevelIterator.cpp | 3 ++- Libraries/LibWeb/Layout/InlineLevelIterator.h | 1 + Libraries/LibWeb/Painting/DisplayListRecorder.cpp | 2 +- 7 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Libraries/LibGfx/Font/ScaledFont.cpp b/Libraries/LibGfx/Font/ScaledFont.cpp index 8569d3fc14c..06f34ec04ff 100644 --- a/Libraries/LibGfx/Font/ScaledFont.cpp +++ b/Libraries/LibGfx/Font/ScaledFont.cpp @@ -58,13 +58,13 @@ ScaledFontMetrics ScaledFont::metrics() const return metrics; } -float ScaledFont::width(StringView view) const { return measure_text_width(Utf8View(view), *this); } -float ScaledFont::width(Utf8View const& view) const { return measure_text_width(view, *this); } +float ScaledFont::width(StringView view) const { return measure_text_width(Utf8View(view), *this, {}); } +float ScaledFont::width(Utf8View const& view) const { return measure_text_width(view, *this, {}); } float ScaledFont::glyph_width(u32 code_point) const { auto string = String::from_code_point(code_point); - return measure_text_width(Utf8View(string), *this); + return measure_text_width(Utf8View(string), *this, {}); } NonnullRefPtr ScaledFont::scaled_with_size(float point_size) const diff --git a/Libraries/LibGfx/TextLayout.cpp b/Libraries/LibGfx/TextLayout.cpp index c1ad71a4939..a1a067210e1 100644 --- a/Libraries/LibGfx/TextLayout.cpp +++ b/Libraries/LibGfx/TextLayout.cpp @@ -12,7 +12,7 @@ namespace Gfx { -RefPtr shape_text(FloatPoint baseline_start, float letter_spacing, Utf8View string, Gfx::Font const& font, GlyphRun::TextType text_type) +RefPtr shape_text(FloatPoint baseline_start, float letter_spacing, Utf8View string, Gfx::Font const& font, GlyphRun::TextType text_type, ShapeFeatures const& features) { hb_buffer_t* buffer = hb_buffer_create(); ScopeGuard destroy_buffer = [&]() { hb_buffer_destroy(buffer); }; @@ -24,7 +24,7 @@ RefPtr shape_text(FloatPoint baseline_start, float letter_spacing, Utf Vector const input_glyph_info({ glyph_info, glyph_count }); auto* hb_font = font.harfbuzz_font(); - hb_shape(hb_font, buffer, nullptr, 0); + hb_shape(hb_font, buffer, features.is_empty() ? nullptr : (hb_feature_t const*)features.data(), features.size()); glyph_info = hb_buffer_get_glyph_infos(buffer, &glyph_count); auto* positions = hb_buffer_get_glyph_positions(buffer, &glyph_count); @@ -45,12 +45,14 @@ RefPtr shape_text(FloatPoint baseline_start, float letter_spacing, Utf point.translate_by(letter_spacing, 0); } + hb_buffer_reset(buffer); + return adopt_ref(*new Gfx::GlyphRun(move(glyph_run), font, text_type, point.x())); } -float measure_text_width(Utf8View const& string, Gfx::Font const& font) +float measure_text_width(Utf8View const& string, Gfx::Font const& font, ShapeFeatures const& features) { - auto glyph_run = shape_text({}, 0, string, font, GlyphRun::TextType::Common); + auto glyph_run = shape_text({}, 0, string, font, GlyphRun::TextType::Common, features); return glyph_run->width(); } diff --git a/Libraries/LibGfx/TextLayout.h b/Libraries/LibGfx/TextLayout.h index 3ca2a4c792d..e67d4e4fe1b 100644 --- a/Libraries/LibGfx/TextLayout.h +++ b/Libraries/LibGfx/TextLayout.h @@ -74,7 +74,7 @@ private: float m_width { 0 }; }; -RefPtr shape_text(FloatPoint baseline_start, float letter_spacing, Utf8View string, Gfx::Font const& font, GlyphRun::TextType); -float measure_text_width(Utf8View const& string, Gfx::Font const& font); +RefPtr shape_text(FloatPoint baseline_start, float letter_spacing, Utf8View string, Gfx::Font const& font, GlyphRun::TextType, ShapeFeatures const& features); +float measure_text_width(Utf8View const& string, Gfx::Font const& font, ShapeFeatures const& features); } diff --git a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp index c8e169f4d29..93fd8982fbf 100644 --- a/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp +++ b/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp @@ -575,7 +575,7 @@ CanvasRenderingContext2D::PreparedText CanvasRenderingContext2D::prepare_text(By Gfx::FloatPoint anchor { 0, 0 }; auto physical_alignment = Gfx::TextAlignment::CenterLeft; - auto glyph_run = Gfx::shape_text(anchor, 0, replaced_text.code_points(), *font, Gfx::GlyphRun::TextType::Ltr); + auto glyph_run = Gfx::shape_text(anchor, 0, replaced_text.code_points(), *font, Gfx::GlyphRun::TextType::Ltr, {}); // 8. Let result be an array constructed by iterating over each glyph in the inline box from left to right (if any), adding to the array, for each glyph, the shape of the glyph as it is in the inline box, positioned on a coordinate space using CSS pixels with its origin is at the anchor point. PreparedText prepared_text { glyph_run, physical_alignment, { 0, 0, static_cast(glyph_run->width()), static_cast(height) } }; diff --git a/Libraries/LibWeb/Layout/InlineLevelIterator.cpp b/Libraries/LibWeb/Layout/InlineLevelIterator.cpp index 333423e8f77..702f7e94363 100644 --- a/Libraries/LibWeb/Layout/InlineLevelIterator.cpp +++ b/Libraries/LibWeb/Layout/InlineLevelIterator.cpp @@ -293,7 +293,8 @@ Optional InlineLevelIterator::next_without_lookahead( x = tab_stop_dist.to_float(); } - auto glyph_run = Gfx::shape_text({ x, 0 }, letter_spacing.to_float(), chunk.view, chunk.font, text_type); + auto shape_features = create_and_merge_font_features(); + auto glyph_run = Gfx::shape_text({ x, 0 }, letter_spacing.to_float(), chunk.view, chunk.font, text_type, shape_features); CSSPixels chunk_width = CSSPixels::nearest_value_for(glyph_run->width()); diff --git a/Libraries/LibWeb/Layout/InlineLevelIterator.h b/Libraries/LibWeb/Layout/InlineLevelIterator.h index de345215932..1bfd90c4a37 100644 --- a/Libraries/LibWeb/Layout/InlineLevelIterator.h +++ b/Libraries/LibWeb/Layout/InlineLevelIterator.h @@ -67,6 +67,7 @@ private: void exit_node_with_box_model_metrics(); void add_extra_box_model_metrics_to_item(Item&, bool add_leading_metrics, bool add_trailing_metrics); + Gfx::ShapeFeatures create_and_merge_font_features(); Layout::Node const* next_inline_node_in_pre_order(Layout::Node const& current, Layout::Node const* stay_within); diff --git a/Libraries/LibWeb/Painting/DisplayListRecorder.cpp b/Libraries/LibWeb/Painting/DisplayListRecorder.cpp index 44385334af5..f259efcae8b 100644 --- a/Libraries/LibWeb/Painting/DisplayListRecorder.cpp +++ b/Libraries/LibWeb/Painting/DisplayListRecorder.cpp @@ -220,7 +220,7 @@ void DisplayListRecorder::draw_text(Gfx::IntRect const& rect, String raw_text, G if (rect.is_empty()) return; - auto glyph_run = Gfx::shape_text({}, 0, raw_text.code_points(), font, Gfx::GlyphRun::TextType::Ltr); + auto glyph_run = Gfx::shape_text({}, 0, raw_text.code_points(), font, Gfx::GlyphRun::TextType::Ltr, {}); float baseline_x = 0; if (alignment == Gfx::TextAlignment::CenterLeft) { baseline_x = rect.x(); From 5f42f1318a438e8ccc2d346b9f4d409480c8012b Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Mon, 11 Nov 2024 22:49:44 +0100 Subject: [PATCH 7/8] LibWeb: Layout/Shape font-variant properties --- Libraries/LibGfx/TextLayout.cpp | 17 +- Libraries/LibGfx/TextLayout.h | 5 +- .../LibWeb/Layout/InlineLevelIterator.cpp | 250 ++++++++++++++++++ Libraries/LibWeb/Layout/InlineLevelIterator.h | 4 +- Libraries/LibWeb/Layout/Node.cpp | 14 +- 5 files changed, 282 insertions(+), 8 deletions(-) diff --git a/Libraries/LibGfx/TextLayout.cpp b/Libraries/LibGfx/TextLayout.cpp index a1a067210e1..e0307fca5bb 100644 --- a/Libraries/LibGfx/TextLayout.cpp +++ b/Libraries/LibGfx/TextLayout.cpp @@ -24,7 +24,22 @@ RefPtr shape_text(FloatPoint baseline_start, float letter_spacing, Utf Vector const input_glyph_info({ glyph_info, glyph_count }); auto* hb_font = font.harfbuzz_font(); - hb_shape(hb_font, buffer, features.is_empty() ? nullptr : (hb_feature_t const*)features.data(), features.size()); + const hb_feature_t* hb_features_data = nullptr; + Vector hb_features; + if (!features.is_empty()) { + hb_features.ensure_capacity(features.size()); + for (auto const& feature : features) { + hb_features.append({ + .tag = HB_TAG(feature.tag[0], feature.tag[1], feature.tag[2], feature.tag[3]), + .value = feature.value, + .start = 0, + .end = static_cast(-1), + }); + } + hb_features_data = hb_features.data(); + } + + hb_shape(hb_font, buffer, hb_features_data, features.size()); glyph_info = hb_buffer_get_glyph_infos(buffer, &glyph_count); auto* positions = hb_buffer_get_glyph_positions(buffer, &glyph_count); diff --git a/Libraries/LibGfx/TextLayout.h b/Libraries/LibGfx/TextLayout.h index e67d4e4fe1b..623d4fb5789 100644 --- a/Libraries/LibGfx/TextLayout.h +++ b/Libraries/LibGfx/TextLayout.h @@ -30,12 +30,9 @@ struct DrawGlyph { } }; -/* ABI compatible with harfbuzz' hb_feature_t */ typedef struct ShapeFeature { - u32 tag; + char tag[4]; u32 value; - unsigned int start; - unsigned int end; } ShapeFeature; using ShapeFeatures = Vector; diff --git a/Libraries/LibWeb/Layout/InlineLevelIterator.cpp b/Libraries/LibWeb/Layout/InlineLevelIterator.cpp index 702f7e94363..0f6879a8ded 100644 --- a/Libraries/LibWeb/Layout/InlineLevelIterator.cpp +++ b/Libraries/LibWeb/Layout/InlineLevelIterator.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -200,6 +201,255 @@ Gfx::GlyphRun::TextType InlineLevelIterator::resolve_text_direction_from_context return Gfx::GlyphRun::TextType::ContextDependent; } +HashMap InlineLevelIterator::shape_features_map() const +{ + HashMap features; + + auto& computed_values = m_current_node->computed_values(); + + // 6.4 https://drafts.csswg.org/css-fonts/#font-variant-ligatures-prop + auto ligature = computed_values.font_variant_ligatures(); + if (ligature.normal) { + // A value of normal specifies that common default features are enabled, as described in detail in the next section. + features.set("liga"sv, 1); + features.set("clig"sv, 1); + } else if (ligature.none) { + /* nothing */ + } else { + switch (ligature.common) { + case Gfx::FontVariantLigatures::Common::Common: + // Enables display of common ligatures (OpenType features: liga, clig). + features.set("liga"sv, 1); + features.set("clig"sv, 1); + break; + case Gfx::FontVariantLigatures::Common::NoCommon: + // Disables display of common ligatures (OpenType features: liga, clig). + features.set("liga"sv, 0); + features.set("clig"sv, 0); + break; + case Gfx::FontVariantLigatures::Common::Unset: + break; + } + + switch (ligature.discretionary) { + case Gfx::FontVariantLigatures::Discretionary::Discretionary: + // Enables display of discretionary ligatures (OpenType feature: dlig). + features.set("dlig"sv, 1); + break; + case Gfx::FontVariantLigatures::Discretionary::NoDiscretionary: + // Disables display of discretionary ligatures (OpenType feature: dlig). + features.set("dlig"sv, 0); + break; + case Gfx::FontVariantLigatures::Discretionary::Unset: + break; + } + + switch (ligature.historical) { + case Gfx::FontVariantLigatures::Historical::Historical: + // Enables display of historical ligatures (OpenType feature: hlig). + features.set("hlig"sv, 1); + break; + case Gfx::FontVariantLigatures::Historical::NoHistorical: + // Disables display of historical ligatures (OpenType feature: hlig). + features.set("hlig"sv, 0); + break; + case Gfx::FontVariantLigatures::Historical::Unset: + break; + } + + switch (ligature.contextual) { + case Gfx::FontVariantLigatures::Contextual::Contextual: + // Enables display of contextual ligatures (OpenType feature: calt). + features.set("calt"sv, 1); + break; + case Gfx::FontVariantLigatures::Contextual::NoContextual: + // Disables display of contextual ligatures (OpenType feature: calt). + features.set("calt"sv, 0); + break; + case Gfx::FontVariantLigatures::Contextual::Unset: + break; + } + } + + // 6.5 https://drafts.csswg.org/css-fonts/#font-variant-position-prop + switch (computed_values.font_variant_position()) { + case CSS::FontVariantPosition::Normal: + // None of the features listed below are enabled. + break; + case CSS::FontVariantPosition::Sub: + // Enables display of subscripts (OpenType feature: subs). + features.set("subs"sv, 1); + break; + case CSS::FontVariantPosition::Super: + // Enables display of superscripts (OpenType feature: sups). + features.set("sups"sv, 1); + break; + default: + break; + } + + // 6.6 https://drafts.csswg.org/css-fonts/#font-variant-caps-prop + switch (computed_values.font_variant_caps()) { + case CSS::FontVariantCaps::Normal: + // None of the features listed below are enabled. + break; + case CSS::FontVariantCaps::SmallCaps: + // Enables display of small capitals (OpenType feature: smcp). Small-caps glyphs typically use the form of uppercase letters but are reduced to the size of lowercase letters. + features.set("smcp"sv, 1); + break; + case CSS::FontVariantCaps::AllSmallCaps: + // Enables display of small capitals for both upper and lowercase letters (OpenType features: c2sc, smcp). + features.set("c2sc"sv, 1); + features.set("smcp"sv, 1); + break; + case CSS::FontVariantCaps::PetiteCaps: + // Enables display of petite capitals (OpenType feature: pcap). + features.set("pcap"sv, 1); + break; + case CSS::FontVariantCaps::AllPetiteCaps: + // Enables display of petite capitals for both upper and lowercase letters (OpenType features: c2pc, pcap). + features.set("c2pc"sv, 1); + features.set("pcap"sv, 1); + break; + case CSS::FontVariantCaps::Unicase: + // Enables display of mixture of small capitals for uppercase letters with normal lowercase letters (OpenType feature: unic). + features.set("unic"sv, 1); + break; + case CSS::FontVariantCaps::TitlingCaps: + // Enables display of titling capitals (OpenType feature: titl). + features.set("titl"sv, 1); + break; + default: + break; + } + + // 6.7 https://drafts.csswg.org/css-fonts/#font-variant-numeric-prop + auto numeric = computed_values.font_variant_numeric(); + // None of the features listed below are enabled. + if (numeric.normal) { + /* passthrough */ + } + + if (numeric.figure == Gfx::FontVariantNumeric::Figure::Oldstyle) { + // Enables display of old-style numerals (OpenType feature: onum). + features.set("onum"sv, 1); + } else if (numeric.figure == Gfx::FontVariantNumeric::Figure::Lining) { + // Enables display of lining numerals (OpenType feature: lnum). + features.set("lnum"sv, 1); + } + + if (numeric.spacing == Gfx::FontVariantNumeric::Spacing::Proportional) { + // Enables display of proportional numerals (OpenType feature: pnum). + features.set("pnum"sv, 1); + } else if (numeric.spacing == Gfx::FontVariantNumeric::Spacing::Tabular) { + // Enables display of tabular numerals (OpenType feature: tnum). + features.set("tnum"sv, 1); + } + + if (numeric.fraction == Gfx::FontVariantNumeric::Fraction::Diagonal) { + // Enables display of diagonal fractions (OpenType feature: frac). + features.set("frac"sv, 1); + } else if (numeric.fraction == Gfx::FontVariantNumeric::Fraction::Stacked) { + // Enables display of stacked fractions (OpenType feature: afrc). + features.set("afrc"sv, 1); + } + + if (numeric.ordinal) { + // Enables display of letter forms used with ordinal numbers (OpenType feature: ordn). + features.set("ordn"sv, 1); + } + if (numeric.slashed_zero) { + // Enables display of slashed zeros (OpenType feature: zero). + features.set("zero"sv, 1); + } + + // 6.10 https://drafts.csswg.org/css-fonts/#font-variant-east-asian-prop + auto east_asian = computed_values.font_variant_east_asian(); + switch (east_asian.variant) { + case Gfx::FontVariantEastAsian::Variant::Jis78: + // Enables display of JIS78 forms (OpenType feature: jp78). + features.set("jp78"sv, 1); + break; + case Gfx::FontVariantEastAsian::Variant::Jis83: + // Enables display of JIS83 forms (OpenType feature: jp83). + features.set("jp83"sv, 1); + break; + case Gfx::FontVariantEastAsian::Variant::Jis90: + // Enables display of JIS90 forms (OpenType feature: jp90). + features.set("jp90"sv, 1); + break; + case Gfx::FontVariantEastAsian::Variant::Jis04: + // Enables display of JIS04 forms (OpenType feature: jp04). + features.set("jp04"sv, 1); + break; + case Gfx::FontVariantEastAsian::Variant::Simplified: + // Enables display of simplified forms (OpenType feature: smpl). + features.set("smpl"sv, 1); + break; + case Gfx::FontVariantEastAsian::Variant::Traditional: + // Enables display of traditional forms (OpenType feature: trad). + features.set("trad"sv, 1); + break; + default: + break; + } + switch (east_asian.width) { + case Gfx::FontVariantEastAsian::Width::FullWidth: + // Enables display of full-width forms (OpenType feature: fwid). + features.set("fwid"sv, 1); + break; + case Gfx::FontVariantEastAsian::Width::Proportional: + // Enables display of proportional-width forms (OpenType feature: pwid). + features.set("pwid"sv, 1); + break; + default: + break; + } + if (east_asian.ruby) { + // Enables display of ruby forms (OpenType feature: ruby). + features.set("ruby"sv, 1); + } + + return features; +} + +Gfx::ShapeFeatures InlineLevelIterator::create_and_merge_font_features() const +{ + HashMap merged_features; + auto& computed_values = m_inline_formatting_context.containing_block().computed_values(); + + // https://www.w3.org/TR/css-fonts-3/#feature-precedence + + // FIXME 1. Font features enabled by default, including features required for a given script. + + // FIXME 2. If the font is defined via an @font-face rule, the font features implied by the font-feature-settings descriptor in the @font-face rule. + + // 3. Font features implied by the value of the ‘font-variant’ property, the related ‘font-variant’ subproperties and any other CSS property that uses OpenType features (e.g. the ‘font-kerning’ property). + for (auto& it : shape_features_map()) { + merged_features.set(it.key, it.value); + } + + // FIXME 4. Feature settings determined by properties other than ‘font-variant’ or ‘font-feature-settings’. For example, setting a non-default value for the ‘letter-spacing’ property disables common ligatures. + + // 5. Font features implied by the value of ‘font-feature-settings’ property. + auto font_feature_settings = computed_values.font_feature_settings(); + if (font_feature_settings.has_value()) { + auto const& feature_settings = font_feature_settings.value(); + for (auto const& [key, feature_value] : feature_settings) { + merged_features.set(key, feature_value.resolved(*m_current_node.ptr())); + } + } + + Gfx::ShapeFeatures shape_features; + shape_features.ensure_capacity(merged_features.size()); + + for (auto& it : merged_features) { + shape_features.append({ { it.key[0], it.key[1], it.key[2], it.key[3] }, static_cast(it.value) }); + } + + return shape_features; +} + Optional InlineLevelIterator::next_without_lookahead() { if (!m_current_node) diff --git a/Libraries/LibWeb/Layout/InlineLevelIterator.h b/Libraries/LibWeb/Layout/InlineLevelIterator.h index 1bfd90c4a37..cdd7a89a0e3 100644 --- a/Libraries/LibWeb/Layout/InlineLevelIterator.h +++ b/Libraries/LibWeb/Layout/InlineLevelIterator.h @@ -67,7 +67,9 @@ private: void exit_node_with_box_model_metrics(); void add_extra_box_model_metrics_to_item(Item&, bool add_leading_metrics, bool add_trailing_metrics); - Gfx::ShapeFeatures create_and_merge_font_features(); + + HashMap shape_features_map() const; + Gfx::ShapeFeatures create_and_merge_font_features() const; Layout::Node const* next_inline_node_in_pre_order(Layout::Node const& current, Layout::Node const* stay_within); diff --git a/Libraries/LibWeb/Layout/Node.cpp b/Libraries/LibWeb/Layout/Node.cpp index f61e8304951..b688251d23c 100644 --- a/Libraries/LibWeb/Layout/Node.cpp +++ b/Libraries/LibWeb/Layout/Node.cpp @@ -458,10 +458,20 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style) if (auto box_sizing = computed_style.box_sizing(); box_sizing.has_value()) computed_values.set_box_sizing(box_sizing.release_value()); - if (auto maybe_font_variant = computed_style.font_variant(); maybe_font_variant.has_value()) - computed_values.set_font_variant(maybe_font_variant.release_value()); if (auto maybe_font_language_override = computed_style.font_language_override(); maybe_font_language_override.has_value()) computed_values.set_font_language_override(maybe_font_language_override.release_value()); + if (auto font_variant_alternates = computed_style.font_variant_alternates(); !font_variant_alternates.normal) + computed_values.set_font_variant_alternates(font_variant_alternates); + if (auto maybe_font_variant_caps = computed_style.font_variant_caps(); maybe_font_variant_caps.has_value()) + computed_values.set_font_variant_caps(maybe_font_variant_caps.release_value()); + if (auto font_variant_east_asian = computed_style.font_variant_east_asian(); !font_variant_east_asian.normal) + computed_values.set_font_variant_east_asian(font_variant_east_asian); + if (auto font_variant_ligatures = computed_style.font_variant_ligatures(); !font_variant_ligatures.normal) + computed_values.set_font_variant_ligatures(font_variant_ligatures); + if (auto font_variant_numeric = computed_style.font_variant_numeric(); !font_variant_numeric.normal) + computed_values.set_font_variant_numeric(font_variant_numeric); + if (auto maybe_font_variant_position = computed_style.font_variant_position(); maybe_font_variant_position.has_value()) + computed_values.set_font_variant_position(maybe_font_variant_position.release_value()); if (auto maybe_font_feature_settings = computed_style.font_feature_settings(); maybe_font_feature_settings.has_value()) computed_values.set_font_feature_settings(maybe_font_feature_settings.release_value()); if (auto maybe_font_variation_settings = computed_style.font_variation_settings(); maybe_font_variation_settings.has_value()) From 4c1d4aa678fc8a7099f5cab468a88496eea47cc9 Mon Sep 17 00:00:00 2001 From: Johan Dahlin Date: Tue, 19 Nov 2024 16:12:29 +0100 Subject: [PATCH 8/8] Tests: Wait for fonts before completing tests (WIP) --- Libraries/LibWeb/CSS/FontFaceSet.cpp | 15 ++++-- Libraries/LibWeb/CSS/FontFaceSet.h | 9 ++-- Libraries/LibWeb/DOM/Document.cpp | 2 +- Libraries/LibWeb/HTML/WorkerGlobalScope.cpp | 2 +- Libraries/LibWeb/Page/Page.cpp | 8 +++ Libraries/LibWeb/Page/Page.h | 3 ++ Libraries/LibWebView/ViewImplementation.h | 4 +- Libraries/LibWebView/WebContentClient.cpp | 9 ++++ Libraries/LibWebView/WebContentClient.h | 1 + Services/WebContent/ConnectionFromClient.cpp | 1 + Services/WebContent/PageClient.cpp | 6 +++ Services/WebContent/PageClient.h | 1 + Services/WebContent/WebContentClient.ipc | 2 +- .../css-fonts/font-variant-numeric-ref.html | 7 ++- .../css/css-fonts/font-variant-numeric.html | 6 ++- UI/Headless/Test.cpp | 49 ++++++++++++++++++- UI/Headless/Test.h | 6 ++- 17 files changed, 112 insertions(+), 19 deletions(-) diff --git a/Libraries/LibWeb/CSS/FontFaceSet.cpp b/Libraries/LibWeb/CSS/FontFaceSet.cpp index 254cb3dfa3d..a68cd2774de 100644 --- a/Libraries/LibWeb/CSS/FontFaceSet.cpp +++ b/Libraries/LibWeb/CSS/FontFaceSet.cpp @@ -28,7 +28,7 @@ namespace Web::CSS { GC_DEFINE_ALLOCATOR(FontFaceSet); // https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-fontfaceset -GC::Ref FontFaceSet::construct_impl(JS::Realm& realm, Vector> const& initial_faces) +GC::Ref FontFaceSet::construct_impl(Web::Page& page, JS::Realm& realm, Vector> const& initial_faces) { auto ready_promise = WebIDL::create_promise(realm); auto set_entries = JS::Set::create(realm); @@ -37,16 +37,17 @@ GC::Ref FontFaceSet::construct_impl(JS::Realm& realm, Vectorset_add(face); - return realm.create(realm, ready_promise, set_entries); + return realm.create(page, realm, ready_promise, set_entries); } -GC::Ref FontFaceSet::create(JS::Realm& realm) +GC::Ref FontFaceSet::create(Web::Page& page, JS::Realm& realm) { - return construct_impl(realm, {}); + return construct_impl(page, realm, {}); } -FontFaceSet::FontFaceSet(JS::Realm& realm, GC::Ref ready_promise, GC::Ref set_entries) +FontFaceSet::FontFaceSet(Web::Page& page, JS::Realm& realm, GC::Ref ready_promise, GC::Ref set_entries) : DOM::EventTarget(realm) + , m_page(page) , m_set_entries(set_entries) , m_ready_promise(ready_promise) , m_status(Bindings::FontFaceSetLoadStatus::Loaded) @@ -63,6 +64,7 @@ void FontFaceSet::initialize(JS::Realm& realm) void FontFaceSet::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); + visitor.visit(m_page); visitor.visit(m_set_entries); visitor.visit(m_ready_promise); visitor.visit(m_loading_fonts); @@ -289,7 +291,10 @@ GC::Ref FontFaceSet::ready() const void FontFaceSet::resolve_ready_promise() { + dbgln("FontFaceSet::resolve_ready_promise(): {}", m_page->url()); WebIDL::resolve_promise(realm(), m_ready_promise); + + m_page->client().page_did_finish_loading_page_and_fonts(m_page->url()); } } diff --git a/Libraries/LibWeb/CSS/FontFaceSet.h b/Libraries/LibWeb/CSS/FontFaceSet.h index 1c84ed10f91..f1f19dc3f84 100644 --- a/Libraries/LibWeb/CSS/FontFaceSet.h +++ b/Libraries/LibWeb/CSS/FontFaceSet.h @@ -13,6 +13,8 @@ #include #include #include +#include +#include namespace Web::CSS { @@ -21,8 +23,8 @@ class FontFaceSet final : public DOM::EventTarget { GC_DECLARE_ALLOCATOR(FontFaceSet); public: - [[nodiscard]] static GC::Ref construct_impl(JS::Realm&, Vector> const& initial_faces); - [[nodiscard]] static GC::Ref create(JS::Realm&); + [[nodiscard]] static GC::Ref construct_impl(Web::Page&, JS::Realm&, Vector> const& initial_faces); + [[nodiscard]] static GC::Ref create(Web::Page&, JS::Realm&); virtual ~FontFaceSet() override = default; GC::Ref set_entries() const { return m_set_entries; } @@ -46,11 +48,12 @@ public: void resolve_ready_promise(); private: - FontFaceSet(JS::Realm&, GC::Ref ready_promise, GC::Ref set_entries); + FontFaceSet(Web::Page&, JS::Realm&, GC::Ref ready_promise, GC::Ref set_entries); virtual void initialize(JS::Realm&) override; virtual void visit_edges(Cell::Visitor&) override; + GC::Ref m_page; GC::Ref m_set_entries; GC::Ref m_ready_promise; // [[ReadyPromise]] diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index eefd4d6ab00..2bec4f097ff 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -1606,7 +1606,7 @@ GC::Ref Document::all() GC::Ref Document::fonts() { if (!m_fonts) - m_fonts = CSS::FontFaceSet::create(realm()); + m_fonts = CSS::FontFaceSet::create(page(), realm()); return *m_fonts; } diff --git a/Libraries/LibWeb/HTML/WorkerGlobalScope.cpp b/Libraries/LibWeb/HTML/WorkerGlobalScope.cpp index 198830a9072..9bdc6154e87 100644 --- a/Libraries/LibWeb/HTML/WorkerGlobalScope.cpp +++ b/Libraries/LibWeb/HTML/WorkerGlobalScope.cpp @@ -160,7 +160,7 @@ ENUMERATE_WORKER_GLOBAL_SCOPE_EVENT_HANDLERS(__ENUMERATE) GC::Ref WorkerGlobalScope::fonts() { if (!m_fonts) - m_fonts = CSS::FontFaceSet::create(realm()); + m_fonts = CSS::FontFaceSet::create(*page(), realm()); return *m_fonts; } diff --git a/Libraries/LibWeb/Page/Page.cpp b/Libraries/LibWeb/Page/Page.cpp index 768475e4bc3..c9795cdbb60 100644 --- a/Libraries/LibWeb/Page/Page.cpp +++ b/Libraries/LibWeb/Page/Page.cpp @@ -578,6 +578,14 @@ Vector> Page::documents_in_active_window() const return documents; } +URL::URL Page::url() const +{ + if (!top_level_traversable_is_initialized()) + return {}; + + return top_level_traversable()->active_document()->url(); +} + void Page::clear_selection() { for (auto const& document : documents_in_active_window()) { diff --git a/Libraries/LibWeb/Page/Page.h b/Libraries/LibWeb/Page/Page.h index 5892ec90981..54459f61215 100644 --- a/Libraries/LibWeb/Page/Page.h +++ b/Libraries/LibWeb/Page/Page.h @@ -212,6 +212,8 @@ public: FindInPageResult find_in_page_previous_match(); Optional last_find_in_page_query() const { return m_last_find_in_page_query; } + URL::URL url() const; + private: explicit Page(GC::Ref); virtual void visit_edges(Visitor&) override; @@ -368,6 +370,7 @@ public: virtual void page_did_request_select_dropdown([[maybe_unused]] Web::CSSPixelPoint content_position, [[maybe_unused]] Web::CSSPixels minimum_width, [[maybe_unused]] Vector items) { } virtual void page_did_finish_text_test([[maybe_unused]] String const& text) { } + virtual void page_did_finish_loading_page_and_fonts([[maybe_unused]] URL::URL const& url) { } virtual void page_did_change_theme_color(Gfx::Color) { } diff --git a/Libraries/LibWebView/ViewImplementation.h b/Libraries/LibWebView/ViewImplementation.h index fd3979cfc4a..9a8bbfaa10e 100644 --- a/Libraries/LibWebView/ViewImplementation.h +++ b/Libraries/LibWebView/ViewImplementation.h @@ -231,11 +231,14 @@ public: Function on_inspector_executed_console_script; Function on_inspector_exported_inspector_html; Function on_web_content_crashed; + Function on_loading_page_and_fonts_finish; virtual Web::DevicePixelSize viewport_size() const = 0; virtual Gfx::IntPoint to_content_position(Gfx::IntPoint widget_position) const = 0; virtual Gfx::IntPoint to_widget_position(Gfx::IntPoint content_position) const = 0; + u64 page_id() const; + protected: static constexpr auto ZOOM_MIN_LEVEL = 0.3f; static constexpr auto ZOOM_MAX_LEVEL = 5.0f; @@ -245,7 +248,6 @@ protected: WebContentClient& client(); WebContentClient const& client() const; - u64 page_id() const; virtual void update_zoom() = 0; void handle_resize(); diff --git a/Libraries/LibWebView/WebContentClient.cpp b/Libraries/LibWebView/WebContentClient.cpp index 81dc1c554e6..31245f0e80c 100644 --- a/Libraries/LibWebView/WebContentClient.cpp +++ b/Libraries/LibWebView/WebContentClient.cpp @@ -93,6 +93,15 @@ void WebContentClient::did_finish_text_test(u64 page_id, String const& text) } } +void WebContentClient::did_finish_loading_page_and_fonts(u64 page_id, URL::URL const& url) +{ + if (auto view = view_for_page_id(page_id); view.has_value()) { + if (view->on_loading_page_and_fonts_finish) + view->on_loading_page_and_fonts_finish(url); + } +} + + void WebContentClient::did_find_in_page(u64 page_id, size_t current_match_index, Optional const& total_match_count) { if (auto view = view_for_page_id(page_id); view.has_value()) { diff --git a/Libraries/LibWebView/WebContentClient.h b/Libraries/LibWebView/WebContentClient.h index b03c38553c9..26c51fd6eda 100644 --- a/Libraries/LibWebView/WebContentClient.h +++ b/Libraries/LibWebView/WebContentClient.h @@ -108,6 +108,7 @@ private: virtual void did_request_select_dropdown(u64 page_id, Gfx::IntPoint content_position, i32 minimum_width, Vector const& items) override; virtual void did_finish_handling_input_event(u64 page_id, Web::EventResult event_result) override; virtual void did_finish_text_test(u64 page_id, String const& text) override; + virtual void did_finish_loading_page_and_fonts(u64 page_id, URL::URL const& url) override; virtual void did_find_in_page(u64 page_id, size_t current_match_index, Optional const& total_match_count) override; virtual void did_change_theme_color(u64 page_id, Gfx::Color color) override; virtual void did_insert_clipboard_entry(u64 page_id, String const& data, String const& presentation_style, String const& mime_type) override; diff --git a/Services/WebContent/ConnectionFromClient.cpp b/Services/WebContent/ConnectionFromClient.cpp index 9c36a255982..c351e85aed0 100644 --- a/Services/WebContent/ConnectionFromClient.cpp +++ b/Services/WebContent/ConnectionFromClient.cpp @@ -140,6 +140,7 @@ void ConnectionFromClient::update_screen_rects(u64 page_id, Vectorpage(page_id); if (!page.has_value()) return; diff --git a/Services/WebContent/PageClient.cpp b/Services/WebContent/PageClient.cpp index f0dfccedd1e..cc2e49ebafb 100644 --- a/Services/WebContent/PageClient.cpp +++ b/Services/WebContent/PageClient.cpp @@ -355,6 +355,12 @@ void PageClient::page_did_finish_text_test(String const& text) client().async_did_finish_text_test(m_id, text); } +void PageClient::page_did_finish_loading_page_and_fonts(URL::URL const& url) +{ + dbgln("PageClient::page_did_finish_loading_page_and_fonts: {}", url); + client().async_did_finish_loading_page_and_fonts(m_id, url); +} + void PageClient::page_did_request_context_menu(Web::CSSPixelPoint content_position) { client().async_did_request_context_menu(m_id, page().css_to_device_point(content_position).to_type()); diff --git a/Services/WebContent/PageClient.h b/Services/WebContent/PageClient.h index f8680b11d5b..477f7b3a312 100644 --- a/Services/WebContent/PageClient.h +++ b/Services/WebContent/PageClient.h @@ -155,6 +155,7 @@ private: virtual void page_did_request_file_picker(Web::HTML::FileFilter accepted_file_types, Web::HTML::AllowMultipleFiles) override; virtual void page_did_request_select_dropdown(Web::CSSPixelPoint content_position, Web::CSSPixels minimum_width, Vector items) override; virtual void page_did_finish_text_test(String const& text) override; + virtual void page_did_finish_loading_page_and_fonts(URL::URL const& url) override; virtual void page_did_change_theme_color(Gfx::Color color) override; virtual void page_did_insert_clipboard_entry(String data, String presentation_style, String mime_type) override; virtual void page_did_change_audio_play_state(Web::HTML::AudioPlayState) override; diff --git a/Services/WebContent/WebContentClient.ipc b/Services/WebContent/WebContentClient.ipc index 548b3b633ab..76b66b0ad18 100644 --- a/Services/WebContent/WebContentClient.ipc +++ b/Services/WebContent/WebContentClient.ipc @@ -95,7 +95,7 @@ endpoint WebContentClient did_get_js_console_messages(u64 page_id, i32 start_index, Vector message_types, Vector messages) =| did_finish_text_test(u64 page_id, String text) =| - + did_finish_loading_page_and_fonts(u64 page_id, URL::URL url) =| did_find_in_page(u64 page_id, size_t current_match_index, Optional total_match_count) =| request_worker_agent(u64 page_id) => (IPC::File socket) // FIXME: Add required attributes to select a SharedWorker Agent diff --git a/Tests/LibWeb/Ref/expected/wpt-import/css/css-fonts/font-variant-numeric-ref.html b/Tests/LibWeb/Ref/expected/wpt-import/css/css-fonts/font-variant-numeric-ref.html index 52f17eee451..360cb20f0f7 100644 --- a/Tests/LibWeb/Ref/expected/wpt-import/css/css-fonts/font-variant-numeric-ref.html +++ b/Tests/LibWeb/Ref/expected/wpt-import/css/css-fonts/font-variant-numeric-ref.html @@ -11,10 +11,13 @@
diff --git a/Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-numeric.html b/Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-numeric.html index eaae3b29dec..fbd23297af9 100644 --- a/Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-numeric.html +++ b/Tests/LibWeb/Ref/input/wpt-import/css/css-fonts/font-variant-numeric.html @@ -14,10 +14,12 @@
diff --git a/UI/Headless/Test.cpp b/UI/Headless/Test.cpp index 8d3b529207c..f5e3f29b260 100644 --- a/UI/Headless/Test.cpp +++ b/UI/Headless/Test.cpp @@ -102,6 +102,7 @@ static ErrorOr collect_ref_tests(Vector& tests, StringView path, Str static void clear_test_callbacks(HeadlessWebView& view) { view.on_load_finish = {}; + view.on_loading_page_and_fonts_finish = {}; view.on_text_test_finish = {}; view.on_web_content_crashed = {}; } @@ -240,6 +241,7 @@ static void run_ref_test(HeadlessWebView& view, Test& test, URL::URL const& url, { auto timer = Core::Timer::create_single_shot(timeout_in_milliseconds, [&view, &test]() { view.on_load_finish = {}; + view.on_loading_page_and_fonts_finish = {}; view.on_text_test_finish = {}; view.on_test_complete({ test, TestResult::Timeout }); @@ -288,20 +290,63 @@ static void run_ref_test(HeadlessWebView& view, Test& test, URL::URL const& url, view.on_test_complete({ test, TestResult::Crashed }); }; - view.on_load_finish = [&view, &test, on_test_complete = move(on_test_complete)](auto const&) { + view.on_load_finish = [&view, &test, &on_test_complete](auto const&) { + dbgln("on loading page: page={}, url={}", test.did_load_page, view.url().serialize_path()); + test.did_load_page = true; + if (!test.did_load_fonts) + return; + //dbgln("DONE on loading page: fonts={}, {}", test.did_load_fonts, view.url()); + test.did_load_page = true; if (test.actual_screenshot) { - view.take_screenshot()->when_resolved([&test, on_test_complete = move(on_test_complete)](RefPtr screenshot) { + //dbgln("Take expectation screenshot <- this happens too early"); + view.take_screenshot()->when_resolved([&test, &on_test_complete](RefPtr screenshot) { test.expectation_screenshot = move(screenshot); + test.did_load_fonts = false; + test.did_load_page = false; on_test_complete(); }); } else { + //dbgln("\nTake actual screenshot"); view.take_screenshot()->when_resolved([&view, &test](RefPtr screenshot) { test.actual_screenshot = move(screenshot); + test.did_load_fonts = false; + test.did_load_page = false; + test.loading_reference_page = true; + dbgln("load-reference-page"); view.debug_request("load-reference-page"); }); } }; + // FIXME: rename to on_fonts_ready + view.on_loading_page_and_fonts_finish = [&view, &test, &on_test_complete](auto const&) { + dbgln("on fonts finish: load={}, url={}", test.did_load_fonts, view.url().serialize_path()); + // This callback will be called multiple times, one for normal page load and one for the reference page load. + // We only want to take the screenshot once the reference page has loaded its fonts + if (test.loading_reference_page && !view.url().serialize_path().ends_with_bytes("-ref.html"sv)) + return; + if (test.did_load_fonts) + return; + test.did_load_fonts = true; + if (!test.did_load_page) + return; + if (test.actual_screenshot) { + view.take_screenshot()->when_resolved([&test, &on_test_complete](RefPtr screenshot) { + test.expectation_screenshot = move(screenshot); + on_test_complete(); + }); + } else { + dbgln("\nTake actual screenshot"); + view.take_screenshot()->when_resolved([&view, &test](RefPtr screenshot) { + test.actual_screenshot = move(screenshot); + test.did_load_fonts = false; + test.did_load_page = false; + test.loading_reference_page = true; + dbgln("load-reference-page"); + view.debug_request("load-reference-page"); + }); + } + }; view.on_text_test_finish = [&](auto const&) { dbgln("Unexpected text test finished during ref test for {}", url); }; diff --git a/UI/Headless/Test.h b/UI/Headless/Test.h index 46b25dad707..68358c66ba9 100644 --- a/UI/Headless/Test.h +++ b/UI/Headless/Test.h @@ -66,9 +66,13 @@ struct Test { String text {}; bool did_finish_test { false }; bool did_finish_loading { false }; - + bool did_load_page { false }; + bool did_load_fonts { false }; + bool loading_reference_page { false }; RefPtr actual_screenshot {}; RefPtr expectation_screenshot {}; + + bool is_ref() const { return mode == TestMode::Ref; }; }; struct TestCompletion {