1/*
2 * Copyright © 2018 Ebrahim Byagowi
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_OT_STAT_TABLE_HH
26#define HB_OT_STAT_TABLE_HH
27
28#include "hb-open-type.hh"
29#include "hb-ot-layout-common.hh"
30
31/*
32 * STAT -- Style Attributes
33 * https://docs.microsoft.com/en-us/typography/opentype/spec/stat
34 */
35#define HB_OT_TAG_STAT HB_TAG('S','T','A','T')
36
37
38namespace OT {
39
40enum
41{
42 OLDER_SIBLING_FONT_ATTRIBUTE = 0x0001, /* If set, this axis value table
43 * provides axis value information
44 * that is applicable to other fonts
45 * within the same font family. This
46 * is used if the other fonts were
47 * released earlier and did not include
48 * information about values for some axis.
49 * If newer versions of the other
50 * fonts include the information
51 * themselves and are present,
52 * then this record is ignored. */
53 ELIDABLE_AXIS_VALUE_NAME = 0x0002 /* If set, it indicates that the axis
54 * value represents the “normal” value
55 * for the axis and may be omitted when
56 * composing name strings. */
57 // Reserved = 0xFFFC /* Reserved for future use — set to zero. */
58};
59
60static bool axis_value_is_outside_axis_range (hb_tag_t axis_tag, float axis_value,
61 const hb_hashmap_t<hb_tag_t, Triple> *user_axes_location)
62{
63 if (!user_axes_location->has (axis_tag))
64 return false;
65
66 Triple axis_range = user_axes_location->get (axis_tag);
67 return (axis_value < axis_range.minimum || axis_value > axis_range.maximum);
68}
69
70struct StatAxisRecord
71{
72 int cmp (hb_tag_t key) const { return tag.cmp (key); }
73
74 hb_ot_name_id_t get_name_id () const { return nameID; }
75
76 hb_tag_t get_axis_tag () const { return tag; }
77
78 bool sanitize (hb_sanitize_context_t *c) const
79 {
80 TRACE_SANITIZE (this);
81 return_trace (likely (c->check_struct (this)));
82 }
83
84 protected:
85 Tag tag; /* A tag identifying the axis of design variation. */
86 NameID nameID; /* The name ID for entries in the 'name' table that
87 * provide a display string for this axis. */
88 HBUINT16 ordering; /* A value that applications can use to determine
89 * primary sorting of face names, or for ordering
90 * of descriptors when composing family or face names. */
91 public:
92 DEFINE_SIZE_STATIC (8);
93};
94
95struct AxisValueFormat1
96{
97 unsigned int get_axis_index () const { return axisIndex; }
98 float get_value () const { return value.to_float (); }
99
100 hb_ot_name_id_t get_value_name_id () const { return valueNameID; }
101
102 hb_tag_t get_axis_tag (const hb_array_t<const StatAxisRecord> axis_records) const
103 {
104 unsigned axis_idx = get_axis_index ();
105 return axis_records[axis_idx].get_axis_tag ();
106 }
107
108 bool keep_axis_value (const hb_array_t<const StatAxisRecord> axis_records,
109 const hb_hashmap_t<hb_tag_t, Triple> *user_axes_location) const
110 {
111 hb_tag_t axis_tag = get_axis_tag (axis_records);
112 float axis_value = get_value ();
113
114 return !axis_value_is_outside_axis_range (axis_tag, axis_value, user_axes_location);
115 }
116
117 bool subset (hb_subset_context_t *c,
118 const hb_array_t<const StatAxisRecord> axis_records) const
119 {
120 TRACE_SUBSET (this);
121 const hb_hashmap_t<hb_tag_t, Triple>* user_axes_location = &c->plan->user_axes_location;
122
123 if (keep_axis_value (axis_records, user_axes_location))
124 return_trace (c->serializer->embed (this));
125
126 return_trace (false);
127 }
128
129 bool sanitize (hb_sanitize_context_t *c) const
130 {
131 TRACE_SANITIZE (this);
132 return_trace (c->check_struct (this));
133 }
134
135 protected:
136 HBUINT16 format; /* Format identifier — set to 1. */
137 HBUINT16 axisIndex; /* Zero-base index into the axis record array
138 * identifying the axis of design variation
139 * to which the axis value record applies.
140 * Must be less than designAxisCount. */
141 HBUINT16 flags; /* Flags — see below for details. */
142 NameID valueNameID; /* The name ID for entries in the 'name' table
143 * that provide a display string for this
144 * attribute value. */
145 F16DOT16 value; /* A numeric value for this attribute value. */
146 public:
147 DEFINE_SIZE_STATIC (12);
148};
149
150struct AxisValueFormat2
151{
152 unsigned int get_axis_index () const { return axisIndex; }
153 float get_value () const { return nominalValue.to_float (); }
154
155 hb_ot_name_id_t get_value_name_id () const { return valueNameID; }
156
157 hb_tag_t get_axis_tag (const hb_array_t<const StatAxisRecord> axis_records) const
158 {
159 unsigned axis_idx = get_axis_index ();
160 return axis_records[axis_idx].get_axis_tag ();
161 }
162
163 bool keep_axis_value (const hb_array_t<const StatAxisRecord> axis_records,
164 const hb_hashmap_t<hb_tag_t, Triple> *user_axes_location) const
165 {
166 hb_tag_t axis_tag = get_axis_tag (axis_records);
167 float axis_value = get_value ();
168
169 return !axis_value_is_outside_axis_range (axis_tag, axis_value, user_axes_location);
170 }
171
172 bool subset (hb_subset_context_t *c,
173 const hb_array_t<const StatAxisRecord> axis_records) const
174 {
175 TRACE_SUBSET (this);
176 const hb_hashmap_t<hb_tag_t, Triple>* user_axes_location = &c->plan->user_axes_location;
177
178 if (keep_axis_value (axis_records, user_axes_location))
179 return_trace (c->serializer->embed (this));
180
181 return_trace (false);
182 }
183
184 bool sanitize (hb_sanitize_context_t *c) const
185 {
186 TRACE_SANITIZE (this);
187 return_trace (c->check_struct (this));
188 }
189
190 protected:
191 HBUINT16 format; /* Format identifier — set to 2. */
192 HBUINT16 axisIndex; /* Zero-base index into the axis record array
193 * identifying the axis of design variation
194 * to which the axis value record applies.
195 * Must be less than designAxisCount. */
196 HBUINT16 flags; /* Flags — see below for details. */
197 NameID valueNameID; /* The name ID for entries in the 'name' table
198 * that provide a display string for this
199 * attribute value. */
200 F16DOT16 nominalValue; /* A numeric value for this attribute value. */
201 F16DOT16 rangeMinValue; /* The minimum value for a range associated
202 * with the specified name ID. */
203 F16DOT16 rangeMaxValue; /* The maximum value for a range associated
204 * with the specified name ID. */
205 public:
206 DEFINE_SIZE_STATIC (20);
207};
208
209struct AxisValueFormat3
210{
211 unsigned int get_axis_index () const { return axisIndex; }
212 float get_value () const { return value.to_float (); }
213
214 hb_ot_name_id_t get_value_name_id () const { return valueNameID; }
215
216 hb_tag_t get_axis_tag (const hb_array_t<const StatAxisRecord> axis_records) const
217 {
218 unsigned axis_idx = get_axis_index ();
219 return axis_records[axis_idx].get_axis_tag ();
220 }
221
222 bool keep_axis_value (const hb_array_t<const StatAxisRecord> axis_records,
223 const hb_hashmap_t<hb_tag_t, Triple> *user_axes_location) const
224 {
225 hb_tag_t axis_tag = get_axis_tag (axis_records);
226 float axis_value = get_value ();
227
228 return !axis_value_is_outside_axis_range (axis_tag, axis_value, user_axes_location);
229 }
230
231 bool subset (hb_subset_context_t *c,
232 const hb_array_t<const StatAxisRecord> axis_records) const
233 {
234 TRACE_SUBSET (this);
235 const hb_hashmap_t<hb_tag_t, Triple>* user_axes_location = &c->plan->user_axes_location;
236
237 if (keep_axis_value (axis_records, user_axes_location))
238 return_trace (c->serializer->embed (this));
239
240 return_trace (false);
241 }
242
243 bool sanitize (hb_sanitize_context_t *c) const
244 {
245 TRACE_SANITIZE (this);
246 return_trace (c->check_struct (this));
247 }
248
249 protected:
250 HBUINT16 format; /* Format identifier — set to 3. */
251 HBUINT16 axisIndex; /* Zero-base index into the axis record array
252 * identifying the axis of design variation
253 * to which the axis value record applies.
254 * Must be less than designAxisCount. */
255 HBUINT16 flags; /* Flags — see below for details. */
256 NameID valueNameID; /* The name ID for entries in the 'name' table
257 * that provide a display string for this
258 * attribute value. */
259 F16DOT16 value; /* A numeric value for this attribute value. */
260 F16DOT16 linkedValue; /* The numeric value for a style-linked mapping
261 * from this value. */
262 public:
263 DEFINE_SIZE_STATIC (16);
264};
265
266struct AxisValueRecord
267{
268 unsigned int get_axis_index () const { return axisIndex; }
269 float get_value () const { return value.to_float (); }
270
271 bool sanitize (hb_sanitize_context_t *c) const
272 {
273 TRACE_SANITIZE (this);
274 return_trace (c->check_struct (this));
275 }
276
277 protected:
278 HBUINT16 axisIndex; /* Zero-base index into the axis record array
279 * identifying the axis to which this value
280 * applies. Must be less than designAxisCount. */
281 F16DOT16 value; /* A numeric value for this attribute value. */
282 public:
283 DEFINE_SIZE_STATIC (6);
284};
285
286struct AxisValueFormat4
287{
288 const AxisValueRecord &get_axis_record (unsigned int axis_index) const
289 { return axisValues.as_array (axisCount)[axis_index]; }
290
291 bool keep_axis_value (const hb_array_t<const StatAxisRecord> axis_records,
292 const hb_hashmap_t<hb_tag_t, Triple> *user_axes_location) const
293 {
294 hb_array_t<const AxisValueRecord> axis_value_records = axisValues.as_array (axisCount);
295
296 for (const auto& rec : axis_value_records)
297 {
298 unsigned axis_idx = rec.get_axis_index ();
299 float axis_value = rec.get_value ();
300 hb_tag_t axis_tag = axis_records[axis_idx].get_axis_tag ();
301
302 if (axis_value_is_outside_axis_range (axis_tag, axis_value, user_axes_location))
303 return false;
304 }
305
306 return true;
307 }
308
309 bool subset (hb_subset_context_t *c,
310 const hb_array_t<const StatAxisRecord> axis_records) const
311 {
312 TRACE_SUBSET (this);
313 const hb_hashmap_t<hb_tag_t, Triple> *user_axes_location = &c->plan->user_axes_location;
314 if (!keep_axis_value (axis_records, user_axes_location))
315 return_trace (false);
316
317 unsigned total_size = min_size + axisCount * AxisValueRecord::static_size;
318 auto *out = c->serializer->allocate_size<AxisValueFormat4> (total_size);
319 if (unlikely (!out)) return_trace (false);
320 hb_memcpy (out, this, total_size);
321 return_trace (true);
322 }
323
324 hb_ot_name_id_t get_value_name_id () const { return valueNameID; }
325
326 bool sanitize (hb_sanitize_context_t *c) const
327 {
328 TRACE_SANITIZE (this);
329 return_trace (likely (c->check_struct (this) &&
330 axisValues.sanitize (c, axisCount)));
331 }
332
333 protected:
334 HBUINT16 format; /* Format identifier — set to 4. */
335 HBUINT16 axisCount; /* The total number of axes contributing to
336 * this axis-values combination. */
337 HBUINT16 flags; /* Flags — see below for details. */
338 NameID valueNameID; /* The name ID for entries in the 'name' table
339 * that provide a display string for this
340 * attribute value. */
341 UnsizedArrayOf<AxisValueRecord>
342 axisValues; /* Array of AxisValue records that provide the
343 * combination of axis values, one for each
344 * contributing axis. */
345 public:
346 DEFINE_SIZE_ARRAY (8, axisValues);
347};
348
349struct AxisValue
350{
351 bool get_value (unsigned int axis_index) const
352 {
353 switch (u.format)
354 {
355 case 1: return u.format1.get_value ();
356 case 2: return u.format2.get_value ();
357 case 3: return u.format3.get_value ();
358 case 4: return u.format4.get_axis_record (axis_index).get_value ();
359 default:return 0;
360 }
361 }
362
363 unsigned int get_axis_index () const
364 {
365 switch (u.format)
366 {
367 case 1: return u.format1.get_axis_index ();
368 case 2: return u.format2.get_axis_index ();
369 case 3: return u.format3.get_axis_index ();
370 /* case 4: Makes more sense for variable fonts which are handled by fvar in hb-style */
371 default:return -1;
372 }
373 }
374
375 hb_ot_name_id_t get_value_name_id () const
376 {
377 switch (u.format)
378 {
379 case 1: return u.format1.get_value_name_id ();
380 case 2: return u.format2.get_value_name_id ();
381 case 3: return u.format3.get_value_name_id ();
382 case 4: return u.format4.get_value_name_id ();
383 default:return HB_OT_NAME_ID_INVALID;
384 }
385 }
386
387 template <typename context_t, typename ...Ts>
388 typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
389 {
390 if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
391 TRACE_DISPATCH (this, u.format);
392 switch (u.format) {
393 case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
394 case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
395 case 3: return_trace (c->dispatch (u.format3, std::forward<Ts> (ds)...));
396 case 4: return_trace (c->dispatch (u.format4, std::forward<Ts> (ds)...));
397 default:return_trace (c->default_return_value ());
398 }
399 }
400
401 bool keep_axis_value (const hb_array_t<const StatAxisRecord> axis_records,
402 hb_hashmap_t<hb_tag_t, Triple> *user_axes_location) const
403 {
404 switch (u.format)
405 {
406 case 1: return u.format1.keep_axis_value (axis_records, user_axes_location);
407 case 2: return u.format2.keep_axis_value (axis_records, user_axes_location);
408 case 3: return u.format3.keep_axis_value (axis_records, user_axes_location);
409 case 4: return u.format4.keep_axis_value (axis_records, user_axes_location);
410 default:return false;
411 }
412 }
413
414 bool sanitize (hb_sanitize_context_t *c) const
415 {
416 TRACE_SANITIZE (this);
417 if (unlikely (!c->check_struct (this)))
418 return_trace (false);
419
420 switch (u.format)
421 {
422 case 1: return_trace (u.format1.sanitize (c));
423 case 2: return_trace (u.format2.sanitize (c));
424 case 3: return_trace (u.format3.sanitize (c));
425 case 4: return_trace (u.format4.sanitize (c));
426 default:return_trace (true);
427 }
428 }
429
430 protected:
431 union
432 {
433 HBUINT16 format;
434 AxisValueFormat1 format1;
435 AxisValueFormat2 format2;
436 AxisValueFormat3 format3;
437 AxisValueFormat4 format4;
438 } u;
439 public:
440 DEFINE_SIZE_UNION (2, format);
441};
442
443struct AxisValueOffsetArray: UnsizedArrayOf<Offset16To<AxisValue>>
444{
445 bool subset (hb_subset_context_t *c,
446 unsigned axisValueCount,
447 unsigned& count,
448 const hb_array_t<const StatAxisRecord> axis_records) const
449 {
450 TRACE_SUBSET (this);
451
452 auto axisValueOffsets = as_array (axisValueCount);
453 count = 0;
454 for (const auto& offset : axisValueOffsets)
455 {
456 if (!offset) continue;
457 auto o_snap = c->serializer->snapshot ();
458 auto *o = c->serializer->embed (offset);
459 if (!o) return_trace (false);
460 if (!o->serialize_subset (c, offset, this, axis_records))
461 {
462 c->serializer->revert (o_snap);
463 continue;
464 }
465 count++;
466 }
467
468 return_trace (count);
469 }
470};
471
472struct STAT
473{
474 static constexpr hb_tag_t tableTag = HB_OT_TAG_STAT;
475
476 bool has_data () const { return version.to_int (); }
477
478 bool get_value (hb_tag_t tag, float *value) const
479 {
480 unsigned int axis_index;
481 if (!get_design_axes ().lfind (tag, &axis_index)) return false;
482
483 hb_array_t<const Offset16To<AxisValue>> axis_values = get_axis_value_offsets ();
484 for (unsigned int i = 0; i < axis_values.length; i++)
485 {
486 const AxisValue& axis_value = this+axis_values[i];
487 if (axis_value.get_axis_index () == axis_index)
488 {
489 if (value)
490 *value = axis_value.get_value (axis_index);
491 return true;
492 }
493 }
494 return false;
495 }
496
497 unsigned get_design_axis_count () const { return designAxisCount; }
498
499 hb_ot_name_id_t get_axis_record_name_id (unsigned axis_record_index) const
500 {
501 if (unlikely (axis_record_index >= designAxisCount)) return HB_OT_NAME_ID_INVALID;
502 const StatAxisRecord &axis_record = get_design_axes ()[axis_record_index];
503 return axis_record.get_name_id ();
504 }
505
506 unsigned get_axis_value_count () const { return axisValueCount; }
507
508 hb_ot_name_id_t get_axis_value_name_id (unsigned axis_value_index) const
509 {
510 if (unlikely (axis_value_index >= axisValueCount)) return HB_OT_NAME_ID_INVALID;
511 const AxisValue &axis_value = (this + get_axis_value_offsets ()[axis_value_index]);
512 return axis_value.get_value_name_id ();
513 }
514
515 void collect_name_ids (hb_hashmap_t<hb_tag_t, Triple> *user_axes_location,
516 hb_set_t *nameids_to_retain /* OUT */) const
517 {
518 if (!has_data ()) return;
519
520 + get_design_axes ()
521 | hb_map (&StatAxisRecord::get_name_id)
522 | hb_sink (nameids_to_retain)
523 ;
524
525 auto designAxes = get_design_axes ();
526
527 + get_axis_value_offsets ()
528 | hb_map (hb_add (&(this + offsetToAxisValueOffsets)))
529 | hb_filter ([&] (const AxisValue& _)
530 { return _.keep_axis_value (designAxes, user_axes_location); })
531 | hb_map (&AxisValue::get_value_name_id)
532 | hb_sink (nameids_to_retain)
533 ;
534
535 nameids_to_retain->add (elidedFallbackNameID);
536 }
537
538 bool subset (hb_subset_context_t *c) const
539 {
540 TRACE_SUBSET (this);
541 STAT *out = c->serializer->embed (this);
542 if (unlikely (!out)) return_trace (false);
543
544 auto designAxes = get_design_axes ();
545 for (unsigned i = 0; i < (unsigned)designAxisCount; i++)
546 if (unlikely (!c->serializer->embed (designAxes[i])))
547 return_trace (false);
548
549 if (designAxisCount)
550 c->serializer->check_assign (out->designAxesOffset, this->get_size (),
551 HB_SERIALIZE_ERROR_INT_OVERFLOW);
552
553 unsigned count = 0;
554 out->offsetToAxisValueOffsets.serialize_subset (c, offsetToAxisValueOffsets, this,
555 axisValueCount, count, designAxes);
556 return_trace (c->serializer->check_assign (out->axisValueCount, count, HB_SERIALIZE_ERROR_INT_OVERFLOW));
557 }
558
559 bool sanitize (hb_sanitize_context_t *c) const
560 {
561 TRACE_SANITIZE (this);
562 return_trace (likely (c->check_struct (this) &&
563 version.major == 1 &&
564 version.minor > 0 &&
565 designAxesOffset.sanitize (c, this, designAxisCount) &&
566 offsetToAxisValueOffsets.sanitize (c, this, axisValueCount, &(this+offsetToAxisValueOffsets))));
567 }
568
569 protected:
570 hb_array_t<const StatAxisRecord> const get_design_axes () const
571 { return (this+designAxesOffset).as_array (designAxisCount); }
572
573 hb_array_t<const Offset16To<AxisValue>> const get_axis_value_offsets () const
574 { return (this+offsetToAxisValueOffsets).as_array (axisValueCount); }
575
576
577 protected:
578 FixedVersion<>version; /* Version of the stat table
579 * initially set to 0x00010002u */
580 HBUINT16 designAxisSize; /* The size in bytes of each axis record. */
581 HBUINT16 designAxisCount;/* The number of design axis records. In a
582 * font with an 'fvar' table, this value must be
583 * greater than or equal to the axisCount value
584 * in the 'fvar' table. In all fonts, must
585 * be greater than zero if axisValueCount
586 * is greater than zero. */
587 NNOffset32To<UnsizedArrayOf<StatAxisRecord>>
588 designAxesOffset;
589 /* Offset in bytes from the beginning of
590 * the STAT table to the start of the design
591 * axes array. If designAxisCount is zero,
592 * set to zero; if designAxisCount is greater
593 * than zero, must be greater than zero. */
594 HBUINT16 axisValueCount; /* The number of axis value tables. */
595 NNOffset32To<AxisValueOffsetArray>
596 offsetToAxisValueOffsets;
597 /* Offset in bytes from the beginning of
598 * the STAT table to the start of the design
599 * axes value offsets array. If axisValueCount
600 * is zero, set to zero; if axisValueCount is
601 * greater than zero, must be greater than zero. */
602 NameID elidedFallbackNameID;
603 /* Name ID used as fallback when projection of
604 * names into a particular font model produces
605 * a subfamily name containing only elidable
606 * elements. */
607 public:
608 DEFINE_SIZE_STATIC (20);
609};
610
611
612} /* namespace OT */
613
614
615#endif /* HB_OT_STAT_TABLE_HH */
616