diff --git a/crates/engine/src/textedit.rs b/crates/engine/src/textedit.rs index 229c0825..beeb04eb 100644 --- a/crates/engine/src/textedit.rs +++ b/crates/engine/src/textedit.rs @@ -103,11 +103,23 @@ pub fn insert(s: &mut Session, text: &str) -> Result<()> { /// ends where the script changes (Han, hiragana, katakana and Hangul runs are words of their own in /// text without spaces). Combining marks and voicing marks belong to the char before them. fn word_class(chars: &[char], mut i: usize) -> u8 { - while i > 0 && chars.get(i).is_some_and(|c| matches!(*c as u32, 0x0300..=0x036F | 0x1AB0..=0x1AFF | 0x1DC0..=0x1DFF | 0x20D0..=0x20FF | 0x3099..=0x309A | 0xFE20..=0xFE2F)) { + while i > 0 + && chars.get(i).is_some_and( + |c| matches!(*c as u32, 0x0300..=0x036F | 0x1AB0..=0x1AFF | 0x1DC0..=0x1DFF | 0x20D0..=0x20FF | 0x3099..=0x309A | 0xFE20..=0xFE2F), + ) + { i -= 1; } match chars.get(i).copied() { - Some('\u{3005}' | '\u{3006}' | '\u{3007}' | '\u{3400}'..='\u{4DBF}' | '\u{4E00}'..='\u{9FFF}' | '\u{F900}'..='\u{FAFF}' | '\u{20000}'..='\u{3FFFF}') => 2, + Some( + '\u{3005}' + | '\u{3006}' + | '\u{3007}' + | '\u{3400}'..='\u{4DBF}' + | '\u{4E00}'..='\u{9FFF}' + | '\u{F900}'..='\u{FAFF}' + | '\u{20000}'..='\u{3FFFF}', + ) => 2, Some('\u{3041}'..='\u{309F}') => 3, Some('\u{30A0}'..='\u{30FF}' | '\u{31F0}'..='\u{31FF}' | '\u{FF66}'..='\u{FF9F}') => 4, Some('\u{1100}'..='\u{11FF}' | '\u{3130}'..='\u{318F}' | '\u{AC00}'..='\u{D7AF}') => 5, diff --git a/crates/text/src/behaviour_tests.rs b/crates/text/src/behaviour_tests.rs index 1769e2e2..04eff79e 100644 --- a/crates/text/src/behaviour_tests.rs +++ b/crates/text/src/behaviour_tests.rs @@ -340,4 +340,3 @@ fn a_cluster_is_shaped_with_one_face() { assert_eq!(faces.len(), 1, "{s:?}"); } } - diff --git a/crates/text/src/breaks.rs b/crates/text/src/breaks.rs index 37881a34..3f5458ea 100644 --- a/crates/text/src/breaks.rs +++ b/crates/text/src/breaks.rs @@ -21,19 +21,24 @@ enum Class { Other, } +/// Opening brackets and quotes (line-end prohibited). +const OPEN: &str = "([{‘“〈《「『【〔〖〘〚〝([{「"; +/// Closing brackets, quotes and punctuation, iteration marks, small kana and the prolonged sound +/// mark (line-start prohibited; the small katakana extensions U+31F0..U+31FF are a range below). +const CLOSE: &str = + ")]},.:;!?’”‥…‼⁇⁈⁉、。々〉》」』】〕〗〙〛〜〞〟〻ぁぃぅぇぉっゃゅょゎゕゖゝゞ゠ァィゥェォッャュョヮヵヶ・ーヽヾ!),.:;?]}。」、・ー"; + fn class(c: char) -> Class { + if c.is_ascii_alphanumeric() { + return Class::Other; + } + if OPEN.contains(c) { + return Class::Open; + } + if CLOSE.contains(c) || ('\u{31F0}'..='\u{31FF}').contains(&c) { + return Class::Close; + } match c { - '(' | '[' | '{' | '\u{2018}' | '\u{201C}' | '\u{3008}' | '\u{300A}' | '\u{300C}' | '\u{300E}' | '\u{3010}' | '\u{3014}' - | '\u{3016}' | '\u{3018}' | '\u{301A}' | '\u{301D}' | '\u{FF08}' | '\u{FF3B}' | '\u{FF5B}' | '\u{FF62}' => Class::Open, - ')' | ']' | '}' | ',' | '.' | ':' | ';' | '!' | '?' | '\u{2019}' | '\u{201D}' | '\u{2025}' | '\u{2026}' | '\u{203C}' - | '\u{2047}'..='\u{2049}' | '\u{3001}' | '\u{3002}' | '\u{3005}' | '\u{3009}' | '\u{300B}' | '\u{300D}' | '\u{300F}' - | '\u{3011}' | '\u{3015}' | '\u{3017}' | '\u{3019}' | '\u{301B}' | '\u{301C}' | '\u{301E}' | '\u{301F}' | '\u{303B}' - | '\u{3041}' | '\u{3043}' | '\u{3045}' | '\u{3047}' | '\u{3049}' | '\u{3063}' | '\u{3083}' | '\u{3085}' | '\u{3087}' - | '\u{308E}' | '\u{3095}' | '\u{3096}' | '\u{309D}' | '\u{309E}' | '\u{30A0}' | '\u{30A1}' | '\u{30A3}' | '\u{30A5}' - | '\u{30A7}' | '\u{30A9}' | '\u{30C3}' | '\u{30E3}' | '\u{30E5}' | '\u{30E7}' | '\u{30EE}' | '\u{30F5}' | '\u{30F6}' - | '\u{30FB}' | '\u{30FC}' | '\u{30FD}' | '\u{30FE}' | '\u{31F0}'..='\u{31FF}' | '\u{FF01}' | '\u{FF09}' | '\u{FF0C}' - | '\u{FF0E}' | '\u{FF1A}' | '\u{FF1B}' | '\u{FF1F}' | '\u{FF3D}' | '\u{FF5D}' | '\u{FF61}' | '\u{FF63}'..='\u{FF65}' - | '\u{FF70}' => Class::Close, ' ' | '\t' | '\u{3000}' | '\u{2002}'..='\u{200B}' => Class::Space, '-' | '/' | '\u{2010}' | '\u{2013}' | '\u{2014}' => Class::Hyphen, '\u{2E80}'..='\u{2FFF}' diff --git a/crates/text/src/lib.rs b/crates/text/src/lib.rs index b63255bf..96546152 100644 --- a/crates/text/src/lib.rs +++ b/crates/text/src/lib.rs @@ -6,17 +6,17 @@ //! fills [`Glyph::path`]s and the editor uses [`TextLayout::caret`] / [`TextLayout::hit`]. #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] +mod breaks; pub mod display; pub mod fontdb; mod layout; -mod breaks; mod onpath; +pub use breaks::grapheme_step; pub use fontdb::{ Axis, CRAFT_FONTS, Coords, Face, FontDb, NamedInstance, axis_label, db, face_with_char, family_names, family_styles, find_face, no_coords, scan_system_fonts, system_fonts_scanned, }; -pub use breaks::grapheme_step; pub use layout::{Decoration, Glyph, Line, TextLayout, layout}; pub use onpath::OnPath; diff --git a/crates/ui-egui/src/typography.rs b/crates/ui-egui/src/typography.rs index 996e1824..899bcd5d 100644 --- a/crates/ui-egui/src/typography.rs +++ b/crates/ui-egui/src/typography.rs @@ -439,7 +439,8 @@ fn type_settings( ui.add_space(6.0); let indent = common(doc, ids, |n| n.kind.text().map(|t| t.paragraph_indent.to_bits())).flatten().map(f64::from_bits); // Indent applies to start-aligned text only; elsewhere the field stays visible but off. - let start_aligned = common(doc, ids, |n| n.kind.text().map(|t| t.align_h != TextAlignH::Center && t.align_h != TextAlignH::Justified)).flatten(); + let start_aligned = + common(doc, ids, |n| n.kind.text().map(|t| t.align_h != TextAlignH::Center && t.align_h != TextAlignH::Justified)).flatten(); ui.horizontal(|ui| { ui.add_enabled_ui(start_aligned != Some(false), |ui| { ui.add_sized(vec2(110.0, FIELD_H), egui::Label::new(egui::RichText::new(tr("Paragraph indent")).font(regular(11.0)).color(t.text)));