1 /*
2  * Copyright © 2019  Google, Inc.
3  *
4  *  This is part of HarfBuzz, a text shaping library.
5  *
6  * Permission is hereby granted, without written agreement and without
7  * license or royalty fees, to use, copy, modify, and distribute this
8  * software and its documentation for any purpose, provided that the
9  * above copyright notice and the following two paragraphs appear in
10  * all copies of this software.
11  *
12  * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
13  * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
14  * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
15  * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
16  * DAMAGE.
17  *
18  * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
19  * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20  * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
21  * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
22  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
23  *
24  * Google Author(s): Garret Rieger
25  */
26 
27 #include "options.hh"
28 
29 #include "hb-subset-input.hh"
30 
31 static gboolean
parse_nameids(const char * name,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)32 parse_nameids (const char *name,
33 	       const char *arg,
34 	       gpointer    data,
35 	       GError    **error G_GNUC_UNUSED)
36 {
37   subset_options_t *subset_opts = (subset_options_t *) data;
38   hb_set_t *name_ids = subset_opts->input->name_ids;
39 
40   char last_name_char = name[strlen (name) - 1];
41 
42   if (last_name_char != '+' && last_name_char != '-')
43     hb_set_clear (name_ids);
44 
45   if (0 == strcmp (arg, "*"))
46   {
47     if (last_name_char == '-')
48       hb_set_del_range (name_ids, 0, 0x7FFF);
49     else
50       hb_set_add_range (name_ids, 0, 0x7FFF);
51     return true;
52   }
53 
54   char *s = (char *) arg;
55   char *p;
56 
57   while (s && *s)
58   {
59     while (*s && strchr (", ", *s))
60       s++;
61     if (!*s)
62       break;
63 
64     errno = 0;
65     hb_codepoint_t u = strtoul (s, &p, 10);
66     if (errno || s == p)
67     {
68       hb_set_destroy (name_ids);
69       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
70 		   "Failed parsing nameID values at: '%s'", s);
71       return false;
72     }
73 
74     if (last_name_char != '-')
75     {
76       hb_set_add (name_ids, u);
77     } else {
78       hb_set_del (name_ids, u);
79     }
80 
81     s = p;
82   }
83 
84   return true;
85 }
86 
87 static gboolean
parse_name_languages(const char * name,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)88 parse_name_languages (const char *name,
89 		      const char *arg,
90 		      gpointer    data,
91 		      GError    **error G_GNUC_UNUSED)
92 {
93   subset_options_t *subset_opts = (subset_options_t *) data;
94   hb_set_t *name_languages = subset_opts->input->name_languages;
95 
96   char last_name_char = name[strlen (name) - 1];
97 
98   if (last_name_char != '+' && last_name_char != '-')
99     hb_set_clear (name_languages);
100 
101   if (0 == strcmp (arg, "*"))
102   {
103     if (last_name_char == '-')
104       hb_set_del_range (name_languages, 0, 0x5FFF);
105     else
106       hb_set_add_range (name_languages, 0, 0x5FFF);
107     return true;
108   }
109 
110   char *s = (char *) arg;
111   char *p;
112 
113   while (s && *s)
114   {
115     while (*s && strchr (", ", *s))
116       s++;
117     if (!*s)
118       break;
119 
120     errno = 0;
121     hb_codepoint_t u = strtoul (s, &p, 10);
122     if (errno || s == p)
123     {
124       hb_set_destroy (name_languages);
125       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
126 		   "Failed parsing name_languages values at: '%s'", s);
127       return false;
128     }
129 
130     if (last_name_char != '-')
131     {
132       hb_set_add (name_languages, u);
133     } else {
134       hb_set_del (name_languages, u);
135     }
136 
137     s = p;
138   }
139 
140   return true;
141 }
142 
143 static gboolean
parse_drop_tables(const char * name,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)144 parse_drop_tables (const char *name,
145 		   const char *arg,
146 		   gpointer    data,
147 		   GError    **error G_GNUC_UNUSED)
148 {
149   subset_options_t *subset_opts = (subset_options_t *) data;
150   hb_set_t *drop_tables = subset_opts->input->drop_tables;
151 
152   char last_name_char = name[strlen (name) - 1];
153 
154   if (last_name_char != '+' && last_name_char != '-')
155     hb_set_clear (drop_tables);
156 
157   char *s = strtok((char *) arg, ", ");
158   while (s)
159   {
160     if (strlen (s) > 4) // Table tags are at most 4 bytes.
161     {
162       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
163 		   "Failed parsing table tag values at: '%s'", s);
164       return false;
165     }
166 
167     hb_tag_t tag = hb_tag_from_string (s, strlen (s));
168 
169     if (last_name_char != '-')
170       hb_set_add (drop_tables, tag);
171     else
172       hb_set_del (drop_tables, tag);
173 
174     s = strtok(nullptr, ", ");
175   }
176 
177   return true;
178 }
179 
180 void
add_options(option_parser_t * parser)181 subset_options_t::add_options (option_parser_t *parser)
182 {
183   GOptionEntry entries[] =
184   {
185     {"no-hinting", 0, 0, G_OPTION_ARG_NONE,  &this->input->drop_hints,   "Whether to drop hints",   nullptr},
186     {"retain-gids", 0, 0, G_OPTION_ARG_NONE,  &this->input->retain_gids,   "If set don't renumber glyph ids in the subset.",   nullptr},
187     {"desubroutinize", 0, 0, G_OPTION_ARG_NONE,  &this->input->desubroutinize,   "Remove CFF/CFF2 use of subroutines",   nullptr},
188     {"name-IDs", 0, 0, G_OPTION_ARG_CALLBACK,  (gpointer) &parse_nameids,  "Subset specified nameids", "list of int numbers"},
189     {"name-legacy", 0, 0, G_OPTION_ARG_NONE,  &this->input->name_legacy,   "Keep legacy (non-Unicode) 'name' table entries",   nullptr},
190     {"name-languages", 0, 0, G_OPTION_ARG_CALLBACK,  (gpointer) &parse_name_languages,  "Subset nameRecords with specified language IDs", "list of int numbers"},
191     {"drop-tables", 0, 0, G_OPTION_ARG_CALLBACK,  (gpointer) &parse_drop_tables,  "Drop the specified tables.", "list of string table tags."},
192     {"drop-tables+", 0, 0, G_OPTION_ARG_CALLBACK,  (gpointer) &parse_drop_tables,  "Drop the specified tables.", "list of string table tags."},
193     {"drop-tables-", 0, 0, G_OPTION_ARG_CALLBACK,  (gpointer) &parse_drop_tables,  "Drop the specified tables.", "list of string table tags."},
194 
195     {nullptr}
196   };
197   parser->add_group (entries,
198 	 "subset",
199 	 "Subset options:",
200 	 "Options subsetting",
201 	 this);
202 }
203