1/*
2 * Copyright © 2022 Behdad Esfahbod
3 *
4 * This is part of HarfBuzz, a text shaping library.
5 *
6 * Permission is hereby granted, without written agreement and without
7 * license or royalty fees, to use, copy, modify, and distribute this
8 * software and its documentation for any purpose, provided that the
9 * above copyright notice and the following two paragraphs appear in
10 * all copies of this software.
11 *
12 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
13 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
14 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
15 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
16 * DAMAGE.
17 *
18 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
19 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20 * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
21 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
22 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
23 */
24
25#ifndef HB_PAINT_EXTENTS_HH
26#define HB_PAINT_EXTENTS_HH
27
28#include "hb.hh"
29#include "hb-paint.h"
30
31
32typedef struct hb_extents_t
33{
34 hb_extents_t () {}
35 hb_extents_t (float xmin, float ymin, float xmax, float ymax) :
36 xmin (xmin), ymin (ymin), xmax (xmax), ymax (ymax) {}
37
38 bool is_empty () const { return xmin >= xmax || ymin >= ymax; }
39 bool is_void () const { return xmin > xmax; }
40
41 void union_ (const hb_extents_t &o)
42 {
43 xmin = hb_min (xmin, o.xmin);
44 ymin = hb_min (ymin, o.ymin);
45 xmax = hb_max (xmax, o.xmax);
46 ymax = hb_max (ymax, o.ymax);
47 }
48
49 void intersect (const hb_extents_t &o)
50 {
51 xmin = hb_max (xmin, o.xmin);
52 ymin = hb_max (ymin, o.ymin);
53 xmax = hb_min (xmax, o.xmax);
54 ymax = hb_min (ymax, o.ymax);
55 }
56
57 void
58 add_point (float x, float y)
59 {
60 if (unlikely (is_void ()))
61 {
62 xmin = xmax = x;
63 ymin = ymax = y;
64 }
65 else
66 {
67 xmin = hb_min (xmin, x);
68 ymin = hb_min (ymin, y);
69 xmax = hb_max (xmax, x);
70 ymax = hb_max (ymax, y);
71 }
72 }
73
74 float xmin = 0.f;
75 float ymin = 0.f;
76 float xmax = -1.f;
77 float ymax = -1.f;
78} hb_extents_t;
79
80typedef struct hb_transform_t
81{
82 hb_transform_t () {}
83 hb_transform_t (float xx, float yx,
84 float xy, float yy,
85 float x0, float y0) :
86 xx (xx), yx (yx), xy (xy), yy (yy), x0 (x0), y0 (y0) {}
87
88 void multiply (const hb_transform_t &o)
89 {
90 /* Copied from cairo, with "o" being "a" there and "this" being "b" there. */
91 hb_transform_t r;
92
93 r.xx = o.xx * xx + o.yx * xy;
94 r.yx = o.xx * yx + o.yx * yy;
95
96 r.xy = o.xy * xx + o.yy * xy;
97 r.yy = o.xy * yx + o.yy * yy;
98
99 r.x0 = o.x0 * xx + o.y0 * xy + x0;
100 r.y0 = o.x0 * yx + o.y0 * yy + y0;
101
102 *this = r;
103 }
104
105 void transform_distance (float &dx, float &dy) const
106 {
107 float new_x = xx * dx + xy * dy;
108 float new_y = yx * dx + yy * dy;
109 dx = new_x;
110 dy = new_y;
111 }
112
113 void transform_point (float &x, float &y) const
114 {
115 transform_distance (x, y);
116 x += x0;
117 y += y0;
118 }
119
120 void transform_extents (hb_extents_t &extents) const
121 {
122 float quad_x[4], quad_y[4];
123
124 quad_x[0] = extents.xmin;
125 quad_y[0] = extents.ymin;
126 quad_x[1] = extents.xmin;
127 quad_y[1] = extents.ymax;
128 quad_x[2] = extents.xmax;
129 quad_y[2] = extents.ymin;
130 quad_x[3] = extents.xmax;
131 quad_y[3] = extents.ymax;
132
133 extents = hb_extents_t {};
134 for (unsigned i = 0; i < 4; i++)
135 {
136 transform_point (quad_x[i], quad_y[i]);
137 extents.add_point (quad_x[i], quad_y[i]);
138 }
139 }
140
141 float xx = 1.f;
142 float yx = 0.f;
143 float xy = 0.f;
144 float yy = 1.f;
145 float x0 = 0.f;
146 float y0 = 0.f;
147} hb_transform_t;
148
149typedef struct hb_bounds_t
150{
151 enum status_t {
152 UNBOUNDED,
153 BOUNDED,
154 EMPTY,
155 };
156
157 hb_bounds_t (status_t status) : status (status) {}
158 hb_bounds_t (const hb_extents_t &extents) :
159 status (extents.is_empty () ? EMPTY : BOUNDED), extents (extents) {}
160
161 void union_ (const hb_bounds_t &o)
162 {
163 if (o.status == UNBOUNDED)
164 status = UNBOUNDED;
165 else if (o.status == BOUNDED)
166 {
167 if (status == EMPTY)
168 *this = o;
169 else if (status == BOUNDED)
170 extents.union_ (o.extents);
171 }
172 }
173
174 void intersect (const hb_bounds_t &o)
175 {
176 if (o.status == EMPTY)
177 status = EMPTY;
178 else if (o.status == BOUNDED)
179 {
180 if (status == UNBOUNDED)
181 *this = o;
182 else if (status == BOUNDED)
183 {
184 extents.intersect (o.extents);
185 if (extents.is_empty ())
186 status = EMPTY;
187 }
188 }
189 }
190
191 status_t status;
192 hb_extents_t extents;
193} hb_bounds_t;
194
195typedef struct hb_paint_extents_context_t hb_paint_extents_context_t;
196
197struct hb_paint_extents_context_t
198{
199 hb_paint_extents_context_t ()
200 {
201 transforms.push (hb_transform_t{});
202 clips.push (hb_bounds_t{hb_bounds_t::UNBOUNDED});
203 groups.push (hb_bounds_t{hb_bounds_t::EMPTY});
204 }
205
206 hb_extents_t get_extents ()
207 {
208 return groups.tail().extents;
209 }
210
211 bool is_bounded ()
212 {
213 return groups.tail().status != hb_bounds_t::UNBOUNDED;
214 }
215
216 void push_transform (const hb_transform_t &trans)
217 {
218 hb_transform_t t = transforms.tail ();
219 t.multiply (trans);
220 transforms.push (t);
221 }
222
223 void pop_transform ()
224 {
225 transforms.pop ();
226 }
227
228 void push_clip (hb_extents_t extents)
229 {
230 /* Transform extents and push a new clip. */
231 const hb_transform_t &t = transforms.tail ();
232 t.transform_extents (extents);
233
234 clips.push (hb_bounds_t {extents});
235 }
236
237 void pop_clip ()
238 {
239 clips.pop ();
240 }
241
242 void push_group ()
243 {
244 groups.push (hb_bounds_t {hb_bounds_t::EMPTY});
245 }
246
247 void pop_group (hb_paint_composite_mode_t mode)
248 {
249 const hb_bounds_t src_bounds = groups.pop ();
250 hb_bounds_t &backdrop_bounds = groups.tail ();
251
252 // https://learn.microsoft.com/en-us/typography/opentype/spec/colr#format-32-paintcomposite
253 switch ((int) mode)
254 {
255 case HB_PAINT_COMPOSITE_MODE_CLEAR:
256 backdrop_bounds.status = hb_bounds_t::EMPTY;
257 break;
258 case HB_PAINT_COMPOSITE_MODE_SRC:
259 case HB_PAINT_COMPOSITE_MODE_SRC_OUT:
260 backdrop_bounds = src_bounds;
261 break;
262 case HB_PAINT_COMPOSITE_MODE_DEST:
263 case HB_PAINT_COMPOSITE_MODE_DEST_OUT:
264 break;
265 case HB_PAINT_COMPOSITE_MODE_SRC_IN:
266 case HB_PAINT_COMPOSITE_MODE_DEST_IN:
267 backdrop_bounds.intersect (src_bounds);
268 break;
269 default:
270 backdrop_bounds.union_ (src_bounds);
271 break;
272 }
273 }
274
275 void paint ()
276 {
277 const hb_bounds_t &clip = clips.tail ();
278 hb_bounds_t &group = groups.tail ();
279
280 group.union_ (clip);
281 }
282
283 protected:
284 hb_vector_t<hb_transform_t> transforms;
285 hb_vector_t<hb_bounds_t> clips;
286 hb_vector_t<hb_bounds_t> groups;
287};
288
289HB_INTERNAL hb_paint_funcs_t *
290hb_paint_extents_get_funcs ();
291
292
293#endif /* HB_PAINT_EXTENTS_HH */
294