Skip to content

GraphCompose v2.4.1

Latest

Choose a tag to compare

@github-actions github-actions released this 21 Sep 22:31
· 5 commits to main since this release

v2.4.1 — 2026-09-21

Performance

  • A barcode is drawn as vector shapes, not as an image, in PDF and PPTX.
    PdfBarcodeFragmentRenderHandler turned ZXing's bit matrix into a bitmap, one setRGB
    call per pixel, wrote it to PNG with ImageIO — which, with its default cache, buffers a
    write to a stream through a temporary file in java.io.tmpdir — and passed the PNG to
    PDImageXObject.createFromByteArray, which decoded it and compressed the pixels again. The
    handler now fills the background as one rectangle and the dark cells, merged row by row
    into rectangles, as one path under a single transform to matrix cells: no bitmap, no
    image stream, no file. The matrix is the one ZXing produced for the bitmap, stretched over
    the fragment box the same way, so every symbology keeps its placement — rasterised on
    screen, the modules land on the same pixels and only anti-aliased edges differ — while the
    edges stay sharp at any zoom. Measured locally on the feature-rich benchmark document (a
    QR code and a Code 128; interleaved A/B against the previous handler, three rounds):
    median render 29 → 1.7 ms, allocation per document 7.0 → 0.7 MB, PDF 6.3 → 4.1 KB, and
    outliers of up to 390 ms — the temporary-file writes — are gone. The committed barcode
    showcase preview drops from 11.4 KB to 4.3 KB.



    PptxBarcodeFragmentRenderHandler placed a bitmap of the same matrix as a picture, written
    to PNG through the same temporary-file path. It now draws the same matrix as two freeform
    shapes — the background over the box and the dark cells as one path — so a slide carries a
    sharp barcode made of native shapes and no picture.



    Colours composite as they did in the bitmap, where each cell is foreground or background
    and never both: when the foreground is not opaque, the dark cells are cut out of the
    background, so a translucent or transparent foreground lands on whatever lies under the
    barcode rather than on the background. A translucent barcode rendered both ways matches at
    every cell centre; only anti-aliased cell edges differ. No public API change.

Tests

  • PdfBarcodeRenderTest rasterises the page and reads the barcode back: each of the eight
    formats decodes to its content, every cell of the matrix lands in place and in the chosen
    colour for all eight, each translucent cell composites with the page rather than with the
    other colour, a transparent background shows the page through, a transparent foreground
    cuts the cells out of the background, and translucent colours stay inside the barcode.
    BarcodeRunsTest holds the row merge to covering every dark cell exactly once. The
    canonical features test now checks the QR code through its drawn rectangles rather than
    through the presence of an image. PptxVectorFragmentsTest renders the slide and scans
    the QR code off it, with an opaque and with a transparent foreground, checks a translucent
    foreground composites with the slide, and checks the background shape lands on the
    fragment box.

Build

  • A CI artifact expires on a schedule that matches what it is for. No
    actions/upload-artifact step declared retention-days, so all seven inherited the
    repository default of 90 days — the ceiling. Artifact storage is billed and capped per
    account rather than per repository, and this repository had grown to 10.7 GB across some
    2968 live artifacts, which exhausted the shared quota; the failure surfaced where it could
    not be diagnosed from, in another repository whose unrelated uploads began failing with
    Artifact storage quota has been hit. Two families were nearly the whole bill —
    examples-pdfs at 7.57 GB over 1009 artifacts and coverage-core-aggregate at 2.66 GB
    over 837 — and nothing downstream reads any of them: there is no actions/download-artifact
    anywhere in .github/workflows/, so each exists for a person to open, and the right window
    is however long a person plausibly wants it. Those two now keep 7 days, the span of a
    review. japicmp-report keeps 30, because it answers "when did this signature move, and
    against which baseline" during release prep rather than during the pull request.
    benchmark-smoke and benchmark-gate-reports keep 14 days together, deliberately the same
    number, since the gate verdict in one explains the numbers in the other and a shorter window
    on either would leave an investigation holding half a pair. The two weekly trend series,
    benchmark-full and jmh-results, keep the full 90: at one run a week a short window holds
    a point or two and shows no trend at all, and they cost tens of KB each. What proves a render
    weeks later is the committed layout-snapshot and visual baselines, not a retained artifact.

  • Build and test dependencies move with the maven-minor-patch group. exec-maven-plugin
    3.6.3 → 3.6.4 in benchmarks/ and examples/, maven-install-plugin and
    maven-deploy-plugin 3.1.4 → 3.2.0, and the test-scope byte-buddy pin 1.18.13 → 1.18.14
    in core/. Every one is build- or test-scope, so the dependency set a consumer inherits
    from a published artifact is what it was in v2.4.0.

Documentation

  • The showcase site's menu and section links reach the gallery, and its pages link
    only to files the site publishes.
    A category section is rendered only while its
    filter is shown, so after picking Features the Templates menu link changed the
    address and moved nothing. A gallery anchor now selects its filter, whether it is
    followed from the menu, reached with Back or Forward, or opened directly, and the
    filter pills keep the address in step. The no-JavaScript index linked to three PDFs
    the site does not publish, and two featured ids named no card, so the featured strip
    showed six of its eight tiles without a sign. ShowcaseSiteGuardTest fails the build
    on a featured id that is not a card, a card file missing from showcase/, a page link
    to a site file that does not exist, and an anchor or filter pill that names nothing
    the page shows. The structured data said JVM 21+ where every module targets Java 17,
    the page counted 16 CV presets where 26 ship, and the template-authoring links
    pointed at develop instead of the released docs on main.

  • The gallery shows each document's whole first page. A card cropped its preview to a
    248-pixel band, so a page was judged by its header and a wide slide lost its sides.
    The preview now shrinks into a fixed-height box at its own aspect ratio, featured
    tiles use the same fit instead of an A4-shaped frame, and the image tags no longer
    declare an A4 size that was wrong for 26 of the 117 previews.

  • The gallery opens a viewer that pages through one family at a time. A card, a
    featured tile or a family tile opens its family (CV, cover letters, invoices and so on)
    in a viewer that shows the whole first page, moves with Previous, Next and the arrow
    keys, shows where it is in the family, and links the PDF and source of the document
    shown. A switch moves to the other families of the category, and each reopens on the
    document it was left on. The address #/<category>/<family>/<id> reopens the same
    document on a reload or from a shared link and follows Back and Forward. The viewer
    replaces the zoom lightbox. A drag across the page moves between documents on a touch
    screen, a strip under the page holds every document of the family and marks the one on
    screen, and the pages either side are fetched before they are asked for. A reader who
    has asked to save data gets neither: no strip of page-sized previews, and nothing fetched
    ahead. ShowcaseSiteGuardTest now also requires unique, address-safe card and family ids,
    and holds any viewer address written into a page to a family and document that exist;
    scripts/site/gallery-viewer.test.mjs tests the addresses, the navigation and the dialog
    in CI.

  • The catalogue says what each document is, and the gallery stops jumping as it loads.
    web/examples.json carries a schemaVersion, and every card now carries the preset it
    renders and the model that preset composes, the artifacts a reader needs to run it, the
    path to its source, its page count, and the pixel size of its preview — so the image
    reserves its slot at the right shape instead of appearing out of nothing, and no single
    size stands in for previews that are not all A4. The viewer's strip and the family tiles
    read thumbnails generated at 320px rather than whole pages: opening the CV family fetches
    1.4 MiB of strip images where it fetched 5.2 MiB. ShowcasePresetRegistrationTest holds
    each card's preset and model to the example that builds them — it found two feature cards
    asking a reader for the engine alone while rendering a template preset — and
    ShowcaseSiteGuardTest fails the build on a manifest without a schemaVersion, a card
    whose measurements are not its preview's, a page count below one, a thumbnail that is not
    published, or a preset count in the page copy the catalogue does not hold.

  • The version the showcase shows is written down once, and a release moves every copy of
    it.
    The published site stated the release in five places that inherit from no pom, and
    the cut rewrote the first match of each — so a page carrying a second install snippet
    kept it a release behind while every check passed, and a spot that stopped matching was
    skipped in silence, leaving the cut to report success on a page still naming the previous
    release. An inline release-context block now holds stableVersion, releaseTag and
    javaMinimum. The JSON-LD, the Maven Central download link, the hero badge and the
    install snippets still repeat the version, because a crawler and a reader with no
    JavaScript both have to see the right release — but each is now a copy of that block, and
    VersionConsistencyGuardTest holds every occurrence of all seven spots equal to it,
    including the download link it never read before. A pattern that matches nothing stops the
    cut and names the spot, rather than leaving it behind.

  • Every document in the gallery shows what reproducing it takes — and 53 of them were
    asking readers for a dependency set that cannot render them.
    Under the document, the
    viewer now shows the Maven and Gradle coordinates at the release the page names, the
    preset class and the record it composes, its family's worked snippet, the command that
    runs the example, and the runnable source and family guide at the release tag. Building
    that panel exposed a defect in the catalogue it reads: a document drawn in a bundled face
    — PT Serif and the rest left the engine in v1.8.0 — cannot be reproduced from
    graph-compose + graph-compose-templates, which compiles and then throws
    Bundled font resource not found at the first glyph, and the artifact carrying those
    faces is versioned independently of the release, so naming it at the release version is a
    404 on Maven Central. Those cards now send a reader to graph-compose-bundle, the one
    published coordinate that carries the faces at the release's own version. Whether a
    document needs them is measured from the PDF rather than declared, because it differs card
    by card inside a single family: 25 of 27 CVs embed a face, 4 of 7 invoices do. The
    snippets are the blocks DocumentationSnippetCompileTest already compiles, copied into
    the manifest because the site is served from web/ alone and cannot reach a page under
    docs/.

    Fonts were not the only thing the catalogue left out. Ten cards reach a second backend and
    asked a reader for the engine alone, which compiles and then throws
    MissingBackendException at render. Two name the DOCX backend in an import. Nine need the
    PPTX one, which is discovered by format and so appears in no source at all: eight publish a
    deck beside their PDF — four of those rendered by a sibling class, so not even their own
    example mentions it — and one renders a deck it does not publish. The requirement now
    follows from what a card publishes as well as from what its example names, and where a
    document also needs the bundled faces the aggregate stands in for the engine and templates
    without swallowing the backend beside it. Two more cards (table-advanced, transforms) have no main of their own —
    GenerateAllExamples renders them — and were being offered an exec:java command that
    answers "doesn't contain a main method"; they now say what does render them.

    Each claim is checked against the example's own source or its rendered document:
    ShowcaseBundledFontClaimTest holds every font claim in both directions,
    ShowcaseCardInstructionsTest holds the backends and the run command the same way,
    ShowcaseSnippetScopeTest holds every published snippet inside the set of pages the
    compile gate actually scans, and ShowcaseSiteGuardTest fails on a snippet that is no
    longer the block it was compiled from, on a preset card missing anything the panel shows,
    and on a family guide the panel links that is no longer a page. A consumer project renders
    a CV from the published aggregate with nothing else installed, which is how the first of
    these defects surfaced.

  • The showcase site's pages are generated from its catalogue. web/index.html and
    web/sitemap.xml are rendered from web-src/ by scripts/site/build.mjs and committed;
    GitHub Pages still serves web/ exactly as committed and runs no build of its own. The index
    a visitor without JavaScript gets named 38 of the 117 documents the site publishes, and the
    preset counts and structured data were hand-copied beside a catalogue that already knew them.
    That index now names every document, under its category and group, and the counts are computed
    from the catalogue by the same rule the guard checks them against. The release the pages
    advertise is written down once, in web-src/data/release.json, and the build injects it into
    the seven spots that inherit from no pom; the release cut moves those two values and rebuilds
    the pages after the catalogue sync instead of editing them, so a later build cannot undo the
    release's own version. scripts/site/build.test.mjs fails when what is committed under web/
    is not what web-src/ builds, and the build refuses rather than publishes when a template
    token, a featured id or a card's title has gone.

  • The showcase home page leads with a result, and the catalogue uses words a newcomer knows.
    The menu is Templates, Examples, Documentation, Releases and GitHub, with a Get started button.
    The hero shows one whole document with an Invoice / CV / Proposal / Report switch — each a real
    catalogue document that opens as a PDF or as its own page — where it used to fan out
    three cropped previews; a phone now gets one compact document instead of none, and the heading
    stays within two lines at every width from 320 to 1440 pixels. The feature demonstrations are listed as Examples and the
    large complete documents as Showcase, instead of Features and Flagships. Category ids and
    every published URL are unchanged, so existing links and shared viewer addresses keep working.
    The template-authoring guide moved from the top of the page into a Documentation block.

  • The showcase says what to install for what you are building, and what each output format
    keeps.
    The install section offers four scenarios where it used to offer the engine alone: a PDF
    from your own layout (graph-compose), ready-made templates (graph-compose-bundle, which pins
    the independently versioned fonts and emoji for you), an editable PowerPoint deck
    (graph-compose-render-pptx, Beta) and a Word document (graph-compose-render-docx), each at the
    release the page names. A new block sets PDF, PowerPoint and Word side by side with their limits,
    taken from the backend capability matrix it links to. Muted text in the light theme sat at
    4.45:1, under the 4.5:1 minimum for body text, and now reads at 6:1; the featured tiles no longer
    skip a heading level; and a menu link to a section lands with its heading below the sticky header,
    which on a phone used to cover it.

  • Every document in the catalogue has a page of its own. Each of the 117 documents gets a
    generated page at <category>/<family>/<id>/ — the same three segments as its viewer address —
    that works without JavaScript and that a crawler can read: every page of the document, each
    linking into the PDF; the PDF, and the deck where one is published; what reproducing it takes;
    and the other documents of its family. Each page has a canonical address, a description, link
    preview tags and structured data, and the sitemap lists them all. The no-JavaScript index, the
    hero and a new Details link in the viewer lead there. What a page tells a reader to add, run and
    read is not written a second time: the viewer's panel became a pure model in gallery-viewer.js
    that the build loads to render the page, and scripts/site/build.test.mjs holds every page's
    section to that model and to nothing besides it. Building the pages exposed a label that was
    wrong in the viewer too: a family's worked snippet composes one preset — the CV block builds
    BoxedSections — yet it was captioned "Compose it" on every card of the family, promising Blue
    Banner's reader code that builds a different CV. Only the card of the preset the snippet composes
    says so now; the others say the snippet comes from the docs, and where it is published on a page
    other than the family guide — the CV block is on using-templates.md, the family starts at the
    quickstart — they link that page. The build owns only the pages it wrote where they sit: a page
    no card builds any more is deleted, --check fails on a page that is missing, stale or orphaned,
    and the release cut stages the pages a rebuild added or deleted along with the ones it rewrote.
    ShowcaseSiteGuardTest finds the generated pages and checks their links and anchors from each
    page's own directory. The viewer also stops captioning every document "First page shown", which
    was untrue of the 33 documents it pages through.

  • The showcase has a Documentation page, and the site reads its guides at the release it names.
    The menu's Documentation used to scroll to a block on the home page that linked three template
    guides. It now opens a page that gathers the guides already in the repository — a first document,
    templates, recipes, output formats, testing and production, versions and upgrades, and the
    internals for changing GraphCompose itself — each with a line on what it covers, checked against
    the guide itself, and each linked at the release tag. No guide is copied, so there is no second
    version of one to fall behind, and a guide path that is not a file in the repository, spelled as
    GitHub serves it, stops the site build. The rest of the site's repository links follow: the home
    page's authoring guides, the capability matrix, the Changelog, the migration guide and the examples
    tree used main, beside panels that already linked the release, and now name the release too; only
    the licence stays on main. Text set in the accent colour — section labels, the hero's links, tags,
    the footer's links, the ghost button — read at 3.7 to 4.2:1 in both themes, under the 4.5:1 minimum
    for text its size; it now reads at 5.3:1 at worst, taken over the page's background glow. And the
    gallery's Show all button, which turned a pale blue on hover and focus, no longer all but vanishes
    (1.05:1) when a keyboard focuses it in the light theme.