use std::mem;
use std::sync::Arc;
use crate::GlyphId;
use fontdb::{Database, ID};
use skrifa::MetadataProvider;
use skrifa::Tag;
use skrifa::bitmap::{BitmapData, BitmapFormat};
use skrifa::outline::{DrawSettings, OutlinePen};
use skrifa::prelude::LocationRef;
use skrifa::raw::TableProvider as _;
use skrifa::raw::types::BoundingBox;
use svgtypes::Color;
use tiny_skia_path::{NonZeroRect, Size, Transform};
use xmlwriter::XmlWriter;
use crate::text::OPSZ;
use crate::text::colr::GlyphPainter;
use crate::*;
fn resolve_rendering_mode(text: &Text) -> ShapeRendering {
match text.rendering_mode {
TextRendering::OptimizeSpeed => ShapeRendering::CrispEdges,
TextRendering::OptimizeLegibility => ShapeRendering::GeometricPrecision,
TextRendering::GeometricPrecision => ShapeRendering::GeometricPrecision,
}
}
fn effective_variations(
cache: &mut Cache,
span: &layout::Span,
glyph: &layout::PositionedGlyph,
) -> Vec<FontVariation> {
let mut variations = span.variations.clone();
if span.font_optical_sizing == crate::FontOpticalSizing::Auto
&& !variations.iter().any(|v| &v.tag == b"opsz")
&& cache.has_opsz_axis(glyph.font)
{
variations.push(FontVariation::new(*b"opsz", glyph.font_size()));
}
variations
}
fn push_outline_paths(
span: &layout::Span,
builder: &mut tiny_skia_path::PathBuilder,
new_children: &mut Vec<Node>,
rendering_mode: ShapeRendering,
abs_transform: Transform,
) {
let builder = mem::replace(builder, tiny_skia_path::PathBuilder::new());
if let Some(path) = builder.finish().and_then(|p| {
Path::new(
String::new(),
span.visible,
span.fill.clone(),
span.stroke.clone(),
span.paint_order,
rendering_mode,
Arc::new(p),
abs_transform,
)
}) {
new_children.push(Node::Path(Box::new(path)));
}
}
pub(crate) fn flatten(text: &mut Text, cache: &mut Cache) -> Option<(Group, NonZeroRect)> {
let mut new_children = vec![];
let abs_transform = text.abs_transform;
let rendering_mode = resolve_rendering_mode(text);
for span in &text.layouted {
if let Some(path) = span.overline.as_ref() {
let mut path = path.clone();
path.rendering_mode = rendering_mode;
new_children.push(Node::Path(Box::new(path)));
}
if let Some(path) = span.underline.as_ref() {
let mut path = path.clone();
path.rendering_mode = rendering_mode;
new_children.push(Node::Path(Box::new(path)));
}
let mut span_builder = tiny_skia_path::PathBuilder::new();
for glyph in &span.positioned_glyphs {
let variations = effective_variations(cache, span, glyph);
if let Some(tree) = cache.fontdb_colr(glyph.font, glyph.id, &variations) {
let mut group = Group {
transform: glyph.colr_transform(),
..Group::empty()
};
group.children.push(Node::Group(Box::new(tree.root)));
group.calculate_bounding_boxes();
new_children.push(Node::Group(Box::new(group)));
}
else if let Some(node) = cache.fontdb_svg(glyph.font, glyph.id) {
push_outline_paths(
span,
&mut span_builder,
&mut new_children,
rendering_mode,
abs_transform,
);
let mut group = Group {
transform: glyph.svg_transform(),
..Group::empty()
};
group.children.push(node);
group.calculate_bounding_boxes();
new_children.push(Node::Group(Box::new(group)));
}
else if let Some(img) = cache.fontdb_raster(glyph.font, glyph.id) {
push_outline_paths(
span,
&mut span_builder,
&mut new_children,
rendering_mode,
abs_transform,
);
let transform = if img.is_sbix {
glyph.sbix_transform(
img.x as f32,
img.y as f32,
img.glyph_bbox.map(|bbox| bbox.x_min).unwrap_or(0) as f32,
img.glyph_bbox.map(|bbox| bbox.y_min).unwrap_or(0) as f32,
img.pixels_per_em as f32,
img.image.size.height(),
)
} else {
glyph.cbdt_transform(img.x as f32, img.y as f32, img.pixels_per_em as f32)
};
let mut group = Group {
transform,
..Group::empty()
};
group.children.push(Node::Image(Box::new(img.image)));
group.calculate_bounding_boxes();
new_children.push(Node::Group(Box::new(group)));
} else {
let outline = cache.fontdb_outline(glyph.font, glyph.id, &variations);
if let Some(outline) = outline.and_then(|p| p.transform(glyph.outline_transform()))
{
span_builder.push_path(&outline);
}
}
}
push_outline_paths(
span,
&mut span_builder,
&mut new_children,
rendering_mode,
abs_transform,
);
if let Some(path) = span.line_through.as_ref() {
let mut path = path.clone();
path.rendering_mode = rendering_mode;
new_children.push(Node::Path(Box::new(path)));
}
}
let mut group = Group {
id: text.id.clone(),
..Group::empty()
};
for child in new_children {
group.children.push(child);
}
group.calculate_bounding_boxes();
let stroke_bbox = group.stroke_bounding_box().to_non_zero_rect()?;
Some((group, stroke_bbox))
}
#[derive(Default)]
struct PathBuilder {
builder: tiny_skia_path::PathBuilder,
}
impl OutlinePen for PathBuilder {
fn move_to(&mut self, x: f32, y: f32) {
self.builder.move_to(x, y);
}
fn line_to(&mut self, x: f32, y: f32) {
self.builder.line_to(x, y);
}
fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
self.builder.quad_to(cx0, cy0, x, y);
}
fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
self.builder.cubic_to(cx0, cy0, cx1, cy1, x, y);
}
fn close(&mut self) {
self.builder.close();
}
}
pub(crate) trait DatabaseExt {
fn outline(
&self,
id: ID,
glyph_id: GlyphId,
variations: &[crate::FontVariation],
) -> Option<tiny_skia_path::Path>;
fn has_opsz_axis(&self, id: ID) -> bool;
fn raster(&self, id: ID, glyph_id: GlyphId) -> Option<BitmapImage>;
fn svg(&self, id: ID, glyph_id: GlyphId) -> Option<Node>;
fn colr(&self, id: ID, glyph_id: GlyphId, variations: &[crate::FontVariation]) -> Option<Tree>;
}
#[derive(Clone)]
pub(crate) struct BitmapImage {
image: Image,
x: i16,
y: i16,
pixels_per_em: u16,
glyph_bbox: Option<BoundingBox<i16>>,
is_sbix: bool,
}
impl DatabaseExt for Database {
#[inline(never)]
fn outline(
&self,
id: ID,
glyph_id: GlyphId,
variations: &[crate::FontVariation],
) -> Option<tiny_skia_path::Path> {
self.with_face_data(id, |data, face_index| -> Option<tiny_skia_path::Path> {
let font = skrifa::FontRef::from_index(data, face_index).ok()?;
let outline = font.outline_glyphs().get(glyph_id.into())?;
let mut builder = PathBuilder::default();
let size = skrifa::prelude::Size::unscaled();
let location = font.axes().location(
variations
.iter()
.map(|v| (Tag::from_be_bytes(v.tag), v.value)),
);
outline
.draw(DrawSettings::unhinted(size, &location), &mut builder)
.ok()?;
builder.builder.finish()
})?
}
fn has_opsz_axis(&self, id: ID) -> bool {
self.with_face_data(id, |data, face_index| -> Option<bool> {
let font = skrifa::FontRef::from_index(data, face_index).ok()?;
Some(font.axes().get_by_tag(OPSZ).is_some())
})
.flatten()
.unwrap_or(false)
}
fn raster(&self, id: ID, glyph_id: GlyphId) -> Option<BitmapImage> {
self.with_face_data(id, |data, face_index| -> Option<BitmapImage> {
let font = skrifa::FontRef::from_index(data, face_index).ok()?;
let bitmap_strikes = font.bitmap_strikes();
let size = skrifa::prelude::Size::unscaled();
let location = LocationRef::default();
let image = bitmap_strikes.glyph_for_size(size, glyph_id.into())?;
match image.data {
BitmapData::Png(data) => {
let metrics = font.glyph_metrics(size, location);
let bounding_box = metrics.bounds(glyph_id.into()).map(|bbox| BoundingBox {
x_min: bbox.x_min as i16,
y_min: bbox.y_min as i16,
x_max: bbox.x_max as i16,
y_max: bbox.y_max as i16,
});
let bitmap_image = BitmapImage {
image: Image {
id: String::new(),
visible: true,
size: Size::from_wh(image.width as f32, image.height as f32)?,
rendering_mode: ImageRendering::OptimizeQuality,
kind: ImageKind::PNG(Arc::new(data.to_vec())),
abs_transform: Transform::default(),
abs_bounding_box: NonZeroRect::from_xywh(
0.0,
0.0,
image.width as f32,
image.height as f32,
)?,
},
x: image.inner_bearing_x as i16,
y: image.inner_bearing_y as i16,
pixels_per_em: image.ppem_x as u16,
glyph_bbox: bounding_box,
is_sbix: bitmap_strikes.format() == Some(BitmapFormat::Sbix),
};
Some(bitmap_image)
}
BitmapData::Bgra(_) | BitmapData::Mask(_) => None,
}
})?
}
fn svg(&self, id: ID, glyph_id: GlyphId) -> Option<Node> {
self.with_face_data(id, |data, face_index| -> Option<Node> {
let font = skrifa::FontRef::from_index(data, face_index).ok()?;
let svg_table = font.svg().ok()?;
let image_data = svg_table.glyph_data(glyph_id.into()).ok()??;
let tree = Tree::from_data(image_data, &Options::default()).ok()?;
let document_list = svg_table.svg_document_list().ok()?;
let doc_record = document_list.document_records().iter().find(|r| {
(r.start_glyph_id.get().to_u32()..=r.end_glyph_id.get().to_u32())
.contains(&glyph_id.0)
})?;
let node = if doc_record.start_glyph_id == doc_record.end_glyph_id {
Node::Group(Box::new(tree.root))
} else {
tree.node_by_id(&format!("glyph{}", glyph_id.0))
.log_none(|| {
log::warn!("Failed to find SVG glyph node for glyph {}", glyph_id.0);
})
.cloned()?
};
Some(node)
})?
}
fn colr(&self, id: ID, glyph_id: GlyphId, variations: &[crate::FontVariation]) -> Option<Tree> {
self.with_face_data(id, |data, face_index| -> Option<Tree> {
let font = skrifa::FontRef::from_index(data, face_index).ok()?;
let location = font.axes().location(
variations
.iter()
.map(|v| (Tag::from_be_bytes(v.tag), v.value)),
);
let mut svg = XmlWriter::new(xmlwriter::Options::default());
svg.start_element("svg");
svg.write_attribute("xmlns", "http://www.w3.org/2000/svg");
svg.write_attribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
let mut path_buf = String::with_capacity(256);
let gradient_index = 1;
let clip_path_index = 1;
svg.start_element("g");
let mut glyph_painter = GlyphPainter {
font: &font,
location: LocationRef::from(&location),
svg: &mut svg,
path_buf: &mut path_buf,
gradient_index,
clip_path_index,
foreground_color: Color::new_rgba(0, 0, 0, 255),
transform: skrifa::color::Transform::default(),
outline_transform: skrifa::color::Transform::default(),
transforms_stack: vec![skrifa::color::Transform::default()],
clip_stack: Vec::new(),
};
font.color_glyphs()
.get(glyph_id.into())?
.paint(&location, &mut glyph_painter)
.ok()?;
svg.end_element();
Tree::from_data(svg.end_document().as_bytes(), &Options::default()).ok()
})?
}
}