diff --git a/util/Makefile.sources b/util/Makefile.sources index bcf85f562..470fd3c64 100644 --- a/util/Makefile.sources +++ b/util/Makefile.sources @@ -1,38 +1,48 @@ HB_VIEW_sources = \ - hb-view.cc \ - options.cc \ - options.hh \ - main-font-text.hh \ - shape-consumer.hh \ - ansi-print.cc \ ansi-print.hh \ - helper-cairo.cc \ - helper-cairo.hh \ - helper-cairo-ansi.cc \ + face-options.hh \ + font-options.hh \ + hb-view.cc \ helper-cairo-ansi.hh \ - view-cairo.cc \ + helper-cairo.hh \ + main-font-text.hh \ + options.hh \ + output-options.hh \ + shape-consumer.hh \ + shape-options.hh \ + text-options.hh \ view-cairo.hh \ + view-options.hh \ $(NULL) HB_SHAPE_sources = \ + face-options.hh \ + font-options.hh \ hb-shape.cc \ - options.cc \ - options.hh \ main-font-text.hh \ + options.hh \ + output-options.hh \ shape-consumer.hh \ - $(NULL) - -HB_OT_SHAPE_CLOSURE_sources = \ - hb-ot-shape-closure.cc \ - options.cc \ - options.hh \ - main-font-text.hh \ + shape-format.hh \ + shape-options.hh \ + text-options.hh \ $(NULL) HB_SUBSET_CLI_sources = \ + face-options.hh \ hb-subset.cc \ - options.cc \ - options-subset.cc \ - options.hh \ main-font-text.hh \ + options.hh \ + output-options.hh \ + subset-options.hh \ + text-options.hh \ + $(NULL) + +HB_OT_SHAPE_CLOSURE_sources = \ + face-options.hh \ + font-options.hh \ + hb-ot-shape-closure.cc \ + main-font-text.hh \ + options.hh \ + text-options.hh \ $(NULL) diff --git a/util/ansi-print.cc b/util/ansi-print.cc deleted file mode 100644 index 80b3704ba..000000000 --- a/util/ansi-print.cc +++ /dev/null @@ -1,427 +0,0 @@ -/* - * Copyright © 2012 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 - */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include "ansi-print.hh" - -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_UNISTD_H -#include /* for isatty() */ -#endif - -#if defined (_MSC_VER) && (_MSC_VER < 1800) -static inline long int -lround (double x) -{ - if (x >= 0) - return floor (x + 0.5); - else - return ceil (x - 0.5); -} -#endif - -#define ESC_E (char)27 - -#define MIN(a,b) ((a) < (b) ? (a) : (b)) - -#define CELL_W 8 -#define CELL_H (2 * CELL_W) - -struct color_diff_t -{ - int dot (const color_diff_t &o) - { return v[0]*o.v[0] + v[1]*o.v[1] + v[2]*o.v[2] + v[3]*o.v[3]; } - - int v[4]; -}; - -struct color_t -{ - static color_t from_ansi (unsigned int x) - { - color_t c = {(0xFFu<<24) | ((0xFFu*(x&1))<<16) | ((0xFFu*((x >> 1)&1))<<8) | (0xFFu*((x >> 2)&1))}; - return c; - } - unsigned int to_ansi () - { - return ((v >> 23) & 1) | ((v >> 14)&2) | ((v >> 5)&4); - } - - color_diff_t diff (const color_t &o) - { - color_diff_t d; - for (unsigned int i = 0; i < 4; i++) - d.v[i] = (int) ((v >> (i*8))&0xFF) - (int) ((o.v >> (i*8))&0xFF); - return d; - } - - uint32_t v; -}; - -struct image_t -{ - public: - - image_t (unsigned int width_, - unsigned int height_, - const uint32_t *data_, - unsigned int stride_) : - width (width_), - height (height_), - own_data (false), - data ((color_t *) data_), - stride (stride_) {} - image_t (unsigned int width_, - unsigned int height_) : - width (width_), - height (height_), - own_data (true), - data ((color_t *) malloc (sizeof (data[0]) * width * height)), - stride (width) {} - ~image_t () - { if (own_data) free (data); } - - color_t &operator () (unsigned int x, unsigned int y) - { return data[x + y * stride]; } - - color_t operator () (unsigned int x, unsigned int y) const - { return data[x + y * stride]; } - - void - copy_sub_image (const image_t &s, - unsigned int x, unsigned int y, - unsigned int w, unsigned int h) - { - assert (x < width); - assert (y < height); - for (unsigned int row = 0; row < h; row++) { - color_t *p = data + x + MIN (y + row, height - 1) * stride; - color_t *q = s.data + row * s.stride; - if (x + w <= width) - for (unsigned int col = 0; col < w; col++) - *q++ = *p++; - else { - unsigned int limit = width - x; - for (unsigned int col = 0; col < limit; col++) - *q++ = *p++; - p--; - for (unsigned int col = limit; col < w; col++) - *q++ = *p; - } - } - } - - const unsigned int width; - const unsigned int height; - - private: - bool own_data; - color_t * const data; - const unsigned int stride; -}; - -struct biimage_t -{ - public: - - biimage_t (unsigned int width, unsigned int height) : - width (width), - height (height), - bg (0), fg (0), unicolor (true), - data ((uint8_t *) malloc (sizeof (data[0]) * width * height)) {} - ~biimage_t () - { free (data); } - - void set (const image_t &image) - { - assert (image.width == width); - assert (image.height == height); - int freq[8] = {0}; - for (unsigned int y = 0; y < height; y++) - for (unsigned int x = 0; x < width; x++) { - color_t c = image (x, y); - freq[c.to_ansi ()]++; - } - bg = 0; - for (unsigned int i = 1; i < 8; i++) - if (freq[bg] < freq[i]) - bg = i; - fg = 0; - for (unsigned int i = 1; i < 8; i++) - if (i != bg && freq[fg] < freq[i]) - fg = i; - if (fg == bg || freq[fg] == 0) { - fg = bg; - unicolor = true; - } - else - unicolor = false; - - /* Set the data... */ - - if (unicolor) { - memset (data, 0, sizeof (data[0]) * width * height); - return; - } - - color_t bgc = color_t::from_ansi (bg); - color_t fgc = color_t::from_ansi (fg); - color_diff_t diff = fgc.diff (bgc); - int dd = diff.dot (diff); - for (unsigned int y = 0; y < height; y++) - for (unsigned int x = 0; x < width; x++) { - int d = diff.dot (image (x, y).diff (bgc)); - (*this)(x, y) = d < 0 ? 0 : d > dd ? 255 : lround (d * 255. / dd); - } - } - - uint8_t &operator () (unsigned int x, unsigned int y) - { return data[x + y * width]; } - - uint8_t operator () (unsigned int x, unsigned int y) const - { return data[x + y * width]; } - - const unsigned int width; - const unsigned int height; - unsigned int bg; - unsigned int fg; - bool unicolor; - - private: - uint8_t * const data; -}; - -static const char * -block_best (const biimage_t &bi, bool *inverse) -{ - assert (bi.width <= CELL_W); - assert (bi.height <= CELL_H); - - unsigned int score = UINT_MAX; - unsigned int row_sum[CELL_H] = {0}; - unsigned int col_sum[CELL_W] = {0}; - unsigned int row_sum_i[CELL_H] = {0}; - unsigned int col_sum_i[CELL_W] = {0}; - unsigned int quad[2][2] = {{0}}; - unsigned int quad_i[2][2] = {{0}}; - unsigned int total = 0; - unsigned int total_i = 0; - for (unsigned int y = 0; y < bi.height; y++) - for (unsigned int x = 0; x < bi.width; x++) { - unsigned int c = bi (x, y); - unsigned int c_i = 255 - c; - row_sum[y] += c; - row_sum_i[y] += c_i; - col_sum[x] += c; - col_sum_i[x] += c_i; - quad[2 * y / bi.height][2 * x / bi.width] += c; - quad_i[2 * y / bi.height][2 * x / bi.width] += c_i; - total += c; - total_i += c_i; - } - - /* Make the sums cummulative */ - for (unsigned int i = 1; i < bi.height; i++) { - row_sum[i] += row_sum[i - 1]; - row_sum_i[i] += row_sum_i[i - 1]; - } - for (unsigned int i = 1; i < bi.width; i++) { - col_sum[i] += col_sum[i - 1]; - col_sum_i[i] += col_sum_i[i - 1]; - } - - const char *best_c = " "; - - /* Maybe empty is better! */ - if (total < score) { - score = total; - *inverse = false; - best_c = " "; - } - /* Maybe full is better! */ - if (total_i < score) { - score = total_i; - *inverse = true; - best_c = " "; - } - - /* Find best lower line */ - if (1) { - unsigned int best_s = UINT_MAX; - bool best_inv = false; - int best_i = 0; - for (unsigned int i = 0; i < bi.height - 1; i++) - { - unsigned int s; - s = row_sum[i] + total_i - row_sum_i[i]; - if (s < best_s) { - best_s = s; - best_i = i; - best_inv = false; - } - s = row_sum_i[i] + total - row_sum[i]; - if (s < best_s) { - best_s = s; - best_i = i; - best_inv = true; - } - } - if (best_s < score) { - static const char *lower[7] = {"▁", "▂", "▃", "▄", "▅", "▆", "▇"}; - unsigned int which = lround ((double) ((best_i + 1) * 8) / bi.height); - if (1 <= which && which <= 7) { - score = best_s; - *inverse = best_inv; - best_c = lower[7 - which]; - } - } - } - - /* Find best left line */ - if (1) { - unsigned int best_s = UINT_MAX; - bool best_inv = false; - int best_i = 0; - for (unsigned int i = 0; i < bi.width - 1; i++) - { - unsigned int s; - s = col_sum[i] + total_i - col_sum_i[i]; - if (s < best_s) { - best_s = s; - best_i = i; - best_inv = true; - } - s = col_sum_i[i] + total - col_sum[i]; - if (s < best_s) { - best_s = s; - best_i = i; - best_inv = false; - } - } - if (best_s < score) { - static const char *left [7] = {"▏", "▎", "▍", "▌", "▋", "▊", "▉"}; - unsigned int which = lround ((double) ((best_i + 1) * 8) / bi.width); - if (1 <= which && which <= 7) { - score = best_s; - *inverse = best_inv; - best_c = left[which - 1]; - } - } - } - - /* Find best quadrant */ - if (1) { - unsigned int q = 0; - unsigned int qs = 0; - for (unsigned int i = 0; i < 2; i++) - for (unsigned int j = 0; j < 2; j++) - if (quad[i][j] > quad_i[i][j]) { - q += 1 << (2 * i + j); - qs += quad_i[i][j]; - } else - qs += quad[i][j]; - if (qs < score) { - const char *c = nullptr; - bool inv = false; - switch (q) { - case 1: c = "▟"; inv = true; break; - case 2: c = "▙"; inv = true; break; - case 4: c = "▖"; inv = false; break; - case 8: c = "▗"; inv = false; break; - case 9: c = "▚"; inv = false; break; - case 6: c = "▞"; inv = false; break; - case 7: c = "▜"; inv = true; break; - case 11: c = "▜"; inv = true; break; - case 13: c = "▙"; inv = true; break; - case 14: c = "▟"; inv = true; break; - } - if (c) { - score = qs; - *inverse = inv; - best_c = c; - } - } - } - - return best_c; -} - -void -ansi_print_image_rgb24 (const uint32_t *data, - unsigned int width, - unsigned int height, - unsigned int stride) -{ - image_t image (width, height, data, stride); - - unsigned int rows = (height + CELL_H - 1) / CELL_H; - unsigned int cols = (width + CELL_W - 1) / CELL_W; - image_t cell (CELL_W, CELL_H); - biimage_t bi (CELL_W, CELL_H); - unsigned int last_bg = -1, last_fg = -1; - for (unsigned int row = 0; row < rows; row++) { - for (unsigned int col = 0; col < cols; col++) { - image.copy_sub_image (cell, col * CELL_W, row * CELL_H, CELL_W, CELL_H); - bi.set (cell); - if (bi.unicolor) { - if (last_bg != bi.bg) { - printf ("%c[%dm", ESC_E, 40 + bi.bg); - last_bg = bi.bg; - } - printf (" "); - } else { - /* Figure out the closest character to the biimage */ - bool inverse = false; - const char *c = block_best (bi, &inverse); - if (inverse) { - if (last_bg != bi.fg || last_fg != bi.bg) { - printf ("%c[%d;%dm", ESC_E, 30 + bi.bg, 40 + bi.fg); - last_bg = bi.fg; - last_fg = bi.bg; - } - } else { - if (last_bg != bi.bg || last_fg != bi.fg) { - printf ("%c[%d;%dm", ESC_E, 40 + bi.bg, 30 + bi.fg); - last_bg = bi.bg; - last_fg = bi.fg; - } - } - printf ("%s", c); - } - } - printf ("%c[0m\n", ESC_E); /* Reset */ - last_bg = last_fg = -1; - } -} diff --git a/util/ansi-print.hh b/util/ansi-print.hh index 9640d892d..d251e54ec 100644 --- a/util/ansi-print.hh +++ b/util/ansi-print.hh @@ -29,11 +29,398 @@ #include "hb.hh" -void +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include /* for isatty() */ +#endif + +#if defined (_MSC_VER) && (_MSC_VER < 1800) +static inline long int +lround (double x) +{ + if (x >= 0) + return floor (x + 0.5); + else + return ceil (x - 0.5); +} +#endif + +#define ESC_E (char)27 + +#define CELL_W 8 +#define CELL_H (2 * CELL_W) + +struct color_diff_t +{ + int dot (const color_diff_t &o) + { return v[0]*o.v[0] + v[1]*o.v[1] + v[2]*o.v[2] + v[3]*o.v[3]; } + + int v[4]; +}; + +struct color_t +{ + static color_t from_ansi (unsigned int x) + { + color_t c = {(0xFFu<<24) | ((0xFFu*(x&1))<<16) | ((0xFFu*((x >> 1)&1))<<8) | (0xFFu*((x >> 2)&1))}; + return c; + } + unsigned int to_ansi () + { + return ((v >> 23) & 1) | ((v >> 14)&2) | ((v >> 5)&4); + } + + color_diff_t diff (const color_t &o) + { + color_diff_t d; + for (unsigned int i = 0; i < 4; i++) + d.v[i] = (int) ((v >> (i*8))&0xFF) - (int) ((o.v >> (i*8))&0xFF); + return d; + } + + uint32_t v; +}; + +struct image_t +{ + public: + + image_t (unsigned int width_, + unsigned int height_, + const uint32_t *data_, + unsigned int stride_) : + width (width_), + height (height_), + own_data (false), + data ((color_t *) data_), + stride (stride_) {} + image_t (unsigned int width_, + unsigned int height_) : + width (width_), + height (height_), + own_data (true), + data ((color_t *) malloc (sizeof (data[0]) * width * height)), + stride (width) {} + ~image_t () + { if (own_data) free (data); } + + color_t &operator () (unsigned int x, unsigned int y) + { return data[x + y * stride]; } + + color_t operator () (unsigned int x, unsigned int y) const + { return data[x + y * stride]; } + + void + copy_sub_image (const image_t &s, + unsigned int x, unsigned int y, + unsigned int w, unsigned int h) + { + assert (x < width); + assert (y < height); + for (unsigned int row = 0; row < h; row++) { + color_t *p = data + x + hb_min (y + row, height - 1) * stride; + color_t *q = s.data + row * s.stride; + if (x + w <= width) + for (unsigned int col = 0; col < w; col++) + *q++ = *p++; + else { + unsigned int limit = width - x; + for (unsigned int col = 0; col < limit; col++) + *q++ = *p++; + p--; + for (unsigned int col = limit; col < w; col++) + *q++ = *p; + } + } + } + + const unsigned int width; + const unsigned int height; + + private: + bool own_data; + color_t * const data; + const unsigned int stride; +}; + +struct biimage_t +{ + public: + + biimage_t (unsigned int width, unsigned int height) : + width (width), + height (height), + bg (0), fg (0), unicolor (true), + data ((uint8_t *) malloc (sizeof (data[0]) * width * height)) {} + ~biimage_t () + { free (data); } + + void set (const image_t &image) + { + assert (image.width == width); + assert (image.height == height); + int freq[8] = {0}; + for (unsigned int y = 0; y < height; y++) + for (unsigned int x = 0; x < width; x++) { + color_t c = image (x, y); + freq[c.to_ansi ()]++; + } + bg = 0; + for (unsigned int i = 1; i < 8; i++) + if (freq[bg] < freq[i]) + bg = i; + fg = 0; + for (unsigned int i = 1; i < 8; i++) + if (i != bg && freq[fg] < freq[i]) + fg = i; + if (fg == bg || freq[fg] == 0) { + fg = bg; + unicolor = true; + } + else + unicolor = false; + + /* Set the data... */ + + if (unicolor) { + memset (data, 0, sizeof (data[0]) * width * height); + return; + } + + color_t bgc = color_t::from_ansi (bg); + color_t fgc = color_t::from_ansi (fg); + color_diff_t diff = fgc.diff (bgc); + int dd = diff.dot (diff); + for (unsigned int y = 0; y < height; y++) + for (unsigned int x = 0; x < width; x++) { + int d = diff.dot (image (x, y).diff (bgc)); + (*this)(x, y) = d < 0 ? 0 : d > dd ? 255 : lround (d * 255. / dd); + } + } + + uint8_t &operator () (unsigned int x, unsigned int y) + { return data[x + y * width]; } + + uint8_t operator () (unsigned int x, unsigned int y) const + { return data[x + y * width]; } + + const unsigned int width; + const unsigned int height; + unsigned int bg; + unsigned int fg; + bool unicolor; + + private: + uint8_t * const data; +}; + +static const char * +block_best (const biimage_t &bi, bool *inverse) +{ + assert (bi.width <= CELL_W); + assert (bi.height <= CELL_H); + + unsigned int score = UINT_MAX; + unsigned int row_sum[CELL_H] = {0}; + unsigned int col_sum[CELL_W] = {0}; + unsigned int row_sum_i[CELL_H] = {0}; + unsigned int col_sum_i[CELL_W] = {0}; + unsigned int quad[2][2] = {{0}}; + unsigned int quad_i[2][2] = {{0}}; + unsigned int total = 0; + unsigned int total_i = 0; + for (unsigned int y = 0; y < bi.height; y++) + for (unsigned int x = 0; x < bi.width; x++) { + unsigned int c = bi (x, y); + unsigned int c_i = 255 - c; + row_sum[y] += c; + row_sum_i[y] += c_i; + col_sum[x] += c; + col_sum_i[x] += c_i; + quad[2 * y / bi.height][2 * x / bi.width] += c; + quad_i[2 * y / bi.height][2 * x / bi.width] += c_i; + total += c; + total_i += c_i; + } + + /* Make the sums cummulative */ + for (unsigned int i = 1; i < bi.height; i++) { + row_sum[i] += row_sum[i - 1]; + row_sum_i[i] += row_sum_i[i - 1]; + } + for (unsigned int i = 1; i < bi.width; i++) { + col_sum[i] += col_sum[i - 1]; + col_sum_i[i] += col_sum_i[i - 1]; + } + + const char *best_c = " "; + + /* Maybe empty is better! */ + if (total < score) { + score = total; + *inverse = false; + best_c = " "; + } + /* Maybe full is better! */ + if (total_i < score) { + score = total_i; + *inverse = true; + best_c = " "; + } + + /* Find best lower line */ + if (1) { + unsigned int best_s = UINT_MAX; + bool best_inv = false; + int best_i = 0; + for (unsigned int i = 0; i < bi.height - 1; i++) + { + unsigned int s; + s = row_sum[i] + total_i - row_sum_i[i]; + if (s < best_s) { + best_s = s; + best_i = i; + best_inv = false; + } + s = row_sum_i[i] + total - row_sum[i]; + if (s < best_s) { + best_s = s; + best_i = i; + best_inv = true; + } + } + if (best_s < score) { + static const char *lower[7] = {"▁", "▂", "▃", "▄", "▅", "▆", "▇"}; + unsigned int which = lround ((double) ((best_i + 1) * 8) / bi.height); + if (1 <= which && which <= 7) { + score = best_s; + *inverse = best_inv; + best_c = lower[7 - which]; + } + } + } + + /* Find best left line */ + if (1) { + unsigned int best_s = UINT_MAX; + bool best_inv = false; + int best_i = 0; + for (unsigned int i = 0; i < bi.width - 1; i++) + { + unsigned int s; + s = col_sum[i] + total_i - col_sum_i[i]; + if (s < best_s) { + best_s = s; + best_i = i; + best_inv = true; + } + s = col_sum_i[i] + total - col_sum[i]; + if (s < best_s) { + best_s = s; + best_i = i; + best_inv = false; + } + } + if (best_s < score) { + static const char *left [7] = {"▏", "▎", "▍", "▌", "▋", "▊", "▉"}; + unsigned int which = lround ((double) ((best_i + 1) * 8) / bi.width); + if (1 <= which && which <= 7) { + score = best_s; + *inverse = best_inv; + best_c = left[which - 1]; + } + } + } + + /* Find best quadrant */ + if (1) { + unsigned int q = 0; + unsigned int qs = 0; + for (unsigned int i = 0; i < 2; i++) + for (unsigned int j = 0; j < 2; j++) + if (quad[i][j] > quad_i[i][j]) { + q += 1 << (2 * i + j); + qs += quad_i[i][j]; + } else + qs += quad[i][j]; + if (qs < score) { + const char *c = nullptr; + bool inv = false; + switch (q) { + case 1: c = "▟"; inv = true; break; + case 2: c = "▙"; inv = true; break; + case 4: c = "▖"; inv = false; break; + case 8: c = "▗"; inv = false; break; + case 9: c = "▚"; inv = false; break; + case 6: c = "▞"; inv = false; break; + case 7: c = "▜"; inv = true; break; + case 11: c = "▜"; inv = true; break; + case 13: c = "▙"; inv = true; break; + case 14: c = "▟"; inv = true; break; + } + if (c) { + score = qs; + *inverse = inv; + best_c = c; + } + } + } + + return best_c; +} + +static inline void ansi_print_image_rgb24 (const uint32_t *data, unsigned int width, unsigned int height, - unsigned int stride); + unsigned int stride) +{ + image_t image (width, height, data, stride); + unsigned int rows = (height + CELL_H - 1) / CELL_H; + unsigned int cols = (width + CELL_W - 1) / CELL_W; + image_t cell (CELL_W, CELL_H); + biimage_t bi (CELL_W, CELL_H); + unsigned int last_bg = -1, last_fg = -1; + for (unsigned int row = 0; row < rows; row++) { + for (unsigned int col = 0; col < cols; col++) { + image.copy_sub_image (cell, col * CELL_W, row * CELL_H, CELL_W, CELL_H); + bi.set (cell); + if (bi.unicolor) { + if (last_bg != bi.bg) { + printf ("%c[%dm", ESC_E, 40 + bi.bg); + last_bg = bi.bg; + } + printf (" "); + } else { + /* Figure out the closest character to the biimage */ + bool inverse = false; + const char *c = block_best (bi, &inverse); + if (inverse) { + if (last_bg != bi.fg || last_fg != bi.bg) { + printf ("%c[%d;%dm", ESC_E, 30 + bi.bg, 40 + bi.fg); + last_bg = bi.fg; + last_fg = bi.bg; + } + } else { + if (last_bg != bi.bg || last_fg != bi.fg) { + printf ("%c[%d;%dm", ESC_E, 40 + bi.bg, 30 + bi.fg); + last_bg = bi.bg; + last_fg = bi.fg; + } + } + printf ("%s", c); + } + } + printf ("%c[0m\n", ESC_E); /* Reset */ + last_bg = last_fg = -1; + } +} #endif diff --git a/util/face-options.hh b/util/face-options.hh new file mode 100644 index 000000000..12cfd3023 --- /dev/null +++ b/util/face-options.hh @@ -0,0 +1,135 @@ +/* + * 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 FACE_OPTIONS_HH +#define FACE_OPTIONS_HH + +#include "options.hh" + +struct face_options_t +{ + void add_options (option_parser_t *parser); + + hb_blob_t *get_blob () const; + hb_face_t *get_face () const; + + static struct cache_t + { + ~cache_t () + { + free ((void *) font_path); + hb_blob_destroy (blob); + hb_face_destroy (face); + } + + const char *font_path = nullptr; + hb_blob_t *blob = nullptr; + unsigned face_index = (unsigned) -1; + hb_face_t *face = nullptr; + } cache; + + char *font_file = nullptr; + unsigned face_index = 0; + private: + mutable hb_face_t *face = nullptr; +}; + + +face_options_t::cache_t face_options_t::cache {}; + +hb_blob_t * +face_options_t::get_blob () const +{ + // XXX This does the job for now; will move to post_parse. + return cache.blob; +} + +hb_face_t * +face_options_t::get_face () const +{ + if (face) + return face; + + if (!font_file) + fail (true, "No font file set"); + + const char *font_path = font_file; + + if (0 == strcmp (font_path, "-")) + { +#if defined(_WIN32) || defined(__CYGWIN__) + setmode (fileno (stdin), O_BINARY); + font_path = "STDIN"; +#else + font_path = "/dev/stdin"; +#endif + } + + if (!cache.font_path || 0 != strcmp (cache.font_path, font_path)) + { + hb_blob_destroy (cache.blob); + cache.blob = hb_blob_create_from_file_or_fail (font_path); + + free ((char *) cache.font_path); + cache.font_path = strdup (font_path); + + if (!cache.blob) + fail (false, "%s: Failed reading file", font_path); + + hb_face_destroy (cache.face); + cache.face = nullptr; + cache.face_index = (unsigned) -1; + } + + if (cache.face_index != face_index) + { + hb_face_destroy (cache.face); + cache.face = hb_face_create (cache.blob, face_index); + cache.face_index = face_index; + } + + face = cache.face; + + return face; +} + +void +face_options_t::add_options (option_parser_t *parser) +{ + GOptionEntry entries[] = + { + {"font-file", 0, 0, G_OPTION_ARG_STRING, &this->font_file, "Set font file-name", "filename"}, + {"face-index", 0, 0, G_OPTION_ARG_INT, &this->face_index, "Set face index (default: 0)", "index"}, + {nullptr} + }; + parser->add_group (entries, + "face", + "Font-face options:", + "Options for the font face", + this); +} + +#endif diff --git a/util/font-options.hh b/util/font-options.hh new file mode 100644 index 000000000..108dbd824 --- /dev/null +++ b/util/font-options.hh @@ -0,0 +1,306 @@ +/* + * 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 FONT_OPTIONS_HH +#define FONT_OPTIONS_HH + +#include "face-options.hh" + +#ifdef HAVE_FREETYPE +#include +#endif +#include + +#define FONT_SIZE_UPEM 0x7FFFFFFF +#define FONT_SIZE_NONE 0 + +extern const unsigned DEFAULT_FONT_SIZE; +extern const unsigned SUBPIXEL_BITS; + +struct font_options_t : face_options_t +{ + ~font_options_t () + { + g_free (font_file); + free (variations); + g_free (font_funcs); + hb_font_destroy (font); + } + + void add_options (option_parser_t *parser); + + hb_font_t *get_font () const; + + hb_variation_t *variations = nullptr; + unsigned int num_variations = 0; + int x_ppem = 0; + int y_ppem = 0; + double ptem = 0.; + unsigned int subpixel_bits = SUBPIXEL_BITS; + mutable double font_size_x = DEFAULT_FONT_SIZE; + mutable double font_size_y = DEFAULT_FONT_SIZE; + char *font_funcs = nullptr; + int ft_load_flags = 2; + + private: + mutable hb_font_t *font = nullptr; +}; + + +static struct supported_font_funcs_t { + char name[4]; + void (*func) (hb_font_t *); +} supported_font_funcs[] = +{ +#ifdef HAVE_FREETYPE + {"ft", hb_ft_font_set_funcs}, +#endif + {"ot", hb_ot_font_set_funcs}, +}; + +hb_font_t * +font_options_t::get_font () const +{ + if (font) + return font; + + auto *face = get_face (); + + font = hb_font_create (face); + + if (font_size_x == FONT_SIZE_UPEM) + font_size_x = hb_face_get_upem (face); + if (font_size_y == FONT_SIZE_UPEM) + font_size_y = hb_face_get_upem (face); + + hb_font_set_ppem (font, x_ppem, y_ppem); + hb_font_set_ptem (font, ptem); + + int scale_x = (int) scalbnf (font_size_x, subpixel_bits); + int scale_y = (int) scalbnf (font_size_y, subpixel_bits); + hb_font_set_scale (font, scale_x, scale_y); + + hb_font_set_variations (font, variations, num_variations); + + void (*set_font_funcs) (hb_font_t *) = nullptr; + if (!font_funcs) + { + set_font_funcs = supported_font_funcs[0].func; + } + else + { + for (unsigned int i = 0; i < ARRAY_LENGTH (supported_font_funcs); i++) + if (0 == g_ascii_strcasecmp (font_funcs, supported_font_funcs[i].name)) + { + set_font_funcs = supported_font_funcs[i].func; + break; + } + if (!set_font_funcs) + { + GString *s = g_string_new (nullptr); + for (unsigned int i = 0; i < ARRAY_LENGTH (supported_font_funcs); i++) + { + if (i) + g_string_append_c (s, '/'); + g_string_append (s, supported_font_funcs[i].name); + } + char *p = g_string_free (s, FALSE); + fail (false, "Unknown font function implementation `%s'; supported values are: %s; default is %s", + font_funcs, + p, + supported_font_funcs[0].name); + //free (p); + } + } + set_font_funcs (font); +#ifdef HAVE_FREETYPE + hb_ft_font_set_load_flags (font, ft_load_flags); +#endif + + return font; +} + + +static gboolean +parse_variations (const char *name G_GNUC_UNUSED, + const char *arg, + gpointer data, + GError **error G_GNUC_UNUSED) +{ + font_options_t *font_opts = (font_options_t *) data; + char *s = (char *) arg; + char *p; + + font_opts->num_variations = 0; + g_free (font_opts->variations); + font_opts->variations = nullptr; + + if (!*s) + return true; + + /* count the variations first, so we can allocate memory */ + p = s; + do { + font_opts->num_variations++; + p = strchr (p, ','); + if (p) + p++; + } while (p); + + font_opts->variations = (hb_variation_t *) calloc (font_opts->num_variations, sizeof (*font_opts->variations)); + if (!font_opts->variations) + return false; + + /* now do the actual parsing */ + p = s; + font_opts->num_variations = 0; + while (p && *p) { + char *end = strchr (p, ','); + if (hb_variation_from_string (p, end ? end - p : -1, &font_opts->variations[font_opts->num_variations])) + font_opts->num_variations++; + p = end ? end + 1 : nullptr; + } + + return true; +} + +static gboolean +parse_font_size (const char *name G_GNUC_UNUSED, + const char *arg, + gpointer data, + GError **error G_GNUC_UNUSED) +{ + font_options_t *font_opts = (font_options_t *) data; + if (0 == strcmp (arg, "upem")) + { + font_opts->font_size_y = font_opts->font_size_x = FONT_SIZE_UPEM; + return true; + } + switch (sscanf (arg, "%lf%*[ ,]%lf", &font_opts->font_size_x, &font_opts->font_size_y)) { + case 1: font_opts->font_size_y = font_opts->font_size_x; HB_FALLTHROUGH; + case 2: return true; + default: + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "%s argument should be one or two space-separated numbers", + name); + return false; + } +} + +static gboolean +parse_font_ppem (const char *name G_GNUC_UNUSED, + const char *arg, + gpointer data, + GError **error G_GNUC_UNUSED) +{ + font_options_t *font_opts = (font_options_t *) data; + switch (sscanf (arg, "%d%*[ ,]%d", &font_opts->x_ppem, &font_opts->y_ppem)) { + case 1: font_opts->y_ppem = font_opts->x_ppem; HB_FALLTHROUGH; + case 2: return true; + default: + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "%s argument should be one or two space-separated numbers", + name); + return false; + } +} + +void +font_options_t::add_options (option_parser_t *parser) +{ + face_options_t::add_options (parser); + + char *text = nullptr; + + { + static_assert ((ARRAY_LENGTH_CONST (supported_font_funcs) > 0), + "No supported font-funcs found."); + GString *s = g_string_new (nullptr); + g_string_printf (s, "Set font functions implementation to use (default: %s)\n\n Supported font function implementations are: %s", + supported_font_funcs[0].name, + supported_font_funcs[0].name); + for (unsigned int i = 1; i < ARRAY_LENGTH (supported_font_funcs); i++) + { + g_string_append_c (s, '/'); + g_string_append (s, supported_font_funcs[i].name); + } + text = g_string_free (s, FALSE); + parser->free_later (text); + } + + char *font_size_text; + if (DEFAULT_FONT_SIZE == FONT_SIZE_UPEM) + font_size_text = (char *) "Font size (default: upem)"; + else + { + font_size_text = g_strdup_printf ("Font size (default: %d)", DEFAULT_FONT_SIZE); + parser->free_later (font_size_text); + } + + int font_size_flags = DEFAULT_FONT_SIZE == FONT_SIZE_NONE ? G_OPTION_FLAG_HIDDEN : 0; + GOptionEntry entries[] = + { + {"font-size", 0, font_size_flags, + G_OPTION_ARG_CALLBACK, (gpointer) &parse_font_size, font_size_text, "1/2 integers or 'upem'"}, + {"font-ppem", 0, font_size_flags, + G_OPTION_ARG_CALLBACK, (gpointer) &parse_font_ppem, "Set x,y pixels per EM (default: 0; disabled)", "1/2 integers"}, + {"font-ptem", 0, 0, + G_OPTION_ARG_DOUBLE, &this->ptem, "Set font point-size (default: 0; disabled)", "point-size"}, + {"font-funcs", 0, 0, G_OPTION_ARG_STRING, &this->font_funcs, text, "impl"}, + {"ft-load-flags", 0, 0, G_OPTION_ARG_INT, &this->ft_load_flags, "Set FreeType load-flags (default: 2)", "integer"}, + {nullptr} + }; + parser->add_group (entries, + "font", + "Font-instance options:", + "Options for the font instance", + this); + + const gchar *variations_help = "Comma-separated list of font variations\n" + "\n" + " Variations are set globally. The format for specifying variation settings\n" + " follows. All valid CSS font-variation-settings values other than 'normal'\n" + " and 'inherited' are also accepted, though, not documented below.\n" + "\n" + " The format is a tag, optionally followed by an equals sign, followed by a\n" + " number. For example:\n" + "\n" + " \"wght=500\"\n" + " \"slnt=-7.5\"\n"; + + GOptionEntry entries2[] = + { + {"variations", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_variations, variations_help, "list"}, + {nullptr} + }; + parser->add_group (entries2, + "variations", + "Variations options:", + "Options for font variations used", + this); +} + +#endif diff --git a/util/hb-ot-shape-closure.cc b/util/hb-ot-shape-closure.cc index 33f531b37..41d18b549 100644 --- a/util/hb-ot-shape-closure.cc +++ b/util/hb-ot-shape-closure.cc @@ -24,23 +24,24 @@ * Google Author(s): Behdad Esfahbod */ +#include "shape-options.hh" +#include "font-options.hh" +#include "text-options.hh" #include "main-font-text.hh" +const unsigned DEFAULT_FONT_SIZE = FONT_SIZE_NONE; +const unsigned SUBPIXEL_BITS = 0; + #ifdef HAVE_FREETYPE #include #endif -struct shape_closure_consumer_t : option_group_t +struct shape_closure_consumer_t { - shape_closure_consumer_t (option_parser_t *parser) : - shaper (parser), - show_glyph_names (true) + void add_options (struct option_parser_t *parser) { - add_options (parser); - } + shaper.add_options (parser); - void add_options (struct option_parser_t *parser) override - { GOptionEntry entries[] = { {"no-glyph-names", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &this->show_glyph_names, "Use glyph indices instead of names", nullptr}, @@ -53,13 +54,12 @@ struct shape_closure_consumer_t : option_group_t this); } - void init (hb_buffer_t *buffer_, - const font_options_t *font_opts) + void init (const font_options_t *font_opts) { glyphs = hb_set_create (); font = hb_font_reference (font_opts->get_font ()); failed = false; - buffer = hb_buffer_reference (buffer_); + buffer = hb_buffer_create (); } void consume_line (const char *text, unsigned int text_len, @@ -104,16 +104,15 @@ struct shape_closure_consumer_t : option_group_t protected: shape_options_t shaper; - hb_bool_t show_glyph_names; + hb_bool_t show_glyph_names = false; - hb_set_t *glyphs; - hb_font_t *font; - hb_buffer_t *buffer; + hb_set_t *glyphs = nullptr; + hb_font_t *font = nullptr; + hb_buffer_t *buffer = nullptr; }; int main (int argc, char **argv) { - main_font_text_t driver; - return driver.main (argc, argv); + return main_font_text (argc, argv); } diff --git a/util/hb-shape.cc b/util/hb-shape.cc index d6b6152e6..d9c727f55 100644 --- a/util/hb-shape.cc +++ b/util/hb-shape.cc @@ -25,44 +25,48 @@ * Google Author(s): Behdad Esfahbod */ -#include "main-font-text.hh" +#include "output-options.hh" +#include "font-options.hh" +#include "text-options.hh" #include "shape-consumer.hh" +#include "shape-format.hh" +#include "main-font-text.hh" -struct output_buffer_t +const unsigned DEFAULT_FONT_SIZE = FONT_SIZE_UPEM; +const unsigned SUBPIXEL_BITS = 0; + +struct output_buffer_t : output_options_t { - output_buffer_t (option_parser_t *parser) - : options (parser, hb_buffer_serialize_list_formats ()), - format (parser), - gs (nullptr), - line_no (0), - font (nullptr), - output_format (HB_BUFFER_SERIALIZE_FORMAT_INVALID), - format_flags (HB_BUFFER_SERIALIZE_FLAG_DEFAULT) {} + void add_options (option_parser_t *parser) + { + output_options_t::add_options (parser, hb_buffer_serialize_list_formats ()); + format.add_options (parser); + } void init (hb_buffer_t *buffer, const font_options_t *font_opts) { - options.get_file_handle (); + get_file_handle (); gs = g_string_new (nullptr); line_no = 0; font = hb_font_reference (font_opts->get_font ()); - if (!options.output_format) - output_format = HB_BUFFER_SERIALIZE_FORMAT_TEXT; + if (!output_format) + serialize_format = HB_BUFFER_SERIALIZE_FORMAT_TEXT; else - output_format = hb_buffer_serialize_format_from_string (options.output_format, -1); + serialize_format = hb_buffer_serialize_format_from_string (output_format, -1); /* An empty "output_format" parameter basically skips output generating. * Useful for benchmarking. */ - if ((!options.output_format || *options.output_format) && - !hb_buffer_serialize_format_to_string (output_format)) + if ((!output_format || *output_format) && + !hb_buffer_serialize_format_to_string (serialize_format)) { - if (options.explicit_output_format) + if (explicit_output_format) fail (false, "Unknown output format `%s'; supported formats are: %s", - options.output_format, - g_strjoinv ("/", const_cast (options.supported_formats))); + output_format, + g_strjoinv ("/", const_cast (hb_buffer_serialize_list_formats ()))); else /* Just default to TEXT if not explicitly requested and the * file extension is not recognized. */ - output_format = HB_BUFFER_SERIALIZE_FORMAT_TEXT; + serialize_format = HB_BUFFER_SERIALIZE_FORMAT_TEXT; } unsigned int flags = HB_BUFFER_SERIALIZE_FLAG_DEFAULT; @@ -78,7 +82,7 @@ struct output_buffer_t flags |= HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS; if (format.show_flags) flags |= HB_BUFFER_SERIALIZE_FLAG_GLYPH_FLAGS; - format_flags = (hb_buffer_serialize_flags_t) flags; + serialize_flags = (hb_buffer_serialize_flags_t) flags; if (format.trace) hb_buffer_set_message_func (buffer, message_func, this, nullptr); @@ -91,13 +95,13 @@ struct output_buffer_t { g_string_set_size (gs, 0); format.serialize_buffer_of_text (buffer, line_no, text, text_len, font, gs); - fprintf (options.fp, "%s", gs->str); + fprintf (fp, "%s", gs->str); } void error (const char *message) { g_string_set_size (gs, 0); format.serialize_message (line_no, "error", message, gs); - fprintf (options.fp, "%s", gs->str); + fprintf (fp, "%s", gs->str); } void consume_glyphs (hb_buffer_t *buffer, const char *text, @@ -106,8 +110,8 @@ struct output_buffer_t { g_string_set_size (gs, 0); format.serialize_buffer_of_glyphs (buffer, line_no, text, text_len, font, - output_format, format_flags, gs); - fprintf (options.fp, "%s", gs->str); + serialize_format, serialize_flags, gs); + fprintf (fp, "%s", gs->str); } void finish (hb_buffer_t *buffer, const font_options_t *font_opts) { @@ -137,29 +141,28 @@ struct output_buffer_t g_string_set_size (gs, 0); format.serialize_line_no (line_no, gs); g_string_append_printf (gs, "trace: %s buffer: ", message); - format.serialize (buffer, font, output_format, format_flags, gs); + format.serialize (buffer, font, serialize_format, serialize_flags, gs); g_string_append_c (gs, '\n'); - fprintf (options.fp, "%s", gs->str); + fprintf (fp, "%s", gs->str); } protected: - output_options_t options; - format_options_t format; - GString *gs; - unsigned int line_no; - hb_font_t *font; - hb_buffer_serialize_format_t output_format; - hb_buffer_serialize_flags_t format_flags; + shape_format_options_t format; + + GString *gs = nullptr; + unsigned int line_no = 0; + hb_font_t *font = nullptr; + hb_buffer_serialize_format_t serialize_format = HB_BUFFER_SERIALIZE_FORMAT_INVALID; + hb_buffer_serialize_flags_t serialize_flags = HB_BUFFER_SERIALIZE_FLAG_DEFAULT; }; -template -using driver_t = main_font_text_t, FONT_SIZE_UPEM, 0, eol>; - int main (int argc, char **argv) { + auto main_func = main_font_text, font_options_t, text_options_t>; + if (argc == 2 && !strcmp (argv[1], "--batch")) { unsigned int ret = 0; @@ -184,13 +187,11 @@ main (int argc, char **argv) start_offset = argc == 2 && p[0] != '\0' && p[0] != ':' && p[1] == ':' && (p[2] == '\\' || p[2] == '/') ? 2 : 0; } - driver_t driver; - ret |= driver.main (argc, args); + ret |= main_func (argc, args, EOF); fflush (stdout); } return ret; } - driver_t<> driver; - return driver.main (argc, argv); + return main_func (argc, argv, '\n'); } diff --git a/util/hb-subset.cc b/util/hb-subset.cc index f05064245..da808f901 100644 --- a/util/hb-subset.cc +++ b/util/hb-subset.cc @@ -27,23 +27,29 @@ #include +#include "subset-options.hh" +#include "output-options.hh" +#include "face-options.hh" +#include "text-options.hh" #include "main-font-text.hh" -#include "hb-subset.h" /* * Command line interface to the harfbuzz font subsetter. */ -struct subset_consumer_t -{ - subset_consumer_t (option_parser_t *parser) - : failed (false), options (parser), subset_options (parser), font (nullptr), input (nullptr) {} - void init (hb_buffer_t *buffer_, - const font_options_t *font_opts) +struct subset_consumer_t : subset_options_t, output_options_t +{ + void add_options (option_parser_t *parser) { - font = hb_font_reference (font_opts->get_font ()); - input = hb_subset_input_reference (subset_options.get_input ()); + subset_options_t::add_options (parser); + output_options_t::add_options (parser); + } + + void init (const face_options_t *face_opts) + { + face = hb_face_reference (face_opts->get_face ()); + input = hb_subset_input_reference (get_input ()); } void consume_line (const char *text, @@ -55,7 +61,6 @@ struct subset_consumer_t hb_set_t *codepoints = hb_subset_input_unicode_set (input); if (0 == strcmp (text, "*")) { - hb_face_t *face = hb_font_get_face (font); hb_face_collect_unicodes (face, codepoints); return; } @@ -94,12 +99,11 @@ struct subset_consumer_t return true; } - void finish (const font_options_t *font_opts) + void finish (const face_options_t *face_opts) { - hb_face_t *face = hb_font_get_face (font); - hb_face_t *new_face = nullptr; - for (unsigned i = 0; i < subset_options.num_iterations; i++) { + for (unsigned i = 0; i < num_iterations; i++) + { hb_face_destroy (new_face); new_face = hb_subset_or_fail (face, input); } @@ -108,31 +112,27 @@ struct subset_consumer_t if (!failed) { hb_blob_t *result = hb_face_reference_blob (new_face); - write_file (options.output_file, result); + write_file (output_file, result); hb_blob_destroy (result); } hb_subset_input_destroy (input); hb_face_destroy (new_face); - hb_font_destroy (font); + hb_face_destroy (face); } public: - bool failed; + bool failed = false; - private: - output_options_t options; - subset_options_t subset_options; - hb_font_t *font; - hb_subset_input_t *input; + hb_face_t *face = nullptr; + hb_subset_input_t *input = nullptr; }; -template -using driver_t = main_font_text_t; - int main (int argc, char **argv) { + auto main_func = main_font_text; + if (argc == 2 && !strcmp (argv[1], "--batch")) { unsigned int ret = 0; @@ -155,8 +155,7 @@ main (int argc, char **argv) args[argc++] = p = e; } - driver_t driver; - int result = driver.main (argc, args); + int result = main_func (argc, args, EOF); fprintf (stdout, result == 0 ? "success\n" : "failure\n"); fflush (stdout); ret |= result; @@ -164,6 +163,5 @@ main (int argc, char **argv) return ret; } - driver_t<> driver; - return driver.main (argc, argv); + return main_func (argc, argv, '\n'); } diff --git a/util/hb-view.cc b/util/hb-view.cc index 69a4c9504..5d2dfe52a 100644 --- a/util/hb-view.cc +++ b/util/hb-view.cc @@ -25,16 +25,17 @@ * Google Author(s): Behdad Esfahbod */ -#include "main-font-text.hh" #include "shape-consumer.hh" #include "view-cairo.hh" +#include "font-options.hh" +#include "text-options.hh" +#include "main-font-text.hh" -#define DEFAULT_FONT_SIZE 256 -#define SUBPIXEL_BITS 6 +const unsigned DEFAULT_FONT_SIZE = 256; +const unsigned SUBPIXEL_BITS = 6; int main (int argc, char **argv) { - main_font_text_t, DEFAULT_FONT_SIZE, SUBPIXEL_BITS> driver; - return driver.main (argc, argv); + return main_font_text, font_options_t, text_options_t> (argc, argv); } diff --git a/util/helper-cairo-ansi.cc b/util/helper-cairo-ansi.cc deleted file mode 100644 index 3db833bfa..000000000 --- a/util/helper-cairo-ansi.cc +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright © 2012 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 - */ - -#include "helper-cairo-ansi.hh" -#include "options.hh" -#include "ansi-print.hh" - -#ifdef HAVE_CHAFA -# include - -/* Similar to ansi-print.cc */ -# define CELL_W 8 -# define CELL_H (2 * CELL_W) - -static void -chafa_print_image_rgb24 (const void *data, int width, int height, int stride) -{ - ChafaTermInfo *term_info; - ChafaSymbolMap *symbol_map; - ChafaCanvasConfig *config; - ChafaCanvas *canvas; - GString *gs; - unsigned int cols = (width + CELL_W - 1) / CELL_W; - unsigned int rows = (height + CELL_H - 1) / CELL_H; - gchar **environ; - ChafaCanvasMode mode; - ChafaPixelMode pixel_mode; - - /* Adapt to terminal; use sixels if available, and fall back to symbols - * with as many colors as are supported */ - - environ = g_get_environ (); - term_info = chafa_term_db_detect (chafa_term_db_get_default (), - environ); - - pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS; - - if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_BEGIN_SIXELS)) - { - pixel_mode = CHAFA_PIXEL_MODE_SIXELS; - mode = CHAFA_CANVAS_MODE_TRUECOLOR; - } -// else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_DIRECT)) -// mode = CHAFA_CANVAS_MODE_TRUECOLOR; - else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_256)) - mode = CHAFA_CANVAS_MODE_INDEXED_240; - else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_16)) - mode = CHAFA_CANVAS_MODE_INDEXED_16; - else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_INVERT_COLORS)) - mode = CHAFA_CANVAS_MODE_FGBG_BGFG; - else - mode = CHAFA_CANVAS_MODE_FGBG; - - /* Create the configuration */ - - symbol_map = chafa_symbol_map_new (); - chafa_symbol_map_add_by_tags (symbol_map, - (ChafaSymbolTags) (CHAFA_SYMBOL_TAG_BLOCK - | CHAFA_SYMBOL_TAG_SPACE)); - - config = chafa_canvas_config_new (); - chafa_canvas_config_set_canvas_mode (config, mode); - chafa_canvas_config_set_pixel_mode (config, pixel_mode); - chafa_canvas_config_set_cell_geometry (config, 10, 20); - chafa_canvas_config_set_geometry (config, cols, rows); - chafa_canvas_config_set_symbol_map (config, symbol_map); - chafa_canvas_config_set_color_extractor (config, CHAFA_COLOR_EXTRACTOR_MEDIAN); - chafa_canvas_config_set_work_factor (config, 1.0f); - - /* Create canvas, draw to it and render output string */ - - canvas = chafa_canvas_new (config); - chafa_canvas_draw_all_pixels (canvas, - /* Cairo byte order is host native */ - G_BYTE_ORDER == G_LITTLE_ENDIAN - ? CHAFA_PIXEL_BGRA8_PREMULTIPLIED - : CHAFA_PIXEL_ARGB8_PREMULTIPLIED, - (const guint8 *) data, - width, - height, - stride); - gs = chafa_canvas_print (canvas, term_info); - - /* Print the string */ - - fwrite (gs->str, sizeof (char), gs->len, stdout); - - if (pixel_mode != CHAFA_PIXEL_MODE_SIXELS) - fputc ('\n', stdout); - - /* Free resources */ - - g_string_free (gs, TRUE); - chafa_canvas_unref (canvas); - chafa_canvas_config_unref (config); - chafa_symbol_map_unref (symbol_map); - chafa_term_info_unref (term_info); - g_strfreev (environ); -} - -#endif /* HAVE_CHAFA */ - -cairo_status_t -helper_cairo_surface_write_to_ansi_stream (cairo_surface_t *surface, - cairo_write_func_t write_func, - void *closure) -{ - unsigned int width = cairo_image_surface_get_width (surface); - unsigned int height = cairo_image_surface_get_height (surface); - if (cairo_image_surface_get_format (surface) != CAIRO_FORMAT_RGB24) { - cairo_surface_t *new_surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height); - cairo_t *cr = cairo_create (new_surface); - if (cairo_image_surface_get_format (surface) == CAIRO_FORMAT_A8) { - cairo_set_source_rgb (cr, 0., 0., 0.); - cairo_paint (cr); - cairo_set_source_rgb (cr, 1., 1., 1.); - cairo_mask_surface (cr, surface, 0, 0); - } else { - cairo_set_source_rgb (cr, 1., 1., 1.); - cairo_paint (cr); - cairo_set_source_surface (cr, surface, 0, 0); - cairo_paint (cr); - } - cairo_destroy (cr); - surface = new_surface; - } else - cairo_surface_reference (surface); - - unsigned int stride = cairo_image_surface_get_stride (surface); - const uint32_t *data = (uint32_t *) (void *) cairo_image_surface_get_data (surface); - - /* We don't have rows to spare on the terminal window... - * Find the tight image top/bottom and only print in between. */ - - /* Use corner color as background color. */ - uint32_t bg_color = data ? * (uint32_t *) data : 0; - - /* Drop first row while empty */ - while (height) - { - unsigned int i; - for (i = 0; i < width; i++) - if (data[i] != bg_color) - break; - if (i < width) - break; - data += stride / 4; - height--; - } - - /* Drop last row while empty */ - unsigned int orig_height = height; - while (height) - { - const uint32_t *row = data + (height - 1) * stride / 4; - unsigned int i; - for (i = 0; i < width; i++) - if (row[i] != bg_color) - break; - if (i < width) - break; - height--; - } - if (height < orig_height) - height++; /* Add one last blank row for padding. */ - - if (width && height) - { -#ifdef HAVE_CHAFA - if (true) - chafa_print_image_rgb24 (data, width, height, stride); - else -#endif - ansi_print_image_rgb24 (data, width, height, stride / 4); - } - - cairo_surface_destroy (surface); - return CAIRO_STATUS_SUCCESS; -} diff --git a/util/helper-cairo-ansi.hh b/util/helper-cairo-ansi.hh index bc2313219..8c8a1eece 100644 --- a/util/helper-cairo-ansi.hh +++ b/util/helper-cairo-ansi.hh @@ -31,10 +31,180 @@ #include -cairo_status_t +#include "ansi-print.hh" + +#ifdef HAVE_CHAFA +# include + +/* Similar to ansi-print.cc */ +# define CELL_W 8 +# define CELL_H (2 * CELL_W) + +static void +chafa_print_image_rgb24 (const void *data, int width, int height, int stride) +{ + ChafaTermInfo *term_info; + ChafaSymbolMap *symbol_map; + ChafaCanvasConfig *config; + ChafaCanvas *canvas; + GString *gs; + unsigned int cols = (width + CELL_W - 1) / CELL_W; + unsigned int rows = (height + CELL_H - 1) / CELL_H; + gchar **environ; + ChafaCanvasMode mode; + ChafaPixelMode pixel_mode; + + /* Adapt to terminal; use sixels if available, and fall back to symbols + * with as many colors as are supported */ + + environ = g_get_environ (); + term_info = chafa_term_db_detect (chafa_term_db_get_default (), + environ); + + pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS; + + if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_BEGIN_SIXELS)) + { + pixel_mode = CHAFA_PIXEL_MODE_SIXELS; + mode = CHAFA_CANVAS_MODE_TRUECOLOR; + } +// else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_DIRECT)) +// mode = CHAFA_CANVAS_MODE_TRUECOLOR; + else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_256)) + mode = CHAFA_CANVAS_MODE_INDEXED_240; + else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_16)) + mode = CHAFA_CANVAS_MODE_INDEXED_16; + else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_INVERT_COLORS)) + mode = CHAFA_CANVAS_MODE_FGBG_BGFG; + else + mode = CHAFA_CANVAS_MODE_FGBG; + + /* Create the configuration */ + + symbol_map = chafa_symbol_map_new (); + chafa_symbol_map_add_by_tags (symbol_map, + (ChafaSymbolTags) (CHAFA_SYMBOL_TAG_BLOCK + | CHAFA_SYMBOL_TAG_SPACE)); + + config = chafa_canvas_config_new (); + chafa_canvas_config_set_canvas_mode (config, mode); + chafa_canvas_config_set_pixel_mode (config, pixel_mode); + chafa_canvas_config_set_cell_geometry (config, 10, 20); + chafa_canvas_config_set_geometry (config, cols, rows); + chafa_canvas_config_set_symbol_map (config, symbol_map); + chafa_canvas_config_set_color_extractor (config, CHAFA_COLOR_EXTRACTOR_MEDIAN); + chafa_canvas_config_set_work_factor (config, 1.0f); + + /* Create canvas, draw to it and render output string */ + + canvas = chafa_canvas_new (config); + chafa_canvas_draw_all_pixels (canvas, + /* Cairo byte order is host native */ + G_BYTE_ORDER == G_LITTLE_ENDIAN + ? CHAFA_PIXEL_BGRA8_PREMULTIPLIED + : CHAFA_PIXEL_ARGB8_PREMULTIPLIED, + (const guint8 *) data, + width, + height, + stride); + gs = chafa_canvas_print (canvas, term_info); + + /* Print the string */ + + fwrite (gs->str, sizeof (char), gs->len, stdout); + + if (pixel_mode != CHAFA_PIXEL_MODE_SIXELS) + fputc ('\n', stdout); + + /* Free resources */ + + g_string_free (gs, TRUE); + chafa_canvas_unref (canvas); + chafa_canvas_config_unref (config); + chafa_symbol_map_unref (symbol_map); + chafa_term_info_unref (term_info); + g_strfreev (environ); +} + +#endif /* HAVE_CHAFA */ + +static inline cairo_status_t helper_cairo_surface_write_to_ansi_stream (cairo_surface_t *surface, cairo_write_func_t write_func, - void *closure); + void *closure) +{ + unsigned int width = cairo_image_surface_get_width (surface); + unsigned int height = cairo_image_surface_get_height (surface); + if (cairo_image_surface_get_format (surface) != CAIRO_FORMAT_RGB24) { + cairo_surface_t *new_surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height); + cairo_t *cr = cairo_create (new_surface); + if (cairo_image_surface_get_format (surface) == CAIRO_FORMAT_A8) { + cairo_set_source_rgb (cr, 0., 0., 0.); + cairo_paint (cr); + cairo_set_source_rgb (cr, 1., 1., 1.); + cairo_mask_surface (cr, surface, 0, 0); + } else { + cairo_set_source_rgb (cr, 1., 1., 1.); + cairo_paint (cr); + cairo_set_source_surface (cr, surface, 0, 0); + cairo_paint (cr); + } + cairo_destroy (cr); + surface = new_surface; + } else + cairo_surface_reference (surface); + + unsigned int stride = cairo_image_surface_get_stride (surface); + const uint32_t *data = (uint32_t *) (void *) cairo_image_surface_get_data (surface); + + /* We don't have rows to spare on the terminal window... + * Find the tight image top/bottom and only print in between. */ + + /* Use corner color as background color. */ + uint32_t bg_color = data ? * (uint32_t *) data : 0; + + /* Drop first row while empty */ + while (height) + { + unsigned int i; + for (i = 0; i < width; i++) + if (data[i] != bg_color) + break; + if (i < width) + break; + data += stride / 4; + height--; + } + + /* Drop last row while empty */ + unsigned int orig_height = height; + while (height) + { + const uint32_t *row = data + (height - 1) * stride / 4; + unsigned int i; + for (i = 0; i < width; i++) + if (row[i] != bg_color) + break; + if (i < width) + break; + height--; + } + if (height < orig_height) + height++; /* Add one last blank row for padding. */ + + if (width && height) + { +#ifdef HAVE_CHAFA + if (true) + chafa_print_image_rgb24 (data, width, height, stride); + else +#endif + ansi_print_image_rgb24 (data, width, height, stride / 4); + } + + cairo_surface_destroy (surface); + return CAIRO_STATUS_SUCCESS; +} #endif diff --git a/util/helper-cairo.cc b/util/helper-cairo.cc deleted file mode 100644 index deb9f0d2c..000000000 --- a/util/helper-cairo.cc +++ /dev/null @@ -1,668 +0,0 @@ -/* - * 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 - */ - -#include "helper-cairo.hh" - -#include -#include -#include FT_MULTIPLE_MASTERS_H - -#include "helper-cairo-ansi.hh" -#ifdef CAIRO_HAS_SVG_SURFACE -# include -#endif -#ifdef CAIRO_HAS_PDF_SURFACE -# include -#endif -#ifdef CAIRO_HAS_PS_SURFACE -# include -# if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,6,0) -# define HAS_EPS 1 - -static cairo_surface_t * -_cairo_eps_surface_create_for_stream (cairo_write_func_t write_func, - void *closure, - double width, - double height) -{ - cairo_surface_t *surface; - - surface = cairo_ps_surface_create_for_stream (write_func, closure, width, height); - cairo_ps_surface_set_eps (surface, true); - - return surface; -} - -# else -# undef HAS_EPS -# endif -#endif - - -static FT_Library ft_library; - -#ifdef HAVE_ATEXIT -static inline -void free_ft_library () -{ - FT_Done_FreeType (ft_library); -} -#endif - -cairo_scaled_font_t * -helper_cairo_create_scaled_font (const font_options_t *font_opts) -{ - hb_font_t *font = hb_font_reference (font_opts->get_font ()); - - cairo_font_face_t *cairo_face; - /* We cannot use the FT_Face from hb_font_t, as doing so will confuse hb_font_t because - * cairo will reset the face size. As such, create new face... - * TODO Perhaps add API to hb-ft to encapsulate this code. */ - FT_Face ft_face = nullptr;//hb_ft_font_get_face (font); - if (!ft_face) - { - if (!ft_library) - { - FT_Init_FreeType (&ft_library); -#ifdef HAVE_ATEXIT - atexit (free_ft_library); -#endif - } - - unsigned int blob_length; - const char *blob_data = hb_blob_get_data (font_opts->blob, &blob_length); - - if (FT_New_Memory_Face (ft_library, - (const FT_Byte *) blob_data, - blob_length, - font_opts->face_index, - &ft_face)) - fail (false, "FT_New_Memory_Face fail"); - } - if (!ft_face) - { - /* This allows us to get some boxes at least... */ - cairo_face = cairo_toy_font_face_create ("@cairo:sans", - CAIRO_FONT_SLANT_NORMAL, - CAIRO_FONT_WEIGHT_NORMAL); - } - else - { -#ifdef HAVE_FT_SET_VAR_BLEND_COORDINATES - unsigned int num_coords; - const int *coords = hb_font_get_var_coords_normalized (font, &num_coords); - if (num_coords) - { - FT_Fixed *ft_coords = (FT_Fixed *) calloc (num_coords, sizeof (FT_Fixed)); - if (ft_coords) - { - for (unsigned int i = 0; i < num_coords; i++) - ft_coords[i] = coords[i] << 2; - FT_Set_Var_Blend_Coordinates (ft_face, num_coords, ft_coords); - free (ft_coords); - } - } -#endif - - cairo_face = cairo_ft_font_face_create_for_ft_face (ft_face, font_opts->ft_load_flags); - } - cairo_matrix_t ctm, font_matrix; - cairo_font_options_t *font_options; - - cairo_matrix_init_identity (&ctm); - cairo_matrix_init_scale (&font_matrix, - font_opts->font_size_x, - font_opts->font_size_y); - font_options = cairo_font_options_create (); - cairo_font_options_set_hint_style (font_options, CAIRO_HINT_STYLE_NONE); - cairo_font_options_set_hint_metrics (font_options, CAIRO_HINT_METRICS_OFF); - - cairo_scaled_font_t *scaled_font = cairo_scaled_font_create (cairo_face, - &font_matrix, - &ctm, - font_options); - - cairo_font_options_destroy (font_options); - cairo_font_face_destroy (cairo_face); - - static cairo_user_data_key_t key; - if (cairo_scaled_font_set_user_data (scaled_font, - &key, - (void *) font, - (cairo_destroy_func_t) hb_font_destroy)) - hb_font_destroy (font); - - return scaled_font; -} - -bool -helper_cairo_scaled_font_has_color (cairo_scaled_font_t *scaled_font) -{ - bool ret = false; -#ifdef FT_HAS_COLOR - FT_Face ft_face = cairo_ft_scaled_font_lock_face (scaled_font); - if (ft_face) - { - if (FT_HAS_COLOR (ft_face)) - ret = true; - cairo_ft_scaled_font_unlock_face (scaled_font); - } -#endif - return ret; -} - - -enum class image_protocol_t { - NONE = 0, - ITERM2, - KITTY, -}; - -struct finalize_closure_t { - void (*callback)(finalize_closure_t *); - cairo_surface_t *surface; - cairo_write_func_t write_func; - void *closure; - image_protocol_t protocol; -}; -static cairo_user_data_key_t finalize_closure_key; - - -static void -finalize_ansi (finalize_closure_t *closure) -{ - cairo_status_t status; - status = helper_cairo_surface_write_to_ansi_stream (closure->surface, - closure->write_func, - closure->closure); - if (status != CAIRO_STATUS_SUCCESS) - fail (false, "Failed to write output: %s", - cairo_status_to_string (status)); -} - -static cairo_surface_t * -_cairo_ansi_surface_create_for_stream (cairo_write_func_t write_func, - void *closure, - double width, - double height, - cairo_content_t content, - image_protocol_t protocol HB_UNUSED) -{ - cairo_surface_t *surface; - int w = ceil (width); - int h = ceil (height); - - switch (content) { - case CAIRO_CONTENT_ALPHA: - surface = cairo_image_surface_create (CAIRO_FORMAT_A8, w, h); - break; - default: - case CAIRO_CONTENT_COLOR: - surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h); - break; - case CAIRO_CONTENT_COLOR_ALPHA: - surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h); - break; - } - cairo_status_t status = cairo_surface_status (surface); - if (status != CAIRO_STATUS_SUCCESS) - fail (false, "Failed to create cairo surface: %s", - cairo_status_to_string (status)); - - finalize_closure_t *ansi_closure = g_new0 (finalize_closure_t, 1); - ansi_closure->callback = finalize_ansi; - ansi_closure->surface = surface; - ansi_closure->write_func = write_func; - ansi_closure->closure = closure; - - if (cairo_surface_set_user_data (surface, - &finalize_closure_key, - (void *) ansi_closure, - (cairo_destroy_func_t) g_free)) - g_free ((void *) closure); - - return surface; -} - - -#ifdef CAIRO_HAS_PNG_FUNCTIONS - -static cairo_status_t -byte_array_write_func (void *closure, - const unsigned char *data, - unsigned int size) -{ - g_byte_array_append ((GByteArray *) closure, data, size); - return CAIRO_STATUS_SUCCESS; -} - -static void -finalize_png (finalize_closure_t *closure) -{ - cairo_status_t status; - GByteArray *bytes = nullptr; - GString *string; - gchar *base64; - size_t base64_len; - - if (closure->protocol == image_protocol_t::NONE) - { - status = cairo_surface_write_to_png_stream (closure->surface, - closure->write_func, - closure->closure); - } - else - { - bytes = g_byte_array_new (); - status = cairo_surface_write_to_png_stream (closure->surface, - byte_array_write_func, - bytes); - } - - if (status != CAIRO_STATUS_SUCCESS) - fail (false, "Failed to write output: %s", - cairo_status_to_string (status)); - - if (closure->protocol == image_protocol_t::NONE) - return; - - base64 = g_base64_encode (bytes->data, bytes->len); - base64_len = strlen (base64); - - string = g_string_new (NULL); - if (closure->protocol == image_protocol_t::ITERM2) - { - /* https://iterm2.com/documentation-images.html */ - g_string_printf (string, "\033]1337;File=inline=1;size=%zu:%s\a\n", - base64_len, base64); - } - else if (closure->protocol == image_protocol_t::KITTY) - { -#define CHUNK_SIZE 4096 - /* https://sw.kovidgoyal.net/kitty/graphics-protocol.html */ - for (size_t pos = 0; pos < base64_len; pos += CHUNK_SIZE) - { - size_t len = base64_len - pos; - - if (pos == 0) - g_string_append (string, "\033_Ga=T,f=100,m="); - else - g_string_append (string, "\033_Gm="); - - if (len > CHUNK_SIZE) - { - g_string_append (string, "1;"); - g_string_append_len (string, base64 + pos, CHUNK_SIZE); - } - else - { - g_string_append (string, "0;"); - g_string_append_len (string, base64 + pos, len); - } - - g_string_append (string, "\033\\"); - } - g_string_append (string, "\n"); -#undef CHUNK_SIZE - } - - closure->write_func (closure->closure, (unsigned char *) string->str, string->len); - - g_byte_array_unref (bytes); - g_free (base64); - g_string_free (string, TRUE); -} - -static cairo_surface_t * -_cairo_png_surface_create_for_stream (cairo_write_func_t write_func, - void *closure, - double width, - double height, - cairo_content_t content, - image_protocol_t protocol) -{ - cairo_surface_t *surface; - int w = ceil (width); - int h = ceil (height); - - switch (content) { - case CAIRO_CONTENT_ALPHA: - surface = cairo_image_surface_create (CAIRO_FORMAT_A8, w, h); - break; - default: - case CAIRO_CONTENT_COLOR: - surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h); - break; - case CAIRO_CONTENT_COLOR_ALPHA: - surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h); - break; - } - cairo_status_t status = cairo_surface_status (surface); - if (status != CAIRO_STATUS_SUCCESS) - fail (false, "Failed to create cairo surface: %s", - cairo_status_to_string (status)); - - finalize_closure_t *png_closure = g_new0 (finalize_closure_t, 1); - png_closure->callback = finalize_png; - png_closure->surface = surface; - png_closure->write_func = write_func; - png_closure->closure = closure; - png_closure->protocol = protocol; - - if (cairo_surface_set_user_data (surface, - &finalize_closure_key, - (void *) png_closure, - (cairo_destroy_func_t) g_free)) - g_free ((void *) closure); - - return surface; -} - -#endif - -static cairo_status_t -stdio_write_func (void *closure, - const unsigned char *data, - unsigned int size) -{ - FILE *fp = (FILE *) closure; - - while (size) { - size_t ret = fwrite (data, 1, size, fp); - size -= ret; - data += ret; - if (size && ferror (fp)) - fail (false, "Failed to write output: %s", strerror (errno)); - } - - return CAIRO_STATUS_SUCCESS; -} - -const char *helper_cairo_supported_formats[] = -{ - "ansi", - #ifdef CAIRO_HAS_PNG_FUNCTIONS - "png", - #endif - #ifdef CAIRO_HAS_SVG_SURFACE - "svg", - #endif - #ifdef CAIRO_HAS_PDF_SURFACE - "pdf", - #endif - #ifdef CAIRO_HAS_PS_SURFACE - "ps", - #ifdef HAS_EPS - "eps", - #endif - #endif - nullptr -}; - -cairo_t * -helper_cairo_create_context (double w, double h, - view_options_t *view_opts, - output_options_t *out_opts, - cairo_content_t content) -{ - cairo_surface_t *(*constructor) (cairo_write_func_t write_func, - void *closure, - double width, - double height) = nullptr; - cairo_surface_t *(*constructor2) (cairo_write_func_t write_func, - void *closure, - double width, - double height, - cairo_content_t content, - image_protocol_t protocol) = nullptr; - - image_protocol_t protocol = image_protocol_t::NONE; - const char *extension = out_opts->output_format; - if (!extension) { -#if HAVE_ISATTY - if (isatty (fileno (out_opts->get_file_handle ()))) - { -#ifdef CAIRO_HAS_PNG_FUNCTIONS - const char *name; - /* https://gitlab.com/gnachman/iterm2/-/issues/7154 */ - if ((name = getenv ("LC_TERMINAL")) != nullptr && - 0 == g_ascii_strcasecmp (name, "iTerm2")) - { - extension = "png"; - protocol = image_protocol_t::ITERM2; - } - else if ((name = getenv ("TERM")) != nullptr && - 0 == g_ascii_strcasecmp (name, "xterm-kitty")) - { - extension = "png"; - protocol = image_protocol_t::KITTY; - } - else - extension = "ansi"; -#else - extension = "ansi"; -#endif - } - else -#endif - { -#ifdef CAIRO_HAS_PNG_FUNCTIONS - extension = "png"; -#else - extension = "ansi"; -#endif - } - } - if (0) - ; - else if (0 == g_ascii_strcasecmp (extension, "ansi")) - constructor2 = _cairo_ansi_surface_create_for_stream; - #ifdef CAIRO_HAS_PNG_FUNCTIONS - else if (0 == g_ascii_strcasecmp (extension, "png")) - constructor2 = _cairo_png_surface_create_for_stream; - #endif - #ifdef CAIRO_HAS_SVG_SURFACE - else if (0 == g_ascii_strcasecmp (extension, "svg")) - constructor = cairo_svg_surface_create_for_stream; - #endif - #ifdef CAIRO_HAS_PDF_SURFACE - else if (0 == g_ascii_strcasecmp (extension, "pdf")) - constructor = cairo_pdf_surface_create_for_stream; - #endif - #ifdef CAIRO_HAS_PS_SURFACE - else if (0 == g_ascii_strcasecmp (extension, "ps")) - constructor = cairo_ps_surface_create_for_stream; - #ifdef HAS_EPS - else if (0 == g_ascii_strcasecmp (extension, "eps")) - constructor = _cairo_eps_surface_create_for_stream; - #endif - #endif - - - unsigned int fr, fg, fb, fa, br, bg, bb, ba; - const char *color; - br = bg = bb = 0; ba = 255; - color = view_opts->back ? view_opts->back : DEFAULT_BACK; - sscanf (color + (*color=='#'), "%2x%2x%2x%2x", &br, &bg, &bb, &ba); - fr = fg = fb = 0; fa = 255; - color = view_opts->fore ? view_opts->fore : DEFAULT_FORE; - sscanf (color + (*color=='#'), "%2x%2x%2x%2x", &fr, &fg, &fb, &fa); - - if (content == CAIRO_CONTENT_ALPHA) - { - if (view_opts->annotate || - br != bg || bg != bb || - fr != fg || fg != fb) - content = CAIRO_CONTENT_COLOR; - } - if (ba != 255) - content = CAIRO_CONTENT_COLOR_ALPHA; - - cairo_surface_t *surface; - FILE *f = out_opts->get_file_handle (); - if (constructor) - surface = constructor (stdio_write_func, f, w, h); - else if (constructor2) - surface = constructor2 (stdio_write_func, f, w, h, content, protocol); - else - fail (false, "Unknown output format `%s'; supported formats are: %s%s", - extension, - g_strjoinv ("/", const_cast (helper_cairo_supported_formats)), - out_opts->explicit_output_format ? "" : - "\nTry setting format using --output-format"); - - cairo_t *cr = cairo_create (surface); - content = cairo_surface_get_content (surface); - - switch (content) { - case CAIRO_CONTENT_ALPHA: - cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); - cairo_set_source_rgba (cr, 1., 1., 1., br / 255.); - cairo_paint (cr); - cairo_set_source_rgba (cr, 1., 1., 1., - (fr / 255.) * (fa / 255.) + (br / 255) * (1 - (fa / 255.))); - break; - default: - case CAIRO_CONTENT_COLOR: - case CAIRO_CONTENT_COLOR_ALPHA: - cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); - cairo_set_source_rgba (cr, br / 255., bg / 255., bb / 255., ba / 255.); - cairo_paint (cr); - cairo_set_operator (cr, CAIRO_OPERATOR_OVER); - cairo_set_source_rgba (cr, fr / 255., fg / 255., fb / 255., fa / 255.); - break; - } - - cairo_surface_destroy (surface); - return cr; -} - -void -helper_cairo_destroy_context (cairo_t *cr) -{ - finalize_closure_t *closure = (finalize_closure_t *) - cairo_surface_get_user_data (cairo_get_target (cr), - &finalize_closure_key); - if (closure) - closure->callback (closure); - - cairo_status_t status = cairo_status (cr); - if (status != CAIRO_STATUS_SUCCESS) - fail (false, "Failed: %s", - cairo_status_to_string (status)); - cairo_destroy (cr); -} - - -void -helper_cairo_line_from_buffer (helper_cairo_line_t *l, - hb_buffer_t *buffer, - const char *text, - unsigned int text_len, - int scale_bits, - hb_bool_t utf8_clusters) -{ - memset (l, 0, sizeof (*l)); - - l->num_glyphs = hb_buffer_get_length (buffer); - hb_glyph_info_t *hb_glyph = hb_buffer_get_glyph_infos (buffer, nullptr); - hb_glyph_position_t *hb_position = hb_buffer_get_glyph_positions (buffer, nullptr); - l->glyphs = cairo_glyph_allocate (l->num_glyphs + 1); - - if (text) { - l->utf8 = g_strndup (text, text_len); - l->utf8_len = text_len; - l->num_clusters = l->num_glyphs ? 1 : 0; - for (unsigned int i = 1; i < l->num_glyphs; i++) - if (hb_glyph[i].cluster != hb_glyph[i-1].cluster) - l->num_clusters++; - l->clusters = cairo_text_cluster_allocate (l->num_clusters); - } - - if ((l->num_glyphs && !l->glyphs) || - (l->utf8_len && !l->utf8) || - (l->num_clusters && !l->clusters)) - { - l->finish (); - return; - } - - hb_position_t x = 0, y = 0; - int i; - for (i = 0; i < (int) l->num_glyphs; i++) - { - l->glyphs[i].index = hb_glyph[i].codepoint; - l->glyphs[i].x = scalbn ((double) hb_position->x_offset + x, scale_bits); - l->glyphs[i].y = scalbn ((double) -hb_position->y_offset + y, scale_bits); - x += hb_position->x_advance; - y += -hb_position->y_advance; - - hb_position++; - } - l->glyphs[i].index = -1; - l->glyphs[i].x = scalbn ((double) x, scale_bits); - l->glyphs[i].y = scalbn ((double) y, scale_bits); - - if (l->num_clusters) { - memset ((void *) l->clusters, 0, l->num_clusters * sizeof (l->clusters[0])); - hb_bool_t backward = HB_DIRECTION_IS_BACKWARD (hb_buffer_get_direction (buffer)); - l->cluster_flags = backward ? CAIRO_TEXT_CLUSTER_FLAG_BACKWARD : (cairo_text_cluster_flags_t) 0; - unsigned int cluster = 0; - const char *start = l->utf8, *end; - l->clusters[cluster].num_glyphs++; - if (backward) { - for (i = l->num_glyphs - 2; i >= 0; i--) { - if (hb_glyph[i].cluster != hb_glyph[i+1].cluster) { - g_assert (hb_glyph[i].cluster > hb_glyph[i+1].cluster); - if (utf8_clusters) - end = start + hb_glyph[i].cluster - hb_glyph[i+1].cluster; - else - end = g_utf8_offset_to_pointer (start, hb_glyph[i].cluster - hb_glyph[i+1].cluster); - l->clusters[cluster].num_bytes = end - start; - start = end; - cluster++; - } - l->clusters[cluster].num_glyphs++; - } - l->clusters[cluster].num_bytes = l->utf8 + text_len - start; - } else { - for (i = 1; i < (int) l->num_glyphs; i++) { - if (hb_glyph[i].cluster != hb_glyph[i-1].cluster) { - g_assert (hb_glyph[i].cluster > hb_glyph[i-1].cluster); - if (utf8_clusters) - end = start + hb_glyph[i].cluster - hb_glyph[i-1].cluster; - else - end = g_utf8_offset_to_pointer (start, hb_glyph[i].cluster - hb_glyph[i-1].cluster); - l->clusters[cluster].num_bytes = end - start; - start = end; - cluster++; - } - l->clusters[cluster].num_glyphs++; - } - l->clusters[cluster].num_bytes = l->utf8 + text_len - start; - } - } -} diff --git a/util/helper-cairo.hh b/util/helper-cairo.hh index 5bfbf7b34..e219e646b 100644 --- a/util/helper-cairo.hh +++ b/util/helper-cairo.hh @@ -27,28 +27,558 @@ #ifndef HELPER_CAIRO_HH #define HELPER_CAIRO_HH -#include "hb.hh" -#include "options.hh" +#include "view-options.hh" +#include "output-options.hh" -#include +#include +#include +#include FT_MULTIPLE_MASTERS_H + +#include "helper-cairo-ansi.hh" +#ifdef CAIRO_HAS_SVG_SURFACE +# include +#endif +#ifdef CAIRO_HAS_PDF_SURFACE +# include +#endif +#ifdef CAIRO_HAS_PS_SURFACE +# include +# if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,6,0) +# define HAS_EPS 1 + +static cairo_surface_t * +_cairo_eps_surface_create_for_stream (cairo_write_func_t write_func, + void *closure, + double width, + double height) +{ + cairo_surface_t *surface; + + surface = cairo_ps_surface_create_for_stream (write_func, closure, width, height); + cairo_ps_surface_set_eps (surface, true); + + return surface; +} + +# else +# undef HAS_EPS +# endif +#endif -cairo_scaled_font_t * -helper_cairo_create_scaled_font (const font_options_t *font_opts); +static FT_Library ft_library; -bool -helper_cairo_scaled_font_has_color (cairo_scaled_font_t *scaled_font); +#ifdef HAVE_ATEXIT +static inline +void free_ft_library () +{ + FT_Done_FreeType (ft_library); +} +#endif -extern const char *helper_cairo_supported_formats[]; +static inline cairo_scaled_font_t * +helper_cairo_create_scaled_font (const font_options_t *font_opts) +{ + hb_font_t *font = hb_font_reference (font_opts->get_font ()); -cairo_t * + cairo_font_face_t *cairo_face; + /* We cannot use the FT_Face from hb_font_t, as doing so will confuse hb_font_t because + * cairo will reset the face size. As such, create new face... + * TODO Perhaps add API to hb-ft to encapsulate this code. */ + FT_Face ft_face = nullptr;//hb_ft_font_get_face (font); + if (!ft_face) + { + if (!ft_library) + { + FT_Init_FreeType (&ft_library); +#ifdef HAVE_ATEXIT + atexit (free_ft_library); +#endif + } + + unsigned int blob_length; + const char *blob_data = hb_blob_get_data (font_opts->get_blob (), &blob_length); + + if (FT_New_Memory_Face (ft_library, + (const FT_Byte *) blob_data, + blob_length, + font_opts->face_index, + &ft_face)) + fail (false, "FT_New_Memory_Face fail"); + } + if (!ft_face) + { + /* This allows us to get some boxes at least... */ + cairo_face = cairo_toy_font_face_create ("@cairo:sans", + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + } + else + { +#ifdef HAVE_FT_SET_VAR_BLEND_COORDINATES + unsigned int num_coords; + const int *coords = hb_font_get_var_coords_normalized (font, &num_coords); + if (num_coords) + { + FT_Fixed *ft_coords = (FT_Fixed *) calloc (num_coords, sizeof (FT_Fixed)); + if (ft_coords) + { + for (unsigned int i = 0; i < num_coords; i++) + ft_coords[i] = coords[i] << 2; + FT_Set_Var_Blend_Coordinates (ft_face, num_coords, ft_coords); + free (ft_coords); + } + } +#endif + + cairo_face = cairo_ft_font_face_create_for_ft_face (ft_face, font_opts->ft_load_flags); + } + cairo_matrix_t ctm, font_matrix; + cairo_font_options_t *font_options; + + cairo_matrix_init_identity (&ctm); + cairo_matrix_init_scale (&font_matrix, + font_opts->font_size_x, + font_opts->font_size_y); + font_options = cairo_font_options_create (); + cairo_font_options_set_hint_style (font_options, CAIRO_HINT_STYLE_NONE); + cairo_font_options_set_hint_metrics (font_options, CAIRO_HINT_METRICS_OFF); + + cairo_scaled_font_t *scaled_font = cairo_scaled_font_create (cairo_face, + &font_matrix, + &ctm, + font_options); + + cairo_font_options_destroy (font_options); + cairo_font_face_destroy (cairo_face); + + static cairo_user_data_key_t key; + if (cairo_scaled_font_set_user_data (scaled_font, + &key, + (void *) font, + (cairo_destroy_func_t) hb_font_destroy)) + hb_font_destroy (font); + + return scaled_font; +} + +static inline bool +helper_cairo_scaled_font_has_color (cairo_scaled_font_t *scaled_font) +{ + bool ret = false; +#ifdef FT_HAS_COLOR + FT_Face ft_face = cairo_ft_scaled_font_lock_face (scaled_font); + if (ft_face) + { + if (FT_HAS_COLOR (ft_face)) + ret = true; + cairo_ft_scaled_font_unlock_face (scaled_font); + } +#endif + return ret; +} + + +enum class image_protocol_t { + NONE = 0, + ITERM2, + KITTY, +}; + +struct finalize_closure_t { + void (*callback)(finalize_closure_t *); + cairo_surface_t *surface; + cairo_write_func_t write_func; + void *closure; + image_protocol_t protocol; +}; +static cairo_user_data_key_t finalize_closure_key; + + +static void +finalize_ansi (finalize_closure_t *closure) +{ + cairo_status_t status; + status = helper_cairo_surface_write_to_ansi_stream (closure->surface, + closure->write_func, + closure->closure); + if (status != CAIRO_STATUS_SUCCESS) + fail (false, "Failed to write output: %s", + cairo_status_to_string (status)); +} + +static cairo_surface_t * +_cairo_ansi_surface_create_for_stream (cairo_write_func_t write_func, + void *closure, + double width, + double height, + cairo_content_t content, + image_protocol_t protocol HB_UNUSED) +{ + cairo_surface_t *surface; + int w = ceil (width); + int h = ceil (height); + + switch (content) { + case CAIRO_CONTENT_ALPHA: + surface = cairo_image_surface_create (CAIRO_FORMAT_A8, w, h); + break; + default: + case CAIRO_CONTENT_COLOR: + surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h); + break; + case CAIRO_CONTENT_COLOR_ALPHA: + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h); + break; + } + cairo_status_t status = cairo_surface_status (surface); + if (status != CAIRO_STATUS_SUCCESS) + fail (false, "Failed to create cairo surface: %s", + cairo_status_to_string (status)); + + finalize_closure_t *ansi_closure = g_new0 (finalize_closure_t, 1); + ansi_closure->callback = finalize_ansi; + ansi_closure->surface = surface; + ansi_closure->write_func = write_func; + ansi_closure->closure = closure; + + if (cairo_surface_set_user_data (surface, + &finalize_closure_key, + (void *) ansi_closure, + (cairo_destroy_func_t) g_free)) + g_free ((void *) closure); + + return surface; +} + + +#ifdef CAIRO_HAS_PNG_FUNCTIONS + +static cairo_status_t +byte_array_write_func (void *closure, + const unsigned char *data, + unsigned int size) +{ + g_byte_array_append ((GByteArray *) closure, data, size); + return CAIRO_STATUS_SUCCESS; +} + +static void +finalize_png (finalize_closure_t *closure) +{ + cairo_status_t status; + GByteArray *bytes = nullptr; + GString *string; + gchar *base64; + size_t base64_len; + + if (closure->protocol == image_protocol_t::NONE) + { + status = cairo_surface_write_to_png_stream (closure->surface, + closure->write_func, + closure->closure); + } + else + { + bytes = g_byte_array_new (); + status = cairo_surface_write_to_png_stream (closure->surface, + byte_array_write_func, + bytes); + } + + if (status != CAIRO_STATUS_SUCCESS) + fail (false, "Failed to write output: %s", + cairo_status_to_string (status)); + + if (closure->protocol == image_protocol_t::NONE) + return; + + base64 = g_base64_encode (bytes->data, bytes->len); + base64_len = strlen (base64); + + string = g_string_new (NULL); + if (closure->protocol == image_protocol_t::ITERM2) + { + /* https://iterm2.com/documentation-images.html */ + g_string_printf (string, "\033]1337;File=inline=1;size=%zu:%s\a\n", + base64_len, base64); + } + else if (closure->protocol == image_protocol_t::KITTY) + { +#define CHUNK_SIZE 4096 + /* https://sw.kovidgoyal.net/kitty/graphics-protocol.html */ + for (size_t pos = 0; pos < base64_len; pos += CHUNK_SIZE) + { + size_t len = base64_len - pos; + + if (pos == 0) + g_string_append (string, "\033_Ga=T,f=100,m="); + else + g_string_append (string, "\033_Gm="); + + if (len > CHUNK_SIZE) + { + g_string_append (string, "1;"); + g_string_append_len (string, base64 + pos, CHUNK_SIZE); + } + else + { + g_string_append (string, "0;"); + g_string_append_len (string, base64 + pos, len); + } + + g_string_append (string, "\033\\"); + } + g_string_append (string, "\n"); +#undef CHUNK_SIZE + } + + closure->write_func (closure->closure, (unsigned char *) string->str, string->len); + + g_byte_array_unref (bytes); + g_free (base64); + g_string_free (string, TRUE); +} + +static cairo_surface_t * +_cairo_png_surface_create_for_stream (cairo_write_func_t write_func, + void *closure, + double width, + double height, + cairo_content_t content, + image_protocol_t protocol) +{ + cairo_surface_t *surface; + int w = ceil (width); + int h = ceil (height); + + switch (content) { + case CAIRO_CONTENT_ALPHA: + surface = cairo_image_surface_create (CAIRO_FORMAT_A8, w, h); + break; + default: + case CAIRO_CONTENT_COLOR: + surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h); + break; + case CAIRO_CONTENT_COLOR_ALPHA: + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h); + break; + } + cairo_status_t status = cairo_surface_status (surface); + if (status != CAIRO_STATUS_SUCCESS) + fail (false, "Failed to create cairo surface: %s", + cairo_status_to_string (status)); + + finalize_closure_t *png_closure = g_new0 (finalize_closure_t, 1); + png_closure->callback = finalize_png; + png_closure->surface = surface; + png_closure->write_func = write_func; + png_closure->closure = closure; + png_closure->protocol = protocol; + + if (cairo_surface_set_user_data (surface, + &finalize_closure_key, + (void *) png_closure, + (cairo_destroy_func_t) g_free)) + g_free ((void *) closure); + + return surface; +} + +#endif + +static cairo_status_t +stdio_write_func (void *closure, + const unsigned char *data, + unsigned int size) +{ + FILE *fp = (FILE *) closure; + + while (size) { + size_t ret = fwrite (data, 1, size, fp); + size -= ret; + data += ret; + if (size && ferror (fp)) + fail (false, "Failed to write output: %s", strerror (errno)); + } + + return CAIRO_STATUS_SUCCESS; +} + +static const char *helper_cairo_supported_formats[] = +{ + "ansi", + #ifdef CAIRO_HAS_PNG_FUNCTIONS + "png", + #endif + #ifdef CAIRO_HAS_SVG_SURFACE + "svg", + #endif + #ifdef CAIRO_HAS_PDF_SURFACE + "pdf", + #endif + #ifdef CAIRO_HAS_PS_SURFACE + "ps", + #ifdef HAS_EPS + "eps", + #endif + #endif + nullptr +}; + +static inline cairo_t * helper_cairo_create_context (double w, double h, view_options_t *view_opts, output_options_t *out_opts, - cairo_content_t content); + cairo_content_t content) +{ + cairo_surface_t *(*constructor) (cairo_write_func_t write_func, + void *closure, + double width, + double height) = nullptr; + cairo_surface_t *(*constructor2) (cairo_write_func_t write_func, + void *closure, + double width, + double height, + cairo_content_t content, + image_protocol_t protocol) = nullptr; -void -helper_cairo_destroy_context (cairo_t *cr); + image_protocol_t protocol = image_protocol_t::NONE; + const char *extension = out_opts->output_format; + if (!extension) { +#if HAVE_ISATTY + if (isatty (fileno (out_opts->get_file_handle ()))) + { +#ifdef CAIRO_HAS_PNG_FUNCTIONS + const char *name; + /* https://gitlab.com/gnachman/iterm2/-/issues/7154 */ + if ((name = getenv ("LC_TERMINAL")) != nullptr && + 0 == g_ascii_strcasecmp (name, "iTerm2")) + { + extension = "png"; + protocol = image_protocol_t::ITERM2; + } + else if ((name = getenv ("TERM")) != nullptr && + 0 == g_ascii_strcasecmp (name, "xterm-kitty")) + { + extension = "png"; + protocol = image_protocol_t::KITTY; + } + else + extension = "ansi"; +#else + extension = "ansi"; +#endif + } + else +#endif + { +#ifdef CAIRO_HAS_PNG_FUNCTIONS + extension = "png"; +#else + extension = "ansi"; +#endif + } + } + if (0) + ; + else if (0 == g_ascii_strcasecmp (extension, "ansi")) + constructor2 = _cairo_ansi_surface_create_for_stream; + #ifdef CAIRO_HAS_PNG_FUNCTIONS + else if (0 == g_ascii_strcasecmp (extension, "png")) + constructor2 = _cairo_png_surface_create_for_stream; + #endif + #ifdef CAIRO_HAS_SVG_SURFACE + else if (0 == g_ascii_strcasecmp (extension, "svg")) + constructor = cairo_svg_surface_create_for_stream; + #endif + #ifdef CAIRO_HAS_PDF_SURFACE + else if (0 == g_ascii_strcasecmp (extension, "pdf")) + constructor = cairo_pdf_surface_create_for_stream; + #endif + #ifdef CAIRO_HAS_PS_SURFACE + else if (0 == g_ascii_strcasecmp (extension, "ps")) + constructor = cairo_ps_surface_create_for_stream; + #ifdef HAS_EPS + else if (0 == g_ascii_strcasecmp (extension, "eps")) + constructor = _cairo_eps_surface_create_for_stream; + #endif + #endif + + + unsigned int fr, fg, fb, fa, br, bg, bb, ba; + const char *color; + br = bg = bb = 0; ba = 255; + color = view_opts->back ? view_opts->back : DEFAULT_BACK; + sscanf (color + (*color=='#'), "%2x%2x%2x%2x", &br, &bg, &bb, &ba); + fr = fg = fb = 0; fa = 255; + color = view_opts->fore ? view_opts->fore : DEFAULT_FORE; + sscanf (color + (*color=='#'), "%2x%2x%2x%2x", &fr, &fg, &fb, &fa); + + if (content == CAIRO_CONTENT_ALPHA) + { + if (view_opts->annotate || + br != bg || bg != bb || + fr != fg || fg != fb) + content = CAIRO_CONTENT_COLOR; + } + if (ba != 255) + content = CAIRO_CONTENT_COLOR_ALPHA; + + cairo_surface_t *surface; + FILE *f = out_opts->get_file_handle (); + if (constructor) + surface = constructor (stdio_write_func, f, w, h); + else if (constructor2) + surface = constructor2 (stdio_write_func, f, w, h, content, protocol); + else + fail (false, "Unknown output format `%s'; supported formats are: %s%s", + extension, + g_strjoinv ("/", const_cast (helper_cairo_supported_formats)), + out_opts->explicit_output_format ? "" : + "\nTry setting format using --output-format"); + + cairo_t *cr = cairo_create (surface); + content = cairo_surface_get_content (surface); + + switch (content) { + case CAIRO_CONTENT_ALPHA: + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba (cr, 1., 1., 1., br / 255.); + cairo_paint (cr); + cairo_set_source_rgba (cr, 1., 1., 1., + (fr / 255.) * (fa / 255.) + (br / 255) * (1 - (fa / 255.))); + break; + default: + case CAIRO_CONTENT_COLOR: + case CAIRO_CONTENT_COLOR_ALPHA: + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba (cr, br / 255., bg / 255., bb / 255., ba / 255.); + cairo_paint (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); + cairo_set_source_rgba (cr, fr / 255., fg / 255., fb / 255., fa / 255.); + break; + } + + cairo_surface_destroy (surface); + return cr; +} + +static inline void +helper_cairo_destroy_context (cairo_t *cr) +{ + finalize_closure_t *closure = (finalize_closure_t *) + cairo_surface_get_user_data (cairo_get_target (cr), + &finalize_closure_key); + if (closure) + closure->callback (closure); + + cairo_status_t status = cairo_status (cr); + if (status != CAIRO_STATUS_SUCCESS) + fail (false, "Failed: %s", + cairo_status_to_string (status)); + cairo_destroy (cr); +} struct helper_cairo_line_t { @@ -75,12 +605,94 @@ struct helper_cairo_line_t { } }; -void +static inline void helper_cairo_line_from_buffer (helper_cairo_line_t *l, hb_buffer_t *buffer, const char *text, unsigned int text_len, int scale_bits, - hb_bool_t utf8_clusters); + hb_bool_t utf8_clusters) +{ + memset (l, 0, sizeof (*l)); + + l->num_glyphs = hb_buffer_get_length (buffer); + hb_glyph_info_t *hb_glyph = hb_buffer_get_glyph_infos (buffer, nullptr); + hb_glyph_position_t *hb_position = hb_buffer_get_glyph_positions (buffer, nullptr); + l->glyphs = cairo_glyph_allocate (l->num_glyphs + 1); + + if (text) { + l->utf8 = g_strndup (text, text_len); + l->utf8_len = text_len; + l->num_clusters = l->num_glyphs ? 1 : 0; + for (unsigned int i = 1; i < l->num_glyphs; i++) + if (hb_glyph[i].cluster != hb_glyph[i-1].cluster) + l->num_clusters++; + l->clusters = cairo_text_cluster_allocate (l->num_clusters); + } + + if ((l->num_glyphs && !l->glyphs) || + (l->utf8_len && !l->utf8) || + (l->num_clusters && !l->clusters)) + { + l->finish (); + return; + } + + hb_position_t x = 0, y = 0; + int i; + for (i = 0; i < (int) l->num_glyphs; i++) + { + l->glyphs[i].index = hb_glyph[i].codepoint; + l->glyphs[i].x = scalbn ((double) hb_position->x_offset + x, scale_bits); + l->glyphs[i].y = scalbn ((double) -hb_position->y_offset + y, scale_bits); + x += hb_position->x_advance; + y += -hb_position->y_advance; + + hb_position++; + } + l->glyphs[i].index = -1; + l->glyphs[i].x = scalbn ((double) x, scale_bits); + l->glyphs[i].y = scalbn ((double) y, scale_bits); + + if (l->num_clusters) { + memset ((void *) l->clusters, 0, l->num_clusters * sizeof (l->clusters[0])); + hb_bool_t backward = HB_DIRECTION_IS_BACKWARD (hb_buffer_get_direction (buffer)); + l->cluster_flags = backward ? CAIRO_TEXT_CLUSTER_FLAG_BACKWARD : (cairo_text_cluster_flags_t) 0; + unsigned int cluster = 0; + const char *start = l->utf8, *end; + l->clusters[cluster].num_glyphs++; + if (backward) { + for (i = l->num_glyphs - 2; i >= 0; i--) { + if (hb_glyph[i].cluster != hb_glyph[i+1].cluster) { + g_assert (hb_glyph[i].cluster > hb_glyph[i+1].cluster); + if (utf8_clusters) + end = start + hb_glyph[i].cluster - hb_glyph[i+1].cluster; + else + end = g_utf8_offset_to_pointer (start, hb_glyph[i].cluster - hb_glyph[i+1].cluster); + l->clusters[cluster].num_bytes = end - start; + start = end; + cluster++; + } + l->clusters[cluster].num_glyphs++; + } + l->clusters[cluster].num_bytes = l->utf8 + text_len - start; + } else { + for (i = 1; i < (int) l->num_glyphs; i++) { + if (hb_glyph[i].cluster != hb_glyph[i-1].cluster) { + g_assert (hb_glyph[i].cluster > hb_glyph[i-1].cluster); + if (utf8_clusters) + end = start + hb_glyph[i].cluster - hb_glyph[i-1].cluster; + else + end = g_utf8_offset_to_pointer (start, hb_glyph[i].cluster - hb_glyph[i-1].cluster); + l->clusters[cluster].num_bytes = end - start; + start = end; + cluster++; + } + l->clusters[cluster].num_glyphs++; + } + l->clusters[cluster].num_bytes = l->utf8 + text_len - start; + } + } +} #endif diff --git a/util/main-font-text.hh b/util/main-font-text.hh index 7915c3786..a3e8d42bc 100644 --- a/util/main-font-text.hh +++ b/util/main-font-text.hh @@ -27,70 +27,45 @@ #ifndef HB_MAIN_FONT_TEXT_HH #define HB_MAIN_FONT_TEXT_HH -#include "hb.hh" #include "options.hh" /* main() body for utilities taking font and processing text.*/ -static char * -locale_to_utf8 (char *s) +template +int +main_font_text (int argc, char **argv, int eol = '\n') { - char *t; - GError *error = nullptr; - t = g_locale_to_utf8 (s, -1, nullptr, nullptr, &error); - if (!t) - { - fail (true, "Failed converting text to UTF-8"); - } - - return t; -} - -template -struct main_font_text_t -{ - main_font_text_t () - : options ("[FONT-FILE] [TEXT]"), - font_opts (&options, default_font_size, subpixel_bits), - input (&options), - consumer (&options) {} - - int - main (int argc, char **argv) - { - options.parse (&argc, &argv); - - argc--, argv++; - if (argc && !font_opts.font_file) font_opts.font_file = locale_to_utf8 (argv[0]), argc--, argv++; - if (argc && !input.text && !input.text_file) input.text = locale_to_utf8 (argv[0]), argc--, argv++; - if (argc) - fail (true, "Too many arguments on the command line"); - if (!font_opts.font_file) - options.usage (); - if (!input.text && !input.text_file) - input.text_file = g_strdup ("-"); - - hb_buffer_t *buffer = hb_buffer_create (); - consumer.init (buffer, &font_opts); - hb_buffer_destroy (buffer); - - unsigned int text_len; - const char *text; - while ((text = input.get_line (&text_len, eol))) - consumer.consume_line (text, text_len, input.text_before, input.text_after); - - consumer.finish (&font_opts); - - return consumer.failed ? 1 : 0; - } - - protected: - option_parser_t options; font_options_t font_opts; text_options_t input; consumer_t consumer; -}; + + option_parser_t options ("[FONT-FILE] [TEXT]"); + font_opts.add_options (&options); + input.add_options (&options); + consumer.add_options (&options); + options.parse (&argc, &argv); + + argc--, argv++; + if (argc && !font_opts.font_file) font_opts.font_file = locale_to_utf8 (argv[0]), argc--, argv++; + if (argc && !input.text && !input.text_file) input.text = locale_to_utf8 (argv[0]), argc--, argv++; + if (argc) + fail (true, "Too many arguments on the command line"); + if (!font_opts.font_file) + options.usage (); + if (!input.text && !input.text_file) + input.text_file = g_strdup ("-"); + + consumer.init (&font_opts); + + unsigned int text_len; + const char *text; + while ((text = input.get_line (&text_len, eol))) + consumer.consume_line (text, text_len, input.text_before, input.text_after); + + consumer.finish (&font_opts); + + return consumer.failed ? 1 : 0; +} #endif - diff --git a/util/meson.build b/util/meson.build index 5aa7585e8..fdab620fc 100644 --- a/util/meson.build +++ b/util/meson.build @@ -1,26 +1,17 @@ hb_view_sources = [ 'hb-view.cc', - 'options.cc', - 'ansi-print.cc', - 'helper-cairo.cc', - 'helper-cairo-ansi.cc', - 'view-cairo.cc', ] hb_shape_sources = [ 'hb-shape.cc', - 'options.cc', ] hb_ot_shape_closure_sources = [ 'hb-ot-shape-closure.cc', - 'options.cc', ] hb_subset_cli_sources = [ 'hb-subset.cc', - 'options.cc', - 'options-subset.cc', ] util_deps = [freetype_dep, cairo_dep, cairo_ft_dep, glib_dep] diff --git a/util/options.cc b/util/options.cc deleted file mode 100644 index 94595a8a7..000000000 --- a/util/options.cc +++ /dev/null @@ -1,1024 +0,0 @@ -/* - * Copyright © 2011,2012 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 - */ - -#include "options.hh" - -#ifdef HAVE_FREETYPE -#include -#endif -#include - -#define DELIMITERS "<+>{},;&#\\xXuUnNiI\n\t\v\f\r " - -static struct supported_font_funcs_t { - char name[4]; - void (*func) (hb_font_t *); -} supported_font_funcs[] = -{ -#ifdef HAVE_FREETYPE - {"ft", hb_ft_font_set_funcs}, -#endif - {"ot", hb_ot_font_set_funcs}, -}; - - -void -fail (hb_bool_t suggest_help, const char *format, ...) -{ - const char *msg; - - va_list vap; - va_start (vap, format); - msg = g_strdup_vprintf (format, vap); - va_end (vap); - const char *prgname = g_get_prgname (); - g_printerr ("%s: %s\n", prgname, msg); - if (suggest_help) - g_printerr ("Try `%s --help' for more information.\n", prgname); - - exit (1); -} - - -static gchar * -shapers_to_string () -{ - GString *shapers = g_string_new (nullptr); - const char **shaper_list = hb_shape_list_shapers (); - - for (; *shaper_list; shaper_list++) { - g_string_append (shapers, *shaper_list); - g_string_append_c (shapers, ','); - } - g_string_truncate (shapers, MAX (0, (gint)shapers->len - 1)); - - return g_string_free (shapers, false); -} - -static G_GNUC_NORETURN gboolean -show_version (const char *name G_GNUC_UNUSED, - const char *arg G_GNUC_UNUSED, - gpointer data G_GNUC_UNUSED, - GError **error G_GNUC_UNUSED) -{ - g_printf ("%s (%s) %s\n", g_get_prgname (), PACKAGE_NAME, PACKAGE_VERSION); - - char *shapers = shapers_to_string (); - g_printf ("Available shapers: %s\n", shapers); - g_free (shapers); - if (strcmp (HB_VERSION_STRING, hb_version_string ())) - g_printf ("Linked HarfBuzz library has a different version: %s\n", hb_version_string ()); - - exit(0); -} - - -void -option_parser_t::add_main_options () -{ - GOptionEntry entries[] = - { - {"version", 0, G_OPTION_FLAG_NO_ARG, - G_OPTION_ARG_CALLBACK, (gpointer) &show_version, "Show version numbers", nullptr}, - {nullptr} - }; - g_option_context_add_main_entries (context, entries, nullptr); -} - -static gboolean -pre_parse (GOptionContext *context G_GNUC_UNUSED, - GOptionGroup *group G_GNUC_UNUSED, - gpointer data, - GError **error) -{ - option_group_t *option_group = (option_group_t *) data; - option_group->pre_parse (error); - return !*error; -} - -static gboolean -post_parse (GOptionContext *context G_GNUC_UNUSED, - GOptionGroup *group G_GNUC_UNUSED, - gpointer data, - GError **error) -{ - option_group_t *option_group = static_cast(data); - option_group->post_parse (error); - return !*error; -} - -void -option_parser_t::add_group (GOptionEntry *entries, - const gchar *name, - const gchar *description, - const gchar *help_description, - option_group_t *option_group) -{ - GOptionGroup *group = g_option_group_new (name, description, help_description, - static_cast(option_group), nullptr); - g_option_group_add_entries (group, entries); - g_option_group_set_parse_hooks (group, pre_parse, post_parse); - g_option_context_add_group (context, group); -} - -void -option_parser_t::parse (int *argc, char ***argv) -{ - setlocale (LC_ALL, ""); - - GError *parse_error = nullptr; - if (!g_option_context_parse (context, argc, argv, &parse_error)) - { - if (parse_error) - { - fail (true, "%s", parse_error->message); - //g_error_free (parse_error); - } - else - fail (true, "Option parse error"); - } -} - - -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; - switch (sscanf (arg, "%lf%*[ ,]%lf%*[ ,]%lf%*[ ,]%lf", &m.t, &m.r, &m.b, &m.l)) { - case 1: m.r = m.t; HB_FALLTHROUGH; - case 2: m.b = m.t; HB_FALLTHROUGH; - case 3: m.l = m.r; HB_FALLTHROUGH; - case 4: return true; - default: - 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; - } -} - - -static gboolean -parse_shapers (const char *name G_GNUC_UNUSED, - const char *arg, - gpointer data, - GError **error) -{ - shape_options_t *shape_opts = (shape_options_t *) data; - char **shapers = g_strsplit (arg, ",", 0); - - for (char **shaper = shapers; *shaper; shaper++) { - bool found = false; - for (const char **hb_shaper = hb_shape_list_shapers (); *hb_shaper; hb_shaper++) { - if (strcmp (*shaper, *hb_shaper) == 0) { - found = true; - break; - } - } - if (!found) { - g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, - "Unknown or unsupported shaper: %s", *shaper); - g_strfreev (shapers); - return false; - } - } - - g_strfreev (shape_opts->shapers); - shape_opts->shapers = shapers; - return true; -} - -static G_GNUC_NORETURN gboolean -list_shapers (const char *name G_GNUC_UNUSED, - const char *arg G_GNUC_UNUSED, - gpointer data G_GNUC_UNUSED, - GError **error G_GNUC_UNUSED) -{ - for (const char **shaper = hb_shape_list_shapers (); *shaper; shaper++) - g_printf ("%s\n", *shaper); - - exit(0); -} - - -static gboolean -parse_features (const char *name G_GNUC_UNUSED, - const char *arg, - gpointer data, - GError **error G_GNUC_UNUSED) -{ - shape_options_t *shape_opts = (shape_options_t *) data; - char *s = (char *) arg; - size_t l = strlen (s); - char *p; - - shape_opts->num_features = 0; - g_free (shape_opts->features); - shape_opts->features = nullptr; - - /* if the string is quoted, strip the quotes */ - if (s[0] == s[l - 1] && (s[0] == '\"' || s[0] == '\'')) - { - s[l - 1] = '\0'; - s++; - } - - if (!*s) - return true; - - /* count the features first, so we can allocate memory */ - p = s; - do { - shape_opts->num_features++; - p = strchr (p, ','); - if (p) - p++; - } while (p); - - shape_opts->features = (hb_feature_t *) calloc (shape_opts->num_features, sizeof (*shape_opts->features)); - if (!shape_opts->features) - return false; - - /* now do the actual parsing */ - p = s; - shape_opts->num_features = 0; - while (p && *p) { - char *end = strchr (p, ','); - if (hb_feature_from_string (p, end ? end - p : -1, &shape_opts->features[shape_opts->num_features])) - shape_opts->num_features++; - p = end ? end + 1 : nullptr; - } - - return true; -} - -static gboolean -parse_variations (const char *name G_GNUC_UNUSED, - const char *arg, - gpointer data, - GError **error G_GNUC_UNUSED) -{ - font_options_t *font_opts = (font_options_t *) data; - char *s = (char *) arg; - char *p; - - font_opts->num_variations = 0; - g_free (font_opts->variations); - font_opts->variations = nullptr; - - if (!*s) - return true; - - /* count the variations first, so we can allocate memory */ - p = s; - do { - font_opts->num_variations++; - p = strchr (p, ','); - if (p) - p++; - } while (p); - - font_opts->variations = (hb_variation_t *) calloc (font_opts->num_variations, sizeof (*font_opts->variations)); - if (!font_opts->variations) - return false; - - /* now do the actual parsing */ - p = s; - font_opts->num_variations = 0; - while (p && *p) { - char *end = strchr (p, ','); - if (hb_variation_from_string (p, end ? end - p : -1, &font_opts->variations[font_opts->num_variations])) - font_opts->num_variations++; - p = end ? end + 1 : nullptr; - } - - return true; -} - -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 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 - { - - char *s = (char *) arg; - 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 values at: '%s'", s); - return false; - } - - g_string_append_unichar (gs, u); - - s = p; - } - } - - text_opts->text_len = gs->len; - text_opts->text = g_string_free (gs, FALSE); - return true; -} - - -void -view_options_t::add_options (option_parser_t *parser) -{ - GOptionEntry entries[] = - { - {"annotate", 0, 0, G_OPTION_ARG_NONE, &this->annotate, "Annotate output rendering", nullptr}, - {"background", 0, 0, G_OPTION_ARG_STRING, &this->back, "Set background color (default: " DEFAULT_BACK ")", "rrggbb/rrggbbaa"}, - {"foreground", 0, 0, G_OPTION_ARG_STRING, &this->fore, "Set foreground color (default: " DEFAULT_FORE ")", "rrggbb/rrggbbaa"}, - {"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"}, - {nullptr} - }; - parser->add_group (entries, - "view", - "View options:", - "Options for output rendering", - this); -} - -void -shape_options_t::add_options (option_parser_t *parser) -{ - GOptionEntry entries[] = - { - {"list-shapers", 0, G_OPTION_FLAG_NO_ARG, - G_OPTION_ARG_CALLBACK, (gpointer) &list_shapers, "List available shapers and quit", nullptr}, - {"shaper", 0, G_OPTION_FLAG_HIDDEN, - G_OPTION_ARG_CALLBACK, (gpointer) &parse_shapers, "Hidden duplicate of --shapers", nullptr}, - {"shapers", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_shapers, "Set comma-separated list of shapers to try","list"}, - {"direction", 0, 0, G_OPTION_ARG_STRING, &this->direction, "Set text direction (default: auto)", "ltr/rtl/ttb/btt"}, - {"language", 0, 0, G_OPTION_ARG_STRING, &this->language, "Set text language (default: $LANG)", "langstr"}, - {"script", 0, 0, G_OPTION_ARG_STRING, &this->script, "Set text script (default: auto)", "ISO-15924 tag"}, - {"bot", 0, 0, G_OPTION_ARG_NONE, &this->bot, "Treat text as beginning-of-paragraph", nullptr}, - {"eot", 0, 0, G_OPTION_ARG_NONE, &this->eot, "Treat text as end-of-paragraph", nullptr}, - {"preserve-default-ignorables",0, 0, G_OPTION_ARG_NONE, &this->preserve_default_ignorables, "Preserve Default-Ignorable characters", nullptr}, - {"remove-default-ignorables",0, 0, G_OPTION_ARG_NONE, &this->remove_default_ignorables, "Remove Default-Ignorable characters", nullptr}, - {"invisible-glyph", 0, 0, G_OPTION_ARG_INT, &this->invisible_glyph, "Glyph value to replace Default-Ignorables with", nullptr}, - {"utf8-clusters", 0, 0, G_OPTION_ARG_NONE, &this->utf8_clusters, "Use UTF8 byte indices, not char indices", nullptr}, - {"cluster-level", 0, 0, G_OPTION_ARG_INT, &this->cluster_level, "Cluster merging level (default: 0)", "0/1/2"}, - {"normalize-glyphs",0, 0, G_OPTION_ARG_NONE, &this->normalize_glyphs, "Rearrange glyph clusters in nominal order", nullptr}, - {"verify", 0, 0, G_OPTION_ARG_NONE, &this->verify, "Perform sanity checks on shaping results", nullptr}, - {"num-iterations", 'n', 0, G_OPTION_ARG_INT, &this->num_iterations, "Run shaper N times (default: 1)", "N"}, - {nullptr} - }; - parser->add_group (entries, - "shape", - "Shape options:", - "Options for the shaping process", - this); - - const gchar *features_help = "Comma-separated list of font features\n" - "\n" - " Features can be enabled or disabled, either globally or limited to\n" - " specific character ranges. The format for specifying feature settings\n" - " follows. All valid CSS font-feature-settings values other than 'normal'\n" - " and the global values are also accepted, though not documented below.\n" - " CSS string escapes are not supported." - "\n" - " The range indices refer to the positions between Unicode characters,\n" - " unless the --utf8-clusters is provided, in which case range indices\n" - " refer to UTF-8 byte indices. The position before the first character\n" - " is always 0.\n" - "\n" - " The format is Python-esque. Here is how it all works:\n" - "\n" - " Syntax: Value: Start: End:\n" - "\n" - " Setting value:\n" - " \"kern\" 1 0 ∞ # Turn feature on\n" - " \"+kern\" 1 0 ∞ # Turn feature on\n" - " \"-kern\" 0 0 ∞ # Turn feature off\n" - " \"kern=0\" 0 0 ∞ # Turn feature off\n" - " \"kern=1\" 1 0 ∞ # Turn feature on\n" - " \"aalt=2\" 2 0 ∞ # Choose 2nd alternate\n" - "\n" - " Setting index:\n" - " \"kern[]\" 1 0 ∞ # Turn feature on\n" - " \"kern[:]\" 1 0 ∞ # Turn feature on\n" - " \"kern[5:]\" 1 5 ∞ # Turn feature on, partial\n" - " \"kern[:5]\" 1 0 5 # Turn feature on, partial\n" - " \"kern[3:5]\" 1 3 5 # Turn feature on, range\n" - " \"kern[3]\" 1 3 3+1 # Turn feature on, single char\n" - "\n" - " Mixing it all:\n" - "\n" - " \"aalt[3:5]=2\" 2 3 5 # Turn 2nd alternate on for range"; - - GOptionEntry entries2[] = - { - {"features", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_features, features_help, "list"}, - {nullptr} - }; - parser->add_group (entries2, - "features", - "Features options:", - "Options for font features used", - this); -} - -static gboolean -parse_font_size (const char *name G_GNUC_UNUSED, - const char *arg, - gpointer data, - GError **error G_GNUC_UNUSED) -{ - font_options_t *font_opts = (font_options_t *) data; - if (0 == strcmp (arg, "upem")) - { - font_opts->font_size_y = font_opts->font_size_x = FONT_SIZE_UPEM; - return true; - } - switch (sscanf (arg, "%lf%*[ ,]%lf", &font_opts->font_size_x, &font_opts->font_size_y)) { - case 1: font_opts->font_size_y = font_opts->font_size_x; HB_FALLTHROUGH; - case 2: return true; - default: - g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, - "%s argument should be one or two space-separated numbers", - name); - return false; - } -} - -static gboolean -parse_font_ppem (const char *name G_GNUC_UNUSED, - const char *arg, - gpointer data, - GError **error G_GNUC_UNUSED) -{ - font_options_t *font_opts = (font_options_t *) data; - switch (sscanf (arg, "%d%*[ ,]%d", &font_opts->x_ppem, &font_opts->y_ppem)) { - case 1: font_opts->y_ppem = font_opts->x_ppem; HB_FALLTHROUGH; - case 2: return true; - default: - g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, - "%s argument should be one or two space-separated numbers", - name); - return false; - } -} - -void -font_options_t::add_options (option_parser_t *parser) -{ - char *text = nullptr; - - { - static_assert ((ARRAY_LENGTH_CONST (supported_font_funcs) > 0), - "No supported font-funcs found."); - GString *s = g_string_new (nullptr); - g_string_printf (s, "Set font functions implementation to use (default: %s)\n\n Supported font function implementations are: %s", - supported_font_funcs[0].name, - supported_font_funcs[0].name); - for (unsigned int i = 1; i < ARRAY_LENGTH (supported_font_funcs); i++) - { - g_string_append_c (s, '/'); - g_string_append (s, supported_font_funcs[i].name); - } - text = g_string_free (s, FALSE); - parser->free_later (text); - } - - char *font_size_text; - if (default_font_size == FONT_SIZE_UPEM) - font_size_text = (char *) "Font size (default: upem)"; - else - { - font_size_text = g_strdup_printf ("Font size (default: %d)", default_font_size); - parser->free_later (font_size_text); - } - - GOptionEntry entries[] = - { - {"font-file", 0, 0, G_OPTION_ARG_STRING, &this->font_file, "Set font file-name", "filename"}, - {"face-index", 0, 0, G_OPTION_ARG_INT, &this->face_index, "Set face index (default: 0)", "index"}, - {"font-size", 0, default_font_size ? 0 : G_OPTION_FLAG_HIDDEN, - G_OPTION_ARG_CALLBACK, (gpointer) &parse_font_size, font_size_text, "1/2 integers or 'upem'"}, - {"font-ppem", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_font_ppem, "Set x,y pixels per EM (default: 0; disabled)", "1/2 integers"}, - {"font-ptem", 0, 0, G_OPTION_ARG_DOUBLE, &this->ptem, "Set font point-size (default: 0; disabled)", "point-size"}, - {"font-funcs", 0, 0, G_OPTION_ARG_STRING, &this->font_funcs, text, "impl"}, - {"ft-load-flags", 0, 0, G_OPTION_ARG_INT, &this->ft_load_flags, "Set FreeType load-flags (default: 2)", "integer"}, - {nullptr} - }; - parser->add_group (entries, - "font", - "Font options:", - "Options for the font", - this); - - const gchar *variations_help = "Comma-separated list of font variations\n" - "\n" - " Variations are set globally. The format for specifying variation settings\n" - " follows. All valid CSS font-variation-settings values other than 'normal'\n" - " and 'inherited' are also accepted, though, not documented below.\n" - "\n" - " The format is a tag, optionally followed by an equals sign, followed by a\n" - " number. For example:\n" - "\n" - " \"wght=500\"\n" - " \"slnt=-7.5\"\n"; - - GOptionEntry entries2[] = - { - {"variations", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_variations, variations_help, "list"}, - {nullptr} - }; - parser->add_group (entries2, - "variations", - "Variations options:", - "Options for font variations used", - this); -} - -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\n\n If no text is provided, standard input is used for input.\n", "filename"}, - {"unicodes", 'u', 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_unicodes, "Set input Unicode codepoints", "list of hex numbers"}, - {"text-before", 0, 0, G_OPTION_ARG_STRING, &this->text_before, "Set text context before each line", "string"}, - {"text-after", 0, 0, G_OPTION_ARG_STRING, &this->text_after, "Set text context after each line", "string"}, - {nullptr} - }; - parser->add_group (entries, - "text", - "Text options:", - "Options for the input text", - this); -} - -void -output_options_t::add_options (option_parser_t *parser) -{ - const char *text; - - if (!supported_formats) - text = "Set output serialization format"; - else - { - char *items = g_strjoinv ("/", const_cast (supported_formats)); - text = g_strdup_printf ("Set output format\n\n Supported output formats are: %s", items); - g_free (items); - parser->free_later ((char *) text); - } - - GOptionEntry entries[] = - { - {"output-file", 'o', 0, G_OPTION_ARG_STRING, &this->output_file, "Set output file-name (default: stdout)","filename"}, - {"output-format", 'O', 0, G_OPTION_ARG_STRING, &this->output_format, text, "format"}, - {nullptr} - }; - parser->add_group (entries, - "output", - "Output destination & format options:", - "Options for the destination & form of the output", - this); -} - - -font_options_t::cache_t font_options_t::cache {}; - -hb_font_t * -font_options_t::get_font () const -{ - if (font) - return font; - - /* Create the blob */ - if (!font_file) - fail (true, "No font file set"); - - const char *font_path = font_file; - - if (0 == strcmp (font_path, "-")) - { -#if defined(_WIN32) || defined(__CYGWIN__) - setmode (fileno (stdin), O_BINARY); - font_path = "STDIN"; -#else - font_path = "/dev/stdin"; -#endif - } - - if (cache.font_path && 0 == strcmp (cache.font_path, font_path)) - blob = hb_blob_reference (cache.blob); - else - { - blob = hb_blob_create_from_file_or_fail (font_path); - - if (!blob) - fail (false, "%s: Failed reading file", font_path); - - /* Update caches. */ - - hb_face_destroy (cache.face); - cache.face = nullptr; - cache.face_index = (unsigned) -1; - - free ((char *) cache.font_path); - cache.font_path = strdup (font_path); - hb_blob_destroy (cache.blob); - cache.blob = hb_blob_reference (blob); - } - - hb_face_t *face = nullptr; - if (cache.face_index == face_index) - face = hb_face_reference (cache.face); - else - { - face = hb_face_create (blob, face_index); - hb_blob_destroy (blob); - - cache.face_index = face_index; - hb_face_destroy (cache.face); - cache.face = hb_face_reference (face); - } - - - font = hb_font_create (face); - - if (font_size_x == FONT_SIZE_UPEM) - font_size_x = hb_face_get_upem (face); - if (font_size_y == FONT_SIZE_UPEM) - font_size_y = hb_face_get_upem (face); - - hb_font_set_ppem (font, x_ppem, y_ppem); - hb_font_set_ptem (font, ptem); - - int scale_x = (int) scalbnf (font_size_x, subpixel_bits); - int scale_y = (int) scalbnf (font_size_y, subpixel_bits); - hb_font_set_scale (font, scale_x, scale_y); - hb_face_destroy (face); - - hb_font_set_variations (font, variations, num_variations); - - void (*set_font_funcs) (hb_font_t *) = nullptr; - if (!font_funcs) - { - set_font_funcs = supported_font_funcs[0].func; - } - else - { - for (unsigned int i = 0; i < ARRAY_LENGTH (supported_font_funcs); i++) - if (0 == g_ascii_strcasecmp (font_funcs, supported_font_funcs[i].name)) - { - set_font_funcs = supported_font_funcs[i].func; - break; - } - if (!set_font_funcs) - { - GString *s = g_string_new (nullptr); - for (unsigned int i = 0; i < ARRAY_LENGTH (supported_font_funcs); i++) - { - if (i) - g_string_append_c (s, '/'); - g_string_append (s, supported_font_funcs[i].name); - } - char *p = g_string_free (s, FALSE); - fail (false, "Unknown font function implementation `%s'; supported values are: %s; default is %s", - font_funcs, - p, - supported_font_funcs[0].name); - //free (p); - } - } - set_font_funcs (font); -#ifdef HAVE_FREETYPE - hb_ft_font_set_load_flags (font, ft_load_flags); -#endif - - return font; -} - - -const char * -text_options_t::get_line (unsigned int *len, int eol) -{ - 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 = (const char *) memchr (line, eol, 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; - } - - if (!fp) { - if (!text_file) - fail (true, "At least one of text or text-file must be set"); - - if (0 != strcmp (text_file, "-")) - fp = fopen (text_file, "r"); - else - fp = stdin; - - if (!fp) - fail (false, "Failed opening text file `%s': %s", - text_file, strerror (errno)); - - gs = g_string_new (nullptr); - } - - g_string_set_size (gs, 0); - char buf[BUFSIZ]; - while (fgets (buf, sizeof (buf), fp)) - { - unsigned bytes = strlen (buf); - if (bytes && (int) (unsigned char) buf[bytes - 1] == eol) - { - bytes--; - g_string_append_len (gs, buf, bytes); - break; - } - g_string_append_len (gs, buf, bytes); - } - if (ferror (fp)) - fail (false, "Failed reading text: %s", strerror (errno)); - *len = gs->len; - return !*len && feof (fp) ? nullptr : gs->str; -} - - -FILE * -output_options_t::get_file_handle () -{ - if (fp) - return fp; - - if (output_file) - fp = fopen (output_file, "wb"); - else { -#if defined(_WIN32) || defined(__CYGWIN__) - setmode (fileno (stdout), O_BINARY); -#endif - fp = stdout; - } - if (!fp) - fail (false, "Cannot open output file `%s': %s", - g_filename_display_name (output_file), strerror (errno)); - - return fp; -} - -static gboolean -parse_verbose (const char *name G_GNUC_UNUSED, - const char *arg G_GNUC_UNUSED, - gpointer data G_GNUC_UNUSED, - GError **error G_GNUC_UNUSED) -{ - format_options_t *format_opts = (format_options_t *) data; - format_opts->show_text = format_opts->show_unicode = format_opts->show_line_num = true; - return true; -} - -static gboolean -parse_ned (const char *name G_GNUC_UNUSED, - const char *arg G_GNUC_UNUSED, - gpointer data G_GNUC_UNUSED, - GError **error G_GNUC_UNUSED) -{ - format_options_t *format_opts = (format_options_t *) data; - format_opts->show_clusters = format_opts->show_advances = false; - return true; -} - -void -format_options_t::add_options (option_parser_t *parser) -{ - GOptionEntry entries[] = - { - {"show-text", 0, 0, G_OPTION_ARG_NONE, &this->show_text, "Prefix each line of output with its corresponding input text", nullptr}, - {"show-unicode", 0, 0, G_OPTION_ARG_NONE, &this->show_unicode, "Prefix each line of output with its corresponding input codepoint(s)", nullptr}, - {"show-line-num", 0, 0, G_OPTION_ARG_NONE, &this->show_line_num, "Prefix each line of output with its corresponding input line number", nullptr}, - {"verbose", 'v', G_OPTION_FLAG_NO_ARG, - G_OPTION_ARG_CALLBACK, (gpointer) &parse_verbose, "Prefix each line of output with all of the above", nullptr}, - {"no-glyph-names", 0, G_OPTION_FLAG_REVERSE, - G_OPTION_ARG_NONE, &this->show_glyph_names, "Output glyph indices instead of names", nullptr}, - {"no-positions", 0, G_OPTION_FLAG_REVERSE, - G_OPTION_ARG_NONE, &this->show_positions, "Do not output glyph positions", nullptr}, - {"no-advances", 0, G_OPTION_FLAG_REVERSE, - G_OPTION_ARG_NONE, &this->show_advances, "Do not output glyph advances", nullptr}, - {"no-clusters", 0, G_OPTION_FLAG_REVERSE, - G_OPTION_ARG_NONE, &this->show_clusters, "Do not output cluster indices", nullptr}, - {"show-extents", 0, 0, G_OPTION_ARG_NONE, &this->show_extents, "Output glyph extents", nullptr}, - {"show-flags", 0, 0, G_OPTION_ARG_NONE, &this->show_flags, "Output glyph flags", nullptr}, - {"ned", 'v', G_OPTION_FLAG_NO_ARG, - G_OPTION_ARG_CALLBACK, (gpointer) &parse_ned, "No Extra Data; Do not output clusters or advances", nullptr}, - {"trace", 'V', 0, G_OPTION_ARG_NONE, &this->trace, "Output interim shaping results", nullptr}, - {nullptr} - }; - parser->add_group (entries, - "output-syntax", - "Output syntax:\n" - " text: [=@,+,|...]\n" - " json: [{\"g\": , \"ax\": , \"ay\": , \"dx\": , \"dy\": , \"cl\": }, ...]\n" - "\nOutput syntax options:", - "Options for the syntax of the output", - this); -} - -void -format_options_t::serialize (hb_buffer_t *buffer, - hb_font_t *font, - hb_buffer_serialize_format_t output_format, - hb_buffer_serialize_flags_t flags, - GString *gs) -{ - unsigned int num_glyphs = hb_buffer_get_length (buffer); - unsigned int start = 0; - - while (start < num_glyphs) - { - char buf[32768]; - unsigned int consumed; - start += hb_buffer_serialize (buffer, start, num_glyphs, - buf, sizeof (buf), &consumed, - font, output_format, flags); - if (!consumed) - break; - g_string_append (gs, buf); - } -} - -void -format_options_t::serialize_line_no (unsigned int line_no, - GString *gs) -{ - if (show_line_num) - g_string_append_printf (gs, "%d: ", line_no); -} -void -format_options_t::serialize_buffer_of_text (hb_buffer_t *buffer, - unsigned int line_no, - const char *text, - unsigned int text_len, - hb_font_t *font, - GString *gs) -{ - if (show_text) - { - serialize_line_no (line_no, gs); - g_string_append_c (gs, '('); - g_string_append_len (gs, text, text_len); - g_string_append_c (gs, ')'); - g_string_append_c (gs, '\n'); - } - - if (show_unicode) - { - serialize_line_no (line_no, gs); - serialize (buffer, font, HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT, gs); - g_string_append_c (gs, '\n'); - } -} -void -format_options_t::serialize_message (unsigned int line_no, - const char *type, - const char *msg, - GString *gs) -{ - serialize_line_no (line_no, gs); - g_string_append_printf (gs, "%s: %s", type, msg); - g_string_append_c (gs, '\n'); -} -void -format_options_t::serialize_buffer_of_glyphs (hb_buffer_t *buffer, - unsigned int line_no, - const char *text, - unsigned int text_len, - hb_font_t *font, - hb_buffer_serialize_format_t output_format, - hb_buffer_serialize_flags_t format_flags, - GString *gs) -{ - serialize_line_no (line_no, gs); - serialize (buffer, font, output_format, format_flags, gs); - g_string_append_c (gs, '\n'); -} diff --git a/util/options.hh b/util/options.hh index 11cd9b20b..ff542f4c5 100644 --- a/util/options.hh +++ b/util/options.hh @@ -28,7 +28,6 @@ #define OPTIONS_HH #include "hb.hh" -#include "hb-subset.h" #include #include @@ -51,28 +50,49 @@ #include #include -void fail (hb_bool_t suggest_help, const char *format, ...) G_GNUC_NORETURN G_GNUC_PRINTF (2, 3); -struct option_group_t +static inline void fail (hb_bool_t suggest_help, const char *format, ...) G_GNUC_NORETURN G_GNUC_PRINTF (2, 3); + +static inline void +fail (hb_bool_t suggest_help, const char *format, ...) { - virtual ~option_group_t () {} + const char *msg; - virtual void add_options (struct option_parser_t *parser) = 0; + va_list vap; + va_start (vap, format); + msg = g_strdup_vprintf (format, vap); + va_end (vap); + const char *prgname = g_get_prgname (); + g_printerr ("%s: %s\n", prgname, msg); + if (suggest_help) + g_printerr ("Try `%s --help' for more information.\n", prgname); - virtual void pre_parse (GError **error G_GNUC_UNUSED) {} - virtual void post_parse (GError **error G_GNUC_UNUSED) {} -}; + exit (1); +} + +static inline char * +locale_to_utf8 (char *s) +{ + char *t; + GError *error = nullptr; + + t = g_locale_to_utf8 (s, -1, nullptr, nullptr, &error); + if (!t) + { + fail (true, "Failed converting text to UTF-8"); + } + + return t; +} struct option_parser_t { option_parser_t (const char *usage) + : usage_str (usage), + context (g_option_context_new (usage)), + to_free (g_ptr_array_new ()) { - memset (this, 0, sizeof (*this)); - usage_str = usage; - context = g_option_context_new (usage); - to_free = g_ptr_array_new (); - add_main_options (); } @@ -87,11 +107,36 @@ struct option_parser_t void add_main_options (); + static void + post_parse_ (void *thiz, GError **error) {} + template + static auto + post_parse_ (Type *thiz, GError **error) -> decltype (thiz->post_parse (error)) + { thiz->post_parse (error); } + template + static gboolean + post_parse (GOptionContext *context G_GNUC_UNUSED, + GOptionGroup *group G_GNUC_UNUSED, + gpointer data, + GError **error) + { + option_parser_t::post_parse_ (static_cast (data), error); + return !*error; + } + + template void add_group (GOptionEntry *entries, const gchar *name, const gchar *description, const gchar *help_description, - option_group_t *option_group); + Type *closure) + { + GOptionGroup *group = g_option_group_new (name, description, help_description, + static_cast(closure), nullptr); + g_option_group_add_entries (group, entries); + g_option_group_set_parse_hooks (group, nullptr, post_parse); + g_option_context_add_group (context, group); + } void free_later (char *p) { g_ptr_array_add (to_free, p); @@ -111,628 +156,68 @@ struct option_parser_t }; -#define DEFAULT_MARGIN 16 -#define DEFAULT_FORE "#000000" -#define DEFAULT_BACK "#FFFFFF" -#define FONT_SIZE_UPEM 0x7FFFFFFF -#define FONT_SIZE_NONE 0 - -struct view_options_t : option_group_t +static inline gchar * +shapers_to_string () { - view_options_t (option_parser_t *parser) - { - annotate = false; - fore = nullptr; - back = nullptr; - line_space = 0; - have_font_extents = false; - font_extents.ascent = font_extents.descent = font_extents.line_gap = 0; - margin.t = margin.r = margin.b = margin.l = DEFAULT_MARGIN; + GString *shapers = g_string_new (nullptr); + const char **shaper_list = hb_shape_list_shapers (); - add_options (parser); - } - ~view_options_t () override - { - g_free (fore); - g_free (back); + for (; *shaper_list; shaper_list++) { + g_string_append (shapers, *shaper_list); + g_string_append_c (shapers, ','); } + g_string_truncate (shapers, MAX (0, (gint)shapers->len - 1)); - void add_options (option_parser_t *parser) override; + return g_string_free (shapers, false); +} - hb_bool_t annotate; - char *fore; - char *back; - double line_space; - bool have_font_extents; - struct font_extents_t { - double ascent, descent, line_gap; - } font_extents; - struct margin_t { - double t, r, b, l; - } margin; -}; - - -struct shape_options_t : option_group_t +static G_GNUC_NORETURN gboolean +show_version (const char *name G_GNUC_UNUSED, + const char *arg G_GNUC_UNUSED, + gpointer data G_GNUC_UNUSED, + GError **error G_GNUC_UNUSED) { - shape_options_t (option_parser_t *parser) - { - direction = language = script = nullptr; - bot = eot = preserve_default_ignorables = remove_default_ignorables = false; - features = nullptr; - num_features = 0; - shapers = nullptr; - utf8_clusters = false; - invisible_glyph = 0; - cluster_level = HB_BUFFER_CLUSTER_LEVEL_DEFAULT; - normalize_glyphs = false; - verify = false; - num_iterations = 1; + g_printf ("%s (%s) %s\n", g_get_prgname (), PACKAGE_NAME, PACKAGE_VERSION); - add_options (parser); - } - ~shape_options_t () override - { - g_free (direction); - g_free (language); - g_free (script); - free (features); - g_strfreev (shapers); - } + char *shapers = shapers_to_string (); + g_printf ("Available shapers: %s\n", shapers); + g_free (shapers); + if (strcmp (HB_VERSION_STRING, hb_version_string ())) + g_printf ("Linked HarfBuzz library has a different version: %s\n", hb_version_string ()); - void add_options (option_parser_t *parser) override; + exit(0); +} - void setup_buffer (hb_buffer_t *buffer) - { - hb_buffer_set_direction (buffer, hb_direction_from_string (direction, -1)); - hb_buffer_set_script (buffer, hb_script_from_string (script, -1)); - hb_buffer_set_language (buffer, hb_language_from_string (language, -1)); - hb_buffer_set_flags (buffer, (hb_buffer_flags_t) - (HB_BUFFER_FLAG_DEFAULT | - (bot ? HB_BUFFER_FLAG_BOT : 0) | - (eot ? HB_BUFFER_FLAG_EOT : 0) | - (preserve_default_ignorables ? HB_BUFFER_FLAG_PRESERVE_DEFAULT_IGNORABLES : 0) | - (remove_default_ignorables ? HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES : 0) | - 0)); - hb_buffer_set_invisible_glyph (buffer, invisible_glyph); - hb_buffer_set_cluster_level (buffer, cluster_level); - hb_buffer_guess_segment_properties (buffer); - } - - static void copy_buffer_properties (hb_buffer_t *dst, hb_buffer_t *src) - { - hb_segment_properties_t props; - hb_buffer_get_segment_properties (src, &props); - hb_buffer_set_segment_properties (dst, &props); - hb_buffer_set_flags (dst, hb_buffer_get_flags (src)); - hb_buffer_set_cluster_level (dst, hb_buffer_get_cluster_level (src)); - } - - void populate_buffer (hb_buffer_t *buffer, const char *text, int text_len, - const char *text_before, const char *text_after) - { - hb_buffer_clear_contents (buffer); - if (text_before) { - unsigned int len = strlen (text_before); - hb_buffer_add_utf8 (buffer, text_before, len, len, 0); - } - hb_buffer_add_utf8 (buffer, text, text_len, 0, text_len); - if (text_after) { - hb_buffer_add_utf8 (buffer, text_after, -1, 0, 0); - } - - if (!utf8_clusters) { - /* Reset cluster values to refer to Unicode character index - * instead of UTF-8 index. */ - unsigned int num_glyphs = hb_buffer_get_length (buffer); - hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, nullptr); - for (unsigned int i = 0; i < num_glyphs; i++) - { - info->cluster = i; - info++; - } - } - - setup_buffer (buffer); - } - - hb_bool_t shape (hb_font_t *font, hb_buffer_t *buffer, const char **error=nullptr) - { - hb_buffer_t *text_buffer = nullptr; - if (verify) - { - text_buffer = hb_buffer_create (); - hb_buffer_append (text_buffer, buffer, 0, -1); - } - - if (!hb_shape_full (font, buffer, features, num_features, shapers)) - { - if (error) - *error = "all shapers failed."; - goto fail; - } - - if (normalize_glyphs) - hb_buffer_normalize_glyphs (buffer); - - if (verify && !verify_buffer (buffer, text_buffer, font, error)) - goto fail; - - if (text_buffer) - hb_buffer_destroy (text_buffer); - - return true; - - fail: - if (text_buffer) - hb_buffer_destroy (text_buffer); - - return false; - } - - bool verify_buffer (hb_buffer_t *buffer, - hb_buffer_t *text_buffer, - hb_font_t *font, - const char **error=nullptr) - { - if (!verify_buffer_monotone (buffer, error)) - return false; - if (!verify_buffer_safe_to_break (buffer, text_buffer, font, error)) - return false; - return true; - } - - bool verify_buffer_monotone (hb_buffer_t *buffer, const char **error=nullptr) - { - /* Check that clusters are monotone. */ - if (cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES || - cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS) - { - bool is_forward = HB_DIRECTION_IS_FORWARD (hb_buffer_get_direction (buffer)); - - unsigned int num_glyphs; - hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, &num_glyphs); - - for (unsigned int i = 1; i < num_glyphs; i++) - if (info[i-1].cluster != info[i].cluster && - (info[i-1].cluster < info[i].cluster) != is_forward) - { - if (error) - *error = "clusters are not monotone."; - return false; - } - } - - return true; - } - - bool verify_buffer_safe_to_break (hb_buffer_t *buffer, - hb_buffer_t *text_buffer, - hb_font_t *font, - const char **error=nullptr) - { - if (cluster_level != HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES && - cluster_level != HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS) - { - /* Cannot perform this check without monotone clusters. - * Then again, unsafe-to-break flag is much harder to use without - * monotone clusters. */ - return true; - } - - /* Check that breaking up shaping at safe-to-break is indeed safe. */ - - hb_buffer_t *fragment = hb_buffer_create (); - hb_buffer_t *reconstruction = hb_buffer_create (); - copy_buffer_properties (reconstruction, buffer); - - unsigned int num_glyphs; - hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, &num_glyphs); - - unsigned int num_chars; - hb_glyph_info_t *text = hb_buffer_get_glyph_infos (text_buffer, &num_chars); - - /* Chop text and shape fragments. */ - bool forward = HB_DIRECTION_IS_FORWARD (hb_buffer_get_direction (buffer)); - unsigned int start = 0; - unsigned int text_start = forward ? 0 : num_chars; - unsigned int text_end = text_start; - for (unsigned int end = 1; end < num_glyphs + 1; end++) - { - if (end < num_glyphs && - (info[end].cluster == info[end-1].cluster || - info[end-(forward?0:1)].mask & HB_GLYPH_FLAG_UNSAFE_TO_BREAK)) - continue; - - /* Shape segment corresponding to glyphs start..end. */ - if (end == num_glyphs) - { - if (forward) - text_end = num_chars; - else - text_start = 0; - } - else - { - if (forward) - { - unsigned int cluster = info[end].cluster; - while (text_end < num_chars && text[text_end].cluster < cluster) - text_end++; - } - else - { - unsigned int cluster = info[end - 1].cluster; - while (text_start && text[text_start - 1].cluster >= cluster) - text_start--; - } - } - assert (text_start < text_end); - - if (0) - printf("start %d end %d text start %d end %d\n", start, end, text_start, text_end); - - hb_buffer_clear_contents (fragment); - copy_buffer_properties (fragment, buffer); - - /* TODO: Add pre/post context text. */ - hb_buffer_flags_t flags = hb_buffer_get_flags (fragment); - if (0 < text_start) - flags = (hb_buffer_flags_t) (flags & ~HB_BUFFER_FLAG_BOT); - if (text_end < num_chars) - flags = (hb_buffer_flags_t) (flags & ~HB_BUFFER_FLAG_EOT); - hb_buffer_set_flags (fragment, flags); - - hb_buffer_append (fragment, text_buffer, text_start, text_end); - if (!hb_shape_full (font, fragment, features, num_features, shapers)) - { - if (error) - *error = "all shapers failed while shaping fragment."; - hb_buffer_destroy (reconstruction); - hb_buffer_destroy (fragment); - return false; - } - hb_buffer_append (reconstruction, fragment, 0, -1); - - start = end; - if (forward) - text_start = text_end; - else - text_end = text_start; - } - - bool ret = true; - hb_buffer_diff_flags_t diff = hb_buffer_diff (reconstruction, buffer, (hb_codepoint_t) -1, 0); - if (diff) - { - if (error) - *error = "Safe-to-break test failed."; - ret = false; - - /* Return the reconstructed result instead so it can be inspected. */ - hb_buffer_set_length (buffer, 0); - hb_buffer_append (buffer, reconstruction, 0, -1); - } - - hb_buffer_destroy (reconstruction); - hb_buffer_destroy (fragment); - - return ret; - } - - void shape_closure (const char *text, int text_len, - hb_font_t *font, hb_buffer_t *buffer, - hb_set_t *glyphs) - { - hb_buffer_reset (buffer); - hb_buffer_add_utf8 (buffer, text, text_len, 0, text_len); - setup_buffer (buffer); - hb_ot_shape_glyphs_closure (font, buffer, features, num_features, glyphs); - } - - /* Buffer properties */ - char *direction; - char *language; - char *script; - - /* Buffer flags */ - hb_bool_t bot; - hb_bool_t eot; - hb_bool_t preserve_default_ignorables; - hb_bool_t remove_default_ignorables; - - hb_feature_t *features; - unsigned int num_features; - char **shapers; - hb_bool_t utf8_clusters; - hb_codepoint_t invisible_glyph; - hb_buffer_cluster_level_t cluster_level; - hb_bool_t normalize_glyphs; - hb_bool_t verify; - unsigned int num_iterations; -}; - - -struct font_options_t : option_group_t +inline void +option_parser_t::add_main_options () { - font_options_t (option_parser_t *parser, - int default_font_size_, - unsigned int subpixel_bits_) + GOptionEntry entries[] = { - variations = nullptr; - num_variations = 0; - default_font_size = default_font_size_; - x_ppem = 0; - y_ppem = 0; - ptem = 0.; - subpixel_bits = subpixel_bits_; - font_file = nullptr; - face_index = 0; - font_size_x = font_size_y = default_font_size; - font_funcs = nullptr; - ft_load_flags = 2; + {"version", 0, G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, (gpointer) &show_version, "Show version numbers", nullptr}, + {nullptr} + }; + g_option_context_add_main_entries (context, entries, nullptr); +} - blob = nullptr; - font = nullptr; +inline void +option_parser_t::parse (int *argc, char ***argv) +{ + setlocale (LC_ALL, ""); - add_options (parser); - } - ~font_options_t () override + GError *parse_error = nullptr; + if (!g_option_context_parse (context, argc, argv, &parse_error)) { - g_free (font_file); - free (variations); - g_free (font_funcs); - hb_font_destroy (font); - } - - void add_options (option_parser_t *parser) override; - - hb_font_t *get_font () const; - - char *font_file; - mutable hb_blob_t *blob; - unsigned face_index; - hb_variation_t *variations; - unsigned int num_variations; - int default_font_size; - int x_ppem; - int y_ppem; - double ptem; - unsigned int subpixel_bits; - mutable double font_size_x; - mutable double font_size_y; - char *font_funcs; - int ft_load_flags; - - private: - mutable hb_font_t *font; - - static struct cache_t - { - ~cache_t () + if (parse_error) { - free ((void *) font_path); - hb_blob_destroy (blob); - hb_face_destroy (face); + fail (true, "%s", parse_error->message); + //g_error_free (parse_error); } - - const char *font_path = nullptr; - hb_blob_t *blob = nullptr; - unsigned face_index = (unsigned) -1; - hb_face_t *face = nullptr; - } cache; -}; - - -struct text_options_t : option_group_t -{ - text_options_t (option_parser_t *parser) - { - text_before = nullptr; - text_after = nullptr; - - text_len = -1; - text = nullptr; - text_file = nullptr; - - fp = nullptr; - gs = nullptr; - line = nullptr; - line_len = UINT_MAX; - - add_options (parser); - } - ~text_options_t () override - { - g_free (text_before); - g_free (text_after); - g_free (text); - g_free (text_file); - if (gs) - g_string_free (gs, true); - if (fp && fp != stdin) - fclose (fp); + else + fail (true, "Option parse error"); } +} - void add_options (option_parser_t *parser) override; - - void post_parse (GError **error G_GNUC_UNUSED) override { - 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"); - } - - const char *get_line (unsigned int *len, int eol = '\n'); - - char *text_before; - char *text_after; - - int text_len; - char *text; - char *text_file; - - private: - FILE *fp; - GString *gs; - char *line; - unsigned int line_len; -}; - -struct output_options_t : option_group_t -{ - output_options_t (option_parser_t *parser, - const char **supported_formats_ = nullptr) - { - output_file = nullptr; - output_format = nullptr; - supported_formats = supported_formats_; - explicit_output_format = false; - - fp = nullptr; - - add_options (parser); - } - ~output_options_t () override - { - g_free (output_file); - g_free (output_format); - if (fp && fp != stdout) - fclose (fp); - } - - void add_options (option_parser_t *parser) override; - - void post_parse (GError **error G_GNUC_UNUSED) override - { - if (output_format) - explicit_output_format = true; - - if (output_file && !output_format) { - output_format = strrchr (output_file, '.'); - if (output_format) - { - output_format++; /* skip the dot */ - output_format = g_strdup (output_format); - } - } - - if (output_file && 0 == strcmp (output_file, "-")) - output_file = nullptr; /* STDOUT */ - } - - FILE *get_file_handle (); - - char *output_file; - char *output_format; - const char **supported_formats; - bool explicit_output_format; - - mutable FILE *fp; -}; - -struct format_options_t : option_group_t -{ - format_options_t (option_parser_t *parser) { - show_glyph_names = true; - show_positions = true; - show_advances = true; - show_clusters = true; - show_text = false; - show_unicode = false; - show_line_num = false; - show_extents = false; - show_flags = false; - trace = false; - - add_options (parser); - } - - void add_options (option_parser_t *parser) override; - - void serialize (hb_buffer_t *buffer, - hb_font_t *font, - hb_buffer_serialize_format_t format, - hb_buffer_serialize_flags_t flags, - GString *gs); - void serialize_line_no (unsigned int line_no, - GString *gs); - void serialize_buffer_of_text (hb_buffer_t *buffer, - unsigned int line_no, - const char *text, - unsigned int text_len, - hb_font_t *font, - GString *gs); - void serialize_message (unsigned int line_no, - const char *type, - const char *msg, - GString *gs); - void serialize_buffer_of_glyphs (hb_buffer_t *buffer, - unsigned int line_no, - const char *text, - unsigned int text_len, - hb_font_t *font, - hb_buffer_serialize_format_t output_format, - hb_buffer_serialize_flags_t format_flags, - GString *gs); - - - hb_bool_t show_glyph_names; - hb_bool_t show_positions; - hb_bool_t show_advances; - hb_bool_t show_clusters; - hb_bool_t show_text; - hb_bool_t show_unicode; - hb_bool_t show_line_num; - hb_bool_t show_extents; - hb_bool_t show_flags; - hb_bool_t trace; -}; - -struct subset_options_t : option_group_t -{ - subset_options_t (option_parser_t *parser) - { - input = hb_subset_input_create_or_fail (); - num_iterations = 1; - add_options (parser); - } - - ~subset_options_t () override - { - hb_subset_input_destroy (input); - } - - void add_options (option_parser_t *parser) override; - - hb_bool_t* bool_for(hb_subset_flags_t flag) - { - for (unsigned i = 0; i < sizeof(int) * 8; i++) - { - if (1u << i == flag) - return &flags[i]; - } - return &flags[sizeof(int) * 8 - 1]; - } - - unsigned num_iterations; - - hb_subset_input_t * get_input () - { - hb_subset_flags_t flags_set = HB_SUBSET_FLAGS_DEFAULT; - for (unsigned i = 0; i < sizeof(int) * 8; i++) - { - if (flags[i]) - flags_set = (hb_subset_flags_t) (flags_set | (1u << i)); - } - hb_subset_input_set_flags (input, flags_set); - return input; - } - - hb_bool_t flags[sizeof(int) * 8] = {0}; - - hb_subset_input_t *input; -}; /* fallback implementation for scalbn()/scalbnf() for pre-2013 MSVC */ #if defined (_MSC_VER) && (_MSC_VER < 1800) diff --git a/util/output-options.hh b/util/output-options.hh new file mode 100644 index 000000000..f3fda61e3 --- /dev/null +++ b/util/output-options.hh @@ -0,0 +1,123 @@ +/* + * 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 OUTPUT_OPTIONS_HH +#define OUTPUT_OPTIONS_HH + +#include "options.hh" + +struct output_options_t +{ + ~output_options_t () + { + g_free (output_file); + g_free (output_format); + if (fp && fp != stdout) + fclose (fp); + } + + void add_options (option_parser_t *parser, + const char **supported_formats = nullptr); + + void post_parse (GError **error G_GNUC_UNUSED) + { + if (output_format) + explicit_output_format = true; + + if (output_file && !output_format) { + output_format = strrchr (output_file, '.'); + if (output_format) + { + output_format++; /* skip the dot */ + output_format = g_strdup (output_format); + } + } + + if (output_file && 0 == strcmp (output_file, "-")) + output_file = nullptr; /* STDOUT */ + } + + FILE *get_file_handle (); + + char *output_file = nullptr; + char *output_format = nullptr; + bool explicit_output_format = false; + + mutable FILE *fp = nullptr; +}; + + +FILE * +output_options_t::get_file_handle () +{ + if (fp) + return fp; + + if (output_file) + fp = fopen (output_file, "wb"); + else { +#if defined(_WIN32) || defined(__CYGWIN__) + setmode (fileno (stdout), O_BINARY); +#endif + fp = stdout; + } + if (!fp) + fail (false, "Cannot open output file `%s': %s", + g_filename_display_name (output_file), strerror (errno)); + + return fp; +} + + +void +output_options_t::add_options (option_parser_t *parser, + const char **supported_formats) +{ + const char *text = nullptr; + + if (supported_formats) + { + char *items = g_strjoinv ("/", const_cast (supported_formats)); + text = g_strdup_printf ("Set output format\n\n Supported output formats are: %s", items); + g_free (items); + parser->free_later ((char *) text); + } + + GOptionEntry entries[] = + { + {"output-file", 'o', 0, G_OPTION_ARG_STRING, &this->output_file, "Set output file-name (default: stdout)","filename"}, + {"output-format", 'O', supported_formats ? 0 : G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_STRING, &this->output_format, text, "format"}, + {nullptr} + }; + parser->add_group (entries, + "output", + "Output destination & format options:", + "Options for the destination & form of the output", + this); +} + +#endif diff --git a/util/shape-consumer.hh b/util/shape-consumer.hh index da0d88033..760756432 100644 --- a/util/shape-consumer.hh +++ b/util/shape-consumer.hh @@ -27,26 +27,24 @@ #ifndef HB_SHAPE_CONSUMER_HH #define HB_SHAPE_CONSUMER_HH -#include "hb.hh" -#include "options.hh" +#include "font-options.hh" +#include "shape-options.hh" template -struct shape_consumer_t +struct shape_consumer_t : shape_options_t { - shape_consumer_t (option_parser_t *parser) - : failed (false), - shaper (parser), - output (parser), - font (nullptr), - buffer (nullptr) {} + void add_options (option_parser_t *parser) + { + shape_options_t::add_options (parser); + output.add_options (parser); + } - void init (hb_buffer_t *buffer_, - const font_options_t *font_opts) + void init (const font_options_t *font_opts) { font = hb_font_reference (font_opts->get_font ()); failed = false; - buffer = hb_buffer_reference (buffer_); + buffer = hb_buffer_create (); output.init (buffer, font_opts); } @@ -57,14 +55,14 @@ struct shape_consumer_t { output.new_line (); - for (unsigned int n = shaper.num_iterations; n; n--) + for (unsigned int n = num_iterations; n; n--) { const char *error = nullptr; - shaper.populate_buffer (buffer, text, text_len, text_before, text_after); + populate_buffer (buffer, text, text_len, text_before, text_after); if (n == 1) - output.consume_text (buffer, text, text_len, shaper.utf8_clusters); - if (!shaper.shape (font, buffer, &error)) + output.consume_text (buffer, text, text_len, utf8_clusters); + if (!shape (font, buffer, &error)) { failed = true; output.error (error); @@ -75,7 +73,7 @@ struct shape_consumer_t } } - output.consume_glyphs (buffer, text, text_len, shaper.utf8_clusters); + output.consume_glyphs (buffer, text, text_len, utf8_clusters); } void finish (const font_options_t *font_opts) { @@ -87,14 +85,13 @@ struct shape_consumer_t } public: - bool failed; + bool failed = false; protected: - shape_options_t shaper; output_t output; - hb_font_t *font; - hb_buffer_t *buffer; + hb_font_t *font = nullptr; + hb_buffer_t *buffer = nullptr; }; diff --git a/util/shape-format.hh b/util/shape-format.hh new file mode 100644 index 000000000..18b0b96e1 --- /dev/null +++ b/util/shape-format.hh @@ -0,0 +1,214 @@ +/* + * 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 SHAPE_FORMAT_OPTIONS_HH +#define SHAPE_FORMAT_OPTIONS_HH + +#include "options.hh" + + +struct shape_format_options_t +{ + void add_options (option_parser_t *parser); + + void serialize (hb_buffer_t *buffer, + hb_font_t *font, + hb_buffer_serialize_format_t format, + hb_buffer_serialize_flags_t flags, + GString *gs); + void serialize_line_no (unsigned int line_no, + GString *gs); + void serialize_buffer_of_text (hb_buffer_t *buffer, + unsigned int line_no, + const char *text, + unsigned int text_len, + hb_font_t *font, + GString *gs); + void serialize_message (unsigned int line_no, + const char *type, + const char *msg, + GString *gs); + void serialize_buffer_of_glyphs (hb_buffer_t *buffer, + unsigned int line_no, + const char *text, + unsigned int text_len, + hb_font_t *font, + hb_buffer_serialize_format_t output_format, + hb_buffer_serialize_flags_t format_flags, + GString *gs); + + + hb_bool_t show_glyph_names = true; + hb_bool_t show_positions = true; + hb_bool_t show_advances = true; + hb_bool_t show_clusters = true; + hb_bool_t show_text = false; + hb_bool_t show_unicode = false; + hb_bool_t show_line_num = false; + hb_bool_t show_extents = false; + hb_bool_t show_flags = false; + hb_bool_t trace = false; +}; + + +static gboolean +parse_verbose (const char *name G_GNUC_UNUSED, + const char *arg G_GNUC_UNUSED, + gpointer data G_GNUC_UNUSED, + GError **error G_GNUC_UNUSED) +{ + shape_format_options_t *format_opts = (shape_format_options_t *) data; + format_opts->show_text = format_opts->show_unicode = format_opts->show_line_num = true; + return true; +} + +static gboolean +parse_ned (const char *name G_GNUC_UNUSED, + const char *arg G_GNUC_UNUSED, + gpointer data G_GNUC_UNUSED, + GError **error G_GNUC_UNUSED) +{ + shape_format_options_t *format_opts = (shape_format_options_t *) data; + format_opts->show_clusters = format_opts->show_advances = false; + return true; +} + +inline void +shape_format_options_t::serialize (hb_buffer_t *buffer, + hb_font_t *font, + hb_buffer_serialize_format_t output_format, + hb_buffer_serialize_flags_t flags, + GString *gs) +{ + unsigned int num_glyphs = hb_buffer_get_length (buffer); + unsigned int start = 0; + + while (start < num_glyphs) + { + char buf[32768]; + unsigned int consumed; + start += hb_buffer_serialize (buffer, start, num_glyphs, + buf, sizeof (buf), &consumed, + font, output_format, flags); + if (!consumed) + break; + g_string_append (gs, buf); + } +} + +inline void +shape_format_options_t::serialize_line_no (unsigned int line_no, + GString *gs) +{ + if (show_line_num) + g_string_append_printf (gs, "%d: ", line_no); +} +inline void +shape_format_options_t::serialize_buffer_of_text (hb_buffer_t *buffer, + unsigned int line_no, + const char *text, + unsigned int text_len, + hb_font_t *font, + GString *gs) +{ + if (show_text) + { + serialize_line_no (line_no, gs); + g_string_append_c (gs, '('); + g_string_append_len (gs, text, text_len); + g_string_append_c (gs, ')'); + g_string_append_c (gs, '\n'); + } + + if (show_unicode) + { + serialize_line_no (line_no, gs); + serialize (buffer, font, HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT, gs); + g_string_append_c (gs, '\n'); + } +} +inline void +shape_format_options_t::serialize_message (unsigned int line_no, + const char *type, + const char *msg, + GString *gs) +{ + serialize_line_no (line_no, gs); + g_string_append_printf (gs, "%s: %s", type, msg); + g_string_append_c (gs, '\n'); +} +inline void +shape_format_options_t::serialize_buffer_of_glyphs (hb_buffer_t *buffer, + unsigned int line_no, + const char *text, + unsigned int text_len, + hb_font_t *font, + hb_buffer_serialize_format_t output_format, + hb_buffer_serialize_flags_t format_flags, + GString *gs) +{ + serialize_line_no (line_no, gs); + serialize (buffer, font, output_format, format_flags, gs); + g_string_append_c (gs, '\n'); +} + + +void +shape_format_options_t::add_options (option_parser_t *parser) +{ + GOptionEntry entries[] = + { + {"show-text", 0, 0, G_OPTION_ARG_NONE, &this->show_text, "Prefix each line of output with its corresponding input text", nullptr}, + {"show-unicode", 0, 0, G_OPTION_ARG_NONE, &this->show_unicode, "Prefix each line of output with its corresponding input codepoint(s)", nullptr}, + {"show-line-num", 0, 0, G_OPTION_ARG_NONE, &this->show_line_num, "Prefix each line of output with its corresponding input line number", nullptr}, + {"verbose", 'v', G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, (gpointer) &parse_verbose, "Prefix each line of output with all of the above", nullptr}, + {"no-glyph-names", 0, G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, &this->show_glyph_names, "Output glyph indices instead of names", nullptr}, + {"no-positions", 0, G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, &this->show_positions, "Do not output glyph positions", nullptr}, + {"no-advances", 0, G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, &this->show_advances, "Do not output glyph advances", nullptr}, + {"no-clusters", 0, G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, &this->show_clusters, "Do not output cluster indices", nullptr}, + {"show-extents", 0, 0, G_OPTION_ARG_NONE, &this->show_extents, "Output glyph extents", nullptr}, + {"show-flags", 0, 0, G_OPTION_ARG_NONE, &this->show_flags, "Output glyph flags", nullptr}, + {"ned", 'v', G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, (gpointer) &parse_ned, "No Extra Data; Do not output clusters or advances", nullptr}, + {"trace", 'V', 0, G_OPTION_ARG_NONE, &this->trace, "Output interim shaping results", nullptr}, + {nullptr} + }; + parser->add_group (entries, + "output-syntax", + "Output syntax:\n" + " text: [=@,+,|...]\n" + " json: [{\"g\": , \"ax\": , \"ay\": , \"dx\": , \"dy\": , \"cl\": }, ...]\n" + "\nOutput syntax options:", + "Options for the syntax of the output", + this); +} + +#endif diff --git a/util/shape-options.hh b/util/shape-options.hh new file mode 100644 index 000000000..f19578bbf --- /dev/null +++ b/util/shape-options.hh @@ -0,0 +1,490 @@ +/* + * 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 SHAPE_OPTIONS_HH +#define SHAPE_OPTIONS_HH + +#include "options.hh" + +struct shape_options_t +{ + ~shape_options_t () + { + g_free (direction); + g_free (language); + g_free (script); + free (features); + g_strfreev (shapers); + } + + void add_options (option_parser_t *parser); + + void setup_buffer (hb_buffer_t *buffer) + { + hb_buffer_set_direction (buffer, hb_direction_from_string (direction, -1)); + hb_buffer_set_script (buffer, hb_script_from_string (script, -1)); + hb_buffer_set_language (buffer, hb_language_from_string (language, -1)); + hb_buffer_set_flags (buffer, (hb_buffer_flags_t) + (HB_BUFFER_FLAG_DEFAULT | + (bot ? HB_BUFFER_FLAG_BOT : 0) | + (eot ? HB_BUFFER_FLAG_EOT : 0) | + (preserve_default_ignorables ? HB_BUFFER_FLAG_PRESERVE_DEFAULT_IGNORABLES : 0) | + (remove_default_ignorables ? HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES : 0) | + 0)); + hb_buffer_set_invisible_glyph (buffer, invisible_glyph); + hb_buffer_set_cluster_level (buffer, cluster_level); + hb_buffer_guess_segment_properties (buffer); + } + + static void copy_buffer_properties (hb_buffer_t *dst, hb_buffer_t *src) + { + hb_segment_properties_t props; + hb_buffer_get_segment_properties (src, &props); + hb_buffer_set_segment_properties (dst, &props); + hb_buffer_set_flags (dst, hb_buffer_get_flags (src)); + hb_buffer_set_cluster_level (dst, hb_buffer_get_cluster_level (src)); + } + + void populate_buffer (hb_buffer_t *buffer, const char *text, int text_len, + const char *text_before, const char *text_after) + { + hb_buffer_clear_contents (buffer); + if (text_before) { + unsigned int len = strlen (text_before); + hb_buffer_add_utf8 (buffer, text_before, len, len, 0); + } + hb_buffer_add_utf8 (buffer, text, text_len, 0, text_len); + if (text_after) { + hb_buffer_add_utf8 (buffer, text_after, -1, 0, 0); + } + + if (!utf8_clusters) { + /* Reset cluster values to refer to Unicode character index + * instead of UTF-8 index. */ + unsigned int num_glyphs = hb_buffer_get_length (buffer); + hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, nullptr); + for (unsigned int i = 0; i < num_glyphs; i++) + { + info->cluster = i; + info++; + } + } + + setup_buffer (buffer); + } + + hb_bool_t shape (hb_font_t *font, hb_buffer_t *buffer, const char **error=nullptr) + { + hb_buffer_t *text_buffer = nullptr; + if (verify) + { + text_buffer = hb_buffer_create (); + hb_buffer_append (text_buffer, buffer, 0, -1); + } + + if (!hb_shape_full (font, buffer, features, num_features, shapers)) + { + if (error) + *error = "all shapers failed."; + goto fail; + } + + if (normalize_glyphs) + hb_buffer_normalize_glyphs (buffer); + + if (verify && !verify_buffer (buffer, text_buffer, font, error)) + goto fail; + + if (text_buffer) + hb_buffer_destroy (text_buffer); + + return true; + + fail: + if (text_buffer) + hb_buffer_destroy (text_buffer); + + return false; + } + + bool verify_buffer (hb_buffer_t *buffer, + hb_buffer_t *text_buffer, + hb_font_t *font, + const char **error=nullptr) + { + if (!verify_buffer_monotone (buffer, error)) + return false; + if (!verify_buffer_safe_to_break (buffer, text_buffer, font, error)) + return false; + return true; + } + + bool verify_buffer_monotone (hb_buffer_t *buffer, const char **error=nullptr) + { + /* Check that clusters are monotone. */ + if (cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES || + cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS) + { + bool is_forward = HB_DIRECTION_IS_FORWARD (hb_buffer_get_direction (buffer)); + + unsigned int num_glyphs; + hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, &num_glyphs); + + for (unsigned int i = 1; i < num_glyphs; i++) + if (info[i-1].cluster != info[i].cluster && + (info[i-1].cluster < info[i].cluster) != is_forward) + { + if (error) + *error = "clusters are not monotone."; + return false; + } + } + + return true; + } + + bool verify_buffer_safe_to_break (hb_buffer_t *buffer, + hb_buffer_t *text_buffer, + hb_font_t *font, + const char **error=nullptr) + { + if (cluster_level != HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES && + cluster_level != HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS) + { + /* Cannot perform this check without monotone clusters. + * Then again, unsafe-to-break flag is much harder to use without + * monotone clusters. */ + return true; + } + + /* Check that breaking up shaping at safe-to-break is indeed safe. */ + + hb_buffer_t *fragment = hb_buffer_create (); + hb_buffer_t *reconstruction = hb_buffer_create (); + copy_buffer_properties (reconstruction, buffer); + + unsigned int num_glyphs; + hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, &num_glyphs); + + unsigned int num_chars; + hb_glyph_info_t *text = hb_buffer_get_glyph_infos (text_buffer, &num_chars); + + /* Chop text and shape fragments. */ + bool forward = HB_DIRECTION_IS_FORWARD (hb_buffer_get_direction (buffer)); + unsigned int start = 0; + unsigned int text_start = forward ? 0 : num_chars; + unsigned int text_end = text_start; + for (unsigned int end = 1; end < num_glyphs + 1; end++) + { + if (end < num_glyphs && + (info[end].cluster == info[end-1].cluster || + info[end-(forward?0:1)].mask & HB_GLYPH_FLAG_UNSAFE_TO_BREAK)) + continue; + + /* Shape segment corresponding to glyphs start..end. */ + if (end == num_glyphs) + { + if (forward) + text_end = num_chars; + else + text_start = 0; + } + else + { + if (forward) + { + unsigned int cluster = info[end].cluster; + while (text_end < num_chars && text[text_end].cluster < cluster) + text_end++; + } + else + { + unsigned int cluster = info[end - 1].cluster; + while (text_start && text[text_start - 1].cluster >= cluster) + text_start--; + } + } + assert (text_start < text_end); + + if (0) + printf("start %d end %d text start %d end %d\n", start, end, text_start, text_end); + + hb_buffer_clear_contents (fragment); + copy_buffer_properties (fragment, buffer); + + /* TODO: Add pre/post context text. */ + hb_buffer_flags_t flags = hb_buffer_get_flags (fragment); + if (0 < text_start) + flags = (hb_buffer_flags_t) (flags & ~HB_BUFFER_FLAG_BOT); + if (text_end < num_chars) + flags = (hb_buffer_flags_t) (flags & ~HB_BUFFER_FLAG_EOT); + hb_buffer_set_flags (fragment, flags); + + hb_buffer_append (fragment, text_buffer, text_start, text_end); + if (!hb_shape_full (font, fragment, features, num_features, shapers)) + { + if (error) + *error = "all shapers failed while shaping fragment."; + hb_buffer_destroy (reconstruction); + hb_buffer_destroy (fragment); + return false; + } + hb_buffer_append (reconstruction, fragment, 0, -1); + + start = end; + if (forward) + text_start = text_end; + else + text_end = text_start; + } + + bool ret = true; + hb_buffer_diff_flags_t diff = hb_buffer_diff (reconstruction, buffer, (hb_codepoint_t) -1, 0); + if (diff) + { + if (error) + *error = "Safe-to-break test failed."; + ret = false; + + /* Return the reconstructed result instead so it can be inspected. */ + hb_buffer_set_length (buffer, 0); + hb_buffer_append (buffer, reconstruction, 0, -1); + } + + hb_buffer_destroy (reconstruction); + hb_buffer_destroy (fragment); + + return ret; + } + + void shape_closure (const char *text, int text_len, + hb_font_t *font, hb_buffer_t *buffer, + hb_set_t *glyphs) + { + hb_buffer_reset (buffer); + hb_buffer_add_utf8 (buffer, text, text_len, 0, text_len); + setup_buffer (buffer); + hb_ot_shape_glyphs_closure (font, buffer, features, num_features, glyphs); + } + + /* Buffer properties */ + char *direction = nullptr; + char *language = nullptr; + char *script = nullptr; + + /* Buffer flags */ + hb_bool_t bot = false; + hb_bool_t eot = false; + hb_bool_t preserve_default_ignorables = false; + hb_bool_t remove_default_ignorables = false; + + hb_feature_t *features = nullptr; + unsigned int num_features = 0; + char **shapers = nullptr; + hb_bool_t utf8_clusters = false; + hb_codepoint_t invisible_glyph = 0; + hb_buffer_cluster_level_t cluster_level = HB_BUFFER_CLUSTER_LEVEL_DEFAULT; + hb_bool_t normalize_glyphs = false; + hb_bool_t verify = false; + unsigned int num_iterations = 1; +}; + + +static gboolean +parse_shapers (const char *name G_GNUC_UNUSED, + const char *arg, + gpointer data, + GError **error) +{ + shape_options_t *shape_opts = (shape_options_t *) data; + char **shapers = g_strsplit (arg, ",", 0); + + for (char **shaper = shapers; *shaper; shaper++) { + bool found = false; + for (const char **hb_shaper = hb_shape_list_shapers (); *hb_shaper; hb_shaper++) { + if (strcmp (*shaper, *hb_shaper) == 0) { + found = true; + break; + } + } + if (!found) { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Unknown or unsupported shaper: %s", *shaper); + g_strfreev (shapers); + return false; + } + } + + g_strfreev (shape_opts->shapers); + shape_opts->shapers = shapers; + return true; +} + +static G_GNUC_NORETURN gboolean +list_shapers (const char *name G_GNUC_UNUSED, + const char *arg G_GNUC_UNUSED, + gpointer data G_GNUC_UNUSED, + GError **error G_GNUC_UNUSED) +{ + for (const char **shaper = hb_shape_list_shapers (); *shaper; shaper++) + g_printf ("%s\n", *shaper); + + exit(0); +} + + +static gboolean +parse_features (const char *name G_GNUC_UNUSED, + const char *arg, + gpointer data, + GError **error G_GNUC_UNUSED) +{ + shape_options_t *shape_opts = (shape_options_t *) data; + char *s = (char *) arg; + size_t l = strlen (s); + char *p; + + shape_opts->num_features = 0; + g_free (shape_opts->features); + shape_opts->features = nullptr; + + /* if the string is quoted, strip the quotes */ + if (s[0] == s[l - 1] && (s[0] == '\"' || s[0] == '\'')) + { + s[l - 1] = '\0'; + s++; + } + + if (!*s) + return true; + + /* count the features first, so we can allocate memory */ + p = s; + do { + shape_opts->num_features++; + p = strchr (p, ','); + if (p) + p++; + } while (p); + + shape_opts->features = (hb_feature_t *) calloc (shape_opts->num_features, sizeof (*shape_opts->features)); + if (!shape_opts->features) + return false; + + /* now do the actual parsing */ + p = s; + shape_opts->num_features = 0; + while (p && *p) { + char *end = strchr (p, ','); + if (hb_feature_from_string (p, end ? end - p : -1, &shape_opts->features[shape_opts->num_features])) + shape_opts->num_features++; + p = end ? end + 1 : nullptr; + } + + return true; +} + +void +shape_options_t::add_options (option_parser_t *parser) +{ + GOptionEntry entries[] = + { + {"list-shapers", 0, G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, (gpointer) &list_shapers, "List available shapers and quit", nullptr}, + {"shaper", 0, G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_CALLBACK, (gpointer) &parse_shapers, "Hidden duplicate of --shapers", nullptr}, + {"shapers", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_shapers, "Set comma-separated list of shapers to try","list"}, + {"direction", 0, 0, G_OPTION_ARG_STRING, &this->direction, "Set text direction (default: auto)", "ltr/rtl/ttb/btt"}, + {"language", 0, 0, G_OPTION_ARG_STRING, &this->language, "Set text language (default: $LANG)", "langstr"}, + {"script", 0, 0, G_OPTION_ARG_STRING, &this->script, "Set text script (default: auto)", "ISO-15924 tag"}, + {"bot", 0, 0, G_OPTION_ARG_NONE, &this->bot, "Treat text as beginning-of-paragraph", nullptr}, + {"eot", 0, 0, G_OPTION_ARG_NONE, &this->eot, "Treat text as end-of-paragraph", nullptr}, + {"preserve-default-ignorables",0, 0, G_OPTION_ARG_NONE, &this->preserve_default_ignorables, "Preserve Default-Ignorable characters", nullptr}, + {"remove-default-ignorables",0, 0, G_OPTION_ARG_NONE, &this->remove_default_ignorables, "Remove Default-Ignorable characters", nullptr}, + {"invisible-glyph", 0, 0, G_OPTION_ARG_INT, &this->invisible_glyph, "Glyph value to replace Default-Ignorables with", nullptr}, + {"utf8-clusters", 0, 0, G_OPTION_ARG_NONE, &this->utf8_clusters, "Use UTF8 byte indices, not char indices", nullptr}, + {"cluster-level", 0, 0, G_OPTION_ARG_INT, &this->cluster_level, "Cluster merging level (default: 0)", "0/1/2"}, + {"normalize-glyphs",0, 0, G_OPTION_ARG_NONE, &this->normalize_glyphs, "Rearrange glyph clusters in nominal order", nullptr}, + {"verify", 0, 0, G_OPTION_ARG_NONE, &this->verify, "Perform sanity checks on shaping results", nullptr}, + {"num-iterations", 'n', 0, G_OPTION_ARG_INT, &this->num_iterations, "Run shaper N times (default: 1)", "N"}, + {nullptr} + }; + parser->add_group (entries, + "shape", + "Shape options:", + "Options for the shaping process", + this); + + const gchar *features_help = "Comma-separated list of font features\n" + "\n" + " Features can be enabled or disabled, either globally or limited to\n" + " specific character ranges. The format for specifying feature settings\n" + " follows. All valid CSS font-feature-settings values other than 'normal'\n" + " and the global values are also accepted, though not documented below.\n" + " CSS string escapes are not supported." + "\n" + " The range indices refer to the positions between Unicode characters,\n" + " unless the --utf8-clusters is provided, in which case range indices\n" + " refer to UTF-8 byte indices. The position before the first character\n" + " is always 0.\n" + "\n" + " The format is Python-esque. Here is how it all works:\n" + "\n" + " Syntax: Value: Start: End:\n" + "\n" + " Setting value:\n" + " \"kern\" 1 0 ∞ # Turn feature on\n" + " \"+kern\" 1 0 ∞ # Turn feature on\n" + " \"-kern\" 0 0 ∞ # Turn feature off\n" + " \"kern=0\" 0 0 ∞ # Turn feature off\n" + " \"kern=1\" 1 0 ∞ # Turn feature on\n" + " \"aalt=2\" 2 0 ∞ # Choose 2nd alternate\n" + "\n" + " Setting index:\n" + " \"kern[]\" 1 0 ∞ # Turn feature on\n" + " \"kern[:]\" 1 0 ∞ # Turn feature on\n" + " \"kern[5:]\" 1 5 ∞ # Turn feature on, partial\n" + " \"kern[:5]\" 1 0 5 # Turn feature on, partial\n" + " \"kern[3:5]\" 1 3 5 # Turn feature on, range\n" + " \"kern[3]\" 1 3 3+1 # Turn feature on, single char\n" + "\n" + " Mixing it all:\n" + "\n" + " \"aalt[3:5]=2\" 2 3 5 # Turn 2nd alternate on for range"; + + GOptionEntry entries2[] = + { + {"features", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_features, features_help, "list"}, + {nullptr} + }; + parser->add_group (entries2, + "features", + "Features options:", + "Options for font features used", + this); +} + +#endif diff --git a/util/options-subset.cc b/util/subset-options.hh similarity index 88% rename from util/options-subset.cc rename to util/subset-options.hh index dbabe3692..b202f1deb 100644 --- a/util/options-subset.cc +++ b/util/subset-options.hh @@ -24,9 +24,51 @@ * Google Author(s): Garret Rieger */ -#include "options.hh" +#ifndef SUBSET_OPTIONS_HH +#define SUBSET_OPTIONS_HH + +#include "options.hh" +#include "hb-subset.h" + +struct subset_options_t +{ + subset_options_t () + : input (hb_subset_input_create_or_fail ()) + {} + ~subset_options_t () + { + hb_subset_input_destroy (input); + } + + void add_options (option_parser_t *parser); + + hb_bool_t* bool_for(hb_subset_flags_t flag) + { + for (unsigned i = 0; i < sizeof(int) * 8; i++) + { + if (1u << i == flag) + return &flags[i]; + } + return &flags[sizeof(int) * 8 - 1]; + } + + hb_subset_input_t * get_input () + { + hb_subset_flags_t flags_set = HB_SUBSET_FLAGS_DEFAULT; + for (unsigned i = 0; i < sizeof(int) * 8; i++) + { + if (flags[i]) + flags_set = (hb_subset_flags_t) (flags_set | (1u << i)); + } + hb_subset_input_set_flags (input, flags_set); + return input; + } + + unsigned num_iterations = 1; + hb_subset_input_t *input = nullptr; + hb_bool_t flags[sizeof(int) * 8] = {0}; +}; -#include "hb-subset-input.hh" static gboolean parse_gids (const char *name G_GNUC_UNUSED, @@ -35,7 +77,7 @@ parse_gids (const char *name G_GNUC_UNUSED, GError **error G_GNUC_UNUSED) { subset_options_t *subset_opts = (subset_options_t *) data; - hb_set_t *gids = subset_opts->input->glyphs; + hb_set_t *gids = hb_subset_input_glyph_set (subset_opts->input); char *s = (char *) arg; char *p; @@ -95,7 +137,7 @@ parse_nameids (const char *name, GError **error G_GNUC_UNUSED) { subset_options_t *subset_opts = (subset_options_t *) data; - hb_set_t *name_ids = subset_opts->input->name_ids; + hb_set_t *name_ids = hb_subset_input_nameid_set (subset_opts->input); char last_name_char = name[strlen (name) - 1]; @@ -151,7 +193,7 @@ parse_name_languages (const char *name, GError **error G_GNUC_UNUSED) { subset_options_t *subset_opts = (subset_options_t *) data; - hb_set_t *name_languages = subset_opts->input->name_languages; + hb_set_t *name_languages = hb_subset_input_namelangid_set (subset_opts->input); char last_name_char = name[strlen (name) - 1]; @@ -207,7 +249,7 @@ parse_layout_features (const char *name, GError **error G_GNUC_UNUSED) { subset_options_t *subset_opts = (subset_options_t *) data; - hb_set_t *layout_features = subset_opts->input->layout_features; + hb_set_t *layout_features = hb_subset_input_layout_features_set (subset_opts->input); char last_name_char = name[strlen (name) - 1]; @@ -256,7 +298,7 @@ parse_drop_tables (const char *name, GError **error G_GNUC_UNUSED) { subset_options_t *subset_opts = (subset_options_t *) data; - hb_set_t *drop_tables = subset_opts->input->drop_tables; + hb_set_t *drop_tables = hb_subset_input_drop_tables_set (subset_opts->input); char last_name_char = name[strlen (name) - 1]; @@ -324,3 +366,5 @@ subset_options_t::add_options (option_parser_t *parser) "Options subsetting", this); } + +#endif diff --git a/util/text-options.hh b/util/text-options.hh new file mode 100644 index 000000000..ad72d7384 --- /dev/null +++ b/util/text-options.hh @@ -0,0 +1,211 @@ + +struct text_options_t +{ + ~text_options_t () + { + g_free (text_before); + g_free (text_after); + g_free (text); + g_free (text_file); + if (gs) + g_string_free (gs, true); + if (fp && fp != stdin) + fclose (fp); + } + + void add_options (option_parser_t *parser); + + void post_parse (GError **error G_GNUC_UNUSED) + { + 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"); + } + + const char *get_line (unsigned int *len, int eol = '\n'); + + char *text_before = nullptr; + char *text_after = nullptr; + + int text_len = -1; + char *text = nullptr; + char *text_file = nullptr; + + private: + FILE *fp = nullptr; + GString *gs = nullptr; + char *line = nullptr; + unsigned int line_len = UINT_MAX; +}; + + +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 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 + { + + char *s = (char *) arg; + char *p; + + while (s && *s) + { +#define DELIMITERS "<+>{},;&#\\xXuUnNiI\n\t\v\f\r " + + 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 values at: '%s'", s); + return false; + } + + g_string_append_unichar (gs, u); + + s = p; + } + } + + text_opts->text_len = gs->len; + text_opts->text = g_string_free (gs, FALSE); + return true; +} + +const char * +text_options_t::get_line (unsigned int *len, int eol) +{ + 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 = (const char *) memchr (line, eol, 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; + } + + if (!fp) + { + if (!text_file) + fail (true, "At least one of text or text-file must be set"); + + if (0 != strcmp (text_file, "-")) + fp = fopen (text_file, "r"); + else + fp = stdin; + + if (!fp) + fail (false, "Failed opening text file `%s': %s", + text_file, strerror (errno)); + + gs = g_string_new (nullptr); + } + + g_string_set_size (gs, 0); + char buf[BUFSIZ]; + while (fgets (buf, sizeof (buf), fp)) + { + unsigned bytes = strlen (buf); + if (bytes && (int) (unsigned char) buf[bytes - 1] == eol) + { + bytes--; + g_string_append_len (gs, buf, bytes); + break; + } + g_string_append_len (gs, buf, bytes); + } + if (ferror (fp)) + fail (false, "Failed reading text: %s", strerror (errno)); + *len = gs->len; + return !*len && feof (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\n\n If no text is provided, standard input is used for input.\n", "filename"}, + {"unicodes", 'u', 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_unicodes, "Set input Unicode codepoints", "list of hex numbers"}, + {"text-before", 0, 0, G_OPTION_ARG_STRING, &this->text_before, "Set text context before each line", "string"}, + {"text-after", 0, 0, G_OPTION_ARG_STRING, &this->text_after, "Set text context after each line", "string"}, + {nullptr} + }; + parser->add_group (entries, + "text", + "Text options:", + "Options for the input text", + this); +} diff --git a/util/view-cairo.cc b/util/view-cairo.cc deleted file mode 100644 index 0db3306af..000000000 --- a/util/view-cairo.cc +++ /dev/null @@ -1,140 +0,0 @@ -/* - * 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 - */ - -#include "view-cairo.hh" - -#include - - -void -view_cairo_t::render (const font_options_t *font_opts) -{ - bool vertical = HB_DIRECTION_IS_VERTICAL (direction); - int vert = vertical ? 1 : 0; - int horiz = vertical ? 0 : 1; - - int x_sign = font_opts->font_size_x < 0 ? -1 : +1; - int y_sign = font_opts->font_size_y < 0 ? -1 : +1; - - hb_font_t *font = font_opts->get_font(); - - view_options_t::font_extents_t extents = view_options.font_extents; - if (!view_options.have_font_extents) - { - hb_font_extents_t hb_extents; - hb_font_get_extents_for_direction (font, direction, &hb_extents); - extents.ascent = scalbn ((double) hb_extents.ascender, scale_bits); - extents.descent = -scalbn ((double) hb_extents.descender, scale_bits); - extents.line_gap = scalbn ((double) hb_extents.line_gap, scale_bits); - } - - double ascent = y_sign * extents.ascent; - double descent = y_sign * extents.descent; - double line_gap = y_sign * extents.line_gap + view_options.line_space; - double leading = ascent + descent + line_gap; - - /* Calculate surface size. */ - double w = 0, h = 0; - (vertical ? w : h) = (int) lines->len * leading - (extents.line_gap + view_options.line_space); - (vertical ? h : w) = 0; - for (unsigned int i = 0; i < lines->len; i++) { - helper_cairo_line_t &line = g_array_index (lines, helper_cairo_line_t, i); - double x_advance, y_advance; - line.get_advance (&x_advance, &y_advance); - if (vertical) - h = MAX (h, y_sign * y_advance); - else - w = MAX (w, x_sign * x_advance); - } - - cairo_scaled_font_t *scaled_font = helper_cairo_create_scaled_font (font_opts); - - /* See if font needs color. */ - cairo_content_t content = CAIRO_CONTENT_ALPHA; - if (helper_cairo_scaled_font_has_color (scaled_font)) - content = CAIRO_CONTENT_COLOR; - - /* Create surface. */ - cairo_t *cr = helper_cairo_create_context (w + view_options.margin.l + view_options.margin.r, - h + view_options.margin.t + view_options.margin.b, - &view_options, &output_options, content); - cairo_set_scaled_font (cr, scaled_font); - - /* Setup coordinate system. */ - cairo_translate (cr, view_options.margin.l, view_options.margin.t); - if (vertical) - cairo_translate (cr, - w - ascent, /* We currently always stack lines right to left */ - y_sign < 0 ? h : 0); - else - { - cairo_translate (cr, - x_sign < 0 ? w : 0, - y_sign < 0 ? descent : ascent); - } - - /* Draw. */ - cairo_translate (cr, +vert * leading, -horiz * leading); - for (unsigned int i = 0; i < lines->len; i++) - { - helper_cairo_line_t &l = g_array_index (lines, helper_cairo_line_t, i); - - cairo_translate (cr, -vert * leading, +horiz * leading); - - if (view_options.annotate) { - cairo_save (cr); - - /* Draw actual glyph origins */ - cairo_set_source_rgba (cr, 1., 0., 0., .5); - cairo_set_line_width (cr, 5); - cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); - for (unsigned i = 0; i < l.num_glyphs; i++) { - cairo_move_to (cr, l.glyphs[i].x, l.glyphs[i].y); - cairo_rel_line_to (cr, 0, 0); - } - cairo_stroke (cr); - - cairo_restore (cr); - } - - if (0 && cairo_surface_get_type (cairo_get_target (cr)) == CAIRO_SURFACE_TYPE_IMAGE) { - /* cairo_show_glyphs() doesn't support subpixel positioning */ - cairo_glyph_path (cr, l.glyphs, l.num_glyphs); - cairo_fill (cr); - } else if (l.num_clusters) - cairo_show_text_glyphs (cr, - l.utf8, l.utf8_len, - l.glyphs, l.num_glyphs, - l.clusters, l.num_clusters, - l.cluster_flags); - else - cairo_show_glyphs (cr, l.glyphs, l.num_glyphs); - } - - /* Clean up. */ - helper_cairo_destroy_context (cr); - cairo_scaled_font_destroy (scaled_font); -} diff --git a/util/view-cairo.hh b/util/view-cairo.hh index 1f51f0e93..bfe4e2a0c 100644 --- a/util/view-cairo.hh +++ b/util/view-cairo.hh @@ -27,22 +27,23 @@ #ifndef VIEW_CAIRO_HH #define VIEW_CAIRO_HH -#include "hb.hh" -#include "options.hh" +#include "view-options.hh" +#include "output-options.hh" #include "helper-cairo.hh" - -struct view_cairo_t +struct view_cairo_t : view_options_t, output_options_t { - view_cairo_t (option_parser_t *parser) - : output_options (parser, helper_cairo_supported_formats), - view_options (parser), - direction (HB_DIRECTION_INVALID), - lines (0), scale_bits (0) {} - ~view_cairo_t () { + ~view_cairo_t () + { cairo_debug_reset_static_data (); } + void add_options (option_parser_t *parser) + { + view_options_t::add_options (parser); + output_options_t::add_options (parser, helper_cairo_supported_formats); + } + void init (hb_buffer_t *buffer, const font_options_t *font_opts) { lines = g_array_new (false, false, sizeof (helper_cairo_line_t)); @@ -82,14 +83,123 @@ struct view_cairo_t protected: - output_options_t output_options; - view_options_t view_options; - void render (const font_options_t *font_opts); - hb_direction_t direction; // Remove this, make segment_properties accessible - GArray *lines; - int scale_bits; + hb_direction_t direction = HB_DIRECTION_INVALID; // Remove this, make segment_properties accessible + GArray *lines = nullptr; + int scale_bits = 0; }; +inline void +view_cairo_t::render (const font_options_t *font_opts) +{ + bool vertical = HB_DIRECTION_IS_VERTICAL (direction); + int vert = vertical ? 1 : 0; + int horiz = vertical ? 0 : 1; + + int x_sign = font_opts->font_size_x < 0 ? -1 : +1; + int y_sign = font_opts->font_size_y < 0 ? -1 : +1; + + hb_font_t *font = font_opts->get_font(); + + if (!have_font_extents) + { + hb_font_extents_t hb_extents; + hb_font_get_extents_for_direction (font, direction, &hb_extents); + font_extents.ascent = scalbn ((double) hb_extents.ascender, scale_bits); + font_extents.descent = -scalbn ((double) hb_extents.descender, scale_bits); + font_extents.line_gap = scalbn ((double) hb_extents.line_gap, scale_bits); + have_font_extents = true; + } + + double ascent = y_sign * font_extents.ascent; + double descent = y_sign * font_extents.descent; + double line_gap = y_sign * font_extents.line_gap + line_space; + double leading = ascent + descent + line_gap; + + /* Calculate surface size. */ + double w = 0, h = 0; + (vertical ? w : h) = (int) lines->len * leading - (font_extents.line_gap + line_space); + (vertical ? h : w) = 0; + for (unsigned int i = 0; i < lines->len; i++) { + helper_cairo_line_t &line = g_array_index (lines, helper_cairo_line_t, i); + double x_advance, y_advance; + line.get_advance (&x_advance, &y_advance); + if (vertical) + h = MAX (h, y_sign * y_advance); + else + w = MAX (w, x_sign * x_advance); + } + + cairo_scaled_font_t *scaled_font = helper_cairo_create_scaled_font (font_opts); + + /* See if font needs color. */ + cairo_content_t content = CAIRO_CONTENT_ALPHA; + if (helper_cairo_scaled_font_has_color (scaled_font)) + content = CAIRO_CONTENT_COLOR; + + /* Create surface. */ + cairo_t *cr = helper_cairo_create_context (w + margin.l + margin.r, + h + margin.t + margin.b, + static_cast (this), + static_cast (this), + content); + cairo_set_scaled_font (cr, scaled_font); + + /* Setup coordinate system. */ + cairo_translate (cr, margin.l, margin.t); + if (vertical) + cairo_translate (cr, + w - ascent, /* We currently always stack lines right to left */ + y_sign < 0 ? h : 0); + else + { + cairo_translate (cr, + x_sign < 0 ? w : 0, + y_sign < 0 ? descent : ascent); + } + + /* Draw. */ + cairo_translate (cr, +vert * leading, -horiz * leading); + for (unsigned int i = 0; i < lines->len; i++) + { + helper_cairo_line_t &l = g_array_index (lines, helper_cairo_line_t, i); + + cairo_translate (cr, -vert * leading, +horiz * leading); + + if (annotate) { + cairo_save (cr); + + /* Draw actual glyph origins */ + cairo_set_source_rgba (cr, 1., 0., 0., .5); + cairo_set_line_width (cr, 5); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + for (unsigned i = 0; i < l.num_glyphs; i++) { + cairo_move_to (cr, l.glyphs[i].x, l.glyphs[i].y); + cairo_rel_line_to (cr, 0, 0); + } + cairo_stroke (cr); + + cairo_restore (cr); + } + + if (0 && cairo_surface_get_type (cairo_get_target (cr)) == CAIRO_SURFACE_TYPE_IMAGE) { + /* cairo_show_glyphs() doesn't support subpixel positioning */ + cairo_glyph_path (cr, l.glyphs, l.num_glyphs); + cairo_fill (cr); + } else if (l.num_clusters) + cairo_show_text_glyphs (cr, + l.utf8, l.utf8_len, + l.glyphs, l.num_glyphs, + l.clusters, l.num_clusters, + l.cluster_flags); + else + cairo_show_glyphs (cr, l.glyphs, l.num_glyphs); + } + + /* Clean up. */ + helper_cairo_destroy_context (cr); + cairo_scaled_font_destroy (scaled_font); +} + #endif diff --git a/util/view-options.hh b/util/view-options.hh new file mode 100644 index 000000000..322009a1e --- /dev/null +++ b/util/view-options.hh @@ -0,0 +1,123 @@ +/* + * 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" + +struct view_options_t +{ + ~view_options_t () + { + g_free (fore); + g_free (back); + } + + void add_options (option_parser_t *parser); + + hb_bool_t annotate = false; + char *fore = nullptr; + char *back = nullptr; + 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}; +}; + + +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; + switch (sscanf (arg, "%lf%*[ ,]%lf%*[ ,]%lf%*[ ,]%lf", &m.t, &m.r, &m.b, &m.l)) { + case 1: m.r = m.t; HB_FALLTHROUGH; + case 2: m.b = m.t; HB_FALLTHROUGH; + case 3: m.l = m.r; HB_FALLTHROUGH; + case 4: return true; + default: + 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, 0, G_OPTION_ARG_NONE, &this->annotate, "Annotate output rendering", nullptr}, + {"background", 0, 0, G_OPTION_ARG_STRING, &this->back, "Set background color (default: " DEFAULT_BACK ")", "rrggbb/rrggbbaa"}, + {"foreground", 0, 0, G_OPTION_ARG_STRING, &this->fore, "Set foreground color (default: " DEFAULT_FORE ")", "rrggbb/rrggbbaa"}, + {"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"}, + {nullptr} + }; + parser->add_group (entries, + "view", + "View options:", + "Options for output rendering", + this); +} + +#endif