Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
4b54ac3
docs(research): add Blink SVG research docs and tighten research skill
softmarshmallow Apr 28, 2026
4cbc05c
fix(docs): replace autolinks with markdown links for MDX compat
softmarshmallow Apr 28, 2026
a1eafdb
refactor(htmlcss::svg): Blink-aligned module organization
softmarshmallow Apr 28, 2026
de1265b
refactor(htmlcss::svg): split dom/element.rs, extract text shaping, d…
softmarshmallow Apr 28, 2026
b8845db
fix(htmlcss::svg): address AI review findings (PR #698)
softmarshmallow Apr 28, 2026
de59d7c
chore(grida_dev): silence dead_code on ComparisonResult.diff_percentage
softmarshmallow Apr 29, 2026
e7a8ee5
fix(htmlcss::svg): drop spec-invalid clipPath `<use>` targets instead…
softmarshmallow Apr 29, 2026
161feea
fix(htmlcss::svg): treat broken filter primitive references as transp…
softmarshmallow Apr 29, 2026
f9618f4
fix(htmlcss::svg): apply requiredExtensions / systemLanguage to all r…
softmarshmallow Apr 29, 2026
808ffa0
fix(htmlcss::svg): keep first-in-document-order entry on duplicated ids
softmarshmallow Apr 29, 2026
950fa90
fix(htmlcss::svg): bound the fill/stroke ancestor walk at the <use> t…
softmarshmallow Apr 29, 2026
d11371e
fix(htmlcss::svg): resolve stroke-width percentages against the viewp…
softmarshmallow Apr 29, 2026
0b1bcf0
fix(htmlcss::svg): honor transform-origin on <clipPath> and <pattern>
softmarshmallow Apr 29, 2026
e2bd180
fix(htmlcss::svg): chain clip-path=url(#empty) clips everything
softmarshmallow Apr 29, 2026
12e3e7e
fix(htmlcss::svg): resolve nested-<svg> percentage x/y/width/height
softmarshmallow Apr 29, 2026
be327fb
fix(htmlcss::svg): explicit feConvolveMatrix divisor=0 emits transparent
softmarshmallow Apr 29, 2026
a9cfb63
fix(htmlcss::svg): resolve userSpaceOnUse pattern percentages against…
softmarshmallow Apr 29, 2026
eb3d730
fix(htmlcss::svg): <use> of <svg> behaves like the spec'd shadow root
softmarshmallow Apr 29, 2026
0c9f422
fix(htmlcss::svg): resolve stroke-dashoffset percentages against the …
softmarshmallow Apr 29, 2026
1f7178a
fix(htmlcss::svg): detect <mask> cycles via an active-mask chain
softmarshmallow Apr 29, 2026
301e86f
fix(htmlcss::svg): collapse non-overlapping primitive subregions to e…
softmarshmallow Apr 29, 2026
2b7b165
fix(htmlcss::svg): reject chained <mask> targets that close a transit…
softmarshmallow Apr 29, 2026
2611959
fix(htmlcss::svg): reject negative arguments to CSS filter shorthands
softmarshmallow Apr 29, 2026
02cc61a
fix(htmlcss::svg): resolve stroke-dasharray percentage entries agains…
softmarshmallow Apr 29, 2026
de4b146
fix(htmlcss::svg): tspan/textPath honour conditional processing and <…
softmarshmallow Apr 29, 2026
56853da
fix(htmlcss::svg): treat feTurbulence numOctaves<=0 as transparent flood
softmarshmallow Apr 29, 2026
f17ec41
fix(htmlcss::svg): compose chained clip-path on clipPath/child with c…
softmarshmallow Apr 29, 2026
a40d693
fix(htmlcss::svg): resolve transform-origin against the right referen…
softmarshmallow Apr 29, 2026
594f70e
fix(htmlcss::svg): text baseline / decoration / anchor / kerning / xm…
softmarshmallow Apr 29, 2026
0491490
fix(htmlcss::svg): per-tspan group opacity via save_layer
softmarshmallow Apr 29, 2026
1a82a2d
fix(htmlcss::svg): marker shorthand cascade and end-of-path angle bac…
softmarshmallow Apr 29, 2026
e438337
fix(htmlcss::svg): honor paint-order on shapes
softmarshmallow Apr 29, 2026
ab77187
fix(htmlcss::svg): resolve percent values on markerWidth/Height/refX/…
softmarshmallow Apr 29, 2026
904c5f9
fix(htmlcss::svg): reject paint-order values with any invalid token
softmarshmallow Apr 29, 2026
8af3873
fix(htmlcss::svg): clip-path stroke-box reference expands by half str…
softmarshmallow Apr 29, 2026
4795ac1
fix(htmlcss::svg): visibility hidden/collapse on tspan suppresses paint
softmarshmallow Apr 29, 2026
4e51f8d
fix(htmlcss::svg): measure '0' glyph for ch unit on shape attributes
softmarshmallow Apr 29, 2026
de4a6e2
feat(htmlcss::svg): mix-blend-mode and isolation:isolate as CSS-only …
softmarshmallow Apr 29, 2026
07584d7
fix(htmlcss::svg): use bilinear sampling for feImage
softmarshmallow Apr 29, 2026
7f4b858
fix(htmlcss::svg): resolve overflow="inherit" on <marker>
softmarshmallow Apr 29, 2026
4652207
fix(htmlcss::svg): exclude display:none children from group bbox
softmarshmallow Apr 29, 2026
97f5f64
fix(htmlcss::svg): use tight bbox (not control-point bbox) for path o…
softmarshmallow Apr 29, 2026
08d2b3f
fix(htmlcss::svg): honor paint-order on text/tspan (fill/stroke order…
softmarshmallow Apr 29, 2026
de1de32
feat(htmlcss::svg): tspan paint pipeline (url() paint, mask, filter, …
softmarshmallow Apr 29, 2026
fed7331
feat(htmlcss::svg): <text> inside <clipPath> as glyph-clip with per-t…
softmarshmallow Apr 29, 2026
d920730
fix(htmlcss::svg): bound text font cascade at <use> target
softmarshmallow Apr 29, 2026
b4ec620
feat(htmlcss::svg): font shorthand resets longhands per CSS Fonts 4 §…
softmarshmallow Apr 29, 2026
2085610
fix(htmlcss::svg): font-relative units in stroke-dasharray
softmarshmallow Apr 29, 2026
c7758f9
fix(htmlcss::svg): mask region % resolves against SVG viewport in use…
softmarshmallow Apr 29, 2026
132fec6
fix(htmlcss::svg): clip <image> overflow when preserveAspectRatio=slice
softmarshmallow Apr 29, 2026
da9dc63
feat(htmlcss::svg): transform-origin on textPath's referenced <path>
softmarshmallow Apr 29, 2026
2cdd8b5
feat(htmlcss::svg): rasterize inline SVG to host <image> pixel size w…
softmarshmallow Apr 29, 2026
bbc25a4
fix(htmlcss::svg): default nested <svg> w/h to 100% when viewBox is set
softmarshmallow Apr 29, 2026
c6157f2
feat(htmlcss::svg): textLength with lengthAdjust spacing and spacingA…
softmarshmallow Apr 29, 2026
fbb4a48
fix(htmlcss::svg): feImage upscale uses Mitchell bicubic, not bilinear
softmarshmallow Apr 29, 2026
28fffcd
feat(htmlcss::svg): cursive run shaping for Arabic / Hebrew / Indic
softmarshmallow Apr 29, 2026
b600c2c
refactor(htmlcss::svg): consolidate transform-origin wrap, paint func…
softmarshmallow Apr 29, 2026
dc6cd11
chore(grida_dev): expand reftest tooling — bake, inspect, oracles, su…
softmarshmallow Apr 29, 2026
2b5f8ce
chore(grida_dev): port reftest bake to playwright + tsx (drop standal…
softmarshmallow Apr 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .agents/skills/dev-render-htmlcss-feature/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ load only when the user explicitly runs it. The loop is a conductor
over `/research`, `/fixtures`, and `/render-reftest` — those auto-trigger
on their own for narrower work.

**Sibling skill.** For features in the SVG path
(`crates/grida/src/htmlcss/svg/`, `resvg-test-suite` corpus,
multi-oracle scoring against expected.png + baked Chrome PNG), use
[`dev-render-htmlcss-svg-feature`](../dev-render-htmlcss-svg-feature/SKILL.md)
instead. Same five-phase shape, different tooling.

**Lifecycle.** Expect this skill to grow as new divergence patterns
surface. It will likely go stale in parts once htmlcss hits
Chromium-parity on L0/L1; treat the _phase structure_ as durable and
Expand Down
504 changes: 504 additions & 0 deletions .agents/skills/dev-render-htmlcss-svg-feature/SKILL.md

Large diffs are not rendered by default.

34 changes: 29 additions & 5 deletions .agents/skills/render-reftest/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,20 +130,44 @@ strategy before authoring a new fixture.

### SVG — pre-baked reference vs. resvg-generated reference

Two strategies, depending on whether a reference image already exists:
Three strategies, depending on what oracle data is available:

**Strategy A — pre-baked reference (preferred when available)**
**Strategy A — pre-baked reference (single oracle)**

Use if the SVG comes from a test suite that ships reference PNGs alongside it
(W3C SVG 1.1, resvg-test-suite, Oxygen Icons). The oracle is the co-located
PNG; `grida_dev reftest` picks it up automatically via `reftest.toml`.
(W3C SVG 1.1, Oxygen Icons). The oracle is the co-located PNG;
`grida_dev reftest run` picks it up automatically via `reftest.toml`.

```sh
# W3C suite — reference PNGs are in png/ next to svg/
cargo run -p grida_dev --release -- reftest \
cargo run -p grida_dev --release -- reftest run \
--suite-dir fixtures/local/W3C_SVG_11_TestSuite --bg white
```

**Strategy A+ — pre-baked reference + Chrome bake (multi-oracle)**

Use for `resvg-test-suite`. The vendored `expected.png` is the suite
author's read of the spec, but for ~12% of fixtures Chrome diverges
from it. The harness ingests the suite's `results.csv` (a 9-renderer
status matrix) and a baked Chrome PNG to classify each fixture into
**consensus** / **disputed** / **UB** buckets. Effective per-fixture
score is `max(vs_expected, vs_chrome)`; the headline parity number is
the consensus pass-rate (excludes disputed and UB).

```sh
# One-time: bake Chrome PNGs (puppeteer; deterministic per Chrome version)
cargo run -p grida_dev --release -- reftest bake

# Run + summarize
cargo run -p grida_dev --release -- reftest run \
--suite-dir fixtures/local/resvg-test-suite --renderer htmlcss
cargo run -p grida_dev --release -- reftest summary
```

For the full driver loop (audit → ground → fixture → impl → verify
against the multi-oracle gate), see
[`dev-render-htmlcss-svg-feature`](../dev-render-htmlcss-svg-feature/SKILL.md).

**Strategy B — resvg as dynamic oracle (when no pre-baked image exists)**

For arbitrary SVG files with no reference PNG, resvg is an independent,
Expand Down
242 changes: 76 additions & 166 deletions .agents/skills/research/SKILL.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ node-compile-cache/
**/__tests__/artifacts/
**/__tests__/.tmp/

# Reftest harness — generated baseline PNGs and puppeteer install
crates/grida_dev/scripts/package.json
crates/grida_dev/scripts/package-lock.json

### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down
2 changes: 0 additions & 2 deletions crates/grida/examples/tool_gen_bench_fixture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ use fixture_helpers::*;
use grida::cg::color::CGColor;
use grida::cg::fe::*;
use grida::cg::stroke_width::StrokeWidth;
use grida::cg::types::*;
use grida::node::schema::*;
use std::collections::HashMap;

/// 100×100 grid of rectangles (10 000 nodes).
Expand Down
84 changes: 0 additions & 84 deletions crates/grida/examples/tool_sk_svgdom.rs

This file was deleted.

10 changes: 5 additions & 5 deletions crates/grida/src/htmlcss/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -518,8 +518,8 @@ fn detect_img_element(node: &DemoNode) -> ReplacedContent {
/// (components/script/dom/svg/svgsvgelement.rs): the `<svg>` subtree is
/// flattened to a standalone XML string so it can be parsed by an
/// out-of-band SVG renderer. Unlike Servo, Grida hands the string to
/// Skia's built-in `svg::Dom` (GPU-capable) rather than resvg's
/// tiny-skia rasterizer.
/// the in-tree `htmlcss::svg` renderer (Skia-backed, GPU-capable)
/// rather than resvg's tiny-skia rasterizer or Skia's built-in svg::Dom.
fn serialize_svg_subtree(dom: &DemoDom, svg_node: &DemoNode) -> String {
let mut out = String::new();
write_svg_element(dom, svg_node, &mut out, /*inject_xmlns=*/ true);
Expand Down Expand Up @@ -618,7 +618,7 @@ fn parse_view_box(s: &str) -> Option<(f32, f32, f32, f32)> {
/// Walks the subtree to produce a standalone XML document, reads
/// `width` / `height` / `viewBox` attributes for intrinsic sizing, and
/// stashes the serialized source on `ReplacedContent` for paint-time
/// delegation to `skia_safe::svg::Dom`.
/// routing through `htmlcss::svg::render_into`.
fn detect_svg_element(dom: &DemoDom, node: &DemoNode) -> ReplacedContent {
let xml = serialize_svg_subtree(dom, node);

Expand Down Expand Up @@ -692,8 +692,8 @@ fn detect_widget(tag: &str, node_data: &DemoNode, dom: &DemoDom, el: &mut Styled
}
"svg" => {
// Treat inline <svg> as a replaced element whose content is
// an XML-serialized subtree. Paint time delegates to
// skia_safe::svg::Dom (see htmlcss/paint.rs). Children are
// an XML-serialized subtree. Paint time routes through
// htmlcss::svg::render_into (see htmlcss/paint.rs). Children are
// captured in the serialized XML — the normal DOM walker
// must not descend into them (they'd be painted twice and
// misinterpreted as HTML text nodes).
Expand Down
50 changes: 22 additions & 28 deletions crates/grida/src/htmlcss/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod github_markdown;
mod layout;
mod paint;
pub mod style;
pub mod svg;
pub mod types;

use crate::runtime::font_repository::FontRepository;
Expand Down Expand Up @@ -204,33 +205,25 @@ pub fn render(

/// Render a standalone SVG document to a Skia Picture.
///
/// Delegates to Skia's built-in `svg::Dom` (enabled via the `svg` feature
/// on `skia-safe`). The Picture is recorded at `(width, height)` in CSS
/// pixels; `viewBox` + `preserveAspectRatio` inside the SVG map user
/// units to that box, interpreted internally by Skia.
/// Routes through the in-tree `htmlcss::svg` pipeline (DemoDom + Stylo +
/// Blink-shaped layout/paint). The pipeline does its own work — there is
/// no fallback to Skia's built-in `svg::Dom`. Features still under
/// construction (e.g. text, filters) render as best-effort: unsupported
/// elements are skipped rather than passed to a different renderer, so
/// missing pixels surface as obvious gaps in the output and we feel the
/// motivation to implement them.
///
/// Accepts `.svg` bytes directly alongside the HTML `render()` entry
/// point. Servo treats inline `<svg>` this way (subtree serialization +
/// out-of-band SVG renderer); this function is the standalone-document
/// equivalent — skip the HTML parser entirely and hand raw SVG bytes to
/// Skia.
/// `width` / `height` are CSS pixels; `viewBox` + `preserveAspectRatio`
/// inside the SVG map user units to that box.
///
/// Returns `Err` on parse failure (malformed XML, missing root `<svg>`).
/// Returns `Err` only when SVG structure is unrecoverable (e.g. no
/// `<svg>` element, picture-recording failure). The XML parser is
/// permissive — best-effort recoverable input may render as `Ok`
/// even if it isn't strictly well-formed. Use this as a render
/// entry point, not as an XML validator.
Comment on lines +219 to +223
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Docstring narrows Err conditions more than the implementation.

Line 219 says Err is only for unrecoverable structure/picture-recording cases, but this path can also surface non-structural parse/unsupported failures from the SVG pipeline. Please broaden the wording to avoid misleading callers.

✏️ Suggested doc fix
-/// Returns `Err` only when SVG structure is unrecoverable (e.g. no
-/// `<svg>` element, picture-recording failure). The XML parser is
-/// permissive — best-effort recoverable input may render as `Ok`
-/// even if it isn't strictly well-formed. Use this as a render
-/// entry point, not as an XML validator.
+/// Returns `Err` for unrecoverable SVG parse/structure/recording failures.
+/// The parser is permissive for some malformed inputs, so best-effort
+/// recovery may still render as `Ok`. Use this as a render entry point,
+/// not as an XML validator.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Returns `Err` only when SVG structure is unrecoverable (e.g. no
/// `<svg>` element, picture-recording failure). The XML parser is
/// permissive — best-effort recoverable input may render as `Ok`
/// even if it isn't strictly well-formed. Use this as a render
/// entry point, not as an XML validator.
/// Returns `Err` for unrecoverable SVG parse/structure/recording failures.
/// The parser is permissive for some malformed inputs, so best-effort
/// recovery may still render as `Ok`. Use this as a render entry point,
/// not as an XML validator.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/grida/src/htmlcss/mod.rs` around lines 219 - 223, The doc comment
above the SVG render entry point in crates::grida::htmlcss::mod.rs currently
states "Returns `Err` only when SVG structure is unrecoverable...", which is too
narrow; update that docstring (the comment for the SVG render entry-point
function in this module) to say that Err can also be returned for other failures
from the SVG pipeline such as non-structural parse errors or unsupported
features (e.g., parser or pipeline failures), so callers are not misled into
assuming only unrecoverable-structure/picture-recording cases produce Err.

pub fn render_svg(svg: &str, width: f32, height: f32) -> Result<skia_safe::Picture, String> {
use skia_safe::{svg, FontMgr, PictureRecorder, Rect, Size};

let data = skia_safe::Data::new_copy(svg.as_bytes());
let mut dom = svg::Dom::from_bytes(&data, FontMgr::default())
.map_err(|e| format!("SVG parse error: {e}"))?;
dom.set_container_size(Size::new(width, height));

let mut recorder = PictureRecorder::new();
let bounds = Rect::from_xywh(0.0, 0.0, width.max(1.0), height.max(1.0));
let canvas = recorder.begin_recording(bounds, false);
dom.render(canvas);
recorder
.finish_recording_as_picture(Some(&bounds))
.ok_or_else(|| "failed to finish SVG picture recording".to_string())
crate::htmlcss::svg::render_to_picture(svg, width, height)
.map_err(|e| format!("htmlcss::svg::render_to_picture: {e}"))
}

/// Render either HTML or SVG to a Skia Picture, auto-detected from the
Expand Down Expand Up @@ -3290,7 +3283,7 @@ code block

/// `render_svg` must accept a raw SVG document and produce a
/// non-empty Picture. Servo-style standalone path — no HTML parser,
/// direct delegation to Skia's svg::Dom.
/// direct routing through `htmlcss::svg::render_to_picture`.
#[test]
fn test_render_svg_standalone_ok() {
let _guard = crate::stylo_test::lock();
Expand All @@ -3307,8 +3300,9 @@ code block
let _guard = crate::stylo_test::lock();
let bad = "<svg>unclosed";
let result = render_svg(bad, 100.0, 100.0);
// Skia's svg::Dom is lenient; either Ok or Err is acceptable —
// what matters is that we don't panic.
// The htmlcss::svg parser is permissive on minor malformations;
// either Ok or Err is acceptable — what matters is that we
// don't panic.
let _ = result;
}

Expand Down Expand Up @@ -3377,7 +3371,7 @@ code block
}

/// Verify inline <svg> renders end-to-end through the HtmlCss pipeline.
/// Skia's built-in svg::Dom must accept the serialized subtree.
/// The in-tree htmlcss::svg renderer must accept the serialized subtree.
#[test]
fn test_svg_inline_render() {
let _guard = crate::stylo_test::lock();
Expand Down
60 changes: 32 additions & 28 deletions crates/grida/src/htmlcss/paint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -789,28 +789,31 @@ fn paint_replaced(
canvas.clip_rect(dest_rect, ClipOp::Intersect, true);
}

// Inline <svg>: delegate to Skia's built-in SVG DOM. Mirrors
// Servo's "<svg> as replaced element with serialized subtree" pattern
// Inline <svg>: route through the in-tree htmlcss::svg pipeline
// (DemoDom + Stylo + Blink-shaped layout/paint). Mirrors Servo's
// "<svg> as replaced element with serialized subtree" pattern
// (components/script/dom/svg/svgsvgelement.rs +
// components/net/image_cache.rs) but swaps resvg + tiny-skia for
// skia_safe::svg::Dom, which paints straight onto the SkCanvas.
let svg_handled = if let Some(ref xml) = content.svg_xml {
paint_inline_svg(canvas, xml.as_bytes(), w, h)
} else {
false
};

if svg_handled {
// components/net/image_cache.rs) but uses our own renderer rather
// than resvg+tiny-skia or Skia's built-in svg::Dom. There is no
// fallback — features still under construction render as best-effort
// gaps so we feel the motivation to implement them.
// Inline SVG: routes through the in-tree htmlcss::svg renderer.
// Per the "no fallback" intent, an inline-SVG element terminates
// here whether the render succeeded or not — we do NOT fall back
// to the gray placeholder. The placeholder is for missing `<img>`
// resources only. (Reviewer note: see PR #698 / coderabbit
// finding "Inline `<svg>` failure still falls back to placeholder
// rendering"; the previous flow let `paint_inline_svg`-returned-
// false fall through to the placeholder draw at the bottom of
// this function.)
if let Some(ref xml) = content.svg_xml {
let _ = paint_inline_svg(canvas, xml.as_bytes(), w, h, images);
canvas.restore();
return;
}

// Image path: only applies to <img>-style replaced elements (not SVG).
let image_opt = if content.svg_xml.is_none() {
images.get(&content.src)
} else {
None
};
// Image path: only applies to <img>-style replaced elements.
let image_opt = images.get(&content.src);
if let Some(image) = image_opt {
let img_w = image.width() as f32;
let img_h = image.height() as f32;
Expand Down Expand Up @@ -870,25 +873,26 @@ fn paint_replaced(
canvas.restore();
}

/// Render a serialized inline SVG subtree via Skia's built-in SVG DOM.
/// Render a serialized inline SVG subtree via the in-tree
/// `htmlcss::svg` renderer.
///
/// The caller has already translated the canvas to the replaced
/// element's top-left and clipped to its content box. Returns `true` on
/// successful render, `false` if the XML fails to parse.
///
/// Container-size semantics match Chromium's `SVGImageForContainer`:
/// the `<svg>` is rendered at the replaced element's box size, and
/// `viewBox` + `preserveAspectRatio` (interpreted internally by Skia)
/// `viewBox` + `preserveAspectRatio` (interpreted by our renderer)
/// determine how SVG user units map into that box.
fn paint_inline_svg(canvas: &Canvas, xml: &[u8], w: f32, h: f32) -> bool {
use skia_safe::{svg, FontMgr, Size};
let data = skia_safe::Data::new_copy(xml);
let Ok(mut dom) = svg::Dom::from_bytes(&data, FontMgr::default()) else {
return false;
};
dom.set_container_size(Size::new(w, h));
dom.render(canvas);
true
fn paint_inline_svg(
canvas: &Canvas,
xml: &[u8],
w: f32,
h: f32,
images: &dyn ImageProvider,
) -> bool {
let viewport = skia_safe::Rect::from_xywh(0.0, 0.0, w, h);
crate::htmlcss::svg::render_into(canvas, xml, viewport, images).is_ok()
}

/// Map CSS `image-rendering` to Skia `SamplingOptions`.
Expand Down
11 changes: 6 additions & 5 deletions crates/grida/src/htmlcss/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,11 +201,12 @@ pub struct ReplacedContent {
///
/// When `Some`, the replaced element is an `<svg>` whose content has
/// been XML-serialized (with `xmlns="http://www.w3.org/2000/svg"`
/// injected if missing). Paint time parses this via
/// `skia_safe::svg::Dom::from_bytes` and renders it onto the canvas.
/// Follows the Servo-style architectural pattern of treating inline
/// SVG as a replaced element, but uses Skia's built-in SVG module
/// (GPU-capable) instead of resvg + tiny-skia.
/// injected if missing). Paint time hands this to
/// `crate::htmlcss::svg::render_into`, which parses with our DemoDom
/// pipeline and paints directly onto the SkCanvas. Follows the
/// Servo-style architectural pattern of treating inline SVG as a
/// replaced element, but uses our in-tree Skia-backed renderer
/// rather than resvg + tiny-skia or Skia's built-in svg::Dom.
pub svg_xml: Option<String>,
/// Parsed `viewBox="min-x min-y width height"` for SVG intrinsic
/// aspect-ratio resolution. Only populated when `svg_xml` is set.
Expand Down
Loading
Loading