xref: /aosp_15_r20/external/harfbuzz_ng/util/hb-subset.cc (revision 2d1272b857b1f7575e6e246373e1cb218663db8a)
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 "glib.h"
31 #include "main-font-text.hh"
32 #include "output-options.hh"
33 #include "helper-subset.hh"
34 
35 #include <hb-subset.h>
36 
preprocess_face(hb_face_t * face)37 static hb_face_t* preprocess_face(hb_face_t* face)
38 {
39   return hb_subset_preprocess (face);
40 }
41 
42 /*
43  * Command line interface to the harfbuzz font subsetter.
44  */
45 
46 struct subset_main_t : option_parser_t, face_options_t, output_options_t<false>
47 {
subset_main_tsubset_main_t48   subset_main_t ()
49   : input (hb_subset_input_create_or_fail ())
50   {}
~subset_main_tsubset_main_t51   ~subset_main_t ()
52   {
53     hb_subset_input_destroy (input);
54   }
55 
parse_facesubset_main_t56   void parse_face (int argc, const char * const *argv)
57   {
58     option_parser_t parser;
59     face_options_t face_opts;
60 
61     face_opts.add_options (&parser);
62 
63     GOptionEntry entries[] =
64     {
65       {G_OPTION_REMAINING,	0, G_OPTION_FLAG_IN_MAIN,
66 				G_OPTION_ARG_CALLBACK,	(gpointer) &collect_face,	nullptr,	"[FONT-FILE] [TEXT]"},
67       {nullptr}
68     };
69     parser.add_main_group (entries, &face_opts);
70     parser.add_options ();
71 
72     g_option_context_set_ignore_unknown_options (parser.context, true);
73     g_option_context_set_help_enabled (parser.context, false);
74 
75     char **args = (char **)
76 #if GLIB_CHECK_VERSION (2, 68, 0)
77       g_memdup2
78 #else
79       g_memdup
80 #endif
81       (argv, argc * sizeof (*argv));
82     parser.parse (&argc, &args);
83     g_free (args);
84 
85     set_face (face_opts.face);
86   }
87 
parsesubset_main_t88   void parse (int argc, char **argv)
89   {
90     bool help = false;
91     for (auto i = 1; i < argc; i++)
92       if (!strncmp ("--help", argv[i], 6))
93       {
94 	help = true;
95 	break;
96       }
97 
98     if (likely (!help))
99     {
100       /* Do a preliminary parse to load font-face, such that we can use it
101        * during main option parsing. */
102       parse_face (argc, argv);
103     }
104 
105     add_options ();
106     option_parser_t::parse (&argc, &argv);
107   }
108 
operator ()subset_main_t109   int operator () (int argc, char **argv)
110   {
111     parse (argc, argv);
112 
113     hb_face_t* orig_face = face;
114     if (preprocess)
115       orig_face = preprocess_face (face);
116 
117     hb_face_t *new_face = nullptr;
118     for (unsigned i = 0; i < num_iterations; i++)
119     {
120       hb_face_destroy (new_face);
121       new_face = hb_subset_or_fail (orig_face, input);
122     }
123 
124     bool success = new_face;
125     if (success)
126     {
127       hb_blob_t *result = hb_face_reference_blob (new_face);
128       write_file (output_file, result);
129       hb_blob_destroy (result);
130     }
131     else if (hb_face_get_glyph_count (orig_face) == 0)
132       fail (false, "Invalid font file.");
133 
134     hb_face_destroy (new_face);
135     if (preprocess)
136       hb_face_destroy (orig_face);
137 
138     return success ? 0 : 1;
139   }
140 
141   bool
write_filesubset_main_t142   write_file (const char *output_file, hb_blob_t *blob)
143   {
144     assert (out_fp);
145 
146     unsigned int size;
147     const char* data = hb_blob_get_data (blob, &size);
148 
149     while (size)
150     {
151       size_t ret = fwrite (data, 1, size, out_fp);
152       size -= ret;
153       data += ret;
154       if (size && ferror (out_fp))
155         fail (false, "Failed to write output: %s", strerror (errno));
156     }
157 
158     return true;
159   }
160 
161   void add_options ();
162 
163   protected:
164   static gboolean
165   collect_face (const char *name,
166 		const char *arg,
167 		gpointer    data,
168 		GError    **error);
169   static gboolean
170   collect_rest (const char *name,
171 		const char *arg,
172 		gpointer    data,
173 		GError    **error);
174 
175   public:
176 
177   unsigned num_iterations = 1;
178   gboolean preprocess = false;
179   hb_subset_input_t *input = nullptr;
180 };
181 
182 static gboolean
parse_gids(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error)183 parse_gids (const char *name G_GNUC_UNUSED,
184 	    const char *arg,
185 	    gpointer    data,
186 	    GError    **error)
187 {
188   subset_main_t *subset_main = (subset_main_t *) data;
189   hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
190   hb_bool_t is_add = (name[strlen (name) - 1] == '+');
191   hb_set_t *gids = hb_subset_input_glyph_set (subset_main->input);
192 
193   if (!is_remove && !is_add) hb_set_clear (gids);
194 
195   if (0 == strcmp (arg, "*"))
196   {
197     hb_set_clear (gids);
198     if (!is_remove)
199       hb_set_invert (gids);
200     return true;
201   }
202 
203   char *s = (char *) arg;
204   char *p;
205 
206   while (s && *s)
207   {
208     while (*s && strchr (", ", *s))
209       s++;
210     if (!*s)
211       break;
212 
213     errno = 0;
214     hb_codepoint_t start_code = strtoul (s, &p, 10);
215     if (s[0] == '-' || errno || s == p)
216     {
217       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
218 		   "Failed parsing glyph-index at: '%s'", s);
219       return false;
220     }
221 
222     if (p && p[0] == '-') // ranges
223     {
224       s = ++p;
225       hb_codepoint_t end_code = strtoul (s, &p, 10);
226       if (s[0] == '-' || errno || s == p)
227       {
228 	g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
229 		     "Failed parsing glyph-index at: '%s'", s);
230 	return false;
231       }
232 
233       if (end_code < start_code)
234       {
235 	g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
236 		     "Invalid glyph-index range %u-%u", start_code, end_code);
237 	return false;
238       }
239       if (!is_remove)
240         hb_set_add_range (gids, start_code, end_code);
241       else
242         hb_set_del_range (gids, start_code, end_code);
243     }
244     else
245     {
246       if (!is_remove)
247         hb_set_add (gids, start_code);
248       else
249         hb_set_del (gids, start_code);
250     }
251 
252     s = p;
253   }
254 
255   return true;
256 }
257 
258 static gboolean
parse_glyphs(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)259 parse_glyphs (const char *name G_GNUC_UNUSED,
260 	      const char *arg,
261 	      gpointer    data,
262 	      GError    **error G_GNUC_UNUSED)
263 {
264   subset_main_t *subset_main = (subset_main_t *) data;
265   hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
266   hb_bool_t is_add = (name[strlen (name) - 1] == '+');
267   hb_set_t *gids = hb_subset_input_glyph_set (subset_main->input);
268 
269   if (!is_remove && !is_add) hb_set_clear (gids);
270 
271   if (0 == strcmp (arg, "*"))
272   {
273     hb_set_clear (gids);
274     if (!is_remove)
275       hb_set_invert (gids);
276     return true;
277   }
278 
279   const char *p = arg;
280   const char *p_end = arg + strlen (arg);
281 
282   hb_font_t *font = hb_font_create (subset_main->face);
283   while (p < p_end)
284   {
285     while (p < p_end && (*p == ' ' || *p == ','))
286       p++;
287 
288     const char *end = p;
289     while (end < p_end && *end != ' ' && *end != ',')
290       end++;
291 
292     if (p < end)
293     {
294       hb_codepoint_t gid;
295       if (!hb_font_get_glyph_from_name (font, p, end - p, &gid))
296       {
297 	g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
298 		     "Failed parsing glyph name: '%s'", p);
299 	return false;
300       }
301 
302       if (!is_remove)
303         hb_set_add (gids, gid);
304       else
305         hb_set_del (gids, gid);
306     }
307 
308     p = end + 1;
309   }
310   hb_font_destroy (font);
311 
312   return true;
313 }
314 
315 static gboolean
parse_text(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)316 parse_text (const char *name G_GNUC_UNUSED,
317 	    const char *arg,
318 	    gpointer    data,
319 	    GError    **error G_GNUC_UNUSED)
320 {
321   subset_main_t *subset_main = (subset_main_t *) data;
322   hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
323   hb_bool_t is_add = (name[strlen (name) - 1] == '+');
324   hb_set_t *unicodes = hb_subset_input_unicode_set (subset_main->input);
325 
326   if (!is_remove && !is_add) hb_set_clear (unicodes);
327 
328   if (0 == strcmp (arg, "*"))
329   {
330     hb_set_clear (unicodes);
331     if (!is_remove)
332       hb_set_invert (unicodes);
333     return true;
334   }
335 
336   for (gchar *c = (gchar *) arg;
337        *c;
338        c = g_utf8_find_next_char(c, nullptr))
339   {
340     gunichar cp = g_utf8_get_char(c);
341     if (!is_remove)
342       hb_set_add (unicodes, cp);
343     else
344       hb_set_del (unicodes, cp);
345   }
346   return true;
347 }
348 
349 static gboolean
parse_unicodes(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error)350 parse_unicodes (const char *name G_GNUC_UNUSED,
351 		const char *arg,
352 		gpointer    data,
353 		GError    **error)
354 {
355   subset_main_t *subset_main = (subset_main_t *) data;
356   hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
357   hb_bool_t is_add = (name[strlen (name) - 1] == '+');
358   hb_set_t *unicodes = hb_subset_input_unicode_set (subset_main->input);
359 
360   if (!is_remove && !is_add) hb_set_clear (unicodes);
361 
362   if (0 == strcmp (arg, "*"))
363   {
364     hb_set_clear (unicodes);
365     if (!is_remove)
366       hb_set_invert (unicodes);
367     return true;
368   }
369 
370   // XXX TODO Ranges
371 #define DELIMITERS "<+->{},;&#\\xXuUnNiI\n\t\v\f\r "
372 
373   char *s = (char *) arg;
374   char *p;
375 
376   while (s && *s)
377   {
378     while (*s && strchr (DELIMITERS, *s))
379       s++;
380     if (!*s)
381       break;
382 
383     errno = 0;
384     hb_codepoint_t start_code = strtoul (s, &p, 16);
385     if (errno || s == p)
386     {
387       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
388 		   "Failed parsing Unicode at: '%s'", s);
389       return false;
390     }
391 
392     if (p && p[0] == '-') // ranges
393     {
394       s = ++p;
395       hb_codepoint_t end_code = strtoul (s, &p, 16);
396       if (s[0] == '-' || errno || s == p)
397       {
398 	g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
399 		     "Failed parsing Unicode at: '%s'", s);
400 	return false;
401       }
402 
403       if (end_code < start_code)
404       {
405 	g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
406 		     "Invalid Unicode range %u-%u", start_code, end_code);
407 	return false;
408       }
409       if (!is_remove)
410         hb_set_add_range (unicodes, start_code, end_code);
411       else
412         hb_set_del_range (unicodes, start_code, end_code);
413     }
414     else
415     {
416       if (!is_remove)
417         hb_set_add (unicodes, start_code);
418       else
419         hb_set_del (unicodes, start_code);
420     }
421 
422     s = p;
423   }
424 
425   return true;
426 }
427 
428 static gboolean
parse_nameids(const char * name,const char * arg,gpointer data,GError ** error)429 parse_nameids (const char *name,
430 	       const char *arg,
431 	       gpointer    data,
432 	       GError    **error)
433 {
434   subset_main_t *subset_main = (subset_main_t *) data;
435   hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
436   hb_bool_t is_add = (name[strlen (name) - 1] == '+');
437   hb_set_t *name_ids = hb_subset_input_set (subset_main->input, HB_SUBSET_SETS_NAME_ID);
438 
439 
440   if (!is_remove && !is_add) hb_set_clear (name_ids);
441 
442   if (0 == strcmp (arg, "*"))
443   {
444     hb_set_clear (name_ids);
445     if (!is_remove)
446       hb_set_invert (name_ids);
447     return true;
448   }
449 
450   char *s = (char *) arg;
451   char *p;
452 
453   while (s && *s)
454   {
455     while (*s && strchr (", ", *s))
456       s++;
457     if (!*s)
458       break;
459 
460     errno = 0;
461     hb_codepoint_t u = strtoul (s, &p, 10);
462     if (errno || s == p)
463     {
464       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
465 		   "Failed parsing nameID at: '%s'", s);
466       return false;
467     }
468 
469     if (!is_remove)
470     {
471       hb_set_add (name_ids, u);
472     } else {
473       hb_set_del (name_ids, u);
474     }
475 
476     s = p;
477   }
478 
479   return true;
480 }
481 
482 static gboolean
parse_name_languages(const char * name,const char * arg,gpointer data,GError ** error)483 parse_name_languages (const char *name,
484 		      const char *arg,
485 		      gpointer    data,
486 		      GError    **error)
487 {
488   subset_main_t *subset_main = (subset_main_t *) data;
489   hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
490   hb_bool_t is_add = (name[strlen (name) - 1] == '+');
491   hb_set_t *name_languages = hb_subset_input_set (subset_main->input, HB_SUBSET_SETS_NAME_LANG_ID);
492 
493   if (!is_remove && !is_add) hb_set_clear (name_languages);
494 
495   if (0 == strcmp (arg, "*"))
496   {
497     hb_set_clear (name_languages);
498     if (!is_remove)
499       hb_set_invert (name_languages);
500     return true;
501   }
502 
503   char *s = (char *) arg;
504   char *p;
505 
506   while (s && *s)
507   {
508     while (*s && strchr (", ", *s))
509       s++;
510     if (!*s)
511       break;
512 
513     errno = 0;
514     hb_codepoint_t u = strtoul (s, &p, 10);
515     if (errno || s == p)
516     {
517       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
518 		   "Failed parsing name-language code at: '%s'", s);
519       return false;
520     }
521 
522     if (!is_remove)
523     {
524       hb_set_add (name_languages, u);
525     } else {
526       hb_set_del (name_languages, u);
527     }
528 
529     s = p;
530   }
531 
532   return true;
533 }
534 
535 static gboolean
_keep_everything(const char * name,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)536 _keep_everything (const char *name,
537 		  const char *arg,
538 		  gpointer    data,
539 		  GError    **error G_GNUC_UNUSED)
540 {
541   subset_main_t *subset_main = (subset_main_t *) data;
542 
543   hb_subset_input_keep_everything (subset_main->input);
544 
545   return true;
546 }
547 
548 template <hb_subset_flags_t flag>
549 static gboolean
set_flag(const char * name,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)550 set_flag (const char *name,
551 	  const char *arg,
552 	  gpointer    data,
553 	  GError    **error G_GNUC_UNUSED)
554 {
555   subset_main_t *subset_main = (subset_main_t *) data;
556 
557   hb_subset_input_set_flags (subset_main->input,
558 			     hb_subset_input_get_flags (subset_main->input) | flag);
559 
560   return true;
561 }
562 
563 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)564 parse_layout_tag_list (hb_subset_sets_t set_type,
565                        const char *name,
566                        const char *arg,
567                        gpointer    data,
568                        GError    **error G_GNUC_UNUSED)
569 {
570   subset_main_t *subset_main = (subset_main_t *) data;
571   hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
572   hb_bool_t is_add = (name[strlen (name) - 1] == '+');
573   hb_set_t *layout_tags = hb_subset_input_set (subset_main->input, set_type);
574 
575   if (!is_remove && !is_add) hb_set_clear (layout_tags);
576 
577   if (0 == strcmp (arg, "*"))
578   {
579     hb_set_clear (layout_tags);
580     if (!is_remove)
581       hb_set_invert (layout_tags);
582     return true;
583   }
584 
585   char *s = strtok((char *) arg, ", ");
586   while (s)
587   {
588     if (strlen (s) > 4) // tags are at most 4 bytes
589     {
590       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
591                    "Failed parsing table tag at: '%s'", s);
592       return false;
593     }
594 
595     hb_tag_t tag = hb_tag_from_string (s, strlen (s));
596 
597     if (!is_remove)
598       hb_set_add (layout_tags, tag);
599     else
600       hb_set_del (layout_tags, tag);
601 
602     s = strtok(nullptr, ", ");
603   }
604 
605   return true;
606 }
607 
608 static gboolean
parse_layout_features(const char * name,const char * arg,gpointer data,GError ** error)609 parse_layout_features (const char *name,
610 		       const char *arg,
611 		       gpointer    data,
612 		       GError    **error)
613 
614 {
615   return parse_layout_tag_list (HB_SUBSET_SETS_LAYOUT_FEATURE_TAG,
616                                 name,
617                                 arg,
618                                 data,
619                                 error);
620 }
621 
622 static gboolean
parse_layout_scripts(const char * name,const char * arg,gpointer data,GError ** error)623 parse_layout_scripts (const char *name,
624 		       const char *arg,
625 		       gpointer    data,
626 		       GError    **error)
627 
628 {
629   return parse_layout_tag_list (HB_SUBSET_SETS_LAYOUT_SCRIPT_TAG,
630                                 name,
631                                 arg,
632                                 data,
633                                 error);
634 }
635 
636 static gboolean
parse_drop_tables(const char * name,const char * arg,gpointer data,GError ** error)637 parse_drop_tables (const char *name,
638 		   const char *arg,
639 		   gpointer    data,
640 		   GError    **error)
641 {
642   subset_main_t *subset_main = (subset_main_t *) data;
643   hb_bool_t is_remove = (name[strlen (name) - 1] == '-');
644   hb_bool_t is_add = (name[strlen (name) - 1] == '+');
645   hb_set_t *drop_tables = hb_subset_input_set (subset_main->input, HB_SUBSET_SETS_DROP_TABLE_TAG);
646 
647   if (!is_remove && !is_add) hb_set_clear (drop_tables);
648 
649   if (0 == strcmp (arg, "*"))
650   {
651     hb_set_clear (drop_tables);
652     if (!is_remove)
653       hb_set_invert (drop_tables);
654     return true;
655   }
656 
657   char *s = strtok((char *) arg, ", ");
658   while (s)
659   {
660     if (strlen (s) > 4) // Table tags are at most 4 bytes.
661     {
662       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
663 		   "Failed parsing table tag at: '%s'", s);
664       return false;
665     }
666 
667     hb_tag_t tag = hb_tag_from_string (s, strlen (s));
668 
669     if (!is_remove)
670       hb_set_add (drop_tables, tag);
671     else
672       hb_set_del (drop_tables, tag);
673 
674     s = strtok(nullptr, ", ");
675   }
676 
677   return true;
678 }
679 
680 #ifndef HB_NO_VAR
681 
682 static gboolean
parse_instance(const char * name,const char * arg,gpointer data,GError ** error)683 parse_instance (const char *name,
684 		const char *arg,
685 		gpointer    data,
686 		GError    **error)
687 {
688   subset_main_t *subset_main = (subset_main_t *) data;
689   if (!subset_main->face) {
690     // There is no face, which is needed to set up instancing. Skip parsing these options.
691     return true;
692   }
693 
694   return parse_instancing_spec(arg, subset_main->face, subset_main->input, error);
695 }
696 #endif
697 
698 static gboolean
parse_glyph_map(const char * name,const char * arg,gpointer data,GError ** error)699 parse_glyph_map (const char *name,
700 		 const char *arg,
701 		 gpointer    data,
702 		 GError    **error)
703 {
704   // Glyph map has the following format:
705   // <entry 1>,<entry 2>,...,<entry n>
706   // <entry> = <old gid>:<new gid>
707   subset_main_t *subset_main = (subset_main_t *) data;
708   hb_subset_input_t* input = subset_main->input;
709   hb_set_t *glyphs = hb_subset_input_glyph_set(input);
710 
711   char *s = (char *) arg;
712   char *p;
713 
714   while (s && *s)
715   {
716     while (*s && strchr (", ", *s))
717       s++;
718     if (!*s)
719       break;
720 
721     errno = 0;
722     hb_codepoint_t start_code = strtoul (s, &p, 10);
723     if (s[0] == '-' || errno || s == p)
724     {
725       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
726 		   "Failed parsing glyph map at: '%s'", s);
727       return false;
728     }
729 
730     if (!p || p[0] != ':') // ranges
731     {
732       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
733 		   "Failed parsing glyph map at: '%s'", s);
734       return false;
735     }
736 
737     s = ++p;
738     hb_codepoint_t end_code = strtoul (s, &p, 10);
739     if (s[0] == '-' || errno || s == p)
740     {
741       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
742 		"Failed parsing glyph map at: '%s'", s);
743       return false;
744     }
745 
746     hb_set_add(glyphs, start_code);
747     hb_map_set (hb_subset_input_old_to_new_glyph_mapping (input), start_code, end_code);
748 
749     s = p;
750   }
751 
752   return true;
753 }
754 
755 template <GOptionArgFunc line_parser, bool allow_comments=true>
756 static gboolean
parse_file_for(const char * name,const char * arg,gpointer data,GError ** error)757 parse_file_for (const char *name,
758 		const char *arg,
759 		gpointer    data,
760 		GError    **error)
761 {
762   FILE *fp = nullptr;
763   if (0 != strcmp (arg, "-"))
764     fp = fopen (arg, "r");
765   else
766     fp = stdin;
767 
768   if (!fp)
769   {
770     g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
771 		 "Failed opening file `%s': %s",
772 		 arg, strerror (errno));
773     return false;
774   }
775 
776   GString *gs = g_string_new (nullptr);
777   do
778   {
779     g_string_set_size (gs, 0);
780     char buf[BUFSIZ];
781     while (fgets (buf, sizeof (buf), fp))
782     {
783       unsigned bytes = strlen (buf);
784       if (bytes && buf[bytes - 1] == '\n')
785       {
786 	bytes--;
787 	g_string_append_len (gs, buf, bytes);
788 	break;
789       }
790       g_string_append_len (gs, buf, bytes);
791     }
792     if (ferror (fp))
793     {
794       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
795 		   "Failed reading file `%s': %s",
796 		   arg, strerror (errno));
797       fclose (fp);
798       return false;
799     }
800     g_string_append_c (gs, '\0');
801 
802     if (allow_comments)
803     {
804       char *comment = strchr (gs->str, '#');
805       if (comment)
806         *comment = '\0';
807     }
808 
809     line_parser ("+", gs->str, data, error);
810 
811     if (*error)
812       break;
813   }
814   while (!feof (fp));
815 
816   g_string_free (gs, false);
817 
818   fclose (fp);
819 
820   return true;
821 }
822 
823 gboolean
collect_face(const char * name,const char * arg,gpointer data,GError ** error)824 subset_main_t::collect_face (const char *name,
825 			     const char *arg,
826 			     gpointer    data,
827 			     GError    **error)
828 {
829   face_options_t *thiz = (face_options_t *) data;
830 
831   if (!thiz->font_file)
832   {
833     thiz->font_file = g_strdup (arg);
834     return true;
835   }
836 
837   return true;
838 }
839 
840 gboolean
collect_rest(const char * name,const char * arg,gpointer data,GError ** error)841 subset_main_t::collect_rest (const char *name,
842 			     const char *arg,
843 			     gpointer    data,
844 			     GError    **error)
845 {
846   subset_main_t *thiz = (subset_main_t *) data;
847 
848   if (!thiz->font_file)
849   {
850     thiz->font_file = g_strdup (arg);
851     return true;
852   }
853 
854   parse_text (name, arg, data, error);
855   return true;
856 }
857 
858 void
add_options()859 subset_main_t::add_options ()
860 {
861   set_summary ("Subset fonts to specification.");
862 
863   face_options_t::add_options (this);
864 
865   GOptionEntry glyphset_entries[] =
866   {
867     {"gids",		'g', 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_gids,
868      "Specify glyph IDs or ranges to include in the subset.\n"
869      "                                                        "
870      "Use --gids-=... to subtract codepoints from the current set.", "list of glyph indices/ranges or *"},
871     {"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 *"},
872     {"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 *"},
873     {"gids-file",	0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_file_for<parse_gids>,	"Specify file to read glyph IDs or ranges from", "filename"},
874     {"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 *"},
875     {"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"},
876     {"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"},
877 
878 
879     {"glyphs-file",	0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_file_for<parse_glyphs>,	"Specify file to read glyph names from", "filename"},
880 
881     {"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"},
882     {"text-",		0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_text,			"Specify text to remove from the subset", "string"},
883     {"text+",		0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_text,			"Specify text to include in the subset", "string"},
884 
885 
886     {"text-file",	0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_file_for<parse_text, false>,"Specify file to read text from", "filename"},
887     {"unicodes",	'u', 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_unicodes,
888      "Specify Unicode codepoints or ranges to include in the subset. Use * to include all codepoints.\n"
889      "                                                        "
890      "--unicodes-=... can be used to subtract codepoints from the current set.\n"
891      "                                                        "
892      "For example: --unicodes=* --unicodes-=41,42,43 would create a subset with all codepoints\n"
893      "                                                        "
894      "except for 41, 42, 43.",
895      "list of hex numbers/ranges or *"},
896     {"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 *"},
897     {"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 *"},
898 
899     {"unicodes-file",	0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_file_for<parse_unicodes>,"Specify file to read Unicode codepoints or ranges from", "filename"},
900     {nullptr}
901   };
902   add_group (glyphset_entries,
903 	     "subset-glyphset",
904 	     "Subset glyph-set option:",
905 	     "Subsetting glyph-set options",
906 	     this);
907 
908   GOptionEntry other_entries[] =
909   {
910     {"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,..."},
911     {"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 *"},
912     {"name-IDs-",	0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_nameids,		"Subset specified nameids", "list of int numbers or *"},
913     {"name-IDs+",	0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_nameids,		"Subset specified nameids", "list of int numbers or *"},
914     {"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 *"},
915     {"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 *"},
916     {"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 *"},
917 
918     {"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 *"},
919     {"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 *"},
920     {"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 *"},
921 
922     {"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 *"},
923     {"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 *"},
924     {"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 *"},
925 
926     {"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 *"},
927     {"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 *"},
928     {"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 *"},
929 #ifndef HB_NO_VAR
930      {"variations",	0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_instance,
931      "(Partially|Fully) Instantiate a variable font. A location consists of the tag "
932      "of a variation axis, followed by '=', followed by a number or the literal "
933      "string 'drop'. For example: --variations=\"wdth=100 wght=200\" or --variations=\"wdth=drop\""
934      ,
935      "list of comma separated axis-locations."
936      },
937      {"instance",	0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (gpointer) &parse_instance,
938      "Alias for --variations.", "list of comma separated axis-locations"},
939 #endif
940     {nullptr}
941   };
942   add_group (other_entries,
943 	     "subset-other",
944 	     "Subset other option:",
945 	     "Subsetting other options",
946 	     this);
947 
948   GOptionEntry flag_entries[] =
949   {
950     {"keep-everything",		0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &_keep_everything,
951      "Keep everything by default. Options after this can override the operation.", nullptr},
952     {"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},
953     {"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},
954     {"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},
955     {"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},
956     {"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},
957     {"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},
958     {"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},
959     {"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},
960     {"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},
961     {"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},
962     {"preprocess-face",		0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &this->preprocess,
963      "Alternative name for --preprocess.", nullptr},
964     {"preprocess",		0, 0, G_OPTION_ARG_NONE, &this->preprocess,
965      "If set preprocesses the face with the add accelerator option before actually subsetting.", nullptr},
966 #ifdef HB_EXPERIMENTAL_API
967     {"iftb-requirements",	0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_IFTB_REQUIREMENTS>,	"Enforce requirements needed to use the subset with incremental font transfer IFTB patches.", nullptr},
968 #endif
969     {"optimize",		0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &set_flag<HB_SUBSET_FLAGS_OPTIMIZE_IUP_DELTAS>,	"Perform IUP delta optimization on the resulting gvar table's deltas", nullptr},
970     {nullptr}
971   };
972   add_group (flag_entries,
973 	     "subset-flags",
974 	     "Subset boolean option:",
975 	     "Subsetting boolean options",
976 	     this);
977 
978   GOptionEntry app_entries[] =
979   {
980     {"num-iterations",	'n', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_INT,
981      &this->num_iterations,
982      "Run subsetter N times (default: 1)", "N"},
983     {nullptr}
984   };
985   add_group (app_entries,
986 	     "subset-app",
987 	     "Subset app option:",
988 	     "Subsetting application options",
989 	     this);
990 
991   output_options_t::add_options (this);
992 
993   GOptionEntry entries[] =
994   {
995     {G_OPTION_REMAINING,	0, G_OPTION_FLAG_IN_MAIN,
996 			      G_OPTION_ARG_CALLBACK,	(gpointer) &collect_rest,	nullptr,	"[FONT-FILE] [TEXT]"},
997     {nullptr}
998   };
999   add_main_group (entries, this);
1000   option_parser_t::add_options ();
1001 }
1002 
1003 int
main(int argc,char ** argv)1004 main (int argc, char **argv)
1005 {
1006   return batch_main<subset_main_t, true> (argc, argv);
1007 }
1008