pub mod fallback; mod font; pub mod json; mod path_builder; pub mod regex; mod text_context; mod to_path; use convert_case::{Boundary, Converter, pattern}; use core_types::graphene_hash::CacheHash; use core_types::list::{Item, List}; use core_types::registry::types::{SignedInteger, TextArea}; use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractVarArgs, OwnedContextImpl}; use dyn_any::DynAny; use glam::{DAffine2, DVec2}; use unicode_segmentation::UnicodeSegmentation; // Re-export for convenience pub use core_types as gcore; pub use fallback::FALLBACK_FONT_RESOURCE; pub use font::*; pub use text_context::{TextContext, for_each_styled_glyph_run}; pub use to_path::*; pub use vector_types; /// Alignment of lines of type within a text block. #[repr(C)] #[cfg_attr(feature = "wasm", derive(tsify::Tsify))] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, CacheHash, DynAny, node_macro::ChoiceType)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[widget(Radio)] pub enum TextAlign { #[default] #[icon("TextAlignLeft")] #[cfg_attr(feature = "serde", serde(alias = "Left"))] AlignLeft, #[icon("TextAlignCenter")] #[cfg_attr(feature = "serde", serde(alias = "Center"))] AlignCenter, #[icon("TextAlignRight")] #[cfg_attr(feature = "serde", serde(alias = "Right"))] AlignRight, #[icon("TextJustifyLeft")] JustifyLeft, #[icon("TextJustifyCenter")] JustifyCenter, #[icon("TextJustifyRight")] JustifyRight, #[icon("TextJustifyAll")] JustifyAll, } impl From for parley::Alignment { fn from(val: TextAlign) -> Self { match val { TextAlign::AlignLeft => parley::Alignment::Left, TextAlign::AlignCenter => parley::Alignment::Center, TextAlign::AlignRight => parley::Alignment::Right, _ => parley::Alignment::Justify, } } } impl TextAlign { /// What `parley::Alignment` to apply as a post-correction to the last line of a paragraph, or `None` if parley's default already handles it. /// /// `JustifyLeft` returns `None` because parley already left-aligns the last line of a `Justify` layout. The other justify modes need /// the last line shifted (`Center`/`Right`) or its inter-word spaces redistributed (`Justify` / `JustifyAll`). pub fn last_line_correction(self) -> Option { match self { Self::JustifyCenter => Some(parley::Alignment::Center), Self::JustifyRight => Some(parley::Alignment::Right), Self::JustifyAll => Some(parley::Alignment::Justify), _ => None, } } /// CSS `(text-align, text-align-last)` values approximating this alignment for the `contenteditable` text overlay. pub fn css(self) -> (&'static str, &'static str) { match self { Self::AlignLeft => ("left", "auto"), Self::AlignCenter => ("center", "auto"), Self::AlignRight => ("right", "auto"), Self::JustifyLeft => ("justify", "auto"), Self::JustifyCenter => ("justify", "center"), Self::JustifyRight => ("justify", "right"), Self::JustifyAll => ("justify", "justify"), } } } #[derive(PartialEq, Clone, Copy, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TypesettingConfig { pub font_size: f64, pub line_height_ratio: f64, pub letter_spacing: f64, pub letter_tilt: f64, pub max_width: Option, pub max_height: Option, pub align: TextAlign, } impl Default for TypesettingConfig { fn default() -> Self { Self { font_size: 24., line_height_ratio: 1.2, letter_spacing: 0., letter_tilt: 0., max_width: None, max_height: None, align: TextAlign::default(), } } } /// Converts escape sequence representations (`\n`, `\r`, `\t`, `\0`, `\\`) into their corresponding control characters. /// Unrecognized escape sequences (e.g. `\x`) are preserved as-is. fn unescape_string(input: String) -> String { let mut result = String::with_capacity(input.len()); let mut chars = input.chars(); while let Some(c) = chars.next() { if c == '\\' { match chars.next() { Some('n') => result.push('\n'), Some('r') => result.push('\r'), Some('t') => result.push('\t'), Some('0') => result.push('\0'), Some('\\') => result.push('\\'), Some(unrecognized) => result.extend(['\\', unrecognized]), None => result.push('\\'), } } else { result.push(c); } } result } /// Converts control characters (newline, carriage return, tab, null, backslash) back into their escape sequence representations. fn escape_string(input: String) -> String { let mut result = String::with_capacity(input.len()); for c in input.chars() { match c { '\n' => result.push_str("\\n"), '\r' => result.push_str("\\r"), '\t' => result.push_str("\\t"), '\0' => result.push_str("\\0"), '\\' => result.push_str("\\\\"), other => result.push(other), } } result } #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, CacheHash, dyn_any::DynAny, node_macro::ChoiceType, serde::Serialize, serde::Deserialize)] #[widget(Dropdown)] pub enum StringCapitalization { /// "on the origin of species" — Converts all letters to lower case. #[default] #[label("lower case")] LowerCase, /// "ON THE ORIGIN OF SPECIES" — Converts all letters to upper case. #[label("UPPER CASE")] UpperCase, /// "On The Origin Of Species" — Converts the first letter of every word to upper case. #[label("Capital Case")] CapitalCase, /// "On the Origin of Species" — Converts the first letter of significant words to upper case. #[label("Headline Case")] HeadlineCase, /// "On the origin of species" — Converts the first letter of every word to lower case, except the initial word which is made upper case. #[label("Sentence case")] SentenceCase, /// "on The Origin Of Species" — Converts the first letter of every word to upper case, except the initial word which is made lower case. #[label("camel Case")] CamelCase, } /// Constructs a string value which may be set to any plain text. #[node_macro::node(category("Value"))] fn string_value(_: impl Ctx, _primary: (), string: Item