diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e8a26463..57e463169 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,9 @@ The release run heads these entries with the version and opens a fresh - An ods formula with spaces between a function name and its parenthesis, as LibreOffice writes `ROUND (`, parses, computes and moves with structural edits. +- A text page that holds a frame anchored to the page, such as a letter + template, prints whole on one sheet, so the frame stays where the page puts + it. ## v7.5.0 - 2026-10-06 diff --git a/src/odr/internal/html/document.cpp b/src/odr/internal/html/document.cpp index 92dbe4f84..e5a3e639f 100644 --- a/src/odr/internal/html/document.cpp +++ b/src/odr/internal/html/document.cpp @@ -157,9 +157,16 @@ viewport_mode_override(const Document &document, const HtmlConfig &config) { /// The sheet a page box prints on, sized by the first page. A text page puts /// its margins on the sheet instead, so text that runs past a sheet keeps them. +/// A frame placed on the page stands off the page edge, not the margin, so a +/// text page holding one prints whole like a slide. void write_print_page_style(HtmlWriter &out, const Document &document) { const Element root = document.root_element(); const bool text = document.document_type() == DocumentType::text; + const bool fixed = + !text || std::ranges::any_of(root.children(), [](const Element &child) { + return child.type() == ElementType::frame && + child.as_frame().anchor_type() == AnchorType::at_page; + }); PageLayout page_layout; if (text) { @@ -178,8 +185,11 @@ void write_print_page_style(HtmlWriter &out, const Document &document) { out.out() << "size:" << css_length(*page_layout.width) << " " << css_length(*page_layout.height) << ";"; } - if (!text) { + if (fixed) { out.out() << "margin:0}"; + if (text) { + out.out() << "@media print{.odr-page-outer{min-height:0!important}}"; + } out.write_header_style_end(); return; } diff --git a/test/src/html_test.cpp b/test/src/html_test.cpp index 525e41439..850aa92f4 100644 --- a/test/src/html_test.cpp +++ b/test/src/html_test.cpp @@ -497,6 +497,42 @@ TEST(html, a_text_page_prints_with_its_margins_on_the_sheet) { EXPECT_EQ(render_odt(config).find("@page{"), std::string::npos); } +TEST(html, a_text_page_with_a_frame_on_the_page_prints_whole) { + const auto render = [](const std::string &anchor) { + const std::string fodt = + R"()" + R"()" + R"()" + R"(head)" + R"(body)"; + HtmlConfig config; + config.text_document_margin = true; + std::ostringstream out; + html::translate(open(File::from_memory(fodt), + DecodeOptions::as(FileType::opendocument_text)), + config) + .list_views() + .at(0) + .write_html(out); + return std::move(out).str(); + }; + + const std::string page = render("page"); + EXPECT_NE(page.find("@page{margin:0}"), std::string::npos); + EXPECT_EQ(page.find(".odr-page-inner{margin:0!important}"), + std::string::npos); + EXPECT_NE(render("paragraph").find(".odr-page-inner{margin:0!important}"), + std::string::npos); +} + // A browser floors the page scale at 0.25, so a page more than four screens // wide cannot be zoomed out to. A wider page states a floor of its own. TEST(html, fitting_the_width_states_a_floor_a_wide_page_needs) {