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