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 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 #[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 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 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}