Skip to content

feat(html): read a document in the dark - #698

Merged
andiwand merged 4 commits into
mainfrom
feat/html-color-scheme
Aug 18, 2026
Merged

feat(html): read a document in the dark#698
andiwand merged 4 commits into
mainfrom
feat/html-color-scheme

Conversation

@andiwand

@andiwand andiwand commented Aug 18, 2026

Copy link
Copy Markdown
Member

🤖 Generated with Claude Code

Following user feedback:

I love this app, and I appreciate the more accurate display that's available now, but a toggle between that and the old way that followed the system dark/light preference would be super helpful. I often read stuff at night and the bright white background makes it uncomfortable for me. […] Also, the toggle between show page edges and not needs to at least have a character's width of space around the edge of the screen when off.

Two things, and they trace to the same corner of the renderer.

The white background

body{margin:0;background:#fff} landed in #683. Before it document_css declared no background at all, so whatever dark theming the reader's webview applied showed through — that is the "old way" the feedback misses. Core has never had a colour scheme of its own.

HtmlConfig::color_scheme is now light (what it has always been), dark, or system, which follows prefers-color-scheme. It reaches every document type: a text document flowing or paged, a sheet, a slide, a drawing.

The colours a file authors arrive as inline styles, which no media query can reach — so a dark scheme cannot simply drop them as the file is read, and a document setting fo:color="#000000" would render black on black. dark.css overrides them instead: every fill gives way to the page and every run of text to one legible against it. A background-image is left alone, so a page printed on a picture keeps it.

Rather than writing every rule twice — once plain, once inside a query — the stylesheet is written once and the element carrying it is gated: <style media="(prefers-color-scheme: dark)">, or the same attribute on the <link> where the config links shipped resources. One asset, one cascade, both modes.

light emits nothing at all, and deliberately declares no color-scheme either, so a reader whose webview darkens the page for them keeps that until the apps offer the setting.

Mirrored in the python, wasm, jni and apple bindings as HtmlColorScheme.

The screen edge

With page margins off the text went into a bare <div>; there is no page box to inset it there, and body{margin:0} left nothing else, so it began at the first pixel of the screen. That div is now .odr-text-flow{padding:3mm} — a physical measure, like a page's own margin, rather than a ch of whatever font the browser happens to default to.

Trade-off worth knowing

A slide loses its own fills in dark mode. That falls out of overriding the document's colours across every document type, and it is what keeps text over a light-filled shape readable. The pdf, source, text-file and file-listing views are untouched — they bring their own styling, and the pdf pipeline paints its own page backgrounds.

Testing

  • 646 tests pass (everything but the reference-output suite). Three new tests cover the padding, the three schemes, and that a linked dark.css keeps its media query and is served.
  • Rendered in Chrome: flowing text, paged text, spreadsheet and a presentation all read correctly in dark; the light path is byte-identical to before apart from the one new rule.
  • No reference-output regen: compare-html compares rendered pages, and the only light-path change selects .odr-text-flow, which that suite never emits — it renders with text_document_margin = true.
  • Bindings compile: jni (Java and C++), apple, python. wasm is unbuilt here (needs emscripten); its ordinals are pinned in wasm/tests/enums.test.mjs.

The apps now need to surface colorScheme next to the existing page-edges toggle.

`HtmlConfig::color_scheme` is `light` — a white page, what every document has
rendered against until now — or `dark`, or `system`, which follows the reader's
`prefers-color-scheme`. It reaches every document type: a text document flowing
or paged, a sheet, a slide, a drawing.

The colors a file authors arrive as inline styles, which no media query can
reach, so a dark scheme cannot drop them as the file is read: a document setting
`fo:color="#000000"` would render black on black. `dark.css` overrides them
instead — every fill gives way to the page and every run of text to one legible
against it, which costs a slide its own fills and is what keeps its text
readable. A `background-image` is left alone: a page printed on a picture keeps
it.

Rather than writing every rule twice, once plain and once inside a query, the
stylesheet is written once and the element carrying it is gated —
`<style media="(prefers-color-scheme: dark)">`, or the same attribute on the
`<link>` where the config links shipped resources. One asset, one cascade, both
modes, and `light` emits nothing at all: it declares no `color-scheme` either,
so a reader whose webview darkens the page for them keeps that until the apps
offer the setting.

Reflowed to the viewport, a text document also gets 3mm of padding. It had none:
there is no page box to inset it there, and the body carries no margin of its
own, so the text began at the first pixel of the screen. A page's own margin is
a physical measure and so is this one, rather than a `ch` of whatever font the
browser happens to default to.

Mirrored in the python, wasm, jni and apple bindings as `HtmlColorScheme`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017rD1SK4k5Y6GQSzbCeKExY

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f7d106b57a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/odr/internal/html/frontend.cpp Outdated
andiwand and others added 3 commits August 18, 2026 08:34
Only the document views had a dark palette. Every view but the pdf one has one
now — a text file and its gutter, a source view and its six syntax cuts, a file
listing and its washes, an image, a font specimen — each written straight after
the sheet it restates and gated the same way. The media view is black in any
scheme and keeps no dark sheet of its own. The pdf view is left out: it paints
its page backgrounds itself, and turning those over is a different job.

The one dark stylesheet is split along the same lines its light counterparts
already follow, `document-dark.css` beside `document.css` and so on, so a page
carries the dark palette of the views it is, not of all of them. A view writing
its style inline rather than as a shipped asset asks `writes_dark_style` and
`dark_style_media` for the same gating.

A shape's fill is not a background: `translate_drawing_style` writes it as the
svg `fill` that the rect or circle inside inherits, so clearing
`background-color` left a light-filled text box light while the ink above it
turned white — the title of an odg read as white on white. The fill gives way
like a background does. The stroke is left as the file set it: it is the shape's
line, not its ground.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017rD1SK4k5Y6GQSzbCeKExY
`FileTypeCapabilities::color_scheme` answers for a file type what
`translate_html` answers about rendering at all: whether the view it renders as
honors `HtmlConfig::color_scheme`. A caller can offer the setting for the files
it applies to rather than for all of them, before it holds one.

True for every type that renders but a pdf, whose pages carry their own painted
backgrounds, and audio and video, which are black in any scheme. A decoded file
narrows it the way it narrows `translate_html`: a scheme only reaches html there
is.

The declaration is held against the renderer rather than maintained by hand:
the new test renders a file of each type in `light` and in `dark` and asserts
that the two differ exactly where the table says they should — flipping the pdf
row to claim support fails it.

Mirrored in the python, wasm, jni and apple bindings beside the capabilities
already there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017rD1SK4k5Y6GQSzbCeKExY
Trim the comments and changelog entries added with the color scheme down
to the point they make. Also drops a wrong line in ODRHtml.h: the source
view and the file listing do honor the scheme.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEQYAReR6DqLpHFSisoehj
@andiwand
andiwand enabled auto-merge (squash) August 18, 2026 07:12
@andiwand
andiwand merged commit 6dffb4d into main Aug 18, 2026
36 checks passed
@andiwand
andiwand deleted the feat/html-color-scheme branch August 18, 2026 07:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant