Add support for drawing COLR glyphs in SVG export (#6693)

This commit is contained in:
Laurenz Stampfl 2025-08-04 11:49:51 +02:00 committed by GitHub
parent 3683399a95
commit 0fe8f09ba9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 65 additions and 10 deletions

View File

@ -127,13 +127,8 @@ fn draw_raster_glyph(
Some(()) Some(())
} }
/// Draws a glyph from the COLR table into the frame. /// Convert a COLR glyph into an SVG file.
fn draw_colr_glyph( pub fn colr_glyph_to_svg(font: &Font, glyph_id: GlyphId) -> Option<String> {
frame: &mut Frame,
font: &Font,
upem: Abs,
glyph_id: GlyphId,
) -> Option<()> {
let mut svg = XmlWriter::new(xmlwriter::Options::default()); let mut svg = XmlWriter::new(xmlwriter::Options::default());
let ttf = font.ttf(); let ttf = font.ttf();
@ -176,7 +171,25 @@ fn draw_colr_glyph(
ttf.paint_color_glyph(glyph_id, 0, RgbaColor::new(0, 0, 0, 255), &mut glyph_painter)?; ttf.paint_color_glyph(glyph_id, 0, RgbaColor::new(0, 0, 0, 255), &mut glyph_painter)?;
svg.end_element(); svg.end_element();
let data = Bytes::from_string(svg.end_document()); Some(svg.end_document())
}
/// Draws a glyph from the COLR table into the frame.
fn draw_colr_glyph(
frame: &mut Frame,
font: &Font,
upem: Abs,
glyph_id: GlyphId,
) -> Option<()> {
let svg_string = colr_glyph_to_svg(font, glyph_id)?;
let ttf = font.ttf();
let width = ttf.global_bounding_box().width() as f64;
let height = ttf.global_bounding_box().height() as f64;
let x_min = ttf.global_bounding_box().x_min as f64;
let y_max = ttf.global_bounding_box().y_max as f64;
let data = Bytes::from_string(svg_string);
let image = Image::plain(SvgImage::new(data).ok()?); let image = Image::plain(SvgImage::new(data).ok()?);
let y_shift = Abs::pt(upem.to_pt() - y_max); let y_shift = Abs::pt(upem.to_pt() - y_max);

View File

@ -5,6 +5,7 @@ use ecow::EcoString;
use ttf_parser::GlyphId; use ttf_parser::GlyphId;
use typst_library::foundations::Bytes; use typst_library::foundations::Bytes;
use typst_library::layout::{Abs, Point, Ratio, Size, Transform}; use typst_library::layout::{Abs, Point, Ratio, Size, Transform};
use typst_library::text::color::colr_glyph_to_svg;
use typst_library::text::{Font, TextItem}; use typst_library::text::{Font, TextItem};
use typst_library::visualize::{ use typst_library::visualize::{
ExchangeFormat, FillRule, Image, Paint, RasterImage, RelativeTo, ExchangeFormat, FillRule, Image, Paint, RasterImage, RelativeTo,
@ -31,7 +32,8 @@ impl SVGRenderer<'_> {
let x_offset = x + glyph.x_offset.at(text.size).to_pt(); let x_offset = x + glyph.x_offset.at(text.size).to_pt();
let y_offset = y + glyph.y_offset.at(text.size).to_pt(); let y_offset = y + glyph.y_offset.at(text.size).to_pt();
self.render_svg_glyph(text, id, x_offset, y_offset, scale) self.render_colr_glyph(text, id, x_offset, y_offset, scale)
.or_else(|| self.render_svg_glyph(text, id, x_offset, y_offset, scale))
.or_else(|| self.render_bitmap_glyph(text, id, x_offset, y_offset)) .or_else(|| self.render_bitmap_glyph(text, id, x_offset, y_offset))
.or_else(|| { .or_else(|| {
self.render_outline_glyph( self.render_outline_glyph(
@ -87,6 +89,42 @@ impl SVGRenderer<'_> {
Some(()) Some(())
} }
/// Render a glyph defined by COLR glyph descriptions.
fn render_colr_glyph(
&mut self,
text: &TextItem,
id: GlyphId,
x_offset: f64,
y_offset: f64,
scale: f64,
) -> Option<()> {
let ttf = text.font.ttf();
let converted = colr_glyph_to_svg(&text.font, id)?;
let width = ttf.global_bounding_box().width() as f64;
let height = ttf.global_bounding_box().height() as f64;
let data_url = svg_to_base64(&converted);
let x_min = ttf.global_bounding_box().x_min as f64;
let y_max = ttf.global_bounding_box().y_max as f64;
let glyph_hash = hash128(&(&text.font, id));
let id = self.glyphs.insert_with(glyph_hash, || RenderedGlyph::Image {
url: data_url,
width,
height,
ts: Transform::scale(Ratio::new(scale), Ratio::new(-scale))
.pre_concat(Transform::translate(Abs::pt(x_min), -Abs::pt(y_max))),
});
self.xml.start_element("use");
self.xml.write_attribute_fmt("xlink:href", format_args!("#{id}"));
self.xml.write_attribute("x", &(x_offset));
self.xml.write_attribute("y", &(y_offset));
self.xml.end_element();
Some(())
}
/// Render a glyph defined by a bitmap. /// Render a glyph defined by a bitmap.
fn render_bitmap_glyph( fn render_bitmap_glyph(
&mut self, &mut self,
@ -320,10 +358,14 @@ fn convert_svg_glyph_to_base64_url(font: &Font, id: GlyphId) -> Option<EcoString
); );
} }
Some(svg_to_base64(&svg_str))
}
fn svg_to_base64(svg_str: &str) -> EcoString {
let mut url: EcoString = "data:image/svg+xml;base64,".into(); let mut url: EcoString = "data:image/svg+xml;base64,".into();
let b64_encoded = let b64_encoded =
base64::engine::general_purpose::STANDARD.encode(svg_str.as_bytes()); base64::engine::general_purpose::STANDARD.encode(svg_str.as_bytes());
url.push_str(&b64_encoded); url.push_str(&b64_encoded);
Some(url) url
} }