1use std::collections::{BTreeMap, HashMap};
9
10use indexmap::IndexMap;
11use kurbo::{Affine, Vec2};
12use smol_str::{format_smolstr, SmolStr};
13
14use crate::{
15 font::Anchor,
16 glyphdata::{Category, Subcategory},
17 Component, Font, Glyph, Layer, Shape,
18};
19
20impl Font {
21 pub fn propagate_all_anchors(&mut self) {
23 propagate_all_anchors_impl(&mut self.glyphs);
24 }
25}
26
27fn propagate_all_anchors_impl(glyphs: &mut BTreeMap<SmolStr, Glyph>) {
29 let todo = depth_sorted_composite_glyphs(glyphs);
33 let mut num_base_glyphs = HashMap::new();
34 let mut all_anchors = HashMap::new();
40 for name in todo {
41 let glyph = glyphs.get(&name).unwrap();
42 for layer in &glyph.layers {
43 let anchors = anchors_traversing_components(
44 glyph,
45 layer,
46 glyphs,
47 &all_anchors,
48 &mut num_base_glyphs,
49 );
50 maybe_log_new_anchors(&anchors, glyph, layer);
51 all_anchors.entry(name.clone()).or_default().push(anchors);
52 }
53 }
54
55 for (name, layers) in all_anchors {
57 let glyph = glyphs.get_mut(&name).unwrap();
58 if glyph.has_components() {
59 assert_eq!(layers.len(), glyph.layers.len());
60 for (i, layer_anchors) in layers.into_iter().enumerate() {
61 glyph.layers[i].anchors = layer_anchors;
62 }
63 }
64 }
65}
66
67fn maybe_log_new_anchors(anchors: &[Anchor], glyph: &Glyph, layer: &Layer) {
68 if !glyph.has_components() || !log::log_enabled!(log::Level::Trace) || anchors == layer.anchors
69 {
70 return;
71 }
72 let prev_names: Vec<_> = layer.anchors.iter().map(|a| &a.name).collect();
73 let new_names: Vec<_> = anchors.iter().map(|a| &a.name).collect();
74 log::trace!(
75 "propagated anchors for ('{}': {prev_names:?} -> {new_names:?}",
76 glyph.name,
77 );
78}
79
80fn anchors_traversing_components<'a>(
93 glyph: &'a Glyph,
94 layer: &'a Layer,
95 glyphs: &BTreeMap<SmolStr, Glyph>,
96 done_anchors: &HashMap<SmolStr, Vec<Vec<Anchor>>>,
100 base_glyph_counts: &mut HashMap<(SmolStr, &'a str), usize>,
102) -> Vec<Anchor> {
103 if layer.anchors.is_empty() && layer.components().count() == 0 {
104 return Vec::new();
105 }
106
107 if !layer.anchors.is_empty() && glyph.category == Some(Category::Mark) {
110 return origin_adjusted_anchors(&layer.anchors).collect();
111 }
112
113 let is_ligature = glyph.sub_category == Some(Subcategory::Ligature);
114 let mut has_underscore = layer
115 .anchors
116 .iter()
117 .any(|anchor| anchor.name.starts_with('_'));
118
119 let mut number_of_base_glyphs = 0usize;
120 let mut all_anchors = IndexMap::new();
122 for (component_idx, component) in layer.components().enumerate() {
123 let Some(mut anchors) =
126 get_component_layer_anchors(component, layer, glyphs, done_anchors)
128 else {
129 log::warn!(
130 "could not get layer '{}' for component '{}' of glyph '{}'",
131 layer.layer_id,
132 component.name,
133 glyph.name
134 );
135 continue;
136 };
137
138 if let Some(comp_anchor) = component.anchor.as_ref().filter(|_| component_idx > 0) {
140 maybe_rename_component_anchor(comp_anchor.to_owned(), &mut anchors);
141 }
142
143 let component_number_of_base_glyphs = base_glyph_counts
144 .get(&(component.name.clone(), layer.layer_id.as_str()))
145 .copied()
146 .unwrap_or(0);
147
148 let comb_has_underscore = anchors
149 .iter()
150 .any(|a| a.name.len() >= 2 && a.name.starts_with('_'));
151 let comb_has_exit = anchors.iter().any(|a| a.name.ends_with("exit"));
152 if !(comb_has_underscore | comb_has_exit) {
153 all_anchors.retain(|name: &SmolStr, _| !name.ends_with("exit"));
156 }
157
158 let scale = get_xy_rotation(component.transform);
159 for mut anchor in anchors {
160 let new_has_underscore = anchor.name.starts_with('_');
161 if (component_idx > 0 || has_underscore) && new_has_underscore {
162 continue;
163 }
164 if component_idx > 0 && anchor.name.ends_with("entry") {
166 continue;
167 }
168
169 let mut new_anchor_name = rename_anchor_for_scale(&anchor.name, scale);
170 if is_ligature
171 && component_number_of_base_glyphs > 0
172 && !new_has_underscore
173 && !(new_anchor_name.ends_with("exit") || new_anchor_name.ends_with("entry"))
174 {
175 new_anchor_name = make_liga_anchor_name(new_anchor_name, number_of_base_glyphs);
177 }
178
179 apply_transform_to_anchor(&mut anchor, component.transform);
180 anchor.name = new_anchor_name;
181 all_anchors.insert(anchor.name.clone(), anchor);
182 has_underscore |= new_has_underscore;
183 }
184 number_of_base_glyphs += base_glyph_counts
185 .get(&(component.name.clone(), layer.layer_id.as_str()))
186 .copied()
187 .unwrap_or(0);
188 }
189
190 all_anchors.extend(origin_adjusted_anchors(&layer.anchors).map(|a| (a.name.clone(), a)));
193 let mut has_underscore_anchor = false;
194 let mut has_mark_anchor = false;
195 let mut component_count_from_anchors = 0;
196
197 for name in all_anchors.keys() {
199 has_underscore_anchor |= name.starts_with('_');
200 has_mark_anchor |= name.chars().next().unwrap_or('\0').is_ascii_alphabetic();
201 if !is_ligature
202 && number_of_base_glyphs == 0
203 && !name.starts_with('_')
204 && !(name.ends_with("entry") | name.ends_with("exit"))
205 && name.contains('_')
206 {
207 let (_, suffix) = name.split_once('_').unwrap();
208 let maybe_add_one = name.starts_with("caret").then_some(1).unwrap_or_default();
211 let anchor_index = suffix.parse::<usize>().unwrap_or(0) + maybe_add_one;
212 component_count_from_anchors = component_count_from_anchors.max(anchor_index);
213 }
214 }
215 if !has_underscore_anchor && number_of_base_glyphs == 0 && has_mark_anchor {
216 number_of_base_glyphs += 1;
217 }
218 number_of_base_glyphs = number_of_base_glyphs.max(component_count_from_anchors);
219
220 if layer.anchors.iter().any(|a| a.name == "_bottom") {
221 all_anchors.shift_remove("top");
222 all_anchors.shift_remove("_top");
223 }
224 if layer.anchors.iter().any(|a| a.name == "_top") {
225 all_anchors.shift_remove("bottom");
226 all_anchors.shift_remove("_bottom");
227 }
228 base_glyph_counts.insert(
229 (glyph.name.clone(), layer.layer_id.as_str()),
230 number_of_base_glyphs,
231 );
232 all_anchors.into_values().collect()
233}
234
235fn origin_adjusted_anchors(anchors: &[Anchor]) -> impl Iterator<Item = Anchor> + '_ {
240 let origin = anchors
241 .iter()
242 .find_map(Anchor::origin_delta)
243 .unwrap_or_default();
244 anchors
245 .iter()
246 .filter(|a| !a.is_origin())
247 .cloned()
248 .map(move |mut a| {
249 a.pos -= origin;
250 a
251 })
252}
253
254fn get_xy_rotation(xform: Affine) -> Vec2 {
256 let [xx, xy, ..] = xform.as_coeffs();
259 let angle = xy.atan2(xx);
261 let rotated = xform.pre_rotate(-angle).as_coeffs();
263 let mut scale = Vec2::new(rotated[0], rotated[3]);
264 if (angle.to_degrees() - 180.0).abs() < 0.001 {
266 scale *= -1.0;
267 }
268
269 scale
270}
271
272fn apply_transform_to_anchor(anchor: &mut Anchor, transform: Affine) {
275 const ROUND_TO: f64 = 1e6;
277 let mut pos = (transform * anchor.pos).to_vec2();
278 pos *= ROUND_TO;
279 pos = pos.round();
280 pos /= ROUND_TO;
281 anchor.pos = pos.to_point();
282}
283
284fn maybe_rename_component_anchor(comp_name: SmolStr, anchors: &mut [Anchor]) {
285 let Some((sub_name, _)) = comp_name.as_str().split_once('_') else {
287 return;
288 };
289 let mark_name = format_smolstr!("_{sub_name}");
290 if anchors.iter().any(|a| a.name == sub_name) && anchors.iter().any(|a| a.name == mark_name) {
291 anchors
292 .iter_mut()
293 .find(|a| a.name == sub_name)
294 .unwrap()
295 .name = comp_name.clone();
296 }
297}
298
299fn make_liga_anchor_name(name: SmolStr, base_number: usize) -> SmolStr {
300 match name.split_once('_') {
301 Some((name, suffix)) => {
303 let suffix = base_number + suffix.parse::<usize>().ok().unwrap_or(1);
304 format_smolstr!("{name}_{suffix}")
305 }
306 None => format_smolstr!("{name}_{}", base_number + 1),
308 }
309}
310
311fn rename_anchor_for_scale(name: &SmolStr, scale: Vec2) -> SmolStr {
313 fn swap_pair(s: &mut String, one: &str, two: &str) {
315 fn replace(s: &mut String, target: &str, by: &str) -> bool {
316 if let Some(idx) = s.find(target) {
317 s.replace_range(idx..idx + target.len(), by);
318 return true;
319 }
320 false
321 }
322 if !replace(s, one, two) {
324 replace(s, two, one);
325 }
326 }
327
328 if scale.x >= 0. && scale.y >= 0. {
329 return name.to_owned();
330 }
331
332 let mut name = name.to_string();
333 if scale.y < 0. {
334 swap_pair(&mut name, "bottom", "top");
335 }
336 if scale.x < 0. {
337 swap_pair(&mut name, "left", "right");
338 swap_pair(&mut name, "exit", "entry");
339 }
340
341 SmolStr::from(name)
342}
343
344fn get_component_layer_anchors(
346 component: &Component,
347 layer: &Layer,
348 glyphs: &BTreeMap<SmolStr, Glyph>,
349 anchors: &HashMap<SmolStr, Vec<Vec<Anchor>>>,
350) -> Option<Vec<Anchor>> {
351 let glyph = glyphs.get(&component.name)?;
352 glyph
353 .layers
354 .iter()
355 .position(|comp_layer| comp_layer.layer_id == layer.layer_id)
356 .and_then(|layer_idx| {
357 anchors
358 .get(&component.name)
359 .and_then(|layers| layers.get(layer_idx))
360 })
361 .cloned()
362}
363
364fn depth_sorted_composite_glyphs(glyphs: &BTreeMap<SmolStr, Glyph>) -> Vec<SmolStr> {
369 let mut indeterminate_depth = Vec::new();
377 let mut depths: HashMap<_, _> = glyphs
378 .iter()
379 .filter_map(|(name, glyph)| {
380 if glyph.has_components() {
381 indeterminate_depth.push(glyph); None
383 } else {
384 Some((name, 0))
385 }
386 })
387 .collect();
388
389 let mut progress = glyphs.len() - indeterminate_depth.len();
392 while progress > 0 {
393 progress = indeterminate_depth.len();
394
395 indeterminate_depth.retain(|glyph| {
397 let max_component_depth = glyph
398 .layers
399 .iter()
400 .flat_map(|layer| layer.shapes.iter())
401 .filter_map(|shape| match shape {
402 Shape::Path(..) => None,
403 Shape::Component(c) => Some(&c.name),
404 })
405 .map(|name| depths.get(name).copied())
406 .try_fold(0, |acc, e| e.map(|e| acc.max(e)));
407 if let Some(max_component_depth) = max_component_depth {
408 depths.insert(&glyph.name, max_component_depth + 1);
409 }
410 max_component_depth.is_none() });
412
413 progress -= indeterminate_depth.len();
414 }
415
416 if !indeterminate_depth.is_empty() {
418 for g in indeterminate_depth.iter() {
420 depths.remove(&g.name);
421 }
422
423 if log::log_enabled!(log::Level::Warn) {
424 let mut names = indeterminate_depth
425 .into_iter()
426 .map(|g| g.name.as_str())
427 .collect::<Vec<_>>();
428 names.sort();
429 log::warn!(
430 "Invalid component graph (cycles or bad refs) for {} glyphs: {:?}",
431 names.len(),
432 names
433 );
434 }
435 }
436
437 let mut by_depth = depths
438 .into_iter()
439 .map(|(glyph, depth)| (depth, glyph))
440 .collect::<Vec<_>>();
441
442 by_depth.sort();
443 by_depth.into_iter().map(|(_, name)| name.clone()).collect()
444}
445
446#[cfg(test)]
447mod tests {
448
449 use std::collections::BTreeSet;
450
451 use kurbo::Point;
452
453 use crate::{glyphdata::GlyphData, Layer, Shape};
454
455 use super::*;
456
457 #[derive(Debug, Default)]
458 struct GlyphSetBuilder(BTreeMap<SmolStr, Glyph>);
459
460 impl GlyphSetBuilder {
461 fn new() -> Self {
462 Default::default()
463 }
464
465 fn build(&self) -> BTreeMap<SmolStr, Glyph> {
466 self.0.clone()
467 }
468
469 fn add_glyph(&mut self, name: &str, build_fn: impl FnOnce(&mut GlyphBuilder)) -> &mut Self {
470 let mut glyph = GlyphBuilder::new(name);
471 build_fn(&mut glyph);
472 self.0.insert(glyph.0.name.clone(), glyph.build());
473 self
474 }
475 }
476 #[derive(Debug, Default)]
478 struct GlyphBuilder(Glyph);
479
480 impl GlyphBuilder {
481 fn new(name: &str) -> Self {
482 let mut this = GlyphBuilder(Glyph {
483 name: name.into(),
484 export: true,
485 ..Default::default()
486 });
487 if let Some(result) = GlyphData::default().query(name, None) {
488 this.set_category(result.category);
489 if let Some(subcategory) = result.subcategory {
490 this.set_subcategory(subcategory);
491 }
492 if let Some(unicode) = result.codepoint {
493 this.set_unicode(unicode);
494 }
495 }
496 this.add_layer();
497 this
498 }
499
500 fn build(&self) -> Glyph {
501 self.0.clone()
502 }
503
504 fn add_layer(&mut self) -> &mut Self {
506 self.0.layers.push(Layer {
507 layer_id: format!("layer-{}", self.0.layers.len()),
508 ..Default::default()
509 });
510 self
511 }
512
513 fn last_layer_mut(&mut self) -> &mut Layer {
514 self.0.layers.last_mut().unwrap()
515 }
516
517 fn set_unicode(&mut self, unicode: u32) -> &mut Self {
518 self.0.unicode = BTreeSet::from([unicode]);
519 self
520 }
521
522 fn set_category(&mut self, category: Category) -> &mut Self {
523 self.0.category = Some(category);
524 self
525 }
526
527 fn set_subcategory(&mut self, sub_category: Subcategory) -> &mut Self {
528 self.0.sub_category = Some(sub_category);
529 self
530 }
531
532 fn add_component(&mut self, name: &str, pos: (i32, i32)) -> &mut Self {
534 self.last_layer_mut()
535 .shapes
536 .push(Shape::Component(Component {
537 name: name.into(),
538 transform: Affine::translate((pos.0 as f64, pos.1 as f64)),
539 ..Default::default()
540 }));
541 self
542 }
543
544 fn rotate_component(&mut self, degrees: f64) -> &mut Self {
546 if let Some(Shape::Component(comp)) = self.last_layer_mut().shapes.last_mut() {
547 comp.transform = comp.transform.pre_rotate(degrees.to_radians());
548 }
549 self
550 }
551
552 fn add_component_anchor(&mut self, name: &str) -> &mut Self {
554 if let Some(Shape::Component(comp)) = self.last_layer_mut().shapes.last_mut() {
555 comp.anchor = Some(name.into());
556 }
557 self
558 }
559 fn add_anchor(&mut self, name: &str, pos: (i32, i32)) -> &mut Self {
560 self.last_layer_mut().anchors.push(Anchor {
561 name: name.into(),
562 pos: Point::new(pos.0 as _, pos.1 as _),
563 });
564 self
565 }
566 }
567
568 impl PartialEq<(&str, (f64, f64))> for Anchor {
569 fn eq(&self, other: &(&str, (f64, f64))) -> bool {
570 self.name == other.0 && self.pos == other.1.into()
571 }
572 }
573
574 #[test]
575 fn components_by_depth() {
576 fn make_glyph(name: &str, components: &[&str]) -> Glyph {
577 let mut builder = GlyphBuilder::new(name);
578 for comp in components {
579 builder.add_component(comp, (0, 0)); }
581 builder.build()
582 }
583
584 let glyphs: &[(&str, &[&str])] = &[
585 ("A", &[]),
586 ("E", &[]),
587 ("acutecomb", &[]),
588 ("brevecomb", &[]),
589 ("brevecomb_acutecomb", &["acutecomb", "brevecomb"]),
590 ("AE", &["A", "E"]),
591 ("Aacute", &["A", "acutecomb"]),
592 ("Aacutebreve", &["A", "brevecomb_acutecomb"]),
593 ("AEacutebreve", &["AE", "brevecomb_acutecomb"]),
594 ];
595 let glyphs = glyphs
596 .iter()
597 .map(|(name, components)| make_glyph(name, components))
598 .map(|glyph| (glyph.name.clone(), glyph))
599 .collect();
600
601 let result = depth_sorted_composite_glyphs(&glyphs);
602 let expected = [
603 "A",
604 "E",
605 "acutecomb",
606 "brevecomb",
607 "AE",
608 "Aacute",
609 "brevecomb_acutecomb",
610 "AEacutebreve",
611 "Aacutebreve",
612 ]
613 .into_iter()
614 .map(SmolStr::new)
615 .collect::<Vec<_>>();
616
617 assert_eq!(result, expected)
618 }
619
620 #[test]
621 fn no_components_anchors_are_unchanged() {
622 let glyphs = GlyphSetBuilder::new()
624 .add_glyph("A", |glyph| {
625 glyph
626 .add_anchor("bottom", (234, 0))
627 .add_anchor("ogonek", (411, 0))
628 .add_anchor("top", (234, 810));
629 })
630 .add_glyph("acutecomb", |glyph| {
631 glyph
632 .add_anchor("_top", (0, 578))
633 .add_anchor("top", (0, 810));
634 })
635 .build();
636
637 let mut glyphs_2 = glyphs.clone();
638 propagate_all_anchors_impl(&mut glyphs_2);
639 assert_eq!(glyphs, glyphs_2, "nothing should change here");
640 }
641
642 #[test]
643 fn basic_composite_anchor() {
644 let mut glyphs = GlyphSetBuilder::new()
646 .add_glyph("A", |glyph| {
647 glyph
648 .add_anchor("bottom", (234, 0))
649 .add_anchor("ogonek", (411, 0))
650 .add_anchor("top", (234, 810));
651 })
652 .add_glyph("acutecomb", |glyph| {
653 glyph
654 .add_anchor("_top", (0, 578))
655 .add_anchor("top", (0, 810));
656 })
657 .add_glyph("Aacute", |glyph| {
658 glyph
659 .add_component("A", (0, 0))
660 .add_component("acutecomb", (234, 232));
661 })
662 .build();
663 propagate_all_anchors_impl(&mut glyphs);
664
665 let new_glyph = glyphs.get("Aacute").unwrap();
666 assert_eq!(
667 new_glyph.layers[0].anchors,
668 [
669 ("bottom", (234., 0.)),
670 ("ogonek", (411., 0.)),
671 ("top", (234., 1042.))
672 ]
673 );
674 }
675
676 #[test]
677 fn propagate_ligature_anchors() {
678 let mut glyphs = GlyphSetBuilder::new()
681 .add_glyph("I", |glyph| {
682 glyph
683 .add_anchor("bottom", (103, 0))
684 .add_anchor("ogonek", (103, 0))
685 .add_anchor("top", (103, 810))
686 .add_anchor("topleft", (20, 810));
687 })
688 .add_glyph("J", |glyph| {
689 glyph
690 .add_anchor("bottom", (133, 0))
691 .add_anchor("top", (163, 810));
692 })
693 .add_glyph("IJ", |glyph| {
694 glyph
695 .set_subcategory(Subcategory::Ligature)
698 .add_component("I", (0, 0))
699 .add_component("J", (206, 0));
700 })
701 .build();
702 propagate_all_anchors_impl(&mut glyphs);
703 let ij = glyphs.get("IJ").unwrap();
704 assert_eq!(
707 ij.layers[0].anchors,
708 [
709 ("bottom_1", (103., 0.)),
710 ("ogonek_1", (103., 0.)),
711 ("top_1", (103., 810.)),
712 ("topleft_1", (20., 810.)),
713 ("bottom_2", (339., 0.)),
714 ("top_2", (369., 810.))
715 ]
716 )
717 }
718
719 #[test]
720 fn digraphs_arent_ligatures() {
721 let mut glyphs = GlyphSetBuilder::new()
724 .add_glyph("I", |glyph| {
725 glyph
726 .add_anchor("bottom", (103, 0))
727 .add_anchor("ogonek", (103, 0))
728 .add_anchor("top", (103, 810))
729 .add_anchor("topleft", (20, 810));
730 })
731 .add_glyph("J", |glyph| {
732 glyph
733 .add_anchor("bottom", (133, 0))
734 .add_anchor("top", (163, 810));
735 })
736 .add_glyph("IJ", |glyph| {
737 glyph
738 .add_component("I", (0, 0))
739 .add_component("J", (206, 0));
740 })
741 .build();
742 propagate_all_anchors_impl(&mut glyphs);
743 let ij = glyphs.get("IJ").unwrap();
744 assert_eq!(
747 ij.layers[0].anchors,
748 [
749 ("bottom", (339., 0.)),
750 ("ogonek", (103., 0.)),
751 ("top", (369., 810.)),
752 ("topleft", (20., 810.)),
753 ]
754 )
755 }
756
757 #[test]
758 fn propagate_across_layers() {
759 let mut glyphs = GlyphSetBuilder::new()
761 .add_glyph("A", |glyph| {
762 glyph
763 .add_anchor("bottom", (290, 10))
764 .add_anchor("ogonek", (490, 3))
765 .add_anchor("top", (290, 690))
766 .add_layer()
767 .add_anchor("bottom", (300, 0))
768 .add_anchor("ogonek", (540, 10))
769 .add_anchor("top", (300, 700));
770 })
771 .add_glyph("acutecomb", |glyph| {
772 glyph
773 .add_anchor("_top", (335, 502))
774 .add_anchor("top", (353, 721))
775 .add_layer()
776 .add_anchor("_top", (366, 500))
777 .add_anchor("top", (366, 765));
778 })
779 .add_glyph("Aacute", |glyph| {
780 glyph
781 .add_component("A", (0, 0))
782 .add_component("acutecomb", (-45, 188))
783 .add_layer()
784 .add_component("A", (0, 0))
785 .add_component("acutecomb", (-66, 200));
786 })
787 .build();
788 propagate_all_anchors_impl(&mut glyphs);
789
790 let new_glyph = glyphs.get("Aacute").unwrap();
791 assert_eq!(
792 new_glyph.layers[0].anchors,
793 [
794 ("bottom", (290., 10.)),
795 ("ogonek", (490., 3.)),
796 ("top", (308., 909.))
797 ]
798 );
799
800 assert_eq!(
801 new_glyph.layers[1].anchors,
802 [
803 ("bottom", (300., 0.)),
804 ("ogonek", (540., 10.)),
805 ("top", (300., 965.))
806 ]
807 );
808 }
809
810 #[test]
811 fn remove_exit_anchor_on_component() {
812 let mut glyphs = GlyphSetBuilder::new()
814 .add_glyph("comma", |_| {})
815 .add_glyph("ain-ar.init", |glyph| {
816 glyph
817 .add_anchor("top", (294, 514))
818 .add_anchor("exit", (0, 0));
819 })
820 .add_glyph("ain-ar.init.alt", |glyph| {
821 glyph
822 .add_component("ain-ar.init", (0, 0))
823 .add_component("comma", (0, 0));
824 })
825 .build();
826 propagate_all_anchors_impl(&mut glyphs);
827
828 let new_glyph = glyphs.get("ain-ar.init.alt").unwrap();
829 assert_eq!(new_glyph.layers[0].anchors, [("top", (294., 514.)),]);
830 }
831
832 #[test]
833 fn component_anchor() {
834 let mut glyphs = GlyphSetBuilder::new()
836 .add_glyph("acutecomb", |glyph| {
837 glyph
838 .add_anchor("_top", (150, 580))
839 .add_anchor("top", (170, 792));
840 })
841 .add_glyph("aa", |glyph| {
842 glyph
843 .add_anchor("bottom_1", (218, 8))
844 .add_anchor("bottom_2", (742, 7))
845 .add_anchor("ogonek_1", (398, 9))
846 .add_anchor("ogonek_2", (902, 9))
847 .add_anchor("top_1", (227, 548))
848 .add_anchor("top_2", (746, 548));
849 })
850 .add_glyph("a_a", |glyph| {
851 glyph.add_component("aa", (0, 0));
852 })
853 .add_glyph("a_aacute", |glyph| {
854 glyph
855 .add_component("a_a", (0, 0))
856 .add_component("acutecomb", (596, -32))
857 .add_component_anchor("top_2");
858 })
859 .build();
860 propagate_all_anchors_impl(&mut glyphs);
861
862 let new_glyph = glyphs.get("a_aacute").unwrap();
863 assert_eq!(
864 new_glyph.layers[0].anchors,
865 [
866 ("bottom_1", (218., 8.)),
867 ("bottom_2", (742., 7.)),
868 ("ogonek_1", (398., 9.)),
869 ("ogonek_2", (902., 9.)),
870 ("top_1", (227., 548.)),
871 ("top_2", (766., 760.)),
872 ]
873 );
874 }
875
876 #[test]
877 fn origin_anchor() {
878 let mut glyphs = GlyphSetBuilder::new()
880 .add_glyph("a", |glyph| {
881 glyph
882 .add_anchor("*origin", (-20, 0))
883 .add_anchor("bottom", (242, 7))
884 .add_anchor("ogonek", (402, 9))
885 .add_anchor("top", (246, 548));
886 })
887 .add_glyph("acutecomb", |glyph| {
888 glyph
889 .add_anchor("_top", (150, 580))
890 .add_anchor("top", (170, 792));
891 })
892 .add_glyph("aacute", |glyph| {
893 glyph
894 .add_component("a", (0, 0))
895 .add_component("acutecomb", (116, -32));
896 })
897 .build();
898 propagate_all_anchors_impl(&mut glyphs);
899
900 let new_glyph = glyphs.get("aacute").unwrap();
901 assert_eq!(
902 new_glyph.layers[0].anchors,
903 [
904 ("bottom", (262.0, 7.0)),
905 ("ogonek", (422.0, 9.0)),
906 ("top", (286.0, 760.0)),
907 ]
908 );
909 }
910
911 #[test]
912 fn invert_names_on_rotation() {
913 let mut glyphs = GlyphSetBuilder::new()
915 .add_glyph("comma", |_| {})
916 .add_glyph("commaaccentcomb", |glyph| {
917 glyph
918 .add_anchor("_bottom", (289, 0))
919 .add_anchor("mybottom", (277, -308))
920 .add_component("comma", (9, -164));
921 })
922 .add_glyph("commaturnedabovecomb", |glyph| {
923 glyph
924 .add_component("commaaccentcomb", (589, 502))
925 .rotate_component(180.);
926 })
927 .build();
928 propagate_all_anchors_impl(&mut glyphs);
929
930 let new_glyph = glyphs.get("commaturnedabovecomb").unwrap();
931 assert_eq!(
932 new_glyph.layers[0].anchors,
933 [("_top", (300., 502.)), ("mytop", (312., 810.)),]
934 );
935 }
936
937 #[test]
938 fn affine_scale() {
939 let affine = Affine::rotate((180.0f64).to_radians()).then_translate((589., 502.).into());
940 let delta = get_xy_rotation(affine);
941 assert!(delta.x.is_sign_negative() && delta.y.is_sign_negative());
942
943 let affine = Affine::translate((10., 10.));
944 let delta = get_xy_rotation(affine);
945 assert!(delta.x.is_sign_positive() && delta.y.is_sign_positive());
946 let flip_y = get_xy_rotation(Affine::FLIP_Y);
947 assert!(flip_y.y.is_sign_negative());
948 assert!(flip_y.x.is_sign_positive());
949 let flip_x = get_xy_rotation(Affine::FLIP_X);
950 assert!(flip_x.y.is_sign_positive());
951 assert!(flip_x.x.is_sign_negative());
952
953 let rotate_flip = Affine::rotate((180.0f64).to_radians())
954 .then_translate((589., 502.).into())
955 * Affine::FLIP_X;
956 let rotate_flip = get_xy_rotation(rotate_flip);
957 assert!(rotate_flip.x.is_sign_positive());
958 assert!(rotate_flip.y.is_sign_negative());
959 }
960
961 #[test]
964 fn real_files() {
965 let expected =
966 Font::load_raw("../resources/testdata/glyphs3/PropagateAnchorsTest-propagated.glyphs")
967 .unwrap();
968 let font = Font::load(std::path::Path::new(
969 "../resources/testdata/glyphs3/PropagateAnchorsTest.glyphs",
970 ))
971 .unwrap();
972
973 assert_eq!(expected.glyphs.len(), font.glyphs.len());
974 assert!(expected
975 .glyphs
976 .keys()
977 .zip(font.glyphs.keys())
978 .all(|(a, b)| a == b));
979
980 for (g1, g2) in expected.glyphs.values().zip(font.glyphs.values()) {
981 assert_eq!(g1.layers.len(), g2.layers.len());
982 for (l1, l2) in g1.layers.iter().zip(g2.layers.iter()) {
983 let a1 = l1.anchors.clone();
984 let a2 = l2.anchors.clone();
985 assert_eq!(a1, a2, "{}", g1.name);
986 }
987 }
988 }
989
990 #[test]
991 fn composite_cycle() {
992 let _ = env_logger::builder().is_test(true).try_init();
993 let glyphs = GlyphSetBuilder::new()
994 .add_glyph("A", |glyph| {
995 glyph.add_component("B", (0, 0));
996 })
997 .add_glyph("B", |glyph| {
998 glyph.add_component("A", (0, 0));
999 })
1000 .build();
1001 let sorted = depth_sorted_composite_glyphs(&glyphs);
1003 assert!(sorted.is_empty())
1005 }
1006
1007 #[test]
1008 fn composite_not_a_cycle() {
1009 let _ = env_logger::builder().is_test(true).try_init();
1010 let glyphs = GlyphSetBuilder::new()
1011 .add_glyph("A", |_glyph| {})
1012 .add_glyph("B", |glyph| {
1013 glyph.add_component("A", (0, 0)).add_component("D", (0, 0));
1014 })
1015 .add_glyph("C", |_glyph| {})
1016 .add_glyph("D", |glyph| {
1017 glyph.add_component("E", (0, 0));
1018 })
1019 .add_glyph("E", |glyph| {
1020 glyph.add_component("C", (0, -180));
1021 })
1022 .build();
1023 let sorted = depth_sorted_composite_glyphs(&glyphs);
1024 assert_eq!(sorted, ["A", "C", "E", "D", "B"]);
1025 }
1026
1027 #[test]
1028 fn dont_propagate_anchors() {
1029 let font = Font::load(std::path::Path::new(
1030 "../resources/testdata/glyphs2/DontPropagateAnchors.glyphs",
1031 ))
1032 .unwrap();
1033 assert_eq!(font.custom_parameters.propagate_anchors, Some(false));
1034 let glyph = font.glyphs.get("Aacute").unwrap();
1035 assert!(glyph.layers.first().unwrap().anchors.is_empty());
1036 }
1037}