-
Notifications
You must be signed in to change notification settings - Fork 35
feat: clean two-level PDF outline (sections/headlines) by default, configurable - #131
Open
Smengerl wants to merge 2 commits into
Open
feat: clean two-level PDF outline (sections/headlines) by default, configurable #131Smengerl wants to merge 2 commits into
Smengerl wants to merge 2 commits into
Conversation
Smengerl
added a commit
to Smengerl/goosepaper-logicpuzzles
that referenced
this pull request
Aug 5, 2026
...vel, warn on collisions Backport of j6k4m8#131's second commit - mainline only had the first commit (the original _bookmark_css), missing all three follow-up fixes found during review: class-only selectors instead of h1./h2.-coupled ones, explicit masthead <h1> anchoring, and a UserWarning when section_bookmark_level == headline_bookmark_level. (cherry picked from commit b4131fc)
Smengerl
added a commit
to Smengerl/goosepaper-logicpuzzles
that referenced
this pull request
Aug 5, 2026
...able Six open PRs (j6k4m8#126-j6k4m8#131, all opened the same day) were never added to the "About this fork" tracking table. j6k4m8#121's row still described its original scope (content_filters + skip_title_patterns) despite the PR having grown substantially since - renamed to skip_content_filters, plus accept_content_filters/accept_title_patterns and min/max_body_text_length.
...nfigurable WeasyPrint's default UA stylesheet turns every <h1>-<h6> into a PDF bookmark matching its tag, which - combined with section headings rendering as <h2> and story headlines as <h1> - produced a flat, cluttered outline where a story's own incidental subheadings (e.g. a product review's own "Pros"/ "Cons" headings, preserved verbatim from the source markup) sat alongside section and story titles. to_pdf() now generates the bookmark-level CSS itself from three parameters (section_bookmark_level=1, headline_bookmark_level=2, body_heading_bookmarks=False by default) instead of requiring callers to ship their own stylesheet for it. Pass None for either level to leave WeasyPrint's default behavior untouched, or body_heading_bookmarks=True to keep in-body headings in the outline.
...vel, warn on collisions _bookmark_css() matched story headlines via `h1.story-headline`, coupling the PDF-outline rule to a heading tag that Story.to_html() actually takes as a variable `headline_tag` parameter (default "h1", currently unused elsewhere but live). Switch both the section and headline selectors to class-only so a future caller passing a different headline_tag doesn't silently fall out of the bookmark rule and back to WeasyPrint's raw per-tag default. The masthead title (a bare <h1> with no story-headline class) was never targeted by _bookmark_css() at all - it only ever looked right by coincidence, because WeasyPrint's UA default level for <h1> happens to equal the default section_bookmark_level of 1. Anchor it explicitly to section_bookmark_level instead of relying on that coincidence. Also warn when section_bookmark_level and headline_bookmark_level are set to the same value, since that silently collapses the two-level hierarchy the whole mechanism exists to produce. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Smengerl
force-pushed
the
feature/pdf-bookmark-levels
branch
from
August 18, 2026 19:51
b4131fc to
b413958
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
Summary
<h1>-<h6>into a PDF bookmark matching its tag, which - combined with section headings rendering as<h2>and story headlines as<h1>- produced a flat, cluttered outline where a story's own incidental subheadings (e.g. an article's preserved "Pros"/"Cons" subheadings from its source markup) sat alongside section and story titles.to_pdf()now generates the bookmark-level CSS itself from three parameters (section_bookmark_level=1,headline_bookmark_level=2,body_heading_bookmarks=Falseby default) instead of requiring callers to ship their own stylesheet for it. PassNonefor either level to leave WeasyPrint's default behavior untouched, orbody_heading_bookmarks=Trueto keep in-body headings in the outline.Why this matters
A clean, two-level outline is what lets you actually navigate a document on a reMarkable: tap the page-overview/outline button and jump straight to a section or story, no scrolling. That matters most exactly when a paper combines many different sources into one long document - a handful of RSS feeds, Wikipedia, weather, puzzles, whatever - where without this, the outline is either flooded with noise from every incidental heading inside fetched article bodies, or (on plain WeasyPrint defaults) just unusably flat. The outline stays available from anywhere in the document, regardless of how deep you've scrolled - not just at the top.
Follow-up fixes (second commit)
A closer review of
_bookmark_css()surfaced three sharp edges, all fixed in the second commit:h1.story-headline, butStory.to_html()takesheadline_tagas a parameter (default"h1", currently unused elsewhere but live). If a future caller ever renders a headline as e.g.<h2>(say, for ear stories), the bookmark rule would silently stop matching and that headline would fall back to WeasyPrint's raw per-tag default. Selectors are now class-only (.story-headline,.story-section-title), independent of the tag._bookmark_css()at all. It's a bare<h1>with no.story-headlineclass, so it only ever landed at the "right" outline level by coincidence - WeasyPrint's UA default for<h1>happens to equal the defaultsection_bookmark_levelof1. It's now explicitly anchored tosection_bookmark_level, so that relationship survives someone changing that parameter later instead of silently drifting.section_bookmark_levelandheadline_bookmark_levelto the same value collapses the whole point of the feature (a two-level outline) with no signal that anything went wrong._bookmark_css()now emits aUserWarningwhen this happens, and the docstring calls out thatheadline_bookmark_level=Nonecan numerically coincide withsection_bookmark_levelfor the same reason (both resolving to WeasyPrint's raw level for their tag).Test plan
pytest goosepaper/test_goosepaper.py- 21 passed, including 8 covering_bookmark_css(): default levels, class-only selectors, the masthead rule, a level set toNone,body_heading_bookmarks=True, everything disabled, and the new same-level warning (both that it fires and that it doesn't fire for independent/Nonelevels)pytest) - 86 passedPros/Cons,Ingredients/Steps, etc. - the exact scenario this PR targets), rendered it to PDF across 5 bookmark configurations, and inspected the actual PDF outline tree withpypdf. Confirms the default config produces a clean two-level outline with body subheadings fully excluded, and that the previously-undetectedheadline_bookmark_level=None/section_bookmark_levelcollision produced an identical flat outline to explicitly setting both to1- the exact issue the second commit's warning now surfaces.DYLD_LIBRARY_PATH=/opt/homebrew/lib; CI images with those system packages installed can run this directly.)