9ebe5d31da2b072bc610dee6c92d90c12dff11cc
[platform/upstream/harfbuzz.git] / src / hb-subset.cc
1 /*
2  * Copyright © 2018  Google, Inc.
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  * Google Author(s): Garret Rieger, Rod Sheeter, Behdad Esfahbod
25  */
26
27 #include "hb-private.hh"
28 #include "hb-object-private.hh"
29 #include "hb-open-type-private.hh"
30
31 #include "hb-subset-glyf.hh"
32 #include "hb-subset-private.hh"
33 #include "hb-subset-plan.hh"
34
35 #include "hb-open-file-private.hh"
36 #include "hb-ot-cmap-table.hh"
37 #include "hb-ot-glyf-table.hh"
38 #include "hb-ot-hdmx-table.hh"
39 #include "hb-ot-head-table.hh"
40 #include "hb-ot-hhea-table.hh"
41 #include "hb-ot-hmtx-table.hh"
42 #include "hb-ot-maxp-table.hh"
43 #include "hb-ot-os2-table.hh"
44
45
46 #ifndef HB_NO_VISIBILITY
47 const void * const OT::_hb_NullPool[HB_NULL_POOL_SIZE / sizeof (void *)] = {};
48 #endif
49
50
51 struct hb_subset_profile_t {
52   hb_object_header_t header;
53   ASSERT_POD ();
54 };
55
56 /**
57  * hb_subset_profile_create:
58  *
59  * Return value: New profile with default settings.
60  *
61  * Since: 1.8.0
62  **/
63 hb_subset_profile_t *
64 hb_subset_profile_create ()
65 {
66   return hb_object_create<hb_subset_profile_t>();
67 }
68
69 /**
70  * hb_subset_profile_destroy:
71  *
72  * Since: 1.8.0
73  **/
74 void
75 hb_subset_profile_destroy (hb_subset_profile_t *profile)
76 {
77   if (!hb_object_destroy (profile)) return;
78
79   free (profile);
80 }
81
82 template<typename TableType>
83 static bool
84 _subset (hb_subset_plan_t *plan)
85 {
86   OT::Sanitizer<TableType> sanitizer;
87
88   hb_blob_t *source_blob = sanitizer.sanitize (plan->source->reference_table (TableType::tableTag));
89   const TableType *table = OT::Sanitizer<TableType>::lock_instance (source_blob);
90
91   hb_bool_t result = false;
92   if (table != &OT::Null(TableType))
93     result = table->subset(plan);
94
95   hb_blob_destroy (source_blob);
96   hb_tag_t tag = TableType::tableTag;
97   DEBUG_MSG(SUBSET, nullptr, "OT::%c%c%c%c::subset %s", HB_UNTAG(tag), result ? "success" : "FAILED!");
98   return result;
99 }
100
101
102 /*
103  * A face that has add_table().
104  */
105
106 struct hb_subset_face_data_t
107 {
108   struct table_entry_t
109   {
110     inline int cmp (const hb_tag_t *t) const
111     {
112       if (*t < tag) return -1;
113       if (*t > tag) return -1;
114       return 0;
115     }
116
117     hb_tag_t   tag;
118     hb_blob_t *blob;
119   };
120
121   hb_prealloced_array_t<table_entry_t, 32> tables;
122 };
123
124 static hb_subset_face_data_t *
125 _hb_subset_face_data_create (void)
126 {
127   hb_subset_face_data_t *data = (hb_subset_face_data_t *) calloc (1, sizeof (hb_subset_face_data_t));
128   if (unlikely (!data))
129     return nullptr;
130
131   return data;
132 }
133
134 static void
135 _hb_subset_face_data_destroy (void *user_data)
136 {
137   hb_subset_face_data_t *data = (hb_subset_face_data_t *) user_data;
138
139   for (unsigned int i = 0; i < data->tables.len; i++)
140     hb_blob_destroy (data->tables[i].blob);
141
142   data->tables.finish ();
143
144   free (data);
145 }
146
147 static hb_blob_t *
148 _hb_subset_face_data_reference_blob (hb_subset_face_data_t *data)
149 {
150
151   unsigned int table_count = data->tables.len;
152   unsigned int face_length = table_count * 16 + 12;
153
154   for (unsigned int i = 0; i < table_count; i++)
155     face_length += _hb_ceil_to_4 (hb_blob_get_length (data->tables.array[i].blob));
156
157   char *buf = (char *) malloc (face_length);
158   if (unlikely (!buf))
159     return nullptr;
160
161   OT::hb_serialize_context_t c (buf, face_length);
162   OT::OpenTypeFontFile *f = c.start_serialize<OT::OpenTypeFontFile> ();
163
164   bool is_cff = data->tables.lsearch (HB_TAG ('C','F','F',' ')) || data->tables.lsearch (HB_TAG ('C','F','F','2'));
165   hb_tag_t sfnt_tag = is_cff ? OT::OpenTypeFontFile::CFFTag : OT::OpenTypeFontFile::TrueTypeTag;
166
167   OT::Supplier<hb_tag_t>    tags_supplier  (&data->tables[0].tag, table_count, sizeof (data->tables[0]));
168   OT::Supplier<hb_blob_t *> blobs_supplier (&data->tables[0].blob, table_count, sizeof (data->tables[0]));
169   bool ret = f->serialize_single (&c,
170                                   sfnt_tag,
171                                   tags_supplier,
172                                   blobs_supplier,
173                                   table_count);
174
175   c.end_serialize ();
176
177   if (unlikely (!ret))
178   {
179     free (buf);
180     return nullptr;
181   }
182
183   return hb_blob_create (buf, face_length, HB_MEMORY_MODE_WRITABLE, buf, free);
184 }
185
186 static hb_blob_t *
187 _hb_subset_face_reference_table (hb_face_t *face, hb_tag_t tag, void *user_data)
188 {
189   hb_subset_face_data_t *data = (hb_subset_face_data_t *) user_data;
190
191   if (!tag)
192     return _hb_subset_face_data_reference_blob (data);
193
194   hb_subset_face_data_t::table_entry_t *entry = data->tables.lsearch (tag);
195   if (entry)
196     return hb_blob_reference (entry->blob);
197
198   return nullptr;
199 }
200
201 /* TODO: Move this to hb-face.h and rename to hb_face_builder_create()
202  * with hb_face_builder_add_table(). */
203 hb_face_t *
204 hb_subset_face_create (void)
205 {
206   hb_subset_face_data_t *data = _hb_subset_face_data_create ();
207   if (unlikely (!data)) return hb_face_get_empty ();
208
209   return hb_face_create_for_tables (_hb_subset_face_reference_table,
210                                     data,
211                                     _hb_subset_face_data_destroy);
212 }
213
214 hb_bool_t
215 hb_subset_face_add_table (hb_face_t *face, hb_tag_t tag, hb_blob_t *blob)
216 {
217   if (unlikely (face->destroy != _hb_subset_face_data_destroy))
218     return false;
219
220   hb_subset_face_data_t *data = (hb_subset_face_data_t *) face->user_data;
221   hb_subset_face_data_t::table_entry_t *entry = data->tables.push ();
222   if (unlikely (!entry))
223     return false;
224
225   entry->tag = tag;
226   entry->blob = hb_blob_reference (blob);
227
228   return true;
229 }
230
231 static bool
232 _subset_table (hb_subset_plan_t *plan,
233                hb_tag_t          tag)
234 {
235   DEBUG_MSG(SUBSET, nullptr, "begin subset %c%c%c%c", HB_UNTAG(tag));
236   bool result = true;
237   switch (tag) {
238     case HB_OT_TAG_glyf:
239       result = _subset<const OT::glyf> (plan);
240       break;
241     case HB_OT_TAG_hdmx:
242       result = _subset<const OT::hdmx> (plan);
243       break;
244     case HB_OT_TAG_head:
245       // TODO that won't work well if there is no glyf
246       DEBUG_MSG(SUBSET, nullptr, "skip head, handled by glyf");
247       result = true;
248       break;
249     case HB_OT_TAG_hhea:
250       DEBUG_MSG(SUBSET, nullptr, "skip hhea handled by hmtx");
251       return true;
252     case HB_OT_TAG_hmtx:
253       result = _subset<const OT::hmtx> (plan);
254       break;
255     case HB_OT_TAG_maxp:
256       result = _subset<const OT::maxp> (plan);
257       break;
258     case HB_OT_TAG_loca:
259       DEBUG_MSG(SUBSET, nullptr, "skip loca handled by glyf");
260       return true;
261     case HB_OT_TAG_cmap:
262       result = _subset<const OT::cmap> (plan);
263       break;
264     case HB_OT_TAG_os2:
265       result = _subset<const OT::os2> (plan);
266       break;
267     default:
268       hb_blob_t *source_table = hb_face_reference_table(plan->source, tag);
269       if (likely (source_table))
270         result = hb_subset_plan_add_table(plan, tag, source_table);
271       else
272         result = false;
273       hb_blob_destroy (source_table);
274       break;
275   }
276   DEBUG_MSG(SUBSET, nullptr, "subset %c%c%c%c %s", HB_UNTAG(tag), result ? "ok" : "FAILED");
277   return result;
278 }
279
280 static bool
281 _should_drop_table(hb_subset_plan_t *plan, hb_tag_t tag)
282 {
283     switch (tag) {
284       case HB_TAG ('c', 'v', 'a', 'r'): /* hint table, fallthrough */
285       case HB_TAG ('c', 'v', 't', ' '): /* hint table, fallthrough */
286       case HB_TAG ('f', 'p', 'g', 'm'): /* hint table, fallthrough */
287       case HB_TAG ('p', 'r', 'e', 'p'): /* hint table, fallthrough */
288       case HB_TAG ('h', 'd', 'm', 'x'): /* hint table, fallthrough */
289       case HB_TAG ('V', 'D', 'M', 'X'): /* hint table, fallthrough */
290         return plan->drop_hints;
291       case HB_TAG ('G', 'D', 'E', 'F'): /* temporary */
292       case HB_TAG ('G', 'P', 'O', 'S'): /* temporary */
293       case HB_TAG ('G', 'S', 'U', 'B'): /* temporary */
294       case HB_TAG ('D', 'S', 'I', 'G'):
295         return true;
296       default:
297         return false;
298   }
299 }
300
301 /**
302  * hb_subset:
303  * @source: font face data to be subset.
304  * @profile: profile to use for the subsetting.
305  * @input: input to use for the subsetting.
306  *
307  * Subsets a font according to provided profile and input.
308  **/
309 hb_face_t *
310 hb_subset (hb_face_t *source,
311            hb_subset_profile_t *profile,
312            hb_subset_input_t *input)
313 {
314   if (unlikely (!profile || !input || !source)) return hb_face_get_empty();
315
316   hb_subset_plan_t *plan = hb_subset_plan_create (source, profile, input);
317
318   hb_tag_t table_tags[32];
319   unsigned int offset = 0, count;
320   bool success = true;
321   do {
322     count = ARRAY_LENGTH (table_tags);
323     hb_face_get_table_tags (source, offset, &count, table_tags);
324     for (unsigned int i = 0; i < count; i++)
325     {
326       hb_tag_t tag = table_tags[i];
327       if (_should_drop_table(plan, tag))
328       {
329         DEBUG_MSG(SUBSET, nullptr, "drop %c%c%c%c", HB_UNTAG(tag));
330         continue;
331       }
332       success = success && _subset_table (plan, tag);
333     }
334   } while (count == ARRAY_LENGTH (table_tags));
335
336   hb_face_t *result = success ? hb_face_reference(plan->dest) : hb_face_get_empty();
337   hb_subset_plan_destroy (plan);
338   return result;
339 }