Add support for drawing COLR glyphs in SVG export (#6693)
This commit is contained in:
parent
3683399a95
commit
0fe8f09ba9
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue