Repository: typst/typst
File: crates/typst-svg/src/lib.rs (Rust)

Code before:
        scale: f64,
    ) -> Option<()> {
        let scale = Ratio::new(scale);
        let path = convert_outline_glyph_to_path(&text.font, glyph_id, scale)?;
        let hash = hash128(&(&text.font, glyph_id, scale));
        let id = self.glyphs.insert_with(hash, || RenderedGlyph::Path(path));

        let glyph_size = text.font.ttf().glyph_bounding_box(glyph_id)?;
        let width = glyph_size.width() as f64 * scale.get();
        let height = glyph_size.height() as f64 * scale.get();

        self.xml.start_element("use");
        self.xml.write_attribute_fmt("xlink:href", format_args!("#{id}"));
        self.xml.write_attribute_fmt("x", format_args!("{x_offset}"));
        self.write_fill(
            &text.fill,
            Size::new(Abs::pt(width), Abs::pt(height)),
            self.text_paint_transform(state, &text.fill),
        );
        if let Some(stroke) = &text.stroke {
            self.write_stroke(
                stroke,
                Size::new(Abs::pt(width), Abs::pt(height)),
                self.text_paint_transform(state, &text.fill),
            );
            self.xml.write_attribute("paint-order", "stroke"); // make sure the stroke is rendered on top of the fill
        }
        self.xml.end_element();

        Some(())
    }

    fn text_paint_transform(&self, state: State, paint: &Paint) -> Transform {
        match paint {
            Paint::Solid(_) => Transform::identity(),
            Paint::Gradient(gradient) => match gradient.unwrap_relative(true) {
                RelativeTo::Self_ => Transform::identity(),
                RelativeTo::Parent => Transform::scale(
                    Ratio::new(state.size.x.to_pt()),
                    Ratio::new(state.size.y.to_pt()),
                )
                .post_concat(state.transform.invert().unwrap()),
            },
            Paint::Pattern(pattern) => match pattern.unwrap_relative(true) {
                RelativeTo::Self_ => Transform::identity(),
                RelativeTo::Parent => state.transform.invert().unwrap(),
            },
        }
    }

    /// Render a shape element.

Review comment (bug):
Actually, it seems that we're already using `paint-order`. So Chrome is probably rendering "wrongly" because it's a very recent addition, so maybe it's just a matter of using a recent Chrome version (see https://caniuse.com/mdn-svg_attributes_presentation_paint-order )

Perhaps it could still be configurable through the `text` Typst element, though :thinking:

Code after:
        scale: f64,
    ) -> Option<()> {
        let scale = Ratio::new(scale);
        let path = convert_outline_glyph_to_path(&text.font, glyph_id, scale)?;
        let hash = hash128(&(&text.font, glyph_id, scale));
        let id = self.glyphs.insert_with(hash, || RenderedGlyph::Path(path));

        let glyph_size = text.font.ttf().glyph_bounding_box(glyph_id)?;
        let width = glyph_size.width() as f64 * scale.get();
        let height = glyph_size.height() as f64 * scale.get();

        self.xml.start_element("use");
        self.xml.write_attribute_fmt("xlink:href", format_args!("#{id}"));
        self.xml.write_attribute_fmt("x", format_args!("{x_offset}"));
        self.write_fill(
            &text.fill,
            Size::new(Abs::pt(width), Abs::pt(height)),
            self.text_paint_transform(state, &text.fill),
        );
        if let Some(stroke) = &text.stroke {
            self.write_stroke(
                stroke,
                Size::new(Abs::pt(width), Abs::pt(height)),
                self.text_paint_transform(state, &stroke.paint),
            );
        }
        self.xml.end_element();

        Some(())
    }

    fn text_paint_transform(&self, state: State, paint: &Paint) -> Transform {
        match paint {
            Paint::Solid(_) => Transform::identity(),
            Paint::Gradient(gradient) => match gradient.unwrap_relative(true) {
                RelativeTo::Self_ => Transform::identity(),
                RelativeTo::Parent => Transform::scale(
                    Ratio::new(state.size.x.to_pt()),
                    Ratio::new(state.size.y.to_pt()),
                )
                .post_concat(state.transform.invert().unwrap()),
            },
            Paint::Pattern(pattern) => match pattern.unwrap_relative(true) {
                RelativeTo::Self_ => Transform::identity(),
                RelativeTo::Parent => state.transform.invert().unwrap(),
            },
        }
    }

    /// Render a shape element.
    fn render_shape(&mut self, state: State, shape: &Shape) {