1 /*
2 * Copyright © 2010 Behdad Esfahbod
3 * Copyright © 2011,2012 Google, Inc.
4 *
5 * This is part of HarfBuzz, a text shaping library.
6 *
7 * Permission is hereby granted, without written agreement and without
8 * license or royalty fees, to use, copy, modify, and distribute this
9 * software and its documentation for any purpose, provided that the
10 * above copyright notice and the following two paragraphs appear in
11 * all copies of this software.
12 *
13 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
14 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
15 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
16 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
17 * DAMAGE.
18 *
19 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
20 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
21 * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
22 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
23 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
24 *
25 * Google Author(s): Garret Rieger, Rod Sheeter
26 */
27
28 #include "batch.hh"
29 #include "face-options.hh"
30 #include "main-font-text.hh"
31 #include "output-options.hh"
32
33 #include <hb-subset.h>
34
preprocess_face(hb_face_t * face)35 static hb_face_t* preprocess_face(hb_face_t* face)
36 {
37 return hb_subset_preprocess (face);
38 }
39
40 /*
41 * Command line interface to the harfbuzz font subsetter.
42 */
43
44 struct subset_main_t : option_parser_t, face_options_t, output_options_t<false>
45 {
subset_main_tsubset_main_t46 subset_main_t ()
47 : input (hb_subset_input_create_or_fail ())
48 {}
~subset_main_tsubset_main_t49 ~subset_main_t ()
50 {
51 hb_subset_input_destroy (input);
52 }
53
parse_facesubset_main_t54 void parse_face (int argc, const char * const *argv)
55 {
56 option_parser_t parser;
57 face_options_t face_opts;
58
59 face_opts.add_options (&parser);
60
61 GOptionEntry entries[] =
62 {
63 {G_OPTION_REMAINING, 0, G_OPTION_FLAG_IN_MAIN,
64 G_OPTION_ARG_CALLBACK, (gpointer) &collect_face, nullptr, "[FONT-FILE] [TEXT]"},
65 {nullptr}
66 };
67 parser.add_main_group (entries, &face_opts);
68 parser.add_options ();
69
70 g_option_context_set_ignore_unknown_options (parser.context, true);
71 g_option_context_set_help_enabled (parser.context, false);
72
73 char **args = (char **)
74 #if GLIB_CHECK_VERSION (2, 68, 0)
75 g_memdup2
76 #else
77 g_memdup
78 #endif
79 (argv, argc * sizeof (*argv));
80 parser.parse (&argc, &args);
81 g_free (args);
82
83 set_face (face_opts.face);
84 }
85
parsesubset_main_t86 void parse (int argc, char **argv)
87 {
88 bool help = false;
89 for (auto i = 1; i < argc; i++)
90 if (!strncmp ("--help", argv[i], 6))
91 {
92 help = true;
93 break;
94 }
95
96 if (likely (!help))
97 {
98 /* Do a preliminary parse to load font-face, such that we can use it
99 * during main option parsing. */
100 parse_face (argc, argv);
101 }
102
103 add_options ();
104 option_parser_t::parse (&argc, &argv);
105 }
106
operator ()subset_main_t107 int operator () (int argc, char **argv)
108 {
109 parse (argc, argv);
110
111 hb_face_t* orig_face = face;
112 if (preprocess)
113 orig_face = preprocess_face (face);
114
115 hb_face_t *new_face = nullptr;
116 for (unsigned i = 0; i < num_iterations; i++)
117 {
118 hb_face_destroy (new_face);
119 new_face = hb_subset_or_fail (orig_face, input);
120 }
121
122 bool success = new_face;
123 if (success)
124 {
125 hb_blob_t *result = hb_face_reference_blob (new_face);
126 write_file (output_file, result);
127 hb_blob_destroy (result);
128 }
129
130 hb_face_destroy (new_face);
131 if (preprocess)
132 hb_face_destroy (orig_face);
133
134 return success ? 0 : 1;
135 }
136
137 bool
write_filesubset_main_t138 write_file (const char *output_file, hb_blob_t *blob)
139 {
140 assert (out_fp);
141
142 unsigned int size;
143 const char* data = hb_blob_get_data (blob, &size);
144
145 while (size)
146 {
147 size_t ret = fwrite (data, 1, size, out_fp);
148 size -= ret;
149 data += ret;
150 if (size && ferror (out_fp))
151 fail (false, "Failed to write output: %s", strerror (errno));
152 }
153
154 return true;
155 }
156
157 void add_options ();
158
159 protected:
160 static gboolean
161 collect_face (const char *name,
162 const char *arg,
163 gpointer data,
164 GError **error);
165 static gboolean
166 collect_rest (const char *name,
167 const char *arg,
168 gpointer data,
169 GError **error);
170
171 public:
172
173 unsigned num_iterations = 1;
174 gboolean preprocess = false;
175 hb_subset_input_t *input = nullptr;
176 };
177
178 static gboolean
parse_gids(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error)179 parse_gids (const char *name G_GNUC_UNUSED,
180 const char *arg,
181 gpointer data,
182 GError **error)
183 {
184 subset_main_t *subset_main = (subset_main_t *) data;
185 hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
186 hb_bool_t is_add = (name[strlen (name) - 1] == '+');
187 hb_set_t *gids = hb_subset_input_glyph_set (subset_main->input);
188
189 if (!is_remove && !is_add) hb_set_clear (gids);
190
191 if (0 == strcmp (arg, "*"))
192 {
193 hb_set_clear (gids);
194 if (!is_remove)
195 hb_set_invert (gids);
196 return true;
197 }
198
199 char *s = (char *) arg;
200 char *p;
201
202 while (s && *s)
203 {
204 while (*s && strchr (", ", *s))
205 s++;
206 if (!*s)
207 break;
208
209 errno = 0;
210 hb_codepoint_t start_code = strtoul (s, &p, 10);
211 if (s[0] == '-' || errno || s == p)
212 {
213 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
214 "Failed parsing glyph-index at: '%s'", s);
215 return false;
216 }
217
218 if (p && p[0] == '-') // ranges
219 {
220 s = ++p;
221 hb_codepoint_t end_code = strtoul (s, &p, 10);
222 if (s[0] == '-' || errno || s == p)
223 {
224 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
225 "Failed parsing glyph-index at: '%s'", s);
226 return false;
227 }
228
229 if (end_code < start_code)
230 {
231 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
232 "Invalid glyph-index range %u-%u", start_code, end_code);
233 return false;
234 }
235 if (!is_remove)
236 hb_set_add_range (gids, start_code, end_code);
237 else
238 hb_set_del_range (gids, start_code, end_code);
239 }
240 else
241 {
242 if (!is_remove)
243 hb_set_add (gids, start_code);
244 else
245 hb_set_del (gids, start_code);
246 }
247
248 s = p;
249 }
250
251 return true;
252 }
253
254 static gboolean
parse_glyphs(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)255 parse_glyphs (const char *name G_GNUC_UNUSED,
256 const char *arg,
257 gpointer data,
258 GError **error G_GNUC_UNUSED)
259 {
260 subset_main_t *subset_main = (subset_main_t *) data;
261 hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
262 hb_bool_t is_add = (name[strlen (name) - 1] == '+');
263 hb_set_t *gids = hb_subset_input_glyph_set (subset_main->input);
264
265 if (!is_remove && !is_add) hb_set_clear (gids);
266
267 if (0 == strcmp (arg, "*"))
268 {
269 hb_set_clear (gids);
270 if (!is_remove)
271 hb_set_invert (gids);
272 return true;
273 }
274
275 const char *p = arg;
276 const char *p_end = arg + strlen (arg);
277
278 hb_font_t *font = hb_font_create (subset_main->face);
279 while (p < p_end)
280 {
281 while (p < p_end && (*p == ' ' || *p == ','))
282 p++;
283
284 const char *end = p;
285 while (end < p_end && *end != ' ' && *end != ',')
286 end++;
287
288 if (p < end)
289 {
290 hb_codepoint_t gid;
291 if (!hb_font_get_glyph_from_name (font, p, end - p, &gid))
292 {
293 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
294 "Failed parsing glyph name: '%s'", p);
295 return false;
296 }
297
298 if (!is_remove)
299 hb_set_add (gids, gid);
300 else
301 hb_set_del (gids, gid);
302 }
303
304 p = end + 1;
305 }
306 hb_font_destroy (font);
307
308 return true;
309 }
310
311 static gboolean
parse_text(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)312 parse_text (const char *name G_GNUC_UNUSED,
313 const char *arg,
314 gpointer data,
315 GError **error G_GNUC_UNUSED)
316 {
317 subset_main_t *subset_main = (subset_main_t *) data;
318 hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
319 hb_bool_t is_add = (name[strlen (name) - 1] == '+');
320 hb_set_t *unicodes = hb_subset_input_unicode_set (subset_main->input);
321
322 if (!is_remove && !is_add) hb_set_clear (unicodes);
323
324 if (0 == strcmp (arg, "*"))
325 {
326 hb_set_clear (unicodes);
327 if (!is_remove)
328 hb_set_invert (unicodes);
329 return true;
330 }
331
332 for (gchar *c = (gchar *) arg;
333 *c;
334 c = g_utf8_find_next_char(c, nullptr))
335 {
336 gunichar cp = g_utf8_get_char(c);
337 if (!is_remove)
338 hb_set_add (unicodes, cp);
339 else
340 hb_set_del (unicodes, cp);
341 }
342 return true;
343 }
344
345 static gboolean
parse_unicodes(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error)346 parse_unicodes (const char *name G_GNUC_UNUSED,
347 const char *arg,
348 gpointer data,
349 GError **error)
350 {
351 subset_main_t *subset_main = (subset_main_t *) data;
352 hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
353 hb_bool_t is_add = (name[strlen (name) - 1] == '+');
354 hb_set_t *unicodes = hb_subset_input_unicode_set (subset_main->input);
355
356 if (!is_remove && !is_add) hb_set_clear (unicodes);
357
358 if (0 == strcmp (arg, "*"))
359 {
360 hb_set_clear (unicodes);
361 if (!is_remove)
362 hb_set_invert (unicodes);
363 return true;
364 }
365
366 // XXX TODO Ranges
367 #define DELIMITERS "<+->{},;&#\\xXuUnNiI\n\t\v\f\r "
368
369 char *s = (char *) arg;
370 char *p;
371
372 while (s && *s)
373 {
374 while (*s && strchr (DELIMITERS, *s))
375 s++;
376 if (!*s)
377 break;
378
379 errno = 0;
380 hb_codepoint_t start_code = strtoul (s, &p, 16);
381 if (errno || s == p)
382 {
383 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
384 "Failed parsing Unicode at: '%s'", s);
385 return false;
386 }
387
388 if (p && p[0] == '-') // ranges
389 {
390 s = ++p;
391 hb_codepoint_t end_code = strtoul (s, &p, 16);
392 if (s[0] == '-' || errno || s == p)
393 {
394 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
395 "Failed parsing Unicode at: '%s'", s);
396 return false;
397 }
398
399 if (end_code < start_code)
400 {
401 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
402 "Invalid Unicode range %u-%u", start_code, end_code);
403 return false;
404 }
405 if (!is_remove)
406 hb_set_add_range (unicodes, start_code, end_code);
407 else
408 hb_set_del_range (unicodes, start_code, end_code);
409 }
410 else
411 {
412 if (!is_remove)
413 hb_set_add (unicodes, start_code);
414 else
415 hb_set_del (unicodes, start_code);
416 }
417
418 s = p;
419 }
420
421 return true;
422 }
423
424 static gboolean
parse_nameids(const char * name,const char * arg,gpointer data,GError ** error)425 parse_nameids (const char *name,
426 const char *arg,
427 gpointer data,
428 GError **error)
429 {
430 subset_main_t *subset_main = (subset_main_t *) data;
431 hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
432 hb_bool_t is_add = (name[strlen (name) - 1] == '+');
433 hb_set_t *name_ids = hb_subset_input_set (subset_main->input, HB_SUBSET_SETS_NAME_ID);
434
435
436 if (!is_remove && !is_add) hb_set_clear (name_ids);
437
438 if (0 == strcmp (arg, "*"))
439 {
440 hb_set_clear (name_ids);
441 if (!is_remove)
442 hb_set_invert (name_ids);
443 return true;
444 }
445
446 char *s = (char *) arg;
447 char *p;
448
449 while (s && *s)
450 {
451 while (*s && strchr (", ", *s))
452 s++;
453 if (!*s)
454 break;
455
456 errno = 0;
457 hb_codepoint_t u = strtoul (s, &p, 10);
458 if (errno || s == p)
459 {
460 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
461 "Failed parsing nameID at: '%s'", s);
462 return false;
463 }
464
465 if (!is_remove)
466 {
467 hb_set_add (name_ids, u);
468 } else {
469 hb_set_del (name_ids, u);
470 }
471
472 s = p;
473 }
474
475 return true;
476 }
477
478 static gboolean
parse_name_languages(const char * name,const char * arg,gpointer data,GError ** error)479 parse_name_languages (const char *name,
480 const char *arg,
481 gpointer data,
482 GError **error)
483 {
484 subset_main_t *subset_main = (subset_main_t *) data;
485 hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
486 hb_bool_t is_add = (name[strlen (name) - 1] == '+');
487 hb_set_t *name_languages = hb_subset_input_set (subset_main->input, HB_SUBSET_SETS_NAME_LANG_ID);
488
489 if (!is_remove && !is_add) hb_set_clear (name_languages);
490
491 if (0 == strcmp (arg, "*"))
492 {
493 hb_set_clear (name_languages);
494 if (!is_remove)
495 hb_set_invert (name_languages);
496 return true;
497 }
498
499 char *s = (char *) arg;
500 char *p;
501
502 while (s && *s)
503 {
504 while (*s && strchr (", ", *s))
505 s++;
506 if (!*s)
507 break;
508
509 errno = 0;
510 hb_codepoint_t u = strtoul (s, &p, 10);
511 if (errno || s == p)
512 {
513 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
514 "Failed parsing name-language code at: '%s'", s);
515 return false;
516 }
517
518 if (!is_remove)
519 {
520 hb_set_add (name_languages, u);
521 } else {
522 hb_set_del (name_languages, u);
523 }
524
525 s = p;
526 }
527
528 return true;
529 }
530
531 static gboolean
_keep_everything(const char * name,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)532 _keep_everything (const char *name,
533 const char *arg,
534 gpointer data,
535 GError **error G_GNUC_UNUSED)
536 {
537 subset_main_t *subset_main = (subset_main_t *) data;
538
539 hb_subset_input_keep_everything (subset_main->input);
540
541 return true;
542 }
543
544 template <hb_subset_flags_t flag>
545 static gboolean
set_flag(const char * name,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)546 set_flag (const char *name,
547 const char *arg,
548 gpointer data,
549 GError **error G_GNUC_UNUSED)
550 {
551 subset_main_t *subset_main = (subset_main_t *) data;
552
553 hb_subset_input_set_flags (subset_main->input,
554 hb_subset_input_get_flags (subset_main->input) | flag);
555
556 return true;
557 }
558
559 static gboolean
parse_layout_tag_list(hb_subset_sets_t set_type,const char * name,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)560 parse_layout_tag_list (hb_subset_sets_t set_type,
561 const char *name,
562 const char *arg,
563 gpointer data,
564 GError **error G_GNUC_UNUSED)
565 {
566 subset_main_t *subset_main = (subset_main_t *) data;
567 hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
568 hb_bool_t is_add = (name[strlen (name) - 1] == '+');
569 hb_set_t *layout_tags = hb_subset_input_set (subset_main->input, set_type);
570
571 if (!is_remove && !is_add) hb_set_clear (layout_tags);
572
573 if (0 == strcmp (arg, "*"))
574 {
575 hb_set_clear (layout_tags);
576 if (!is_remove)
577 hb_set_invert (layout_tags);
578 return true;
579 }
580
581 char *s = strtok((char *) arg, ", ");
582 while (s)
583 {
584 if (strlen (s) > 4) // tags are at most 4 bytes
585 {
586 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
587 "Failed parsing table tag at: '%s'", s);
588 return false;
589 }
590
591 hb_tag_t tag = hb_tag_from_string (s, strlen (s));
592
593 if (!is_remove)
594 hb_set_add (layout_tags, tag);
595 else
596 hb_set_del (layout_tags, tag);
597
598 s = strtok(nullptr, ", ");
599 }
600
601 return true;
602 }
603
604 static gboolean
parse_layout_features(const char * name,const char * arg,gpointer data,GError ** error)605 parse_layout_features (const char *name,
606 const char *arg,
607 gpointer data,
608 GError **error)
609
610 {
611 return parse_layout_tag_list (HB_SUBSET_SETS_LAYOUT_FEATURE_TAG,
612 name,
613 arg,
614 data,
615 error);
616 }
617
618 static gboolean
parse_layout_scripts(const char * name,const char * arg,gpointer data,GError ** error)619 parse_layout_scripts (const char *name,
620 const char *arg,
621 gpointer data,
622 GError **error)
623
624 {
625 return parse_layout_tag_list (HB_SUBSET_SETS_LAYOUT_SCRIPT_TAG,
626 name,
627 arg,
628 data,
629 error);
630 }
631
632 static gboolean
parse_drop_tables(const char * name,const char * arg,gpointer data,GError ** error)633 parse_drop_tables (const char *name,
634 const char *arg,
635 gpointer data,
636 GError **error)
637 {
638 subset_main_t *subset_main = (subset_main_t *) data;
639 hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
640 hb_bool_t is_add = (name[strlen (name) - 1] == '+');
641 hb_set_t *drop_tables = hb_subset_input_set (subset_main->input, HB_SUBSET_SETS_DROP_TABLE_TAG);
642
643 if (!is_remove && !is_add) hb_set_clear (drop_tables);
644
645 if (0 == strcmp (arg, "*"))
646 {
647 hb_set_clear (drop_tables);
648 if (!is_remove)
649 hb_set_invert (drop_tables);
650 return true;
651 }
652
653 char *s = strtok((char *) arg, ", ");
654 while (s)
655 {
656 if (strlen (s) > 4) // Table tags are at most 4 bytes.
657 {
658 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
659 "Failed parsing table tag at: '%s'", s);
660 return false;
661 }
662
663 hb_tag_t tag = hb_tag_from_string (s, strlen (s));
664
665 if (!is_remove)
666 hb_set_add (drop_tables, tag);
667 else
668 hb_set_del (drop_tables, tag);
669
670 s = strtok(nullptr, ", ");
671 }
672
673 return true;
674 }
675
676 #ifndef HB_NO_VAR
677 static gboolean
parse_instance(const char * name,const char * arg,gpointer data,GError ** error)678 parse_instance (const char *name,
679 const char *arg,
680 gpointer data,
681 GError **error)
682 {
683 subset_main_t *subset_main = (subset_main_t *) data;
684 if (!subset_main->face) {
685 // There is no face, which is needed to set up instancing. Skip parsing these options.
686 return true;
687 }
688
689 char *s = strtok((char *) arg, "=");
690 while (s)
691 {
692 unsigned len = strlen (s);
693 if (len > 4) //Axis tags are 4 bytes.
694 {
695 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
696 "Failed parsing axis tag at: '%s'", s);
697 return false;
698 }
699
700 hb_tag_t axis_tag = hb_tag_from_string (s, len);
701
702 s = strtok(nullptr, ", ");
703 if (!s)
704 {
705 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
706 "Value not specified for axis: %c%c%c%c", HB_UNTAG (axis_tag));
707 return false;
708 }
709
710 #ifdef HB_EXPERIMENTAL_API
711 char *pp = s;
712 pp = strpbrk (pp, ":");
713 if (pp) // partial instancing
714 {
715 errno = 0;
716 char *pend;
717 float min_val = strtof (s, &pend);
718 if (errno || s == pend || pend != pp)
719 {
720 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
721 "Failed parsing axis value at: '%s'", s);
722 return false;
723 }
724 pp++;
725 float max_val = strtof (pp, &pend);
726 /* we need to specify 2 values or 3 values for partial instancing:
727 * at least new min and max values, new default is optional */
728 if (errno || pp == pend || (*pend != ':' && *pend != '\0'))
729 {
730 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
731 "Failed parsing axis value at: '%s'", s);
732 return false;
733 }
734 /* 3 values are specified */
735 float *def_val_p = nullptr;
736 float def_val;
737 if (*pend == ':')
738 {
739 def_val = max_val;
740 def_val_p = &def_val;
741 pp = pend + 1;
742 max_val = strtof (pp, &pend);
743 if (errno || pp == pend || *pend != '\0')
744 {
745 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
746 "Failed parsing axis value at: '%s'", s);
747 return false;
748 }
749 }
750 if (!hb_subset_input_set_axis_range (subset_main->input, subset_main->face, axis_tag, min_val, max_val, def_val_p))
751 {
752 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
753 "Error: axis: '%c%c%c%c', not present in fvar or invalid range with min:%.6f max:%.6f",
754 HB_UNTAG (axis_tag), min_val, max_val);
755 return false;
756 }
757 }
758 else
759 {
760 #endif
761 if (strcmp (s, "drop") == 0)
762 {
763 if (!hb_subset_input_pin_axis_to_default (subset_main->input, subset_main->face, axis_tag))
764 {
765 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
766 "Cannot pin axis: '%c%c%c%c', not present in fvar", HB_UNTAG (axis_tag));
767 return false;
768 }
769 }
770 else
771 {
772 errno = 0;
773 char *p;
774 float axis_value = strtof (s, &p);
775 if (errno || s == p)
776 {
777 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
778 "Failed parsing axis value at: '%s'", s);
779 return false;
780 }
781
782 if (!hb_subset_input_pin_axis_location (subset_main->input, subset_main->face, axis_tag, axis_value))
783 {
784 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
785 "Cannot pin axis: '%c%c%c%c', not present in fvar", HB_UNTAG (axis_tag));
786 return false;
787 }
788 }
789 #ifdef HB_EXPERIMENTAL_API
790 }
791 #endif
792 s = strtok(nullptr, "=");
793 }
794
795 return true;
796 }
797 #endif
798
799 static gboolean
parse_glyph_map(const char * name,const char * arg,gpointer data,GError ** error)800 parse_glyph_map (const char *name,
801 const char *arg,
802 gpointer data,
803 GError **error)
804 {
805 // Glyph map has the following format:
806 // <entry 1>,<entry 2>,...,<entry n>
807 // <entry> = <old gid>:<new gid>
808 subset_main_t *subset_main = (subset_main_t *) data;
809 hb_subset_input_t* input = subset_main->input;
810 hb_set_t *glyphs = hb_subset_input_glyph_set(input);
811
812 char *s = (char *) arg;
813 char *p;
814
815 while (s && *s)
816 {
817 while (*s && strchr (", ", *s))
818 s++;
819 if (!*s)
820 break;
821
822 errno = 0;
823 hb_codepoint_t start_code = strtoul (s, &p, 10);
824 if (s[0] == '-' || errno || s == p)
825 {
826 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
827 "Failed parsing glyph map at: '%s'", s);
828 return false;
829 }
830
831 if (!p || p[0] != ':') // ranges
832 {
833 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
834 "Failed parsing glyph map at: '%s'", s);
835 return false;
836 }
837
838 s = ++p;
839 hb_codepoint_t end_code = strtoul (s, &p, 10);
840 if (s[0] == '-' || errno || s == p)
841 {
842 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
843 "Failed parsing glyph map at: '%s'", s);
844 return false;
845 }
846
847 hb_set_add(glyphs, start_code);
848 hb_map_set (hb_subset_input_old_to_new_glyph_mapping (input), start_code, end_code);
849
850 s = p;
851 }
852
853 return true;
854 }
855
856 template <GOptionArgFunc line_parser, bool allow_comments=true>
857 static gboolean
parse_file_for(const char * name,const char * arg,gpointer data,GError ** error)858 parse_file_for (const char *name,
859 const char *arg,
860 gpointer data,
861 GError **error)
862 {
863 FILE *fp = nullptr;
864 if (0 != strcmp (arg, "-"))
865 fp = fopen (arg, "r");
866 else
867 fp = stdin;
868
869 if (!fp)
870 {
871 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
872 "Failed opening file `%s': %s",
873 arg, strerror (errno));
874 return false;
875 }
876
877 GString *gs = g_string_new (nullptr);
878 do
879 {
880 g_string_set_size (gs, 0);
881 char buf[BUFSIZ];
882 while (fgets (buf, sizeof (buf), fp))
883 {
884 unsigned bytes = strlen (buf);
885 if (bytes && buf[bytes - 1] == '\n')
886 {
887 bytes--;
888 g_string_append_len (gs, buf, bytes);
889 break;
890 }
891 g_string_append_len (gs, buf, bytes);
892 }
893 if (ferror (fp))
894 {
895 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
896 "Failed reading file `%s': %s",
897 arg, strerror (errno));
898 return false;
899 }
900 g_string_append_c (gs, '\0');
901
902 if (allow_comments)
903 {
904 char *comment = strchr (gs->str, '#');
905 if (comment)
906 *comment = '\0';
907 }
908
909 line_parser ("+", gs->str, data, error);
910
911 if (*error)
912 break;
913 }
914 while (!feof (fp));
915
916 g_string_free (gs, false);
917
918 return true;
919 }
920
921 gboolean
collect_face(const char * name,const char * arg,gpointer data,GError ** error)922 subset_main_t::collect_face (const char *name,
923 const char *arg,
924 gpointer data,
925 GError **error)
926 {
927 face_options_t *thiz = (face_options_t *) data;
928
929 if (!thiz->font_file)
930 {
931 thiz->font_file = g_strdup (arg);
932 return true;
933 }
934
935 return true;
936 }
937
938 gboolean
collect_rest(const char * name,const char * arg,gpointer data,GError ** error)939 subset_main_t::collect_rest (const char *name,
940 const char *arg,
941 gpointer data,
942 GError **error)
943 {
944 subset_main_t *thiz = (subset_main_t *) data;
945
946 if (!thiz->font_file)
947 {
948 thiz->font_file = g_strdup (arg);
949 return true;
950 }
951
952 parse_text (name, arg, data, error);
953 return true;
954 }
955
956 void
add_options()957 subset_main_t::add_options ()
958 {
959 set_summary ("Subset fonts to specification.");
960
961 face_options_t::add_options (this);
962
963 GOptionEntry glyphset_entries[] =
964 {
965 {"gids", 'g', 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_gids,
966 "Specify glyph IDs or ranges to include in the subset.\n"
967 " "
968 "Use --gids-=... to subtract codepoints from the current set.", "list of glyph indices/ranges or *"},
969 {"gids-", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_gids, "Specify glyph IDs or ranges to remove from the subset", "list of glyph indices/ranges or *"},
970 {"gids+", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_gids, "Specify glyph IDs or ranges to include in the subset", "list of glyph indices/ranges or *"},
971 {"gids-file", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_file_for<parse_gids>, "Specify file to read glyph IDs or ranges from", "filename"},
972 {"glyphs", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_glyphs, "Specify glyph names to include in the subset. Use --glyphs-=... to subtract glyphs from the current set.", "list of glyph names or *"},
973 {"glyphs+", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_glyphs, "Specify glyph names to include in the subset", "list of glyph names"},
974 {"glyphs-", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_glyphs, "Specify glyph names to remove from the subset", "list of glyph names"},
975
976
977 {"glyphs-file", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_file_for<parse_glyphs>, "Specify file to read glyph names from", "filename"},
978
979 {"text", 't', 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_text, "Specify text to include in the subset. Use --text-=... to subtract codepoints from the current set.", "string"},
980 {"text-", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_text, "Specify text to remove from the subset", "string"},
981 {"text+", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_text, "Specify text to include in the subset", "string"},
982
983
984 {"text-file", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_file_for<parse_text, false>,"Specify file to read text from", "filename"},
985 {"unicodes", 'u', 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_unicodes,
986 "Specify Unicode codepoints or ranges to include in the subset. Use * to include all codepoints.\n"
987 " "
988 "--unicodes-=... can be used to subtract codepoints from the current set.\n"
989 " "
990 "For example: --unicodes=* --unicodes-=41,42,43 would create a subset with all codepoints\n"
991 " "
992 "except for 41, 42, 43.",
993 "list of hex numbers/ranges or *"},
994 {"unicodes-", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_unicodes, "Specify Unicode codepoints or ranges to remove from the subset", "list of hex numbers/ranges or *"},
995 {"unicodes+", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_unicodes, "Specify Unicode codepoints or ranges to include in the subset", "list of hex numbers/ranges or *"},
996
997 {"unicodes-file", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_file_for<parse_unicodes>,"Specify file to read Unicode codepoints or ranges from", "filename"},
998 {nullptr}
999 };
1000 add_group (glyphset_entries,
1001 "subset-glyphset",
1002 "Subset glyph-set option:",
1003 "Subsetting glyph-set options",
1004 this);
1005
1006 GOptionEntry other_entries[] =
1007 {
1008 {"gid-map", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_glyph_map, "Specify a glyph mapping to use, any unmapped gids will be automatically assigned.", "List of pairs old_gid1:new_gid1,old_gid2:new_gid2,..."},
1009 {"name-IDs", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_nameids, "Subset specified nameids. Use --name-IDs-=... to subtract from the current set.", "list of int numbers or *"},
1010 {"name-IDs-", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_nameids, "Subset specified nameids", "list of int numbers or *"},
1011 {"name-IDs+", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_nameids, "Subset specified nameids", "list of int numbers or *"},
1012 {"name-languages", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_name_languages, "Subset nameRecords with specified language IDs. Use --name-languages-=... to subtract from the current set.", "list of int numbers or *"},
1013 {"name-languages-", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_name_languages, "Subset nameRecords with specified language IDs", "list of int numbers or *"},
1014 {"name-languages+", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_name_languages, "Subset nameRecords with specified language IDs", "list of int numbers or *"},
1015
1016 {"layout-features", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_layout_features, "Specify set of layout feature tags that will be preserved. Use --layout-features-=... to subtract from the current set.", "list of string table tags or *"},
1017 {"layout-features+",0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_layout_features, "Specify set of layout feature tags that will be preserved", "list of string tags or *"},
1018 {"layout-features-",0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_layout_features, "Specify set of layout feature tags that will be preserved", "list of string tags or *"},
1019
1020 {"layout-scripts", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_layout_scripts, "Specify set of layout script tags that will be preserved. Use --layout-scripts-=... to subtract from the current set.", "list of string table tags or *"},
1021 {"layout-scripts+",0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_layout_scripts, "Specify set of layout script tags that will be preserved", "list of string tags or *"},
1022 {"layout-scripts-",0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_layout_scripts, "Specify set of layout script tags that will be preserved", "list of string tags or *"},
1023
1024 {"drop-tables", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_drop_tables, "Drop the specified tables. Use --drop-tables-=... to subtract from the current set.", "list of string table tags or *"},
1025 {"drop-tables+", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_drop_tables, "Drop the specified tables.", "list of string table tags or *"},
1026 {"drop-tables-", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_drop_tables, "Drop the specified tables.", "list of string table tags or *"},
1027 #ifndef HB_NO_VAR
1028 {"instance", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_instance,
1029 "(Partially|Fully) Instantiate a variable font. A location consists of the tag of a variation axis, followed by '=', followed by a\n"
1030 "number or the literal string 'drop'\n"
1031 " "
1032 "For example: --instance=\"wdth=100 wght=200\" or --instance=\"wdth=drop\"\n"
1033 " "
1034 "Note: currently only fully instancing is supported\n",
1035 "list of comma separated axis-locations"},
1036 #endif
1037 {nullptr}
1038 };
1039 add_group (other_entries,
1040 "subset-other",
1041 "Subset other option:",
1042 "Subsetting other options",
1043 this);
1044
1045 GOptionEntry flag_entries[] =
1046 {
1047 {"keep-everything", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &_keep_everything,
1048 "Keep everything by default. Options after this can override the operation.", nullptr},
1049 {"no-hinting", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_NO_HINTING>, "Whether to drop hints", nullptr},
1050 {"retain-gids", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_RETAIN_GIDS>, "If set don't renumber glyph ids in the subset.", nullptr},
1051 {"desubroutinize", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_DESUBROUTINIZE>, "Remove CFF/CFF2 use of subroutines", nullptr},
1052 {"name-legacy", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_NAME_LEGACY>, "Keep legacy (non-Unicode) 'name' table entries", nullptr},
1053 {"set-overlaps-flag", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_SET_OVERLAPS_FLAG>, "Set the overlaps flag on each glyph.", nullptr},
1054 {"notdef-outline", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_NOTDEF_OUTLINE>, "Keep the outline of \'.notdef\' glyph", nullptr},
1055 {"no-prune-unicode-ranges", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_NO_PRUNE_UNICODE_RANGES>, "Don't change the 'OS/2 ulUnicodeRange*' bits.", nullptr},
1056 {"no-layout-closure", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_NO_LAYOUT_CLOSURE>, "Don't perform glyph closure for layout substitution (GSUB).", nullptr},
1057 {"glyph-names", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_GLYPH_NAMES>, "Keep PS glyph names in TT-flavored fonts. ", nullptr},
1058 {"passthrough-tables", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_PASSTHROUGH_UNRECOGNIZED>, "Do not drop tables that the tool does not know how to subset.", nullptr},
1059 {"preprocess-face", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &this->preprocess,
1060 "Alternative name for --preprocess.", nullptr},
1061 {"preprocess", 0, 0, G_OPTION_ARG_NONE, &this->preprocess,
1062 "If set preprocesses the face with the add accelerator option before actually subsetting.", nullptr},
1063 {nullptr}
1064 };
1065 add_group (flag_entries,
1066 "subset-flags",
1067 "Subset boolean option:",
1068 "Subsetting boolean options",
1069 this);
1070
1071 GOptionEntry app_entries[] =
1072 {
1073 {"num-iterations", 'n', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_INT,
1074 &this->num_iterations,
1075 "Run subsetter N times (default: 1)", "N"},
1076 {nullptr}
1077 };
1078 add_group (app_entries,
1079 "subset-app",
1080 "Subset app option:",
1081 "Subsetting application options",
1082 this);
1083
1084 output_options_t::add_options (this);
1085
1086 GOptionEntry entries[] =
1087 {
1088 {G_OPTION_REMAINING, 0, G_OPTION_FLAG_IN_MAIN,
1089 G_OPTION_ARG_CALLBACK, (gpointer) &collect_rest, nullptr, "[FONT-FILE] [TEXT]"},
1090 {nullptr}
1091 };
1092 add_main_group (entries, this);
1093 option_parser_t::add_options ();
1094 }
1095
1096 int
main(int argc,char ** argv)1097 main (int argc, char **argv)
1098 {
1099 return batch_main<subset_main_t, true> (argc, argv);
1100 }
1101