Skip to content

feat(examples): render the repository social preview card with the engine - #462

Merged
DemchaAV merged 7 commits into
developfrom
feat/social-preview-card
Jul 26, 2026
Merged

feat(examples): render the repository social preview card with the engine#462
DemchaAV merged 7 commits into
developfrom
feat/social-preview-card

Conversation

@DemchaAV

Copy link
Copy Markdown
Owner

Why

The image GitHub shows when the repo is linked — on Threads, X, Slack, LinkedIn — reads "Declarative document layout engine for Java". True, and silent about the thing the 2.1 line is built on. It is also hand-made, which means it has no way to stay current: it drifts the moment the project does, exactly like the hero banner did until #458 and the deck until #460.

SocialCardExample composes the card through the canonical DSL at 1280×640, from the same palette constants and the same wordmark SVG the rest of the brand uses.

The picture argues its own point

A single sheet on the left resolves into two shapes on the right — a portrait page and a 16:9 slide. The bars inside both come from one table:

privatestaticfinaldouble[][] CONTENT_BARS = {
{0.07, 0.09, 0.62, 0.085},
{0.07, 0.30, 0.72, 0.048},
{0.07, 0.40, 0.55, 0.048},
};

outputCard scales those fractions to whichever box it is drawing. So the claim the card makes — one layout arriving in two shapes — is enforced by the code, not by an illustrator lining rectangles up by eye. If someone later edits one output's bars, the other moves with it or the table has to be split deliberately.

Two things learned the hard way while building it

PathBuilder coordinates are fractions of the declared box, and y runs bottom-up. My first pass passed absolute points; every path silently landed outside its box and vanished, while the two beams — which happened to have larger numbers — rendered as stray wedges across the canvas. BookTemplateExample is the working idiom and settled it. Noted in the code so the next reader does not repeat it.

Bold comes from the decoration, not from a *_BOLD font name. The card uses .fontName(HELVETICA).decoration(BOLD); naming HELVETICA_BOLD selects the family and leaves the face regular — #451.

Verification

Examples module: 41 tests, 0 failures. Rendered at 72 DPI the page measures exactly 1280 × 640 pixels — the page is declared in points, so 96 DPI would have produced 1706 × 853 and been letterboxed by GitHub.

Wired into GenerateAllExamples, so a change that breaks the card turns the Examples Generation Smoke Test red rather than being noticed on a social feed.

assets/readme/social-card.png is committed as the file to upload under Settings → Social preview; GitHub has no API for that, so the upload stays manual.

…gine
The card GitHub shows when the repo is linked was a hand-made image
saying "Declarative document layout engine for Java" — true, and silent
about the thing the 2.1 line is built on. A hand-made card also has no
way to stay current: it drifts the moment the project does.
SocialCardExample composes it through the canonical DSL at 1280x640, so
it reads the same palette and wordmark as the rest of the brand and can
be regenerated like the README hero.
What it draws is the claim itself. One sheet on the left resolves into a
portrait page and a 16:9 slide, and the content bars inside both are
placed from one table of relative fractions — the picture asserts that a
layout arrives in two shapes, and the code that draws it enforces that.
Wired into GenerateAllExamples so a change that breaks it turns the
examples smoke test red.
The two output shapes held grey placeholder bars, which made the picture
an illustration of the claim rather than the claim. They now hold an
actual document the engine composed: a title, a line of body text and a
bar chart with Q1-Q4 category labels, built once by documentBody and
handed to both shapes.
Only the chart's height follows the box it lands in. With a single
aspect ratio the slide's chart overran its card while the page's left a
third of the box empty — which is the reason a layout engine exists, so
the card now demonstrates that instead of hiding it.
The source sheet was a five-point outline whose folded corner read as a
dent; it is a plain quadrilateral in gentle perspective now.
…tputs
The card jumped straight from a sheet to two shapes, which left out the
part the name is about. It now reads in three stages: authored documents
on the left, the resolved layout graph in the middle, and the two
backends that consume it on the right.
The middle panel carries a node tree — a document root, its sections and
the leaf fragments each resolves to — over the flat ordered fragment
stream the compiler emits. That stream is what both backends read, and
drawing it is what makes "one layout pass" a statement about the
architecture rather than about the picture.
Two engine contracts learned in passing and noted in the code: a
ShapeContainerNode must carry at least one layer, so an empty leaf box is
a ShapeBuilder; and a paragraph helper that sizes its box against the
page width collapses to nothing inside a narrower parent, which had the
panel's footer rendering at zero width.
… rails
The chart ran to the card's border. A node positioned into a card is
measured against the card, not against the card minus its padding, so
the width passed to the body was only ever used by the prose block while
the chart laid out against the full container. An invisible content box
of the padded size gives it something to lay out against.
The graph rails were sized from literals that did not match the drops
they joined: the tier rail overshot its last drop by two points, which
at this size reads as a whisker past the junction. Both rails are now
derived from the drop positions, and the leaf gap is a named constant
rather than a repeated 29.
Output labels moved off the cards' rounded corners.
The root sat at w/2 while the section drops sit at w*t + 31, so their
span is centred 2.3pt to the left. Centring the root on the panel put it
visibly off the child it points at — the drop entered the middle section
node off-axis.
Both the root and its drop now derive from the tier row's centre, so the
column stays plumb whatever the tier fractions become. Measured on the
render: root and middle section centres agree to 0.0px, where they were
2.3pt apart.
…wordmark
Each wedge ran to the full height of the card it points at, so its point
landed exactly on the rounded corner and stuck out past the curve as a
sharp spike. Both now stop short of the corner radius and meet the
straight part of the border.
The tagline sat directly against the wordmark — measured on the render
the two had no gap at all between them. It drops far enough to read as a
second line rather than a descender.
A social card is read at roughly 40% of its rendered width, so the type
was chosen for 1:1 and vanished at the size anyone actually sees it. The
labels, the document heading and the chart axis all move up, and the two
faintest lines gain contrast instead of points — at that reduction
brightness buys more legibility than size does.
The card now also writes itself as a PowerPoint deck. It asserts that one
composition reaches a PDF and an editable deck; emitting both from one
session is the cheapest proof, and it keeps the assertion honest whenever
this file changes.
Making that deck editable meant dropping a clip: the content box carried
CLIP_BOUNDS, and a clip that could cut ink sends its whole region through
the PPTX raster fallback. The deck arrived with 6 text runs and three
pictures. Without it: 29 runs, 163 native shapes, and the one remaining
picture is the SVG wordmark, which the capability matrix already records
as a raster fallback. The content is sized to that box anyway, so nothing
needed cutting.
Both artifacts are committed beside the other example output.
@DemchaAV
DemchaAV merged commit 7f4ec8d into developJul 26, 2026
10 checks passed
@DemchaAV
DemchaAV deleted the feat/social-preview-card branch July 26, 2026 18:54
Sign up for freeto 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

@DemchaAV