Expand description
Image HTML synthesizer — the single entry point for emitting <img> /
<picture> markup in moss output.
See docs/reference/structural-html-emission.md
for the architectural principle: structural HTML decisions are made at the
typed-data layer (pulldown-cmark events, shortcode AST, typed component
props), with all three call sites converging on the function in this
module. Regex post-passes are reserved for non-markdown-origin attribute
injection only.
§Migration state (post-Step-7, 2026-05-16)
Steps 1-7 of the structural-html-emission migration are complete:
- Step 1: extracted
synthesize_image_html - Step 2: routed markdown
Tag::Imageevents - Step 3: routed
:::heroshortcode image - Step 4: routed link-preview favicon
- Step 6: routed every cover image path (folder cards, child summary
cards, folder index hero, photo/video gallery thumbnails) through
render_cover_html - Step 7: retired
wrap_img_in_picture(the structural part of the legacy regex post-pass). The synthesizer now owns every<picture>wrap in moss output.add_image_placeholder_attributessurvives as the attribute-injection seam for the documented carve-outs (see below).
The byte-shape contract is captured by snapshot tests at the bottom of this file. They are the line of defense against accidental output drift; any future change to attribute order, quoting, or whitespace must update them deliberately.
Later steps will:
- Step 8: switch
MarkdownStandaloneto a<figure class="moss-image">wrapper (breaking change for user themes — staged separately) - Add AVIF
<source>lines once the image pipeline produces AVIF - Drop the inline LQIP
style=in favor of a wrapper CSS custom prop
§Step-8 contract: synthesizer owns the outer <figure> (planned)
transform_events currently wraps the synthesizer’s MarkdownStandalone
output in its own <figure> for the three caption-pattern branches
(image+emphasis, separate-emphasis, implicit-figure). After Step 8 the
synthesizer emits <figure class="moss-image"> itself; if transform_events
still wraps, the output will be `
The Step-8 contract: caption flows into the synthesizer via
MarkdownStandalone { caption: Option<&str> } (or a richer
CaptionMarkdown for emphasis-in-caption support), and the three
caption-pattern branches collapse into a single Event::Html( synthesize_image_html(..., MarkdownStandalone { caption })) emission.
The <figcaption> becomes the synthesizer’s responsibility, NOT
transform_events. Captures the spec at
docs/reference/structural-html-emission.md#output-shape.
§Carve-outs: bare <img> emitters not routed through the synthesizer
Four emission paths land bare <img> HTML in the output stream that
does NOT flow through synthesize_image_html. They rely on the regex
post-pass (build/media/placeholder.rs::add_image_placeholder_attributes)
for attribute injection (dims/loading/decoding/LQIP). None of them
are bugs — each has a documented architectural reason to stay outside
the synthesizer:
- Site logo (
build/components/nav.rs::render_logo) — themed UI affordance, not content. The logo has its own CSS sizing (.site-logo { height: 1.8em }) and does not participate in LQIP/dims/WebP-variant rendering. - RSS read-tracking pixel (
build/feeds/rss.rs) — 1×1<img>not rendered visibly; the synthesizer’s dims fallback (800×600) and LQIP would be wrong for this case. - Email body images (
infra/newsletter.rs) — email clients (Gmail, Outlook, Apple Mail) do not consistently support<picture>ordata-placeholder-src-driven hydration. Keep flat for cross-client degradation. - Raw HTML
<img>in markdown source — author-written<img src="...">literally embedded in.mdfiles. pulldown-cmark emits these asEvent::Htmlopaque pass-through, so they never reachTag::Imageand are not a moss-controlled emitter. Treated as user input, the markdown HTML is opaque to the synthesizer and gets only the additive attribute injection pass.
Photography/video gallery thumbnails — previously a carve-out — were
folded into the synthesizer in Step 7’s commit. The review colophon
cover (build/features/review.rs::render_colophon) — also previously
a carve-out — was folded in 2026-05-16. Both use
ImageContext::FolderCardCover (container-bounded thumbnail semantics).
These four remaining carve-outs are flagged here so future maintenance
does not drop their attribute injection. Step 7 retired the structural
part of the regex (wrap_img_in_picture); the surviving
add_image_placeholder_attributes provides additive attrs only for
these bare-img paths.
Structs§
- Image
Render Options - Optional rendering attributes a caller may pass.
Enums§
- Image
Context - Where the image lives in the document, which determines the wrapper element and attribute set.
Functions§
- synthesize_
image_ html - Synthesize the HTML for an image reference.
- wrap_
in_ figure - Wrap an already-shaped image fragment in the standalone-image figure container.