options.cc revision 639b5957d9c7b6d8bef6784e3467ccc055ddeea4
1/*
2 * Copyright © 2011  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#if HAVE_FREETYPE
30#include <hb-ft.h>
31#endif
32
33
34bool debug = FALSE;
35
36static gchar *
37shapers_to_string (void)
38{
39  GString *shapers = g_string_new (NULL);
40  const char **shaper_list = hb_shape_list_shapers ();
41
42  for (; *shaper_list; shaper_list++) {
43    g_string_append (shapers, *shaper_list);
44    g_string_append_c (shapers, ',');
45  }
46  g_string_truncate (shapers, MAX (0, (gint)shapers->len - 1));
47
48  return g_string_free (shapers, FALSE);
49}
50
51static G_GNUC_NORETURN gboolean
52show_version (const char *name G_GNUC_UNUSED,
53	      const char *arg G_GNUC_UNUSED,
54	      gpointer    data G_GNUC_UNUSED,
55	      GError    **error G_GNUC_UNUSED)
56{
57  g_printf ("%s (%s) %s\n", g_get_prgname (), PACKAGE_NAME, PACKAGE_VERSION);
58
59  char *shapers = shapers_to_string ();
60  g_printf ("Available shapers: %s\n", shapers);
61  g_free (shapers);
62  if (strcmp (HB_VERSION_STRING, hb_version_string ()))
63    g_printf ("Linked HarfBuzz library has a different version: %s\n", hb_version_string ());
64
65  exit(0);
66}
67
68
69void
70option_parser_t::add_main_options (void)
71{
72  GOptionEntry entries[] =
73  {
74    {"version",		0, G_OPTION_FLAG_NO_ARG,
75			      G_OPTION_ARG_CALLBACK,	(gpointer) &show_version,	"Show version numbers",			NULL},
76    {"debug",		0, 0, G_OPTION_ARG_NONE,	&debug,				"Free all resources before exit",	NULL},
77    {NULL}
78  };
79  g_option_context_add_main_entries (context, entries, NULL);
80}
81
82static gboolean
83pre_parse (GOptionContext *context G_GNUC_UNUSED,
84	   GOptionGroup *group G_GNUC_UNUSED,
85	   gpointer data,
86	   GError **error)
87{
88  option_group_t *option_group = (option_group_t *) data;
89  option_group->pre_parse (error);
90  return *error == NULL;
91}
92
93static gboolean
94post_parse (GOptionContext *context G_GNUC_UNUSED,
95	    GOptionGroup *group G_GNUC_UNUSED,
96	    gpointer data,
97	    GError **error)
98{
99  option_group_t *option_group = static_cast<option_group_t *>(data);
100  option_group->post_parse (error);
101  return *error == NULL;
102}
103
104void
105option_parser_t::add_group (GOptionEntry   *entries,
106			    const gchar    *name,
107			    const gchar    *description,
108			    const gchar    *help_description,
109			    option_group_t *option_group)
110{
111  GOptionGroup *group = g_option_group_new (name, description, help_description,
112					    static_cast<gpointer>(option_group), NULL);
113  g_option_group_add_entries (group, entries);
114  g_option_group_set_parse_hooks (group, pre_parse, post_parse);
115  g_option_context_add_group (context, group);
116}
117
118void
119option_parser_t::parse (int *argc, char ***argv)
120{
121  GError *parse_error = NULL;
122  if (!g_option_context_parse (context, argc, argv, &parse_error))
123  {
124    if (parse_error != NULL)
125      fail (TRUE, "%s", parse_error->message);
126    else
127      fail (TRUE, "Option parse error");
128  }
129}
130
131
132static gboolean
133parse_margin (const char *name G_GNUC_UNUSED,
134	      const char *arg,
135	      gpointer    data,
136	      GError    **error G_GNUC_UNUSED)
137{
138  view_options_t *view_opts = (view_options_t *) data;
139  view_options_t::margin_t &m = view_opts->margin;
140  switch (sscanf (arg, "%lf %lf %lf %lf", &m.t, &m.r, &m.b, &m.l)) {
141    case 1: m.r = m.t;
142    case 2: m.b = m.t;
143    case 3: m.l = m.r;
144    case 4: return TRUE;
145    default:
146      g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
147		   "%s argument should be one to four space-separated numbers",
148		   name);
149      return FALSE;
150  }
151}
152
153
154static gboolean
155parse_shapers (const char *name G_GNUC_UNUSED,
156	       const char *arg,
157	       gpointer    data,
158	       GError    **error G_GNUC_UNUSED)
159{
160  shape_options_t *shape_opts = (shape_options_t *) data;
161  shape_opts->shapers = g_strsplit (arg, ",", 0);
162  return TRUE;
163}
164
165
166static void
167parse_space (char **pp)
168{
169  char c;
170#define ISSPACE(c) ((c)==' '||(c)=='\f'||(c)=='\n'||(c)=='\r'||(c)=='\t'||(c)=='\v')
171  while (c = **pp, ISSPACE (c))
172    (*pp)++;
173#undef ISSPACE
174}
175
176static hb_bool_t
177parse_char (char **pp, char c)
178{
179  parse_space (pp);
180
181  if (**pp != c)
182    return FALSE;
183
184  (*pp)++;
185  return TRUE;
186}
187
188static hb_bool_t
189parse_uint (char **pp, unsigned int *pv)
190{
191  char *p = *pp;
192  unsigned int v;
193
194  v = strtol (p, pp, 0);
195
196  if (p == *pp)
197    return FALSE;
198
199  *pv = v;
200  return TRUE;
201}
202
203
204static hb_bool_t
205parse_feature_value_prefix (char **pp, hb_feature_t *feature)
206{
207  if (parse_char (pp, '-'))
208    feature->value = 0;
209  else {
210    parse_char (pp, '+');
211    feature->value = 1;
212  }
213
214  return TRUE;
215}
216
217static hb_bool_t
218parse_feature_tag (char **pp, hb_feature_t *feature)
219{
220  char *p = *pp, c;
221
222  parse_space (pp);
223
224#define ISALNUM(c) (('a' <= (c) && (c) <= 'z') || ('A' <= (c) && (c) <= 'Z') || ('0' <= (c) && (c) <= '9'))
225  while (c = **pp, ISALNUM(c))
226    (*pp)++;
227#undef ISALNUM
228
229  if (p == *pp)
230    return FALSE;
231
232  feature->tag = hb_tag_from_string (p, *pp - p);
233  return TRUE;
234}
235
236static hb_bool_t
237parse_feature_indices (char **pp, hb_feature_t *feature)
238{
239  hb_bool_t has_start;
240
241  feature->start = 0;
242  feature->end = (unsigned int) -1;
243
244  if (!parse_char (pp, '['))
245    return TRUE;
246
247  has_start = parse_uint (pp, &feature->start);
248
249  if (parse_char (pp, ':')) {
250    parse_uint (pp, &feature->end);
251  } else {
252    if (has_start)
253      feature->end = feature->start + 1;
254  }
255
256  return parse_char (pp, ']');
257}
258
259static hb_bool_t
260parse_feature_value_postfix (char **pp, hb_feature_t *feature)
261{
262  return !parse_char (pp, '=') || parse_uint (pp, &feature->value);
263}
264
265
266static hb_bool_t
267parse_one_feature (char **pp, hb_feature_t *feature)
268{
269  return parse_feature_value_prefix (pp, feature) &&
270	 parse_feature_tag (pp, feature) &&
271	 parse_feature_indices (pp, feature) &&
272	 parse_feature_value_postfix (pp, feature) &&
273	 (parse_char (pp, ',') || **pp == '\0');
274}
275
276static void
277skip_one_feature (char **pp)
278{
279  char *e;
280  e = strchr (*pp, ',');
281  if (e)
282    *pp = e + 1;
283  else
284    *pp = *pp + strlen (*pp);
285}
286
287static gboolean
288parse_features (const char *name G_GNUC_UNUSED,
289	        const char *arg,
290	        gpointer    data,
291	        GError    **error G_GNUC_UNUSED)
292{
293  shape_options_t *shape_opts = (shape_options_t *) data;
294  char *s = (char *) arg;
295  char *p;
296
297  shape_opts->num_features = 0;
298  shape_opts->features = NULL;
299
300  if (!*s)
301    return TRUE;
302
303  /* count the features first, so we can allocate memory */
304  p = s;
305  do {
306    shape_opts->num_features++;
307    p = strchr (p, ',');
308    if (p)
309      p++;
310  } while (p);
311
312  shape_opts->features = (hb_feature_t *) calloc (shape_opts->num_features, sizeof (*shape_opts->features));
313
314  /* now do the actual parsing */
315  p = s;
316  shape_opts->num_features = 0;
317  while (*p) {
318    if (parse_one_feature (&p, &shape_opts->features[shape_opts->num_features]))
319      shape_opts->num_features++;
320    else
321      skip_one_feature (&p);
322  }
323
324  return TRUE;
325}
326
327
328void
329view_options_t::add_options (option_parser_t *parser)
330{
331  GOptionEntry entries[] =
332  {
333    {"annotate",	0, 0, G_OPTION_ARG_NONE,	&this->annotate,		"Annotate output rendering",				NULL},
334    {"background",	0, 0, G_OPTION_ARG_STRING,	&this->back,			"Set background color (default: "DEFAULT_BACK")",	"red/#rrggbb/#rrggbbaa"},
335    {"foreground",	0, 0, G_OPTION_ARG_STRING,	&this->fore,			"Set foreground color (default: "DEFAULT_FORE")",	"red/#rrggbb/#rrggbbaa"},
336    {"line-space",	0, 0, G_OPTION_ARG_DOUBLE,	&this->line_space,		"Set space between lines (default: 0)",			"units"},
337    {"margin",		0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_margin,	"Margin around output (default: "G_STRINGIFY(DEFAULT_MARGIN)")","one to four numbers"},
338    {NULL}
339  };
340  parser->add_group (entries,
341		     "view",
342		     "View options:",
343		     "Options controlling the output rendering",
344		     this);
345}
346
347void
348shape_options_t::add_options (option_parser_t *parser)
349{
350  GOptionEntry entries[] =
351  {
352    {"shapers",		0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_shapers,	"Comma-separated list of shapers",	"list"},
353    {"direction",	0, 0, G_OPTION_ARG_STRING,	&this->direction,		"Set text direction (default: auto)",	"ltr/rtl/ttb/btt"},
354    {"language",	0, 0, G_OPTION_ARG_STRING,	&this->language,		"Set text language (default: $LANG)",	"langstr"},
355    {"script",		0, 0, G_OPTION_ARG_STRING,	&this->script,			"Set text script (default: auto)",	"ISO-15924 tag"},
356    {"features",	0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_features,	"Font features to apply to text",	"TODO"},
357    {NULL}
358  };
359  parser->add_group (entries,
360		     "shape",
361		     "Shape options:",
362		     "Options controlling the shaping process",
363		     this);
364}
365
366void
367font_options_t::add_options (option_parser_t *parser)
368{
369  GOptionEntry entries[] =
370  {
371    {"font-file",	0, 0, G_OPTION_ARG_STRING,	&this->font_file,		"Font file-name",					"filename"},
372    {"face-index",	0, 0, G_OPTION_ARG_INT,		&this->face_index,		"Face index (default: 0)",                              "index"},
373    {"font-size",	0, 0, G_OPTION_ARG_DOUBLE,	&this->font_size,		"Font size (default: "G_STRINGIFY(DEFAULT_FONT_SIZE)")","size"},
374    {NULL}
375  };
376  parser->add_group (entries,
377		     "font",
378		     "Font options:",
379		     "Options controlling the font",
380		     this);
381}
382
383void
384text_options_t::add_options (option_parser_t *parser)
385{
386  GOptionEntry entries[] =
387  {
388    {"text",		0, 0, G_OPTION_ARG_STRING,	&this->text,			"Set input text",			"string"},
389    {"text-file",	0, 0, G_OPTION_ARG_STRING,	&this->text_file,		"Set input text file-name",		"filename"},
390    {NULL}
391  };
392  parser->add_group (entries,
393		     "text",
394		     "Text options:",
395		     "Options controlling the input text",
396		     this);
397}
398
399void
400output_options_t::add_options (option_parser_t *parser)
401{
402  GOptionEntry entries[] =
403  {
404    {"output",		0, 0, G_OPTION_ARG_STRING,	&this->output_file,		"Set output file-name (default: stdout)","filename"},
405    {"format",		0, 0, G_OPTION_ARG_STRING,	&this->output_format,		"Set output format",			"format"},
406    {NULL}
407  };
408  parser->add_group (entries,
409		     "output",
410		     "Output options:",
411		     "Options controlling the output",
412		     this);
413}
414
415
416
417hb_font_t *
418font_options_t::get_font (void) const
419{
420  if (font)
421    return font;
422
423  hb_blob_t *blob = NULL;
424
425  /* Create the blob */
426  {
427    const char *font_data;
428    unsigned int len;
429    hb_destroy_func_t destroy;
430    void *user_data;
431    hb_memory_mode_t mm;
432
433    if (!font_file)
434      fail (TRUE, "No font file set");
435
436    GMappedFile *mf = g_mapped_file_new (font_file, FALSE, NULL);
437    if (!mf)
438      fail (FALSE, "Failed opening font file `%s'", g_filename_display_name (font_file));
439    font_data = g_mapped_file_get_contents (mf);
440    len = g_mapped_file_get_length (mf);
441    destroy = (hb_destroy_func_t) g_mapped_file_unref;
442    user_data = (void *) mf;
443    mm = HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITABLE;
444
445    blob = hb_blob_create (font_data, len, mm, user_data, destroy);
446  }
447
448  /* Create the face */
449  hb_face_t *face = hb_face_create (blob, face_index);
450  hb_blob_destroy (blob);
451
452
453  font = hb_font_create (face);
454
455  unsigned int upem = hb_face_get_upem (face);
456  hb_font_set_scale (font, font_size * upem, font_size * upem);
457  hb_face_destroy (face);
458
459#if HAVE_FREETYPE
460  hb_ft_font_set_funcs (font);
461#endif
462
463  return font;
464}
465
466
467const char *
468text_options_t::get_line (unsigned int *len)
469{
470  if (!text) {
471    if (!text_file)
472      fail (TRUE, "At least one of text or text-file must be set");
473
474    GMappedFile *mf = g_mapped_file_new (text_file, FALSE, NULL);
475    if (!mf)
476      fail (FALSE, "Failed opening text file `%s'", g_filename_display_name (text_file));
477    text = g_mapped_file_get_contents (mf);
478    text_len = g_mapped_file_get_length (mf);
479    printf ("%d\n", text_len);
480  }
481
482  if (text_len == (unsigned int) -1)
483    text_len = strlen (text);
484
485  if (!text_len) {
486    *len = 0;
487    return NULL;
488  }
489
490  const char *ret = text;
491  const char *p = (const char *) memchr (text, '\n', text_len);
492  unsigned int ret_len;
493  if (!p) {
494    ret_len = text_len;
495    text += ret_len;
496    text_len = 0;
497  } else {
498    ret_len = p - ret;
499    text += ret_len + 1;
500    text_len -= ret_len + 1;
501  }
502
503  *len = ret_len;
504  return ret;
505}
506