1mod contextual;
4
5use std::{
6 collections::{BTreeMap, HashMap, HashSet},
7 convert::TryInto,
8 fmt::Debug,
9};
10
11use smol_str::SmolStr;
12
13use write_fonts::{
14 tables::{
15 gdef::GlyphClassDef,
16 gpos::{
17 self as write_gpos,
18 builders::{
19 AnchorBuilder as Anchor, CursivePosBuilder, MarkToBaseBuilder, MarkToLigBuilder,
20 MarkToMarkBuilder, PairPosBuilder, SinglePosBuilder,
21 ValueRecordBuilder as ValueRecord,
22 },
23 },
24 gsub::{
25 self as write_gsub,
26 builders::{
27 AlternateSubBuilder, LigatureSubBuilder, MultipleSubBuilder, SingleSubBuilder,
28 },
29 },
30 layout::{
31 ConditionSet as RawConditionSet, Feature, FeatureList, FeatureRecord,
32 FeatureTableSubstitution, FeatureTableSubstitutionRecord, FeatureVariationRecord,
33 FeatureVariations, LangSys, LangSysRecord, LookupFlag, LookupList, Script, ScriptList,
34 ScriptRecord,
35 builders::{Builder, LookupBuilder},
36 },
37 variations::ivs_builder::VariationStoreBuilder,
38 },
39 types::Tag,
40};
41
42use crate::{
43 Kind, Opts,
44 common::{GlyphId16, GlyphOrClass, GlyphSet},
45 compile::lookups::contextual::ChainOrNot,
46};
47
48use super::{features::AllFeatures, tags};
49
50use contextual::{
51 ContextualLookupBuilder, PosChainContextBuilder, PosContextBuilder, ReverseChainBuilder,
52 SubChainContextBuilder, SubContextBuilder,
53};
54
55pub(crate) type FilterSetId = u16;
56
57#[derive(Clone, Debug, Default)]
58pub(crate) struct AllLookups {
59 current: Option<SomeLookup>,
60 current_name: Option<SmolStr>,
61 gpos: Vec<PositionLookup>,
62 gsub: Vec<SubstitutionLookup>,
63 named: HashMap<SmolStr, LookupId>,
64}
65
66#[derive(Clone, Debug)]
67pub(crate) enum PositionLookup {
68 Single(LookupBuilder<SinglePosBuilder>),
69 Pair(LookupBuilder<PairPosBuilder>),
70 Cursive(LookupBuilder<CursivePosBuilder>),
71 MarkToBase(LookupBuilder<MarkToBaseBuilder>),
72 MarkToLig(LookupBuilder<MarkToLigBuilder>),
73 MarkToMark(LookupBuilder<MarkToMarkBuilder>),
74 #[allow(dead_code)]
76 Contextual(LookupBuilder<PosContextBuilder>),
77 ChainedContextual(LookupBuilder<PosChainContextBuilder>),
78}
79
80macro_rules! impl_into_lookup {
86 ($builder:ty, $typ:ident, $variant:ident) => {
87 impl From<LookupBuilder<$builder>> for $typ {
88 fn from(src: LookupBuilder<$builder>) -> $typ {
89 $typ::$variant(src)
90 }
91 }
92 };
93}
94
95impl_into_lookup!(PairPosBuilder, PositionLookup, Pair);
96impl_into_lookup!(MarkToBaseBuilder, PositionLookup, MarkToBase);
97impl_into_lookup!(MarkToMarkBuilder, PositionLookup, MarkToMark);
98impl_into_lookup!(MarkToLigBuilder, PositionLookup, MarkToLig);
99impl_into_lookup!(CursivePosBuilder, PositionLookup, Cursive);
100impl_into_lookup!(SingleSubBuilder, SubstitutionLookup, Single);
101
102#[derive(Clone, Debug)]
103pub(crate) enum SubstitutionLookup {
104 Single(LookupBuilder<SingleSubBuilder>),
105 Multiple(LookupBuilder<MultipleSubBuilder>),
106 Alternate(LookupBuilder<AlternateSubBuilder>),
107 Ligature(LookupBuilder<LigatureSubBuilder>),
108 Contextual(LookupBuilder<SubContextBuilder>),
109 ChainedContextual(LookupBuilder<SubChainContextBuilder>),
110 Reverse(LookupBuilder<ReverseChainBuilder>),
111}
112
113#[derive(Clone, Debug)]
114pub(crate) enum SomeLookup {
115 GsubLookup(SubstitutionLookup),
116 GposLookup(PositionLookup),
117 GposContextual(ContextualLookupBuilder<PositionLookup>),
118 GsubContextual(ContextualLookupBuilder<SubstitutionLookup>),
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
123pub enum LookupId {
124 Gpos(usize),
126 Gsub(usize),
128 ExternalGpos(usize),
133 ExternalGsub(usize),
135 ExternalFrontOfList(usize),
140 Empty,
144}
145
146#[derive(Clone, Debug, Default)]
154pub(crate) struct LookupIdMap {
155 mapping: HashMap<LookupId, LookupId>,
157}
158
159#[derive(Clone, Copy, Debug, Default, PartialEq)]
161pub(crate) struct LookupFlagInfo {
162 pub(crate) flags: LookupFlag,
163 pub(crate) mark_filter_set: Option<FilterSetId>,
164}
165
166#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
168#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
169pub struct FeatureKey {
170 pub(crate) feature: Tag,
171 pub(crate) language: Tag,
172 pub(crate) script: Tag,
173}
174
175type FeatureIdx = u16;
176type LookupIdx = u16;
177
178pub(crate) struct PosSubBuilder<T> {
180 lookups: Vec<T>,
181 scripts: BTreeMap<Tag, BTreeMap<Tag, LangSys>>,
182 features: BTreeMap<(Tag, Vec<LookupIdx>), FeatureIdx>,
184 variations: HashMap<RawConditionSet, HashMap<FeatureIdx, Vec<LookupIdx>>>,
186}
187
188trait RemapIds {
189 fn remap_ids(&mut self, id_map: &LookupIdMap);
190}
191
192impl<T: RemapIds> RemapIds for LookupBuilder<T> {
193 fn remap_ids(&mut self, id_map: &LookupIdMap) {
194 for lookup in &mut self.subtables {
195 lookup.remap_ids(id_map);
196 }
197 }
198}
199
200pub(crate) trait IterAaltPairs {
201 fn iter_aalt_pairs(&self) -> impl Iterator<Item = (GlyphId16, GlyphId16)>;
202}
203
204impl IterAaltPairs for LigatureSubBuilder {
205 fn iter_aalt_pairs(&self) -> impl Iterator<Item = (GlyphId16, GlyphId16)> {
206 self.iter()
207 .flat_map(|(targ, ligs)| ligs.iter().map(|x| (*targ, x)))
208 .filter_map(|(targ, (components, replacement))| {
209 if components.is_empty() {
210 Some((targ, *replacement))
211 } else {
212 None
213 }
214 })
215 }
216}
217
218impl IterAaltPairs for MultipleSubBuilder {
219 fn iter_aalt_pairs(&self) -> impl Iterator<Item = (GlyphId16, GlyphId16)> {
220 self.iter()
221 .filter_map(|(targ, replacement)| match replacement.as_slice() {
222 &[just_one] => Some((*targ, just_one)),
223 _ => None,
224 })
225 }
226}
227
228impl IterAaltPairs for AlternateSubBuilder {
229 fn iter_aalt_pairs(&self) -> impl Iterator<Item = (GlyphId16, GlyphId16)> {
230 self.iter_pairs()
231 }
232}
233
234impl IterAaltPairs for SingleSubBuilder {
235 fn iter_aalt_pairs(&self) -> impl Iterator<Item = (GlyphId16, GlyphId16)> {
236 self.iter()
237 }
238}
239
240impl PositionLookup {
241 fn remap_ids(&mut self, id_map: &LookupIdMap) {
242 match self {
243 PositionLookup::Contextual(lookup) => lookup.remap_ids(id_map),
244 PositionLookup::ChainedContextual(lookup) => lookup.remap_ids(id_map),
245 _ => (),
246 }
247 }
248
249 fn kind(&self) -> Kind {
250 match self {
251 PositionLookup::Single(_) => Kind::GposType1,
252 PositionLookup::Pair(_) => Kind::GposType2,
253 PositionLookup::Cursive(_) => Kind::GposType3,
254 PositionLookup::MarkToBase(_) => Kind::GposType4,
255 PositionLookup::MarkToLig(_) => Kind::GposType5,
256 PositionLookup::MarkToMark(_) => Kind::GposType6,
257 PositionLookup::Contextual(_) => Kind::GposType7,
258 PositionLookup::ChainedContextual(_) => Kind::GposType8,
259 }
260 }
261
262 fn force_subtable_break(&mut self) {
263 match self {
264 PositionLookup::Single(lookup) => lookup.force_subtable_break(),
265 PositionLookup::Pair(lookup) => lookup.force_subtable_break(),
266 PositionLookup::Cursive(lookup) => lookup.force_subtable_break(),
267 PositionLookup::MarkToBase(lookup) => lookup.force_subtable_break(),
268 PositionLookup::MarkToLig(lookup) => lookup.force_subtable_break(),
269 PositionLookup::MarkToMark(lookup) => lookup.force_subtable_break(),
270 PositionLookup::Contextual(lookup) => lookup.force_subtable_break(),
271 PositionLookup::ChainedContextual(lookup) => lookup.force_subtable_break(),
272 }
273 }
274}
275
276impl SubstitutionLookup {
277 fn remap_ids(&mut self, id_map: &LookupIdMap) {
278 match self {
279 SubstitutionLookup::Contextual(lookup) => lookup.remap_ids(id_map),
280 SubstitutionLookup::ChainedContextual(lookup) => lookup.remap_ids(id_map),
281 _ => (),
282 }
283 }
284
285 fn kind(&self) -> Kind {
286 match self {
287 SubstitutionLookup::Single(_) => Kind::GsubType1,
288 SubstitutionLookup::Multiple(_) => Kind::GsubType2,
289 SubstitutionLookup::Alternate(_) => Kind::GsubType3,
290 SubstitutionLookup::Ligature(_) => Kind::GsubType4,
291 SubstitutionLookup::Contextual(_) => Kind::GsubType5,
292 SubstitutionLookup::ChainedContextual(_) => Kind::GsubType6,
293 SubstitutionLookup::Reverse(_) => Kind::GsubType8,
294 }
295 }
296
297 fn force_subtable_break(&mut self) {
298 match self {
299 SubstitutionLookup::Single(lookup) => lookup.force_subtable_break(),
300 SubstitutionLookup::Multiple(lookup) => lookup.force_subtable_break(),
301 SubstitutionLookup::Alternate(lookup) => lookup.force_subtable_break(),
302 SubstitutionLookup::Ligature(lookup) => lookup.force_subtable_break(),
303 SubstitutionLookup::Contextual(lookup) => lookup.force_subtable_break(),
304 SubstitutionLookup::Reverse(lookup) => lookup.force_subtable_break(),
305 SubstitutionLookup::ChainedContextual(lookup) => lookup.force_subtable_break(),
306 }
307 }
308}
309
310impl Builder for PositionLookup {
311 type Output = write_gpos::PositionLookup;
312
313 fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
314 match self {
315 PositionLookup::Single(lookup) => {
316 write_gpos::PositionLookup::Single(lookup.build(var_store))
317 }
318 PositionLookup::Pair(lookup) => {
319 write_gpos::PositionLookup::Pair(lookup.build(var_store))
320 }
321 PositionLookup::Cursive(lookup) => {
322 write_gpos::PositionLookup::Cursive(lookup.build(var_store))
323 }
324 PositionLookup::MarkToBase(lookup) => {
325 write_gpos::PositionLookup::MarkToBase(lookup.build(var_store))
326 }
327 PositionLookup::MarkToLig(lookup) => {
328 write_gpos::PositionLookup::MarkToLig(lookup.build(var_store))
329 }
330 PositionLookup::MarkToMark(lookup) => {
331 write_gpos::PositionLookup::MarkToMark(lookup.build(var_store))
332 }
333 PositionLookup::Contextual(lookup) => {
334 write_gpos::PositionLookup::Contextual(lookup.build(var_store).into_concrete())
335 }
336 PositionLookup::ChainedContextual(lookup) => {
337 write_gpos::PositionLookup::ChainContextual(lookup.build(var_store).into_concrete())
338 }
339 }
340 }
341}
342
343impl Builder for SubstitutionLookup {
344 type Output = write_gsub::SubstitutionLookup;
345
346 fn build(self, _var_store: &mut VariationStoreBuilder) -> Self::Output {
347 match self {
348 SubstitutionLookup::Single(lookup) => {
349 write_gsub::SubstitutionLookup::Single(lookup.build(_var_store))
350 }
351 SubstitutionLookup::Multiple(lookup) => {
352 write_gsub::SubstitutionLookup::Multiple(lookup.build(_var_store))
353 }
354 SubstitutionLookup::Alternate(lookup) => {
355 write_gsub::SubstitutionLookup::Alternate(lookup.build(_var_store))
356 }
357 SubstitutionLookup::Ligature(lookup) => {
358 write_gsub::SubstitutionLookup::Ligature(lookup.build(_var_store))
359 }
360 SubstitutionLookup::Contextual(lookup) => {
361 write_gsub::SubstitutionLookup::Contextual(lookup.build(_var_store).into_concrete())
362 }
363 SubstitutionLookup::ChainedContextual(lookup) => {
364 write_gsub::SubstitutionLookup::ChainContextual(
365 lookup.build(_var_store).into_concrete(),
366 )
367 }
368 SubstitutionLookup::Reverse(lookup) => {
369 write_gsub::SubstitutionLookup::Reverse(lookup.build(_var_store))
370 }
371 }
372 }
373}
374
375impl AllLookups {
376 fn push(&mut self, lookup: SomeLookup) -> LookupId {
377 match lookup {
378 SomeLookup::GsubLookup(sub) => {
379 self.gsub.push(sub);
380 LookupId::Gsub(self.gsub.len() - 1)
381 }
382 SomeLookup::GposLookup(pos) => {
383 self.gpos.push(pos);
384 LookupId::Gpos(self.gpos.len() - 1)
385 }
386 SomeLookup::GposContextual(lookup) => {
387 let id = LookupId::Gpos(self.gpos.len());
388 assert_eq!(id, lookup.root_id); let (lookup, anon_lookups) = lookup.into_lookups();
390 match lookup {
391 ChainOrNot::Context(lookup) => self
392 .gpos
393 .push(PositionLookup::ChainedContextual(lookup.convert())),
396 ChainOrNot::Chain(lookup) => self
397 .gpos
398 .push(PositionLookup::ChainedContextual(lookup.convert())),
399 }
400 self.gpos.extend(anon_lookups);
401 id
402 }
403 SomeLookup::GsubContextual(lookup) => {
404 let id = LookupId::Gsub(self.gsub.len());
405 assert_eq!(id, lookup.root_id); let (lookup, anon_lookups) = lookup.into_lookups();
407 match lookup {
408 ChainOrNot::Context(lookup) => self
409 .gsub
410 .push(SubstitutionLookup::Contextual(lookup.convert())),
411 ChainOrNot::Chain(lookup) => self
412 .gsub
413 .push(SubstitutionLookup::ChainedContextual(lookup.convert())),
414 }
415 self.gsub.extend(anon_lookups);
416 id
417 }
418 }
419 }
420
421 pub(crate) fn get_named(&self, name: &str) -> Option<LookupId> {
422 self.named.get(name).copied()
423 }
424
425 pub(crate) fn current_mut(&mut self) -> Option<&mut SomeLookup> {
426 self.current.as_mut()
427 }
428
429 pub(crate) fn has_current(&self) -> bool {
430 self.current.is_some()
431 }
432
433 pub(crate) fn next_gpos_id(&self) -> LookupId {
434 LookupId::Gpos(self.gpos.len())
435 }
436
437 pub(crate) fn next_gsub_id(&self) -> LookupId {
438 LookupId::Gsub(self.gsub.len())
439 }
440
441 pub(crate) fn splice_gpos(
447 &mut self,
448 pos: usize,
449 lookups: impl IntoIterator<Item = PositionLookup>,
450 ) {
451 self.gpos.splice(pos..pos, lookups);
452 }
453
454 pub(crate) fn splice_gsub(
460 &mut self,
461 pos: usize,
462 lookups: impl IntoIterator<Item = SubstitutionLookup>,
463 ) {
464 self.gsub.splice(pos..pos, lookups);
465 }
466
467 pub(crate) fn has_current_kind(&self, kind: Kind) -> bool {
469 self.current.as_ref().map(SomeLookup::kind) == Some(kind)
470 }
471
472 pub(crate) fn has_same_flags(&self, flags: LookupFlagInfo) -> bool {
473 self.current.as_ref().map(SomeLookup::flags) == Some(flags)
474 }
475
476 pub(crate) fn add_subtable_break(&mut self) -> bool {
478 if let Some(current) = self.current.as_mut() {
479 match current {
480 SomeLookup::GsubLookup(lookup) => lookup.force_subtable_break(),
481 SomeLookup::GposLookup(lookup) => lookup.force_subtable_break(),
482 SomeLookup::GposContextual(lookup) => lookup.force_subtable_break(),
483 SomeLookup::GsubContextual(lookup) => lookup.force_subtable_break(),
484 }
485 true
486 } else {
487 false
488 }
489 }
490
491 pub(crate) fn start_named(&mut self, name: SmolStr) {
493 self.current_name = Some(name);
494 }
495
496 pub(crate) fn start_lookup(&mut self, kind: Kind, flags: LookupFlagInfo) -> Option<LookupId> {
497 let finished_id = self.current.take().map(|lookup| self.push(lookup));
498 let mut new_one = SomeLookup::new(kind, flags.flags, flags.mark_filter_set);
499
500 let new_id = if is_gpos_rule(kind) {
501 LookupId::Gpos(self.gpos.len())
502 } else {
503 LookupId::Gsub(self.gsub.len())
504 };
505
506 match &mut new_one {
507 SomeLookup::GsubContextual(lookup) => lookup.root_id = new_id,
508 SomeLookup::GposContextual(lookup) => lookup.root_id = new_id,
509 SomeLookup::GsubLookup(_) | SomeLookup::GposLookup(_) => (),
511 }
512 self.current = Some(new_one);
513 finished_id
514 }
515
516 pub(crate) fn finish_current(&mut self) -> Option<(LookupId, Option<SmolStr>)> {
517 if let Some(lookup) = self.current.take() {
518 let id = self.push(lookup);
519 if let Some(name) = self.current_name.take() {
520 self.named.insert(name.clone(), id);
521 Some((id, Some(name)))
522 } else {
523 Some((id, None))
524 }
525 } else if let Some(name) = self.current_name.take() {
526 self.named.insert(name.clone(), LookupId::Empty);
527 Some((LookupId::Empty, Some(name)))
529 } else {
530 None
531 }
532 }
533
534 pub(crate) fn promote_single_sub_to_multi_if_necessary(&mut self) {
535 if !self.has_current_kind(Kind::GsubType1) {
536 return;
537 }
538 let Some(SomeLookup::GsubLookup(SubstitutionLookup::Single(lookup))) = self.current.take()
539 else {
540 unreachable!()
541 };
542 let promoted = LookupBuilder {
543 flags: lookup.flags,
544 mark_set: lookup.mark_set,
545 subtables: lookup
546 .subtables
547 .into_iter()
548 .map(SingleSubBuilder::promote_to_multi_sub)
549 .collect(),
550 };
551 self.current = Some(SomeLookup::GsubLookup(SubstitutionLookup::Multiple(
552 promoted,
553 )));
554 }
555
556 pub(crate) fn promote_single_sub_to_liga_if_necessary(&mut self) {
557 if !self.has_current_kind(Kind::GsubType1) {
558 return;
559 }
560
561 let Some(SomeLookup::GsubLookup(SubstitutionLookup::Single(lookup))) = self.current.take()
562 else {
563 return;
564 };
565 let promoted = LookupBuilder {
566 flags: lookup.flags,
567 mark_set: lookup.mark_set,
568 subtables: lookup
569 .subtables
570 .into_iter()
571 .map(SingleSubBuilder::promote_to_ligature_sub)
572 .collect(),
573 };
574 self.current = Some(SomeLookup::GsubLookup(SubstitutionLookup::Ligature(
575 promoted,
576 )));
577 }
578
579 pub(crate) fn infer_glyph_classes(&self, mut f: impl FnMut(GlyphId16, GlyphClassDef)) {
580 for lookup in &self.gpos {
581 match lookup {
582 PositionLookup::MarkToBase(lookup) => {
583 for subtable in &lookup.subtables {
584 subtable
585 .base_glyphs()
586 .for_each(|k| f(k, GlyphClassDef::Base));
587 subtable
588 .mark_glyphs()
589 .for_each(|k| f(k, GlyphClassDef::Mark));
590 }
591 }
592 PositionLookup::MarkToLig(lookup) => {
593 for subtable in &lookup.subtables {
594 subtable
595 .lig_glyphs()
596 .for_each(|k| f(k, GlyphClassDef::Ligature));
597 subtable
598 .mark_glyphs()
599 .for_each(|k| f(k, GlyphClassDef::Mark));
600 }
601 }
602 PositionLookup::MarkToMark(lookup) => {
603 for subtable in &lookup.subtables {
604 subtable
605 .mark1_glyphs()
606 .chain(subtable.mark2_glyphs())
607 .for_each(|k| f(k, GlyphClassDef::Mark));
608 }
609 }
610 _ => (),
611 }
612 }
613 }
615
616 pub(crate) fn aalt_lookups(&self, id: LookupId) -> Vec<&SubstitutionLookup> {
621 let mut collect = Vec::new();
622 let mut seen = HashSet::new();
623 self.aalt_lookups_impl(id, &mut collect, &mut seen);
624 collect
625 }
626
627 fn aalt_lookups_impl<'a>(
628 &'a self,
629 id: LookupId,
630 collect: &mut Vec<&'a SubstitutionLookup>,
631 seen: &mut HashSet<LookupId>,
632 ) {
633 let Some(lookup) = self.get_gsub_lookup(&id) else {
634 return;
635 };
636 if !seen.insert(id) {
637 return;
638 }
639
640 match lookup {
641 SubstitutionLookup::Single(_)
642 | SubstitutionLookup::Alternate(_)
643 | SubstitutionLookup::Multiple(_)
644 | SubstitutionLookup::Ligature(_) => collect.push(lookup),
645
646 SubstitutionLookup::Contextual(lookup) => lookup
647 .subtables
648 .iter()
649 .flat_map(|sub| sub.iter_lookups())
650 .for_each(|id| self.aalt_lookups_impl(id, collect, seen)),
651 SubstitutionLookup::ChainedContextual(lookup) => lookup
652 .subtables
653 .iter()
654 .flat_map(|sub| sub.iter_lookups())
655 .for_each(|id| self.aalt_lookups_impl(id, collect, seen)),
656 _ => (),
657 }
658 }
659
660 fn get_gsub_lookup(&self, id: &LookupId) -> Option<&SubstitutionLookup> {
661 match id {
662 LookupId::Gsub(idx) => self.gsub.get(*idx),
663 _ => None,
664 }
665 }
666
667 pub(crate) fn remap_ids(&mut self, ids: &LookupIdMap) {
668 self.gpos
669 .iter_mut()
670 .for_each(|lookup| lookup.remap_ids(ids));
671 self.gsub
672 .iter_mut()
673 .for_each(|lookup| lookup.remap_ids(ids));
674 }
675
676 pub(crate) fn insert_aalt_lookups(
677 &mut self,
678 insert_point: usize,
679 all_alts: HashMap<GlyphId16, Vec<GlyphId16>>,
680 ) -> Vec<LookupId> {
681 let mut single = SingleSubBuilder::default();
682 let mut alt = AlternateSubBuilder::default();
683
684 for (target, alts) in all_alts {
685 if alts.len() == 1 {
686 single.insert(target, alts[0]);
687 } else {
688 alt.insert(target, alts);
689 }
690 }
691 let one = (!single.is_empty()).then(|| {
692 SubstitutionLookup::Single(LookupBuilder::new_with_lookups(
693 LookupFlag::empty(),
694 None,
695 vec![single],
696 ))
697 });
698 let two = (!alt.is_empty()).then(|| {
699 SubstitutionLookup::Alternate(LookupBuilder::new_with_lookups(
700 LookupFlag::empty(),
701 None,
702 vec![alt],
703 ))
704 });
705
706 let lookups = one.into_iter().chain(two).collect::<Vec<_>>();
707 let lookup_ids = (insert_point..insert_point + lookups.len())
708 .map(LookupId::Gsub)
709 .collect();
710
711 self.gsub.iter_mut().for_each(|lookup| match lookup {
715 SubstitutionLookup::Contextual(lookup) => lookup
716 .subtables
717 .iter_mut()
718 .for_each(|sub| sub.bump_all_lookup_ids(insert_point, lookups.len())),
719 SubstitutionLookup::ChainedContextual(lookup) => lookup
720 .subtables
721 .iter_mut()
722 .for_each(|sub| sub.bump_all_lookup_ids(insert_point, lookups.len())),
723 _ => (),
724 });
725
726 self.gsub.splice(insert_point..insert_point, lookups);
727
728 lookup_ids
729 }
730
731 pub(crate) fn build(
732 &self,
733 features: &AllFeatures,
734 var_store: &mut VariationStoreBuilder,
735 opts: &Opts,
736 ) -> (Option<write_gsub::Gsub>, Option<write_gpos::Gpos>) {
737 let mut gpos_builder = PosSubBuilder::new(self.gpos.clone());
738 let mut gsub_builder = PosSubBuilder::new(self.gsub.clone());
739
740 for (key, feature_lookups) in features.iter() {
741 let required = features.is_required(key);
742
743 if key.feature == tags::SIZE {
744 gpos_builder.add(*key, Vec::new(), required);
745 continue;
746 }
747
748 let (gpos_idxes, gsub_idxes) = feature_lookups.split_base_lookups();
749 let mut gpos_feat_id = None;
750 let mut gsub_feat_id = None;
751 if opts.compile_gpos && !gpos_idxes.is_empty() {
752 gpos_feat_id = Some(gpos_builder.add(*key, gpos_idxes.clone(), required));
753 }
754
755 if opts.compile_gsub && !gsub_idxes.is_empty() {
756 gsub_feat_id = Some(gsub_builder.add(*key, gsub_idxes.clone(), required));
757 }
758
759 let variations = feature_lookups.split_variations();
760 for (cond, gpos_var_idxes, gsub_var_idxes) in variations {
761 if opts.compile_gpos && !gpos_var_idxes.is_empty() {
762 let mut all_ids = gpos_idxes.clone();
764 all_ids.extend(gpos_var_idxes);
765
766 let feat_id = gpos_feat_id
769 .get_or_insert_with(|| gpos_builder.add(*key, Vec::new(), false));
770 gpos_builder.add_variation(*feat_id, cond, all_ids);
771 }
772 if opts.compile_gsub && !gsub_var_idxes.is_empty() {
773 let mut all_ids = gsub_idxes.clone();
775 all_ids.extend(gsub_var_idxes);
776
777 let feat_id = gsub_feat_id
778 .get_or_insert_with(|| gsub_builder.add(*key, Vec::new(), false));
779
780 gsub_builder.add_variation(*feat_id, cond, all_ids);
781 }
782 }
783 }
784
785 (gsub_builder.build(var_store), gpos_builder.build(var_store))
786 }
787}
788
789impl LookupId {
790 pub(crate) fn to_raw(self) -> usize {
791 match self {
792 LookupId::Gpos(idx) => idx,
793 LookupId::Gsub(idx) => idx,
794 LookupId::Empty => usize::MAX,
795 LookupId::ExternalGpos(idx)
796 | LookupId::ExternalGsub(idx)
797 | LookupId::ExternalFrontOfList(idx) => idx,
798 }
799 }
800
801 pub(crate) fn adjust_if_gsub(&mut self, value: usize) {
802 if let LookupId::Gsub(idx) = self {
803 *idx += value;
804 }
805 if matches!(
806 self,
807 LookupId::ExternalGsub(_)
808 | LookupId::ExternalGpos(_)
809 | LookupId::ExternalFrontOfList(_)
810 ) {
811 panic!("external ids should be resolved before adjustment")
812 }
813 }
814
815 pub(crate) fn to_gpos_id_or_die(self) -> u16 {
816 let LookupId::Gpos(x) = self else {
817 panic!("this *really* shouldn't happen")
818 };
819 x.try_into().unwrap()
820 }
821
822 pub(crate) fn to_gsub_id_or_die(self) -> u16 {
823 let LookupId::Gsub(x) = self else {
824 panic!("this *really* shouldn't happen")
825 };
826 x.try_into().unwrap()
827 }
828}
829
830impl LookupIdMap {
831 pub(crate) fn insert(&mut self, from: LookupId, to: LookupId) {
832 self.mapping.insert(from, to);
833 }
834
835 pub(crate) fn get(&self, id: LookupId) -> LookupId {
836 self.mapping.get(&id).copied().unwrap_or(id)
837 }
838}
839
840impl LookupFlagInfo {
841 pub(crate) fn new(flags: LookupFlag, mark_filter_set: Option<FilterSetId>) -> Self {
842 LookupFlagInfo {
843 flags,
844 mark_filter_set,
845 }
846 }
847
848 pub(crate) fn clear(&mut self) {
849 self.flags = LookupFlag::empty();
850 self.mark_filter_set = None;
851 }
852}
853
854impl SomeLookup {
855 fn new(kind: Kind, flags: LookupFlag, filter: Option<FilterSetId>) -> Self {
856 match kind {
858 Kind::GposType7 | Kind::GposType8 => {
859 return SomeLookup::GposContextual(ContextualLookupBuilder::new(flags, filter));
860 }
861 Kind::GsubType5 | Kind::GsubType6 => {
862 return SomeLookup::GsubContextual(ContextualLookupBuilder::new(flags, filter));
863 }
864 _ => (),
865 }
866
867 if is_gpos_rule(kind) {
868 let lookup = match kind {
869 Kind::GposType1 => PositionLookup::Single(LookupBuilder::new(flags, filter)),
870 Kind::GposType2 => PositionLookup::Pair(LookupBuilder::new(flags, filter)),
871 Kind::GposType3 => PositionLookup::Cursive(LookupBuilder::new(flags, filter)),
872 Kind::GposType4 => PositionLookup::MarkToBase(LookupBuilder::new(flags, filter)),
873 Kind::GposType5 => PositionLookup::MarkToLig(LookupBuilder::new(flags, filter)),
874 Kind::GposType6 => PositionLookup::MarkToMark(LookupBuilder::new(flags, filter)),
875 Kind::GposNode => unimplemented!("other gpos type?"),
876 other => panic!("illegal kind for lookup: '{other}'"),
877 };
878 SomeLookup::GposLookup(lookup)
879 } else {
880 let lookup = match kind {
881 Kind::GsubType1 => SubstitutionLookup::Single(LookupBuilder::new(flags, filter)),
882 Kind::GsubType2 => SubstitutionLookup::Multiple(LookupBuilder::new(flags, filter)),
883 Kind::GsubType3 => SubstitutionLookup::Alternate(LookupBuilder::new(flags, filter)),
884 Kind::GsubType4 => SubstitutionLookup::Ligature(LookupBuilder::new(flags, filter)),
885 Kind::GsubType5 => {
886 SubstitutionLookup::Contextual(LookupBuilder::new(flags, filter))
887 }
888 Kind::GsubType7 => unimplemented!("extension"),
889 Kind::GsubType8 => SubstitutionLookup::Reverse(LookupBuilder::new(flags, filter)),
890 other => panic!("illegal kind for lookup: '{other}'"),
891 };
892 SomeLookup::GsubLookup(lookup)
893 }
894 }
895
896 fn kind(&self) -> Kind {
897 match self {
898 SomeLookup::GsubContextual(_) => Kind::GsubType6,
899 SomeLookup::GposContextual(_) => Kind::GposType8,
900 SomeLookup::GsubLookup(gsub) => gsub.kind(),
901 SomeLookup::GposLookup(gpos) => gpos.kind(),
902 }
903 }
904
905 fn flags(&self) -> LookupFlagInfo {
906 match self {
907 SomeLookup::GsubLookup(l) => match l {
908 SubstitutionLookup::Single(l) => LookupFlagInfo::new(l.flags, l.mark_set),
909 SubstitutionLookup::Multiple(l) => LookupFlagInfo::new(l.flags, l.mark_set),
910 SubstitutionLookup::Alternate(l) => LookupFlagInfo::new(l.flags, l.mark_set),
911 SubstitutionLookup::Ligature(l) => LookupFlagInfo::new(l.flags, l.mark_set),
912 SubstitutionLookup::Contextual(l) => LookupFlagInfo::new(l.flags, l.mark_set),
913 SubstitutionLookup::ChainedContextual(l) => {
914 LookupFlagInfo::new(l.flags, l.mark_set)
915 }
916 SubstitutionLookup::Reverse(l) => LookupFlagInfo::new(l.flags, l.mark_set),
917 },
918 SomeLookup::GposLookup(l) => match l {
919 PositionLookup::Single(l) => LookupFlagInfo::new(l.flags, l.mark_set),
920 PositionLookup::Pair(l) => LookupFlagInfo::new(l.flags, l.mark_set),
921 PositionLookup::Cursive(l) => LookupFlagInfo::new(l.flags, l.mark_set),
922 PositionLookup::MarkToBase(l) => LookupFlagInfo::new(l.flags, l.mark_set),
923 PositionLookup::MarkToLig(l) => LookupFlagInfo::new(l.flags, l.mark_set),
924 PositionLookup::MarkToMark(l) => LookupFlagInfo::new(l.flags, l.mark_set),
925 PositionLookup::Contextual(l) => LookupFlagInfo::new(l.flags, l.mark_set),
926 PositionLookup::ChainedContextual(l) => LookupFlagInfo::new(l.flags, l.mark_set),
927 },
928 SomeLookup::GposContextual(l) => LookupFlagInfo::new(l.flags, l.mark_set),
929 SomeLookup::GsubContextual(l) => LookupFlagInfo::new(l.flags, l.mark_set),
930 }
931 }
932
933 pub(crate) fn add_gpos_type_1(&mut self, id: GlyphId16, record: ValueRecord) {
934 if let SomeLookup::GposLookup(PositionLookup::Single(table)) = self {
935 let subtable = table.last_mut().unwrap();
936 subtable.insert(id, record);
937 } else {
938 panic!("lookup mismatch");
939 }
940 }
941
942 pub(crate) fn add_gpos_type_2_pair(
943 &mut self,
944 one: GlyphId16,
945 two: GlyphId16,
946 val_one: ValueRecord,
947 val_two: ValueRecord,
948 ) {
949 if let SomeLookup::GposLookup(PositionLookup::Pair(table)) = self {
950 let subtable = table.last_mut().unwrap();
951 subtable.insert_pair(one, val_one, two, val_two)
952 } else {
953 panic!("lookup mismatch");
954 }
955 }
956
957 pub(crate) fn add_gpos_type_2_class(
958 &mut self,
959 one: GlyphSet,
960 two: GlyphSet,
961 val_one: ValueRecord,
962 val_two: ValueRecord,
963 ) {
964 if let SomeLookup::GposLookup(PositionLookup::Pair(table)) = self {
965 let subtable = table.last_mut().unwrap();
966 subtable.insert_classes(one, val_one, two, val_two)
967 } else {
968 panic!("lookup mismatch");
969 }
970 }
971 pub(crate) fn add_gpos_type_3(
972 &mut self,
973 id: GlyphId16,
974 entry: Option<Anchor>,
975 exit: Option<Anchor>,
976 ) {
977 if let SomeLookup::GposLookup(PositionLookup::Cursive(table)) = self {
978 let subtable = table.last_mut().unwrap();
979 subtable.insert(id, entry, exit);
980 } else {
981 panic!("lookup mismatch");
982 }
983 }
984
985 pub(crate) fn with_gpos_type_4<R>(&mut self, f: impl FnOnce(&mut MarkToBaseBuilder) -> R) -> R {
986 if let SomeLookup::GposLookup(PositionLookup::MarkToBase(table)) = self {
987 let subtable = table.last_mut().unwrap();
988 f(subtable)
989 } else {
990 panic!("lookup mismatch");
991 }
992 }
993
994 pub(crate) fn with_gpos_type_5<R>(&mut self, f: impl FnOnce(&mut MarkToLigBuilder) -> R) -> R {
995 if let SomeLookup::GposLookup(PositionLookup::MarkToLig(table)) = self {
996 let subtable = table.last_mut().unwrap();
997 f(subtable)
998 } else {
999 panic!("lookup mismatch");
1000 }
1001 }
1002
1003 pub(crate) fn with_gpos_type_6<R>(&mut self, f: impl FnOnce(&mut MarkToMarkBuilder) -> R) -> R {
1004 if let SomeLookup::GposLookup(PositionLookup::MarkToMark(table)) = self {
1005 let subtable = table.last_mut().unwrap();
1006 f(subtable)
1007 } else {
1008 panic!("lookup mismatch");
1009 }
1010 }
1011
1012 pub(crate) fn add_contextual_rule(
1014 &mut self,
1015 backtrack: Vec<GlyphOrClass>,
1016 input: Vec<(GlyphOrClass, Vec<LookupId>)>,
1017 lookahead: Vec<GlyphOrClass>,
1018 ) {
1019 match self {
1020 SomeLookup::GposContextual(lookup) => {
1021 lookup.last_mut().add(backtrack, input, lookahead)
1022 }
1023 SomeLookup::GsubContextual(lookup) => {
1024 lookup.last_mut().add(backtrack, input, lookahead)
1025 }
1026 _ => panic!("lookup mismatch : '{}'", self.kind()),
1027 }
1028 }
1029
1030 pub(crate) fn add_gsub_type_1(&mut self, id: GlyphId16, replacement: GlyphId16) {
1031 if let SomeLookup::GsubLookup(SubstitutionLookup::Single(table)) = self {
1032 let subtable = table.last_mut().unwrap();
1033 subtable.insert(id, replacement);
1034 } else {
1035 panic!("lookup mismatch");
1036 }
1037 }
1038
1039 pub(crate) fn add_gsub_type_2(&mut self, id: GlyphId16, replacement: Vec<GlyphId16>) {
1040 if let SomeLookup::GsubLookup(SubstitutionLookup::Multiple(table)) = self {
1041 let subtable = table.last_mut().unwrap();
1042 subtable.insert(id, replacement);
1043 } else {
1044 panic!("lookup mismatch");
1045 }
1046 }
1047
1048 pub(crate) fn add_gsub_type_3(&mut self, id: GlyphId16, alternates: Vec<GlyphId16>) {
1049 if let SomeLookup::GsubLookup(SubstitutionLookup::Alternate(table)) = self {
1050 let subtable = table.last_mut().unwrap();
1051 subtable.insert(id, alternates);
1052 } else {
1053 panic!("lookup mismatch");
1054 }
1055 }
1056
1057 pub(crate) fn add_gsub_type_4(
1061 &mut self,
1062 target: Vec<GlyphId16>,
1063 replacement: GlyphId16,
1064 ) -> bool {
1065 if let SomeLookup::GsubLookup(SubstitutionLookup::Ligature(table)) = self {
1066 let subtable = table.last_mut().unwrap();
1067 if !subtable.can_add(&target, replacement) {
1068 return true;
1069 }
1070 subtable.insert(target, replacement);
1071 } else {
1072 panic!("lookup mismatch");
1073 }
1074 false
1075 }
1076
1077 pub(crate) fn add_gsub_type_8(
1078 &mut self,
1079 backtrack: Vec<GlyphOrClass>,
1080 input: BTreeMap<GlyphId16, GlyphId16>,
1081 lookahead: Vec<GlyphOrClass>,
1082 ) {
1083 if let SomeLookup::GsubLookup(SubstitutionLookup::Reverse(table)) = self {
1084 let subtable = table.last_mut().unwrap();
1085 subtable.add(backtrack, input, lookahead);
1086 }
1087 }
1088
1089 pub(crate) fn as_gsub_contextual(
1090 &mut self,
1091 ) -> &mut ContextualLookupBuilder<SubstitutionLookup> {
1092 let SomeLookup::GsubContextual(table) = self else {
1093 panic!("lookup mismatch")
1094 };
1095 table
1096 }
1097
1098 pub(crate) fn as_gpos_contextual(&mut self) -> &mut ContextualLookupBuilder<PositionLookup> {
1099 if let SomeLookup::GposContextual(table) = self {
1100 table
1101 } else {
1102 panic!("lookup mismatch")
1103 }
1104 }
1105}
1106
1107impl<T> PosSubBuilder<T> {
1108 fn new(lookups: Vec<T>) -> Self {
1109 PosSubBuilder {
1110 lookups,
1111 scripts: Default::default(),
1112 features: Default::default(),
1113 variations: Default::default(),
1114 }
1115 }
1116
1117 fn add(&mut self, key: FeatureKey, lookups: Vec<LookupIdx>, required: bool) -> FeatureIdx {
1118 let feat_key = (key.feature, lookups);
1119 let next_feature = self.features.len();
1120 let idx = *self
1121 .features
1122 .entry(feat_key)
1123 .or_insert_with(|| next_feature.try_into().expect("ran out of u16s"));
1124
1125 let lang_sys = self
1126 .scripts
1127 .entry(key.script)
1128 .or_default()
1129 .entry(key.language)
1130 .or_default();
1131
1132 if required {
1133 lang_sys.required_feature_index = idx;
1134 } else {
1135 lang_sys.feature_indices.push(idx);
1136 }
1137 idx
1138 }
1139
1140 fn add_variation(
1141 &mut self,
1142 idx: FeatureIdx,
1143 conditions: &RawConditionSet,
1144 lookups: Vec<LookupIdx>,
1145 ) {
1146 if !self.variations.contains_key(conditions) {
1148 self.variations
1149 .insert(conditions.clone(), Default::default());
1150 }
1151 self.variations
1152 .get_mut(conditions)
1153 .unwrap()
1154 .insert(idx, lookups);
1155 }
1156}
1157
1158impl<T> PosSubBuilder<T>
1159where
1160 T: Builder,
1161 T::Output: Default,
1162{
1163 #[allow(clippy::type_complexity)] fn build_raw(
1165 self,
1166 var_store: &mut VariationStoreBuilder,
1167 ) -> Option<(
1168 LookupList<T::Output>,
1169 ScriptList,
1170 FeatureList,
1171 Option<FeatureVariations>,
1172 )> {
1173 if self.lookups.is_empty() && self.features.is_empty() {
1174 return None;
1175 }
1176
1177 let mut features = vec![Default::default(); self.features.len()];
1179 for ((tag, lookups), idx) in self.features {
1180 features[idx as usize] = FeatureRecord::new(tag, Feature::new(None, lookups));
1181 }
1182
1183 let scripts = self
1184 .scripts
1185 .into_iter()
1186 .map(|(script_tag, entry)| {
1187 let mut script = Script::default();
1188 for (lang_tag, lang_sys) in entry {
1189 if lang_tag == tags::LANG_DFLT {
1190 script.default_lang_sys = lang_sys.into();
1191 } else {
1192 script
1193 .lang_sys_records
1194 .push(LangSysRecord::new(lang_tag, lang_sys));
1195 }
1196 }
1197 ScriptRecord::new(script_tag, script)
1198 })
1199 .collect::<Vec<_>>();
1200
1201 let lookups = self
1202 .lookups
1203 .into_iter()
1204 .map(|x| x.build(var_store))
1205 .collect();
1206
1207 let variations = if self.variations.is_empty() {
1208 None
1209 } else {
1210 let records = self
1211 .variations
1212 .into_iter()
1213 .map(|(condset, features)| {
1214 let condset = (!condset.conditions.is_empty()).then_some(condset);
1216 FeatureVariationRecord::new(
1217 condset,
1218 FeatureTableSubstitution::new(
1219 features
1220 .into_iter()
1221 .map(|(feat_id, lookup_ids)| {
1222 FeatureTableSubstitutionRecord::new(
1223 feat_id,
1224 Feature::new(None, lookup_ids),
1225 )
1226 })
1227 .collect(),
1228 )
1229 .into(),
1230 )
1231 })
1232 .collect();
1233 Some(FeatureVariations::new(records))
1234 };
1235 Some((
1236 LookupList::new(lookups),
1237 ScriptList::new(scripts),
1238 FeatureList::new(features),
1239 variations,
1240 ))
1241 }
1242}
1243
1244impl Builder for PosSubBuilder<PositionLookup> {
1245 type Output = Option<write_gpos::Gpos>;
1246
1247 fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
1248 self.build_raw(var_store)
1249 .map(|(lookups, scripts, features, variations)| {
1250 let mut gpos = write_gpos::Gpos::new(scripts, features, lookups);
1251 gpos.feature_variations = variations.into();
1252 gpos
1253 })
1254 }
1255}
1256
1257impl Builder for PosSubBuilder<SubstitutionLookup> {
1258 type Output = Option<write_gsub::Gsub>;
1259
1260 fn build(self, var_store: &mut VariationStoreBuilder) -> Self::Output {
1261 self.build_raw(var_store)
1262 .map(|(lookups, scripts, features, variations)| {
1263 let mut gsub = write_gsub::Gsub::new(scripts, features, lookups);
1264 gsub.feature_variations = variations.into();
1265 gsub
1266 })
1267 }
1268}
1269
1270impl FeatureKey {
1271 pub const fn new(feature: Tag, language: Tag, script: Tag) -> Self {
1276 FeatureKey {
1277 feature,
1278 language,
1279 script,
1280 }
1281 }
1282}
1283
1284impl Debug for FeatureKey {
1285 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1286 write!(f, "{}: {}/{}", self.feature, self.script, self.language)
1287 }
1288}
1289
1290fn is_gpos_rule(kind: Kind) -> bool {
1291 matches!(
1292 kind,
1293 Kind::GposType1
1294 | Kind::GposType2
1295 | Kind::GposType3
1296 | Kind::GposType4
1297 | Kind::GposType5
1298 | Kind::GposType6
1299 | Kind::GposType7
1300 | Kind::GposType8
1301 )
1302}