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