From b7aa2ff6908451677b090d75b8da5f02818b704e Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Tue, 11 Aug 2026 20:47:48 +0200 Subject: [PATCH] refactor(html): name the boxes a frame anchors against, not every element `*{position:relative}` made every element a containing block, so an absolutely positioned frame or shape resolved against its direct parent by accident. Three boxes were doing that work - the page for a slide's and a drawing's shapes, a paragraph or a cell for a text document's frames - so they state it, and the blanket rule goes. Being a containing block is also what a page-anchored frame has to get past to reach its page, which no element can while every ancestor is one. `*{margin:0}` goes with it: of the elements this sheet reaches, only `body` carries a margin from the browser's own sheet, and `body{margin:0}` already covers it. Rendering is unchanged - htmlcmp compares 332 public and 1062 private outputs, generated from one build and rendered against either sheet, with no mismatch. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01QVkpzsGS6YASMqm3NwmWib --- src/odr/internal/html/document_style.cpp | 3 +-- src/odr/internal/html/frontend.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/odr/internal/html/document_style.cpp b/src/odr/internal/html/document_style.cpp index 4fa5f7dd..aac996bb 100644 --- a/src/odr/internal/html/document_style.cpp +++ b/src/odr/internal/html/document_style.cpp @@ -407,8 +407,7 @@ std::string html::translate_frame_properties(const Frame &frame) { horizontal_position = *style.horizontal_position; } - // The frame says it positions itself: read without the stylesheet's - // `*{position:relative}`, its image would fill the viewport instead. + // The frame is what its image sizes against. std::string result; if (const AnchorType anchor_type = frame.anchor_type(); anchor_type == AnchorType::as_char) { diff --git a/src/odr/internal/html/frontend.cpp b/src/odr/internal/html/frontend.cpp index 808139e2..46256aad 100644 --- a/src/odr/internal/html/frontend.cpp +++ b/src/odr/internal/html/frontend.cpp @@ -19,8 +19,10 @@ namespace { /// Every page we write states its own body margin and background, rather than /// reading whatever the browser's default sheet says. constexpr std::string_view document_css = R"css( -*{margin:0;position:relative} body{margin:0;background:#fff} +/* What the formats anchor against: a page for shapes, a paragraph or a cell + for frames. */ +x-p,td,.odr-page-outer{position:relative} x-p{display:block;font-size:0} x-s{display:inline} .odr-background{padding:0;background:#525659} @@ -75,7 +77,7 @@ td x-p{height:inherit} .odr-sheet tbody tr.odr-sheet-pinned>*{background-image:linear-gradient(var(--odr-sheet-wash-pinned),var(--odr-sheet-wash-pinned))} .odr-sheet tbody tr:hover>th,.odr-sheet tbody tr.odr-sheet-pinned>th{background-image:linear-gradient(var(--odr-sheet-wash-ruler),var(--odr-sheet-wash-ruler))} .odr-sheet .odr-sheet-pinned-cell{outline:2px solid var(--odr-sheet-focus);outline-offset:-2px} -/* `*{position:relative}` already makes the header a containing block. */ +/* The header's `position:sticky` already makes it a containing block. */ .odr-sheet-sort{position:absolute;top:1px;right:1px;bottom:1px;width:17px;display:flex;align-items:center;justify-content:center;border-radius:2px;opacity:0;cursor:pointer} .odr-sheet-column-header:hover .odr-sheet-sort,.odr-sheet-sort-asc,.odr-sheet-sort-desc{opacity:1} .odr-sheet-sort:hover{background:var(--odr-sheet-wash-ruler)}