options.cc revision 11e51993ab562d4c7460eb7c43d0e97404e628e7
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#ifdef 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 //g_error_free (parse_error); 127 } else 128 fail (TRUE, "Option parse error"); 129 } 130} 131 132 133static gboolean 134parse_margin (const char *name G_GNUC_UNUSED, 135 const char *arg, 136 gpointer data, 137 GError **error G_GNUC_UNUSED) 138{ 139 view_options_t *view_opts = (view_options_t *) data; 140 view_options_t::margin_t &m = view_opts->margin; 141 switch (sscanf (arg, "%lf %lf %lf %lf", &m.t, &m.r, &m.b, &m.l)) { 142 case 1: m.r = m.t; 143 case 2: m.b = m.t; 144 case 3: m.l = m.r; 145 case 4: return TRUE; 146 default: 147 g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, 148 "%s argument should be one to four space-separated numbers", 149 name); 150 return FALSE; 151 } 152} 153 154 155static gboolean 156parse_shapers (const char *name G_GNUC_UNUSED, 157 const char *arg, 158 gpointer data, 159 GError **error G_GNUC_UNUSED) 160{ 161 shape_options_t *shape_opts = (shape_options_t *) data; 162 shape_opts->shapers = g_strsplit (arg, ",", 0); 163 return TRUE; 164} 165 166 167static void 168parse_space (char **pp) 169{ 170 char c; 171#define ISSPACE(c) ((c)==' '||(c)=='\f'||(c)=='\n'||(c)=='\r'||(c)=='\t'||(c)=='\v') 172 while (c = **pp, ISSPACE (c)) 173 (*pp)++; 174#undef ISSPACE 175} 176 177static hb_bool_t 178parse_char (char **pp, char c) 179{ 180 parse_space (pp); 181 182 if (**pp != c) 183 return FALSE; 184 185 (*pp)++; 186 return TRUE; 187} 188 189static hb_bool_t 190parse_uint (char **pp, unsigned int *pv) 191{ 192 char *p = *pp; 193 unsigned int v; 194 195 v = strtol (p, pp, 0); 196 197 if (p == *pp) 198 return FALSE; 199 200 *pv = v; 201 return TRUE; 202} 203 204 205static hb_bool_t 206parse_feature_value_prefix (char **pp, hb_feature_t *feature) 207{ 208 if (parse_char (pp, '-')) 209 feature->value = 0; 210 else { 211 parse_char (pp, '+'); 212 feature->value = 1; 213 } 214 215 return TRUE; 216} 217 218static hb_bool_t 219parse_feature_tag (char **pp, hb_feature_t *feature) 220{ 221 char *p = *pp, c; 222 223 parse_space (pp); 224 225#define ISALNUM(c) (('a' <= (c) && (c) <= 'z') || ('A' <= (c) && (c) <= 'Z') || ('0' <= (c) && (c) <= '9')) 226 while (c = **pp, ISALNUM(c)) 227 (*pp)++; 228#undef ISALNUM 229 230 if (p == *pp) 231 return FALSE; 232 233 feature->tag = hb_tag_from_string (p, *pp - p); 234 return TRUE; 235} 236 237static hb_bool_t 238parse_feature_indices (char **pp, hb_feature_t *feature) 239{ 240 hb_bool_t has_start; 241 242 feature->start = 0; 243 feature->end = (unsigned int) -1; 244 245 if (!parse_char (pp, '[')) 246 return TRUE; 247 248 has_start = parse_uint (pp, &feature->start); 249 250 if (parse_char (pp, ':')) { 251 parse_uint (pp, &feature->end); 252 } else { 253 if (has_start) 254 feature->end = feature->start + 1; 255 } 256 257 return parse_char (pp, ']'); 258} 259 260static hb_bool_t 261parse_feature_value_postfix (char **pp, hb_feature_t *feature) 262{ 263 return !parse_char (pp, '=') || parse_uint (pp, &feature->value); 264} 265 266 267static hb_bool_t 268parse_one_feature (char **pp, hb_feature_t *feature) 269{ 270 return parse_feature_value_prefix (pp, feature) && 271 parse_feature_tag (pp, feature) && 272 parse_feature_indices (pp, feature) && 273 parse_feature_value_postfix (pp, feature) && 274 (parse_char (pp, ',') || **pp == '\0'); 275} 276 277static void 278skip_one_feature (char **pp) 279{ 280 char *e; 281 e = strchr (*pp, ','); 282 if (e) 283 *pp = e + 1; 284 else 285 *pp = *pp + strlen (*pp); 286} 287 288static gboolean 289parse_features (const char *name G_GNUC_UNUSED, 290 const char *arg, 291 gpointer data, 292 GError **error G_GNUC_UNUSED) 293{ 294 shape_options_t *shape_opts = (shape_options_t *) data; 295 char *s = (char *) arg; 296 char *p; 297 298 shape_opts->num_features = 0; 299 shape_opts->features = NULL; 300 301 if (!*s) 302 return TRUE; 303 304 /* count the features first, so we can allocate memory */ 305 p = s; 306 do { 307 shape_opts->num_features++; 308 p = strchr (p, ','); 309 if (p) 310 p++; 311 } while (p); 312 313 shape_opts->features = (hb_feature_t *) calloc (shape_opts->num_features, sizeof (*shape_opts->features)); 314 315 /* now do the actual parsing */ 316 p = s; 317 shape_opts->num_features = 0; 318 while (*p) { 319 if (parse_one_feature (&p, &shape_opts->features[shape_opts->num_features])) 320 shape_opts->num_features++; 321 else 322 skip_one_feature (&p); 323 } 324 325 return TRUE; 326} 327 328 329void 330view_options_t::add_options (option_parser_t *parser) 331{ 332 GOptionEntry entries[] = 333 { 334 {"annotate", 0, 0, G_OPTION_ARG_NONE, &this->annotate, "Annotate output rendering", NULL}, 335 {"background", 0, 0, G_OPTION_ARG_STRING, &this->back, "Set background color (default: "DEFAULT_BACK")", "red/#rrggbb/#rrggbbaa"}, 336 {"foreground", 0, 0, G_OPTION_ARG_STRING, &this->fore, "Set foreground color (default: "DEFAULT_FORE")", "red/#rrggbb/#rrggbbaa"}, 337 {"line-space", 0, 0, G_OPTION_ARG_DOUBLE, &this->line_space, "Set space between lines (default: 0)", "units"}, 338 {"margin", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_margin, "Margin around output (default: "G_STRINGIFY(DEFAULT_MARGIN)")","one to four numbers"}, 339 {"font-size", 0, 0, G_OPTION_ARG_DOUBLE, &this->font_size, "Font size (default: "G_STRINGIFY(DEFAULT_FONT_SIZE)")","size"}, 340 {NULL} 341 }; 342 parser->add_group (entries, 343 "view", 344 "View options:", 345 "Options controlling the output rendering", 346 this); 347} 348 349void 350shape_options_t::add_options (option_parser_t *parser) 351{ 352 GOptionEntry entries[] = 353 { 354 {"shapers", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_shapers, "Comma-separated list of shapers", "list"}, 355 {"direction", 0, 0, G_OPTION_ARG_STRING, &this->direction, "Set text direction (default: auto)", "ltr/rtl/ttb/btt"}, 356 {"language", 0, 0, G_OPTION_ARG_STRING, &this->language, "Set text language (default: $LANG)", "langstr"}, 357 {"script", 0, 0, G_OPTION_ARG_STRING, &this->script, "Set text script (default: auto)", "ISO-15924 tag"}, 358 {"features", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_features, "Font features to apply to text", "TODO"}, 359 {NULL} 360 }; 361 parser->add_group (entries, 362 "shape", 363 "Shape options:", 364 "Options controlling the shaping process", 365 this); 366} 367 368void 369font_options_t::add_options (option_parser_t *parser) 370{ 371 GOptionEntry entries[] = 372 { 373 {"font-file", 0, 0, G_OPTION_ARG_STRING, &this->font_file, "Font file-name", "filename"}, 374 {"face-index", 0, 0, G_OPTION_ARG_INT, &this->face_index, "Face index (default: 0)", "index"}, 375 {NULL} 376 }; 377 parser->add_group (entries, 378 "font", 379 "Font options:", 380 "Options controlling the font", 381 this); 382} 383 384void 385text_options_t::add_options (option_parser_t *parser) 386{ 387 GOptionEntry entries[] = 388 { 389 {"text", 0, 0, G_OPTION_ARG_STRING, &this->text, "Set input text", "string"}, 390 {"text-file", 0, 0, G_OPTION_ARG_STRING, &this->text_file, "Set input text file-name", "filename"}, 391 {NULL} 392 }; 393 parser->add_group (entries, 394 "text", 395 "Text options:", 396 "Options controlling the input text", 397 this); 398} 399 400void 401output_options_t::add_options (option_parser_t *parser) 402{ 403 GOptionEntry entries[] = 404 { 405 {"output", 0, 0, G_OPTION_ARG_STRING, &this->output_file, "Set output file-name (default: stdout)","filename"}, 406 {"format", 0, 0, G_OPTION_ARG_STRING, &this->output_format, "Set output format", "format"}, 407 {NULL} 408 }; 409 parser->add_group (entries, 410 "output", 411 "Output options:", 412 "Options controlling the output", 413 this); 414} 415 416 417 418hb_font_t * 419font_options_t::get_font (void) const 420{ 421 if (font) 422 return font; 423 424 hb_blob_t *blob = NULL; 425 426 /* Create the blob */ 427 { 428 char *font_data; 429 unsigned int len = 0; 430 hb_destroy_func_t destroy; 431 void *user_data; 432 hb_memory_mode_t mm; 433 434 /* This is a hell of a lot of code for just reading a file! */ 435 if (!font_file) 436 fail (TRUE, "No font file set"); 437 438 if (0 == strcmp (font_file, "-")) { 439 /* read it */ 440 GString *gs = g_string_new (NULL); 441 char buf[BUFSIZ]; 442#ifdef HAVE__SETMODE 443 _setmode (fileno (stdin), _O_BINARY); 444#endif 445 while (!feof (stdin)) { 446 size_t ret = fread (buf, 1, sizeof (buf), stdin); 447 if (ferror (stdin)) 448 fail (FALSE, "Failed reading font from standard input: %s", 449 strerror (errno)); 450 g_string_append_len (gs, buf, ret); 451 } 452 len = gs->len; 453 font_data = g_string_free (gs, FALSE); 454 user_data = font_data; 455 destroy = (hb_destroy_func_t) g_free; 456 mm = HB_MEMORY_MODE_WRITABLE; 457 } else { 458 GMappedFile *mf = g_mapped_file_new (font_file, FALSE, NULL); 459 if (mf) { 460 font_data = g_mapped_file_get_contents (mf); 461 len = g_mapped_file_get_length (mf); 462 if (len) { 463 destroy = (hb_destroy_func_t) g_mapped_file_unref; 464 user_data = (void *) mf; 465 mm = HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITABLE; 466 } else 467 g_mapped_file_unref (mf); 468 } 469 if (!len) { 470 /* GMappedFile is buggy, it doesn't fail if file isn't regular. 471 * Try reading. 472 * https://bugzilla.gnome.org/show_bug.cgi?id=659212 */ 473 GError *error = NULL; 474 gsize l; 475 if (g_file_get_contents (font_file, &font_data, &l, &error)) { 476 len = l; 477 destroy = (hb_destroy_func_t) g_free; 478 user_data = (void *) font_data; 479 mm = HB_MEMORY_MODE_WRITABLE; 480 } else { 481 fail (FALSE, "%s", error->message); 482 //g_error_free (error); 483 } 484 } 485 } 486 487 blob = hb_blob_create (font_data, len, mm, user_data, destroy); 488 } 489 490 /* Create the face */ 491 hb_face_t *face = hb_face_create (blob, face_index); 492 hb_blob_destroy (blob); 493 494 495 font = hb_font_create (face); 496 497 unsigned int upem = hb_face_get_upem (face); 498 hb_font_set_scale (font, upem, upem); 499 hb_face_destroy (face); 500 501#ifdef HAVE_FREETYPE 502 hb_ft_font_set_funcs (font); 503#endif 504 505 return font; 506} 507 508 509const char * 510text_options_t::get_line (unsigned int *len) 511{ 512 if (text) { 513 if (text_len == (unsigned int) -1) 514 text_len = strlen (text); 515 516 if (!text_len) { 517 *len = 0; 518 return NULL; 519 } 520 521 const char *ret = text; 522 const char *p = (const char *) memchr (text, '\n', text_len); 523 unsigned int ret_len; 524 if (!p) { 525 ret_len = text_len; 526 text += ret_len; 527 text_len = 0; 528 } else { 529 ret_len = p - ret; 530 text += ret_len + 1; 531 text_len -= ret_len + 1; 532 } 533 534 *len = ret_len; 535 return ret; 536 } 537 538 if (!fp) { 539 if (!text_file) 540 fail (TRUE, "At least one of text or text-file must be set"); 541 542 if (0 != strcmp (text_file, "-")) 543 fp = fopen (text_file, "r"); 544 else 545 fp = stdin; 546 547 if (!fp) 548 fail (FALSE, "Failed opening text file `%s': %s", 549 text_file, strerror (errno)); 550 551 gs = g_string_new (NULL); 552 } 553 554 g_string_set_size (gs, 0); 555 char buf[BUFSIZ]; 556 while (fgets (buf, sizeof (buf), fp)) { 557 unsigned int bytes = strlen (buf); 558 if (buf[bytes - 1] == '\n') { 559 bytes--; 560 g_string_append_len (gs, buf, bytes); 561 break; 562 } 563 g_string_append_len (gs, buf, bytes); 564 } 565 if (ferror (fp)) 566 fail (FALSE, "Failed reading text: %s", 567 strerror (errno)); 568 *len = gs->len; 569 return !*len && feof (fp) ? NULL : gs->str; 570} 571 572 573FILE * 574output_options_t::get_file_handle (void) 575{ 576 if (fp) 577 return fp; 578 579 if (output_file) 580 fp = fopen (output_file, "wb"); 581 else { 582#ifdef HAVE__SETMODE 583 _setmode (fileno (stdout), _O_BINARY); 584#endif 585 fp = stdout; 586 } 587 if (!fp) 588 fail (FALSE, "Cannot open output file `%s': %s", 589 g_filename_display_name (output_file), strerror (errno)); 590 591 return fp; 592} 593