mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-20 11:28:30 +08:00
Restructure node crates (#3384)
* Restructure node-graph folder * Fix wasm compilation * Move node definitions out of *-types crates * Cleanup * Fix warnings * Fix warnings * Start adding migrations * Add migrations and move memo nodes to gcore * Move nodes/gsvg-render -> rendering * Replace some hard coded identifiers and fix automatic conversion * Fix Vec2Value node migration * Fix formatting * Add more migrations * Cleanup features * Fix core_types::raster import * Update demo artwork (to make profile ci work) * Move *-types to node-graph/libraries folder * Add missing node migrations * Migrate more nodes * Remove impure memo node * More fixes and remove warning * Migrate context and add a few missing migrations --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
127
node-graph/nodes/text/src/text_context.rs
Normal file
127
node-graph/nodes/text/src/text_context.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
use super::{Font, FontCache, TypesettingConfig};
|
||||
use core::cell::RefCell;
|
||||
use core_types::table::Table;
|
||||
use glam::DVec2;
|
||||
use parley::fontique::{Blob, FamilyId, FontInfo};
|
||||
use parley::{AlignmentOptions, FontContext, Layout, LayoutContext, LineHeight, PositionedLayoutItem, StyleProperty};
|
||||
use std::collections::HashMap;
|
||||
use vector_types::Vector;
|
||||
|
||||
use super::path_builder::PathBuilder;
|
||||
|
||||
thread_local! {
|
||||
static THREAD_TEXT: RefCell<TextContext> = RefCell::new(TextContext::default());
|
||||
}
|
||||
|
||||
/// Unified thread-local text processing context that combines font and layout management
|
||||
/// for efficient text rendering operations.
|
||||
#[derive(Default)]
|
||||
pub struct TextContext {
|
||||
font_context: FontContext,
|
||||
layout_context: LayoutContext<()>,
|
||||
/// Cached font metadata for performance optimization
|
||||
font_info_cache: HashMap<Font, (FamilyId, FontInfo)>,
|
||||
}
|
||||
|
||||
impl TextContext {
|
||||
/// Access the thread-local TextContext instance for text processing operations
|
||||
pub fn with_thread_local<F, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut TextContext) -> R,
|
||||
{
|
||||
THREAD_TEXT.with_borrow_mut(f)
|
||||
}
|
||||
|
||||
/// Resolve a font and return its data as a Blob if available
|
||||
fn resolve_font_data<'a>(&self, font: &'a Font, font_cache: &'a FontCache) -> Option<(Blob<u8>, &'a Font)> {
|
||||
font_cache.get_blob(font)
|
||||
}
|
||||
|
||||
/// Get or cache font information for a given font
|
||||
fn get_font_info(&mut self, font: &Font, font_data: &Blob<u8>) -> Option<(String, FontInfo)> {
|
||||
// Check if we already have the font info cached
|
||||
if let Some((family_id, font_info)) = self.font_info_cache.get(font)
|
||||
&& 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);
|
||||
|
||||
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| {
|
||||
// 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())
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// 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)?;
|
||||
|
||||
const DISPLAY_SCALE: f32 = 1.;
|
||||
let mut builder = self.layout_context.ranged_builder(&mut self.font_context, text, DISPLAY_SCALE, false);
|
||||
|
||||
builder.push_default(StyleProperty::FontSize(typesetting.font_size as f32));
|
||||
builder.push_default(StyleProperty::LetterSpacing(typesetting.character_spacing as f32));
|
||||
builder.push_default(StyleProperty::FontStack(parley::FontStack::Single(parley::FontFamily::Named(std::borrow::Cow::Owned(font_family)))));
|
||||
builder.push_default(StyleProperty::FontWeight(font_info.weight()));
|
||||
builder.push_default(StyleProperty::FontStyle(font_info.style()));
|
||||
builder.push_default(StyleProperty::FontWidth(font_info.width()));
|
||||
builder.push_default(LineHeight::FontSizeRelative(typesetting.line_height_ratio as f32));
|
||||
|
||||
let mut layout: Layout<()> = builder.build(text);
|
||||
|
||||
layout.break_all_lines(typesetting.max_width.map(|mw| mw as f32));
|
||||
layout.align(typesetting.max_width.map(|max_w| max_w as f32), typesetting.align.into(), AlignmentOptions::default());
|
||||
|
||||
Some(layout)
|
||||
}
|
||||
|
||||
/// Convert text to vector paths using the specified font and typesetting configuration
|
||||
pub fn to_path<Upstream: Default + 'static>(&mut self, text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, per_glyph_instances: bool) -> Table<Vector<Upstream>> {
|
||||
let Some(layout) = self.layout_text(text, font, font_cache, typesetting) else {
|
||||
return Table::new_from_element(Vector::default());
|
||||
};
|
||||
|
||||
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.finalize()
|
||||
}
|
||||
|
||||
/// Calculate the bounding box of text using the specified font and typesetting configuration
|
||||
pub fn bounding_box(&mut self, text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig, for_clipping_test: bool) -> DVec2 {
|
||||
if !for_clipping_test && let (Some(max_height), Some(max_width)) = (typesetting.max_height, typesetting.max_width) {
|
||||
return DVec2::new(max_width, max_height);
|
||||
}
|
||||
|
||||
let Some(layout) = self.layout_text(text, font, font_cache, typesetting) else {
|
||||
return DVec2::ZERO;
|
||||
};
|
||||
|
||||
DVec2::new(layout.full_width() as f64, layout.height() as f64)
|
||||
}
|
||||
|
||||
/// Check if text lines are being clipped due to height constraints
|
||||
pub fn lines_clipping(&mut self, text: &str, font: &Font, font_cache: &FontCache, typesetting: TypesettingConfig) -> bool {
|
||||
let Some(max_height) = typesetting.max_height else { return false };
|
||||
let bounds = self.bounding_box(text, font, font_cache, typesetting, true);
|
||||
max_height < bounds.y
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user