Merge pull request #7390 from dotCooCoo/fix/hyphenation-exceptions-relayout

Type: hyphenated type lays out again when the hyphenation exceptions change
This commit is contained in:
BFlat
2026-11-14 13:41:57 +07:00
committed by GitHub
19 changed files with 163 additions and 60 deletions
+5 -4
View File
@@ -369,12 +369,13 @@ impl DocState {
/// menu asks on every frame it is open, and measuring builds the solids of 3D effects, so the
/// answer is kept for this version of the document and those artboards, and a change of the
/// selection or the view keeps it. It is measured again when what the measure reads outside the
/// document changes: the installed plug-ins, whose effects draw art, and the installed fonts,
/// which set the bounds of all type.
/// document changes: the installed plug-ins, whose effects draw art, and the installed fonts and
/// the hyphenation exceptions, which set the bounds of all type.
pub fn any_art_to_fit(&self, indices: &[usize]) -> bool {
self.any_art_to_fit_with(indices, (vectorcraft_plugins::registry::revision(), vectorcraft_text::FontDb::global().generation()))
self.any_art_to_fit_with(indices, (vectorcraft_plugins::registry::revision(), vectorcraft_text::layout_inputs()))
}
/// [`Self::any_art_to_fit`], with `outside` the generations of the plug-ins and the fonts.
/// [`Self::any_art_to_fit`], with `outside` the generations of the plug-ins and the layout inputs
/// ([`vectorcraft_text::layout_inputs`]).
pub(crate) fn any_art_to_fit_with(&self, indices: &[usize], outside: (u64, u64)) -> bool {
// Measuring runs renderer and plug-in code; should it ever ask again while measuring, it
// is measured without being kept rather than borrowed twice.
+4 -2
View File
@@ -25,9 +25,11 @@ impl Session {
// Clicks find an image's own fills and strokes where their effects draw them.
vectorcraft_doc::hit::set_image_paint(vectorcraft_render::effects::image_paint);
// Clicks find a blend's steps and an envelope's distorted art where the canvas draws them,
// evaluated again when the fonts or the plug-ins change.
// evaluated again when the fonts, the hyphenation exceptions or the plug-ins change.
vectorcraft_doc::hit::set_live_art(vectorcraft_render::effects::expand_live, || {
vectorcraft_text::FontDb::global().generation().rotate_left(32) ^ vectorcraft_render::effects::plugin_revision()
let mut h = std::hash::DefaultHasher::new();
std::hash::Hash::hash(&(vectorcraft_text::layout_inputs(), vectorcraft_render::effects::plugin_revision()), &mut h);
std::hash::Hasher::finish(&h)
});
// Envelopes map type from the bounds of its glyph outlines, underlines and strikethroughs.
vectorcraft_text::register_envelope_ink();
+2 -2
View File
@@ -110,8 +110,8 @@ pub(crate) struct PlacedEntry {
pub(crate) art: std::sync::Weak<Node>,
/// The art placed, or `None` for art placed again in every frame ([`depends_on_document`]).
pub(crate) placed: Option<Arc<Node>>,
/// The font generation ([`vectorcraft_text::FontDb::generation`]) the art was placed in, when
/// it holds type: the art is placed again when the fonts change.
/// The layout inputs ([`vectorcraft_text::layout_inputs`]) the art was placed with, when it
/// holds type: the art is placed again when the fonts or the hyphenation exceptions change.
pub(crate) fonts: Option<u64>,
/// The 9-slice map the art was placed through ([`Renderer::sliced_art`]), or `None` for art
/// placed by the transform alone ([`Renderer::placed_art`]). An entry is used only by the
+2 -2
View File
@@ -412,7 +412,7 @@ impl Renderer {
/// brush library it reads; for images and placed documents their pixels and files, the
/// layer's Dim Images and the options they are drawn with (with
/// [`crate::RenderOptions::kept_images_only`], also which pixels and undecodable notes the
/// image store holds); for type the fonts, whether overprinting shows
/// image store holds); for type the fonts and the hyphenation exceptions, whether overprinting shows
/// ([`crate::proof::overprints`]) and Document Setup's substitution highlights; and the
/// plug-ins installed, with the names of the document's swatches and patterns and its pattern
/// definitions (a plug-in's painted pieces can name them), when an object in it has a plug-in
@@ -457,7 +457,7 @@ impl Renderer {
f.doc.setup.highlight_substituted_glyphs,
o.highlight_alternate_glyphs,
));
(vectorcraft_text::FontDb::global().generation(), crate::proof::overprints(f.opts), highlights).hash(&mut h);
(vectorcraft_text::layout_inputs(), crate::proof::overprints(f.opts), highlights).hash(&mut h);
}
if reads.plugins || (doc.plugins && (reads.symbols || reads.patterns)) {
(effects::plugin_revision(), doc.names, doc.patterns).hash(&mut h);
+2 -2
View File
@@ -65,8 +65,8 @@ pub struct Renderer {
/// The latest cached glyph geometry for each text object id. A move changes the node's Arc
/// but not its glyphs in text space, so the old geometry can still be used after comparison.
text_ids: PtrMap<NodeId, usize>,
/// The [`vectorcraft_text::FontDb::generation`] `texts` was laid out with: type lays out
/// again when fonts are added or rescanned.
/// The [`vectorcraft_text::layout_inputs`] `texts` was laid out with: type lays out again
/// when fonts are added or rescanned and when the hyphenation exceptions change.
text_fonts: u64,
/// Clip regions per clipping path (see [`Self::clip_of`]).
clips: FrameCache<usize, ClipEntry>,
+4 -3
View File
@@ -127,8 +127,9 @@ struct Walked {
}
impl Walked {
/// The [`resources_key`] of the node walked: the walk's hash, the font generation when it found
/// type or a placed file, the plug-in revision and the hash of the document's swatch and pattern
/// The [`resources_key`] of the node walked: the walk's hash, the layout inputs (fonts and
/// hyphenation exceptions, [`vectorcraft_text::layout_inputs`]) when it found type or a placed
/// file, the plug-in revision and the hash of the document's swatch and pattern
/// names (`names`) when it found a plug-in effect, and the color settings `cms`. Whether a
/// plug-in paints its pieces depends on the plug-ins installed, so the names count for every
/// plug-in effect.
@@ -136,7 +137,7 @@ impl Walked {
let mut h = DefaultHasher::new();
self.hash.hash(&mut h);
if self.text {
vectorcraft_text::FontDb::global().generation().hash(&mut h);
vectorcraft_text::layout_inputs().hash(&mut h);
}
if self.plugins {
crate::effects::plugin_revision().hash(&mut h);
+4 -3
View File
@@ -39,7 +39,8 @@ type TileKey = (String, u64, Ink, bool);
/// What a tile was drawn from besides its pattern's own art, for art that shows more than itself:
/// the art of every pattern and symbol and the pixels of every image in the document, the
/// document's other data (which holds its brushes), and the fonts. Editing a symbol leaves the art
/// document's other data (which holds its brushes), and the fonts and hyphenation exceptions
/// ([`vectorcraft_text::layout_inputs`]). Editing a symbol leaves the art
/// of a pattern that shows it as it was, so the art alone kept the tile from before (#1723). Held
/// weakly: a weak reference keeps the allocation, so no other value gets one of these addresses
/// while the tile is kept.
@@ -66,13 +67,13 @@ impl Deps {
art: Self::art(doc).map(Arc::downgrade).collect(),
pixels: Self::pixels(doc).map(Arc::downgrade).collect(),
unknown: vectorcraft_doc::Shared::downgrade(&doc.unknown),
fonts: vectorcraft_text::FontDb::global().generation(),
fonts: vectorcraft_text::layout_inputs(),
}
}
/// Does `doc` hold what the tile was drawn from?
fn current(&self, doc: &Document) -> bool {
self.fonts == vectorcraft_text::FontDb::global().generation()
self.fonts == vectorcraft_text::layout_inputs()
&& std::ptr::eq(self.unknown.as_ptr(), &*doc.unknown)
&& self.art.iter().map(Weak::as_ptr).eq(Self::art(doc).map(Arc::as_ptr))
&& self.pixels.iter().map(Weak::as_ptr).eq(Self::pixels(doc).map(Arc::as_ptr))
+5 -5
View File
@@ -55,7 +55,7 @@ impl Renderer {
/// objects) hit for them. Other art is placed again in every frame, and so is art that holds
/// symbol instances, type with inline graphics, images, placed documents or raster effects
/// ([`depends_on_document`]), and art that the cache has no room for. Art that holds type is
/// placed again when the fonts change.
/// placed again when the fonts or the hyphenation exceptions change.
pub(super) fn placed_art(&mut self, art: &Arc<Node>, xf: Affine, scale_strokes: bool, inst: Option<&Node>) -> (Arc<Node>, bool) {
let kept_kind = matches!(art.kind, NodeKind::Group { .. } | NodeKind::Layer { .. }) || vectorcraft_doc::live::is_live(art);
// An instance with appearance changes of its own isn't kept: the key doesn't tell them apart.
@@ -67,7 +67,7 @@ impl Renderer {
let key = (Arc::as_ptr(art) as usize, xf.as_coeffs().map(f64::to_bits), scale_strokes, stain);
if let Some(e) = self.placed_arts.get(&key, self.stamp)
&& std::ptr::eq(e.art.as_ptr(), Arc::as_ptr(art))
&& e.fonts.is_none_or(|g| g == vectorcraft_text::FontDb::global().generation())
&& e.fonts.is_none_or(|g| g == vectorcraft_text::layout_inputs())
&& e.slice.is_none()
{
return match &e.placed {
@@ -106,7 +106,7 @@ impl Renderer {
if keeps
&& let Some(e) = self.placed_arts.get(&key, self.stamp)
&& std::ptr::eq(e.art.as_ptr(), Arc::as_ptr(art))
&& e.fonts.is_none_or(|g| g == vectorcraft_text::FontDb::global().generation())
&& e.fonts.is_none_or(|g| g == vectorcraft_text::layout_inputs())
&& e.slice.as_ref() == Some(map)
{
match e.placed.clone() {
@@ -134,7 +134,7 @@ impl Renderer {
fn keep_placed(&mut self, key: PlacedKey, art: &Arc<Node>, placed: &Arc<Node>, slice: Option<vectorcraft_doc::NineSliceMap>) -> bool {
let kept = (!depends_on_document(art)).then(|| placed.clone());
let keeps = kept.is_some();
let fonts = (keeps && holds_type(art)).then(|| vectorcraft_text::FontDb::global().generation());
let fonts = (keeps && holds_type(art)).then(vectorcraft_text::layout_inputs);
// The bytes count the key and the entry, the art's allocation that the weak reference
// keeps, and the kept art.
let bytes = size_of::<(PlacedKey, PlacedEntry, Node)>() + kept.as_deref().map_or(0, lru::node_bytes);
@@ -150,7 +150,7 @@ impl Renderer {
if !matches!(&a.kind, NodeKind::Text(own) if std::ptr::eq(&**own, t)) {
return (Arc::new(text_geom(t)), false);
}
let fonts = vectorcraft_text::FontDb::global().generation();
let fonts = vectorcraft_text::layout_inputs();
if fonts != self.text_fonts {
self.texts.clear();
self.text_ids.clear();
+78 -11
View File
@@ -3,16 +3,16 @@
use std::sync::{PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard};
use vectorcraft_doc::text::{CharStyle, TextObject};
use vectorcraft_doc::text::{CharStyle, TextKind, TextObject};
use vectorcraft_doc::{Node, NodeKind};
use vectorcraft_geom::Point;
use vectorcraft_geom::{PathData, Point};
use crate::*;
/// Taken to add fonts, or to look up an installed one by name (`FontDb::face` loads it, which
/// changes the fonts too). Renderers lay type out again when the fonts change, so tests of what
/// they keep between frames hold it shared ([`fonts_steady`]) and the tests that change the fonts
/// hold it alone ([`adding_fonts`]).
/// Taken to add fonts, to look up an installed one by name (`FontDb::face` loads it, which
/// changes the fonts too) or to change the hyphenation exceptions. Renderers lay type out again
/// when the fonts or the exceptions change, so tests of what they keep between frames hold it
/// shared ([`fonts_steady`]) and the tests that change them hold it alone ([`adding_fonts`]).
static FONTS: RwLock<()> = RwLock::new(());
/// No fonts are added by these tests until the guard is dropped.
@@ -29,14 +29,12 @@ pub(crate) fn adding_fonts() -> RwLockWriteGuard<'static, ()> {
/// What `step` returns from a run in which the fonts stayed the same. The fonts can change while
/// [`fonts_steady`] is held: the first lookup by family name catalogs the installed fonts, and
/// resolving a cataloged family, or a character no loaded font has, loads a face. `step` runs up
/// to 100 times, until [`vectorcraft_text::FontDb::generation`] is the same after a run as before
/// it.
/// to 100 times, until [`vectorcraft_text::layout_inputs`] is the same after a run as before it.
pub(crate) fn with_fonts_unchanged<T>(mut step: impl FnMut() -> T) -> T {
let db = vectorcraft_text::FontDb::global();
for _ in 0..100 {
let before = db.generation();
let before = vectorcraft_text::layout_inputs();
let out = step();
if db.generation() == before {
if vectorcraft_text::layout_inputs() == before {
return out;
}
}
@@ -466,6 +464,75 @@ fn enveloped_type_under_effects_redraws_when_its_font_becomes_available() {
}
}
/// Hyphenated area type redraws with its new line breaks when Preferences › Hyphenation ›
/// Exceptions change: its glyph geometry, the pixels of its raster effects, the art drawn for
/// geometry effects, the art kept for a symbol instance and an envelope's evaluation are made
/// again.
#[test]
fn hyphenated_type_redraws_when_the_exceptions_change() {
let _adding = adding_fonts();
struct Clear;
impl Drop for Clear {
fn drop(&mut self) {
vectorcraft_text::set_hyphenation_exceptions("");
}
}
let _clear = Clear;
use vectorcraft_doc::Effect;
let moved = || Effect { id: "distort.transform".into(), params: serde_json::json!({"moveH": 0.0}), visible: true };
const WORDS: &str = "Extraordinarily comprehensive typographical considerations notwithstanding everything.";
const NEVER: &str = "extraordinarily, comprehensive, typographical, considerations, notwithstanding";
let cases = ["the area type", "the type with a drop shadow", "the group with a Transform effect", "the symbol instance", "the envelope"];
for (i, what) in cases.into_iter().enumerate() {
vectorcraft_text::set_hyphenation_exceptions("");
let mut d = Document::new(300.0, 150.0);
let mut t = TextObject::point(Point::ZERO, WORDS, CharStyle { size: 14.0, ..Default::default() });
t.kind = TextKind::Area { frame: PathData::from_bezpath(&Rect::new(10.0, 10.0, 130.0, 145.0).to_path(0.1)) };
t.xf = Affine::IDENTITY;
t.para.hyphenate = true;
let mut object = Node::new(d.alloc_id(), NodeKind::Text(Box::new(t)));
match i {
0 => {}
1 => {
let params = serde_json::json!({"x": 4, "y": 4, "blur": 1, "opacity": 100});
object.appearance.effects.push(Effect { id: "stylize.dropShadow".into(), params, visible: true });
}
2 => {
object = Node::group(d.alloc_id(), vec![Arc::new(object)]);
object.appearance.effects.push(moved());
}
3 => {
object.appearance.effects.push(moved());
let art = Node::group(d.alloc_id(), vec![Arc::new(object)]);
d.symbols.push(vectorcraft_doc::Symbol { dynamic: false, name: "Words".into(), art: Arc::new(art) });
object = Node::new(d.alloc_id(), NodeKind::SymbolInstance { symbol: "Words".into(), xf: Affine::IDENTITY });
}
_ => {
let kind = vectorcraft_doc::EnvelopeKind::Warp { style: "arc".into(), bend: 30.0, h: 0.0, v: 0.0, horizontal: true, version: 0 };
let envelope = NodeKind::Envelope {
content: vec![Arc::new(object)],
kind: Box::new(kind),
fidelity: 50.0,
editing: false,
options: Default::default(),
frame: Box::new(Affine::IDENTITY),
};
object = Node::new(d.alloc_id(), envelope);
}
}
let l = d.layers[0].id;
d.insert(Some(l), 0, object).unwrap();
let opts = RenderOptions { background: Some([255, 255, 255, 255]), ..Default::default() };
let mut r = Renderer::new();
let before = r.render(&d, 300, 150, Affine::IDENTITY, &opts).pixels;
vectorcraft_text::set_hyphenation_exceptions(NEVER);
let after = r.render(&d, 300, 150, Affine::IDENTITY, &opts).pixels;
let fresh = Renderer::new().render(&d, 300, 150, Affine::IDENTITY, &opts).pixels;
assert_ne!(before, fresh, "{what}: the words in the list are not broken");
assert!(after == fresh, "{what}: drawn as a new renderer draws it");
}
}
/// Without an engine session, a renderer maps an envelope of type from its glyph outline bounds,
/// and measures the type again when its font becomes available.
#[test]
+4 -4
View File
@@ -320,13 +320,13 @@ fn kept_glyphs(r: &mut Renderer, d: &Document) -> Vec<Option<Arc<TextGeom>>> {
.collect()
}
/// `f`'s result from a run during which the fonts did not change: other tests add fonts, and the
/// renderer lays all type out again when the fonts change.
/// `f`'s result from a run during which the fonts and the hyphenation exceptions did not change:
/// other tests change them, and the renderer lays all type out again when they change.
fn fonts_unchanged<T>(mut f: impl FnMut() -> T) -> T {
loop {
let fonts = vectorcraft_text::FontDb::global().generation();
let inputs = vectorcraft_text::layout_inputs();
let t = f();
if vectorcraft_text::FontDb::global().generation() == fonts {
if vectorcraft_text::layout_inputs() == inputs {
return t;
}
}
+1 -1
View File
@@ -859,7 +859,7 @@ fn type_outside_its_frame_is_drawn() {
fn a_pan_draws_the_type_from_the_kept_pixels() {
let _fonts = crate::tests_fontchange::fonts_steady();
let opts = RenderOptions::default();
let fonts = || vectorcraft_text::FontDb::global().generation();
let fonts = vectorcraft_text::layout_inputs;
// How many type objects a renderer drew offscreen for `b` in `second` after drawing `a` in
// `first`. The second frame's pixels are a new renderer's, each channel within `near`. The
// frames are drawn again while the fonts change between them (other tests load fonts, and the
+5 -5
View File
@@ -502,16 +502,16 @@ mod tests {
}
/// Process-wide store used by `prefs.set` / restore. Each change makes envelopes measure type
/// again.
/// again, and caches of layouts lay type out again.
#[test]
fn set_hyphenation_exceptions_round_trips_the_source_string() {
let inputs = crate::layout::envelope_ink_inputs();
let inputs = crate::layout_inputs();
set_hyphenation_exceptions("typography, hap-pen");
assert_eq!(hyphenation_exceptions(), "typography, hap-pen");
let changed = crate::layout::envelope_ink_inputs();
assert_ne!(changed, inputs, "the envelope inputs change with the exceptions");
let changed = crate::layout_inputs();
assert_ne!(changed, inputs, "the layout inputs change with the exceptions");
set_hyphenation_exceptions("");
assert!(hyphenation_exceptions().is_empty());
assert_ne!(crate::layout::envelope_ink_inputs(), changed, "and again when they are cleared");
assert_ne!(crate::layout_inputs(), changed, "and again when they are cleared");
}
}
+1 -3
View File
@@ -46,9 +46,7 @@ pub(crate) use driver::{fit_passes, layout_faces_per_call};
pub(crate) use on_path::{layout_trying_each_start, path_arrangements};
use vertical::{TcyStyle, stands_upright, tate_chu_yoko, upright_cell, upright_centre, upright_extent, whole_character_widths};
#[cfg(test)]
pub(crate) use driver::envelope_ink_inputs;
pub use driver::{envelope_ink, register_envelope_ink};
pub use driver::{envelope_ink, layout_inputs, register_envelope_ink};
const EPS: f64 = 1e-6;
+5 -4
View File
@@ -191,12 +191,13 @@ pub fn envelope_ink(t: &TextObject, m: Affine, projection: Option<(vectorcraft_g
/// Make envelopes map type from [`envelope_ink`], measured again when the fonts or the hyphenation
/// exceptions change (the first registration stays, see `vectorcraft_doc::live::set_type_ink`).
pub fn register_envelope_ink() {
vectorcraft_doc::live::set_type_ink(envelope_ink, envelope_ink_inputs);
vectorcraft_doc::live::set_type_ink(envelope_ink, layout_inputs);
}
/// A number that changes whenever what [`envelope_ink`] reads besides the type changes: the
/// installed fonts and the hyphenation exceptions.
pub(crate) fn envelope_ink_inputs() -> u64 {
/// A number that changes when the fonts ([`FontDb::generation`]) or the hyphenation exceptions
/// (Preferences › Hyphenation › Exceptions) change. Caches of layouts made with the global font
/// database, and of art made from them, key on it.
pub fn layout_inputs() -> u64 {
FontDb::global().generation().rotate_left(32) ^ crate::hyphen::exceptions_generation()
}
+1 -1
View File
@@ -47,7 +47,7 @@ pub use fontdb::{
user_font_dirs,
};
pub use hyphen::{hyphenation_exceptions, set_hyphenation_exceptions};
pub use layout::{envelope_ink, register_envelope_ink};
pub use layout::{envelope_ink, layout_inputs, register_envelope_ink};
use std::sync::Arc;
pub use svg_glyph::{SvgGlyphReader, set_svg_glyph_reader};
+3 -3
View File
@@ -134,7 +134,7 @@ fn text_hash(s: &str) -> u64 {
h.finish()
}
/// The edited text's layout, keyed by the font database's generation and the text.
/// The edited text's layout, keyed by [`vectorcraft_text::layout_inputs`] and the text.
type CachedLayout = ((u64, TextObject), Arc<TextLayout>);
/// Process-global Type-tool edit-session counter: the IME target's replacement epoch.
@@ -281,9 +281,9 @@ impl TypeTool {
}
/// Layout of `t`, cached by content (overlays run every frame) and by the fonts available (the
/// web build adds fonts after startup).
/// web build adds fonts after startup) and the hyphenation exceptions.
fn layout(&self, t: &TextObject) -> Arc<TextLayout> {
let fonts = FontDb::global().generation();
let fonts = vectorcraft_text::layout_inputs();
let mut c = self.cache.lock().unwrap_or_else(|e| e.into_inner());
if let Some(((g, k), l)) = c.as_ref()
&& *g == fonts
+3 -2
View File
@@ -167,7 +167,8 @@ pub(crate) fn port_near(cx: &ToolContext, p: Point) -> Option<(NodeId, Port, f64
}
/// Which selected text overflows, for [`thread_ports`]: each text object's layout verdict kept
/// with the object and the fonts it was made from, as overlays run every frame.
/// with the object and the layout inputs it was made with (fonts and hyphenation exceptions,
/// [`vectorcraft_text::layout_inputs`]), as overlays run every frame.
#[derive(Default)]
pub struct PortMarks {
seen: std::collections::HashMap<NodeId, ((u64, TextObject), bool)>,
@@ -178,7 +179,7 @@ pub struct PortMarks {
/// port whether text is hidden by the size of its frame, whatever tool is active. None when more
/// than [`PORTS_MAX`] of them are selected.
pub fn thread_ports(marks: &mut PortMarks, cx: &ToolContext) -> Vec<Overlay> {
let fonts = FontDb::global().generation();
let fonts = vectorcraft_text::layout_inputs();
let mut o = Vec::new();
let mut kept = std::collections::HashMap::new();
if !ports_shown(cx.doc, &cx.selection.objects) {
+30
View File
@@ -1538,6 +1538,36 @@ fn selected_type_shows_its_ports_in_each_state() {
assert!(thread_ports(&mut marks, &cx(&d, &sel, &p)).is_empty(), "only selected type shows them");
}
/// The out port shows whether the text fits as the hyphenation exceptions lay it out now: when
/// Preferences › Hyphenation › Exceptions change, the selected type is laid out again.
#[test]
fn the_out_port_follows_the_hyphenation_exceptions() {
struct Clear;
impl Drop for Clear {
fn drop(&mut self) {
vectorcraft_text::set_hyphenation_exceptions("");
}
}
let _clear = Clear;
const WORDS: &str = "Extraordinarily comprehensive typographical considerations notwithstanding everything.";
const NEVER: &str = "extraordinarily, comprehensive, typographical, considerations, notwithstanding";
let (mut d, id) = crate::testutil::doc_with_area_type();
// A 70 by 170 pt frame holds the hyphenated text, and the text without those breaks overflows
// it.
let Some(NodeKind::Text(t)) = d.node_mut(id).map(|n| &mut n.kind) else { panic!("not text") };
t.runs = vec![TextRun::new(WORDS, CharStyle { size: 14.0, ..Default::default() })];
t.para.hyphenate = true;
t.kind = TextKind::Area { frame: vectorcraft_geom::PathData::from_bezpath(&Rect::new(0.0, 0.0, 70.0, 170.0).to_path(0.1)) };
let (mut sel, p) = (Selection::default(), paint());
sel.set([id]);
let mut marks = PortMarks::default();
let red = |ov: &[Overlay]| ov.iter().filter(|o| matches!(o, Overlay::Path { color: [230, 30, 30], .. })).count();
vectorcraft_text::set_hyphenation_exceptions("");
assert_eq!(red(&thread_ports(&mut marks, &cx(&d, &sel, &p))), 0, "the hyphenated text fits");
vectorcraft_text::set_hyphenation_exceptions(NEVER);
assert_eq!(red(&thread_ports(&mut marks, &cx(&d, &sel, &p))), 1, "the text without those breaks does not");
}
/// Past [`PORTS_MAX`] selected frames (select all of an opened PDF) no ports are drawn or clicked:
/// they would bury the canvas in boxes; up to it every frame has its two.
#[test]
+4 -3
View File
@@ -99,7 +99,7 @@ struct Drag {
began: bool,
}
/// The type object's layout, keyed by the font database's generation and the object.
/// The type object's layout, keyed by [`vectorcraft_text::layout_inputs`] and the object.
type CachedLayout = ((u64, TextObject), Arc<TextLayout>);
#[derive(Default)]
@@ -196,9 +196,10 @@ fn char_box(t: &TextObject, lay: &TextLayout, gi: usize) -> Option<CharBox> {
}
impl TouchTypeTool {
/// Layout of `t`, cached by content (overlays run every frame) and by the fonts available.
/// Layout of `t`, cached by content (overlays run every frame), by the fonts available and by
/// the hyphenation exceptions.
fn layout(&self, t: &TextObject) -> Arc<TextLayout> {
let fonts = FontDb::global().generation();
let fonts = vectorcraft_text::layout_inputs();
let mut c = self.cache.lock().unwrap_or_else(|e| e.into_inner());
if let Some(((g, k), l)) = c.as_ref()
&& *g == fonts