New Typography type

This commit is contained in:
Adam
2025-10-03 19:22:53 -07:00
parent 593a89f2e2
commit f96a44c4bc
13 changed files with 348 additions and 4 deletions
+111
View File
@@ -15,6 +15,7 @@ use graphene_core::raster_types::{CPU, GPU, Raster};
use graphene_core::render_complexity::RenderComplexity;
use graphene_core::subpath::Subpath;
use graphene_core::table::{Table, TableRow};
use graphene_core::text::Typography;
use graphene_core::transform::{Footprint, Transform};
use graphene_core::uuid::{NodeId, generate_uuid};
use graphene_core::vector::Vector;
@@ -23,11 +24,14 @@ use graphene_core::vector::style::{Fill, PaintOrder, RenderMode, Stroke, StrokeA
use graphene_core::{Artboard, Graphic};
use kurbo::Affine;
use num_traits::Zero;
use skrifa::MetadataProvider;
use skrifa::attribute::Style;
use std::collections::{HashMap, HashSet};
use std::fmt::Write;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::ops::Deref;
use std::sync::{Arc, LazyLock};
use vello::peniko::StyleRef;
#[cfg(feature = "vello")]
use vello::*;
@@ -115,6 +119,18 @@ impl SvgRender {
self.svg.push("/>".into());
}
pub fn leaf_text(&mut self, text: impl Into<SvgSegment>, attributes: impl FnOnce(&mut SvgRenderAttrs)) {
self.indent();
self.svg.push("<tspan".into());
attributes(&mut SvgRenderAttrs(self));
self.svg.push(">".into());
self.svg.push(text.into());
self.svg.push("</tspan>".into());
}
pub fn leaf_node(&mut self, content: impl Into<SvgSegment>) {
self.indent();
self.svg.push(content.into());
@@ -289,6 +305,7 @@ impl Render for Graphic {
Graphic::RasterGPU(_) => (),
Graphic::Color(table) => table.render_svg(render, render_params),
Graphic::Gradient(table) => table.render_svg(render, render_params),
Graphic::Typography(table) => table.render_svg(render, render_params),
}
}
@@ -301,6 +318,7 @@ impl Render for Graphic {
Graphic::RasterGPU(table) => table.render_to_vello(scene, transform, context, render_params),
Graphic::Color(table) => table.render_to_vello(scene, transform, context, render_params),
Graphic::Gradient(table) => table.render_to_vello(scene, transform, context, render_params),
Graphic::Typography(table) => table.render_to_vello(scene, transform, context, render_params),
}
}
@@ -345,6 +363,14 @@ impl Render for Graphic {
Graphic::Gradient(table) => {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than the first row
if let Some(row) = table.iter().next() {
metadata.local_transforms.insert(element_id, *row.transform);
}
}
Graphic::Typography(table) => {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than the first row
if let Some(row) = table.iter().next() {
metadata.local_transforms.insert(element_id, *row.transform);
@@ -360,6 +386,7 @@ impl Render for Graphic {
Graphic::RasterGPU(table) => table.collect_metadata(metadata, footprint, element_id),
Graphic::Color(table) => table.collect_metadata(metadata, footprint, element_id),
Graphic::Gradient(table) => table.collect_metadata(metadata, footprint, element_id),
Graphic::Typography(table) => table.collect_metadata(metadata, footprint, element_id),
}
}
@@ -371,6 +398,7 @@ impl Render for Graphic {
Graphic::RasterGPU(table) => table.add_upstream_click_targets(click_targets),
Graphic::Color(table) => table.add_upstream_click_targets(click_targets),
Graphic::Gradient(table) => table.add_upstream_click_targets(click_targets),
Graphic::Typography(table) => table.add_upstream_click_targets(click_targets),
}
}
@@ -382,6 +410,7 @@ impl Render for Graphic {
Graphic::RasterGPU(table) => table.contains_artboard(),
Graphic::Color(table) => table.contains_artboard(),
Graphic::Gradient(table) => table.contains_artboard(),
Graphic::Typography(table) => table.contains_artboard(),
}
}
@@ -393,6 +422,7 @@ impl Render for Graphic {
Graphic::RasterGPU(_) => (),
Graphic::Color(_) => (),
Graphic::Gradient(_) => (),
Graphic::Typography(_) => (),
}
}
}
@@ -1542,6 +1572,87 @@ impl Render for Table<GradientStops> {
}
}
impl Render for Table<Typography> {
fn render_svg(&self, render: &mut SvgRender, _render_params: &RenderParams) {
for table_row in self.iter() {
for line in table_row.element.layout.lines() {
for item in line.items() {
match item {
parley::PositionedLayoutItem::GlyphRun(glyph_run) => {
let font = glyph_run.run().font();
let font_ref = skrifa::FontRef::from_index(font.data.as_ref(), font.index).unwrap();
let font_attributes = font_ref.attributes();
let font_style = match font_attributes.style {
Style::Normal => "normal".to_string(),
Style::Italic => "italic".to_string(),
Style::Oblique(Some(angle)) => format!("oblique {}deg", angle),
Style::Oblique(None) => "oblique".to_string(),
};
render.parent_tag(
"text",
|attributes| {
let matrix = format_transform_matrix(*table_row.transform);
if !matrix.is_empty() {
attributes.push("transform", matrix);
}
attributes.push("font-family", table_row.element.font_family.clone());
attributes.push("font-weight", font_attributes.weight.value().to_string());
attributes.push("font-size", glyph_run.run().font_size().to_string());
attributes.push("font-style", font_style);
attributes.push("fill", format!("#{}", table_row.element.color.to_rgb_hex_srgb_from_gamma()));
attributes.push("opacity", table_row.alpha_blending.opacity.to_string());
if let Some((stroke_color, stroke_width)) = table_row.element.stroke.as_ref().cloned() {
attributes.push("stroke-color", format!("#{}", stroke_color.to_rgb_hex_srgb_from_gamma()));
attributes.push("stroke-width", format!("{stroke_width}"));
}
},
|render| {
for glyph in glyph_run.positioned_glyphs() {
let character = font_ref.glyph_names().get(skrifa::GlyphId::new(glyph.id as u32)).unwrap().as_str().to_string();
let character = character.replace("space", " ");
render.leaf_text(character, |attributes| {
attributes.push("x", glyph.x.to_string());
attributes.push("y", glyph.y.to_string());
});
}
},
);
}
parley::PositionedLayoutItem::InlineBox(_positioned_inline_box) => {
log::error!("Inline box text rendering not supported");
}
}
}
}
}
}
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _context: &mut RenderContext, _render_params: &RenderParams) {
for table_row in self.iter() {
for line in table_row.element.layout.lines() {
for item in line.items() {
match item {
parley::PositionedLayoutItem::GlyphRun(glyph_run) => {
let color = table_row.element.color.clone();
scene
.draw_glyphs(glyph_run.run().font())
.transform(kurbo::Affine::new((transform * *table_row.transform).to_cols_array()))
.font_size(glyph_run.run().font_size())
.brush(peniko::BrushRef::Solid(peniko::Color::new([color.r(), color.g(), color.b(), color.a()])))
.brush_alpha(table_row.alpha_blending.opacity)
.draw(StyleRef::Fill(peniko::Fill::EvenOdd), glyph_run.glyphs().map(|g| Glyph { id: g.id as u32, x: g.x, y: g.y }));
}
parley::PositionedLayoutItem::InlineBox(_positioned_inline_box) => {
log::error!("Cannot render positioned inline box to vello");
}
}
}
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SvgSegment {
Slice(&'static str),