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