• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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