1use std::collections::{BTreeMap, BTreeSet, HashMap};
4
5use write_fonts::{
6 tables::layout::{builders::LookupBuilder, LookupFlag},
7 types::{GlyphId16, Tag},
8};
9
10use crate::GlyphSet;
11
12use super::{
13 features::{AllFeatures, FeatureLookups},
14 language_system::{DefaultLanguageSystems, LanguageSystem},
15 lookups::{AllLookups, FeatureKey, FilterSetId, LookupId, LookupIdMap, PositionLookup},
16 tables::{GdefBuilder, Tables},
17 CaretValue,
18};
19
20pub trait FeatureProvider {
22 fn add_features(&self, builder: &mut FeatureBuilder);
24}
25
26pub struct NopFeatureProvider;
28
29impl FeatureProvider for NopFeatureProvider {
30 fn add_features(&self, _: &mut FeatureBuilder) {}
31}
32
33pub struct FeatureBuilder<'a> {
35 pub(crate) language_systems: &'a DefaultLanguageSystems,
36 pub(crate) tables: &'a mut Tables,
37 pub(crate) lookups: Vec<(LookupId, PositionLookup)>,
38 pub(crate) features: BTreeMap<FeatureKey, FeatureLookups>,
39 pub(crate) lig_carets: BTreeMap<GlyphId16, Vec<CaretValue>>,
40 mark_filter_sets: &'a mut HashMap<GlyphSet, FilterSetId>,
41}
42
43pub trait GposSubtableBuilder: Sized {
44 #[doc(hidden)]
45 fn to_pos_lookup(
46 flags: LookupFlag,
47 filter_set: Option<FilterSetId>,
48 subtables: Vec<Self>,
49 ) -> ExternalGposLookup;
50}
51
52#[derive(Debug, Default, Clone, PartialEq)]
56#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
57pub struct PendingLookup<T> {
58 subtables: Vec<T>,
59 flags: LookupFlag,
60 mark_filter_set: Option<GlyphSet>,
61}
62
63impl<T> PendingLookup<T> {
64 pub fn new(subtables: Vec<T>, flags: LookupFlag, mark_filter_set: Option<GlyphSet>) -> Self {
68 Self {
69 subtables,
70 flags,
71 mark_filter_set,
72 }
73 }
74
75 pub fn subtables(&self) -> &[T] {
77 &self.subtables
78 }
79
80 pub fn flags(&self) -> LookupFlag {
82 self.flags
83 }
84}
85
86pub struct ExternalGposLookup(PositionLookup);
90
91impl<'a> FeatureBuilder<'a> {
92 pub(crate) fn new(
93 language_systems: &'a DefaultLanguageSystems,
94 tables: &'a mut Tables,
95 mark_filter_sets: &'a mut HashMap<GlyphSet, u16>,
96 ) -> Self {
97 Self {
98 language_systems,
99 tables,
100 lookups: Default::default(),
101 features: Default::default(),
102 mark_filter_sets,
103 lig_carets: Default::default(),
104 }
105 }
106
107 pub fn language_systems(&self) -> impl Iterator<Item = LanguageSystem> + 'a {
109 self.language_systems.iter()
110 }
111
112 pub fn gdef(&self) -> Option<&GdefBuilder> {
114 self.tables.gdef.as_ref()
115 }
116
117 pub fn add_lig_carets(&mut self, lig_carets: BTreeMap<GlyphId16, Vec<CaretValue>>) {
119 self.lig_carets = lig_carets;
120 }
121
122 pub fn add_lookup<T: GposSubtableBuilder>(&mut self, lookup: PendingLookup<T>) -> LookupId {
127 let PendingLookup {
128 subtables,
129 flags,
130 mark_filter_set,
131 } = lookup;
132 let filter_set_id = mark_filter_set.map(|cls| self.get_filter_set_id(cls));
133 let lookup = T::to_pos_lookup(flags, filter_set_id, subtables);
134 let next_id = LookupId::External(self.lookups.len());
135 self.lookups.push((next_id, lookup.0));
136 next_id
137 }
138
139 pub fn add_to_default_language_systems(&mut self, feature_tag: Tag, lookups: &[LookupId]) {
143 for langsys in self.language_systems() {
144 let feature_key = langsys.to_feature_key(feature_tag);
145 self.add_feature(feature_key, lookups.to_vec());
146 }
147 }
148
149 pub fn add_feature(&mut self, key: FeatureKey, lookups: Vec<LookupId>) {
154 self.features.entry(key).or_default().base = lookups;
155 }
156
157 fn get_filter_set_id(&mut self, cls: GlyphSet) -> FilterSetId {
158 let next_id = self.mark_filter_sets.len();
159 *self.mark_filter_sets.entry(cls).or_insert_with(|| {
160 next_id
161 .try_into()
162 .expect("too many filter sets?")
164 })
165 }
166
167 pub(crate) fn finish(self) -> ExternalFeatures {
168 let FeatureBuilder {
169 lookups,
170 features,
171 lig_carets,
172 ..
173 } = self;
174 ExternalFeatures {
175 features,
176 lookups,
177 lig_carets,
178 }
179 }
180}
181
182impl<T> GposSubtableBuilder for T
183where
184 T: Default,
185 LookupBuilder<T>: Into<PositionLookup>,
186{
187 fn to_pos_lookup(
188 flags: LookupFlag,
189 filter_set: Option<FilterSetId>,
190 subtables: Vec<Self>,
191 ) -> ExternalGposLookup {
192 ExternalGposLookup(LookupBuilder::new_with_lookups(flags, filter_set, subtables).into())
193 }
194}
195
196const CURS: Tag = Tag::new(b"curs");
198const MARK: Tag = Tag::new(b"mark");
199const MKMK: Tag = Tag::new(b"mkmk");
200const ABVM: Tag = Tag::new(b"abvm");
201const BLWM: Tag = Tag::new(b"blwm");
202const KERN: Tag = Tag::new(b"kern");
203const DIST: Tag = Tag::new(b"dist");
204
205pub(crate) struct ExternalFeatures {
207 pub(crate) lookups: Vec<(LookupId, PositionLookup)>,
208 pub(crate) features: BTreeMap<FeatureKey, FeatureLookups>,
209 pub(crate) lig_carets: BTreeMap<GlyphId16, Vec<CaretValue>>,
210}
211
212#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)]
213pub(crate) struct InsertionPoint {
214 pub(crate) lookup_id: LookupId,
216 pub(crate) priority: usize,
222}
223
224struct MergeCtx<'a> {
225 all_lookups: &'a mut AllLookups,
226 all_feats: &'a mut AllFeatures,
227 insert_markers: &'a HashMap<Tag, InsertionPoint>,
229 ext_lookups: BTreeMap<LookupId, PositionLookup>,
230 ext_features: BTreeMap<FeatureKey, FeatureLookups>,
231 processed_lookups: Vec<(InsertionPoint, Vec<(LookupId, PositionLookup)>)>,
233 append_priority: usize,
236}
237
238impl MergeCtx<'_> {
239 fn merge(mut self) {
240 self.do_curs();
261 self.do_kern_and_dist();
262 self.do_marks();
263
264 if !self.ext_lookups.is_empty() {
268 log::warn!("feature merging left unhandled features!");
269 }
270 self.finalize();
271 }
272
273 fn finalize(mut self) {
274 self.processed_lookups.sort_by_key(|(key, _)| *key);
275
276 let mut map = LookupIdMap::default();
280 let mut inserted_so_far = 0;
281
282 let mut adjustments = Vec::new();
284
285 for (insert_point, mut lookups) in self.processed_lookups {
286 let first_id = insert_point.lookup_id.to_raw();
287 lookups.sort_by_key(|(key, _)| *key);
290 for (i, (temp_id, _)) in lookups.iter().enumerate() {
292 let final_id = LookupId::Gpos(first_id + inserted_so_far + i);
293 map.insert(*temp_id, final_id);
294 }
295 let insert_at = first_id + inserted_so_far;
297 inserted_so_far += lookups.len();
298 self.all_lookups
299 .splice_gpos(insert_at, lookups.into_iter().map(|v| v.1.clone()));
300 adjustments.push((first_id, inserted_so_far));
301 }
302
303 if !adjustments.is_empty() {
307 adjustments.push((self.all_lookups.next_gpos_id().to_raw(), inserted_so_far));
309 }
310 let (mut range_start, mut adjust) = (0, 0);
311 let mut adjustments = adjustments.as_slice();
312 while let Some(((next_start, next_adjust), remaining)) = adjustments.split_first() {
313 if adjust > 0 {
314 for old_id in range_start..*next_start {
315 map.insert(LookupId::Gpos(old_id), LookupId::Gpos(old_id + adjust));
316 }
317 }
318 (range_start, adjust, adjustments) = (*next_start, *next_adjust, remaining);
319 }
320
321 self.all_feats.merge_external_features(self.ext_features);
322 self.all_feats.remap_ids(&map);
323 self.all_lookups.remap_ids(&map);
324 }
325
326 fn do_curs(&mut self) {
327 let curs_pos = self
328 .insert_markers
329 .get(&CURS)
330 .copied()
331 .unwrap_or_else(|| self.insertion_point_for_append());
332 self.finalize_lookups_for_feature(CURS, curs_pos);
333 }
334
335 fn do_kern_and_dist(&mut self) {
336 let marker = self
340 .insert_markers
341 .get(&DIST)
342 .or_else(|| self.insert_markers.get(&KERN))
343 .copied()
344 .unwrap_or_else(|| self.insertion_point_for_append());
345
346 let lookups = self.take_lookups_for_features(&[KERN, DIST]);
347 if !lookups.is_empty() {
348 self.processed_lookups.push((marker, lookups));
349 }
350 }
351
352 fn do_marks(&mut self) {
353 const ORDER: [Tag; 4] = [ABVM, BLWM, MARK, MKMK];
367 let mut inserts = [None; 4];
368 for (i, tag) in ORDER.iter().enumerate() {
369 inserts[i] = self.insert_markers.get(tag).copied();
370 }
371
372 for i in 0..ORDER.len() {
373 if let Some(insert) = inserts[i] {
374 for j in 0..i {
377 let j = i - j - 1; if inserts[j].is_none() {
380 inserts[j] = Some(InsertionPoint {
381 lookup_id: insert.lookup_id,
382 priority: insert.priority - 1,
383 })
384 }
385 }
386 }
387 }
388
389 for insert in inserts.iter_mut() {
392 if insert.is_none() {
393 *insert = Some(self.insertion_point_for_append());
394 }
395 }
396
397 self.finalize_lookups_for_feature(ABVM, inserts[0].unwrap());
399 self.finalize_lookups_for_feature(BLWM, inserts[1].unwrap());
400 self.finalize_lookups_for_feature(MARK, inserts[2].unwrap());
401 self.finalize_lookups_for_feature(MKMK, inserts[3].unwrap());
402 }
403
404 fn finalize_lookups_for_feature(&mut self, feature: Tag, pos: InsertionPoint) {
405 let lookups = self.take_lookups_for_features(&[feature]);
406 if !lookups.is_empty() {
407 self.processed_lookups.push((pos, lookups));
408 }
409 }
410
411 fn lookup_ids_for_features(&self, features: &[Tag]) -> BTreeSet<LookupId> {
412 self.ext_features
413 .iter()
414 .filter(|(feat, _)| features.contains(&feat.feature))
415 .flat_map(|(_, lookups)| lookups.iter_ids())
416 .collect()
417 }
418
419 fn take_lookups_for_features(&mut self, features: &[Tag]) -> Vec<(LookupId, PositionLookup)> {
420 self.lookup_ids_for_features(features)
421 .into_iter()
422 .map(|id| (id, self.ext_lookups.remove(&id).unwrap()))
423 .collect()
424 }
425
426 fn insertion_point_for_append(&mut self) -> InsertionPoint {
427 let lookup_id = self.all_lookups.next_gpos_id();
428 self.append_priority += 1;
429 InsertionPoint {
430 lookup_id,
431 priority: self.append_priority,
432 }
433 }
434}
435
436impl ExternalFeatures {
437 pub(crate) fn merge_into(
439 &mut self,
440 all_lookups: &mut AllLookups,
441 all_feats: &mut AllFeatures,
442 markers: &HashMap<Tag, InsertionPoint>,
443 ) {
444 let ctx = MergeCtx {
445 all_lookups,
446 all_feats,
447 ext_lookups: self.lookups.iter().cloned().collect(),
448 ext_features: self.features.clone(),
449 insert_markers: markers,
450 processed_lookups: Default::default(),
451 append_priority: 1_000_000_000,
452 };
453 ctx.merge();
454 }
455}
456
457#[cfg(test)]
458mod tests {
459 use super::*;
460
461 use crate::compile::tags::{LANG_DFLT, SCRIPT_DFLT};
462
463 impl AllFeatures {
464 fn feature_order_for_test(&self) -> Vec<Tag> {
472 let mut id_and_tag = self
473 .features
474 .iter()
475 .map(|(key, val)| (val.iter_ids().next().unwrap(), key.feature))
476 .collect::<Vec<_>>();
477 id_and_tag.sort();
478 id_and_tag.into_iter().map(|(_, tag)| tag).collect()
479 }
480 }
481
482 #[test]
483 fn merge_external_lookups_before() {
484 let mut all = AllLookups::default();
485 all.splice_gpos(
486 0,
487 (0..8).map(|_| PositionLookup::Single(Default::default())),
488 );
489
490 let lookups = (0..6)
491 .map(|id| {
492 (
493 LookupId::External(id),
494 PositionLookup::Pair(Default::default()),
495 )
496 })
497 .collect();
498 let features: BTreeMap<_, _> =
499 [(MARK, [0].as_slice()), (MKMK, &[1, 2]), (KERN, &[3, 4, 5])]
500 .iter()
501 .map(|(tag, ids)| {
502 let mut features = FeatureLookups::default();
503 features.base = ids.iter().copied().map(LookupId::External).collect();
504 (FeatureKey::new(*tag, LANG_DFLT, SCRIPT_DFLT), features)
505 })
506 .collect();
507
508 let markers = HashMap::from([
510 (
511 MARK,
512 InsertionPoint {
513 lookup_id: LookupId::Gpos(3),
514 priority: 100,
515 },
516 ),
517 (
518 KERN,
519 InsertionPoint {
520 lookup_id: LookupId::Gpos(5),
521 priority: 200,
522 },
523 ),
524 ]);
525
526 let mut external_features = ExternalFeatures {
527 lookups,
528 features,
529 lig_carets: Default::default(),
530 };
531
532 let mut all_features = AllFeatures::default();
533 external_features.merge_into(&mut all, &mut all_features, &markers);
534 let expected_ids: [(Tag, &[usize]); 3] =
535 [(MARK, &[3]), (MKMK, &[12, 13]), (KERN, &[6, 7, 8])];
536
537 for (tag, ids) in expected_ids {
538 let key = FeatureKey::new(tag, LANG_DFLT, SCRIPT_DFLT);
539 let result = all_features
540 .get_or_insert(key)
541 .iter_ids()
542 .map(|id| id.to_raw())
543 .collect::<Vec<_>>();
544 assert_eq!(ids, result)
545 }
546 }
547
548 fn mock_external_features(tags: &[Tag]) -> ExternalFeatures {
549 let mut lookups = Vec::new();
550 let mut features = BTreeMap::new();
551
552 for (i, feature) in tags.iter().enumerate() {
553 let id = LookupId::External(i);
554 let lookup = PositionLookup::Single(Default::default());
555 let key = FeatureKey::new(*feature, LANG_DFLT, SCRIPT_DFLT);
556
557 let mut feature_lookups = FeatureLookups::default();
558 feature_lookups.base = vec![id];
559 lookups.push((id, lookup));
560 features.insert(key, feature_lookups);
561 }
562 ExternalFeatures {
563 lookups,
564 features,
565 lig_carets: Default::default(),
566 }
567 }
568
569 fn make_markers_with_order<const N: usize>(order: [Tag; N]) -> HashMap<Tag, InsertionPoint> {
570 order
571 .into_iter()
572 .enumerate()
573 .map(|(i, tag)| {
574 (
575 tag,
576 InsertionPoint {
577 lookup_id: LookupId::Gpos(0),
578 priority: i + 10,
579 },
580 )
581 })
582 .collect()
583 }
584
585 #[test]
587 fn feature_ordering_without_markers() {
588 let mut external = mock_external_features(&[KERN, DIST, MKMK, ABVM, BLWM, MARK, CURS]);
589 let markers = make_markers_with_order([]);
590 let mut all = AllLookups::default();
591 let mut all_feats = AllFeatures::default();
592 external.merge_into(&mut all, &mut all_feats, &markers);
593
594 assert_eq!(
595 all_feats.feature_order_for_test(),
596 [CURS, KERN, DIST, ABVM, BLWM, MARK, MKMK]
597 );
598 }
599
600 #[test]
601 fn kern_and_dist_respect_input_order() {
602 let mut external = mock_external_features(&[DIST, KERN, CURS]);
606
607 let markers = make_markers_with_order([]);
608 let mut all = AllLookups::default();
609 let mut all_feats = AllFeatures::default();
610 external.merge_into(&mut all, &mut all_feats, &markers);
611 assert_eq!(all_feats.feature_order_for_test(), [CURS, DIST, KERN]);
612 }
613
614 #[test]
615 fn kern_and_dist_respect_input_order_with_marker() {
616 let mut external = mock_external_features(&[CURS, DIST, KERN]);
621
622 let markers = make_markers_with_order([KERN]);
623 let mut all = AllLookups::default();
624 let mut all_feats = AllFeatures::default();
625 external.merge_into(&mut all, &mut all_feats, &markers);
626 assert_eq!(all_feats.feature_order_for_test(), [DIST, KERN, CURS]);
627 }
628
629 #[test]
630 fn blwm_with_marker_takes_abvm_with_it() {
631 let mut external = mock_external_features(&[BLWM, ABVM, DIST]);
632 let markers = make_markers_with_order([BLWM]);
633 let mut all = AllLookups::default();
634 let mut all_feats = AllFeatures::default();
635 external.merge_into(&mut all, &mut all_feats, &markers);
636 assert_eq!(all_feats.feature_order_for_test(), [ABVM, BLWM, DIST]);
638 }
639
640 #[test]
641 fn marks_with_marker_goes_before_kern() {
642 let mut external = mock_external_features(&[MARK, KERN]);
643 let markers = make_markers_with_order([MARK]);
645 let mut all = AllLookups::default();
646 let mut all_feats = AllFeatures::default();
647 external.merge_into(&mut all, &mut all_feats, &markers);
648 assert_eq!(all_feats.feature_order_for_test(), [MARK, KERN]);
649 }
650
651 #[test]
652 fn mkmk_brings_along_the_whole_family() {
653 let mut external = mock_external_features(&[BLWM, KERN, MKMK, DIST, MARK, ABVM]);
654 let markers = make_markers_with_order([MKMK]);
655 let mut all = AllLookups::default();
656 let mut all_feats = AllFeatures::default();
657 external.merge_into(&mut all, &mut all_feats, &markers);
658 assert_eq!(
659 all_feats.feature_order_for_test(),
660 [ABVM, BLWM, MARK, MKMK, KERN, DIST]
662 );
663 }
664}