font_index/
index.rs

1//! Font index.
2
3use super::index_data::*;
4use super::types::*;
5use super::{
6    fallback::Fallbacks,
7    types::{FamilyId, GenericFamily},
8};
9#[cfg(feature = "emacs")]
10use crate::emacs::FontSpec;
11use crate::util::{
12    fxhash::FxHashMap,
13    string::{LowercaseString, SmallString},
14};
15#[cfg(feature = "emacs")]
16use fancy_regex::Regex;
17#[cfg(feature = "emacs")]
18use log::warn;
19#[cfg(feature = "emacs")]
20use std::collections::HashSet;
21use std::path::Path;
22use swash::text::{Cjk, Script};
23use swash::Tag;
24#[cfg(feature = "emacs")]
25use swash::{text::Language, Stretch, Style, Weight};
26use swash::{Attributes, CacheKey};
27
28/// Type alias for signatures to distinguish between inherent and
29/// requested attributes.
30pub type RequestedAttributes = Attributes;
31
32#[derive(Default)]
33pub struct BaseIndex {
34    pub family_map: FxHashMap<SmallString, FamilyId>,
35    pub fonts: Vec<FontData>,
36    pub sources: Vec<SourceData>,
37}
38
39pub struct StaticIndex {
40    pub base: BaseIndex,
41    pub families: Vec<FamilyData>,
42    pub script_map: FxHashMap<Script, Fallbacks>,
43    pub script_tag_map: FxHashMap<Tag, Vec<FamilyId>>,
44    pub language_tag_map: FxHashMap<Tag, Vec<FamilyId>>,
45    #[cfg(feature = "emacs")]
46    pub emacs_charset_map: FxHashMap<SmallString, Vec<FamilyId>>,
47    #[cfg(feature = "emacs")]
48    pub emacs_script_map: FxHashMap<SmallString, Vec<FamilyId>>,
49    pub cjk: [Fallbacks; 5],
50    pub generic: [Option<FamilyId>; 13],
51}
52
53impl Default for StaticIndex {
54    fn default() -> Self {
55        let fallbacks = Fallbacks::new();
56        Self {
57            base: BaseIndex::default(),
58            families: Vec::new(),
59            script_map: Default::default(),
60            script_tag_map: Default::default(),
61            language_tag_map: Default::default(),
62            #[cfg(feature = "emacs")]
63            emacs_charset_map: Default::default(),
64            #[cfg(feature = "emacs")]
65            emacs_script_map: Default::default(),
66            cjk: [fallbacks; 5],
67            generic: [None; 13],
68        }
69    }
70}
71
72impl StaticIndex {
73    pub fn setup_default_fallbacks(&mut self) {
74        use super::system::*;
75        use Cjk::*;
76        use Script::*;
77        match OS {
78            Os::Windows => {
79                // Simplified Chinese
80                self.cjk[Simplified as usize] =
81                    self.find_fallbacks(&["microsoft yahei", "simsun", "simsun-extb"]);
82                // Traditional Chinese
83                self.cjk[Traditional as usize] =
84                    self.find_fallbacks(&["microsoft jhenghei", "pmingliu", "pmingliu-extb"]);
85                self.cjk[Cjk::None as usize] = self.cjk[Traditional as usize];
86                // Japanese
87                self.cjk[Japanese as usize] = self.find_fallbacks(&[
88                    "meiryo",
89                    "yu gothic",
90                    "microsoft yahei",
91                    "simsun",
92                    "simsun-extb",
93                ]);
94                // Korean
95                self.cjk[Korean as usize] = self.find_fallbacks(&[
96                    "malgun gothic",
97                    "gulim",
98                    "microsoft yahei",
99                    "simsun",
100                    "simsun-extb",
101                ]);
102                self.map_script(Latin, &["times new roman"]);
103                self.map_script(Arabic, &["tahoma", "segoe ui"]);
104                self.map_script(Armenian, &["segoe ui", "sylfaen"]);
105                self.map_script(Bengali, &["nirmala ui", "vrinda"]);
106                self.map_script(Brahmi, &["segoe ui historic"]);
107                self.map_script(Braille, &["segoe ui symbol"]);
108                self.map_script(Buginese, &["leelawadee ui"]);
109                self.map_script(CanadianAboriginal, &["gadugi", "euphemia"]);
110                self.map_script(Carian, &["segoe ui historic"]);
111                self.map_script(Devanagari, &["nirmala ui", "mangal"]);
112                self.map_script(Hebrew, &["david", "segoe ui", "calibri"]);
113                self.map_script(Hangul, &["malgun gothic", "gulim"]);
114                self.map_script(Myanmar, &["myanmar text"]);
115                self.map_script(Malayalam, &["nirmala ui", "kartika"]);
116                self.map_script(Han, &["microsoft yahei", "simsun", "simsun-extb"]);
117                self.map_script(
118                    Hiragana,
119                    &["meiryo", "yu gothic", "ms pgothic", "microsoft yahei"],
120                );
121                self.map_script(
122                    Katakana,
123                    &["meiryo", "yu gothic", "ms pgothic", "microsoft yahei"],
124                );
125                self.map_script(Kharoshthi, &["segoe ui historic"]);
126                self.map_script(
127                    Khmer,
128                    &[
129                        "leelawadee ui",
130                        "khmer ui",
131                        "khmer os",
132                        "moolboran",
133                        "daunpenh",
134                    ],
135                );
136                self.map_script(
137                    Lao,
138                    &[
139                        "leelawadee ui",
140                        "lao ui",
141                        "dokchampa",
142                        "saysettha ot",
143                        "phetsarath ot",
144                        "code2000",
145                    ],
146                );
147                self.map_script(Lisu, &["segoe ui"]);
148                self.map_script(
149                    Syriac,
150                    &["estrangelo edessa", "estrangelo nisibin", "code2000"],
151                );
152                self.map_script(Thai, &["tahoma", "leelawadee ui", "leelawadee"]);
153                self.map_script(
154                    Tibetan,
155                    &["microsoft himalaya", "jomolhari", "tibetan machine uni"],
156                );
157                self.map_script(Vai, &["ebrima"]);
158                self.map_script(Yi, &["microsoft yi baiti", "nuosu sil", "code2000"]);
159            }
160            Os::MacOs => {
161                // Simplified Chinese
162                self.cjk[Simplified as usize] = self.find_fallbacks(&["pingfang sc"]);
163                // Traditional Chinese
164                self.cjk[Traditional as usize] = self.find_fallbacks(&["pingfang tc"]);
165                self.cjk[Cjk::None as usize] = self.cjk[Traditional as usize];
166                // Japanese
167                self.cjk[Japanese as usize] =
168                    self.find_fallbacks(&["hiragino kaku gothic pron w3"]);
169                // Korean
170                self.cjk[Korean as usize] = self.find_fallbacks(&["apple sd gothic neo"]);
171                self.map_script(Latin, &["times", "times new roman"]);
172                self.map_script(Arabic, &["geeza pro"]);
173                self.map_script(
174                    Devanagari,
175                    &[
176                        "itf devanagari",
177                        "kohinoor devanagari",
178                        "devanagari sangam mn",
179                        "devanagari mt",
180                    ],
181                );
182                self.map_script(Bengali, &[]);
183                self.map_script(Myanmar, &["noto sans myanmar", "myanmar mn"]);
184                self.map_script(Malayalam, &["malayalam mn"]);
185                self.map_script(Hebrew, &["lucida grande", "arial hebrew"]);
186            }
187            _ => {
188                self.map_script(
189                    Latin,
190                    &[
191                        "liberation sans",
192                        "dejavu sans",
193                        "ubuntu",
194                        "source sans pro",
195                    ],
196                );
197                self.map_script(Arabic, &["noto sans arabic"]);
198                self.map_script(Hebrew, &["noto sans hebrew", "noto serif hebrew"]);
199                self.map_script(Bengali, &["noto sans bengali", "noto serif bengali"]);
200                self.map_script(
201                    Devanagari,
202                    &["noto sans devanagari", "noto serif devanagari"],
203                );
204                self.map_script(Malayalam, &["noto sans malayalam", "noto serif malayalam"]);
205                self.map_script(Myanmar, &["noto sans myanmar", "noto serif myanmar"]);
206            }
207        }
208    }
209
210    pub fn setup_default_generic(&mut self) {
211        use super::system::*;
212        use GenericFamily::*;
213        match OS {
214            Os::Windows => {
215                self.generic[SansSerif as usize] = self.find_family(&["arial"]);
216                self.generic[Serif as usize] = self.find_family(&["times new roman"]);
217                self.generic[Monospace as usize] = self.find_family(&["courier new"]);
218                self.generic[Fantasy as usize] = self.find_family(&["impact"]);
219                self.generic[Cursive as usize] = self.find_family(&["comic sans ms"]);
220                self.generic[SystemUI as usize] = self.find_family(&["segoe ui"]);
221                self.generic[Emoji as usize] = self.find_family(&["segoe ui emoji"]);
222            }
223            Os::MacOs => {
224                self.generic[SansSerif as usize] = self.find_family(&["helvetica"]);
225                self.generic[Serif as usize] = self.find_family(&["times"]);
226                self.generic[Monospace as usize] = self.find_family(&["courier"]);
227                self.generic[Fantasy as usize] = self.find_family(&["papyrus"]);
228                self.generic[Cursive as usize] = self.find_family(&["apple chancery"]);
229                self.generic[SystemUI as usize] = self.find_family(&["system font", "helvetica"]);
230                self.generic[Emoji as usize] = self.find_family(&["apple color emoji"]);
231            }
232            Os::Ios => {
233                self.generic[SansSerif as usize] = self.find_family(&["helvetica"]);
234                self.generic[Serif as usize] = self.find_family(&["times new roman"]);
235                self.generic[Monospace as usize] = self.find_family(&["courier"]);
236                self.generic[Fantasy as usize] = self.find_family(&["papyrus"]);
237                self.generic[Cursive as usize] = self.find_family(&["snell roundhand"]);
238                self.generic[SystemUI as usize] = self.find_family(&["system font", "helvetica"]);
239                self.generic[Emoji as usize] = self.find_family(&["apple color emoji"]);
240            }
241            Os::Android => {
242                self.generic[SansSerif as usize] = self.find_family(&["roboto"]);
243                self.generic[Serif as usize] = self.find_family(&["noto serif", "droid serif"]);
244                self.generic[Monospace as usize] = self.find_family(&["droid sans mono"]);
245                self.generic[Fantasy as usize] = self.find_family(&["noto serif"]);
246                self.generic[Cursive as usize] = self.find_family(&["dancing script"]);
247                self.generic[SystemUI as usize] = self.find_family(&["roboto"]);
248                self.generic[Emoji as usize] = self.find_family(&["noto color emoji"]);
249            }
250            Os::Unix | Os::Other => {
251                self.generic[SansSerif as usize] =
252                    self.find_family(&["liberation sans", "dejavu sans"]);
253                self.generic[Serif as usize] = self.find_family(&[
254                    "liberation serif",
255                    "dejavu serif",
256                    "noto serif",
257                    "times new roman",
258                ]);
259                self.generic[Monospace as usize] = self.find_family(&["dejavu sans mono"]);
260                self.generic[Fantasy as usize] =
261                    self.find_family(&["liberation serif", "dejavu serif"]);
262                self.generic[Cursive as usize] =
263                    self.find_family(&["liberation serif", "dejavu serif"]);
264                self.generic[SystemUI as usize] =
265                    self.find_family(&["liberation sans", "dejavu sans"]);
266                self.generic[Emoji as usize] = self.find_family(&["noto color emoji", "emoji one"]);
267            }
268        }
269    }
270
271    pub fn emoji_family(&self) -> Option<FamilyId> {
272        self.generic[GenericFamily::Emoji as usize]
273    }
274
275    pub fn fallbacks(&self, script: Script, cjk: Cjk) -> &[FamilyId] {
276        if script == Script::Han {
277            self.cjk[cjk as usize].get()
278        } else {
279            self.script_map.get(&script).map(|f| f.get()).unwrap_or(&[])
280        }
281    }
282
283    fn map_script(&mut self, script: Script, families: &[&str]) {
284        let fallbacks = self.find_fallbacks(families);
285        if fallbacks.len() != 0 {
286            self.script_map.insert(script, fallbacks);
287        }
288    }
289
290    fn find_family(&self, families: &[&str]) -> Option<FamilyId> {
291        for family in families {
292            if let Some(id) = self.base.family_map.get(*family) {
293                return Some(*id);
294            }
295        }
296        None
297    }
298
299    fn find_fallbacks(&self, families: &[&str]) -> Fallbacks {
300        let mut fallbacks = Fallbacks::new();
301        for family in families {
302            if let Some(id) = self.base.family_map.get(*family) {
303                if !fallbacks.push(*id) {
304                    break;
305                }
306            }
307        }
308        fallbacks
309    }
310}
311
312impl StaticIndex {
313    /// Returns a font entry that matches the specified family and
314    /// attributes.
315    pub fn query<'a>(
316        &'a self,
317        family: impl Into<FamilyKey<'a>>,
318        attributes: impl Into<Attributes>,
319    ) -> Option<FontEntry<'a>> {
320        let family = self.family_by_key(family)?;
321        let attrs = attributes.into();
322        let font_id = family.data.query(attrs)?;
323        let data = self.base.fonts.get(font_id.to_usize())?;
324        Some(FontEntry {
325            index: &self.base,
326            family: family.data,
327            data,
328        })
329    }
330
331    /// Returns a list font entries that matches the specified family and
332    /// attributes.
333    #[cfg(feature = "emacs")]
334    pub fn list<'a>(&'a self, spec: FontSpec) -> Vec<FontEntry<'a>> {
335        let filter = |family: FamilyKey<'a>,
336                      stretch: Option<Stretch>,
337                      weight: Option<Weight>,
338                      style: Option<Style>,
339                      otf: Option<OpentypeSpec>| {
340            let family = self.family_by_key(family);
341            if family.is_none() {
342                return vec![];
343            }
344            let family = family.unwrap();
345            let fonts = family.data.list(stretch, weight, style, otf);
346            let fonts = fonts
347                .iter()
348                .filter_map(|font_id| {
349                    if let Some(data) = self.base.fonts.get(font_id.to_usize()) {
350                        return Some(FontEntry {
351                            index: &self.base,
352                            family: family.data,
353                            data,
354                        });
355                    }
356                    None
357                })
358                .collect();
359            return fonts;
360        };
361
362        //TODO impl spacing
363        let FontSpec {
364            width,
365            weight,
366            slant,
367            spacing,
368            otf,
369            ..
370        } = spec.clone();
371
372        if let Some(_) = spacing {
373            warn!("spacing is not yet supported");
374        }
375        self.families_by_spec(spec)
376            .iter()
377            .map(|family_id| FamilyKey::from(*family_id))
378            .map(|key| filter(key, width, weight, slant, otf.clone()))
379            .flatten()
380            .collect()
381    }
382
383    #[cfg(feature = "emacs")]
384    pub fn match_<'a>(&'a self, spec: FontSpec) -> Option<FontEntry<'a>> {
385        //TODO impl spacing
386        let FontSpec {
387            width,
388            weight,
389            slant,
390            spacing,
391            otf,
392            ..
393        } = spec.clone();
394        if let Some(_) = spacing {
395            warn!("spacing is not yet supported");
396        }
397        let attrs = Attributes::new(
398            width.unwrap_or(Stretch::NORMAL),
399            weight.unwrap_or(Weight::NORMAL),
400            slant.unwrap_or(Style::Normal),
401        );
402
403        let query = |family: FamilyId, attributes: Attributes, otf: Option<OpentypeSpec>| {
404            let family = self.family_by_key(family)?;
405            let attrs = attributes.into();
406            let font_id = family.data.match_(attrs, otf)?;
407            let data = self.base.fonts.get(font_id.to_usize())?;
408            Some(FontEntry {
409                index: &self.base,
410                family: family.data,
411                data,
412            })
413        };
414
415        self.families_by_spec(spec)
416            .iter()
417            .find_map(|family| query(*family, attrs, otf.clone()))
418    }
419
420    //TODO impl foundry, ref to fontconfig foundry implementation
421    //TODO impl XLFD-style or fontconfig-style font name, more defails from Emacs info
422    #[cfg(feature = "emacs")]
423    pub fn families_by_spec<'a>(
424        &'a self,
425        FontSpec {
426            family,
427            foundry,
428            registry,
429            name,
430            script,
431            lang,
432            otf,
433            ..
434        }: FontSpec,
435    ) -> Vec<FamilyId> {
436        if let Some(_) = foundry {
437            warn!("foundry is not yet supported");
438        }
439
440        if let Some(_) = name {
441            warn!("name is not yet supported");
442        }
443
444        let intersection = |families: Vec<FamilyId>, _families: Option<&Vec<FamilyId>>| {
445            if _families.is_none() {
446                return vec![];
447            }
448            let _families = _families.unwrap();
449
450            if families.is_empty() && !_families.is_empty() {
451                return _families.clone();
452            } else {
453                let unique_a = families.iter().collect::<HashSet<_>>();
454                let unique_b = _families.iter().collect::<HashSet<_>>();
455
456                return unique_a
457                    .intersection(&unique_b)
458                    .map(|id| **id)
459                    .collect::<Vec<_>>();
460            }
461        };
462
463        let mut families = family
464            .map(|family| {
465                self.family_by_name(family.as_str())
466                    .map(|entry| vec![entry.data.id])
467            })
468            .flatten()
469            .unwrap_or(vec![]);
470
471        if let Some((script, ..)) = otf {
472            let _families = self.families_by_script(script);
473            families = intersection(families, _families);
474        }
475
476        if let Some(regexp) = registry {
477            let _families = self.families_by_charset(regexp);
478            families = intersection(families, _families);
479        }
480
481        if let Some(script) = script {
482            let _families = self.families_by_emacs_script(script);
483            families = intersection(families, _families);
484        }
485
486        if let Some(lang) = lang {
487            let _f = lang
488                .to_639_1()
489                .map(|lang| Language::parse(lang))
490                .flatten()
491                .map(|lang| lang.to_opentype())
492                .flatten()
493                .map(|lang| self.families_by_lang(lang))
494                .flatten();
495            families = intersection(families, _f);
496        }
497
498        families.dedup();
499        families
500    }
501
502    #[cfg(feature = "emacs")]
503    pub fn families_by_charset(&self, regexp: String) -> Option<&Vec<FamilyId>> {
504        let string_match_p = |regexp: &str, string: &str, start: Option<i64>| {
505            let re = Regex::new(&lisp_regex_to_rust(regexp)).ok().unwrap();
506
507            let start = start.unwrap_or(0) as usize;
508            if let Some(_) = re.captures_iter(&string[start..]).next() {
509                true
510            } else {
511                false
512            }
513        };
514        self.emacs_charset_map
515            .iter()
516            .find_map(|(charset, families)| {
517                if string_match_p(regexp.as_str(), charset.as_str(), Some(0)) {
518                    return Some(families);
519                }
520                None
521            })
522    }
523
524    #[cfg(feature = "emacs")]
525    pub fn families_by_emacs_script(&self, script: String) -> Option<&Vec<FamilyId>> {
526        self.emacs_script_map
527            .iter()
528            .find_map(|(script_, families)| {
529                if script.as_str() == script_.as_str() {
530                    return Some(families);
531                }
532                None
533            })
534    }
535
536    pub fn families_by_script(&self, lang: Tag) -> Option<&Vec<FamilyId>> {
537        self.script_tag_map.iter().find_map(|(lang_, families)| {
538            if lang == *lang_ {
539                return Some(families);
540            }
541            None
542        })
543    }
544
545    pub fn families_by_lang(&self, lang: Tag) -> Option<&Vec<FamilyId>> {
546        self.language_tag_map.iter().find_map(|(lang_, families)| {
547            if lang == *lang_ {
548                return Some(families);
549            }
550            None
551        })
552    }
553
554    /// Returns a font family entry for the specified family key.
555    pub fn family_by_key<'a>(&'a self, key: impl Into<FamilyKey<'a>>) -> Option<FamilyEntry<'a>> {
556        match key.into() {
557            FamilyKey::Id(id) => self.family_by_id(id),
558            FamilyKey::Name(name) => self.family_by_name(name),
559            FamilyKey::Generic(generic) => {
560                self.family_by_id(self.generic.get(generic as usize).copied()??)
561            }
562        }
563    }
564
565    /// Returns a font family entry for the specified name.
566    pub fn family_by_name<'a>(&'a self, name: &str) -> Option<FamilyEntry<'a>> {
567        let mut s = LowercaseString::new();
568        let name = s.get(name)?;
569        let id = if let Some(generic) = GenericFamily::parse(name) {
570            self.generic.get(generic as usize).copied()??
571        } else {
572            *self.base.family_map.get(name)?
573        };
574
575        self.family_by_id(id)
576    }
577
578    /// Returns a font family entry for the specified identifier.
579    pub fn family_by_id<'a>(&'a self, id: FamilyId) -> Option<FamilyEntry<'a>> {
580        let data = self.families.get(id.to_usize())?;
581        Some(FamilyEntry {
582            index: &self.base,
583            data,
584        })
585    }
586
587    /// Returns a font entry for the specified identifier.
588    pub fn font_by_id<'a>(&'a self, id: FontId) -> Option<FontEntry<'a>> {
589        let data = self.base.fonts.get(id.to_usize())?;
590        let family = self.families.get(data.family.to_usize())?;
591        Some(FontEntry {
592            index: &self.base,
593            family,
594            data,
595        })
596    }
597}
598
599/// Font family entry in a library.
600#[derive(Copy, Clone)]
601pub struct FamilyEntry<'a> {
602    index: &'a BaseIndex,
603    data: &'a FamilyData,
604}
605
606impl<'a> FamilyEntry<'a> {
607    /// Returns the family identifier.
608    pub fn id(&self) -> FamilyId {
609        self.data.id
610    }
611
612    /// Returns the name of the family.
613    pub fn name(&self) -> &str {
614        self.data.name.as_str()
615    }
616
617    /// Returns an iterator over the fonts in the family.
618    pub fn fonts(&'a self) -> impl Iterator<Item = FontEntry<'a>> + 'a {
619        self.data.fonts.iter().filter_map(move |f| {
620            let data = self.index.fonts.get(f.id.to_usize())?;
621            Some(FontEntry {
622                index: self.index,
623                family: self.data,
624                data,
625            })
626        })
627    }
628}
629
630/// Font entry in a library.
631#[derive(Copy, Clone)]
632pub struct FontEntry<'a> {
633    index: &'a BaseIndex,
634    family: &'a FamilyData,
635    data: &'a FontData,
636}
637
638impl<'a> FontEntry<'a> {
639    /// Returns the font identifier.
640    pub fn id(&self) -> FontId {
641        self.data.id
642    }
643
644    /// Returns the font source.
645    pub fn source(&self) -> SourceEntry<'a> {
646        SourceEntry {
647            index: self.index,
648            data: &self.index.sources[self.data.source.to_usize()],
649        }
650    }
651
652    /// Returns the index of the font in the source.
653    pub fn index(&self) -> u32 {
654        self.data.index
655    }
656
657    /// Returns the offset to the font table directory in the source.
658    pub fn offset(&self) -> u32 {
659        self.data.offset
660    }
661
662    /// Returns the family entry.
663    pub fn family(&self) -> FamilyEntry<'a> {
664        FamilyEntry {
665            index: self.index,
666            data: self.family,
667        }
668    }
669
670    /// Returns the family name.
671    pub fn family_name(&self) -> &str {
672        self.family.name.as_str()
673    }
674
675    /// Returns the font attributes.
676    pub fn attributes(&self) -> Attributes {
677        self.data.attributes
678    }
679
680    pub fn cache_key(&self) -> CacheKey {
681        self.data.key
682    }
683
684    pub fn selector(
685        &self,
686        attrs: RequestedAttributes,
687    ) -> (FontId, Attributes, RequestedAttributes) {
688        (self.data.id, self.data.attributes, attrs)
689    }
690}
691
692/// Source entry in a library.
693#[derive(Copy, Clone)]
694pub struct SourceEntry<'a> {
695    pub index: &'a BaseIndex,
696    data: &'a SourceData,
697}
698
699impl<'a> SourceEntry<'a> {
700    /// Returns the source identifier.
701    pub fn id(&self) -> SourceId {
702        self.data.id
703    }
704
705    /// Returns the path of the source, if it is represented by a file.
706    pub fn path(&self) -> Option<&Path> {
707        match &self.data.kind {
708            SourceKind::Memory(..) => None,
709            SourceKind::File(data) => Some(&data.path),
710        }
711    }
712}
713
714// Invert the escaping of parens. i.e. \( => ( and ( => \(
715// copied from https://github.com/CeleritasCelery/rune/blob/master/src/search.rs#L38
716#[cfg(feature = "emacs")]
717fn lisp_regex_to_rust(regexp: &str) -> String {
718    let mut norm_regex = String::new();
719    let mut chars = regexp.chars().peekable();
720    while let Some(ch) = chars.next() {
721        match ch {
722            '(' => norm_regex.push_str("\\("),
723            ')' => norm_regex.push_str("\\)"),
724            '\\' if matches!(chars.peek(), Some('(' | ')')) => {
725                norm_regex.push(chars.next().unwrap());
726            }
727            c => norm_regex.push(c),
728        }
729    }
730    norm_regex
731}