1 #ifndef OT_LAYOUT_GPOS_CURSIVEPOSFORMAT1_HH
2 #define OT_LAYOUT_GPOS_CURSIVEPOSFORMAT1_HH
3
4 #include "Anchor.hh"
5
6 namespace OT {
7 namespace Layout {
8 namespace GPOS_impl {
9
10 struct EntryExitRecord
11 {
12 friend struct CursivePosFormat1;
13
sanitizeOT::Layout::GPOS_impl::EntryExitRecord14 bool sanitize (hb_sanitize_context_t *c, const void *base) const
15 {
16 TRACE_SANITIZE (this);
17 return_trace (entryAnchor.sanitize (c, base) && exitAnchor.sanitize (c, base));
18 }
19
collect_variation_indicesOT::Layout::GPOS_impl::EntryExitRecord20 void collect_variation_indices (hb_collect_variation_indices_context_t *c,
21 const void *src_base) const
22 {
23 (src_base+entryAnchor).collect_variation_indices (c);
24 (src_base+exitAnchor).collect_variation_indices (c);
25 }
26
subsetOT::Layout::GPOS_impl::EntryExitRecord27 EntryExitRecord* subset (hb_subset_context_t *c,
28 const void *src_base) const
29 {
30 TRACE_SERIALIZE (this);
31 auto *out = c->serializer->embed (this);
32 if (unlikely (!out)) return_trace (nullptr);
33
34 out->entryAnchor.serialize_subset (c, entryAnchor, src_base);
35 out->exitAnchor.serialize_subset (c, exitAnchor, src_base);
36 return_trace (out);
37 }
38
39 protected:
40 Offset16To<Anchor>
41 entryAnchor; /* Offset to EntryAnchor table--from
42 * beginning of CursivePos
43 * subtable--may be NULL */
44 Offset16To<Anchor>
45 exitAnchor; /* Offset to ExitAnchor table--from
46 * beginning of CursivePos
47 * subtable--may be NULL */
48 public:
49 DEFINE_SIZE_STATIC (4);
50 };
51
52 static void
reverse_cursive_minor_offset(hb_glyph_position_t * pos,unsigned int i,hb_direction_t direction,unsigned int new_parent)53 reverse_cursive_minor_offset (hb_glyph_position_t *pos, unsigned int i, hb_direction_t direction, unsigned int new_parent) {
54 int chain = pos[i].attach_chain(), type = pos[i].attach_type();
55 if (likely (!chain || 0 == (type & ATTACH_TYPE_CURSIVE)))
56 return;
57
58 pos[i].attach_chain() = 0;
59
60 unsigned int j = (int) i + chain;
61
62 /* Stop if we see new parent in the chain. */
63 if (j == new_parent)
64 return;
65
66 reverse_cursive_minor_offset (pos, j, direction, new_parent);
67
68 if (HB_DIRECTION_IS_HORIZONTAL (direction))
69 pos[j].y_offset = -pos[i].y_offset;
70 else
71 pos[j].x_offset = -pos[i].x_offset;
72
73 pos[j].attach_chain() = -chain;
74 pos[j].attach_type() = type;
75 }
76
77
78 struct CursivePosFormat1
79 {
80 protected:
81 HBUINT16 format; /* Format identifier--format = 1 */
82 Offset16To<Coverage>
83 coverage; /* Offset to Coverage table--from
84 * beginning of subtable */
85 Array16Of<EntryExitRecord>
86 entryExitRecord; /* Array of EntryExit records--in
87 * Coverage Index order */
88 public:
89 DEFINE_SIZE_ARRAY (6, entryExitRecord);
90
sanitizeOT::Layout::GPOS_impl::CursivePosFormat191 bool sanitize (hb_sanitize_context_t *c) const
92 {
93 TRACE_SANITIZE (this);
94 return_trace (coverage.sanitize (c, this) && entryExitRecord.sanitize (c, this));
95 }
96
intersectsOT::Layout::GPOS_impl::CursivePosFormat197 bool intersects (const hb_set_t *glyphs) const
98 { return (this+coverage).intersects (glyphs); }
99
closure_lookupsOT::Layout::GPOS_impl::CursivePosFormat1100 void closure_lookups (hb_closure_lookups_context_t *c) const {}
101
collect_variation_indicesOT::Layout::GPOS_impl::CursivePosFormat1102 void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
103 {
104 + hb_zip (this+coverage, entryExitRecord)
105 | hb_filter (c->glyph_set, hb_first)
106 | hb_map (hb_second)
107 | hb_apply ([&] (const EntryExitRecord& record) { record.collect_variation_indices (c, this); })
108 ;
109 }
110
collect_glyphsOT::Layout::GPOS_impl::CursivePosFormat1111 void collect_glyphs (hb_collect_glyphs_context_t *c) const
112 { if (unlikely (!(this+coverage).collect_coverage (c->input))) return; }
113
get_coverageOT::Layout::GPOS_impl::CursivePosFormat1114 const Coverage &get_coverage () const { return this+coverage; }
115
applyOT::Layout::GPOS_impl::CursivePosFormat1116 bool apply (hb_ot_apply_context_t *c) const
117 {
118 TRACE_APPLY (this);
119 hb_buffer_t *buffer = c->buffer;
120
121 const EntryExitRecord &this_record = entryExitRecord[(this+coverage).get_coverage (buffer->cur().codepoint)];
122 if (!this_record.entryAnchor) return_trace (false);
123
124 hb_ot_apply_context_t::skipping_iterator_t &skippy_iter = c->iter_input;
125 skippy_iter.reset (buffer->idx, 1);
126 unsigned unsafe_from;
127 if (!skippy_iter.prev (&unsafe_from))
128 {
129 buffer->unsafe_to_concat_from_outbuffer (unsafe_from, buffer->idx + 1);
130 return_trace (false);
131 }
132
133 const EntryExitRecord &prev_record = entryExitRecord[(this+coverage).get_coverage (buffer->info[skippy_iter.idx].codepoint)];
134 if (!prev_record.exitAnchor)
135 {
136 buffer->unsafe_to_concat_from_outbuffer (skippy_iter.idx, buffer->idx + 1);
137 return_trace (false);
138 }
139
140 unsigned int i = skippy_iter.idx;
141 unsigned int j = buffer->idx;
142
143 if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
144 {
145 c->buffer->message (c->font,
146 "cursive attaching glyph at %d to glyph at %d",
147 i, j);
148 }
149
150 buffer->unsafe_to_break (i, j + 1);
151 float entry_x, entry_y, exit_x, exit_y;
152 (this+prev_record.exitAnchor).get_anchor (c, buffer->info[i].codepoint, &exit_x, &exit_y);
153 (this+this_record.entryAnchor).get_anchor (c, buffer->info[j].codepoint, &entry_x, &entry_y);
154
155 hb_glyph_position_t *pos = buffer->pos;
156
157 hb_position_t d;
158 /* Main-direction adjustment */
159 switch (c->direction) {
160 case HB_DIRECTION_LTR:
161 pos[i].x_advance = roundf (exit_x) + pos[i].x_offset;
162
163 d = roundf (entry_x) + pos[j].x_offset;
164 pos[j].x_advance -= d;
165 pos[j].x_offset -= d;
166 break;
167 case HB_DIRECTION_RTL:
168 d = roundf (exit_x) + pos[i].x_offset;
169 pos[i].x_advance -= d;
170 pos[i].x_offset -= d;
171
172 pos[j].x_advance = roundf (entry_x) + pos[j].x_offset;
173 break;
174 case HB_DIRECTION_TTB:
175 pos[i].y_advance = roundf (exit_y) + pos[i].y_offset;
176
177 d = roundf (entry_y) + pos[j].y_offset;
178 pos[j].y_advance -= d;
179 pos[j].y_offset -= d;
180 break;
181 case HB_DIRECTION_BTT:
182 d = roundf (exit_y) + pos[i].y_offset;
183 pos[i].y_advance -= d;
184 pos[i].y_offset -= d;
185
186 pos[j].y_advance = roundf (entry_y);
187 break;
188 case HB_DIRECTION_INVALID:
189 default:
190 break;
191 }
192
193 /* Cross-direction adjustment */
194
195 /* We attach child to parent (think graph theory and rooted trees whereas
196 * the root stays on baseline and each node aligns itself against its
197 * parent.
198 *
199 * Optimize things for the case of RightToLeft, as that's most common in
200 * Arabic. */
201 unsigned int child = i;
202 unsigned int parent = j;
203 hb_position_t x_offset = entry_x - exit_x;
204 hb_position_t y_offset = entry_y - exit_y;
205 if (!(c->lookup_props & LookupFlag::RightToLeft))
206 {
207 unsigned int k = child;
208 child = parent;
209 parent = k;
210 x_offset = -x_offset;
211 y_offset = -y_offset;
212 }
213
214 /* If child was already connected to someone else, walk through its old
215 * chain and reverse the link direction, such that the whole tree of its
216 * previous connection now attaches to new parent. Watch out for case
217 * where new parent is on the path from old chain...
218 */
219 reverse_cursive_minor_offset (pos, child, c->direction, parent);
220
221 pos[child].attach_type() = ATTACH_TYPE_CURSIVE;
222 pos[child].attach_chain() = (int) parent - (int) child;
223 buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
224 if (likely (HB_DIRECTION_IS_HORIZONTAL (c->direction)))
225 pos[child].y_offset = y_offset;
226 else
227 pos[child].x_offset = x_offset;
228
229 /* If parent was attached to child, separate them.
230 * https://github.com/harfbuzz/harfbuzz/issues/2469
231 */
232 if (unlikely (pos[parent].attach_chain() == -pos[child].attach_chain()))
233 {
234 pos[parent].attach_chain() = 0;
235 if (likely (HB_DIRECTION_IS_HORIZONTAL (c->direction)))
236 pos[parent].y_offset = 0;
237 else
238 pos[parent].x_offset = 0;
239 }
240
241 if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
242 {
243 c->buffer->message (c->font,
244 "cursive attached glyph at %d to glyph at %d",
245 i, j);
246 }
247
248 buffer->idx++;
249 return_trace (true);
250 }
251
252 template <typename Iterator,
253 hb_requires (hb_is_iterator (Iterator))>
serializeOT::Layout::GPOS_impl::CursivePosFormat1254 void serialize (hb_subset_context_t *c,
255 Iterator it,
256 const void *src_base)
257 {
258 if (unlikely (!c->serializer->extend_min ((*this)))) return;
259 this->format = 1;
260 this->entryExitRecord.len = it.len ();
261
262 for (const EntryExitRecord& entry_record : + it
263 | hb_map (hb_second))
264 entry_record.subset (c, src_base);
265
266 auto glyphs =
267 + it
268 | hb_map_retains_sorting (hb_first)
269 ;
270
271 coverage.serialize_serialize (c->serializer, glyphs);
272 }
273
subsetOT::Layout::GPOS_impl::CursivePosFormat1274 bool subset (hb_subset_context_t *c) const
275 {
276 TRACE_SUBSET (this);
277 const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
278 const hb_map_t &glyph_map = *c->plan->glyph_map;
279
280 auto *out = c->serializer->start_embed (*this);
281 if (unlikely (!out)) return_trace (false);
282
283 auto it =
284 + hb_zip (this+coverage, entryExitRecord)
285 | hb_filter (glyphset, hb_first)
286 | hb_map_retains_sorting ([&] (hb_pair_t<hb_codepoint_t, const EntryExitRecord&> p) -> hb_pair_t<hb_codepoint_t, const EntryExitRecord&>
287 { return hb_pair (glyph_map[p.first], p.second);})
288 ;
289
290 bool ret = bool (it);
291 out->serialize (c, it, this);
292 return_trace (ret);
293 }
294 };
295
296
297 }
298 }
299 }
300
301 #endif /* OT_LAYOUT_GPOS_CURSIVEPOSFORMAT1_HH */
302