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