Skip to main content

harfrust/hb/ot/gpos/
mark.rs

1use crate::hb::buffer::{hb_buffer_t, HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT};
2use crate::hb::ot_layout_common::lookup_flags;
3use crate::hb::ot_layout_gpos_table::attach_type;
4use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t;
5use crate::hb::ot_layout_gsubgpos::{match_t, skipping_iterator_t, Apply, MatchSource};
6use read_fonts::tables::gpos::{
7    AnchorTable, MarkArray, MarkBasePosFormat1, MarkLigPosFormat1, MarkMarkPosFormat1,
8};
9
10trait MarkArrayExt {
11    fn apply(
12        &self,
13        ctx: &mut hb_ot_apply_context_t,
14        base_anchor: &AnchorTable,
15        mark_anchor: &AnchorTable,
16        glyph_pos: usize,
17    ) -> Option<()>;
18}
19
20impl MarkArrayExt for MarkArray<'_> {
21    fn apply(
22        &self,
23        ctx: &mut hb_ot_apply_context_t,
24        base_anchor: &AnchorTable,
25        mark_anchor: &AnchorTable,
26        glyph_pos: usize,
27    ) -> Option<()> {
28        // If this subtable doesn't have an anchor for this base and this class
29        // return `None` such that the subsequent subtables have a chance at it.
30
31        let (base_x, base_y) = ctx.face.ot_tables.resolve_anchor(base_anchor);
32        let (mark_x, mark_y) = ctx.face.ot_tables.resolve_anchor(mark_anchor);
33        let x_offset = ctx.scale_x(base_x - mark_x);
34        let y_offset = ctx.scale_y(base_y - mark_y);
35
36        ctx.buffer
37            .unsafe_to_break(Some(glyph_pos), Some(ctx.buffer.idx + 1));
38
39        let idx = ctx.buffer.idx;
40        let pos = ctx.buffer.cur_pos_mut();
41        pos.x_offset = x_offset;
42        pos.y_offset = y_offset;
43        pos.set_attach_type(attach_type::MARK);
44        pos.set_attach_chain((glyph_pos as isize - idx as isize) as i16);
45
46        ctx.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
47        ctx.buffer.idx += 1;
48
49        Some(())
50    }
51}
52
53impl Apply for MarkBasePosFormat1<'_> {
54    fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
55        let mark_glyph = ctx.buffer.cur(0).as_glyph();
56        let mark_index = self.mark_coverage().ok()?.get(mark_glyph)?;
57
58        let base_coverage = self.base_coverage().ok()?;
59        let last_base_until = ctx.last_base_until;
60        let mut last_base = ctx.last_base;
61
62        // Due to borrowing rules, we have this piece of code before creating the
63        // iterator, unlike in harfbuzz.
64        if ctx.last_base_until > ctx.buffer.idx as u32 {
65            ctx.last_base_until = 0;
66            ctx.last_base = -1;
67        }
68
69        // Now we search backwards for a non-mark glyph
70        // We don't use skippy_iter.prev() to avoid O(n^2) behavior.
71        let mut iter = skipping_iterator_t::new(ctx, false);
72        iter.set_lookup_props(u32::from(lookup_flags::IGNORE_MARKS));
73
74        let mut j = iter.buffer.idx;
75        while j > last_base_until as usize {
76            let mut _match = iter.match_at(j - 1, MatchSource::Info);
77            if _match == match_t::MATCH {
78                // https://github.com/harfbuzz/harfbuzz/issues/4124
79                if !accept(iter.buffer, j - 1)
80                    && base_coverage
81                        .get(iter.buffer.info[j - 1].as_glyph())
82                        .is_none()
83                {
84                    _match = match_t::SKIP;
85                }
86            }
87
88            if _match == match_t::MATCH {
89                last_base = j as i32 - 1;
90                break;
91            }
92
93            j -= 1;
94        }
95        ctx.last_base_until = ctx.buffer.idx as u32;
96        ctx.last_base = last_base;
97
98        if ctx.last_base == -1 {
99            ctx.buffer
100                .unsafe_to_concat_from_outbuffer(Some(0), Some(ctx.buffer.idx + 1));
101            return None;
102        }
103
104        let idx = ctx.last_base as u32;
105
106        let info = &ctx.buffer.info;
107
108        // Checking that matched glyph is actually a base glyph by GDEF is too strong; disabled
109        let base_glyph = info[idx as usize].as_glyph();
110        let Some(base_index) = self.base_coverage().ok()?.get(base_glyph) else {
111            ctx.buffer
112                .unsafe_to_concat_from_outbuffer(Some(idx as usize), Some(ctx.buffer.idx + 1));
113            return None;
114        };
115
116        let mark_array = self.mark_array().ok()?;
117        let mark_record = mark_array.mark_records().get(mark_index as usize)?;
118        let mark_anchor = mark_record.mark_anchor(mark_array.offset_data()).ok()?;
119
120        let base_array = self.base_array().ok()?;
121        let base_record = base_array.base_records().get(base_index as usize).ok()?;
122        let base_anchor = base_record
123            .base_anchors(base_array.offset_data())
124            .get(mark_record.mark_class() as usize)?
125            .ok()?;
126
127        mark_array.apply(ctx, &base_anchor, &mark_anchor, idx as usize)
128    }
129}
130
131fn accept(buffer: &hb_buffer_t, idx: usize) -> bool {
132    /* We only want to attach to the first of a MultipleSubst sequence.
133     * https://github.com/harfbuzz/harfbuzz/issues/740
134     * Reject others...
135     * ...but stop if we find a mark in the MultipleSubst sequence:
136     * https://github.com/harfbuzz/harfbuzz/issues/1020 */
137    !buffer.info[idx].multiplied()
138        || 0 == buffer.info[idx].lig_comp()
139        || (idx == 0
140            || buffer.info[idx - 1].is_mark()
141            || !buffer.info[idx - 1].multiplied()
142            || buffer.info[idx].lig_id() != buffer.info[idx - 1].lig_id()
143            || buffer.info[idx].lig_comp() != buffer.info[idx - 1].lig_comp() + 1)
144}
145
146impl Apply for MarkMarkPosFormat1<'_> {
147    fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
148        let mark1_glyph = ctx.buffer.cur(0).as_glyph();
149        let mark1_index = self.mark1_coverage().ok()?.get(mark1_glyph)?;
150        let lookup_props = ctx.lookup_props;
151        // Now we search backwards for a suitable mark glyph until a non-mark glyph
152        let mut iter = skipping_iterator_t::new(ctx, false);
153        iter.reset_fast(iter.buffer.idx);
154        iter.set_lookup_props(lookup_props & !u32::from(lookup_flags::IGNORE_FLAGS));
155
156        let mut unsafe_from = 0;
157        if !iter.prev(Some(&mut unsafe_from)) {
158            iter.buffer
159                .unsafe_to_concat_from_outbuffer(Some(unsafe_from), Some(iter.buffer.idx + 1));
160            return None;
161        }
162
163        let iter_idx = iter.index();
164        if !ctx.buffer.info[iter_idx].is_mark() {
165            ctx.buffer
166                .unsafe_to_concat_from_outbuffer(Some(iter_idx), Some(ctx.buffer.idx + 1));
167            return None;
168        }
169
170        let id1 = ctx.buffer.cur(0).lig_id();
171        let id2 = ctx.buffer.info[iter_idx].lig_id();
172        let comp1 = ctx.buffer.cur(0).lig_comp();
173        let comp2 = ctx.buffer.info[iter_idx].lig_comp();
174
175        let matches = if id1 == id2 {
176            // Marks belonging to the same base
177            // or marks belonging to the same ligature component.
178            id1 == 0 || comp1 == comp2
179        } else {
180            // If ligature ids don't match, it may be the case that one of the marks
181            // itself is a ligature.  In which case match.
182            (id1 > 0 && comp1 == 0) || (id2 > 0 && comp2 == 0)
183        };
184
185        if !matches {
186            ctx.buffer
187                .unsafe_to_concat_from_outbuffer(Some(iter_idx), Some(ctx.buffer.idx + 1));
188            return None;
189        }
190
191        let mark2_glyph = ctx.buffer.info[iter_idx].as_glyph();
192        let mark2_index = self.mark2_coverage().ok()?.get(mark2_glyph)?;
193
194        let mark1_array = self.mark1_array().ok()?;
195        let mark1_record = mark1_array.mark_records().get(mark1_index as usize)?;
196        let mark1_anchor = mark1_record.mark_anchor(mark1_array.offset_data()).ok()?;
197
198        let base_array = self.mark2_array().ok()?;
199        let base_record = base_array.mark2_records().get(mark2_index as usize).ok()?;
200        let base_anchor = base_record
201            .mark2_anchors(base_array.offset_data())
202            .get(mark1_record.mark_class() as usize)?
203            .ok()?;
204
205        mark1_array.apply(ctx, &base_anchor, &mark1_anchor, iter_idx)
206    }
207}
208
209impl Apply for MarkLigPosFormat1<'_> {
210    fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
211        let mark_glyph = ctx.buffer.cur(0).as_glyph();
212        let mark_index = self.mark_coverage().ok()?.get(mark_glyph)? as usize;
213
214        // Due to borrowing rules, we have this piece of code before creating the
215        // iterator, unlike in harfbuzz.
216        if ctx.last_base_until > ctx.buffer.idx as u32 {
217            ctx.last_base_until = 0;
218            ctx.last_base = -1;
219        }
220
221        let last_base_until = ctx.last_base_until;
222        let mut last_base = ctx.last_base;
223
224        // Now we search backwards for a non-mark glyph
225        let mut iter = skipping_iterator_t::new(ctx, false);
226        iter.set_lookup_props(u32::from(lookup_flags::IGNORE_MARKS));
227
228        let mut j = iter.buffer.idx;
229        while j > last_base_until as usize {
230            let mut _match = iter.match_at(j - 1, MatchSource::Info);
231            if _match == match_t::MATCH {
232                last_base = j as i32 - 1;
233                break;
234            }
235            j -= 1;
236        }
237
238        ctx.last_base_until = ctx.buffer.idx as u32;
239        ctx.last_base = last_base;
240
241        if ctx.last_base == -1 {
242            ctx.buffer
243                .unsafe_to_concat_from_outbuffer(Some(0), Some(ctx.buffer.idx + 1));
244            return None;
245        }
246
247        let idx = ctx.last_base as usize;
248
249        // Checking that matched glyph is actually a ligature by GDEF is too strong; disabled
250
251        let lig_glyph = ctx.buffer.info[idx].as_glyph();
252        let Some(lig_index) = self.ligature_coverage().ok()?.get(lig_glyph) else {
253            ctx.buffer
254                .unsafe_to_concat_from_outbuffer(Some(idx), Some(ctx.buffer.idx + 1));
255            return None;
256        };
257        let lig_attach = self
258            .ligature_array()
259            .ok()?
260            .ligature_attaches()
261            .get(lig_index as usize)
262            .ok()?;
263
264        // Find component to attach to
265        let comp_count = lig_attach.component_count();
266        if comp_count == 0 {
267            ctx.buffer
268                .unsafe_to_concat_from_outbuffer(Some(idx), Some(ctx.buffer.idx + 1));
269            return None;
270        }
271
272        // We must now check whether the ligature ID of the current mark glyph
273        // is identical to the ligature ID of the found ligature.  If yes, we
274        // can directly use the component index.  If not, we attach the mark
275        // glyph to the last component of the ligature.
276        let lig_id = ctx.buffer.info[idx].lig_id();
277        let mark_id = ctx.buffer.cur(0).lig_id();
278        let mark_comp = u16::from(ctx.buffer.cur(0).lig_comp());
279        let matches = lig_id != 0 && lig_id == mark_id && mark_comp > 0;
280        let comp_index = if matches {
281            mark_comp.min(comp_count)
282        } else {
283            comp_count
284        } - 1;
285
286        let mark_array = self.mark_array().ok()?;
287        let mark_record = mark_array.mark_records().get(mark_index)?;
288        let mark_anchor = mark_record.mark_anchor(mark_array.offset_data()).ok()?;
289
290        let base_record = lig_attach
291            .component_records()
292            .get(comp_index as usize)
293            .ok()?;
294        let base_anchor = base_record
295            .ligature_anchors(lig_attach.offset_data())
296            .get(mark_record.mark_class() as usize)?
297            .ok()?;
298
299        mark_array.apply(ctx, &base_anchor, &mark_anchor, idx)
300    }
301}