mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Separate the Text node's generated glyphs into separate vector table rows (#2821)
* Separate glyphs into Vector data rows * Fix `String Length` node - Properly count characters with `str.chars().count()` instead of bytes `str.len()` - Change `String Length` node's output to `u32` * Apply transform on instance instead of applying it when drawing the glyph * Add checkbox to enable/disable per-glyph instances * Tooltips --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -34,7 +34,7 @@ fn string_slice(_: impl Ctx, #[implementations(String)] string: String, start: f
|
||||
|
||||
#[node_macro::node(category("Text"))]
|
||||
fn string_length(_: impl Ctx, #[implementations(String)] string: String) -> u32 {
|
||||
string.len() as u32
|
||||
string.chars().count() as u32
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::vector::PointId;
|
||||
use crate::instances::Instance;
|
||||
use crate::vector::{PointId, VectorData, VectorDataTable};
|
||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use core::cell::RefCell;
|
||||
use glam::{DAffine2, DVec2};
|
||||
@@ -20,24 +21,20 @@ thread_local! {
|
||||
|
||||
struct PathBuilder {
|
||||
current_subpath: Subpath<PointId>,
|
||||
glyph_subpaths: Vec<Subpath<PointId>>,
|
||||
other_subpaths: Vec<Subpath<PointId>>,
|
||||
origin: DVec2,
|
||||
glyph_subpaths: Vec<Subpath<PointId>>,
|
||||
vector_table: VectorDataTable,
|
||||
scale: f64,
|
||||
id: PointId,
|
||||
}
|
||||
|
||||
impl PathBuilder {
|
||||
fn point(&self, x: f32, y: f32) -> DVec2 {
|
||||
// Y-axis inversion converts from font coordinate system (Y-up) to graphics coordinate system (Y-down)
|
||||
DVec2::new(self.origin.x + x as f64, self.origin.y - y as f64) * self.scale
|
||||
}
|
||||
|
||||
fn set_origin(&mut self, x: f64, y: f64) {
|
||||
self.origin = DVec2::new(x, y);
|
||||
}
|
||||
|
||||
fn draw_glyph(&mut self, glyph: &OutlineGlyph<'_>, size: f32, normalized_coords: &[NormalizedCoord], style_skew: Option<DAffine2>, skew: DAffine2) {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn draw_glyph(&mut self, glyph: &OutlineGlyph<'_>, size: f32, normalized_coords: &[NormalizedCoord], glyph_offset: DVec2, style_skew: Option<DAffine2>, skew: DAffine2, per_glyph_instances: bool) {
|
||||
let location_ref = LocationRef::new(normalized_coords);
|
||||
let settings = DrawSettings::unhinted(Size::new(size), location_ref);
|
||||
glyph.draw(settings, self).unwrap();
|
||||
@@ -52,8 +49,19 @@ impl PathBuilder {
|
||||
glyph_subpath.apply_transform(skew);
|
||||
}
|
||||
|
||||
if !self.glyph_subpaths.is_empty() {
|
||||
self.other_subpaths.extend(core::mem::take(&mut self.glyph_subpaths));
|
||||
if per_glyph_instances {
|
||||
if !self.glyph_subpaths.is_empty() {
|
||||
self.vector_table.push(Instance {
|
||||
instance: VectorData::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false),
|
||||
transform: DAffine2::from_translation(glyph_offset),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
} else if !self.glyph_subpaths.is_empty() {
|
||||
for subpath in self.glyph_subpaths.iter() {
|
||||
// Unwrapping here is ok, since the check above guarantees there is at least one `VectorData`
|
||||
self.vector_table.get_mut(0).unwrap().instance.append_subpath(subpath, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,7 +120,7 @@ impl Default for TypesettingConfig {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_glyph_run(glyph_run: &GlyphRun<'_, ()>, path_builder: &mut PathBuilder, tilt: f64) {
|
||||
fn render_glyph_run(glyph_run: &GlyphRun<'_, ()>, path_builder: &mut PathBuilder, tilt: f64, per_glyph_instances: bool) {
|
||||
let mut run_x = glyph_run.offset();
|
||||
let run_y = glyph_run.baseline();
|
||||
|
||||
@@ -145,14 +153,15 @@ fn render_glyph_run(glyph_run: &GlyphRun<'_, ()>, path_builder: &mut PathBuilder
|
||||
let outlines = font_ref.outline_glyphs();
|
||||
|
||||
for glyph in glyph_run.glyphs() {
|
||||
let glyph_x = run_x + glyph.x;
|
||||
let glyph_y = run_y - glyph.y;
|
||||
let glyph_offset = DVec2::new((run_x + glyph.x) as f64, (run_y - glyph.y) as f64);
|
||||
run_x += glyph.advance;
|
||||
|
||||
let glyph_id = GlyphId::from(glyph.id);
|
||||
if let Some(glyph_outline) = outlines.get(glyph_id) {
|
||||
path_builder.set_origin(glyph_x as f64, glyph_y as f64);
|
||||
path_builder.draw_glyph(&glyph_outline, font_size, &normalized_coords, style_skew, skew);
|
||||
if !per_glyph_instances {
|
||||
path_builder.origin = glyph_offset;
|
||||
}
|
||||
path_builder.draw_glyph(&glyph_outline, font_size, &normalized_coords, glyph_offset, style_skew, skew, per_glyph_instances);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,27 +196,37 @@ fn layout_text(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingC
|
||||
Some(layout)
|
||||
}
|
||||
|
||||
pub fn to_path(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingConfig) -> Vec<Subpath<PointId>> {
|
||||
let Some(layout) = layout_text(str, font_data, typesetting) else { return Vec::new() };
|
||||
pub fn to_path(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingConfig, per_glyph_instances: bool) -> VectorDataTable {
|
||||
let Some(layout) = layout_text(str, font_data, typesetting) else {
|
||||
return VectorDataTable::new(VectorData::default());
|
||||
};
|
||||
|
||||
let mut path_builder = PathBuilder {
|
||||
current_subpath: Subpath::new(Vec::new(), false),
|
||||
glyph_subpaths: Vec::new(),
|
||||
other_subpaths: Vec::new(),
|
||||
origin: DVec2::ZERO,
|
||||
vector_table: if per_glyph_instances {
|
||||
VectorDataTable::default()
|
||||
} else {
|
||||
VectorDataTable::new(VectorData::default())
|
||||
},
|
||||
scale: layout.scale() as f64,
|
||||
id: PointId::ZERO,
|
||||
origin: DVec2::default(),
|
||||
};
|
||||
|
||||
for line in layout.lines() {
|
||||
for item in line.items() {
|
||||
if let PositionedLayoutItem::GlyphRun(glyph_run) = item {
|
||||
render_glyph_run(&glyph_run, &mut path_builder, typesetting.tilt);
|
||||
render_glyph_run(&glyph_run, &mut path_builder, typesetting.tilt, per_glyph_instances);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
path_builder.other_subpaths
|
||||
if path_builder.vector_table.is_empty() {
|
||||
path_builder.vector_table = VectorDataTable::new(VectorData::default());
|
||||
}
|
||||
|
||||
path_builder.vector_table
|
||||
}
|
||||
|
||||
pub fn bounding_box(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingConfig, for_clipping_test: bool) -> DVec2 {
|
||||
|
||||
@@ -2041,7 +2041,7 @@ fn point_inside(_: impl Ctx, source: VectorDataTable, point: DVec2) -> bool {
|
||||
|
||||
#[node_macro::node(category("General"), path(graphene_core::vector))]
|
||||
async fn count_elements<I>(_: impl Ctx, #[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>, RasterDataTable<GPU>)] source: Instances<I>) -> u64 {
|
||||
source.instance_iter().count() as u64
|
||||
source.len() as u64
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::vector::{VectorData, VectorDataTable};
|
||||
use crate::vector::VectorDataTable;
|
||||
use graph_craft::wasm_application_io::WasmEditorApi;
|
||||
use graphene_core::Ctx;
|
||||
pub use graphene_core::text::*;
|
||||
@@ -24,9 +24,13 @@ fn text<'i: 'n>(
|
||||
#[unit(" px")]
|
||||
#[default(None)]
|
||||
max_height: Option<f64>,
|
||||
/// Faux italic.
|
||||
#[unit("°")]
|
||||
#[default(0.)]
|
||||
tilt: f64,
|
||||
/// Splits each text glyph into its own instance, i.e. row in the table of vector data.
|
||||
#[default(false)]
|
||||
per_glyph_instances: bool,
|
||||
) -> VectorDataTable {
|
||||
let typesetting = TypesettingConfig {
|
||||
font_size,
|
||||
@@ -39,7 +43,5 @@ fn text<'i: 'n>(
|
||||
|
||||
let font_data = editor.font_cache.get(&font_name).map(|f| load_font(f));
|
||||
|
||||
let result = VectorData::from_subpaths(to_path(&text, font_data, typesetting), false);
|
||||
|
||||
VectorDataTable::new(result)
|
||||
to_path(&text, font_data, typesetting, per_glyph_instances)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user