1 /*
2  * Copyright © 2011,2012  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): Behdad Esfahbod
25  */
26 
27 #include "options.hh"
28 
29 #ifdef HAVE_FREETYPE
30 #include <hb-ft.h>
31 #endif
32 #include <hb-ot.h>
33 
34 #define DELIMITERS "<+>{},;&#\\xXuUnNiI\n\t\v\f\r "
35 
36 static struct supported_font_funcs_t {
37 	char name[4];
38 	void (*func) (hb_font_t *);
39 } supported_font_funcs[] =
40 {
41 #ifdef HAVE_FREETYPE
42   {"ft",	hb_ft_font_set_funcs},
43 #endif
44   {"ot",	hb_ot_font_set_funcs},
45 };
46 
47 
48 void
fail(hb_bool_t suggest_help,const char * format,...)49 fail (hb_bool_t suggest_help, const char *format, ...)
50 {
51   const char *msg;
52 
53   va_list vap;
54   va_start (vap, format);
55   msg = g_strdup_vprintf (format, vap);
56   va_end (vap);
57   const char *prgname = g_get_prgname ();
58   g_printerr ("%s: %s\n", prgname, msg);
59   if (suggest_help)
60     g_printerr ("Try `%s --help' for more information.\n", prgname);
61 
62   exit (1);
63 }
64 
65 
66 static gchar *
shapers_to_string()67 shapers_to_string ()
68 {
69   GString *shapers = g_string_new (nullptr);
70   const char **shaper_list = hb_shape_list_shapers ();
71 
72   for (; *shaper_list; shaper_list++) {
73     g_string_append (shapers, *shaper_list);
74     g_string_append_c (shapers, ',');
75   }
76   g_string_truncate (shapers, MAX (0, (gint)shapers->len - 1));
77 
78   return g_string_free (shapers, false);
79 }
80 
81 static G_GNUC_NORETURN gboolean
show_version(const char * name G_GNUC_UNUSED,const char * arg G_GNUC_UNUSED,gpointer data G_GNUC_UNUSED,GError ** error G_GNUC_UNUSED)82 show_version (const char *name G_GNUC_UNUSED,
83 	      const char *arg G_GNUC_UNUSED,
84 	      gpointer    data G_GNUC_UNUSED,
85 	      GError    **error G_GNUC_UNUSED)
86 {
87   g_printf ("%s (%s) %s\n", g_get_prgname (), PACKAGE_NAME, PACKAGE_VERSION);
88 
89   char *shapers = shapers_to_string ();
90   g_printf ("Available shapers: %s\n", shapers);
91   g_free (shapers);
92   if (strcmp (HB_VERSION_STRING, hb_version_string ()))
93     g_printf ("Linked HarfBuzz library has a different version: %s\n", hb_version_string ());
94 
95   exit(0);
96 }
97 
98 
99 void
add_main_options()100 option_parser_t::add_main_options ()
101 {
102   GOptionEntry entries[] =
103   {
104     {"version",		0, G_OPTION_FLAG_NO_ARG,
105 			      G_OPTION_ARG_CALLBACK,	(gpointer) &show_version,	"Show version numbers",			nullptr},
106     {nullptr}
107   };
108   g_option_context_add_main_entries (context, entries, nullptr);
109 }
110 
111 static gboolean
pre_parse(GOptionContext * context G_GNUC_UNUSED,GOptionGroup * group G_GNUC_UNUSED,gpointer data,GError ** error)112 pre_parse (GOptionContext *context G_GNUC_UNUSED,
113 	   GOptionGroup *group G_GNUC_UNUSED,
114 	   gpointer data,
115 	   GError **error)
116 {
117   option_group_t *option_group = (option_group_t *) data;
118   option_group->pre_parse (error);
119   return *error == nullptr;
120 }
121 
122 static gboolean
post_parse(GOptionContext * context G_GNUC_UNUSED,GOptionGroup * group G_GNUC_UNUSED,gpointer data,GError ** error)123 post_parse (GOptionContext *context G_GNUC_UNUSED,
124 	    GOptionGroup *group G_GNUC_UNUSED,
125 	    gpointer data,
126 	    GError **error)
127 {
128   option_group_t *option_group = static_cast<option_group_t *>(data);
129   option_group->post_parse (error);
130   return *error == nullptr;
131 }
132 
133 void
add_group(GOptionEntry * entries,const gchar * name,const gchar * description,const gchar * help_description,option_group_t * option_group)134 option_parser_t::add_group (GOptionEntry   *entries,
135 			    const gchar    *name,
136 			    const gchar    *description,
137 			    const gchar    *help_description,
138 			    option_group_t *option_group)
139 {
140   GOptionGroup *group = g_option_group_new (name, description, help_description,
141 					    static_cast<gpointer>(option_group), nullptr);
142   g_option_group_add_entries (group, entries);
143   g_option_group_set_parse_hooks (group, pre_parse, post_parse);
144   g_option_context_add_group (context, group);
145 }
146 
147 void
parse(int * argc,char *** argv)148 option_parser_t::parse (int *argc, char ***argv)
149 {
150   setlocale (LC_ALL, "");
151 
152   GError *parse_error = nullptr;
153   if (!g_option_context_parse (context, argc, argv, &parse_error))
154   {
155     if (parse_error != nullptr) {
156       fail (true, "%s", parse_error->message);
157       //g_error_free (parse_error);
158     } else
159       fail (true, "Option parse error");
160   }
161 }
162 
163 
164 static gboolean
parse_margin(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)165 parse_margin (const char *name G_GNUC_UNUSED,
166 	      const char *arg,
167 	      gpointer    data,
168 	      GError    **error G_GNUC_UNUSED)
169 {
170   view_options_t *view_opts = (view_options_t *) data;
171   view_options_t::margin_t &m = view_opts->margin;
172   switch (sscanf (arg, "%lf%*[ ,]%lf%*[ ,]%lf%*[ ,]%lf", &m.t, &m.r, &m.b, &m.l)) {
173     case 1: m.r = m.t; HB_FALLTHROUGH;
174     case 2: m.b = m.t; HB_FALLTHROUGH;
175     case 3: m.l = m.r; HB_FALLTHROUGH;
176     case 4: return true;
177     default:
178       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
179 		   "%s argument should be one to four space-separated numbers",
180 		   name);
181       return false;
182   }
183 }
184 
185 
186 static gboolean
parse_shapers(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error)187 parse_shapers (const char *name G_GNUC_UNUSED,
188 	       const char *arg,
189 	       gpointer    data,
190 	       GError    **error)
191 {
192   shape_options_t *shape_opts = (shape_options_t *) data;
193   char **shapers = g_strsplit (arg, ",", 0);
194 
195   for (char **shaper = shapers; *shaper; shaper++) {
196     bool found = false;
197     for (const char **hb_shaper = hb_shape_list_shapers (); *hb_shaper; hb_shaper++) {
198       if (strcmp (*shaper, *hb_shaper) == 0) {
199 	found = true;
200 	break;
201       }
202     }
203     if (!found) {
204       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
205 		   "Unknown or unsupported shaper: %s", *shaper);
206       g_strfreev (shapers);
207       return false;
208     }
209   }
210 
211   g_strfreev (shape_opts->shapers);
212   shape_opts->shapers = shapers;
213   return true;
214 }
215 
216 static G_GNUC_NORETURN gboolean
list_shapers(const char * name G_GNUC_UNUSED,const char * arg G_GNUC_UNUSED,gpointer data G_GNUC_UNUSED,GError ** error G_GNUC_UNUSED)217 list_shapers (const char *name G_GNUC_UNUSED,
218 	      const char *arg G_GNUC_UNUSED,
219 	      gpointer    data G_GNUC_UNUSED,
220 	      GError    **error G_GNUC_UNUSED)
221 {
222   for (const char **shaper = hb_shape_list_shapers (); *shaper; shaper++)
223     g_printf ("%s\n", *shaper);
224 
225   exit(0);
226 }
227 
228 
229 static gboolean
parse_features(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)230 parse_features (const char *name G_GNUC_UNUSED,
231 		const char *arg,
232 		gpointer    data,
233 		GError    **error G_GNUC_UNUSED)
234 {
235   shape_options_t *shape_opts = (shape_options_t *) data;
236   char *s = (char *) arg;
237   char *p;
238 
239   shape_opts->num_features = 0;
240   g_free (shape_opts->features);
241   shape_opts->features = nullptr;
242 
243   if (!*s)
244     return true;
245 
246   /* count the features first, so we can allocate memory */
247   p = s;
248   do {
249     shape_opts->num_features++;
250     p = strchr (p, ',');
251     if (p)
252       p++;
253   } while (p);
254 
255   shape_opts->features = (hb_feature_t *) calloc (shape_opts->num_features, sizeof (*shape_opts->features));
256   if (!shape_opts->features)
257     return false;
258 
259   /* now do the actual parsing */
260   p = s;
261   shape_opts->num_features = 0;
262   while (p && *p) {
263     char *end = strchr (p, ',');
264     if (hb_feature_from_string (p, end ? end - p : -1, &shape_opts->features[shape_opts->num_features]))
265       shape_opts->num_features++;
266     p = end ? end + 1 : nullptr;
267   }
268 
269   return true;
270 }
271 
272 static gboolean
parse_variations(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)273 parse_variations (const char *name G_GNUC_UNUSED,
274 		  const char *arg,
275 		  gpointer    data,
276 		  GError    **error G_GNUC_UNUSED)
277 {
278   font_options_t *font_opts = (font_options_t *) data;
279   char *s = (char *) arg;
280   char *p;
281 
282   font_opts->num_variations = 0;
283   g_free (font_opts->variations);
284   font_opts->variations = nullptr;
285 
286   if (!*s)
287     return true;
288 
289   /* count the variations first, so we can allocate memory */
290   p = s;
291   do {
292     font_opts->num_variations++;
293     p = strchr (p, ',');
294     if (p)
295       p++;
296   } while (p);
297 
298   font_opts->variations = (hb_variation_t *) calloc (font_opts->num_variations, sizeof (*font_opts->variations));
299   if (!font_opts->variations)
300     return false;
301 
302   /* now do the actual parsing */
303   p = s;
304   font_opts->num_variations = 0;
305   while (p && *p) {
306     char *end = strchr (p, ',');
307     if (hb_variation_from_string (p, end ? end - p : -1, &font_opts->variations[font_opts->num_variations]))
308       font_opts->num_variations++;
309     p = end ? end + 1 : nullptr;
310   }
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   text_options_t *text_opts = (text_options_t *) data;
322 
323   if (text_opts->text)
324   {
325     g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
326 		 "Either --text or --unicodes can be provided but not both");
327     return false;
328   }
329 
330   text_opts->text_len = -1;
331   text_opts->text = g_strdup (arg);
332   return true;
333 }
334 
335 
336 static gboolean
parse_unicodes(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)337 parse_unicodes (const char *name G_GNUC_UNUSED,
338 		const char *arg,
339 		gpointer    data,
340 		GError    **error G_GNUC_UNUSED)
341 {
342   text_options_t *text_opts = (text_options_t *) data;
343 
344   if (text_opts->text)
345   {
346     g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
347 		 "Either --text or --unicodes can be provided but not both");
348     return false;
349   }
350 
351   GString *gs = g_string_new (nullptr);
352   if (0 == strcmp (arg, "*"))
353   {
354     g_string_append_c (gs, '*');
355   }
356   else
357   {
358 
359     char *s = (char *) arg;
360     char *p;
361 
362     while (s && *s)
363     {
364       while (*s && strchr (DELIMITERS, *s))
365 	s++;
366       if (!*s)
367 	break;
368 
369       errno = 0;
370       hb_codepoint_t u = strtoul (s, &p, 16);
371       if (errno || s == p)
372       {
373 	g_string_free (gs, TRUE);
374 	g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
375 		     "Failed parsing Unicode values at: '%s'", s);
376 	return false;
377       }
378 
379       g_string_append_unichar (gs, u);
380 
381       s = p;
382     }
383   }
384 
385   text_opts->text_len = gs->len;
386   text_opts->text = g_string_free (gs, FALSE);
387   return true;
388 }
389 
390 
391 void
add_options(option_parser_t * parser)392 view_options_t::add_options (option_parser_t *parser)
393 {
394   GOptionEntry entries[] =
395   {
396     {"annotate",	0, 0, G_OPTION_ARG_NONE,	&this->annotate,		"Annotate output rendering",				nullptr},
397     {"background",	0, 0, G_OPTION_ARG_STRING,	&this->back,			"Set background color (default: " DEFAULT_BACK ")",	"rrggbb/rrggbbaa"},
398     {"foreground",	0, 0, G_OPTION_ARG_STRING,	&this->fore,			"Set foreground color (default: " DEFAULT_FORE ")",	"rrggbb/rrggbbaa"},
399     {"line-space",	0, 0, G_OPTION_ARG_DOUBLE,	&this->line_space,		"Set space between lines (default: 0)",			"units"},
400     {"margin",		0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_margin,	"Margin around output (default: " G_STRINGIFY(DEFAULT_MARGIN) ")","one to four numbers"},
401     {nullptr}
402   };
403   parser->add_group (entries,
404 		     "view",
405 		     "View options:",
406 		     "Options for output rendering",
407 		     this);
408 }
409 
410 void
add_options(option_parser_t * parser)411 shape_options_t::add_options (option_parser_t *parser)
412 {
413   GOptionEntry entries[] =
414   {
415     {"list-shapers",	0, G_OPTION_FLAG_NO_ARG,
416 			      G_OPTION_ARG_CALLBACK,	(gpointer) &list_shapers,	"List available shapers and quit",	nullptr},
417     {"shaper",		0, G_OPTION_FLAG_HIDDEN,
418 			      G_OPTION_ARG_CALLBACK,	(gpointer) &parse_shapers,	"Hidden duplicate of --shapers",	nullptr},
419     {"shapers",		0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_shapers,	"Set comma-separated list of shapers to try","list"},
420     {"direction",	0, 0, G_OPTION_ARG_STRING,	&this->direction,		"Set text direction (default: auto)",	"ltr/rtl/ttb/btt"},
421     {"language",	0, 0, G_OPTION_ARG_STRING,	&this->language,		"Set text language (default: $LANG)",	"langstr"},
422     {"script",		0, 0, G_OPTION_ARG_STRING,	&this->script,			"Set text script (default: auto)",	"ISO-15924 tag"},
423     {"bot",		0, 0, G_OPTION_ARG_NONE,	&this->bot,			"Treat text as beginning-of-paragraph",	nullptr},
424     {"eot",		0, 0, G_OPTION_ARG_NONE,	&this->eot,			"Treat text as end-of-paragraph",	nullptr},
425     {"preserve-default-ignorables",0, 0, G_OPTION_ARG_NONE,	&this->preserve_default_ignorables,	"Preserve Default-Ignorable characters",	nullptr},
426     {"remove-default-ignorables",0, 0, G_OPTION_ARG_NONE,	&this->remove_default_ignorables,	"Remove Default-Ignorable characters",	nullptr},
427     {"invisible-glyph",	0, 0, G_OPTION_ARG_INT,		&this->invisible_glyph,		"Glyph value to replace Default-Ignorables with",	nullptr},
428     {"utf8-clusters",	0, 0, G_OPTION_ARG_NONE,	&this->utf8_clusters,		"Use UTF8 byte indices, not char indices",	nullptr},
429     {"cluster-level",	0, 0, G_OPTION_ARG_INT,		&this->cluster_level,		"Cluster merging level (default: 0)",	"0/1/2"},
430     {"normalize-glyphs",0, 0, G_OPTION_ARG_NONE,	&this->normalize_glyphs,	"Rearrange glyph clusters in nominal order",	nullptr},
431     {"verify",		0, 0, G_OPTION_ARG_NONE,	&this->verify,			"Perform sanity checks on shaping results",	nullptr},
432     {"num-iterations", 'n', 0, G_OPTION_ARG_INT,		&this->num_iterations,		"Run shaper N times (default: 1)",	"N"},
433     {nullptr}
434   };
435   parser->add_group (entries,
436 		     "shape",
437 		     "Shape options:",
438 		     "Options for the shaping process",
439 		     this);
440 
441   const gchar *features_help = "Comma-separated list of font features\n"
442     "\n"
443     "    Features can be enabled or disabled, either globally or limited to\n"
444     "    specific character ranges.  The format for specifying feature settings\n"
445     "    follows.  All valid CSS font-feature-settings values other than 'normal'\n"
446     "    and the global values are also accepted, though not documented below.\n"
447     "    CSS string escapes are not supported."
448     "\n"
449     "    The range indices refer to the positions between Unicode characters,\n"
450     "    unless the --utf8-clusters is provided, in which case range indices\n"
451     "    refer to UTF-8 byte indices. The position before the first character\n"
452     "    is always 0.\n"
453     "\n"
454     "    The format is Python-esque.  Here is how it all works:\n"
455     "\n"
456     "      Syntax:       Value:    Start:    End:\n"
457     "\n"
458     "    Setting value:\n"
459     "      \"kern\"        1         0         ∞         # Turn feature on\n"
460     "      \"+kern\"       1         0         ∞         # Turn feature on\n"
461     "      \"-kern\"       0         0         ∞         # Turn feature off\n"
462     "      \"kern=0\"      0         0         ∞         # Turn feature off\n"
463     "      \"kern=1\"      1         0         ∞         # Turn feature on\n"
464     "      \"aalt=2\"      2         0         ∞         # Choose 2nd alternate\n"
465     "\n"
466     "    Setting index:\n"
467     "      \"kern[]\"      1         0         ∞         # Turn feature on\n"
468     "      \"kern[:]\"     1         0         ∞         # Turn feature on\n"
469     "      \"kern[5:]\"    1         5         ∞         # Turn feature on, partial\n"
470     "      \"kern[:5]\"    1         0         5         # Turn feature on, partial\n"
471     "      \"kern[3:5]\"   1         3         5         # Turn feature on, range\n"
472     "      \"kern[3]\"     1         3         3+1       # Turn feature on, single char\n"
473     "\n"
474     "    Mixing it all:\n"
475     "\n"
476     "      \"aalt[3:5]=2\" 2         3         5         # Turn 2nd alternate on for range";
477 
478   GOptionEntry entries2[] =
479   {
480     {"features",	0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_features,	features_help,	"list"},
481     {nullptr}
482   };
483   parser->add_group (entries2,
484 		     "features",
485 		     "Features options:",
486 		     "Options for font features used",
487 		     this);
488 }
489 
490 static gboolean
parse_font_size(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)491 parse_font_size (const char *name G_GNUC_UNUSED,
492 		 const char *arg,
493 		 gpointer    data,
494 		 GError    **error G_GNUC_UNUSED)
495 {
496   font_options_t *font_opts = (font_options_t *) data;
497   if (0 == strcmp (arg, "upem"))
498   {
499     font_opts->font_size_y = font_opts->font_size_x = FONT_SIZE_UPEM;
500     return true;
501   }
502   switch (sscanf (arg, "%lf%*[ ,]%lf", &font_opts->font_size_x, &font_opts->font_size_y)) {
503     case 1: font_opts->font_size_y = font_opts->font_size_x; HB_FALLTHROUGH;
504     case 2: return true;
505     default:
506       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
507 		   "%s argument should be one or two space-separated numbers",
508 		   name);
509       return false;
510   }
511 }
512 
513 static gboolean
parse_font_ppem(const char * name G_GNUC_UNUSED,const char * arg,gpointer data,GError ** error G_GNUC_UNUSED)514 parse_font_ppem (const char *name G_GNUC_UNUSED,
515 		 const char *arg,
516 		 gpointer    data,
517 		 GError    **error G_GNUC_UNUSED)
518 {
519   font_options_t *font_opts = (font_options_t *) data;
520   switch (sscanf (arg, "%d%*[ ,]%d", &font_opts->x_ppem, &font_opts->y_ppem)) {
521     case 1: font_opts->y_ppem = font_opts->x_ppem; HB_FALLTHROUGH;
522     case 2: return true;
523     default:
524       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
525 		   "%s argument should be one or two space-separated numbers",
526 		   name);
527       return false;
528   }
529 }
530 
531 void
add_options(option_parser_t * parser)532 font_options_t::add_options (option_parser_t *parser)
533 {
534   char *text = nullptr;
535 
536   {
537     static_assert ((ARRAY_LENGTH_CONST (supported_font_funcs) > 0),
538 		   "No supported font-funcs found.");
539     GString *s = g_string_new (nullptr);
540     g_string_printf (s, "Set font functions implementation to use (default: %s)\n\n    Supported font function implementations are: %s",
541 		     supported_font_funcs[0].name,
542 		     supported_font_funcs[0].name);
543     for (unsigned int i = 1; i < ARRAY_LENGTH (supported_font_funcs); i++)
544     {
545       g_string_append_c (s, '/');
546       g_string_append (s, supported_font_funcs[i].name);
547     }
548     text = g_string_free (s, FALSE);
549     parser->free_later (text);
550   }
551 
552   char *font_size_text;
553   if (default_font_size == FONT_SIZE_UPEM)
554     font_size_text = (char *) "Font size (default: upem)";
555   else
556   {
557     font_size_text = g_strdup_printf ("Font size (default: %d)", default_font_size);
558     parser->free_later (font_size_text);
559   }
560 
561   GOptionEntry entries[] =
562   {
563     {"font-file",	0, 0, G_OPTION_ARG_STRING,	&this->font_file,		"Set font file-name",				"filename"},
564     {"face-index",	0, 0, G_OPTION_ARG_INT,		&this->face_index,		"Set face index (default: 0)",			"index"},
565     {"font-size",	0, default_font_size ? 0 : G_OPTION_FLAG_HIDDEN,
566 			      G_OPTION_ARG_CALLBACK,	(gpointer) &parse_font_size,	font_size_text,					"1/2 integers or 'upem'"},
567     {"font-ppem",	0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_font_ppem,	"Set x,y pixels per EM (default: 0; disabled)",	"1/2 integers"},
568     {"font-ptem",	0, 0, G_OPTION_ARG_DOUBLE,	&this->ptem,			"Set font point-size (default: 0; disabled)",	"point-size"},
569     {"font-funcs",	0, 0, G_OPTION_ARG_STRING,	&this->font_funcs,		text,						"impl"},
570     {"ft-load-flags",	0, 0, G_OPTION_ARG_INT,		&this->ft_load_flags,		"Set FreeType load-flags (default: 2)",		"integer"},
571     {nullptr}
572   };
573   parser->add_group (entries,
574 		     "font",
575 		     "Font options:",
576 		     "Options for the font",
577 		     this);
578 
579   const gchar *variations_help = "Comma-separated list of font variations\n"
580     "\n"
581     "    Variations are set globally. The format for specifying variation settings\n"
582     "    follows.  All valid CSS font-variation-settings values other than 'normal'\n"
583     "    and 'inherited' are also accepted, though, not documented below.\n"
584     "\n"
585     "    The format is a tag, optionally followed by an equals sign, followed by a\n"
586     "    number. For example:\n"
587     "\n"
588     "      \"wght=500\"\n"
589     "      \"slnt=-7.5\"\n";
590 
591   GOptionEntry entries2[] =
592   {
593     {"variations",	0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_variations,	variations_help,	"list"},
594     {nullptr}
595   };
596   parser->add_group (entries2,
597 		     "variations",
598 		     "Variations options:",
599 		     "Options for font variations used",
600 		     this);
601 }
602 
603 void
add_options(option_parser_t * parser)604 text_options_t::add_options (option_parser_t *parser)
605 {
606   GOptionEntry entries[] =
607   {
608     {"text",		0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_text,		"Set input text",			"string"},
609     {"text-file",	0, 0, G_OPTION_ARG_STRING,	&this->text_file,		"Set input text file-name\n\n    If no text is provided, standard input is used for input.\n",		"filename"},
610     {"unicodes",      'u', 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_unicodes,		"Set input Unicode codepoints",		"list of hex numbers"},
611     {"text-before",	0, 0, G_OPTION_ARG_STRING,	&this->text_before,		"Set text context before each line",	"string"},
612     {"text-after",	0, 0, G_OPTION_ARG_STRING,	&this->text_after,		"Set text context after each line",	"string"},
613     {nullptr}
614   };
615   parser->add_group (entries,
616 		     "text",
617 		     "Text options:",
618 		     "Options for the input text",
619 		     this);
620 }
621 
622 void
add_options(option_parser_t * parser)623 output_options_t::add_options (option_parser_t *parser)
624 {
625   const char *text;
626 
627   if (nullptr == supported_formats)
628     text = "Set output serialization format";
629   else
630   {
631     char *items = g_strjoinv ("/", const_cast<char **> (supported_formats));
632     text = g_strdup_printf ("Set output format\n\n    Supported output formats are: %s", items);
633     g_free (items);
634     parser->free_later ((char *) text);
635   }
636 
637   GOptionEntry entries[] =
638   {
639     {"output-file",   'o', 0, G_OPTION_ARG_STRING,	&this->output_file,		"Set output file-name (default: stdout)","filename"},
640     {"output-format", 'O', 0, G_OPTION_ARG_STRING,	&this->output_format,		text,					"format"},
641     {nullptr}
642   };
643   parser->add_group (entries,
644 		     "output",
645 		     "Output destination & format options:",
646 		     "Options for the destination & form of the output",
647 		     this);
648 }
649 
650 
651 
652 hb_font_t *
get_font() const653 font_options_t::get_font () const
654 {
655   if (font)
656     return font;
657 
658   /* Create the blob */
659   if (!font_file)
660     fail (true, "No font file set");
661 
662   const char *font_path = font_file;
663 
664   if (0 == strcmp (font_path, "-"))
665   {
666 #if defined(_WIN32) || defined(__CYGWIN__)
667     setmode (fileno (stdin), O_BINARY);
668     font_path = "STDIN";
669 #else
670     font_path = "/dev/stdin";
671 #endif
672   }
673 
674   blob = hb_blob_create_from_file (font_path);
675 
676   if (blob == hb_blob_get_empty ())
677     fail (false, "Couldn't read or find %s, or it was empty.", font_path);
678 
679   /* Create the face */
680   hb_face_t *face = hb_face_create (blob, face_index);
681   hb_blob_destroy (blob);
682 
683 
684   font = hb_font_create (face);
685 
686   if (font_size_x == FONT_SIZE_UPEM)
687     font_size_x = hb_face_get_upem (face);
688   if (font_size_y == FONT_SIZE_UPEM)
689     font_size_y = hb_face_get_upem (face);
690 
691   hb_font_set_ppem (font, x_ppem, y_ppem);
692   hb_font_set_ptem (font, ptem);
693 
694   int scale_x = (int) scalbnf (font_size_x, subpixel_bits);
695   int scale_y = (int) scalbnf (font_size_y, subpixel_bits);
696   hb_font_set_scale (font, scale_x, scale_y);
697   hb_face_destroy (face);
698 
699   hb_font_set_variations (font, variations, num_variations);
700 
701   void (*set_font_funcs) (hb_font_t *) = nullptr;
702   if (!font_funcs)
703   {
704     set_font_funcs = supported_font_funcs[0].func;
705   }
706   else
707   {
708     for (unsigned int i = 0; i < ARRAY_LENGTH (supported_font_funcs); i++)
709       if (0 == g_ascii_strcasecmp (font_funcs, supported_font_funcs[i].name))
710       {
711 	set_font_funcs = supported_font_funcs[i].func;
712 	break;
713       }
714     if (!set_font_funcs)
715     {
716       GString *s = g_string_new (nullptr);
717       for (unsigned int i = 0; i < ARRAY_LENGTH (supported_font_funcs); i++)
718       {
719 	if (i)
720 	  g_string_append_c (s, '/');
721 	g_string_append (s, supported_font_funcs[i].name);
722       }
723       char *p = g_string_free (s, FALSE);
724       fail (false, "Unknown font function implementation `%s'; supported values are: %s; default is %s",
725 	    font_funcs,
726 	    p,
727 	    supported_font_funcs[0].name);
728       //free (p);
729     }
730   }
731   set_font_funcs (font);
732 #ifdef HAVE_FREETYPE
733   hb_ft_font_set_load_flags (font, ft_load_flags);
734 #endif
735 
736   return font;
737 }
738 
739 
740 const char *
get_line(unsigned int * len)741 text_options_t::get_line (unsigned int *len)
742 {
743   if (text) {
744     if (!line)
745     {
746       line = text;
747       line_len = text_len;
748     }
749     if (line_len == UINT_MAX)
750       line_len = strlen (line);
751 
752     if (!line_len) {
753       *len = 0;
754       return nullptr;
755     }
756 
757     const char *ret = line;
758     const char *p = (const char *) memchr (line, '\n', line_len);
759     unsigned int ret_len;
760     if (!p) {
761       ret_len = line_len;
762       line += ret_len;
763       line_len = 0;
764     } else {
765       ret_len = p - ret;
766       line += ret_len + 1;
767       line_len -= ret_len + 1;
768     }
769 
770     *len = ret_len;
771     return ret;
772   }
773 
774   if (!fp) {
775     if (!text_file)
776       fail (true, "At least one of text or text-file must be set");
777 
778     if (0 != strcmp (text_file, "-"))
779       fp = fopen (text_file, "r");
780     else
781       fp = stdin;
782 
783     if (!fp)
784       fail (false, "Failed opening text file `%s': %s",
785 	    text_file, strerror (errno));
786 
787     gs = g_string_new (nullptr);
788   }
789 
790   g_string_set_size (gs, 0);
791   char buf[BUFSIZ];
792   while (fgets (buf, sizeof (buf), fp)) {
793     unsigned int bytes = strlen (buf);
794     if (bytes && buf[bytes - 1] == '\n') {
795       bytes--;
796       g_string_append_len (gs, buf, bytes);
797       break;
798     }
799       g_string_append_len (gs, buf, bytes);
800   }
801   if (ferror (fp))
802     fail (false, "Failed reading text: %s",
803 	  strerror (errno));
804   *len = gs->len;
805   return !*len && feof (fp) ? nullptr : gs->str;
806 }
807 
808 
809 FILE *
get_file_handle()810 output_options_t::get_file_handle ()
811 {
812   if (fp)
813     return fp;
814 
815   if (output_file)
816     fp = fopen (output_file, "wb");
817   else {
818 #if defined(_WIN32) || defined(__CYGWIN__)
819     setmode (fileno (stdout), O_BINARY);
820 #endif
821     fp = stdout;
822   }
823   if (!fp)
824     fail (false, "Cannot open output file `%s': %s",
825 	  g_filename_display_name (output_file), strerror (errno));
826 
827   return fp;
828 }
829 
830 static gboolean
parse_verbose(const char * name G_GNUC_UNUSED,const char * arg G_GNUC_UNUSED,gpointer data G_GNUC_UNUSED,GError ** error G_GNUC_UNUSED)831 parse_verbose (const char *name G_GNUC_UNUSED,
832 	       const char *arg G_GNUC_UNUSED,
833 	       gpointer    data G_GNUC_UNUSED,
834 	       GError    **error G_GNUC_UNUSED)
835 {
836   format_options_t *format_opts = (format_options_t *) data;
837   format_opts->show_text = format_opts->show_unicode = format_opts->show_line_num = true;
838   return true;
839 }
840 
841 static gboolean
parse_ned(const char * name G_GNUC_UNUSED,const char * arg G_GNUC_UNUSED,gpointer data G_GNUC_UNUSED,GError ** error G_GNUC_UNUSED)842 parse_ned (const char *name G_GNUC_UNUSED,
843 	   const char *arg G_GNUC_UNUSED,
844 	   gpointer    data G_GNUC_UNUSED,
845 	   GError    **error G_GNUC_UNUSED)
846 {
847   format_options_t *format_opts = (format_options_t *) data;
848   format_opts->show_clusters = format_opts->show_advances = false;
849   return true;
850 }
851 
852 void
add_options(option_parser_t * parser)853 format_options_t::add_options (option_parser_t *parser)
854 {
855   GOptionEntry entries[] =
856   {
857     {"show-text",	0, 0, G_OPTION_ARG_NONE,	&this->show_text,		"Prefix each line of output with its corresponding input text",		nullptr},
858     {"show-unicode",	0, 0, G_OPTION_ARG_NONE,	&this->show_unicode,		"Prefix each line of output with its corresponding input codepoint(s)",	nullptr},
859     {"show-line-num",	0, 0, G_OPTION_ARG_NONE,	&this->show_line_num,		"Prefix each line of output with its corresponding input line number",	nullptr},
860     {"verbose",	      'v', G_OPTION_FLAG_NO_ARG,
861 			      G_OPTION_ARG_CALLBACK,	(gpointer) &parse_verbose,	"Prefix each line of output with all of the above",			nullptr},
862     {"no-glyph-names",	0, G_OPTION_FLAG_REVERSE,
863 			      G_OPTION_ARG_NONE,	&this->show_glyph_names,	"Output glyph indices instead of names",				nullptr},
864     {"no-positions",	0, G_OPTION_FLAG_REVERSE,
865 			      G_OPTION_ARG_NONE,	&this->show_positions,		"Do not output glyph positions",					nullptr},
866     {"no-advances",	0, G_OPTION_FLAG_REVERSE,
867 			      G_OPTION_ARG_NONE,	&this->show_advances,		"Do not output glyph advances",						nullptr},
868     {"no-clusters",	0, G_OPTION_FLAG_REVERSE,
869 			      G_OPTION_ARG_NONE,	&this->show_clusters,		"Do not output cluster indices",					nullptr},
870     {"show-extents",	0, 0, G_OPTION_ARG_NONE,	&this->show_extents,		"Output glyph extents",							nullptr},
871     {"show-flags",	0, 0, G_OPTION_ARG_NONE,	&this->show_flags,		"Output glyph flags",							nullptr},
872     {"ned",	      'v', G_OPTION_FLAG_NO_ARG,
873 			      G_OPTION_ARG_CALLBACK,	(gpointer) &parse_ned,		"No Extra Data; Do not output clusters or advances",			nullptr},
874     {"trace",	      'V', 0, G_OPTION_ARG_NONE,	&this->trace,			"Output interim shaping results",					nullptr},
875     {nullptr}
876   };
877   parser->add_group (entries,
878 		     "output-syntax",
879 		     "Output syntax:\n"
880 	 "    text: [<glyph name or index>=<glyph cluster index within input>@<horizontal displacement>,<vertical displacement>+<horizontal advance>,<vertical advance>|...]\n"
881 	 "    json: [{\"g\": <glyph name or index>, \"ax\": <horizontal advance>, \"ay\": <vertical advance>, \"dx\": <horizontal displacement>, \"dy\": <vertical displacement>, \"cl\": <glyph cluster index within input>}, ...]\n"
882 	 "\nOutput syntax options:",
883 		     "Options for the syntax of the output",
884 		     this);
885 }
886 
887 void
serialize_unicode(hb_buffer_t * buffer,GString * gs)888 format_options_t::serialize_unicode (hb_buffer_t *buffer,
889 				     GString     *gs)
890 {
891   unsigned int num_glyphs = hb_buffer_get_length (buffer);
892   hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, nullptr);
893 
894   g_string_append_c (gs, '<');
895   for (unsigned int i = 0; i < num_glyphs; i++)
896   {
897     if (i)
898       g_string_append_c (gs, ',');
899     g_string_append_printf (gs, "U+%04X", info->codepoint);
900     info++;
901   }
902   g_string_append_c (gs, '>');
903 }
904 
905 void
serialize_glyphs(hb_buffer_t * buffer,hb_font_t * font,hb_buffer_serialize_format_t output_format,hb_buffer_serialize_flags_t flags,GString * gs)906 format_options_t::serialize_glyphs (hb_buffer_t *buffer,
907 				    hb_font_t   *font,
908 				    hb_buffer_serialize_format_t output_format,
909 				    hb_buffer_serialize_flags_t flags,
910 				    GString     *gs)
911 {
912   g_string_append_c (gs, '[');
913   unsigned int num_glyphs = hb_buffer_get_length (buffer);
914   unsigned int start = 0;
915 
916   while (start < num_glyphs)
917   {
918     char buf[1024];
919     unsigned int consumed;
920     start += hb_buffer_serialize_glyphs (buffer, start, num_glyphs,
921 					 buf, sizeof (buf), &consumed,
922 					 font, output_format, flags);
923     if (!consumed)
924       break;
925     g_string_append (gs, buf);
926   }
927   g_string_append_c (gs, ']');
928 }
929 void
serialize_line_no(unsigned int line_no,GString * gs)930 format_options_t::serialize_line_no (unsigned int  line_no,
931 				     GString      *gs)
932 {
933   if (show_line_num)
934     g_string_append_printf (gs, "%d: ", line_no);
935 }
936 void
serialize_buffer_of_text(hb_buffer_t * buffer,unsigned int line_no,const char * text,unsigned int text_len,hb_font_t * font,GString * gs)937 format_options_t::serialize_buffer_of_text (hb_buffer_t  *buffer,
938 					    unsigned int  line_no,
939 					    const char   *text,
940 					    unsigned int  text_len,
941 					    hb_font_t    *font,
942 					    GString      *gs)
943 {
944   if (show_text)
945   {
946     serialize_line_no (line_no, gs);
947     g_string_append_c (gs, '(');
948     g_string_append_len (gs, text, text_len);
949     g_string_append_c (gs, ')');
950     g_string_append_c (gs, '\n');
951   }
952 
953   if (show_unicode)
954   {
955     serialize_line_no (line_no, gs);
956     serialize_unicode (buffer, gs);
957     g_string_append_c (gs, '\n');
958   }
959 }
960 void
serialize_message(unsigned int line_no,const char * type,const char * msg,GString * gs)961 format_options_t::serialize_message (unsigned int  line_no,
962 				     const char   *type,
963 				     const char   *msg,
964 				     GString      *gs)
965 {
966   serialize_line_no (line_no, gs);
967   g_string_append_printf (gs, "%s: %s", type, msg);
968   g_string_append_c (gs, '\n');
969 }
970 void
serialize_buffer_of_glyphs(hb_buffer_t * buffer,unsigned int line_no,const char * text,unsigned int text_len,hb_font_t * font,hb_buffer_serialize_format_t output_format,hb_buffer_serialize_flags_t format_flags,GString * gs)971 format_options_t::serialize_buffer_of_glyphs (hb_buffer_t  *buffer,
972 					      unsigned int  line_no,
973 					      const char   *text,
974 					      unsigned int  text_len,
975 					      hb_font_t    *font,
976 					      hb_buffer_serialize_format_t output_format,
977 					      hb_buffer_serialize_flags_t format_flags,
978 					      GString      *gs)
979 {
980   serialize_line_no (line_no, gs);
981   serialize_glyphs (buffer, font, output_format, format_flags, gs);
982   g_string_append_c (gs, '\n');
983 }
984