| /* | |
| * Copyright © 2011 Google, Inc. | |
| * | |
| * This is part of HarfBuzz, a text shaping library. | |
| * | |
| * Permission is hereby granted, without written agreement and without | |
| * license or royalty fees, to use, copy, modify, and distribute this | |
| * software and its documentation for any purpose, provided that the | |
| * above copyright notice and the following two paragraphs appear in | |
| * all copies of this software. | |
| * | |
| * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR | |
| * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES | |
| * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN | |
| * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH | |
| * DAMAGE. | |
| * | |
| * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, | |
| * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND | |
| * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS | |
| * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO | |
| * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. | |
| * | |
| * Google Author(s): Behdad Esfahbod | |
| */ | |
| #ifndef TEXT_OPTIONS_HH | |
| #define TEXT_OPTIONS_HH | |
| #include "options.hh" | |
| struct text_options_t | |
| { | |
| text_options_t () | |
| : gs (g_string_new (nullptr)) | |
| {} | |
| virtual ~text_options_t () | |
| { | |
| g_free (text); | |
| g_free (text_file); | |
| if (gs) | |
| g_string_free (gs, true); | |
| if (in_fp && in_fp != stdin) | |
| fclose (in_fp); | |
| } | |
| void add_options (option_parser_t *parser); | |
| virtual const char *default_text () { return nullptr; } | |
| void post_parse (GError **error G_GNUC_UNUSED) | |
| { | |
| if (!text && !text_file) | |
| { | |
| const char *dt = default_text (); | |
| if (dt) | |
| text = g_strdup (dt); | |
| else | |
| text_file = g_strdup ("-"); | |
| } | |
| if (text && text_file) | |
| { | |
| g_set_error (error, | |
| G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, | |
| "Only one of text and text-file can be set"); | |
| return; | |
| } | |
| if (text_file) | |
| { | |
| if (0 != strcmp (text_file, "-")) | |
| in_fp = fopen (text_file, "r"); | |
| else | |
| in_fp = stdin; | |
| if (!in_fp) | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, | |
| "Failed opening text file `%s': %s", | |
| text_file, strerror (errno)); | |
| } | |
| } | |
| const char *get_line (unsigned int *len); | |
| int text_len = -1; | |
| char *text = nullptr; | |
| char *text_file = nullptr; | |
| private: | |
| FILE *in_fp = nullptr; | |
| GString *gs = nullptr; | |
| char *line = nullptr; | |
| unsigned line_len = UINT_MAX; | |
| hb_bool_t single_par = false; | |
| }; | |
| struct shape_text_options_t : text_options_t | |
| { | |
| ~shape_text_options_t () | |
| { | |
| g_free (text_before); | |
| g_free (text_after); | |
| } | |
| void add_options (option_parser_t *parser); | |
| char *text_before = nullptr; | |
| char *text_after = nullptr; | |
| }; | |
| static gboolean | |
| parse_text (const char *name G_GNUC_UNUSED, | |
| const char *arg, | |
| gpointer data, | |
| GError **error G_GNUC_UNUSED) | |
| { | |
| text_options_t *text_opts = (text_options_t *) data; | |
| if (text_opts->text && !*arg) | |
| { | |
| // Empty --text provided but --unicodes called; ignore it. This is useful for scripting. | |
| return true; | |
| } | |
| if (text_opts->text && !text_opts->text_len) | |
| { | |
| // Empty --unicodes provided as well as text; ignore it. This is useful for scripting. | |
| g_free (text_opts->text); | |
| text_opts->text = nullptr; | |
| } | |
| if (text_opts->text) | |
| { | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, | |
| "Either --text or --unicodes can be provided but not both"); | |
| return false; | |
| } | |
| text_opts->text_len = -1; | |
| text_opts->text = g_strdup (arg); | |
| return true; | |
| } | |
| static bool | |
| encode_unicodes (const char *unicodes, | |
| GString *gs, | |
| GError **error) | |
| { | |
| #define DELIMITERS "<+-|>{},;&#\\xXuUnNiI\n\t\v\f\r " | |
| char *s = (char *) unicodes; | |
| char *p; | |
| while (s && *s) | |
| { | |
| while (*s && strchr (DELIMITERS, *s)) | |
| s++; | |
| if (!*s) | |
| break; | |
| errno = 0; | |
| hb_codepoint_t u = strtoul (s, &p, 16); | |
| if (errno || s == p) | |
| { | |
| g_string_free (gs, TRUE); | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, | |
| "Failed parsing Unicode value at: '%s'", s); | |
| return false; | |
| } | |
| g_string_append_unichar (gs, u); | |
| s = p; | |
| } | |
| #undef DELIMITERS | |
| return true; | |
| } | |
| static gboolean | |
| parse_unicodes (const char *name G_GNUC_UNUSED, | |
| const char *arg, | |
| gpointer data, | |
| GError **error G_GNUC_UNUSED) | |
| { | |
| text_options_t *text_opts = (text_options_t *) data; | |
| if (text_opts->text) | |
| { | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, | |
| "Either --text or --unicodes can be provided but not both"); | |
| return false; | |
| } | |
| GString *gs = g_string_new (nullptr); | |
| if (0 == strcmp (arg, "*")) | |
| g_string_append_c (gs, '*'); | |
| else | |
| if (!encode_unicodes (arg, gs, error)) | |
| return false; | |
| text_opts->text_len = gs->len; | |
| text_opts->text = g_string_free (gs, FALSE); | |
| return true; | |
| } | |
| static gboolean | |
| parse_text_before (const char *name G_GNUC_UNUSED, | |
| const char *arg, | |
| gpointer data, | |
| GError **error) | |
| { | |
| auto *opts = (shape_text_options_t *) data; | |
| if (opts->text_before) | |
| { | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, | |
| "Either --text-before or --unicodes-before can be provided but not both"); | |
| return false; | |
| } | |
| opts->text_before = g_strdup (arg); | |
| fprintf(stderr, "%s\n", opts->text_before); | |
| return true; | |
| } | |
| static gboolean | |
| parse_unicodes_before (const char *name G_GNUC_UNUSED, | |
| const char *arg, | |
| gpointer data, | |
| GError **error) | |
| { | |
| auto *opts = (shape_text_options_t *) data; | |
| if (opts->text_before) | |
| { | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, | |
| "Either --text-before or --unicodes-before can be provided but not both"); | |
| return false; | |
| } | |
| GString *gs = g_string_new (nullptr); | |
| if (!encode_unicodes (arg, gs, error)) | |
| return false; | |
| opts->text_before = g_string_free (gs, FALSE); | |
| return true; | |
| } | |
| static gboolean | |
| parse_text_after (const char *name G_GNUC_UNUSED, | |
| const char *arg, | |
| gpointer data, | |
| GError **error) | |
| { | |
| auto *opts = (shape_text_options_t *) data; | |
| if (opts->text_after) | |
| { | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, | |
| "Either --text-after or --unicodes-after can be provided but not both"); | |
| return false; | |
| } | |
| opts->text_after = g_strdup (arg); | |
| return true; | |
| } | |
| static gboolean | |
| parse_unicodes_after (const char *name G_GNUC_UNUSED, | |
| const char *arg, | |
| gpointer data, | |
| GError **error) | |
| { | |
| auto *opts = (shape_text_options_t *) data; | |
| if (opts->text_after) | |
| { | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, | |
| "Either --text-after or --unicodes-after can be provided but not both"); | |
| return false; | |
| } | |
| GString *gs = g_string_new (nullptr); | |
| if (!encode_unicodes (arg, gs, error)) | |
| return false; | |
| opts->text_after = g_string_free (gs, FALSE); | |
| return true; | |
| } | |
| const char * | |
| text_options_t::get_line (unsigned int *len) | |
| { | |
| if (text) | |
| { | |
| if (!line) | |
| { | |
| line = text; | |
| line_len = text_len; | |
| } | |
| if (line_len == UINT_MAX) | |
| line_len = strlen (line); | |
| if (!line_len) | |
| { | |
| *len = 0; | |
| return nullptr; | |
| } | |
| const char *ret = line; | |
| const char *p = single_par ? nullptr : (const char *) memchr (line, '\n', line_len); | |
| unsigned int ret_len; | |
| if (!p) | |
| { | |
| ret_len = line_len; | |
| line += ret_len; | |
| line_len = 0; | |
| } | |
| else | |
| { | |
| ret_len = p - ret; | |
| line += ret_len + 1; | |
| line_len -= ret_len + 1; | |
| } | |
| *len = ret_len; | |
| return ret; | |
| } | |
| g_string_set_size (gs, 0); | |
| char buf[BUFSIZ]; | |
| while (fgets (buf, sizeof (buf), in_fp)) | |
| { | |
| unsigned bytes = strlen (buf); | |
| if (!single_par && bytes && buf[bytes - 1] == '\n') | |
| { | |
| bytes--; | |
| g_string_append_len (gs, buf, bytes); | |
| break; | |
| } | |
| g_string_append_len (gs, buf, bytes); | |
| } | |
| if (ferror (in_fp)) | |
| fail (false, "Failed reading text: %s", strerror (errno)); | |
| *len = gs->len; | |
| return !*len && feof (in_fp) ? nullptr : gs->str; | |
| } | |
| void | |
| text_options_t::add_options (option_parser_t *parser) | |
| { | |
| GOptionEntry entries[] = | |
| { | |
| {"text", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_text, "Set input text", "string"}, | |
| {"text-file", 0, 0, G_OPTION_ARG_STRING, &this->text_file, "Set input text file-name", "filename"}, | |
| {"unicodes", 'u', 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_unicodes, "Set input Unicode codepoints", "list of hex numbers"}, | |
| {"single-par", 0, 0, G_OPTION_ARG_NONE, &this->single_par, "Treat text as single paragraph", nullptr}, | |
| {nullptr} | |
| }; | |
| parser->add_group (entries, | |
| "text", | |
| "Text options:\n\nIf no text is provided, standard input is used for input.\n", | |
| "Options for the input text", | |
| this); | |
| } | |
| void | |
| shape_text_options_t::add_options (option_parser_t *parser) | |
| { | |
| text_options_t::add_options (parser); | |
| GOptionEntry entries[] = | |
| { | |
| {"text-before", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_text_before, "Set text context before each line", "string"}, | |
| {"text-after", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_text_after, "Set text context after each line", "string"}, | |
| {"unicodes-before", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_unicodes_before, "Set Unicode codepoints context before each line", "list of hex numbers"}, | |
| {"unicodes-after", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_unicodes_after, "Set Unicode codepoints context after each line", "list of hex numbers"}, | |
| {nullptr} | |
| }; | |
| parser->add_group (entries, | |
| "text-context", | |
| "Textual context options:", | |
| "Options for the input context text", | |
| this); | |
| } | |
| #endif |