HarfBuzz text shaping engine
http://harfbuzz.github.io/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
370 lines
9.0 KiB
370 lines
9.0 KiB
/* |
|
* 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)) |
|
{} |
|
~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); |
|
|
|
void post_parse (GError **error G_GNUC_UNUSED) |
|
{ |
|
if (!text && !text_file) |
|
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) |
|
{ |
|
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
|
|
|