forked from 1b-agent/opencadstudio-upstream
style: apply cargo fmt across workspace
This commit is contained in:
1 parent
85710e5454
commit
9cf9ff2c5c
531 files changed
+57740
-33906
No files matched your search
@@ -8,8 +8,8 @@
|
||||
|
||||
use crate::entities::text_support::resolve_dxf_special_chars;
|
||||
use crate::io::linetypes::{ComplexLt, LtSegment};
|
||||
use crate::scene::text::lff;
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
use crate::scene::text::lff;
|
||||
|
||||
// ── Public entry point ────────────────────────────────────────────────────
|
||||
|
||||
@@ -271,18 +271,12 @@ pub fn apply_along(
|
||||
// layout_glyph_quads), collected for the text wire.
|
||||
if let Ok(mut atlas) = crate::scene::text::sdf_atlas::text_atlas().lock() {
|
||||
let (quads, pen_adv) = crate::scene::text::glyph_quads::layout_glyph_quads(
|
||||
&mut atlas,
|
||||
*tx_scale,
|
||||
fwd_angle,
|
||||
1.0, // width_factor
|
||||
&mut atlas, *tx_scale, fwd_angle, 1.0, // width_factor
|
||||
0.0, // oblique
|
||||
// tracking = 1.0 applies the font's natural
|
||||
// letter_spacing; 0.0 zeroes it and cramps the glyphs
|
||||
// (every other text path passes 1.0).
|
||||
1.0,
|
||||
style,
|
||||
false,
|
||||
&resolved,
|
||||
1.0, style, false, &resolved,
|
||||
);
|
||||
crate::scene::pipeline::text_gpu::push_glyph_vertices(
|
||||
&mut text_verts,
|
||||
@@ -300,11 +294,7 @@ pub fn apply_along(
|
||||
if !visible.is_empty() {
|
||||
searchable.push(SearchableTextRun {
|
||||
text: visible,
|
||||
origin: [
|
||||
insert[0] as f64,
|
||||
insert[1] as f64,
|
||||
insert[2] as f64,
|
||||
],
|
||||
origin: [insert[0] as f64, insert[1] as f64, insert[2] as f64],
|
||||
height: *tx_scale,
|
||||
rotation: fwd_angle,
|
||||
color,
|
||||
@@ -365,9 +355,9 @@ pub fn apply_along(
|
||||
plinegen: true,
|
||||
fill_tris: vec![],
|
||||
fill_tris_low: Vec::new(),
|
||||
|
||||
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Embedded linetype text (SDF): one extra wire carrying the glyph quads,
|
||||
|
||||
@@ -98,7 +98,8 @@ impl Face {
|
||||
let is_builtin = lff::is_builtin(font_name);
|
||||
let has_sys = sysfont::has_family(font_name);
|
||||
if !is_builtin && has_sys {
|
||||
let canonical = sysfont::canonical_family_name(font_name).unwrap_or_else(|| font_name.to_string());
|
||||
let canonical =
|
||||
sysfont::canonical_family_name(font_name).unwrap_or_else(|| font_name.to_string());
|
||||
let word = ttf_glyph::glyph(&canonical, ' ')
|
||||
.map(|g| g.advance)
|
||||
// Fall back to a sensible blank-width if the font has no space.
|
||||
@@ -255,8 +256,7 @@ mod tests {
|
||||
.find(|f| ttf_glyph::glyph(f, 'A').is_some())
|
||||
.expect("a system family with an 'A'");
|
||||
assert!(matches!(Face::resolve(fam), Face::Ttf { .. }));
|
||||
let (strokes, _) =
|
||||
lff::tessellate_text_ex([0.0, 0.0], 10.0, 0.0, 1.0, 0.0, fam, "ABC");
|
||||
let (strokes, _) = lff::tessellate_text_ex([0.0, 0.0], 10.0, 0.0, 1.0, 0.0, fam, "ABC");
|
||||
assert!(!strokes.is_empty(), "TTF run produced no strokes");
|
||||
}
|
||||
|
||||
@@ -310,7 +310,7 @@ mod tests {
|
||||
eprintln!("no system fonts; skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Arial and Cambria are typical on Windows/Mac/Linux
|
||||
for test_name in &["arial", "ARIAL", "ARIALN", "cambria"] {
|
||||
let resolved = Face::resolve(test_name);
|
||||
@@ -318,14 +318,18 @@ mod tests {
|
||||
Face::Ttf { family, .. } => {
|
||||
assert!(
|
||||
family == "Arial" || family == "Arial Narrow" || family == "Cambria",
|
||||
"Resolved to unexpected family name: {}", family
|
||||
"Resolved to unexpected family name: {}",
|
||||
family
|
||||
);
|
||||
}
|
||||
Face::Lff(_) => {
|
||||
// It's possible some test environments don't have Arial or Cambria,
|
||||
// but if they are present in sysfont::families() (or we can fallback),
|
||||
// they should resolve to Ttf. If they aren't installed, Lff fallback is accepted.
|
||||
eprintln!("Font {} resolved to Lff (probably not installed)", test_name);
|
||||
eprintln!(
|
||||
"Font {} resolved to Lff (probably not installed)",
|
||||
test_name
|
||||
);
|
||||
}
|
||||
Face::Shx { .. } | Face::WithBig { .. } => {
|
||||
// A system TTF name never resolves to an SHX shape font
|
||||
@@ -336,8 +340,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[test]
|
||||
fn big_font_pairing_routes_ideographs_and_leaves_latin_alone() {
|
||||
// Without a real big-font file the pairing is dropped: the primary
|
||||
|
||||
@@ -225,8 +225,7 @@ mod tests {
|
||||
#[test]
|
||||
fn space_emits_no_quad_but_advances() {
|
||||
let mut atlas = GlyphAtlas::new(512, 512);
|
||||
let (ab, _) =
|
||||
layout_glyph_quads(&mut atlas, 10.0, 0.0, 1.0, 0.0, 1.0, "txt", false, "AA");
|
||||
let (ab, _) = layout_glyph_quads(&mut atlas, 10.0, 0.0, 1.0, 0.0, 1.0, "txt", false, "AA");
|
||||
let (a_sp_a, _) =
|
||||
layout_glyph_quads(&mut atlas, 10.0, 0.0, 1.0, 0.0, 1.0, "txt", false, "A A");
|
||||
assert_eq!(a_sp_a.len(), 2, "space produces no quad");
|
||||
@@ -270,8 +269,7 @@ mod tests {
|
||||
let mut atlas = GlyphAtlas::new(512, 512);
|
||||
let (small, _) =
|
||||
layout_glyph_quads(&mut atlas, 10.0, 0.0, 1.0, 0.0, 1.0, "txt", false, "A");
|
||||
let (big, _) =
|
||||
layout_glyph_quads(&mut atlas, 20.0, 0.0, 1.0, 0.0, 1.0, "txt", false, "A");
|
||||
let (big, _) = layout_glyph_quads(&mut atlas, 20.0, 0.0, 1.0, 0.0, 1.0, "txt", false, "A");
|
||||
let extent = |q: &GlyphQuad| {
|
||||
let (mut lo, mut hi) = ([f32::MAX; 2], [f32::MIN; 2]);
|
||||
for p in &q.corners {
|
||||
@@ -295,12 +293,16 @@ mod tests {
|
||||
let (plain, plain_adv) =
|
||||
layout_glyph_quads(&mut atlas, 10.0, 0.0, 1.0, 0.0, 1.0, "txt", false, "AB");
|
||||
// `\L…\l` underlines the run — same glyph quads plus one decoration quad.
|
||||
let (under, under_adv) =
|
||||
layout_glyph_quads(&mut atlas, 10.0, 0.0, 1.0, 0.0, 1.0, "txt", false, "\\LAB\\l");
|
||||
let (under, under_adv) = layout_glyph_quads(
|
||||
&mut atlas, 10.0, 0.0, 1.0, 0.0, 1.0, "txt", false, "\\LAB\\l",
|
||||
);
|
||||
assert_eq!(under.len(), plain.len() + 1, "underline adds one quad");
|
||||
// The decoration quad samples the atlas's solid texel (degenerate UV).
|
||||
let deco = under.last().unwrap();
|
||||
assert_eq!(deco.uv_min, deco.uv_max, "decoration quad uses the solid UV");
|
||||
assert_eq!(
|
||||
deco.uv_min, deco.uv_max,
|
||||
"decoration quad uses the solid UV"
|
||||
);
|
||||
assert_eq!(deco.uv_min, atlas.solid_uv());
|
||||
// Decorations don't move the pen: same advance either way, and the
|
||||
// advance covers both glyphs (positive, wider than one glyph).
|
||||
@@ -328,8 +330,7 @@ mod tests {
|
||||
(wide / base - 2.0).abs() < 0.05,
|
||||
"double width factor ~ double advance: {wide} vs {base}"
|
||||
);
|
||||
let (_, empty) =
|
||||
layout_glyph_quads(&mut atlas, 10.0, 0.0, 1.0, 0.0, 1.0, "txt", false, "");
|
||||
let (_, empty) = layout_glyph_quads(&mut atlas, 10.0, 0.0, 1.0, 0.0, 1.0, "txt", false, "");
|
||||
assert_eq!(empty, 0.0, "empty text advances nothing");
|
||||
}
|
||||
}
|
||||
+334
-67
@@ -24,7 +24,10 @@ use std::sync::{Mutex, OnceLock};
|
||||
/// Every LibreCAD LFF font, keyed by its file stem (lower-case). Registered
|
||||
/// under the upper-cased stem and the font's `# Name:` header.
|
||||
const FONTS_SRC: &[(&str, &str)] = &[
|
||||
("cyrillic_ii", include_str!("../../../assets/fonts/cyrillic_ii.lff")),
|
||||
(
|
||||
"cyrillic_ii",
|
||||
include_str!("../../../assets/fonts/cyrillic_ii.lff"),
|
||||
),
|
||||
("gothgbt", include_str!("../../../assets/fonts/gothgbt.lff")),
|
||||
("gothgrt", include_str!("../../../assets/fonts/gothgrt.lff")),
|
||||
("gothitt", include_str!("../../../assets/fonts/gothitt.lff")),
|
||||
@@ -34,7 +37,10 @@ const FONTS_SRC: &[(&str, &str)] = &[
|
||||
("iso", include_str!("../../../assets/fonts/iso.lff")),
|
||||
("italicc", include_str!("../../../assets/fonts/italicc.lff")),
|
||||
("italict", include_str!("../../../assets/fonts/italict.lff")),
|
||||
("ltypeshp", include_str!("../../../assets/fonts/ltypeshp.lff")),
|
||||
(
|
||||
"ltypeshp",
|
||||
include_str!("../../../assets/fonts/ltypeshp.lff"),
|
||||
),
|
||||
("romanc", include_str!("../../../assets/fonts/romanc.lff")),
|
||||
("romand", include_str!("../../../assets/fonts/romand.lff")),
|
||||
("romans", include_str!("../../../assets/fonts/romans.lff")),
|
||||
@@ -42,7 +48,10 @@ const FONTS_SRC: &[(&str, &str)] = &[
|
||||
("scriptc", include_str!("../../../assets/fonts/scriptc.lff")),
|
||||
("scripts", include_str!("../../../assets/fonts/scripts.lff")),
|
||||
("simplex", include_str!("../../../assets/fonts/simplex.lff")),
|
||||
("standard", include_str!("../../../assets/fonts/standard.lff")),
|
||||
(
|
||||
"standard",
|
||||
include_str!("../../../assets/fonts/standard.lff"),
|
||||
),
|
||||
("syastro", include_str!("../../../assets/fonts/syastro.lff")),
|
||||
("symap", include_str!("../../../assets/fonts/symap.lff")),
|
||||
("symath", include_str!("../../../assets/fonts/symath.lff")),
|
||||
@@ -111,64 +120,295 @@ const SHX_ADVANCE: &[(&str, &[(u32, f32)])] = &[
|
||||
(
|
||||
"TXT",
|
||||
&[
|
||||
(33, 3.0000), (34, 6.0000), (35, 9.0000), (36, 9.0000), (38, 9.0000), (39, 4.5000),
|
||||
(40, 6.0000), (41, 6.0000), (42, 9.0000), (43, 9.0000), (44, 4.5000), (45, 9.0000),
|
||||
(46, 3.0000), (47, 9.0000), (48, 7.5000), (49, 6.0000), (50, 9.0000), (51, 9.0000),
|
||||
(52, 9.0000), (53, 9.0000), (54, 9.0000), (55, 9.0000), (56, 9.0000), (57, 9.0000),
|
||||
(58, 3.0000), (59, 4.5000), (60, 7.5000), (61, 9.0000), (62, 7.5000), (63, 7.5000),
|
||||
(64, 9.0000), (65, 9.0000), (66, 9.0000), (67, 9.0000), (68, 9.0000), (69, 9.0000),
|
||||
(70, 9.0000), (71, 9.0000), (72, 9.0000), (73, 6.0000), (74, 9.0000), (75, 9.0000),
|
||||
(76, 9.0000), (77, 9.0000), (78, 9.0000), (79, 9.0000), (80, 9.0000), (81, 9.0000),
|
||||
(82, 9.0000), (83, 9.0000), (84, 9.0000), (85, 9.0000), (86, 12.0000), (87, 12.0000),
|
||||
(88, 9.0000), (89, 9.0000), (90, 9.0000), (91, 6.0000), (92, 9.0000), (93, 6.0000),
|
||||
(94, 9.0000), (95, 9.0000), (96, 4.5000), (97, 9.0000), (98, 9.0000), (99, 9.0000),
|
||||
(100, 9.0000), (101, 9.0000), (102, 9.0000), (103, 9.0000), (104, 9.0000), (105, 3.0000),
|
||||
(106, 7.5000), (107, 9.0000), (108, 4.5000), (109, 9.0000), (110, 9.0000), (111, 9.0000),
|
||||
(112, 9.0000), (113, 9.0000), (114, 9.0000), (115, 9.0000), (116, 9.0000), (117, 9.0000),
|
||||
(118, 9.0000), (119, 9.0000), (120, 9.0000), (121, 9.0000), (122, 9.0000), (123, 6.0000),
|
||||
(124, 3.0000), (125, 6.0000), (126, 9.0000),
|
||||
(33, 3.0000),
|
||||
(34, 6.0000),
|
||||
(35, 9.0000),
|
||||
(36, 9.0000),
|
||||
(38, 9.0000),
|
||||
(39, 4.5000),
|
||||
(40, 6.0000),
|
||||
(41, 6.0000),
|
||||
(42, 9.0000),
|
||||
(43, 9.0000),
|
||||
(44, 4.5000),
|
||||
(45, 9.0000),
|
||||
(46, 3.0000),
|
||||
(47, 9.0000),
|
||||
(48, 7.5000),
|
||||
(49, 6.0000),
|
||||
(50, 9.0000),
|
||||
(51, 9.0000),
|
||||
(52, 9.0000),
|
||||
(53, 9.0000),
|
||||
(54, 9.0000),
|
||||
(55, 9.0000),
|
||||
(56, 9.0000),
|
||||
(57, 9.0000),
|
||||
(58, 3.0000),
|
||||
(59, 4.5000),
|
||||
(60, 7.5000),
|
||||
(61, 9.0000),
|
||||
(62, 7.5000),
|
||||
(63, 7.5000),
|
||||
(64, 9.0000),
|
||||
(65, 9.0000),
|
||||
(66, 9.0000),
|
||||
(67, 9.0000),
|
||||
(68, 9.0000),
|
||||
(69, 9.0000),
|
||||
(70, 9.0000),
|
||||
(71, 9.0000),
|
||||
(72, 9.0000),
|
||||
(73, 6.0000),
|
||||
(74, 9.0000),
|
||||
(75, 9.0000),
|
||||
(76, 9.0000),
|
||||
(77, 9.0000),
|
||||
(78, 9.0000),
|
||||
(79, 9.0000),
|
||||
(80, 9.0000),
|
||||
(81, 9.0000),
|
||||
(82, 9.0000),
|
||||
(83, 9.0000),
|
||||
(84, 9.0000),
|
||||
(85, 9.0000),
|
||||
(86, 12.0000),
|
||||
(87, 12.0000),
|
||||
(88, 9.0000),
|
||||
(89, 9.0000),
|
||||
(90, 9.0000),
|
||||
(91, 6.0000),
|
||||
(92, 9.0000),
|
||||
(93, 6.0000),
|
||||
(94, 9.0000),
|
||||
(95, 9.0000),
|
||||
(96, 4.5000),
|
||||
(97, 9.0000),
|
||||
(98, 9.0000),
|
||||
(99, 9.0000),
|
||||
(100, 9.0000),
|
||||
(101, 9.0000),
|
||||
(102, 9.0000),
|
||||
(103, 9.0000),
|
||||
(104, 9.0000),
|
||||
(105, 3.0000),
|
||||
(106, 7.5000),
|
||||
(107, 9.0000),
|
||||
(108, 4.5000),
|
||||
(109, 9.0000),
|
||||
(110, 9.0000),
|
||||
(111, 9.0000),
|
||||
(112, 9.0000),
|
||||
(113, 9.0000),
|
||||
(114, 9.0000),
|
||||
(115, 9.0000),
|
||||
(116, 9.0000),
|
||||
(117, 9.0000),
|
||||
(118, 9.0000),
|
||||
(119, 9.0000),
|
||||
(120, 9.0000),
|
||||
(121, 9.0000),
|
||||
(122, 9.0000),
|
||||
(123, 6.0000),
|
||||
(124, 3.0000),
|
||||
(125, 6.0000),
|
||||
(126, 9.0000),
|
||||
],
|
||||
),
|
||||
(
|
||||
"SIMPLEX",
|
||||
&[
|
||||
(33, 5.5714), (34, 6.4286), (35, 8.1429), (36, 8.5714), (38, 11.1429), (39, 3.8571),
|
||||
(40, 6.0000), (41, 6.0000), (42, 6.8571), (43, 11.1429), (44, 4.2857), (45, 11.1429),
|
||||
(46, 4.2857), (47, 9.4286), (48, 8.5714), (49, 6.8571), (50, 8.5714), (51, 8.5714),
|
||||
(52, 8.5714), (53, 8.5714), (54, 8.5714), (55, 8.5714), (56, 8.5714), (57, 8.5714),
|
||||
(58, 6.4286), (59, 6.0000), (60, 8.5714), (61, 11.1429), (62, 8.5714), (63, 7.7143),
|
||||
(64, 10.2857), (65, 9.4286), (66, 9.0000), (67, 9.0000), (68, 9.0000), (69, 8.1429),
|
||||
(70, 7.7143), (71, 9.0000), (72, 9.4286), (73, 3.4286), (74, 6.8571), (75, 9.0000),
|
||||
(76, 7.2857), (77, 10.2857), (78, 9.4286), (79, 9.4286), (80, 9.0000), (81, 9.4286),
|
||||
(82, 9.0000), (83, 8.5714), (84, 7.7143), (85, 9.4286), (86, 8.5714), (87, 10.2857),
|
||||
(88, 8.5714), (89, 8.5714), (90, 8.5714), (91, 6.4286), (92, 9.4286), (93, 5.1429),
|
||||
(94, 8.5714), (95, 10.2857), (96, 3.8571), (97, 8.1429), (98, 8.1429), (99, 7.7143),
|
||||
(100, 8.1429), (101, 7.7143), (102, 5.1429), (103, 8.1429), (104, 8.1429), (105, 3.4286),
|
||||
(106, 4.2857), (107, 7.2857), (108, 3.4286), (109, 12.8571), (110, 8.1429), (111, 8.1429),
|
||||
(112, 8.1429), (113, 8.1429), (114, 5.5714), (115, 7.2857), (116, 6.0000), (117, 8.1429),
|
||||
(118, 6.8571), (119, 9.4286), (120, 7.2857), (121, 6.8571), (122, 7.2857), (123, 5.1429),
|
||||
(124, 3.0000), (125, 4.7143), (126, 11.1429),
|
||||
(33, 5.5714),
|
||||
(34, 6.4286),
|
||||
(35, 8.1429),
|
||||
(36, 8.5714),
|
||||
(38, 11.1429),
|
||||
(39, 3.8571),
|
||||
(40, 6.0000),
|
||||
(41, 6.0000),
|
||||
(42, 6.8571),
|
||||
(43, 11.1429),
|
||||
(44, 4.2857),
|
||||
(45, 11.1429),
|
||||
(46, 4.2857),
|
||||
(47, 9.4286),
|
||||
(48, 8.5714),
|
||||
(49, 6.8571),
|
||||
(50, 8.5714),
|
||||
(51, 8.5714),
|
||||
(52, 8.5714),
|
||||
(53, 8.5714),
|
||||
(54, 8.5714),
|
||||
(55, 8.5714),
|
||||
(56, 8.5714),
|
||||
(57, 8.5714),
|
||||
(58, 6.4286),
|
||||
(59, 6.0000),
|
||||
(60, 8.5714),
|
||||
(61, 11.1429),
|
||||
(62, 8.5714),
|
||||
(63, 7.7143),
|
||||
(64, 10.2857),
|
||||
(65, 9.4286),
|
||||
(66, 9.0000),
|
||||
(67, 9.0000),
|
||||
(68, 9.0000),
|
||||
(69, 8.1429),
|
||||
(70, 7.7143),
|
||||
(71, 9.0000),
|
||||
(72, 9.4286),
|
||||
(73, 3.4286),
|
||||
(74, 6.8571),
|
||||
(75, 9.0000),
|
||||
(76, 7.2857),
|
||||
(77, 10.2857),
|
||||
(78, 9.4286),
|
||||
(79, 9.4286),
|
||||
(80, 9.0000),
|
||||
(81, 9.4286),
|
||||
(82, 9.0000),
|
||||
(83, 8.5714),
|
||||
(84, 7.7143),
|
||||
(85, 9.4286),
|
||||
(86, 8.5714),
|
||||
(87, 10.2857),
|
||||
(88, 8.5714),
|
||||
(89, 8.5714),
|
||||
(90, 8.5714),
|
||||
(91, 6.4286),
|
||||
(92, 9.4286),
|
||||
(93, 5.1429),
|
||||
(94, 8.5714),
|
||||
(95, 10.2857),
|
||||
(96, 3.8571),
|
||||
(97, 8.1429),
|
||||
(98, 8.1429),
|
||||
(99, 7.7143),
|
||||
(100, 8.1429),
|
||||
(101, 7.7143),
|
||||
(102, 5.1429),
|
||||
(103, 8.1429),
|
||||
(104, 8.1429),
|
||||
(105, 3.4286),
|
||||
(106, 4.2857),
|
||||
(107, 7.2857),
|
||||
(108, 3.4286),
|
||||
(109, 12.8571),
|
||||
(110, 8.1429),
|
||||
(111, 8.1429),
|
||||
(112, 8.1429),
|
||||
(113, 8.1429),
|
||||
(114, 5.5714),
|
||||
(115, 7.2857),
|
||||
(116, 6.0000),
|
||||
(117, 8.1429),
|
||||
(118, 6.8571),
|
||||
(119, 9.4286),
|
||||
(120, 7.2857),
|
||||
(121, 6.8571),
|
||||
(122, 7.2857),
|
||||
(123, 5.1429),
|
||||
(124, 3.0000),
|
||||
(125, 4.7143),
|
||||
(126, 11.1429),
|
||||
],
|
||||
),
|
||||
(
|
||||
"ROMANS",
|
||||
&[
|
||||
(33, 4.2857), (34, 6.0000), (35, 9.0000), (36, 8.5714), (38, 11.1429), (39, 4.2857),
|
||||
(40, 6.0000), (41, 6.0000), (42, 6.8571), (43, 11.1429), (44, 4.2857), (45, 11.1429),
|
||||
(46, 4.2857), (47, 9.4286), (48, 8.5714), (49, 8.5714), (50, 8.5714), (51, 8.5714),
|
||||
(52, 8.5714), (53, 8.5714), (54, 8.5714), (55, 8.5714), (56, 8.5714), (57, 8.5714),
|
||||
(58, 4.2857), (59, 4.2857), (60, 10.2857), (61, 11.1429), (62, 10.2857), (63, 7.7143),
|
||||
(64, 11.5714), (65, 7.7143), (66, 9.0000), (67, 9.0000), (68, 9.0000), (69, 8.1429),
|
||||
(70, 7.7143), (71, 9.0000), (72, 9.4286), (73, 3.4286), (74, 6.8571), (75, 9.0000),
|
||||
(76, 7.2857), (77, 10.2857), (78, 9.4286), (79, 9.4286), (80, 9.0000), (81, 9.4286),
|
||||
(82, 9.0000), (83, 8.5714), (84, 6.8571), (85, 9.4286), (86, 7.7143), (87, 10.2857),
|
||||
(88, 8.5714), (89, 7.7143), (90, 8.5714), (91, 6.0000), (92, 9.4286), (93, 5.5714),
|
||||
(94, 9.4286), (95, 10.2857), (96, 5.1429), (97, 8.1429), (98, 8.1429), (99, 7.7143),
|
||||
(100, 8.1429), (101, 7.7143), (102, 5.1429), (103, 8.1429), (104, 8.1429), (105, 3.4286),
|
||||
(106, 4.2857), (107, 7.2857), (108, 3.4286), (109, 12.8571), (110, 8.1429), (111, 8.1429),
|
||||
(112, 8.1429), (113, 8.1429), (114, 5.5714), (115, 7.2857), (116, 5.1429), (117, 8.1429),
|
||||
(118, 6.8571), (119, 9.4286), (120, 7.2857), (121, 6.8571), (122, 7.2857), (123, 6.0000),
|
||||
(124, 3.4286), (125, 6.0000), (126, 11.1429),
|
||||
(33, 4.2857),
|
||||
(34, 6.0000),
|
||||
(35, 9.0000),
|
||||
(36, 8.5714),
|
||||
(38, 11.1429),
|
||||
(39, 4.2857),
|
||||
(40, 6.0000),
|
||||
(41, 6.0000),
|
||||
(42, 6.8571),
|
||||
(43, 11.1429),
|
||||
(44, 4.2857),
|
||||
(45, 11.1429),
|
||||
(46, 4.2857),
|
||||
(47, 9.4286),
|
||||
(48, 8.5714),
|
||||
(49, 8.5714),
|
||||
(50, 8.5714),
|
||||
(51, 8.5714),
|
||||
(52, 8.5714),
|
||||
(53, 8.5714),
|
||||
(54, 8.5714),
|
||||
(55, 8.5714),
|
||||
(56, 8.5714),
|
||||
(57, 8.5714),
|
||||
(58, 4.2857),
|
||||
(59, 4.2857),
|
||||
(60, 10.2857),
|
||||
(61, 11.1429),
|
||||
(62, 10.2857),
|
||||
(63, 7.7143),
|
||||
(64, 11.5714),
|
||||
(65, 7.7143),
|
||||
(66, 9.0000),
|
||||
(67, 9.0000),
|
||||
(68, 9.0000),
|
||||
(69, 8.1429),
|
||||
(70, 7.7143),
|
||||
(71, 9.0000),
|
||||
(72, 9.4286),
|
||||
(73, 3.4286),
|
||||
(74, 6.8571),
|
||||
(75, 9.0000),
|
||||
(76, 7.2857),
|
||||
(77, 10.2857),
|
||||
(78, 9.4286),
|
||||
(79, 9.4286),
|
||||
(80, 9.0000),
|
||||
(81, 9.4286),
|
||||
(82, 9.0000),
|
||||
(83, 8.5714),
|
||||
(84, 6.8571),
|
||||
(85, 9.4286),
|
||||
(86, 7.7143),
|
||||
(87, 10.2857),
|
||||
(88, 8.5714),
|
||||
(89, 7.7143),
|
||||
(90, 8.5714),
|
||||
(91, 6.0000),
|
||||
(92, 9.4286),
|
||||
(93, 5.5714),
|
||||
(94, 9.4286),
|
||||
(95, 10.2857),
|
||||
(96, 5.1429),
|
||||
(97, 8.1429),
|
||||
(98, 8.1429),
|
||||
(99, 7.7143),
|
||||
(100, 8.1429),
|
||||
(101, 7.7143),
|
||||
(102, 5.1429),
|
||||
(103, 8.1429),
|
||||
(104, 8.1429),
|
||||
(105, 3.4286),
|
||||
(106, 4.2857),
|
||||
(107, 7.2857),
|
||||
(108, 3.4286),
|
||||
(109, 12.8571),
|
||||
(110, 8.1429),
|
||||
(111, 8.1429),
|
||||
(112, 8.1429),
|
||||
(113, 8.1429),
|
||||
(114, 5.5714),
|
||||
(115, 7.2857),
|
||||
(116, 5.1429),
|
||||
(117, 8.1429),
|
||||
(118, 6.8571),
|
||||
(119, 9.4286),
|
||||
(120, 7.2857),
|
||||
(121, 6.8571),
|
||||
(122, 7.2857),
|
||||
(123, 6.0000),
|
||||
(124, 3.4286),
|
||||
(125, 6.0000),
|
||||
(126, 11.1429),
|
||||
],
|
||||
),
|
||||
];
|
||||
@@ -241,7 +481,8 @@ fn fonts_map() -> &'static HashMap<String, Font> {
|
||||
for (stem, src) in FONTS_SRC {
|
||||
let f = parse_lff(src);
|
||||
map.insert(stem.to_ascii_uppercase(), f.clone());
|
||||
map.entry(f.name.to_ascii_uppercase()).or_insert_with(|| f.clone());
|
||||
map.entry(f.name.to_ascii_uppercase())
|
||||
.or_insert_with(|| f.clone());
|
||||
}
|
||||
// AutoCAD/DXF SHX names → the matching LFF stem.
|
||||
for (alias, stem) in ALIASES {
|
||||
@@ -426,7 +667,11 @@ pub(crate) fn tokenize_run(text: &str) -> Vec<Tok> {
|
||||
continue;
|
||||
}
|
||||
|
||||
toks.push(if ch == ' ' { Tok::Space } else { Tok::Glyph(ch) });
|
||||
toks.push(if ch == ' ' {
|
||||
Tok::Space
|
||||
} else {
|
||||
Tok::Glyph(ch)
|
||||
});
|
||||
}
|
||||
toks
|
||||
}
|
||||
@@ -527,7 +772,10 @@ pub fn tessellate_text_run(
|
||||
// Flush a buffered TTF segment: shape it, emit the positioned glyph
|
||||
// contours, and advance the pen by the shaped run width. Falls back to
|
||||
// per-glyph outlines if shaping is unavailable.
|
||||
let flush_ttf = |seg: &mut String, cursor_x: &mut f32, out: &mut Vec<Vec<[f32; 2]>>, fill_tris: &mut Vec<[f32; 2]>| {
|
||||
let flush_ttf = |seg: &mut String,
|
||||
cursor_x: &mut f32,
|
||||
out: &mut Vec<Vec<[f32; 2]>>,
|
||||
fill_tris: &mut Vec<[f32; 2]>| {
|
||||
if seg.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -624,13 +872,22 @@ pub fn tessellate_text_run(
|
||||
}
|
||||
|
||||
if let Some(start) = underline {
|
||||
out.push(vec![xform(start, UNDER_Y, 0.0), xform(cursor_x, UNDER_Y, 0.0)]);
|
||||
out.push(vec![
|
||||
xform(start, UNDER_Y, 0.0),
|
||||
xform(cursor_x, UNDER_Y, 0.0),
|
||||
]);
|
||||
}
|
||||
if let Some(start) = overline {
|
||||
out.push(vec![xform(start, OVER_Y, 0.0), xform(cursor_x, OVER_Y, 0.0)]);
|
||||
out.push(vec![
|
||||
xform(start, OVER_Y, 0.0),
|
||||
xform(cursor_x, OVER_Y, 0.0),
|
||||
]);
|
||||
}
|
||||
if let Some(start) = strikethrough {
|
||||
out.push(vec![xform(start, STRIKE_Y, 0.0), xform(cursor_x, STRIKE_Y, 0.0)]);
|
||||
out.push(vec![
|
||||
xform(start, STRIKE_Y, 0.0),
|
||||
xform(cursor_x, STRIKE_Y, 0.0),
|
||||
]);
|
||||
}
|
||||
|
||||
(out, fill_tris)
|
||||
@@ -774,11 +1031,25 @@ fn parse_lff(src: &str) -> Font {
|
||||
};
|
||||
for (c, g) in raw {
|
||||
let advance = advance_of(&g.strokes);
|
||||
font.glyphs.insert(c, Glyph { strokes: g.strokes, advance, fill_tris: Vec::new() });
|
||||
font.glyphs.insert(
|
||||
c,
|
||||
Glyph {
|
||||
strokes: g.strokes,
|
||||
advance,
|
||||
fill_tris: Vec::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
for (n, g) in raw_shapes {
|
||||
let advance = advance_of(&g.strokes);
|
||||
font.shapes.insert(n, Glyph { strokes: g.strokes, advance, fill_tris: Vec::new() });
|
||||
font.shapes.insert(
|
||||
n,
|
||||
Glyph {
|
||||
strokes: g.strokes,
|
||||
advance,
|
||||
fill_tris: Vec::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
font
|
||||
}
|
||||
@@ -871,11 +1142,7 @@ mod tests {
|
||||
for &(t, segs, verts, sum_ref) in cases {
|
||||
let (st, _) = tessellate_text_ex([0.0, 0.0], 10.0, 0.0, 1.0, 0.0, "txt", t);
|
||||
let nv: usize = st.iter().map(|s| s.len()).sum();
|
||||
let sum: f64 = st
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|p| p[0] as f64 + p[1] as f64)
|
||||
.sum();
|
||||
let sum: f64 = st.iter().flatten().map(|p| p[0] as f64 + p[1] as f64).sum();
|
||||
assert_eq!(st.len(), segs, "segment count drift for {t:?}");
|
||||
assert_eq!(nv, verts, "vertex count drift for {t:?}");
|
||||
assert!(
|
||||
@@ -914,15 +1181,15 @@ mod tests {
|
||||
assert!(!is_builtin("amiri-regular"));
|
||||
assert!(get_font("kochigothic").glyph('A').is_some());
|
||||
// Unicode fallback covers a non-ASCII letter via the renderer path.
|
||||
let (strokes, _) = tessellate_text_run([0.0, 0.0], 2.5, 0.0, 1.0, 0.0, 1.0, "Standard", "Aб");
|
||||
let (strokes, _) =
|
||||
tessellate_text_run([0.0, 0.0], 2.5, 0.0, 1.0, 0.0, 1.0, "Standard", "Aб");
|
||||
assert!(!strokes.is_empty());
|
||||
// The bulge belongs to the segment ENDING at the vertex (LibreCAD
|
||||
// convention): the standard/iso/unicode 'O' must come out as an
|
||||
// upright oval (taller than wide), not a sideways capsule.
|
||||
for name in ["standard", "iso", "unicode", "txt"] {
|
||||
let o = get_font(name).glyph('O').expect("O glyph");
|
||||
let (mut minx, mut miny, mut maxx, mut maxy) =
|
||||
(f32::MAX, f32::MAX, f32::MIN, f32::MIN);
|
||||
let (mut minx, mut miny, mut maxx, mut maxy) = (f32::MAX, f32::MAX, f32::MIN, f32::MIN);
|
||||
for s in &o.strokes {
|
||||
for &[x, y] in s {
|
||||
minx = minx.min(x);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
pub mod lff;
|
||||
pub mod complex_lt;
|
||||
pub mod font_face;
|
||||
pub mod glyph_quads;
|
||||
pub mod lff;
|
||||
pub mod sdf_atlas;
|
||||
pub mod shx;
|
||||
pub mod sysfont;
|
||||
pub mod ttf_glyph;
|
||||
pub mod web_font;
|
||||
pub mod complex_lt;
|
||||
pub mod shx;
|
||||
+26
-14
@@ -457,10 +457,8 @@ fn bake_glyph(g: &Glyph, pen_half: f32) -> Option<BakedTile> {
|
||||
let plane_min = [min_x - SPREAD_UNITS, min_y - SPREAD_UNITS];
|
||||
let plane_max = [max_x + SPREAD_UNITS, max_y + SPREAD_UNITS];
|
||||
|
||||
let w = (((plane_max[0] - plane_min[0]) * PX_PER_UNIT).ceil() as u32)
|
||||
.clamp(1, MAX_TILE_PX);
|
||||
let h = (((plane_max[1] - plane_min[1]) * PX_PER_UNIT).ceil() as u32)
|
||||
.clamp(1, MAX_TILE_PX);
|
||||
let w = (((plane_max[0] - plane_min[0]) * PX_PER_UNIT).ceil() as u32).clamp(1, MAX_TILE_PX);
|
||||
let h = (((plane_max[1] - plane_min[1]) * PX_PER_UNIT).ceil() as u32).clamp(1, MAX_TILE_PX);
|
||||
|
||||
let mut data = vec![0u8; (w * h) as usize];
|
||||
let inv = 1.0 / PX_PER_UNIT;
|
||||
@@ -562,10 +560,10 @@ mod tests {
|
||||
|
||||
/// Sample the SDF value at a glyph-space point of a baked tile (0..255).
|
||||
fn sample(tile: &BakedTile, gx: f32, gy: f32) -> u8 {
|
||||
let px = (((gx - tile.plane_min[0]) * PX_PER_UNIT) as i32)
|
||||
.clamp(0, tile.w as i32 - 1) as u32;
|
||||
let py = (((tile.plane_max[1] - gy) * PX_PER_UNIT) as i32)
|
||||
.clamp(0, tile.h as i32 - 1) as u32;
|
||||
let px =
|
||||
(((gx - tile.plane_min[0]) * PX_PER_UNIT) as i32).clamp(0, tile.w as i32 - 1) as u32;
|
||||
let py =
|
||||
(((tile.plane_max[1] - gy) * PX_PER_UNIT) as i32).clamp(0, tile.h as i32 - 1) as u32;
|
||||
tile.data[(py * tile.w + px) as usize]
|
||||
}
|
||||
|
||||
@@ -599,7 +597,10 @@ mod tests {
|
||||
fn filled_glyph_inside_is_high_outside_is_low() {
|
||||
let tile = bake_glyph(&filled_square(), PEN_HALF_UNITS).expect("square bakes");
|
||||
// Deep inside the fill -> well above the 0.5 (128) edge.
|
||||
assert!(sample(&tile, 2.0, 2.0) > 200, "center should read as inside");
|
||||
assert!(
|
||||
sample(&tile, 2.0, 2.0) > 200,
|
||||
"center should read as inside"
|
||||
);
|
||||
// Well outside the fill -> below the edge.
|
||||
assert!(
|
||||
sample(&tile, -1.0, -1.0) < 60,
|
||||
@@ -611,7 +612,10 @@ mod tests {
|
||||
fn stroke_glyph_on_line_is_high_off_line_is_low() {
|
||||
let tile = bake_glyph(&diagonal_stroke(), PEN_HALF_UNITS).expect("stroke bakes");
|
||||
// On the diagonal -> inside the pen band.
|
||||
assert!(sample(&tile, 2.0, 2.0) > 128, "on-stroke should be inside band");
|
||||
assert!(
|
||||
sample(&tile, 2.0, 2.0) > 128,
|
||||
"on-stroke should be inside band"
|
||||
);
|
||||
// Off the diagonal by more than the pen half-width -> outside.
|
||||
assert!(
|
||||
sample(&tile, 3.0, 0.5) < 128,
|
||||
@@ -632,8 +636,12 @@ mod tests {
|
||||
#[test]
|
||||
fn packing_places_tiles_without_overlap() {
|
||||
let mut atlas = GlyphAtlas::new(256, 256);
|
||||
let a = atlas.pack(bake_glyph(&filled_square(), PEN_HALF_UNITS).unwrap()).unwrap();
|
||||
let b = atlas.pack(bake_glyph(&diagonal_stroke(), PEN_HALF_UNITS).unwrap()).unwrap();
|
||||
let a = atlas
|
||||
.pack(bake_glyph(&filled_square(), PEN_HALF_UNITS).unwrap())
|
||||
.unwrap();
|
||||
let b = atlas
|
||||
.pack(bake_glyph(&diagonal_stroke(), PEN_HALF_UNITS).unwrap())
|
||||
.unwrap();
|
||||
assert!(atlas.is_dirty());
|
||||
// UVs are within the atlas.
|
||||
for e in [&a, &b] {
|
||||
@@ -651,7 +659,9 @@ mod tests {
|
||||
// A tiny atlas cannot fit even one padded tile (wider than the atlas —
|
||||
// the width the packer does not grow).
|
||||
let mut atlas = GlyphAtlas::new(4, 4);
|
||||
assert!(atlas.pack(bake_glyph(&filled_square(), PEN_HALF_UNITS).unwrap()).is_none());
|
||||
assert!(atlas
|
||||
.pack(bake_glyph(&filled_square(), PEN_HALF_UNITS).unwrap())
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -682,7 +692,9 @@ mod tests {
|
||||
#[test]
|
||||
fn real_lff_glyph_bakes_and_packs() {
|
||||
let mut atlas = GlyphAtlas::new(512, 512);
|
||||
let e = atlas.get_or_insert("txt", 'A', false).expect("LFF 'A' bakes");
|
||||
let e = atlas
|
||||
.get_or_insert("txt", 'A', false)
|
||||
.expect("LFF 'A' bakes");
|
||||
assert!(atlas.is_dirty());
|
||||
assert!(
|
||||
e.plane_max[0] > e.plane_min[0] && e.plane_max[1] > e.plane_min[1],
|
||||
|
||||
+11
-6
@@ -329,9 +329,8 @@ pub fn font_metrics(path: &str) -> Option<(f64, f64)> {
|
||||
|
||||
fn glyph_cache(
|
||||
) -> &'static Mutex<HashMap<(String, u16), Option<Arc<crate::scene::text::lff::Glyph>>>> {
|
||||
static C: OnceLock<
|
||||
Mutex<HashMap<(String, u16), Option<Arc<crate::scene::text::lff::Glyph>>>>,
|
||||
> = OnceLock::new();
|
||||
static C: OnceLock<Mutex<HashMap<(String, u16), Option<Arc<crate::scene::text::lff::Glyph>>>>> =
|
||||
OnceLock::new();
|
||||
C.get_or_init(|| Mutex::new(HashMap::new()))
|
||||
}
|
||||
|
||||
@@ -663,7 +662,6 @@ fn interpret(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -737,7 +735,10 @@ mod tests {
|
||||
assert!((g.advance - 1.125).abs() < 1e-4, "advance {}", g.advance);
|
||||
assert_eq!(g.strokes.len(), 1);
|
||||
assert!(bigfont_glyph(&path, 'A').is_none());
|
||||
assert!(bigfont_glyph(&path, '國').is_none(), "unknown code must miss, not alias");
|
||||
assert!(
|
||||
bigfont_glyph(&path, '國').is_none(),
|
||||
"unknown code must miss, not alias"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -746,7 +747,11 @@ mod tests {
|
||||
assert!(!is_bigfont(&path));
|
||||
assert_eq!(font_metrics(&path), Some((6.0, 2.0)));
|
||||
let g = font_glyph(&path, 'A' as u16).expect("glyph for A");
|
||||
assert!((g.advance - 9.0).abs() < 1e-4, "6 units at above 6 → 9: {}", g.advance);
|
||||
assert!(
|
||||
(g.advance - 9.0).abs() < 1e-4,
|
||||
"6 units at above 6 → 9: {}",
|
||||
g.advance
|
||||
);
|
||||
assert!(font_glyph(&path, 'B' as u16).is_none());
|
||||
}
|
||||
}
|
||||
@@ -82,24 +82,32 @@ pub fn canonical_family_name(family: &str) -> Option<String> {
|
||||
|
||||
fn canonical_family_name_uncached(family: &str) -> Option<String> {
|
||||
let db = &fonts().db;
|
||||
|
||||
|
||||
// 1. Try exact match first
|
||||
let query = fontdb::Query {
|
||||
families: &[fontdb::Family::Name(family)],
|
||||
..Default::default()
|
||||
};
|
||||
if db.query(&query).is_some() {
|
||||
if let Some(canonical) = fonts().families.iter().find(|&f| f.eq_ignore_ascii_case(family)) {
|
||||
if let Some(canonical) = fonts()
|
||||
.families
|
||||
.iter()
|
||||
.find(|&f| f.eq_ignore_ascii_case(family))
|
||||
{
|
||||
return Some(canonical.clone());
|
||||
}
|
||||
return Some(family.to_string());
|
||||
}
|
||||
|
||||
|
||||
// 2. Try case-insensitive match on the families we have
|
||||
if let Some(matched) = fonts().families.iter().find(|&f| f.eq_ignore_ascii_case(family)) {
|
||||
if let Some(matched) = fonts()
|
||||
.families
|
||||
.iter()
|
||||
.find(|&f| f.eq_ignore_ascii_case(family))
|
||||
{
|
||||
return Some(matched.clone());
|
||||
}
|
||||
|
||||
|
||||
// 3. Match common prefixes / variations
|
||||
let family_lower = family.to_lowercase();
|
||||
let alias = match family_lower.as_str() {
|
||||
@@ -109,13 +117,17 @@ fn canonical_family_name_uncached(family: &str) -> Option<String> {
|
||||
"cour" => Some("Courier New"),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
|
||||
if let Some(alias_name) = alias {
|
||||
if let Some(matched) = fonts().families.iter().find(|&f| f.eq_ignore_ascii_case(alias_name)) {
|
||||
if let Some(matched) = fonts()
|
||||
.families
|
||||
.iter()
|
||||
.find(|&f| f.eq_ignore_ascii_case(alias_name))
|
||||
{
|
||||
return Some(matched.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 4. Try matching prefix/subset case-insensitively. Require at least 3
|
||||
// chars so a 1–2 letter request can't grab an arbitrary family by the
|
||||
// first iteration order. Iterating sorted keeps the pick deterministic.
|
||||
@@ -246,4 +258,3 @@ mod tests {
|
||||
assert_eq!(family_for_reference(" "), None);
|
||||
}
|
||||
}
|
||||
|
||||
+65
-20
@@ -588,14 +588,24 @@ pub struct ShapedRun {
|
||||
pub fn char_cells(run: &ShapedRun, text: &str) -> Vec<(f32, f32)> {
|
||||
text.char_indices()
|
||||
.map(|(byte, _)| {
|
||||
let Some(&(start, end, x0, x1)) =
|
||||
run.cells.iter().find(|(start, end, _, _)| (*start..(*end).max(start + 1)).contains(&byte))
|
||||
let Some(&(start, end, x0, x1)) = run
|
||||
.cells
|
||||
.iter()
|
||||
.find(|(start, end, _, _)| (*start..(*end).max(start + 1)).contains(&byte))
|
||||
else {
|
||||
return (0.0, 0.0);
|
||||
};
|
||||
let chars: Vec<usize> = text[start..end.min(text.len())].char_indices().map(|(i, _)| start + i).collect();
|
||||
let (n, k) = (chars.len().max(1), chars.iter().position(|&b| b == byte).unwrap_or(0));
|
||||
let rtl = text[start..end.min(text.len())].chars().any(crate::entities::text_support::is_rtl_char);
|
||||
let chars: Vec<usize> = text[start..end.min(text.len())]
|
||||
.char_indices()
|
||||
.map(|(i, _)| start + i)
|
||||
.collect();
|
||||
let (n, k) = (
|
||||
chars.len().max(1),
|
||||
chars.iter().position(|&b| b == byte).unwrap_or(0),
|
||||
);
|
||||
let rtl = text[start..end.min(text.len())]
|
||||
.chars()
|
||||
.any(crate::entities::text_support::is_rtl_char);
|
||||
let slot = (x1 - x0) / n as f32;
|
||||
let k = if rtl { n - 1 - k } else { k };
|
||||
(x0 + slot * k as f32, x0 + slot * (k + 1) as f32)
|
||||
@@ -760,7 +770,9 @@ fn outline_char(
|
||||
advance,
|
||||
fill_tris,
|
||||
};
|
||||
let source = with_source.then(|| source_for(data, index, face, gid, k)).flatten();
|
||||
let source = with_source
|
||||
.then(|| source_for(data, index, face, gid, k))
|
||||
.flatten();
|
||||
Some((glyph, source))
|
||||
}
|
||||
|
||||
@@ -898,10 +910,8 @@ fn build_shaped(_family: &str, text: &str) -> Option<ShapedRun> {
|
||||
database.load_font_data((*bytes).clone());
|
||||
}
|
||||
database.set_sans_serif_family(crate::scene::text::web_font::primary_script().family());
|
||||
let mut font_system = FontSystem::new_with_locale_and_db(
|
||||
crate::i18n::active_language_tag(),
|
||||
database,
|
||||
);
|
||||
let mut font_system =
|
||||
FontSystem::new_with_locale_and_db(crate::i18n::active_language_tag(), database);
|
||||
let attrs = Attrs::new().family(Family::SansSerif);
|
||||
let mut buffer = Buffer::new(&mut font_system, Metrics::new(SHAPE_FS, SHAPE_FS));
|
||||
buffer.set_size(&mut font_system, None, None);
|
||||
@@ -922,7 +932,12 @@ fn build_shaped(_family: &str, text: &str) -> Option<ShapedRun> {
|
||||
let m = LRM.len_utf8();
|
||||
if glyph.end > m {
|
||||
let start = glyph.start.saturating_sub(m);
|
||||
cells.push((start, glyph.end - m, glyph.x * px_to_9, (glyph.x + glyph.w) * px_to_9));
|
||||
cells.push((
|
||||
start,
|
||||
glyph.end - m,
|
||||
glyph.x * px_to_9,
|
||||
(glyph.x + glyph.w) * px_to_9,
|
||||
));
|
||||
}
|
||||
let face_index = font_system
|
||||
.db_mut()
|
||||
@@ -962,7 +977,11 @@ fn build_shaped(_family: &str, text: &str) -> Option<ShapedRun> {
|
||||
if advance <= 0.0 && glyphs.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(ShapedRun { glyphs, advance, cells })
|
||||
Some(ShapedRun {
|
||||
glyphs,
|
||||
advance,
|
||||
cells,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1037,7 +1056,12 @@ fn build_shaped(family: &str, text: &str) -> Option<ShapedRun> {
|
||||
// A cluster the shaper merged into the mark keeps its own part.
|
||||
let m = LRM.len_utf8();
|
||||
if g.end > m {
|
||||
cells.push((g.start.saturating_sub(m), g.end - m, g.x * px_to_9, (g.x + g.w) * px_to_9));
|
||||
cells.push((
|
||||
g.start.saturating_sub(m),
|
||||
g.end - m,
|
||||
g.x * px_to_9,
|
||||
(g.x + g.w) * px_to_9,
|
||||
));
|
||||
}
|
||||
let face_index = fs.db_mut().face(g.font_id).map(|f| f.index).unwrap_or(0);
|
||||
let Some(font) = fs.get_font(g.font_id, g.font_weight) else {
|
||||
@@ -1077,14 +1101,17 @@ fn build_shaped(family: &str, text: &str) -> Option<ShapedRun> {
|
||||
if advance <= 0.0 && glyphs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(ShapedRun { glyphs, advance, cells })
|
||||
Some(ShapedRun {
|
||||
glyphs,
|
||||
advance,
|
||||
cells,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
|
||||
#[test]
|
||||
fn shape_run_falls_back_for_missing_script() {
|
||||
let fams = sysfont::families();
|
||||
@@ -1140,18 +1167,36 @@ mod tests {
|
||||
#[test]
|
||||
fn shape_run_positions_glyphs() {
|
||||
let fams = sysfont::families();
|
||||
if fams.is_empty() { eprintln!("no fonts; skip"); return; }
|
||||
let fam = fams.iter().find(|f| glyph(f, 'A').is_some()).expect("family");
|
||||
if fams.is_empty() {
|
||||
eprintln!("no fonts; skip");
|
||||
return;
|
||||
}
|
||||
let fam = fams
|
||||
.iter()
|
||||
.find(|f| glyph(f, 'A').is_some())
|
||||
.expect("family");
|
||||
let run = shape_run(fam, "AVA").expect("shaped");
|
||||
eprintln!("glyphs={} advance={:.3}", run.glyphs.len(), run.advance);
|
||||
assert_eq!(run.glyphs.len(), 3);
|
||||
assert!(run.advance > 0.0);
|
||||
// glyphs must be laid out left-to-right: each glyph's strokes sit at a
|
||||
// greater x than the previous glyph's start.
|
||||
let xs: Vec<f32> = run.glyphs.iter()
|
||||
.filter_map(|g| g.strokes.iter().flatten().map(|p| p[0]).fold(None, |m,x| Some(m.map_or(x, |mm:f32| mm.min(x)))))
|
||||
let xs: Vec<f32> = run
|
||||
.glyphs
|
||||
.iter()
|
||||
.filter_map(|g| {
|
||||
g.strokes
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|p| p[0])
|
||||
.fold(None, |m, x| Some(m.map_or(x, |mm: f32| mm.min(x))))
|
||||
})
|
||||
.collect();
|
||||
assert!(xs.windows(2).all(|w| w[1] >= w[0] - 1.0), "glyphs not L->R: {:?}", xs);
|
||||
assert!(
|
||||
xs.windows(2).all(|w| w[1] >= w[0] - 1.0),
|
||||
"glyphs not L->R: {:?}",
|
||||
xs
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -329,7 +329,9 @@ mod imp {
|
||||
let resp_val = JsFuture::from(win.fetch_with_str(script.asset()))
|
||||
.await
|
||||
.map_err(|e| format!("{e:?}"))?;
|
||||
let resp: web_sys::Response = resp_val.dyn_into().map_err(|_| "bad response".to_string())?;
|
||||
let resp: web_sys::Response = resp_val
|
||||
.dyn_into()
|
||||
.map_err(|_| "bad response".to_string())?;
|
||||
if !resp.ok() {
|
||||
return Err(format!("HTTP {}", resp.status()));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user