Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use graphene_std::gradient::GradientStops;
use graphene_std::memo::IORecord;
use graphene_std::raster_types::{CPU, GPU, Raster};
use graphene_std::table::Table;
use graphene_std::text::Typography;
use graphene_std::vector::Vector;
use graphene_std::vector::style::{Fill, FillChoice};
use graphene_std::{Artboard, Graphic};
Expand Down Expand Up @@ -269,6 +270,7 @@ impl TableRowLayout for Graphic {
Self::RasterGPU(table) => table.identifier(),
Self::Color(table) => table.identifier(),
Self::Gradient(table) => table.identifier(),
Self::Typography(table) => table.identifier(),
}
}
// Don't put a breadcrumb for Graphic
Expand All @@ -283,6 +285,7 @@ impl TableRowLayout for Graphic {
Self::RasterGPU(table) => table.layout_with_breadcrumb(data),
Self::Color(table) => table.layout_with_breadcrumb(data),
Self::Gradient(table) => table.layout_with_breadcrumb(data),
Self::Typography(table) => table.layout_with_breadcrumb(data),
}
}
}
Expand Down Expand Up @@ -538,6 +541,19 @@ impl TableRowLayout for GradientStops {
}
}

impl TableRowLayout for Typography {
fn type_name() -> &'static str {
"Typography"
}
fn identifier(&self) -> String {
format!("Typography: {self:?}")
}
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
let widgets = vec![TextLabel::new("TODO").widget_holder()];
vec![LayoutGroup::Row { widgets }]
}
}

impl TableRowLayout for f64 {
fn type_name() -> &'static str {
"Number (f64)"
Expand Down
8 changes: 7 additions & 1 deletion node-graph/gcore/src/bounds.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Color, gradient::GradientStops};
use crate::{Color, gradient::GradientStops, text::Typography};
use glam::{DAffine2, DVec2};

#[derive(Clone, Copy, Default, Debug, PartialEq)]
Expand Down Expand Up @@ -38,3 +38,9 @@ impl BoundingBox for GradientStops {
RenderBoundingBox::Infinite
}
}
impl BoundingBox for Typography {
fn bounding_box(&self, transform: DAffine2, _include_stroke: bool) -> RenderBoundingBox {
let bbox = DVec2::new(self.layout.full_width() as f64, self.layout.height() as f64);
RenderBoundingBox::Rectangle([transform.translation, transform.transform_point2(bbox)])
}
}
25 changes: 24 additions & 1 deletion node-graph/gcore/src/graphic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::bounds::{BoundingBox, RenderBoundingBox};
use crate::gradient::GradientStops;
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::{Table, TableRow};
use crate::text::Typography;
use crate::uuid::NodeId;
use crate::vector::Vector;
use crate::{Artboard, Color, Ctx};
Expand All @@ -11,14 +12,34 @@ use glam::{DAffine2, DVec2};
use std::hash::Hash;

/// The possible forms of graphical content that can be rendered by the Render node into either an image or SVG syntax.
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug, Hash, PartialEq, DynAny)]
pub enum Graphic {
Graphic(Table<Graphic>),
Vector(Table<Vector>),
RasterCPU(Table<Raster<CPU>>),
RasterGPU(Table<Raster<GPU>>),
Color(Table<Color>),
Gradient(Table<GradientStops>),
Typography(Table<Typography>),
}

impl serde::Serialize for Graphic {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let default: Table<Graphic> = Table::new();
default.serialize(serializer)
}
}

impl<'de> serde::Deserialize<'de> for Graphic {
fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(Graphic::Graphic(Table::new()))
}
Comment on lines +26 to +42
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you just decided to not do any Serialization? Is there any reason to do this? This is a very confusing design decision and will likely lead to bugs in the future

Copy link
Collaborator Author

@adamgerhant adamgerhant Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Layout within the Typography cannot be serialized. Also its not meant to be a Tagged value, as it can only be produced at runtime by the text node. It only has this implemented since Graphic needs a tagged value for layer inputs. Similar to the wgpu::Texture.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have just successfully disabled serialization / deserialization for all Grahic data not just Typography

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphic data can never be serialized since it should never be anything other than an empty table (There is no widget to modify it)

}

impl Default for Graphic {
Expand Down Expand Up @@ -232,6 +253,7 @@ impl Graphic {
Graphic::RasterGPU(raster) => raster.iter().all(|row| row.alpha_blending.clip),
Graphic::Color(color) => color.iter().all(|row| row.alpha_blending.clip),
Graphic::Gradient(gradient) => gradient.iter().all(|row| row.alpha_blending.clip),
Graphic::Typography(typography) => typography.iter().all(|row| row.alpha_blending.clip),
}
}

Expand All @@ -256,6 +278,7 @@ impl BoundingBox for Graphic {
Graphic::Graphic(graphic) => graphic.bounding_box(transform, include_stroke),
Graphic::Color(color) => color.bounding_box(transform, include_stroke),
Graphic::Gradient(gradient) => gradient.bounding_box(transform, include_stroke),
Graphic::Typography(typography) => typography.bounding_box(transform, include_stroke),
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions node-graph/gcore/src/render_complexity.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::gradient::GradientStops;
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::Table;
use crate::text::Typography;
use crate::vector::Vector;
use crate::{Artboard, Color, Graphic};

Expand Down Expand Up @@ -31,6 +32,7 @@ impl RenderComplexity for Graphic {
Self::RasterGPU(table) => table.render_complexity(),
Self::Color(table) => table.render_complexity(),
Self::Gradient(table) => table.render_complexity(),
Self::Typography(table) => table.render_complexity(),
}
}
}
Expand Down Expand Up @@ -65,3 +67,9 @@ impl RenderComplexity for GradientStops {
1
}
}

impl RenderComplexity for Typography {
fn render_complexity(&self) -> usize {
self.layout.lines().map(|line| line.items().count()).sum()
}
}
35 changes: 35 additions & 0 deletions node-graph/gcore/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ mod path_builder;
mod text_context;
mod to_path;

use std::fmt;

use dyn_any::DynAny;
pub use font_cache::*;
use graphene_core_shaders::color::Color;
use parley::Layout;
use std::hash::{Hash, Hasher};
pub use text_context::TextContext;
pub use to_path::*;

Expand Down Expand Up @@ -57,3 +62,33 @@ impl Default for TypesettingConfig {
}
}
}

#[derive(Clone, DynAny)]
pub struct Typography {
pub layout: Layout<()>,
pub family_name: String,
pub color: Color,
pub stroke: Option<(Color, f64)>,
}

impl fmt::Debug for Typography {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Typography")
.field("font_family", &self.family_name)
.field("color", &self.color)
.field("stroke", &self.stroke)
.finish()
}
}

impl PartialEq for Typography {
fn eq(&self, _other: &Self) -> bool {
unimplemented!("Typography cannot be compared")
}
}

impl Hash for Typography {
fn hash<H: Hasher>(&self, _: &mut H) {
unimplemented!("Typography cannot be hashed")
}
}
35 changes: 20 additions & 15 deletions node-graph/gcore/src/text/text_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,34 +38,35 @@ impl TextContext {
}

/// Get or cache font information for a given font
fn get_font_info(&mut self, font: &Font, font_data: &Blob<u8>) -> Option<(String, FontInfo)> {
pub fn get_font_info(&mut self, font: &Font, font_cache: &FontCache) -> Option<(String, FontInfo)> {
// Note that the actual_font may not be the desired font if that font is not yet loaded.
// It is important not to cache the default font under the name of another font.
let (font_data, actual_font) = self.resolve_font_data(font, font_cache)?;

// Check if we already have the font info cached
if let Some((family_id, font_info)) = self.font_info_cache.get(font) {
if let Some((family_id, font_info)) = self.font_info_cache.get(actual_font) {
if let Some(family_name) = self.font_context.collection.family_name(*family_id) {
return Some((family_name.to_string(), font_info.clone()));
}
}

// Register the font and cache the info
let families = self.font_context.collection.register_fonts(font_data.clone(), None);
let families = self.font_context.collection.register_fonts(font_data, None);

families.first().and_then(|(family_id, fonts_info)| {
fonts_info.first().and_then(|font_info| {
self.font_context.collection.family_name(*family_id).map(|family_name| {
families.into_iter().next().and_then(|(family_id, fonts_info)| {
fonts_info.into_iter().next().and_then(|font_info| {
self.font_context.collection.family_name(family_id).map(|family_name| {
// Cache the font info for future use
self.font_info_cache.insert(font.clone(), (*family_id, font_info.clone()));
(family_name.to_string(), font_info.clone())
self.font_info_cache.insert(actual_font.clone(), (family_id, font_info.clone()));
(family_name.to_string(), font_info)
})
})
})
}

/// Create a text layout using the specified font and typesetting configuration
fn layout_text(&mut self, text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig) -> Option<Layout<()>> {
// Note that the actual_font may not be the desired font if that font is not yet loaded.
// It is important not to cache the default font under the name of another font.
let (font_data, actual_font) = self.resolve_font_data(font, font_cache)?;
let (font_family, font_info) = self.get_font_info(actual_font, &font_data)?;
pub fn layout_text(&mut self, text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig) -> Option<Layout<()>> {
let (font_family, font_info) = self.get_font_info(font, font_cache)?;

const DISPLAY_SCALE: f32 = 1.;
let mut builder = self.layout_context.ranged_builder(&mut self.font_context, text, DISPLAY_SCALE, false);
Expand All @@ -87,17 +88,21 @@ impl TextContext {
}

/// Convert text to vector paths using the specified font and typesetting configuration
pub fn to_path(&mut self, text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, per_glyph_instances: bool) -> Table<Vector> {
pub fn text_to_path(&mut self, text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, per_glyph_instances: bool) -> Table<Vector> {
let Some(layout) = self.layout_text(text, font, font_cache, typesetting) else {
return Table::new_from_element(Vector::default());
};

self.layout_to_path(layout, 0., per_glyph_instances)
}

pub fn layout_to_path(&mut self, layout: Layout<()>, tilt: f64, per_glyph_instances: bool) -> Table<Vector> {
let mut path_builder = PathBuilder::new(per_glyph_instances, layout.scale() as f64);

for line in layout.lines() {
for item in line.items() {
if let PositionedLayoutItem::GlyphRun(glyph_run) = item {
path_builder.render_glyph_run(&glyph_run, typesetting.tilt, per_glyph_instances);
path_builder.render_glyph_run(&glyph_run, tilt, per_glyph_instances);
}
}
}
Expand Down
17 changes: 16 additions & 1 deletion node-graph/gcore/src/text/to_path.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
use super::text_context::TextContext;
use super::{Font, FontCache, TypesettingConfig};
use crate::table::Table;
use crate::text::Typography;
use crate::vector::Vector;
use glam::DVec2;
use graphene_core_shaders::color::Color;
use parley::fontique::Blob;
use std::sync::Arc;

pub fn to_typography(text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig) -> Option<Typography> {
TextContext::with_thread_local(|ctx| {
let layout = ctx.layout_text(text, font, font_cache, typesetting)?;
let (family_name, _) = ctx.get_font_info(font, font_cache)?;
Some(Typography {
layout,
family_name,
color: Color::BLACK,
stroke: None,
})
})
}

pub fn to_path(text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, per_glyph_instances: bool) -> Table<Vector> {
TextContext::with_thread_local(|ctx| ctx.to_path(text, font, font_cache, typesetting, per_glyph_instances))
TextContext::with_thread_local(|ctx| ctx.text_to_path(text, font, font_cache, typesetting, per_glyph_instances))
}

pub fn bounding_box(text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, for_clipping_test: bool) -> DVec2 {
Expand Down
5 changes: 5 additions & 0 deletions node-graph/gpath-bool/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
use graphene_core::subpath::{ManipulatorGroup, PathSegPoints, Subpath, pathseg_points};
use graphene_core::table::{Table, TableRow, TableRowRef};
use graphene_core::text::TextContext;
use graphene_core::vector::algorithms::merge_by_distance::MergeByDistanceExt;
use graphene_core::vector::style::Fill;
use graphene_core::vector::{PointId, Vector};
Expand Down Expand Up @@ -318,6 +319,10 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
}
})
.collect::<Vec<_>>(),
Graphic::Typography(typography) => typography
.into_iter()
.flat_map(|row| TextContext::with_thread_local(|ctx| ctx.layout_to_path(row.element.layout, 0., false)))
.collect::<Vec<_>>(),
}
})
.collect()
Expand Down
2 changes: 2 additions & 0 deletions node-graph/gsvg-renderer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ log = { workspace = true }
num-traits = { workspace = true }
usvg = { workspace = true }
kurbo = { workspace = true }
parley = { workspace = true }
skrifa = { workspace = true }

# Optional workspace dependencies
vello = { workspace = true, optional = true }
Loading
Loading