mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-29 12:19:54 +00:00
LibWeb/CSS: Parse @page bleed, marks, page-orientation
descriptors
These don't have WPT tests so I've added some myself.
This commit is contained in:
parent
9415bffd9b
commit
3a235e9050
Notes:
github-actions[bot]
2025-05-15 08:54:25 +00:00
Author: https://github.com/AtkinsSJ
Commit: 3a235e9050
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4735
10 changed files with 190 additions and 1 deletions
|
@ -108,6 +108,13 @@
|
||||||
"spec": "https://drafts.csswg.org/css-page-3/#at-page-rule",
|
"spec": "https://drafts.csswg.org/css-page-3/#at-page-rule",
|
||||||
"FIXME": "There are a lot more properties that are valid, see https://drafts.csswg.org/css-page-3/#properties-list",
|
"FIXME": "There are a lot more properties that are valid, see https://drafts.csswg.org/css-page-3/#properties-list",
|
||||||
"descriptors": {
|
"descriptors": {
|
||||||
|
"bleed": {
|
||||||
|
"initial": "auto",
|
||||||
|
"syntax": [
|
||||||
|
"auto",
|
||||||
|
"<length>"
|
||||||
|
]
|
||||||
|
},
|
||||||
"margin": {
|
"margin": {
|
||||||
"initial": "0",
|
"initial": "0",
|
||||||
"syntax": [
|
"syntax": [
|
||||||
|
@ -138,6 +145,21 @@
|
||||||
"<'margin-top'>"
|
"<'margin-top'>"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"marks": {
|
||||||
|
"initial": "none",
|
||||||
|
"syntax": [
|
||||||
|
"none",
|
||||||
|
"crop || cross"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"page-orientation": {
|
||||||
|
"initial": "upright",
|
||||||
|
"syntax": [
|
||||||
|
"upright",
|
||||||
|
"rotate-left",
|
||||||
|
"rotate-right"
|
||||||
|
]
|
||||||
|
},
|
||||||
"size": {
|
"size": {
|
||||||
"initial": "auto",
|
"initial": "auto",
|
||||||
"FIXME": "Replace with actual syntax once we parse grammar properly",
|
"FIXME": "Replace with actual syntax once we parse grammar properly",
|
||||||
|
|
|
@ -141,6 +141,8 @@
|
||||||
"copy",
|
"copy",
|
||||||
"cover",
|
"cover",
|
||||||
"crisp-edges",
|
"crisp-edges",
|
||||||
|
"crop",
|
||||||
|
"cross",
|
||||||
"crosshair",
|
"crosshair",
|
||||||
"currentcolor",
|
"currentcolor",
|
||||||
"cursive",
|
"cursive",
|
||||||
|
@ -391,6 +393,8 @@
|
||||||
"revert-layer",
|
"revert-layer",
|
||||||
"ridge",
|
"ridge",
|
||||||
"right",
|
"right",
|
||||||
|
"rotate-left",
|
||||||
|
"rotate-right",
|
||||||
"round",
|
"round",
|
||||||
"row",
|
"row",
|
||||||
"row-resize",
|
"row-resize",
|
||||||
|
@ -506,6 +510,7 @@
|
||||||
"upper-latin",
|
"upper-latin",
|
||||||
"upper-roman",
|
"upper-roman",
|
||||||
"uppercase",
|
"uppercase",
|
||||||
|
"upright",
|
||||||
"use-credentials",
|
"use-credentials",
|
||||||
"vertical-lr",
|
"vertical-lr",
|
||||||
"vertical-rl",
|
"vertical-rl",
|
||||||
|
|
|
@ -61,6 +61,36 @@ Parser::ParseErrorOr<NonnullRefPtr<CSSStyleValue const>> Parser::parse_descripto
|
||||||
},
|
},
|
||||||
[&](DescriptorMetadata::ValueType value_type) -> RefPtr<CSSStyleValue const> {
|
[&](DescriptorMetadata::ValueType value_type) -> RefPtr<CSSStyleValue const> {
|
||||||
switch (value_type) {
|
switch (value_type) {
|
||||||
|
case DescriptorMetadata::ValueType::CropOrCross: {
|
||||||
|
// crop || cross
|
||||||
|
auto first = parse_keyword_value(tokens);
|
||||||
|
auto second = parse_keyword_value(tokens);
|
||||||
|
|
||||||
|
if (!first)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
RefPtr<CSSStyleValue const> crop;
|
||||||
|
RefPtr<CSSStyleValue const> cross;
|
||||||
|
|
||||||
|
if (first->to_keyword() == Keyword::Crop)
|
||||||
|
crop = first;
|
||||||
|
else if (first->to_keyword() == Keyword::Cross)
|
||||||
|
cross = first;
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
if (!second)
|
||||||
|
return first.release_nonnull();
|
||||||
|
|
||||||
|
if (crop.is_null() && second->to_keyword() == Keyword::Crop)
|
||||||
|
crop = second.release_nonnull();
|
||||||
|
else if (cross.is_null() && second->to_keyword() == Keyword::Cross)
|
||||||
|
cross = second.release_nonnull();
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return StyleValueList::create(StyleValueVector { crop.release_nonnull(), cross.release_nonnull() }, StyleValueList::Separator::Space);
|
||||||
|
}
|
||||||
case DescriptorMetadata::ValueType::FamilyName:
|
case DescriptorMetadata::ValueType::FamilyName:
|
||||||
return parse_family_name_value(tokens);
|
return parse_family_name_value(tokens);
|
||||||
case DescriptorMetadata::ValueType::FontSrcList: {
|
case DescriptorMetadata::ValueType::FontSrcList: {
|
||||||
|
@ -84,6 +114,8 @@ Parser::ParseErrorOr<NonnullRefPtr<CSSStyleValue const>> Parser::parse_descripto
|
||||||
return nullptr;
|
return nullptr;
|
||||||
return StyleValueList::create(move(valid_sources), StyleValueList::Separator::Comma);
|
return StyleValueList::create(move(valid_sources), StyleValueList::Separator::Comma);
|
||||||
}
|
}
|
||||||
|
case DescriptorMetadata::ValueType::Length:
|
||||||
|
return parse_length_value(tokens);
|
||||||
case DescriptorMetadata::ValueType::OptionalDeclarationValue: {
|
case DescriptorMetadata::ValueType::OptionalDeclarationValue: {
|
||||||
// `component_values` already has what we want. Just skip through its tokens so code below knows we consumed them.
|
// `component_values` already has what we want. Just skip through its tokens so code below knows we consumed them.
|
||||||
while (tokens.has_next_token())
|
while (tokens.has_next_token())
|
||||||
|
|
|
@ -118,8 +118,10 @@ RefPtr<CSSStyleValue const> descriptor_initial_value(AtRuleID, DescriptorID);
|
||||||
struct DescriptorMetadata {
|
struct DescriptorMetadata {
|
||||||
enum class ValueType {
|
enum class ValueType {
|
||||||
// FIXME: Parse the grammar instead of hard-coding all the options!
|
// FIXME: Parse the grammar instead of hard-coding all the options!
|
||||||
|
CropOrCross,
|
||||||
FamilyName,
|
FamilyName,
|
||||||
FontSrcList,
|
FontSrcList,
|
||||||
|
Length,
|
||||||
OptionalDeclarationValue,
|
OptionalDeclarationValue,
|
||||||
PageSize,
|
PageSize,
|
||||||
PositivePercentage,
|
PositivePercentage,
|
||||||
|
@ -387,6 +389,8 @@ DescriptorMetadata get_descriptor_metadata(AtRuleID at_rule_id, DescriptorID des
|
||||||
return "FontSrcList"_string;
|
return "FontSrcList"_string;
|
||||||
if (syntax_string == "<declaration-value>?"sv)
|
if (syntax_string == "<declaration-value>?"sv)
|
||||||
return "OptionalDeclarationValue"_string;
|
return "OptionalDeclarationValue"_string;
|
||||||
|
if (syntax_string == "<length>"sv)
|
||||||
|
return "Length"_string;
|
||||||
if (syntax_string == "<page-size>"sv)
|
if (syntax_string == "<page-size>"sv)
|
||||||
return "PageSize"_string;
|
return "PageSize"_string;
|
||||||
if (syntax_string == "<percentage [0,∞]>"sv)
|
if (syntax_string == "<percentage [0,∞]>"sv)
|
||||||
|
@ -395,13 +399,18 @@ DescriptorMetadata get_descriptor_metadata(AtRuleID at_rule_id, DescriptorID des
|
||||||
return "String"_string;
|
return "String"_string;
|
||||||
if (syntax_string == "<unicode-range-token>#"sv)
|
if (syntax_string == "<unicode-range-token>#"sv)
|
||||||
return "UnicodeRangeTokens"_string;
|
return "UnicodeRangeTokens"_string;
|
||||||
|
dbgln("Unrecognized value type: `{}`", syntax_string);
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}();
|
}();
|
||||||
option_generator.set("value_type"sv, value_type);
|
option_generator.set("value_type"sv, value_type);
|
||||||
option_generator.append(R"~~~(
|
option_generator.append(R"~~~(
|
||||||
metadata.syntax.empend(DescriptorMetadata::ValueType::@value_type@);
|
metadata.syntax.empend(DescriptorMetadata::ValueType::@value_type@);
|
||||||
)~~~");
|
)~~~");
|
||||||
|
} else if (syntax_string == "crop || cross"sv) {
|
||||||
|
// FIXME: This is extra hacky.
|
||||||
|
option_generator.append(R"~~~(
|
||||||
|
metadata.syntax.empend(DescriptorMetadata::ValueType::CropOrCross);
|
||||||
|
)~~~");
|
||||||
} else {
|
} else {
|
||||||
// Keyword
|
// Keyword
|
||||||
option_generator.set("keyword:titlecase"sv, title_casify(syntax_string));
|
option_generator.set("keyword:titlecase"sv, title_casify(syntax_string));
|
||||||
|
|
8
Tests/LibWeb/Text/expected/css/page-bleed.txt
Normal file
8
Tests/LibWeb/Text/expected/css/page-bleed.txt
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
"bleed: auto": parsed as "auto"
|
||||||
|
"bleed: 1in": parsed as "1in"
|
||||||
|
"bleed: -2cm": parsed as "-2cm"
|
||||||
|
"bleed: auto 1in": INVALID
|
||||||
|
"bleed: 3cm auto": INVALID
|
||||||
|
"bleed: 1cm 2cm": INVALID
|
||||||
|
"bleed: orange": INVALID
|
||||||
|
"bleed: none": INVALID
|
11
Tests/LibWeb/Text/expected/css/page-marks.txt
Normal file
11
Tests/LibWeb/Text/expected/css/page-marks.txt
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
"marks: none": parsed as "none"
|
||||||
|
"marks: crop": parsed as "crop"
|
||||||
|
"marks: cross": parsed as "cross"
|
||||||
|
"marks: crop cross": parsed as "crop cross"
|
||||||
|
"marks: cross crop": parsed as "crop cross"
|
||||||
|
"marks: none crop": INVALID
|
||||||
|
"marks: crop crop": INVALID
|
||||||
|
"marks: cross cross": INVALID
|
||||||
|
"marks: cross crop cross": INVALID
|
||||||
|
"marks: orange": INVALID
|
||||||
|
"marks: auto": INVALID
|
7
Tests/LibWeb/Text/expected/css/page-page-orientation.txt
Normal file
7
Tests/LibWeb/Text/expected/css/page-page-orientation.txt
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
"page-orientation: upright": parsed as "upright"
|
||||||
|
"page-orientation: rotate-left": parsed as "rotate-left"
|
||||||
|
"page-orientation: rotate-right": parsed as "rotate-right"
|
||||||
|
"page-orientation: auto": INVALID
|
||||||
|
"page-orientation: none": INVALID
|
||||||
|
"page-orientation: rotate-left rotate-right": INVALID
|
||||||
|
"page-orientation: 90deg": INVALID
|
31
Tests/LibWeb/Text/input/css/page-bleed.html
Normal file
31
Tests/LibWeb/Text/input/css/page-bleed.html
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<style>
|
||||||
|
@page {}
|
||||||
|
</style>
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
<script>
|
||||||
|
test(() => {
|
||||||
|
let page = document.styleSheets[0].cssRules[0];
|
||||||
|
const testCases = [
|
||||||
|
"auto",
|
||||||
|
"1in",
|
||||||
|
"-2cm",
|
||||||
|
// Invalid
|
||||||
|
"auto 1in",
|
||||||
|
"3cm auto",
|
||||||
|
"1cm 2cm",
|
||||||
|
"orange",
|
||||||
|
"none"
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let testCase of testCases) {
|
||||||
|
page.style.removeProperty("bleed");
|
||||||
|
page.style.bleed = testCase;
|
||||||
|
let parsed = page.style.bleed;
|
||||||
|
if (parsed.length)
|
||||||
|
println(`"bleed: ${testCase}": parsed as "${parsed}"`);
|
||||||
|
else
|
||||||
|
println(`"bleed: ${testCase}": INVALID`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
34
Tests/LibWeb/Text/input/css/page-marks.html
Normal file
34
Tests/LibWeb/Text/input/css/page-marks.html
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<style>
|
||||||
|
@page {}
|
||||||
|
</style>
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
<script>
|
||||||
|
test(() => {
|
||||||
|
let page = document.styleSheets[0].cssRules[0];
|
||||||
|
const testCases = [
|
||||||
|
"none",
|
||||||
|
"crop",
|
||||||
|
"cross",
|
||||||
|
"crop cross",
|
||||||
|
"cross crop",
|
||||||
|
// Invalid
|
||||||
|
"none crop",
|
||||||
|
"crop crop",
|
||||||
|
"cross cross",
|
||||||
|
"cross crop cross",
|
||||||
|
"orange",
|
||||||
|
"auto"
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let testCase of testCases) {
|
||||||
|
page.style.removeProperty("marks");
|
||||||
|
page.style.marks = testCase;
|
||||||
|
let parsed = page.style.marks;
|
||||||
|
if (parsed.length)
|
||||||
|
println(`"marks: ${testCase}": parsed as "${parsed}"`);
|
||||||
|
else
|
||||||
|
println(`"marks: ${testCase}": INVALID`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
30
Tests/LibWeb/Text/input/css/page-page-orientation.html
Normal file
30
Tests/LibWeb/Text/input/css/page-page-orientation.html
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<style>
|
||||||
|
@page {}
|
||||||
|
</style>
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
<script>
|
||||||
|
test(() => {
|
||||||
|
let page = document.styleSheets[0].cssRules[0];
|
||||||
|
const testCases = [
|
||||||
|
"upright",
|
||||||
|
"rotate-left",
|
||||||
|
"rotate-right",
|
||||||
|
// Invalid
|
||||||
|
"auto",
|
||||||
|
"none",
|
||||||
|
"rotate-left rotate-right",
|
||||||
|
"90deg"
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let testCase of testCases) {
|
||||||
|
page.style.removeProperty("page-orientation");
|
||||||
|
page.style.pageOrientation = testCase;
|
||||||
|
let parsed = page.style.pageOrientation;
|
||||||
|
if (parsed.length)
|
||||||
|
println(`"page-orientation: ${testCase}": parsed as "${parsed}"`);
|
||||||
|
else
|
||||||
|
println(`"page-orientation: ${testCase}": INVALID`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
Add table
Add a link
Reference in a new issue