hb-subset.cc (13703B)
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.hh" 28 29 #include "hb-open-type.hh" 30 #include "hb-open-file.hh" 31 32 #include "hb-subset.hh" 33 #include "hb-subset-table.hh" 34 #include "hb-subset-accelerator.hh" 35 36 #include "hb-ot-cmap-table.hh" 37 #include "hb-ot-var-cvar-table.hh" 38 #include "hb-ot-head-table.hh" 39 #include "hb-ot-stat-table.hh" 40 #include "hb-ot-post-table-v2subset.hh" 41 42 43 /** 44 * SECTION:hb-subset 45 * @title: hb-subset 46 * @short_description: Subsets font files. 47 * @include: hb-subset.h 48 * 49 * Subsetting reduces the codepoint coverage of font files and removes all data 50 * that is no longer needed. A subset input describes the desired subset. The input is 51 * provided along with a font to the subsetting operation. Output is a new font file 52 * containing only the data specified in the input. 53 * 54 * Currently most outline and bitmap tables are supported: glyf, CFF, CFF2, sbix, 55 * COLR, and CBDT/CBLC. This also includes fonts with variable outlines via OpenType 56 * variations. Notably EBDT/EBLC and SVG are not supported. Layout subsetting is supported 57 * only for OpenType Layout tables (GSUB, GPOS, GDEF). Notably subsetting of graphite or AAT tables 58 * is not yet supported. 59 * 60 * Fonts with graphite or AAT tables may still be subsetted but will likely need to use the 61 * retain glyph ids option and configure the subset to pass through the layout tables untouched. 62 */ 63 64 65 hb_user_data_key_t _hb_subset_accelerator_user_data_key = {}; 66 67 68 /* 69 * The list of tables in the open type spec. Used to check for tables that may need handling 70 * if we are unable to list the tables in a face. 71 */ 72 static hb_tag_t known_tables[] { 73 HB_TAG('a','v','a','r'), 74 HB_TAG('B','A','S','E'), 75 HB_TAG('C','B','D','T'), 76 HB_TAG('C','B','L','C'), 77 HB_TAG('C','F','F',' '), 78 HB_TAG('C','F','F','2'), 79 HB_TAG('c','m','a','p'), 80 HB_TAG('C','O','L','R'), 81 HB_TAG('C','P','A','L'), 82 HB_TAG('c','v','a','r'), 83 HB_TAG('c','v','t',' '), 84 HB_TAG('D','S','I','G'), 85 HB_TAG('E','B','D','T'), 86 HB_TAG('E','B','L','C'), 87 HB_TAG('E','B','S','C'), 88 HB_TAG('f','p','g','m'), 89 HB_TAG('f','v','a','r'), 90 HB_TAG('g','a','s','p'), 91 HB_TAG('G','D','E','F'), 92 HB_TAG('g','l','y','f'), 93 HB_TAG('G','P','O','S'), 94 HB_TAG('G','S','U','B'), 95 HB_TAG('g','v','a','r'), 96 HB_TAG('h','d','m','x'), 97 HB_TAG('h','e','a','d'), 98 HB_TAG('h','h','e','a'), 99 HB_TAG('h','m','t','x'), 100 HB_TAG('H','V','A','R'), 101 HB_TAG('J','S','T','F'), 102 HB_TAG('k','e','r','n'), 103 HB_TAG('l','o','c','a'), 104 HB_TAG('L','T','S','H'), 105 HB_TAG('M','A','T','H'), 106 HB_TAG('m','a','x','p'), 107 HB_TAG('M','E','R','G'), 108 HB_TAG('m','e','t','a'), 109 HB_TAG('M','V','A','R'), 110 HB_TAG('P','C','L','T'), 111 HB_TAG('p','o','s','t'), 112 HB_TAG('p','r','e','p'), 113 HB_TAG('s','b','i','x'), 114 HB_TAG('S','T','A','T'), 115 HB_TAG('S','V','G',' '), 116 HB_TAG('V','D','M','X'), 117 HB_TAG('v','h','e','a'), 118 HB_TAG('v','m','t','x'), 119 HB_TAG('V','O','R','G'), 120 HB_TAG('V','V','A','R'), 121 HB_TAG('n','a','m','e'), 122 HB_TAG('O','S','/','2') 123 }; 124 125 static bool _table_is_empty (const hb_face_t *face, hb_tag_t tag) 126 { 127 hb_blob_t* blob = hb_face_reference_table (face, tag); 128 bool result = (blob == hb_blob_get_empty ()); 129 hb_blob_destroy (blob); 130 return result; 131 } 132 133 static unsigned int 134 _get_table_tags (const hb_subset_plan_t* plan, 135 unsigned int start_offset, 136 unsigned int *table_count, /* IN/OUT */ 137 hb_tag_t *table_tags /* OUT */) 138 { 139 unsigned num_tables = hb_face_get_table_tags (plan->source, 0, nullptr, nullptr); 140 if (num_tables) 141 return hb_face_get_table_tags (plan->source, start_offset, table_count, table_tags); 142 143 // If face has 0 tables associated with it, assume that it was built from 144 // hb_face_create_tables and thus is unable to list its tables. Fallback to 145 // checking each table type we can handle for existence instead. 146 auto it = 147 hb_concat ( 148 + hb_array (known_tables) 149 | hb_filter ([&] (hb_tag_t tag) { 150 return !_table_is_empty (plan->source, tag) && !plan->no_subset_tables.has (tag); 151 }) 152 | hb_map ([] (hb_tag_t tag) -> hb_tag_t { return tag; }), 153 154 plan->no_subset_tables.iter () 155 | hb_filter([&] (hb_tag_t tag) { 156 return !_table_is_empty (plan->source, tag); 157 })); 158 159 it += start_offset; 160 161 unsigned num_written = 0; 162 while (bool (it) && num_written < *table_count) 163 table_tags[num_written++] = *it++; 164 165 *table_count = num_written; 166 return num_written; 167 } 168 169 170 static bool 171 _is_table_present (hb_face_t *source, hb_tag_t tag) 172 { 173 174 if (!hb_face_get_table_tags (source, 0, nullptr, nullptr)) { 175 // If face has 0 tables associated with it, assume that it was built from 176 // hb_face_create_tables and thus is unable to list its tables. Fallback to 177 // checking if the blob associated with tag is empty. 178 return !_table_is_empty (source, tag); 179 } 180 181 hb_tag_t table_tags[32]; 182 unsigned offset = 0, num_tables = ARRAY_LENGTH (table_tags); 183 while (((void) hb_face_get_table_tags (source, offset, &num_tables, table_tags), num_tables)) 184 { 185 for (unsigned i = 0; i < num_tables; ++i) 186 if (table_tags[i] == tag) 187 return true; 188 offset += num_tables; 189 } 190 return false; 191 } 192 193 static bool 194 _should_drop_table (hb_subset_plan_t *plan, hb_tag_t tag) 195 { 196 if (plan->drop_tables.has (tag)) 197 return true; 198 199 switch (tag) 200 { 201 case HB_TAG('c','v','a','r'): /* hint table, fallthrough */ 202 return plan->all_axes_pinned || (plan->flags & HB_SUBSET_FLAGS_NO_HINTING); 203 204 case HB_TAG('c','v','t',' '): /* hint table, fallthrough */ 205 case HB_TAG('f','p','g','m'): /* hint table, fallthrough */ 206 case HB_TAG('p','r','e','p'): /* hint table, fallthrough */ 207 case HB_TAG('h','d','m','x'): /* hint table, fallthrough */ 208 case HB_TAG('V','D','M','X'): /* hint table, fallthrough */ 209 return plan->flags & HB_SUBSET_FLAGS_NO_HINTING; 210 211 #ifdef HB_NO_SUBSET_LAYOUT 212 // Drop Layout Tables if requested. 213 case HB_TAG('G','D','E','F'): 214 case HB_TAG('G','P','O','S'): 215 case HB_TAG('G','S','U','B'): 216 case HB_TAG('m','o','r','x'): 217 case HB_TAG('m','o','r','t'): 218 case HB_TAG('k','e','r','x'): 219 case HB_TAG('k','e','r','n'): 220 return true; 221 #endif 222 223 case HB_TAG('a','v','a','r'): 224 case HB_TAG('f','v','a','r'): 225 case HB_TAG('g','v','a','r'): 226 case HB_TAG('H','V','A','R'): 227 case HB_TAG('V','V','A','R'): 228 case HB_TAG('M','V','A','R'): 229 return plan->all_axes_pinned; 230 231 default: 232 return false; 233 } 234 } 235 236 static bool 237 _dependencies_satisfied (hb_subset_plan_t *plan, hb_tag_t tag, 238 const hb_set_t &subsetted_tags, 239 const hb_set_t &pending_subset_tags) 240 { 241 switch (tag) 242 { 243 case HB_TAG('h','m','t','x'): 244 case HB_TAG('v','m','t','x'): 245 case HB_TAG('m','a','x','p'): 246 case HB_TAG('O','S','/','2'): 247 return !plan->normalized_coords || !pending_subset_tags.has (HB_TAG('g','l','y','f')); 248 case HB_TAG('G','P','O','S'): 249 return plan->all_axes_pinned || !pending_subset_tags.has (HB_TAG('G','D','E','F')); 250 default: 251 return true; 252 } 253 } 254 255 static bool 256 _subset_table (hb_subset_plan_t *plan, 257 hb_vector_t<char> &buf, 258 hb_tag_t tag) 259 { 260 if (plan->no_subset_tables.has (tag)) { 261 return _hb_subset_table_passthrough (plan, tag); 262 } 263 264 DEBUG_MSG (SUBSET, nullptr, "subset %c%c%c%c", HB_UNTAG (tag)); 265 266 bool success; 267 if (_hb_subset_table_layout (plan, buf, tag, &success) || 268 _hb_subset_table_var (plan, buf, tag, &success) || 269 _hb_subset_table_cff (plan, buf, tag, &success) || 270 _hb_subset_table_color (plan, buf, tag, &success) || 271 _hb_subset_table_other (plan, buf, tag, &success)) 272 return success; 273 274 275 switch (tag) 276 { 277 case HB_TAG('h','e','a','d'): 278 if (_is_table_present (plan->source, HB_TAG('g','l','y','f')) && !_should_drop_table (plan, HB_TAG('g','l','y','f'))) 279 return true; /* skip head, handled by glyf */ 280 return _hb_subset_table<const OT::head> (plan, buf); 281 282 case HB_TAG('S','T','A','T'): 283 if (!plan->user_axes_location.is_empty ()) return _hb_subset_table<const OT::STAT> (plan, buf); 284 else return _hb_subset_table_passthrough (plan, tag); 285 286 case HB_TAG('c','v','t',' '): 287 #ifndef HB_NO_VAR 288 if (_is_table_present (plan->source, HB_TAG('c','v','a','r')) && 289 plan->normalized_coords && !plan->pinned_at_default) 290 { 291 auto &cvar = *plan->source->table.cvar; 292 return OT::cvar::add_cvt_and_apply_deltas (plan, cvar.get_tuple_var_data (), &cvar); 293 } 294 #endif 295 return _hb_subset_table_passthrough (plan, tag); 296 } 297 298 if (plan->flags & HB_SUBSET_FLAGS_PASSTHROUGH_UNRECOGNIZED) 299 return _hb_subset_table_passthrough (plan, tag); 300 301 // Drop table 302 return true; 303 } 304 305 static void _attach_accelerator_data (hb_subset_plan_t* plan, 306 hb_face_t* face /* IN/OUT */) 307 { 308 if (!plan->inprogress_accelerator) return; 309 310 // Transfer the accelerator from the plan to us. 311 hb_subset_accelerator_t* accel = plan->inprogress_accelerator; 312 plan->inprogress_accelerator = nullptr; 313 314 if (accel->in_error ()) 315 { 316 hb_subset_accelerator_t::destroy (accel); 317 return; 318 } 319 320 // Populate caches that need access to the final tables. 321 hb_blob_ptr_t<OT::cmap> cmap_ptr (hb_sanitize_context_t ().reference_table<OT::cmap> (face)); 322 accel->cmap_cache = OT::cmap::create_filled_cache (cmap_ptr); 323 accel->destroy_cmap_cache = OT::SubtableUnicodesCache::destroy; 324 325 if (!hb_face_set_user_data(face, 326 hb_subset_accelerator_t::user_data_key(), 327 accel, 328 hb_subset_accelerator_t::destroy, 329 true)) 330 hb_subset_accelerator_t::destroy (accel); 331 } 332 333 /** 334 * hb_subset_or_fail: 335 * @source: font face data to be subset. 336 * @input: input to use for the subsetting. 337 * 338 * Subsets a font according to provided input. Returns nullptr 339 * if the subset operation fails or the face has no glyphs. 340 * 341 * Since: 2.9.0 342 **/ 343 hb_face_t * 344 hb_subset_or_fail (hb_face_t *source, const hb_subset_input_t *input) 345 { 346 if (unlikely (!input || !source)) return nullptr; 347 348 if (unlikely (!source->get_num_glyphs ())) 349 { 350 DEBUG_MSG (SUBSET, nullptr, "No glyphs in source font."); 351 return nullptr; 352 } 353 354 hb_subset_plan_t *plan = hb_subset_plan_create_or_fail (source, input); 355 if (unlikely (!plan)) { 356 return nullptr; 357 } 358 359 hb_face_t * result = hb_subset_plan_execute_or_fail (plan); 360 hb_subset_plan_destroy (plan); 361 return result; 362 } 363 364 365 /** 366 * hb_subset_plan_execute_or_fail: 367 * @plan: a subsetting plan. 368 * 369 * Executes the provided subsetting @plan. 370 * 371 * Return value: 372 * on success returns a reference to generated font subset. If the subsetting operation fails 373 * returns nullptr. 374 * 375 * Since: 4.0.0 376 **/ 377 hb_face_t * 378 hb_subset_plan_execute_or_fail (hb_subset_plan_t *plan) 379 { 380 if (unlikely (!plan || plan->in_error ())) { 381 return nullptr; 382 } 383 384 hb_tag_t table_tags[32]; 385 unsigned offset = 0, num_tables = ARRAY_LENGTH (table_tags); 386 387 hb_set_t subsetted_tags, pending_subset_tags; 388 while (((void) _get_table_tags (plan, offset, &num_tables, table_tags), num_tables)) 389 { 390 for (unsigned i = 0; i < num_tables; ++i) 391 { 392 hb_tag_t tag = table_tags[i]; 393 if (_should_drop_table (plan, tag)) continue; 394 pending_subset_tags.add (tag); 395 } 396 397 offset += num_tables; 398 } 399 400 bool success = true; 401 402 { 403 // Grouping to deallocate buf before calling hb_face_reference (plan->dest). 404 405 hb_vector_t<char> buf; 406 buf.alloc (8192 - 16); 407 408 while (!pending_subset_tags.is_empty ()) 409 { 410 if (subsetted_tags.in_error () 411 || pending_subset_tags.in_error ()) { 412 success = false; 413 goto end; 414 } 415 416 bool made_changes = false; 417 for (hb_tag_t tag : pending_subset_tags) 418 { 419 if (!_dependencies_satisfied (plan, tag, 420 subsetted_tags, 421 pending_subset_tags)) 422 { 423 // delayed subsetting for some tables since they might have dependency on other tables 424 // in some cases: e.g: during instantiating glyf tables, hmetrics/vmetrics are updated 425 // and saved in subset plan, hmtx/vmtx subsetting need to use these updated metrics values 426 continue; 427 } 428 429 pending_subset_tags.del (tag); 430 subsetted_tags.add (tag); 431 made_changes = true; 432 433 success = _subset_table (plan, buf, tag); 434 if (unlikely (!success)) goto end; 435 } 436 437 if (!made_changes) 438 { 439 DEBUG_MSG (SUBSET, nullptr, "Table dependencies unable to be satisfied. Subset failed."); 440 success = false; 441 goto end; 442 } 443 } 444 } 445 446 if (success && plan->attach_accelerator_data) { 447 _attach_accelerator_data (plan, plan->dest); 448 } 449 450 end: 451 return success ? hb_face_reference (plan->dest) : nullptr; 452 }