This repository has no description
bez tasks visual-proof.md
8.4 kB
Markdown
at main


enabled: true touches: [crates/layout/Cargo.toml, crates/layout/src/bin/paint.rs, crates/dom/src/computed.rs, roadmap.md] depends-on: [] #

Visual proof: rasterize the fragment tree to a PNG #

roadmap.md → "Visual proof": "nothing in the engine has ever drawn a pixel" — every measurement so far is a JSON diff of geometry (getBoundingClientRect) and computed style against the three-browser oracle, never against pixels. The goal it states: "take the fragment tree layout() already produces and turn it into a picture on screen (or, at minimum, in a saved image) — a blank page first, then a box with a background color and a border... This is demo-ware, explicitly not production: no text, no images, no interactivity, no compositing beyond flat rectangles, no accessibility tree, no input handling. One box rendered correctly is success." The same section notes "A software rasterizer writing a PNG (e.g. via the image crate, walking border-box rects to filled rectangles) is very likely simpler and faster to demo than wiring up wgpu/vello plus a window — but the exact mechanism is an implementation decision for the track that builds this, not fixed here." This spec makes that decision: a software rasterizer via the image crate, no window.

Scope. In: two deliverables, each a PNG file at a fixed path under target/visual-proof/ (target/ is already gitignored build output, not a committed asset). "Blank page" is an 800×600 canvas (fragment_check.rs's own VIEWPORT_WIDTH and the dump format's Viewport { width: 800, height: 600 }) filled with one background color and nothing else, produced by running the real pipeline (Document::parse → BoxTree::build → layout()) on an empty document and confirming there is nothing to paint, not by skipping the pipeline and writing a blank buffer directly. "A box" is a single <div> with an explicit background-color and a border, laid out and painted at its actual fragment rect: the properties painted are exactly background-color and the four border-*-color/ border-*-width values, nothing else. Out: this is not a generated seam. No model batch, no candidate-admission loop, no corpus of its own, no provenance record — hand-written engineering directly against the existing substrate, the same shape as tasks/device-pixel-rounding.md's disclaimer for fragment.rs; a future reader must not assume the batch-loop machinery this repo uses for queued CSS seams is in play here. Also out: an on-screen window — the roadmap's own phrasing ("on screen (or, at minimum, in a saved image)") makes a saved image sufficient, and a window pulls in an entire second category of complexity (winit, an event loop) the demo-ware framing does not require; a saved PNG is the actual deliverable this spec builds, and no future reader should treat a window as still owed. Also out: text metrics, images, border-radius, shadows, transforms, any corner rounding or overlap logic beyond two nested flat rectangles, and reusing crates/harness/src/cases.rs's recipe corpus for color — that corpus carries no color today (confirmed: no recipe sets background/border-color, and its own RESET rule zeroes border: 0), and coloring it is its own scope creep this track does not take on.

Build:

  • crates/layout/Cargo.toml — add image (latest 0.25-series) as a dependency, the workspace's first. No other crate references a raster or PNG-writing crate today.

  • crates/dom/src/computed.rs — four new accessors, background_color and border_{top,right,bottom,left}_color, mirroring border_top_width's existing pattern (style.clone_border_top_width() gated on style.clone_border_top_style() .none_or_hidden(), lines 381–401): read style.clone_background_color()/ style.clone_border_*_color(), resolve to an RGBA quad, and zero out (fully transparent) a border side whose style is none/hidden, matching the CSSOM rule comparable_style's existing border-width entries already apply. computed.rs's own doc comment says color accessors are net-new to this module (there are currently zero); this is the one place the exact Stylo 0.21.0 API name for the computed color type needs confirming against the crate directly, since no vendored source was available to check here — the shape (a clone_* accessor returning a Stylo computed-color type with an RGBA resolution path) is not in doubt, only the exact type/method name.

  • crates/layout/src/bin/paint.rs — new binary, mirroring fragment_check.rs's/ style_check.rs's shape (a main() with --case/--out args, no shared library module needed given the scope). Pipeline: Document::parse(html) → BoxTree::build(&document) → fragment::layout(&document, &tree, 800.0), exactly fragment_check.rs's engine_dump()'s first four lines, reused verbatim rather than inventing a second entry point. Two literal HTML documents, hand-built in this file (not cases.rs):

    • blank: <!doctype html><html><body></body></html>.
    • box: a single reset rule (* { margin:0; padding:0; box-sizing:border-box; }) plus one <div style="width:200px;height:100px;background-color:red;border:10px solid blue;"></div>. box-sizing:border-box makes the fragment's border-box exactly 200×100, so the expected paint rect is the literal declared size, not something that needs computing from content-box + border independently.

    Rasterization: an image::RgbImage (or RgbaImage) 800×600, filled with a fixed background color (white), then Fragment::walk_absolute(0.0, 0.0, f) walks every fragment's absolute rect in the same order the tree was built (parent before children — correct paint order for non-overlapping boxes, sufficient given "no compositing beyond flat rectangles"). For each (node, x, y, w, h): look up background_color/ border_*_color/border_*_width via the new computed.rs accessors on that node; if any border side has nonzero width and non-transparent color, fill the full border-box rect (x, y, w, h) with that side's color first (uniform-border case: one rect for all four sides, since the milestone's test document sets one shorthand and all four sides match); then fill the inner rect — (x + border_left, y + border_top, w - border_left - border_right, h - border_top - border_bottom) — with background-color, on top. A fully transparent background-color (CSS initial value) leaves the canvas's existing pixels untouched rather than painting white over what a parent already painted. Write the canvas to target/visual-proof/{blank,box}.png via image's PNG encoder.

Gates:

  • cargo test --workspace passes.
  • cargo run --release -p layout --bin paint -- --case blank exits 0 and produces target/visual-proof/blank.png; a test reads it back with image::open, asserting 800×600 dimensions and that every pixel equals the fixed background color — this is what makes "produces a valid image" checkable rather than eyeballed.
  • cargo run --release -p layout --bin paint -- --case box exits 0 and produces target/visual-proof/box.png; a test reads it back and asserts, at pixel coordinates derived from the same fragment rect the binary painted from (not a hand-computed or independently-guessed coordinate): the interior point (rect center) equals red (255, 0, 0), and a point inside the border band (e.g. rect origin + 2px in both axes, inside the 10px border) equals blue (0, 0, 255).
  • No requirement to move or re-check fragment_check --random's 216/227 floor: this track reads Fragment's existing node/offset_x/offset_y/width/height fields through walk_absolute, which already exists, and adds no field and no changed computation to fragment.rs itself — paint properties are read independently through new computed.rs accessors, not through the fragment tree. The floor is unaffected and this spec does not re-run fragment_check as a gate.

Afterwards, update roadmap.md → "Visual proof" with what landed: a software rasterizer via the image crate (crates/layout/src/bin/paint.rs), producing saved PNGs rather than an on-screen window, and the blank-page and one-box results, the way tasks/floats-bfc.md's own closing paragraph updates its roadmap section.