Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/design/lazy-loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ chunk and not to the document (#781 for ods, the issue #764 for the rest).
and the HTTP route. A text file needs no engine work, so it proves the
API.
3. **pdf, level 1.** A page has its size, so each page is a chunk of
exactly the right size.
exactly the right size. The observer moves into `viewport.js`, which
every view loads.
4. **Sheets, level 1.** `content-visibility` does not apply to a table row,
because layout containment does not apply to the inner boxes of a table.
A sheet needs a table for each chunk of rows, with the column widths
Expand Down
14 changes: 14 additions & 0 deletions src/odr/internal/html/frontend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,20 @@ void html::write_search_dark_style(const WritingState &state) {
write_dark_style(search_dark_css_asset, state);
}

void html::write_chunk_style(const WritingState &state) {
HtmlWriter &out = state.out();
out.write_header_style_begin();
// WebKit prints a chunk that it skips blank
out.out() << ".odr-chunk{content-visibility:hidden}"
".odr-chunk-near{content-visibility:visible}"
"@media print{.odr-chunk{content-visibility:visible}}";
out.write_header_style_end();
// without the script, the browser decides what is near, at the cost of
// one layout of each chunk on the first frame
out.write_raw("<noscript><style>@media screen{.odr-chunk{content-visibility:"
"auto}}</style></noscript>");
}

void html::write_editing_script(const WritingState &state) {
write_error_codes(state);
write_script(editing_js_asset, state);
Expand Down
3 changes: 3 additions & 0 deletions src/odr/internal/html/frontend.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ void write_media_style(const WritingState &state);
/// Written by every view that writes the search script.
void write_search_style(const WritingState &state);
void write_search_dark_style(const WritingState &state);
/// The browser lays out an `odr-chunk` only where `viewport.js` marks it near
/// the viewport. Inline, because a chunk must start hidden on the first frame.
void write_chunk_style(const WritingState &state);

/// For a view writing its style inline: whether to write a dark one, and the
/// `media` gating it. A print stays light, so the query names `screen`.
Expand Down
7 changes: 3 additions & 4 deletions src/odr/internal/html/frontend/text.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ body{margin:0;background:#fff}
.odr-text{--odr-text-gutter-left:max(16px,var(--odr-min-margin-left,0px));--odr-text-gutter-width:calc(var(--odr-text-gutter-left) + var(--odr-text-digits,1) * 1ch + 12px);display:flex;align-items:stretch;min-height:100vh;color:var(--odr-text-fg);font:13px/20px var(--odr-text-mono);tab-size:4;background:linear-gradient(to right,var(--odr-text-gutter) var(--odr-text-gutter-width),var(--odr-text-line) 0 calc(var(--odr-text-gutter-width) + 1px),transparent 0)}
.odr-text-body{display:flex;flex-direction:column;flex:1;min-width:0;padding:max(16px,var(--odr-min-margin-top,0px)) max(16px,var(--odr-min-margin-right,0px)) max(16px,var(--odr-min-margin-bottom,0px)) 0;white-space:pre}
.odr-text-wrap{white-space:break-spaces;word-break:break-word;overflow-wrap:anywhere}
/* The browser lays out only a chunk that `text.js` marks near the viewport. A
far chunk keeps the height it had when it was last shown. */
.odr-chunk{display:flex;flex-direction:column;content-visibility:hidden;contain-intrinsic-height:auto calc(var(--odr-text-lines,256) * 20px)}
.odr-chunk-near{content-visibility:visible}
/* A chunk far from the viewport keeps the height it had when it was last
shown. */
.odr-chunk{display:flex;flex-direction:column;contain-intrinsic-height:auto calc(var(--odr-text-lines,256) * 20px)}
.odr-chunk>div{position:relative;padding-left:calc(var(--odr-text-gutter-width) + 17px);counter-increment:odr-line}
.odr-chunk>div:hover{background:var(--odr-text-wash)}
/* The numbers are ours, not the file's: generated content stays out of a
Expand Down
20 changes: 0 additions & 20 deletions src/odr/internal/html/frontend/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -406,26 +406,6 @@
}
var editor = new TextEditor(textBody);

// A chunk within a screen of the viewport is laid out, and one further away
// is not, so the browser holds the layout of a few chunks at a time.
var chunks = textBody.children;
var near = new IntersectionObserver(
function (entries) {
entries.forEach(function (entry) {
entry.target.classList.toggle("odr-chunk-near", entry.isIntersecting);
});
},
{ rootMargin: "100% 0px" }
);
for (var i = 0; i < chunks.length; ++i) {
near.observe(chunks[i]);
}
window.addEventListener("beforeprint", function () {
for (var j = 0; j < chunks.length; ++j) {
chunks[j].classList.add("odr-chunk-near");
}
});

odr.editing.attach({
enable: function () {
textBody.setAttribute("contenteditable", "true");
Expand Down
25 changes: 25 additions & 0 deletions src/odr/internal/html/frontend/viewport.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,4 +352,29 @@
window.addEventListener("wheel", taken, { passive: true });
window.addEventListener("touchstart", taken, { passive: true });
window.addEventListener("keydown", taken);

// The browser lays out a chunk within a screen of the viewport, and not one
// further away, so it holds the layout of a few chunks at a time.
var chunks = document.getElementsByClassName("odr-chunk");
var near =
typeof IntersectionObserver === "function"
? new IntersectionObserver(
function (entries) {
entries.forEach(function (entry) {
entry.target.classList.toggle(
"odr-chunk-near",
entry.isIntersecting
);
});
},
{ rootMargin: "100% 0px" }
)
: null;
for (var i = 0; i < chunks.length; ++i) {
if (near !== null) {
near.observe(chunks[i]);
} else {
chunks[i].classList.add("odr-chunk-near");
}
}
})();
28 changes: 19 additions & 9 deletions src/odr/internal/html/pdf_file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1978,9 +1978,14 @@ class HtmlServiceImpl final : public HtmlService {
out.write_element_begin("div", HtmlElementOptions().set_class("d"));
std::size_t page_number = first_page_number;
for (const DualPageOut &page : pages_out) {
// the first page is near the viewport before the script says so
std::string classes = page.classes;
if (page_number == first_page_number) {
classes += " odr-chunk-near";
}
out.write_element_begin(
"div", HtmlElementOptions()
.set_class(page.classes)
.set_class(std::move(classes))
.set_extra(R"(id="p)" + std::to_string(page_number++) +
R"(")" + page.attributes));

Expand Down Expand Up @@ -2481,9 +2486,14 @@ class HtmlServiceImpl final : public HtmlService {
out.write_element_begin("div", HtmlElementOptions().set_class("d"));
std::size_t page_number = first_page_number;
for (const SinglePageOut &page : pages_out) {
// the first page is near the viewport before the script says so
std::string classes = page.classes;
if (page_number == first_page_number) {
classes += " odr-chunk-near";
}
out.write_element_begin(
"div", HtmlElementOptions()
.set_class(page.classes)
.set_class(std::move(classes))
.set_extra(R"(id="p)" + std::to_string(page_number++) +
R"(")" + page.attributes));
write_page_items(out, page.clip_defs, page.items, page.width, page.height,
Expand Down Expand Up @@ -2665,7 +2675,7 @@ class HtmlServiceImpl final : public HtmlService {
const double width = quarter_turn ? box_height : box_width;
const double height = quarter_turn ? box_width : box_height;

std::string classes = "p";
std::string classes = "p odr-chunk";
{
std::ostringstream w;
w << "width:" << width * pt_to_in << "in";
Expand Down Expand Up @@ -2809,17 +2819,16 @@ class HtmlServiceImpl final : public HtmlService {
"width:max-content;min-width:100%}";
// `overflow:hidden` clips to the crop box, as a viewer does: content may
// sit outside it (a bleed, or an InDesign spread's other page).
// `content-visibility` skips an off-screen page; the box states its own
// size, so no `contain-intrinsic-size` and no scroll geometry change.
out.out() << ".p{content-visibility:auto;position:relative;"
// A page is a chunk (`write_chunk_style`). The box states its own size,
// so a page that is not laid out needs no `contain-intrinsic-size`.
out.out() << ".p{position:relative;"
"margin:0 max(16px,var(--odr-min-margin-right,0px)) 0 "
"max(16px,var(--odr-min-margin-left,0px));background:#fff;"
"overflow:hidden;box-shadow:0 1px 4px rgba(0,0,0,.5)}";
// One page to a sheet. WebKit prints an off-screen `content-visibility`
// page blank.
// One page to a sheet.
out.out() << "@media print{body{background:none}"
".d{display:block;padding:0;width:auto;min-width:0}"
".p{content-visibility:visible;margin:0;box-shadow:none;"
".p{margin:0;box-shadow:none;"
"break-after:page}.p:last-child{break-after:auto}}"
"@page{margin:0}";
std::set<std::string> print_pages;
Expand Down Expand Up @@ -2849,6 +2858,7 @@ class HtmlServiceImpl final : public HtmlService {
out.out() << font_styles;
styles.write_rules(out.out());
out.write_header_style_end();
write_chunk_style(state);
write_search_style(state);
write_pdf_annotation_style(state);
write_permissions(out);
Expand Down
8 changes: 2 additions & 6 deletions src/odr/internal/html/text_file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,7 @@ class HtmlServiceImpl final : public HtmlService {
write_text_dark_style(state);
write_search_style(state);
write_search_dark_style(state);
// without the script that marks the chunks near the viewport, the
// browser decides that itself, at the cost of one layout of each
out.write_raw("<noscript><style>.odr-chunk{content-visibility:auto}</"
"style></noscript>");
write_chunk_style(state);

out.write_header_end();

Expand Down Expand Up @@ -147,8 +144,7 @@ class HtmlServiceImpl final : public HtmlService {
"div", HtmlElementOptions().set_class("odr-text-body odr-text-wrap"));
in = std::istringstream(text);
for (std::uint32_t line = 0; line < line_count; ++line) {
// the browser lays out only the chunks near the viewport, which
// `text.js` marks
// the browser lays out only the chunks near the viewport
const bool first = line % chunk_lines == 0;
if (first) {
if (line > 0) {
Expand Down
4 changes: 2 additions & 2 deletions test/data.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ odr_test_data(
odr_test_data(
PATH "reference-output/odr-public"
URL "https://github.com/opendocument-app/OpenDocument.test.output.git"
REVISION "b0a3b91d5bd4ff2060f4926becd72135490c0d98")
REVISION "f3724eecd4c6fa5a1bfb47d8ae26254fabf15630")

odr_test_data(
PATH "reference-output/odr-private"
URL "https://github.com/opendocument-app/OpenDocument.test-private.output.git"
REVISION "44062fe91c2d6ff1f61e3741b71179fc8ae98e7f")
REVISION "71bd517a915dbebc1354eb6f9ecbb986218fa7fc")
Loading