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_gids(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)32 parse_gids (const char *name G_GNUC_UNUSED,
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 *gids = subset_opts->input->glyphs;
39
40 char *s = (char *) arg;
41 char *p;
42
43 while (s && *s)
44 {
45 while (*s && strchr (", ", *s))
46 s++;
47 if (!*s)
48 break;
49
50 errno = 0;
51 hb_codepoint_t start_code = strtoul (s, &p, 10);
52 if (s[0] == '-' || errno || s == p)
53 {
54 hb_set_destroy (gids);
55 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
56 "Failed parsing gids values at: '%s'", s);
57 return false;
58 }
59
60 if (p && p[0] == '-') //gid ranges
61 {
62 s = ++p;
63 hb_codepoint_t end_code = strtoul (s, &p, 10);
64 if (s[0] == '-' || errno || s == p)
65 {
66 hb_set_destroy (gids);
67 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
68 "Failed parsing gids values at: '%s'", s);
69 return false;
70 }
71
72 if (end_code < start_code)
73 {
74 hb_set_destroy (gids);
75 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
76 "Invalid gids range value %u-%u", start_code, end_code);
77 return false;
78 }
79 hb_set_add_range (gids, start_code, end_code);
80 }
81 else
82 {
83 hb_set_add (gids, start_code);
84 }
85 s = p;
86 }
87
88 return true;
89 }
90
91 static gboolean
parse_nameids(const char * name,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)92 parse_nameids (const char *name,
93 const char *arg,
94 gpointer data,
95 GError **error G_GNUC_UNUSED)
96 {
97 subset_options_t *subset_opts = (subset_options_t *) data;
98 hb_set_t *name_ids = subset_opts->input->name_ids;
99
100 char last_name_char = name[strlen (name) - 1];
101
102 if (last_name_char != '+' && last_name_char != '-')
103 hb_set_clear (name_ids);
104
105 if (0 == strcmp (arg, "*"))
106 {
107 if (last_name_char == '-')
108 hb_set_del_range (name_ids, 0, 0x7FFF);
109 else
110 hb_set_add_range (name_ids, 0, 0x7FFF);
111 return true;
112 }
113
114 char *s = (char *) arg;
115 char *p;
116
117 while (s && *s)
118 {
119 while (*s && strchr (", ", *s))
120 s++;
121 if (!*s)
122 break;
123
124 errno = 0;
125 hb_codepoint_t u = strtoul (s, &p, 10);
126 if (errno || s == p)
127 {
128 hb_set_destroy (name_ids);
129 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
130 "Failed parsing nameID values at: '%s'", s);
131 return false;
132 }
133
134 if (last_name_char != '-')
135 {
136 hb_set_add (name_ids, u);
137 } else {
138 hb_set_del (name_ids, u);
139 }
140
141 s = p;
142 }
143
144 return true;
145 }
146
147 static gboolean
parse_name_languages(const char * name,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)148 parse_name_languages (const char *name,
149 const char *arg,
150 gpointer data,
151 GError **error G_GNUC_UNUSED)
152 {
153 subset_options_t *subset_opts = (subset_options_t *) data;
154 hb_set_t *name_languages = subset_opts->input->name_languages;
155
156 char last_name_char = name[strlen (name) - 1];
157
158 if (last_name_char != '+' && last_name_char != '-')
159 hb_set_clear (name_languages);
160
161 if (0 == strcmp (arg, "*"))
162 {
163 if (last_name_char == '-')
164 hb_set_del_range (name_languages, 0, 0x5FFF);
165 else
166 hb_set_add_range (name_languages, 0, 0x5FFF);
167 return true;
168 }
169
170 char *s = (char *) arg;
171 char *p;
172
173 while (s && *s)
174 {
175 while (*s && strchr (", ", *s))
176 s++;
177 if (!*s)
178 break;
179
180 errno = 0;
181 hb_codepoint_t u = strtoul (s, &p, 10);
182 if (errno || s == p)
183 {
184 hb_set_destroy (name_languages);
185 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
186 "Failed parsing name_languages values at: '%s'", s);
187 return false;
188 }
189
190 if (last_name_char != '-')
191 {
192 hb_set_add (name_languages, u);
193 } else {
194 hb_set_del (name_languages, u);
195 }
196
197 s = p;
198 }
199
200 return true;
201 }
202
203 static gboolean
parse_layout_features(const char * name,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)204 parse_layout_features (const char *name,
205 const char *arg,
206 gpointer data,
207 GError **error G_GNUC_UNUSED)
208 {
209 subset_options_t *subset_opts = (subset_options_t *) data;
210 hb_set_t *layout_features = subset_opts->input->layout_features;
211
212 char last_name_char = name[strlen (name) - 1];
213
214 if (last_name_char != '+' && last_name_char != '-')
215 hb_set_clear (layout_features);
216
217 if (0 == strcmp (arg, "*"))
218 {
219 if (last_name_char == '-')
220 hb_set_clear (layout_features);
221 else
222 subset_opts->input->retain_all_layout_features = true;
223 return true;
224 }
225
226 char *s = strtok((char *) arg, ", ");
227 while (s)
228 {
229 if (strlen (s) > 4) // table tags are at most 4 bytes
230 {
231 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
232 "Failed parsing table tag values at: '%s'", s);
233 return false;
234 }
235
236 hb_tag_t tag = hb_tag_from_string (s, strlen (s));
237
238 if (last_name_char != '-')
239 hb_set_add (layout_features, tag);
240 else
241 hb_set_del (layout_features, tag);
242
243 s = strtok(nullptr, ", ");
244 }
245
246 return true;
247 }
248
249 static gboolean
parse_drop_tables(const char * name,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)250 parse_drop_tables (const char *name,
251 const char *arg,
252 gpointer data,
253 GError **error G_GNUC_UNUSED)
254 {
255 subset_options_t *subset_opts = (subset_options_t *) data;
256 hb_set_t *drop_tables = subset_opts->input->drop_tables;
257
258 char last_name_char = name[strlen (name) - 1];
259
260 if (last_name_char != '+' && last_name_char != '-')
261 hb_set_clear (drop_tables);
262
263 char *s = strtok((char *) arg, ", ");
264 while (s)
265 {
266 if (strlen (s) > 4) // Table tags are at most 4 bytes.
267 {
268 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
269 "Failed parsing table tag values at: '%s'", s);
270 return false;
271 }
272
273 hb_tag_t tag = hb_tag_from_string (s, strlen (s));
274
275 if (last_name_char != '-')
276 hb_set_add (drop_tables, tag);
277 else
278 hb_set_del (drop_tables, tag);
279
280 s = strtok(nullptr, ", ");
281 }
282
283 return true;
284 }
285
286 void
add_options(option_parser_t * parser)287 subset_options_t::add_options (option_parser_t *parser)
288 {
289 GOptionEntry entries[] =
290 {
291 {"no-hinting", 0, 0, G_OPTION_ARG_NONE, &this->input->drop_hints, "Whether to drop hints", nullptr},
292 {"retain-gids", 0, 0, G_OPTION_ARG_NONE, &this->input->retain_gids, "If set don't renumber glyph ids in the subset.", nullptr},
293 {"gids", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_gids, "Specify glyph IDs or ranges to include in the subset", "list of comma/whitespace-separated int numbers or ranges"},
294 {"desubroutinize", 0, 0, G_OPTION_ARG_NONE, &this->input->desubroutinize, "Remove CFF/CFF2 use of subroutines", nullptr},
295 {"name-IDs", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_nameids, "Subset specified nameids", "list of int numbers"},
296 {"name-IDs-", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_nameids, "Subset specified nameids", "list of int numbers"},
297 {"name-IDs+", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_nameids, "Subset specified nameids", "list of int numbers"},
298 {"name-legacy", 0, 0, G_OPTION_ARG_NONE, &this->input->name_legacy, "Keep legacy (non-Unicode) 'name' table entries", nullptr},
299 {"name-languages", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_name_languages, "Subset nameRecords with specified language IDs", "list of int numbers"},
300 {"name-languages-", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_name_languages, "Subset nameRecords with specified language IDs", "list of int numbers"},
301 {"name-languages+", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_name_languages, "Subset nameRecords with specified language IDs", "list of int numbers"},
302 {"layout-features", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_layout_features, "Specify set of layout feature tags that will be preserved", "list of string table tags."},
303 {"layout-features+", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_layout_features, "Specify set of layout feature tags that will be preserved", "list of string table tags."},
304 {"layout-features-", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_layout_features, "Specify set of layout feature tags that will be preserved", "list of string table tags."},
305 {"drop-tables", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_drop_tables, "Drop the specified tables.", "list of string table tags."},
306 {"drop-tables+", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_drop_tables, "Drop the specified tables.", "list of string table tags."},
307 {"drop-tables-", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_drop_tables, "Drop the specified tables.", "list of string table tags."},
308 {"num-iterations", 'n', 0, G_OPTION_ARG_INT,
309 &this->num_iterations,
310 "Run subsetter N times (default: 1)", "N"},
311 {"set-overlaps-flag", 0, 0, G_OPTION_ARG_NONE, &this->input->overlaps_flag,
312 "Set the overlaps flag on each glyph.", nullptr},
313 {"notdef-outline", 0, 0, G_OPTION_ARG_NONE, &this->input->notdef_outline, "Keep the outline of \'.notdef\' glyph", nullptr},
314 {"no-prune-unicode-ranges", 0, 0, G_OPTION_ARG_NONE, &this->input->no_prune_unicode_ranges, "Don't change the 'OS/2 ulUnicodeRange*' bits.", nullptr},
315 {nullptr}
316 };
317 parser->add_group (entries,
318 "subset",
319 "Subset options:",
320 "Options subsetting",
321 this);
322 }
323