Skip to main content

harfrust/hb/ot/gpos/
pair.rs

1use crate::hb::ot::{coverage_index, coverage_index_cached, ClassDefInfo, CoverageInfo};
2use crate::hb::ot::{glyph_class, glyph_class_cached};
3use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t;
4use crate::hb::ot_layout_gsubgpos::{
5    skipping_iterator_t, Apply, PairPosFormat1Cache, PairPosFormat1SmallCache, PairPosFormat2Cache,
6    PairPosFormat2SmallCache, SubtableExternalCache, SubtableExternalCacheMode,
7};
8use alloc::boxed::Box;
9use read_fonts::tables::gpos::{PairPosFormat1, PairPosFormat2};
10
11impl Apply for PairPosFormat1<'_> {
12    fn apply_with_external_cache(
13        &self,
14        ctx: &mut hb_ot_apply_context_t,
15        external_cache: &SubtableExternalCache,
16    ) -> Option<()> {
17        let first_glyph = ctx.buffer.cur(0).as_glyph();
18
19        let first_glyph_coverage_index = match external_cache {
20            SubtableExternalCache::PairPosFormat1Cache(cache) => coverage_index_cached(
21                |gid| self.coverage().ok()?.get(gid),
22                first_glyph,
23                &cache.coverage,
24            )?,
25            SubtableExternalCache::PairPosFormat1SmallCache(cache) => {
26                cache.coverage.index(&self.offset_data(), first_glyph)?
27            }
28            _ => coverage_index(self.coverage(), first_glyph)?,
29        };
30
31        let mut iter = skipping_iterator_t::new(ctx, false);
32        iter.reset(iter.buffer.idx);
33
34        let mut unsafe_to = 0;
35        if !iter.next(Some(&mut unsafe_to)) {
36            ctx.buffer
37                .unsafe_to_concat(Some(ctx.buffer.idx), Some(unsafe_to));
38            return None;
39        }
40
41        let second_glyph_index = iter.index();
42        let second_glyph = iter.buffer.info[second_glyph_index].as_glyph();
43
44        let finish = |ctx: &mut hb_ot_apply_context_t, iter_index: &mut usize, has_record2| {
45            if has_record2 {
46                *iter_index += 1;
47                // https://github.com/harfbuzz/harfbuzz/issues/3824
48                // https://github.com/harfbuzz/harfbuzz/issues/3888#issuecomment-1326781116
49                ctx.buffer
50                    .unsafe_to_break(Some(ctx.buffer.idx), Some(*iter_index + 1));
51            }
52
53            ctx.buffer.idx = *iter_index;
54
55            Some(())
56        };
57
58        let boring = |ctx: &mut hb_ot_apply_context_t, iter_index: &mut usize, has_record2| {
59            ctx.buffer
60                .unsafe_to_concat(Some(ctx.buffer.idx), Some(second_glyph_index + 1));
61            finish(ctx, iter_index, has_record2)
62        };
63
64        let success =
65            |ctx: &mut hb_ot_apply_context_t, iter_index: &mut usize, flag1, flag2, has_record2| {
66                if flag1 || flag2 {
67                    ctx.buffer
68                        .unsafe_to_break(Some(ctx.buffer.idx), Some(second_glyph_index + 1));
69                    finish(ctx, iter_index, has_record2)
70                } else {
71                    boring(ctx, iter_index, has_record2)
72                }
73            };
74
75        let mut buf_idx = iter.buf_idx;
76        let set_offset = self
77            .pair_set_offsets()
78            .get(first_glyph_coverage_index as usize)?
79            .get()
80            .to_u32() as usize;
81        let format1 = self.value_format1();
82        let format1_len = format1.record_byte_len();
83        let format2 = self.value_format2();
84        let record_size = format1_len + format2.record_byte_len() + 2;
85        let data = self.offset_data();
86        let set_data = data.split_off(set_offset)?;
87        let pair_count = set_data.read_at::<u16>(0).ok()? as usize;
88        let mut hi = pair_count;
89        let mut lo = 0;
90        while lo < hi {
91            // This recommends using usize::midpoint which expands to u128.
92            // We definitely do not want to do that here since the input values
93            // are 16-bit.
94            #[allow(clippy::manual_midpoint)]
95            let mid = (lo + hi) / 2;
96            let record_offset = 2 + mid * record_size;
97            let glyph_id = set_data
98                .read_at::<read_fonts::types::GlyphId16>(record_offset)
99                .ok()?;
100            if glyph_id < second_glyph {
101                lo = mid + 1;
102            } else if glyph_id > second_glyph {
103                hi = mid;
104            } else {
105                let has_record2 = !format2.is_empty();
106                let worked1 = !format1.is_empty()
107                    && super::apply_value(
108                        ctx,
109                        ctx.buffer.idx,
110                        &set_data,
111                        record_offset + 2,
112                        format1,
113                    ) == Some(true);
114                let worked2 = has_record2
115                    && super::apply_value(
116                        ctx,
117                        second_glyph_index,
118                        &set_data,
119                        record_offset + format1_len + 2,
120                        format2,
121                    ) == Some(true);
122                return success(ctx, &mut buf_idx, worked1, worked2, has_record2);
123            }
124        }
125        None
126    }
127
128    fn external_cache_create(&self, mode: SubtableExternalCacheMode) -> SubtableExternalCache {
129        match mode {
130            SubtableExternalCacheMode::Full => {
131                SubtableExternalCache::PairPosFormat1Cache(Box::new(PairPosFormat1Cache::new()))
132            }
133            SubtableExternalCacheMode::Small => {
134                if let Some(coverage) =
135                    CoverageInfo::new(&self.offset_data(), self.coverage_offset().to_u32() as u16)
136                {
137                    SubtableExternalCache::PairPosFormat1SmallCache(PairPosFormat1SmallCache {
138                        coverage,
139                    })
140                } else {
141                    SubtableExternalCache::None
142                }
143            }
144            SubtableExternalCacheMode::None => SubtableExternalCache::None,
145        }
146    }
147}
148
149impl Apply for PairPosFormat2<'_> {
150    fn apply_with_external_cache(
151        &self,
152        ctx: &mut hb_ot_apply_context_t,
153        external_cache: &SubtableExternalCache,
154    ) -> Option<()> {
155        let first_glyph = ctx.buffer.cur(0).as_glyph();
156        match external_cache {
157            SubtableExternalCache::PairPosFormat2Cache(cache) => coverage_index_cached(
158                |gid| self.coverage().ok()?.get(gid),
159                first_glyph,
160                &cache.coverage,
161            )?,
162            SubtableExternalCache::PairPosFormat2SmallCache(cache) => {
163                cache.coverage.index(&self.offset_data(), first_glyph)?
164            }
165            _ => coverage_index(self.coverage(), first_glyph)?,
166        };
167        let mut iter = skipping_iterator_t::new(ctx, false);
168        iter.reset(iter.buffer.idx);
169
170        let mut unsafe_to = 0;
171        if !iter.next(Some(&mut unsafe_to)) {
172            ctx.buffer
173                .unsafe_to_concat(Some(ctx.buffer.idx), Some(unsafe_to));
174            return None;
175        }
176
177        let second_glyph_index = iter.index();
178        let second_glyph = iter.buffer.info[second_glyph_index].as_glyph();
179
180        let finish = |ctx: &mut hb_ot_apply_context_t, iter_index: &mut usize, has_record2| {
181            if has_record2 {
182                *iter_index += 1;
183                // https://github.com/harfbuzz/harfbuzz/issues/3824
184                // https://github.com/harfbuzz/harfbuzz/issues/3888#issuecomment-1326781116
185                ctx.buffer
186                    .unsafe_to_break(Some(ctx.buffer.idx), Some(*iter_index + 1));
187            }
188
189            ctx.buffer.idx = *iter_index;
190
191            Some(())
192        };
193
194        let boring = |ctx: &mut hb_ot_apply_context_t, iter_index: &mut usize, has_record2| {
195            ctx.buffer
196                .unsafe_to_concat(Some(ctx.buffer.idx), Some(second_glyph_index + 1));
197            finish(ctx, iter_index, has_record2)
198        };
199
200        let success =
201            |ctx: &mut hb_ot_apply_context_t, iter_index: &mut usize, flag1, flag2, has_record2| {
202                if flag1 || flag2 {
203                    ctx.buffer
204                        .unsafe_to_break(Some(ctx.buffer.idx), Some(second_glyph_index + 1));
205                    finish(ctx, iter_index, has_record2)
206                } else {
207                    boring(ctx, iter_index, has_record2)
208                }
209            };
210        let data = self.offset_data();
211        let (class1, class2) = match external_cache {
212            SubtableExternalCache::PairPosFormat2Cache(cache) => (
213                glyph_class_cached(
214                    |gid| glyph_class(self.class_def1(), gid),
215                    first_glyph,
216                    &cache.first,
217                ),
218                glyph_class_cached(
219                    |gid| glyph_class(self.class_def2(), gid),
220                    second_glyph,
221                    &cache.second,
222                ),
223            ),
224            SubtableExternalCache::PairPosFormat2SmallCache(cache) => (
225                cache.first.class(&data, first_glyph),
226                cache.second.class(&data, second_glyph),
227            ),
228            _ => (
229                glyph_class(self.class_def1(), first_glyph),
230                glyph_class(self.class_def2(), second_glyph),
231            ),
232        };
233        let mut buf_idx = iter.buf_idx;
234        let format1 = self.value_format1();
235        let format1_len = format1.record_byte_len();
236        let format2 = self.value_format2();
237        let record_size = format1_len + format2.record_byte_len();
238        // Compute an offset into the 2D array of positioning records
239        let record_offset = (class1 as usize * record_size * self.class2_count() as usize)
240            + (class2 as usize * record_size)
241            + self.class1_records_byte_range().start;
242        let has_record2 = !format2.is_empty();
243        let worked1 = !format1.is_empty()
244            && super::apply_value(ctx, ctx.buffer.idx, &data, record_offset, format1) == Some(true);
245        let worked2 = has_record2
246            && super::apply_value(
247                ctx,
248                second_glyph_index,
249                &data,
250                record_offset + format1_len,
251                format2,
252            ) == Some(true);
253        success(ctx, &mut buf_idx, worked1, worked2, has_record2)
254    }
255
256    fn external_cache_create(&self, mode: SubtableExternalCacheMode) -> SubtableExternalCache {
257        match mode {
258            SubtableExternalCacheMode::Full => {
259                SubtableExternalCache::PairPosFormat2Cache(Box::new(PairPosFormat2Cache::new()))
260            }
261            SubtableExternalCacheMode::Small => {
262                let data = self.offset_data();
263                let coverage = CoverageInfo::new(&data, self.coverage_offset().to_u32() as u16);
264                let class1 = ClassDefInfo::new(&data, self.class_def1_offset().to_u32() as u16);
265                let class2 = ClassDefInfo::new(&data, self.class_def2_offset().to_u32() as u16);
266                if let Some((coverage, (first, second))) = coverage.zip(class1.zip(class2)) {
267                    SubtableExternalCache::PairPosFormat2SmallCache(PairPosFormat2SmallCache {
268                        coverage,
269                        first,
270                        second,
271                    })
272                } else {
273                    SubtableExternalCache::None
274                }
275            }
276            SubtableExternalCacheMode::None => SubtableExternalCache::None,
277        }
278    }
279}