| /* | |
| * 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 VIEW_OPTIONS_HH | |
| #define VIEW_OPTIONS_HH | |
| #include "options.hh" | |
| #define DEFAULT_MARGIN 16 | |
| #define DEFAULT_FORE "#000000" | |
| #define DEFAULT_BACK "#FFFFFF" | |
| #define DEFAULT_STROKE "#888" | |
| #define DEFAULT_STROKE_WIDTH 1 | |
| struct view_options_t | |
| { | |
| ~view_options_t () | |
| { | |
| g_free (fore); | |
| g_free (stroke); | |
| g_free (back); | |
| g_free (custom_palette); | |
| if (foreground_palette) | |
| g_array_unref (foreground_palette); | |
| if (custom_palette_entries) | |
| g_array_unref (custom_palette_entries); | |
| } | |
| void add_options (option_parser_t *parser); | |
| bool include_logical_extents () const { return logical || !ink; } | |
| bool include_ink_extents () const { return ink || !logical; } | |
| void post_parse (GError **error G_GNUC_UNUSED) | |
| { | |
| stroke_enabled = false; | |
| stroke_width = DEFAULT_STROKE_WIDTH; | |
| if (!parse_color (DEFAULT_STROKE, stroke_color.r, stroke_color.g, stroke_color.b, stroke_color.a)) | |
| fail (false, "Failed parsing default stroke color `%s`", DEFAULT_STROKE); | |
| const bool stroke_specified = stroke != nullptr; | |
| if (stroke_specified) | |
| { | |
| stroke_enabled = true; | |
| char *spec = g_strdup (stroke); | |
| char *s = g_strstrip (spec); | |
| if (*s) | |
| { | |
| char *plus = strchr (s, '+'); | |
| if (plus && strchr (plus + 1, '+')) | |
| fail (false, "Failed parsing stroke spec `%s`", stroke); | |
| char *color_spec = s; | |
| char *width_spec = nullptr; | |
| if (plus) | |
| { | |
| *plus = '\0'; | |
| width_spec = g_strstrip (plus + 1); | |
| } | |
| color_spec = g_strstrip (color_spec); | |
| if (*color_spec) | |
| { | |
| if (!parse_color (color_spec, stroke_color.r, stroke_color.g, stroke_color.b, stroke_color.a)) | |
| fail (false, "Failed parsing stroke color `%s`", color_spec); | |
| } | |
| if (width_spec && *width_spec) | |
| { | |
| char *end = nullptr; | |
| errno = 0; | |
| double w = g_ascii_strtod (width_spec, &end); | |
| if (!end || errno || *g_strstrip (end) || w <= 0) | |
| fail (false, "Failed parsing stroke width `%s`", width_spec); | |
| stroke_width = w; | |
| } | |
| } | |
| g_free (spec); | |
| } | |
| if (!parse_custom_palette_entries (error)) | |
| return; | |
| if (!parse_color (DEFAULT_BACK, | |
| background_color.r, background_color.g, | |
| background_color.b, background_color.a)) | |
| fail (false, "Failed parsing default background color `%s`", DEFAULT_BACK); | |
| has_background = false; | |
| if (back && *back) | |
| { | |
| if (0 == g_ascii_strcasecmp (back, "none")) | |
| { | |
| background_color = {0, 0, 0, 0}; | |
| } | |
| else if (!parse_color (back, | |
| background_color.r, background_color.g, | |
| background_color.b, background_color.a)) | |
| { | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, | |
| "Invalid background color `%s`", back); | |
| return; | |
| } | |
| has_background = true; | |
| } | |
| foreground_use_palette = false; | |
| if (foreground_palette) | |
| { | |
| g_array_unref (foreground_palette); | |
| foreground_palette = nullptr; | |
| } | |
| const char *foreground = fore ? fore : DEFAULT_FORE; | |
| if (!foreground) | |
| return; | |
| if (!parse_color (DEFAULT_FORE, | |
| foreground_color.r, foreground_color.g, | |
| foreground_color.b, foreground_color.a)) | |
| fail (false, "Failed parsing default foreground color `%s`", DEFAULT_FORE); | |
| const bool use_rainbow = | |
| foreground_use_rainbow || | |
| 0 == g_ascii_strcasecmp (foreground, "rainbow") || | |
| 0 == g_ascii_strcasecmp (foreground, "lgbtq"); | |
| if (use_rainbow) | |
| { | |
| static const char *rainbow[] = | |
| { | |
| "#DA0000BB", | |
| "#FF6200BB", | |
| "#D4B900BB", | |
| "#005100BB", | |
| "#000CFFBB", | |
| "#42005BBB", | |
| nullptr | |
| }; | |
| foreground_palette = g_array_new (false, false, sizeof (rgba_color_t)); | |
| for (const char **entry = rainbow; *entry; entry++) | |
| { | |
| rgba_color_t color = {0, 0, 0, 255}; | |
| if (!parse_color (*entry, color.r, color.g, color.b, color.a)) | |
| fail (false, "Failed parsing foreground color `%s`", *entry); | |
| g_array_append_val (foreground_palette, color); | |
| } | |
| if (foreground_palette->len) | |
| foreground_color = | |
| g_array_index (foreground_palette, rgba_color_t, 0); | |
| foreground_use_palette = true; | |
| if (!stroke_specified) | |
| stroke_enabled = true; | |
| return; | |
| } | |
| if (!strchr (foreground, ',')) | |
| { | |
| rgba_color_t color = {0, 0, 0, 255}; | |
| if (!parse_color (foreground, color.r, color.g, color.b, color.a)) | |
| fail (false, "Failed parsing foreground color `%s`", foreground); | |
| foreground_color = color; | |
| return; | |
| } | |
| foreground_palette = g_array_new (false, false, sizeof (rgba_color_t)); | |
| char **entries = g_strsplit (foreground, ",", -1); | |
| for (unsigned i = 0; entries[i]; i++) | |
| { | |
| char *entry = g_strstrip (entries[i]); | |
| if (!*entry) | |
| continue; | |
| rgba_color_t color = {0, 0, 0, 255}; | |
| if (!parse_color (entry, color.r, color.g, color.b, color.a)) | |
| fail (false, "Failed parsing foreground color `%s`", entry); | |
| g_array_append_val (foreground_palette, color); | |
| } | |
| g_strfreev (entries); | |
| if (!foreground_palette->len) | |
| { | |
| g_array_unref (foreground_palette); | |
| foreground_palette = nullptr; | |
| return; | |
| } | |
| foreground_color = g_array_index (foreground_palette, rgba_color_t, 0); | |
| foreground_use_palette = true; | |
| } | |
| struct rgba_color_t { | |
| unsigned r, g, b, a; | |
| }; | |
| struct custom_palette_entry_t { | |
| unsigned index; | |
| rgba_color_t color; | |
| }; | |
| char *fore = nullptr; | |
| char *stroke = nullptr; | |
| char *back = nullptr; | |
| unsigned int palette = 0; | |
| char *custom_palette = nullptr; | |
| GArray *custom_palette_entries = nullptr; | |
| GArray *foreground_palette = nullptr; | |
| hb_bool_t foreground_use_palette = false; | |
| hb_bool_t foreground_use_rainbow = false; | |
| hb_bool_t stroke_enabled = false; | |
| rgba_color_t stroke_color = {0, 0, 0, 255}; | |
| double stroke_width = DEFAULT_STROKE_WIDTH; | |
| rgba_color_t foreground_color = {0, 0, 0, 255}; | |
| rgba_color_t background_color = {255, 255, 255, 255}; | |
| hb_bool_t has_background = false; | |
| double line_space = 0; | |
| bool have_font_extents = false; | |
| struct font_extents_t { | |
| double ascent, descent, line_gap; | |
| } font_extents = {0., 0., 0.}; | |
| struct margin_t { | |
| double t, r, b, l; | |
| } margin = {DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN}; | |
| hb_bool_t logical = false; | |
| hb_bool_t ink = false; | |
| hb_bool_t show_extents = false; | |
| hb_bool_t force_draw = false; /* --draw : use mono outline path */ | |
| hb_bool_t force_paint = false; /* --paint : use color paint path */ | |
| bool parse_custom_palette_entries (GError **error) | |
| { | |
| if (custom_palette_entries) | |
| { | |
| g_array_unref (custom_palette_entries); | |
| custom_palette_entries = nullptr; | |
| } | |
| if (!custom_palette || !*custom_palette) | |
| return true; | |
| custom_palette_entries = g_array_new (false, false, sizeof (custom_palette_entry_t)); | |
| char **entries = g_strsplit (custom_palette, ",", -1); | |
| unsigned next_index = 0; | |
| for (unsigned i = 0; entries && entries[i]; i++) | |
| { | |
| char *entry = g_strstrip (entries[i]); | |
| if (!*entry) | |
| continue; | |
| unsigned idx = next_index; | |
| char *color_spec = entry; | |
| char *eq = strchr (entry, '='); | |
| if (eq) | |
| { | |
| *eq = '\0'; | |
| char *idx_spec = g_strstrip (entry); | |
| color_spec = g_strstrip (eq + 1); | |
| if (!*idx_spec || !*color_spec) | |
| { | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, | |
| "Invalid --custom-palette entry"); | |
| g_strfreev (entries); | |
| return false; | |
| } | |
| char *end = nullptr; | |
| errno = 0; | |
| unsigned long parsed = strtoul (idx_spec, &end, 10); | |
| if (errno || !end || *g_strstrip (end)) | |
| { | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, | |
| "Invalid --custom-palette index `%s`", idx_spec); | |
| g_strfreev (entries); | |
| return false; | |
| } | |
| idx = (unsigned) parsed; | |
| next_index = idx + 1; | |
| } | |
| rgba_color_t color = {0, 0, 0, 255}; | |
| if (!parse_color (color_spec, color.r, color.g, color.b, color.a)) | |
| { | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, | |
| "Invalid --custom-palette color `%s`", color_spec); | |
| g_strfreev (entries); | |
| return false; | |
| } | |
| custom_palette_entry_t parsed = {idx, color}; | |
| g_array_append_val (custom_palette_entries, parsed); | |
| if (!eq) | |
| next_index++; | |
| } | |
| g_strfreev (entries); | |
| if (!custom_palette_entries->len) | |
| { | |
| g_array_unref (custom_palette_entries); | |
| custom_palette_entries = nullptr; | |
| } | |
| return true; | |
| } | |
| }; | |
| static gboolean | |
| parse_font_extents (const char *name G_GNUC_UNUSED, | |
| const char *arg, | |
| gpointer data, | |
| GError **error G_GNUC_UNUSED) | |
| { | |
| view_options_t *view_opts = (view_options_t *) data; | |
| view_options_t::font_extents_t &e = view_opts->font_extents; | |
| switch (sscanf (arg, "%lf%*[ ,]%lf%*[ ,]%lf", &e.ascent, &e.descent, &e.line_gap)) { | |
| case 1: HB_FALLTHROUGH; | |
| case 2: HB_FALLTHROUGH; | |
| case 3: | |
| view_opts->have_font_extents = true; | |
| return true; | |
| default: | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, | |
| "%s argument should be one to three space-separated numbers", | |
| name); | |
| return false; | |
| } | |
| } | |
| static gboolean | |
| parse_margin (const char *name G_GNUC_UNUSED, | |
| const char *arg, | |
| gpointer data, | |
| GError **error G_GNUC_UNUSED) | |
| { | |
| view_options_t *view_opts = (view_options_t *) data; | |
| view_options_t::margin_t &m = view_opts->margin; | |
| if (parse_1to4_doubles (arg, &m.t, &m.r, &m.b, &m.l)) | |
| return true; | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, | |
| "%s argument should be one to four space-separated numbers", | |
| name); | |
| return false; | |
| } | |
| void | |
| view_options_t::add_options (option_parser_t *parser) | |
| { | |
| GOptionEntry entries[] = | |
| { | |
| {"annotate", 0, G_OPTION_FLAG_HIDDEN, | |
| G_OPTION_ARG_NONE, &this->show_extents, "Annotate output rendering", nullptr}, | |
| {"background", 0, 0, G_OPTION_ARG_STRING, &this->back, "Set background color (default: " DEFAULT_BACK "); use \"none\" for transparent", "rrggbb/rrggbbaa/none"}, | |
| {"foreground", 0, 0, G_OPTION_ARG_STRING, &this->fore, "Set foreground color (default: " DEFAULT_FORE ")", "rrggbb/rrggbbaa[,...]"}, | |
| {"rainbow", 0, 0, G_OPTION_ARG_NONE, &this->foreground_use_rainbow, "Rotate glyph foreground colors through a default palette", nullptr}, | |
| {"lgbtq", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &this->foreground_use_rainbow, "Hidden alias for --rainbow", nullptr}, | |
| {"stroke", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &this->stroke, "Stroke glyph outlines; `--stroke=` enables defaults", "rrggbb/rrggbbaa[+width]"}, | |
| {"font-palette", 0, 0, G_OPTION_ARG_INT, &this->palette, "Set font palette (default: 0)", "index"}, | |
| {"custom-palette", 0, 0, G_OPTION_ARG_STRING, &this->custom_palette, "Custom palette", "comma-separated colors"}, | |
| {"line-space", 0, 0, G_OPTION_ARG_DOUBLE, &this->line_space, "Set space between lines (default: 0)", "units"}, | |
| {"font-extents", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_font_extents, "Set font ascent/descent/line-gap (default: auto)","one to three numbers"}, | |
| {"margin", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_margin, "Margin around output (default: " G_STRINGIFY(DEFAULT_MARGIN) ")","one to four numbers"}, | |
| {"logical", 0, 0, G_OPTION_ARG_NONE, &this->logical, "Render to logical box instead of union of logical and ink boxes", nullptr}, | |
| {"ink", 0, 0, G_OPTION_ARG_NONE, &this->ink, "Render to ink box instead of union of logical and ink boxes", nullptr}, | |
| {"show-extents", 0, 0, G_OPTION_ARG_NONE, &this->show_extents, "Draw glyph extents", nullptr}, | |
| {"draw", 0, 0, G_OPTION_ARG_NONE, &this->force_draw, "Force monochrome draw path (overrides auto-detect)", nullptr}, | |
| {"paint", 0, 0, G_OPTION_ARG_NONE, &this->force_paint, "Force color paint path (overrides auto-detect)", nullptr}, | |
| {nullptr} | |
| }; | |
| parser->add_group (entries, | |
| "view", | |
| "View options:", | |
| "Options for output rendering", | |
| this); | |
| } | |
| #endif |