cosmic-text/Cargo.toml
leyoda 63072bbe29 yoda: snap monospace cell width via Unicode East Asian Width
Replaces the previous heuristic (font_monospace_em_width.is_none() ⇒ 2
cells) which was reviewed as unsound: Arabic, dingbats, math symbols and
other narrow scripts pulled from non-monospace fallback fonts would all
have been forced to 2 cells. It also didn't handle ZWJ emoji clusters
or ambiguous-width chars correctly.

Proper fix, computed at shape time when `line: &str` is in scope:
- new ShapeGlyph.terminal_cells: u8 (0, 1 or 2)
- populated via unicode-width crate applied to the cluster text
  line[start..end] (harfrust path, uses UnicodeWidthStr) or to the
  single codepoint (no-font fallback path, UnicodeWidthChar)
- layout_to_buffer consumes it when match_mono_width is Some:
      x_advance = cells * mono_width
  instead of the previous round(x_advance / mono_width) * mono_width
  which produced variable cell counts for fallback glyphs.

Covers:
- ASCII + Latin        → width 1  (unchanged visual)
- CJK + fullwidth      → width 2  ✓
- Emoji (incl. ZWJ)    → width 2  ✓  (cluster text handles the ZWJ case)
- Arabic / Hebrew      → width 1  ✓  (was wrongly snapped to 2 before)
- Combining marks      → width 0  ✓  (zero-advance, matches terminals)
- Variation selectors  → width 0  ✓

Limitations: ambiguous-width chars (EAW=A) resolve to 1 via unicode-width
default; a 'cjk' ambiguous mode (unicode-width::UnicodeWidthChar::width_cjk)
could be exposed later as a Buffer flag if needed — not needed for typical
terminal use, matching most wcwidth implementations.

Based on review feedback from lionel@wopr.io on the initial heuristic patch.
2026-04-23 23:20:40 +02:00

89 lines
2.4 KiB
TOML

[package]
name = "cosmic-text"
description = "Pure Rust multi-line text handling"
version = "0.19.0"
authors = ["Jeremy Soller <jeremy@system76.com>"]
edition = "2021"
license = "MIT OR Apache-2.0"
documentation = "https://docs.rs/cosmic-text/latest/cosmic_text/"
repository = "https://github.com/pop-os/cosmic-text"
rust-version = "1.89"
[dependencies]
bitflags = "2.10.0"
core_maths = { version = "0.1.1", optional = true }
cosmic_undo_2 = { version = "0.2.0", optional = true }
fontdb = { version = "0.23", default-features = false }
harfrust = { version = "0.5.0", default-features = false }
hashbrown = { version = "0.17", optional = true, default-features = false }
libm = { version = "0.2.16", optional = true }
linebender_resource_handle = { version = "0.1.1", default-features = false }
log = "0.4.29"
modit = { version = "0.1.5", optional = true }
rangemap = "1.7.1"
rustc-hash = { version = "2.1.1", default-features = false }
self_cell = "1.2.2"
skrifa = { version = "0.40.0", default-features = false }
smol_str = { version = "0.3.2", default-features = false }
syntect = { version = "5.3.0", optional = true }
sys-locale = { version = "0.3.2", optional = true }
unicode-linebreak = "0.1.5"
unicode-script = "0.5.8"
unicode-segmentation = "1.12.0"
# Yoda: EAW-aware cell width for monospace/terminal rendering.
unicode-width = "0.2"
[dependencies.swash]
version = "0.2.6"
default-features = false
features = ["render", "scale"]
optional = true
[dependencies.unicode-bidi]
version = "0.3.18"
default-features = false
features = ["hardcoded-data"]
[features]
default = ["std", "swash", "fontconfig"]
fontconfig = ["fontdb/fontconfig", "std"]
monospace_fallback = []
no_std = ["hashbrown", "dep:libm", "skrifa/libm", "core_maths", "swash?/libm"]
peniko = []
shape-run-cache = []
std = [
"fontdb/memmap",
"fontdb/std",
"harfrust/std",
"linebender_resource_handle/std",
"skrifa/std",
"swash?/std",
"sys-locale",
"unicode-bidi/std",
]
vi = ["modit", "syntect", "cosmic_undo_2"]
wasm-web = ["sys-locale?/js"]
warn_on_missing_glyphs = []
[[bench]]
name = "layout"
harness = false
[[bench]]
name = "text_shaping_benchmarks"
harness = false
[workspace]
members = ["examples/*"]
[dev-dependencies]
tiny-skia = "0.11.4"
criterion = { version = "0.8.1", default-features = false, features = [
"cargo_bench_support",
] }
[profile.test]
opt-level = 1
[package.metadata.docs.rs]
features = ["vi"]