diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 540f20dea19..5bc60eb94e0 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -80,6 +80,7 @@ set(SOURCES CSS/CSSKeyframesRule.cpp CSS/CSSLayerBlockRule.cpp CSS/CSSLayerStatementRule.cpp + CSS/CSSMarginRule.cpp CSS/CSSMediaRule.cpp CSS/CSSNamespaceRule.cpp CSS/CSSNestedDeclarations.cpp diff --git a/Libraries/LibWeb/CSS/CSSMarginRule.cpp b/Libraries/LibWeb/CSS/CSSMarginRule.cpp new file mode 100644 index 00000000000..d3c5da95c2b --- /dev/null +++ b/Libraries/LibWeb/CSS/CSSMarginRule.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2025, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace Web::CSS { + +GC_DEFINE_ALLOCATOR(CSSMarginRule); + +GC::Ref CSSMarginRule::create(JS::Realm& realm, FlyString name, GC::Ref style) +{ + return realm.create(realm, move(name), style); +} + +CSSMarginRule::CSSMarginRule(JS::Realm& realm, FlyString name, GC::Ref style) + : CSSRule(realm, Type::Margin) + , m_name(name.to_ascii_lowercase()) + , m_style(style) +{ + m_style->set_parent_rule(*this); +} + +void CSSMarginRule::initialize(JS::Realm& realm) +{ + WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSMarginRule); + Base::initialize(realm); +} + +String CSSMarginRule::serialized() const +{ + // AD-HOC: There is currently no spec for serializing CSSMarginRule. + StringBuilder builder; + builder.appendff("@{} {{ ", m_name); + if (m_style->length() > 0) { + builder.append(m_style->serialized()); + builder.append(' '); + } + builder.append('}'); + + return builder.to_string_without_validation(); +} + +void CSSMarginRule::visit_edges(Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_style); +} + +// https://drafts.csswg.org/css-page-3/#syntax-page-selector +bool is_margin_rule_name(StringView name) +{ + return name.equals_ignoring_ascii_case("top-left-corner"sv) + || name.equals_ignoring_ascii_case("top-left"sv) + || name.equals_ignoring_ascii_case("top-center"sv) + || name.equals_ignoring_ascii_case("top-right"sv) + || name.equals_ignoring_ascii_case("top-right-corner"sv) + || name.equals_ignoring_ascii_case("bottom-left-corner"sv) + || name.equals_ignoring_ascii_case("bottom-left"sv) + || name.equals_ignoring_ascii_case("bottom-center"sv) + || name.equals_ignoring_ascii_case("bottom-right"sv) + || name.equals_ignoring_ascii_case("bottom-right-corner"sv) + || name.equals_ignoring_ascii_case("left-top"sv) + || name.equals_ignoring_ascii_case("left-middle"sv) + || name.equals_ignoring_ascii_case("left-bottom"sv) + || name.equals_ignoring_ascii_case("right-top"sv) + || name.equals_ignoring_ascii_case("right-middle"sv) + || name.equals_ignoring_ascii_case("right-bottom"sv); +} + +} diff --git a/Libraries/LibWeb/CSS/CSSMarginRule.h b/Libraries/LibWeb/CSS/CSSMarginRule.h new file mode 100644 index 00000000000..29ba2885288 --- /dev/null +++ b/Libraries/LibWeb/CSS/CSSMarginRule.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::CSS { + +// https://drafts.csswg.org/cssom/#cssmarginrule +class CSSMarginRule final : public CSSRule { + WEB_PLATFORM_OBJECT(CSSMarginRule, CSSRule); + GC_DECLARE_ALLOCATOR(CSSMarginRule); + +public: + [[nodiscard]] static GC::Ref create(JS::Realm&, FlyString name, GC::Ref); + + virtual ~CSSMarginRule() override = default; + + String name() const { return m_name.to_string(); } + GC::Ref style() { return m_style; } + GC::Ref style() const { return m_style; } + +private: + CSSMarginRule(JS::Realm&, FlyString name, GC::Ref); + + virtual void initialize(JS::Realm&) override; + virtual String serialized() const override; + virtual void visit_edges(Visitor&) override; + + FlyString m_name; + GC::Ref m_style; +}; + +bool is_margin_rule_name(StringView); + +} diff --git a/Libraries/LibWeb/CSS/CSSMarginRule.idl b/Libraries/LibWeb/CSS/CSSMarginRule.idl new file mode 100644 index 00000000000..a5ed49e2cad --- /dev/null +++ b/Libraries/LibWeb/CSS/CSSMarginRule.idl @@ -0,0 +1,10 @@ +#import +#import + +// https://drafts.csswg.org/cssom/#cssmarginrule +[Exposed=Window] +interface CSSMarginRule : CSSRule { + readonly attribute CSSOMString name; + // FIXME: This should be CSSMarginDescriptors, but that has no spec. https://github.com/w3c/csswg-drafts/issues/10106 + [SameObject, PutForwards=cssText] readonly attribute CSSStyleProperties style; +}; diff --git a/Libraries/LibWeb/CSS/CSSPageRule.cpp b/Libraries/LibWeb/CSS/CSSPageRule.cpp index 23f98374d3b..1114144ce9a 100644 --- a/Libraries/LibWeb/CSS/CSSPageRule.cpp +++ b/Libraries/LibWeb/CSS/CSSPageRule.cpp @@ -104,7 +104,7 @@ String CSSPageRule::serialized() const StringBuilder builder; - // AD-HOC: There's no spec for this yet. + // AD-HOC: There's no spec for this yet, but Chrome puts declarations before margin rules. builder.append("@page "sv); if (auto selector = selector_text(); !selector.is_empty()) builder.appendff("{} ", selector); @@ -113,6 +113,15 @@ String CSSPageRule::serialized() const builder.append(descriptors.serialized()); builder.append(' '); } + for (size_t i = 0; i < css_rules().length(); i++) { + auto rule = css_rules().item(i); + auto result = rule->css_text(); + + if (result.is_empty()) + continue; + + builder.appendff("{} ", result); + } builder.append("}"sv); return builder.to_string_without_validation(); diff --git a/Libraries/LibWeb/CSS/CSSRule.cpp b/Libraries/LibWeb/CSS/CSSRule.cpp index fe7108b6844..51b7544399f 100644 --- a/Libraries/LibWeb/CSS/CSSRule.cpp +++ b/Libraries/LibWeb/CSS/CSSRule.cpp @@ -95,6 +95,7 @@ FlyString const& CSSRule::parent_layer_internal_qualified_name_slow_case() const case Type::NestedDeclarations: case Type::Property: case Type::Page: + case Type::Margin: break; } } diff --git a/Libraries/LibWeb/CSS/CSSRule.h b/Libraries/LibWeb/CSS/CSSRule.h index b3ee377024c..aeeb1242616 100644 --- a/Libraries/LibWeb/CSS/CSSRule.h +++ b/Libraries/LibWeb/CSS/CSSRule.h @@ -31,6 +31,7 @@ public: Page = 6, Keyframes = 7, Keyframe = 8, + Margin = 9, Namespace = 10, Supports = 12, // AD-HOC: These are not included in the spec, but we need them internally. So, their numbers are arbitrary. diff --git a/Libraries/LibWeb/CSS/CSSRuleList.cpp b/Libraries/LibWeb/CSS/CSSRuleList.cpp index e2ffcf08956..c503b183287 100644 --- a/Libraries/LibWeb/CSS/CSSRuleList.cpp +++ b/Libraries/LibWeb/CSS/CSSRuleList.cpp @@ -152,6 +152,7 @@ void CSSRuleList::for_each_effective_rule(TraversalOrder order, Function(*rule).for_each_effective_rule(order, callback); @@ -161,10 +162,10 @@ void CSSRuleList::for_each_effective_rule(TraversalOrder order, Function #include +#include #include #include #include @@ -1438,7 +1439,8 @@ bool Parser::is_valid_in_the_current_context(Declaration const&) const case RuleContext::AtFontFace: case RuleContext::AtPage: case RuleContext::AtProperty: - // @font-face, @page, and @property have descriptor declarations + case RuleContext::Margin: + // These have descriptor declarations return true; case RuleContext::AtKeyframes: @@ -1455,9 +1457,9 @@ bool Parser::is_valid_in_the_current_context(Declaration const&) const bool Parser::is_valid_in_the_current_context(AtRule const& at_rule) const { - // All at-rules can appear at the top level + // All at-rules can appear at the top level, except margin rules if (m_rule_context.is_empty()) - return true; + return !is_margin_rule_name(at_rule.name); // Only grouping rules can be nested within style rules if (m_rule_context.contains_slow(RuleContext::Style)) @@ -1482,13 +1484,16 @@ bool Parser::is_valid_in_the_current_context(AtRule const& at_rule) const // @supports cannot check for at-rules return false; + case RuleContext::AtPage: + // @page rules can contain margin rules + return is_margin_rule_name(at_rule.name); + case RuleContext::AtFontFace: case RuleContext::AtKeyframes: case RuleContext::Keyframe: - case RuleContext::AtPage: case RuleContext::AtProperty: + case RuleContext::Margin: // These can't contain any at-rules - // FIXME: Eventually @page can contain margin-box at-rules: https://drafts.csswg.org/css-page-3/#margin-at-rules return false; } @@ -1530,6 +1535,7 @@ bool Parser::is_valid_in_the_current_context(QualifiedRule const&) const case RuleContext::AtPage: case RuleContext::AtProperty: case RuleContext::Keyframe: + case RuleContext::Margin: // These can't contain qualified rules return false; } diff --git a/Libraries/LibWeb/CSS/Parser/Parser.h b/Libraries/LibWeb/CSS/Parser/Parser.h index ca2eca83276..5d715531e36 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Libraries/LibWeb/CSS/Parser/Parser.h @@ -243,6 +243,7 @@ private: GC::Ptr convert_to_keyframes_rule(AtRule const&); GC::Ptr convert_to_import_rule(AtRule const&); GC::Ptr convert_to_layer_rule(AtRule const&, Nested); + GC::Ptr convert_to_margin_rule(AtRule const&); GC::Ptr convert_to_media_rule(AtRule const&, Nested); GC::Ptr convert_to_namespace_rule(AtRule const&); GC::Ptr convert_to_page_rule(AtRule const& rule); diff --git a/Libraries/LibWeb/CSS/Parser/RuleContext.cpp b/Libraries/LibWeb/CSS/Parser/RuleContext.cpp index 4f41e5f065d..56aeccf7287 100644 --- a/Libraries/LibWeb/CSS/Parser/RuleContext.cpp +++ b/Libraries/LibWeb/CSS/Parser/RuleContext.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include namespace Web::CSS::Parser { @@ -25,6 +26,8 @@ RuleContext rule_context_type_for_rule(CSSRule::Type rule_type) return RuleContext::AtSupports; case CSSRule::Type::LayerBlock: return RuleContext::AtLayer; + case CSSRule::Type::Margin: + return RuleContext::Margin; case CSSRule::Type::NestedDeclarations: return RuleContext::Style; case CSSRule::Type::Page: @@ -56,6 +59,8 @@ RuleContext rule_context_type_for_at_rule(FlyString const& name) return RuleContext::AtProperty; if (name.equals_ignoring_ascii_case("page"sv)) return RuleContext::AtPage; + if (is_margin_rule_name(name)) + return RuleContext::Margin; return RuleContext::Unknown; } diff --git a/Libraries/LibWeb/CSS/Parser/RuleContext.h b/Libraries/LibWeb/CSS/Parser/RuleContext.h index 963a6ef9593..fa6bce81928 100644 --- a/Libraries/LibWeb/CSS/Parser/RuleContext.h +++ b/Libraries/LibWeb/CSS/Parser/RuleContext.h @@ -23,6 +23,7 @@ enum class RuleContext : u8 { AtLayer, AtProperty, AtPage, + Margin, }; RuleContext rule_context_type_for_rule(CSSRule::Type); RuleContext rule_context_type_for_at_rule(FlyString const&); diff --git a/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp b/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp index d9a48367116..491dd480634 100644 --- a/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/RuleParsing.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -102,6 +103,9 @@ GC::Ptr Parser::convert_to_rule(Rule const& rule, Nested nested) if (at_rule.name.equals_ignoring_ascii_case("layer"sv)) return convert_to_layer_rule(at_rule, nested); + if (is_margin_rule_name(at_rule.name)) + return convert_to_margin_rule(at_rule); + if (at_rule.name.equals_ignoring_ascii_case("media"sv)) return convert_to_media_rule(at_rule, nested); @@ -697,20 +701,25 @@ static Optional parse_page_selector_list(Vector Parser::convert_to_page_rule(AtRule const& rule) +GC::Ptr Parser::convert_to_page_rule(AtRule const& page_rule) { // https://drafts.csswg.org/css-page-3/#syntax-page-selector // @page = @page ? { } - auto page_selectors = parse_page_selector_list(rule.prelude); + auto page_selectors = parse_page_selector_list(page_rule.prelude); if (!page_selectors.has_value()) return nullptr; GC::RootVector> child_rules { realm().heap() }; DescriptorList descriptors { AtRuleID::Page }; - rule.for_each_as_declaration_rule_list( + page_rule.for_each_as_declaration_rule_list( [&](auto& at_rule) { - // FIXME: Parse margin rules here. - (void)at_rule; + if (auto converted_rule = convert_to_rule(at_rule, Nested::No)) { + if (is(*converted_rule)) { + child_rules.append(*converted_rule); + } else { + dbgln_if(CSS_PARSER_DEBUG, "CSSParser: nested {} is not allowed inside @page rule; discarding.", converted_rule->class_name()); + } + } }, [&](auto& declaration) { if (auto descriptor = convert_to_descriptor(AtRuleID::Page, declaration); descriptor.has_value()) { @@ -722,4 +731,21 @@ GC::Ptr Parser::convert_to_page_rule(AtRule const& rule) return CSSPageRule::create(realm(), page_selectors.release_value(), CSSPageDescriptors::create(realm(), descriptors.release_descriptors()), rule_list); } +GC::Ptr Parser::convert_to_margin_rule(AtRule const& rule) +{ + // https://drafts.csswg.org/css-page-3/#syntax-page-selector + // There are lots of these, but they're all in the format: + // @foo = @foo { }; + + // FIXME: The declaration list should be a CSSMarginDescriptors, but that has no spec definition: + // https://github.com/w3c/csswg-drafts/issues/10106 + // So, we just parse a CSSStyleProperties instead for now. + PropertiesAndCustomProperties properties; + rule.for_each_as_declaration_list([&](auto const& declaration) { + extract_property(declaration, properties); + }); + auto style = CSSStyleProperties::create(realm(), move(properties.properties), move(properties.custom_properties)); + return CSSMarginRule::create(realm(), rule.name, style); +} + } diff --git a/Libraries/LibWeb/CSS/StyleComputer.cpp b/Libraries/LibWeb/CSS/StyleComputer.cpp index bb8603ba80d..32b0c39be4f 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -3025,6 +3025,7 @@ void StyleComputer::build_qualified_layer_names_cache() case CSSRule::Type::FontFace: case CSSRule::Type::Keyframes: case CSSRule::Type::Keyframe: + case CSSRule::Type::Margin: case CSSRule::Type::Namespace: case CSSRule::Type::NestedDeclarations: case CSSRule::Type::Page: diff --git a/Libraries/LibWeb/Dump.cpp b/Libraries/LibWeb/Dump.cpp index 5916a3a49b8..993c95e9364 100644 --- a/Libraries/LibWeb/Dump.cpp +++ b/Libraries/LibWeb/Dump.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -687,6 +688,9 @@ void dump_rule(StringBuilder& builder, CSS::CSSRule const& rule, int indent_leve case CSS::CSSRule::Type::LayerStatement: dump_layer_statement_rule(builder, as(rule), indent_levels); break; + case CSS::CSSRule::Type::Margin: + dump_margin_rule(builder, as(rule), indent_levels); + break; case CSS::CSSRule::Type::Media: dump_media_rule(builder, as(rule), indent_levels); break; @@ -774,6 +778,18 @@ void dump_page_rule(StringBuilder& builder, CSS::CSSPageRule const& page, int in indent(builder, indent_levels + 1); builder.appendff("selector: {}\n", page.selector_text()); dump_descriptors(builder, page.descriptors(), indent_levels + 1); + + indent(builder, indent_levels + 1); + builder.appendff(" Rules ({}):\n", page.css_rules().length()); + for (auto& rule : page.css_rules()) + dump_rule(builder, rule, indent_levels + 2); +} + +void dump_margin_rule(StringBuilder& builder, CSS::CSSMarginRule const& margin, int indent_levels) +{ + indent(builder, indent_levels + 1); + builder.appendff("name: {}\n", margin.name()); + dump_style_properties(builder, margin.style(), indent_levels + 1); } void dump_supports_rule(StringBuilder& builder, CSS::CSSSupportsRule const& supports, int indent_levels) diff --git a/Libraries/LibWeb/Dump.h b/Libraries/LibWeb/Dump.h index 9f29b93d2c8..0b6306b55b8 100644 --- a/Libraries/LibWeb/Dump.h +++ b/Libraries/LibWeb/Dump.h @@ -31,6 +31,7 @@ void dump_keyframe_rule(StringBuilder&, CSS::CSSKeyframeRule const&, int indent_ void dump_keyframes_rule(StringBuilder&, CSS::CSSKeyframesRule const&, int indent_levels = 0); void dump_media_rule(StringBuilder&, CSS::CSSMediaRule const&, int indent_levels = 0); void dump_page_rule(StringBuilder&, CSS::CSSPageRule const&, int indent_levels = 0); +void dump_margin_rule(StringBuilder&, CSS::CSSMarginRule const&, int indent_levels = 0); void dump_style_rule(StringBuilder&, CSS::CSSStyleRule const&, int indent_levels = 0); void dump_supports_rule(StringBuilder&, CSS::CSSSupportsRule const&, int indent_levels = 0); void dump_property_rule(StringBuilder&, CSS::CSSPropertyRule const&, int indent_levels = 0); diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 55daceb4293..7a857b6fccc 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -200,6 +200,7 @@ class CSSKeyframesRule; class CSSKeywordValue; class CSSLayerBlockRule; class CSSLayerStatementRule; +class CSSMarginRule; class CSSMediaRule; class CSSNamespaceRule; class CSSNestedDeclarations; diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index 58115513529..2ffce4524df 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -31,6 +31,7 @@ libweb_js_bindings(CSS/CSSKeyframeRule) libweb_js_bindings(CSS/CSSKeyframesRule) libweb_js_bindings(CSS/CSSLayerBlockRule) libweb_js_bindings(CSS/CSSLayerStatementRule) +libweb_js_bindings(CSS/CSSMarginRule) libweb_js_bindings(CSS/CSSMediaRule) libweb_js_bindings(CSS/CSS NAMESPACE) libweb_js_bindings(CSS/CSSNamespaceRule) diff --git a/Tests/LibWeb/Text/expected/all-window-properties.txt b/Tests/LibWeb/Text/expected/all-window-properties.txt index 38c35b2375a..bcb262b15da 100644 --- a/Tests/LibWeb/Text/expected/all-window-properties.txt +++ b/Tests/LibWeb/Text/expected/all-window-properties.txt @@ -46,6 +46,7 @@ CSSKeyframeRule CSSKeyframesRule CSSLayerBlockRule CSSLayerStatementRule +CSSMarginRule CSSMediaRule CSSNamespaceRule CSSNestedDeclarations diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-page/parsing/margin-rules-001.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-page/parsing/margin-rules-001.txt index 897e9ee99e2..19eb17b3ae8 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-page/parsing/margin-rules-001.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-page/parsing/margin-rules-001.txt @@ -2,8 +2,7 @@ Harness status: OK Found 33 tests -17 Pass -16 Fail +33 Pass Pass margin-rules-001 Pass @top-left-corner{ } should be an invalid rule Pass @top-left{ } should be an invalid rule @@ -21,19 +20,19 @@ Pass @left-bottom{ } should be an invalid rule Pass @right-top{ } should be an invalid rule Pass @right-middle{ } should be an invalid rule Pass @right-bottom{ } should be an invalid rule -Fail @page { @top-left-corner { } } should be a valid rule -Fail @page { @top-left { } } should be a valid rule -Fail @page { @top-center { } } should be a valid rule -Fail @page { @top-right { } } should be a valid rule -Fail @page { @top-right-corner { } } should be a valid rule -Fail @page { @bottom-left-corner { } } should be a valid rule -Fail @page { @bottom-left { } } should be a valid rule -Fail @page { @bottom-center { } } should be a valid rule -Fail @page { @bottom-right { } } should be a valid rule -Fail @page { @bottom-right-corner { } } should be a valid rule -Fail @page { @left-top { } } should be a valid rule -Fail @page { @left-middle { } } should be a valid rule -Fail @page { @left-bottom { } } should be a valid rule -Fail @page { @right-top { } } should be a valid rule -Fail @page { @right-middle { } } should be a valid rule -Fail @page { @right-bottom { } } should be a valid rule \ No newline at end of file +Pass @page { @top-left-corner { } } should be a valid rule +Pass @page { @top-left { } } should be a valid rule +Pass @page { @top-center { } } should be a valid rule +Pass @page { @top-right { } } should be a valid rule +Pass @page { @top-right-corner { } } should be a valid rule +Pass @page { @bottom-left-corner { } } should be a valid rule +Pass @page { @bottom-left { } } should be a valid rule +Pass @page { @bottom-center { } } should be a valid rule +Pass @page { @bottom-right { } } should be a valid rule +Pass @page { @bottom-right-corner { } } should be a valid rule +Pass @page { @left-top { } } should be a valid rule +Pass @page { @left-middle { } } should be a valid rule +Pass @page { @left-bottom { } } should be a valid rule +Pass @page { @right-top { } } should be a valid rule +Pass @page { @right-middle { } } should be a valid rule +Pass @page { @right-bottom { } } should be a valid rule \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-page/parsing/margin-rules-002.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-page/parsing/margin-rules-002.txt index 11a2f6bad4c..5d3ddb9865e 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-page/parsing/margin-rules-002.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-page/parsing/margin-rules-002.txt @@ -2,8 +2,9 @@ Harness status: OK Found 4 tests -4 Fail -Fail margin rule without any preceding @page properties +1 Pass +3 Fail +Pass margin rule without any preceding @page properties Fail margin rule followed by @page properties Fail margin rule preceded by @page properties Fail margin rules with @page properties between \ No newline at end of file