From b9b10ad78b1f977494a3a42b58f8040fe16505a3 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Tue, 13 Sep 2011 13:30:39 -0400 Subject: [PATCH] [util] Refactor hb-view completely Now we can use the same code to do other utils... --- configure.ac | 2 + util/Makefile.am | 2 + util/common.cc | 7 +- util/common.hh | 2 +- util/hb-view.cc | 250 ++++---------------------- util/options.cc | 337 +++++++++++++++++++++++----------- util/options.hh | 179 +++++++++++++++--- util/view-cairo.cc | 440 +++++++++++++++++++++++++++++++++++++++++++++ util/view-cairo.hh | 63 +++++++ 9 files changed, 938 insertions(+), 344 deletions(-) create mode 100644 util/view-cairo.cc create mode 100644 util/view-cairo.hh diff --git a/configure.ac b/configure.ac index 2dc9d7d3e..82451814d 100644 --- a/configure.ac +++ b/configure.ac @@ -49,6 +49,8 @@ m4_define([hb_libtool_current], HB_LIBTOOL_VERSION_INFO=hb_libtool_current:hb_libtool_revision:hb_libtool_age AC_SUBST(HB_LIBTOOL_VERSION_INFO) +GTK_DOC_CHECK([1.15],[--flavour no-tmpl]) + # Functions and headers AC_CHECK_FUNCS(mprotect sysconf getpagesize mmap) AC_CHECK_HEADERS(unistd.h sys/mman.h) diff --git a/util/Makefile.am b/util/Makefile.am index 7939975f9..a899c2654 100644 --- a/util/Makefile.am +++ b/util/Makefile.am @@ -18,6 +18,8 @@ hb_view_SOURCES = \ common.hh \ options.cc \ options.hh \ + view-cairo.cc \ + view-cairo.hh \ $(NULL) hb_view_CPPFLAGS = \ -I$(top_srcdir)/src/ \ diff --git a/util/common.cc b/util/common.cc index 7326b8cd4..b31991423 100644 --- a/util/common.cc +++ b/util/common.cc @@ -27,14 +27,17 @@ #include "common.hh" void -fail (const char *format, ...) +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); - g_printerr ("%s: %s\n", g_get_prgname (), msg); + 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); } diff --git a/util/common.hh b/util/common.hh index 58dec6fd5..45ac6efe2 100644 --- a/util/common.hh +++ b/util/common.hh @@ -45,7 +45,7 @@ #include -void fail (const char *format, ...) G_GNUC_NORETURN; +void fail (hb_bool_t suggest_help, const char *format, ...) G_GNUC_NORETURN; #endif diff --git a/util/hb-view.cc b/util/hb-view.cc index 419b7d5bb..d745c21cb 100644 --- a/util/hb-view.cc +++ b/util/hb-view.cc @@ -26,234 +26,50 @@ */ #include "common.hh" - - -#include -#include - #include "options.hh" - -/* Ugh, global vars. Ugly, but does the job */ -static int width = 0; -static int height = 0; -static cairo_surface_t *surface = NULL; -static cairo_pattern_t *fore_pattern = NULL; -static cairo_pattern_t *back_pattern = NULL; -static cairo_font_face_t *cairo_face; - - - -static cairo_glyph_t * -_hb_cr_text_glyphs (cairo_t *cr, - const char *utf8, int len, - unsigned int *pnum_glyphs) -{ - cairo_scaled_font_t *scaled_font = cairo_get_scaled_font (cr); - FT_Face ft_face = cairo_ft_scaled_font_lock_face (scaled_font); - hb_font_t *hb_font = hb_ft_font_create (ft_face, NULL); - hb_buffer_t *hb_buffer; - cairo_glyph_t *cairo_glyphs; - hb_glyph_info_t *hb_glyph; - hb_glyph_position_t *hb_position; - unsigned int num_glyphs, i; - hb_position_t x, y; - - hb_buffer = hb_buffer_create (); - - - hb_buffer_add_utf8 (hb_buffer, utf8, len, 0, len); - - if (!shape_opts->shape (hb_font, hb_buffer)) - fail ("All shapers failed"); - - num_glyphs = hb_buffer_get_length (hb_buffer); - hb_glyph = hb_buffer_get_glyph_infos (hb_buffer, NULL); - hb_position = hb_buffer_get_glyph_positions (hb_buffer, NULL); - cairo_glyphs = cairo_glyph_allocate (num_glyphs); - x = 0; - y = 0; - for (i = 0; i < num_glyphs; i++) - { - cairo_glyphs[i].index = hb_glyph->codepoint; - cairo_glyphs[i].x = ( hb_position->x_offset + x) * (1./64); - cairo_glyphs[i].y = (-hb_position->y_offset + y) * (1./64); - x += hb_position->x_advance; - y += -hb_position->y_advance; - - hb_glyph++; - hb_position++; - } - hb_buffer_destroy (hb_buffer); - hb_font_destroy (hb_font); - cairo_ft_scaled_font_unlock_face (scaled_font); - - if (pnum_glyphs) - *pnum_glyphs = num_glyphs; - return cairo_glyphs; -} - -static cairo_t * -create_context (void) -{ - cairo_t *cr; - unsigned int fr, fg, fb, fa, br, bg, bb, ba; - - if (surface) - cairo_surface_destroy (surface); - if (back_pattern) - cairo_pattern_destroy (back_pattern); - if (fore_pattern) - cairo_pattern_destroy (fore_pattern); - - br = bg = bb = ba = 255; - sscanf (view_opts->back + (*view_opts->back=='#'), "%2x%2x%2x%2x", &br, &bg, &bb, &ba); - fr = fg = fb = 0; fa = 255; - sscanf (view_opts->fore + (*view_opts->fore=='#'), "%2x%2x%2x%2x", &fr, &fg, &fb, &fa); - - if (!view_opts->annotate && ba == 255 && fa == 255 && br == bg && bg == bb && fr == fg && fg == fb) { - /* grayscale. use A8 surface */ - surface = cairo_image_surface_create (CAIRO_FORMAT_A8, width, height); - cr = cairo_create (surface); - cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); - cairo_set_source_rgba (cr, 1., 1., 1., br / 255.); - cairo_paint (cr); - back_pattern = cairo_pattern_reference (cairo_get_source (cr)); - cairo_set_source_rgba (cr, 1., 1., 1., fr / 255.); - fore_pattern = cairo_pattern_reference (cairo_get_source (cr)); - } else { - /* color. use (A)RGB surface */ - if (ba != 255) - surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); - else - surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height); - cr = cairo_create (surface); - cairo_set_source_rgba (cr, br / 255., bg / 255., bb / 255., ba / 255.); - cairo_paint (cr); - back_pattern = cairo_pattern_reference (cairo_get_source (cr)); - cairo_set_source_rgba (cr, fr / 255., fg / 255., fb / 255., fa / 255.); - fore_pattern = cairo_pattern_reference (cairo_get_source (cr)); - } - - cairo_set_font_face (cr, cairo_face); - - return cr; -} - -static void -draw (void) -{ - cairo_t *cr; - cairo_font_extents_t font_extents; - - cairo_glyph_t *glyphs = NULL; - unsigned int num_glyphs = 0; - - const char *end, *p = text; - double x, y; - - cr= create_context (); - - cairo_set_font_size (cr, font_opts->font_size); - cairo_font_extents (cr, &font_extents); - - height = 0; - width = 0; - - x = view_opts->margin.l; - y = view_opts->margin.t; - - do { - cairo_text_extents_t extents; - - end = strchr (p, '\n'); - if (!end) - end = p + strlen (p); - - if (p != text) - y += view_opts->line_space; - - if (p != end) { - glyphs = _hb_cr_text_glyphs (cr, p, end - p, &num_glyphs); - - cairo_glyph_extents (cr, glyphs, num_glyphs, &extents); - - y += ceil (font_extents.ascent); - width = MAX (width, extents.x_advance); - cairo_save (cr); - cairo_translate (cr, x, y); - if (view_opts->annotate) { - unsigned int i; - 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 (i = 0; i < num_glyphs; i++) { - cairo_move_to (cr, glyphs[i].x, glyphs[i].y); - cairo_rel_line_to (cr, 0, 0); - } - cairo_stroke (cr); - - cairo_restore (cr); - } - cairo_show_glyphs (cr, glyphs, num_glyphs); - cairo_restore (cr); - y += ceil (font_extents.height - ceil (font_extents.ascent)); - - cairo_glyph_free (glyphs); - } - - p = end + 1; - } while (*end); - - height = y + view_opts->margin.b; - width += view_opts->margin.l + view_opts->margin.r; - - cairo_destroy (cr); -} - - +#include "view-cairo.hh" int main (int argc, char **argv) { - static FT_Library ft_library; - static FT_Face ft_face; - cairo_status_t status; - setlocale (LC_ALL, ""); - parse_options (argc, argv); + option_parser_t options ("[FONT-FILE] [TEXT]"); - FT_Init_FreeType (&ft_library); - if (FT_New_Face (ft_library, font_opts->font_file, font_opts->face_index, &ft_face)) { - fprintf (stderr, "Failed to open font file `%s'\n", font_opts->font_file); - exit (1); + shape_options_t shaper (&options); + font_options_t font_opts (&options); + text_options_t input (&options); + + view_cairo_t output (&options); + + options.parse (&argc, &argv); + + argc--, argv++; + if (argc && !font_opts.font_file) font_opts.font_file = argv[0], argc--, argv++; + if (argc && !input.text && !input.text_file) input.text = argv[0], argc--, argv++; + if (argc) + fail (TRUE, "Too many arguments on the command line"); + if (!font_opts.font_file || (!input.text && !input.text_file)) + options.usage (); + + output.init (&font_opts); + + hb_buffer_t *buffer = hb_buffer_create (); + unsigned int text_len; + const char *text; + while ((text = input.get_line (&text_len))) + { + if (!shaper.shape (text, text_len, + font_opts.get_font (), + buffer)) + fail (FALSE, "All shapers failed"); + + output.consume_line (buffer, text, text_len); } - cairo_face = cairo_ft_font_face_create_for_ft_face (ft_face, 0); + hb_buffer_destroy (buffer); - draw (); - draw (); - - status = cairo_surface_write_to_png (surface, out_file); - if (status != CAIRO_STATUS_SUCCESS) { - fprintf (stderr, "Failed to write output file `%s': %s\n", - out_file, cairo_status_to_string (status)); - exit (1); - } - - if (debug) { - cairo_pattern_destroy (fore_pattern); - cairo_pattern_destroy (back_pattern); - cairo_surface_destroy (surface); - cairo_font_face_destroy (cairo_face); - cairo_debug_reset_static_data (); - - FT_Done_Face (ft_face); - FT_Done_FreeType (ft_library); - } + output.finish (&font_opts); return 0; } diff --git a/util/options.cc b/util/options.cc index 6fc40299b..eb3ce69b7 100644 --- a/util/options.cc +++ b/util/options.cc @@ -26,14 +26,107 @@ #include "options.hh" +#if HAVE_FREETYPE +#include +#endif -view_options_t view_opts[1]; -shape_options_t shape_opts[1]; -font_options_t font_opts[1]; -const char *text; -const char *out_file = "/dev/stdout"; -hb_bool_t debug = FALSE; +bool debug = FALSE; + +static gchar * +shapers_to_string (void) +{ + GString *shapers = g_string_new (NULL); + 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 (void) +{ + GOptionEntry entries[] = + { + {"version", 0, G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, (gpointer) &show_version, "Show version numbers", NULL}, + {"debug", 0, 0, G_OPTION_ARG_NONE, &debug, "Free all resources before exit", NULL}, + {NULL} + }; + g_option_context_add_main_entries (context, entries, NULL); +} + +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 == NULL; +} + +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 == NULL; +} + +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), NULL); + 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) +{ + GError *parse_error = NULL; + if (!g_option_context_parse (context, argc, argv, &parse_error)) + { + if (parse_error != NULL) + fail (TRUE, "%s", parse_error->message); + else + fail (TRUE, "Option parse error"); + } +} static gboolean @@ -232,54 +325,8 @@ parse_features (const char *name G_GNUC_UNUSED, } -static gchar * -shapers_to_string (void) -{ - GString *shapers = g_string_new (NULL); - 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); -} - - -static void -option_context_add_entries (GOptionContext *context, - GOptionEntry *entries, - const gchar *name, - const gchar *description, - const gchar *help_description, - gpointer user_data) -{ - GOptionGroup *group = g_option_group_new (name, description, help_description, user_data, NULL); - g_option_group_add_entries (group, entries); - g_option_context_add_group (context, group); -} - void -view_options_t::add_options (GOptionContext *context) +view_options_t::add_options (option_parser_t *parser) { GOptionEntry entries[] = { @@ -290,15 +337,15 @@ view_options_t::add_options (GOptionContext *context) {"margin", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_margin, "Margin around output (default: "G_STRINGIFY(DEFAULT_MARGIN)")","one to four numbers"}, {NULL} }; - option_context_add_entries (context, entries, - "view", - "View options:", - "Options controlling the output rendering", - this); + parser->add_group (entries, + "view", + "View options:", + "Options controlling the output rendering", + this); } void -shape_options_t::add_options (GOptionContext *context) +shape_options_t::add_options (option_parser_t *parser) { GOptionEntry entries[] = { @@ -309,65 +356,149 @@ shape_options_t::add_options (GOptionContext *context) {"features", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_features, "Font features to apply to text", "TODO"}, {NULL} }; - option_context_add_entries (context, entries, - "shape", - "Shape options:", - "Options controlling the shaping process", - this); + parser->add_group (entries, + "shape", + "Shape options:", + "Options controlling the shaping process", + this); } void -font_options_t::add_options (GOptionContext *context) +font_options_t::add_options (option_parser_t *parser) { GOptionEntry entries[] = { - {"face-index", 0, 0, G_OPTION_ARG_INT, &this->face_index, "Face index (default: 0)", "index"}, + {"font-file", 0, 0, G_OPTION_ARG_STRING, &this->font_file, "Font file-name", "filename"}, + {"face-index", 0, 0, G_OPTION_ARG_INT, &this->face_index, "Face index (default: 0)", "index"}, {"font-size", 0, 0, G_OPTION_ARG_DOUBLE, &this->font_size, "Font size (default: "G_STRINGIFY(DEFAULT_FONT_SIZE)")","size"}, {NULL} }; - option_context_add_entries (context, entries, - "font", - "Font options:", - "Options controlling the font", - NULL); + parser->add_group (entries, + "font", + "Font options:", + "Options controlling the font", + this); } void -parse_options (int argc, char *argv[]) +text_options_t::add_options (option_parser_t *parser) { GOptionEntry entries[] = { - {"version", 0, G_OPTION_FLAG_NO_ARG, - G_OPTION_ARG_CALLBACK, (gpointer) &show_version, "Show version numbers", NULL}, - {"debug", 0, 0, G_OPTION_ARG_NONE, &debug, "Free all resources before exit", NULL}, - {"output", 0, 0, G_OPTION_ARG_STRING, &out_file, "Set output file name", "filename"}, + {"text", 0, 0, G_OPTION_ARG_STRING, &this->text, "Set input text", "string"}, + {"text-file", 0, 0, G_OPTION_ARG_STRING, &this->text_file, "Set input text file-name", "filename"}, {NULL} }; - GError *parse_error = NULL; - GOptionContext *context; - - context = g_option_context_new ("- FONT-FILE TEXT"); - - g_option_context_add_main_entries (context, entries, NULL); - view_opts->add_options (context); - shape_opts->add_options (context); - font_opts->add_options (context); - - if (!g_option_context_parse (context, &argc, &argv, &parse_error)) - { - if (parse_error != NULL) - fail ("%s", parse_error->message); - else - fail ("Option parse error"); - exit(1); - } - g_option_context_free(context); - - if (argc != 3) { - g_printerr ("Usage: %s [OPTION...] FONT-FILE TEXT\n", g_get_prgname ()); - exit (1); - } - - font_opts->font_file = argv[1]; - text = argv[2]; + parser->add_group (entries, + "text", + "Text options:", + "Options controlling the input text", + this); +} + +void +output_options_t::add_options (option_parser_t *parser) +{ + GOptionEntry entries[] = + { + {"output", 0, 0, G_OPTION_ARG_STRING, &this->output_file, "Set output file-name (default: stdout)","filename"}, + {"format", 0, 0, G_OPTION_ARG_STRING, &this->output_format, "Set output format", "format"}, + {NULL} + }; + parser->add_group (entries, + "output", + "Output options:", + "Options controlling the output", + this); +} + + + +hb_font_t * +font_options_t::get_font (void) const +{ + if (font) + return font; + + hb_blob_t *blob = NULL; + + /* Create the blob */ + { + const char *font_data; + unsigned int len; + hb_destroy_func_t destroy; + void *user_data; + hb_memory_mode_t mm; + + if (!font_file) + fail (TRUE, "No font file set"); + + GMappedFile *mf = g_mapped_file_new (font_file, FALSE, NULL); + if (!mf) + fail (FALSE, "Failed opening font file `%s'", font_file); + font_data = g_mapped_file_get_contents (mf); + len = g_mapped_file_get_length (mf); + destroy = (hb_destroy_func_t) g_mapped_file_unref; + user_data = (void *) mf; + mm = HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITABLE; + + blob = hb_blob_create (font_data, len, mm, user_data, destroy); + } + + /* Create the face */ + hb_face_t *face = hb_face_create (blob, face_index); + hb_blob_destroy (blob); + + + font = hb_font_create (face); + + unsigned int upem = hb_face_get_upem (face); + hb_font_set_scale (font, font_size * upem, font_size * upem); + hb_face_destroy (face); + +#if HAVE_FREETYPE + hb_ft_font_set_funcs (font); +#endif + + return font; +} + + +const char * +text_options_t::get_line (unsigned int *len) +{ + if (!text) { + if (!text_file) + fail (TRUE, "At least one of text or text-file must be set"); + + GMappedFile *mf = g_mapped_file_new (text_file, FALSE, NULL); + if (!mf) + fail (FALSE, "Failed opening text file `%s'", text_file); + text = g_mapped_file_get_contents (mf); + text_len = g_mapped_file_get_length (mf); + } + + if (text_len == (unsigned int) -1) + text_len = strlen (text); + + if (!text_len) { + *len = 0; + return NULL; + } + + const char *ret = text; + const char *p = (const char *) memchr (text, '\n', text_len); + unsigned int ret_len; + if (!p) { + ret_len = text_len; + text += ret_len; + text_len = 0; + } else { + ret_len = p - ret; + text += ret_len + 1; + text_len -= ret_len + 1; + } + + *len = ret_len; + return ret; } diff --git a/util/options.hh b/util/options.hh index 422dffc2a..404fa7333 100644 --- a/util/options.hh +++ b/util/options.hh @@ -30,42 +30,94 @@ #define OPTIONS_HH +extern bool debug; + +struct option_group_t +{ + virtual void add_options (struct option_parser_t *parser) = 0; + + virtual void pre_parse (GError **error G_GNUC_UNUSED) {}; + virtual void post_parse (GError **error G_GNUC_UNUSED) {}; +}; + + +struct option_parser_t +{ + option_parser_t (const char *usage) { + memset (this, 0, sizeof (*this)); + usage_str = usage; + context = g_option_context_new (usage); + + add_main_options (); + } + ~option_parser_t (void) { + g_option_context_free (context); + } + + void add_main_options (void); + + void add_group (GOptionEntry *entries, + const gchar *name, + const gchar *description, + const gchar *help_description, + option_group_t *option_group); + + void parse (int *argc, char ***argv); + + G_GNUC_NORETURN void usage (void) { + g_printerr ("Usage: %s [OPTION...] %s\n", g_get_prgname (), usage_str); + exit (1); + } + + const char *usage_str; + GOptionContext *context; +}; + + #define DEFAULT_MARGIN 18 #define DEFAULT_FORE "#000000" #define DEFAULT_BACK "#FFFFFF" -extern struct view_options_t +struct view_options_t : option_group_t { - view_options_t (void) { - memset (this, 0, sizeof (*this)); + view_options_t (option_parser_t *parser) { + annotate = false; fore = DEFAULT_FORE; back = DEFAULT_BACK; + line_space = 0; margin.t = margin.r = margin.b = margin.l = DEFAULT_MARGIN; + + add_options (parser); } - void add_options (GOptionContext *context); + void add_options (option_parser_t *parser); - hb_bool_t annotate; + bool annotate; const char *fore; const char *back; double line_space; struct margin_t { double t, r, b, l; } margin; -} view_opts[1]; +}; -extern struct shape_options_t +struct shape_options_t : option_group_t { - shape_options_t (void) { - memset (this, 0, sizeof (*this)); + shape_options_t (option_parser_t *parser) { + direction = language = script = NULL; + features = NULL; + num_features = 0; + shapers = NULL; + + add_options (parser); } ~shape_options_t (void) { free (features); g_free (shapers); } - void add_options (GOptionContext *context); + 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)); @@ -73,7 +125,10 @@ extern struct shape_options_t hb_buffer_set_language (buffer, hb_language_from_string (language, -1)); } - bool shape (hb_font_t *font, hb_buffer_t *buffer) { + bool shape (const char *text, int text_len, + hb_font_t *font, hb_buffer_t *buffer) { + hb_buffer_reset (buffer); + hb_buffer_add_utf8 (buffer, text, text_len, 0, text_len); setup_buffer (buffer); return hb_shape_full (font, buffer, features, num_features, NULL, shapers); } @@ -84,31 +139,113 @@ extern struct shape_options_t hb_feature_t *features; unsigned int num_features; char **shapers; -} shape_opts[1]; +}; #define DEFAULT_FONT_SIZE 36 -extern struct font_options_t +struct font_options_t : option_group_t { - font_options_t (void) { - memset (this, 0, sizeof (*this)); + font_options_t (option_parser_t *parser) { + font_file = NULL; + face_index = 0; font_size = DEFAULT_FONT_SIZE; + + font = NULL; + + add_options (parser); + } + ~font_options_t (void) { + hb_font_destroy (font); } - void add_options (GOptionContext *context); + void add_options (option_parser_t *parser); + + hb_font_t *get_font (void) const; const char *font_file; int face_index; double font_size; -} font_opts[1]; + + private: + mutable hb_font_t *font; +}; -extern const char *text; -extern const char *out_file; -extern hb_bool_t debug; +struct text_options_t : option_group_t +{ + text_options_t (option_parser_t *parser) { + text = NULL; + text_file = NULL; -void parse_options (int argc, char *argv[]); + file = NULL; + text_len = (unsigned int) -1; + + add_options (parser); + } + ~text_options_t (void) { + if (file) + g_mapped_file_unref (file); + } + + 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 must be set"); + + }; + + const char *get_line (unsigned int *len); + + const char *text; + const char *text_file; + + private: + mutable GMappedFile *file; + mutable unsigned int text_len; +}; + + +struct output_options_t : option_group_t +{ + output_options_t (option_parser_t *parser) { + output_file = NULL; + output_format = NULL; + + add_options (parser); + } + + void add_options (option_parser_t *parser); + + void post_parse (GError **error G_GNUC_UNUSED) + { + if (output_file && !output_format) { + output_format = strrchr (output_file, '.'); + if (output_format) + output_format++; /* skip the dot */ + } + + if (!output_file) { +#if defined(_MSC_VER) || defined(__MINGW32__) + output_file = "CON"; /* XXX right? */ +#else + output_file = "/dev/stdout"; +#endif + } + } + + virtual void init (const font_options_t *font_opts) = 0; + virtual void consume_line (hb_buffer_t *buffer, + const char *text, + unsigned int text_len) = 0; + virtual void finish (const font_options_t *font_opts) = 0; + + const char *output_file; + const char *output_format; +}; #endif diff --git a/util/view-cairo.cc b/util/view-cairo.cc new file mode 100644 index 000000000..b09a1fe30 --- /dev/null +++ b/util/view-cairo.cc @@ -0,0 +1,440 @@ +/* + * 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" + + +#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 (const char *filename, + double width, + double height) +{ + cairo_surface_t *surface; + + surface = cairo_ps_surface_create (filename, width, height); + cairo_ps_surface_set_eps (surface, TRUE); + + return surface; +} + +# else +# undef HAS_EPS +# endif +#endif + +struct line_t { + cairo_glyph_t *glyphs; + unsigned int num_glyphs; + char *utf8; + unsigned int utf8_len; + cairo_text_cluster_t *clusters; + unsigned int num_clusters; + cairo_text_cluster_flags_t cluster_flags; + + void finish (void) { + if (glyphs) + cairo_glyph_free (glyphs); + if (clusters) + cairo_text_cluster_free (clusters); + if (utf8) + g_free (utf8); + } +}; + +void +view_cairo_t::init (const font_options_t *font_opts) +{ + lines = g_array_new (FALSE, FALSE, sizeof (line_t)); + upem = hb_face_get_upem (hb_font_get_face (font_opts->get_font ())); +} + +void +view_cairo_t::consume_line (hb_buffer_t *buffer, + const char *text, + unsigned int text_len) +{ + line_t l = {0}; + + l.num_glyphs = hb_buffer_get_length (buffer); + hb_glyph_info_t *hb_glyph = hb_buffer_get_glyph_infos (buffer, NULL); + hb_glyph_position_t *hb_position = hb_buffer_get_glyph_positions (buffer, NULL); + l.glyphs = cairo_glyph_allocate (l.num_glyphs + 1); + l.utf8 = g_strndup (text, text_len); + l.utf8_len = text_len; + l.num_clusters = 1; + 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 = ( hb_position->x_offset + x) / double (upem); + l.glyphs[i].y = (-hb_position->y_offset + y) / double (upem); + x += hb_position->x_advance; + y += -hb_position->y_advance; + + hb_position++; + } + l.glyphs[i].index = 0; + l.glyphs[i].x = x; + l.glyphs[i].y = y; + + memset ((void *) l.clusters, 0, l.num_clusters * sizeof (l.clusters[0])); + bool 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; + g_assert (l.num_glyphs); + unsigned int cluster = 0; + 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); + l.clusters[cluster].num_bytes += hb_glyph[i].cluster - hb_glyph[i+1].cluster; + cluster++; + } + l.clusters[cluster].num_glyphs++; + } + l.clusters[cluster].num_bytes += text_len - hb_glyph[0].cluster; + } 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); + l.clusters[cluster].num_bytes += hb_glyph[i].cluster - hb_glyph[i-1].cluster; + cluster++; + } + l.clusters[cluster].num_glyphs++; + } + l.clusters[cluster].num_bytes += text_len - hb_glyph[i - 1].cluster; + } + + g_array_append_val (lines, l); +} + +void +view_cairo_t::finish (const font_options_t *font_opts) +{ + render (font_opts); + + for (unsigned int i = 0; i < lines->len; i++) { + line_t &line = g_array_index (lines, line_t, i); + line.finish (); + } + + g_array_unref (lines); +} + +double +view_cairo_t::line_width (unsigned int i) +{ + line_t &line = g_array_index (lines, line_t, i); + return line.glyphs[line.num_glyphs].x / double (upem); +} + +void +view_cairo_t::get_surface_size (cairo_scaled_font_t *scaled_font, + double *w, double *h) +{ + cairo_font_extents_t font_extents; + + cairo_scaled_font_extents (scaled_font, &font_extents); + + *h = font_extents.ascent + font_extents.descent + ((int) lines->len - 1) * font_extents.height; + *w = 0; + for (unsigned int i = 0; i < lines->len; i++) + *w = MAX (*w, line_width (i)); + + *w += margin.l + margin.r; + *h += margin.t + margin.b; +} + +cairo_scaled_font_t * +view_cairo_t::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 = cairo_ft_font_face_create_for_ft_face (hb_ft_font_get_face (font), 0); + 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, font_opts->font_size); + 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; +} + +struct finalize_closure_t { + void (*callback)(finalize_closure_t *); + cairo_surface_t *surface; + const char *filename; +}; +static cairo_user_data_key_t finalize_closure_key; + +#ifdef CAIRO_HAS_PNG_FUNCTIONS + +static void +finalize_png (finalize_closure_t *closure) +{ + cairo_status_t status; + status = cairo_surface_write_to_png (closure->surface, closure->filename); + if (status != CAIRO_STATUS_SUCCESS) + fail (FALSE, "Failed to write output to `%s': %s", + closure->filename, cairo_status_to_string (status)); +} + +static cairo_surface_t * +_cairo_png_surface_create (const char *filename, + double width, + double height, + cairo_content_t content) +{ + 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 *closure = g_new0 (finalize_closure_t, 1); + closure->callback = finalize_png; + closure->surface = surface; + closure->filename = filename; + + if (cairo_surface_set_user_data (surface, &finalize_closure_key, (void *) closure, (cairo_destroy_func_t) g_free)) + g_free ((void *) closure); + + return surface; +} + +#endif + +void +view_cairo_t::render (const font_options_t *font_opts) +{ + cairo_scaled_font_t *scaled_font = create_scaled_font (font_opts); + double w, h; + get_surface_size (scaled_font, &w, &h); + cairo_t *cr = create_context (w, h); + cairo_set_scaled_font (cr, scaled_font); + cairo_scaled_font_destroy (scaled_font); + + draw (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); +} + +cairo_t * +view_cairo_t::create_context (double w, double h) +{ + cairo_surface_t *(*constructor) (const char *filename, + double width, + double height) = NULL; + cairo_surface_t *(*constructor2) (const char *filename, + double width, + double height, + cairo_content_t content) = NULL; + + const char *extension = output_format; + if (!extension) + extension = "png"; + if (0) + ; + #ifdef CAIRO_HAS_PNG_FUNCTIONS + else if (0 == strcasecmp (extension, "png")) + constructor2 = _cairo_png_surface_create; + #endif + #ifdef CAIRO_HAS_SVG_SURFACE + else if (0 == strcasecmp (extension, "svg")) + constructor = cairo_svg_surface_create; + #endif + #ifdef CAIRO_HAS_PDF_SURFACE + else if (0 == strcasecmp (extension, "pdf")) + constructor = cairo_pdf_surface_create; + #endif + #ifdef CAIRO_HAS_PS_SURFACE + else if (0 == strcasecmp (extension, "ps")) + constructor = cairo_ps_surface_create; + #ifdef HAS_EPS + else if (0 == strcasecmp (extension, "eps")) + constructor = _cairo_eps_surface_create; + #endif + #endif + + + unsigned int fr, fg, fb, fa, br, bg, bb, ba; + br = bg = bb = ba = 255; + sscanf (back + (*back=='#'), "%2x%2x%2x%2x", &br, &bg, &bb, &ba); + fr = fg = fb = 0; fa = 255; + sscanf (fore + (*fore=='#'), "%2x%2x%2x%2x", &fr, &fg, &fb, &fa); + + cairo_content_t content; + if (!annotate && ba == 255 && br == bg && bg == bb && fr == fg && fg == fb) + content = CAIRO_CONTENT_ALPHA; + else if (ba == 255) + content = CAIRO_CONTENT_COLOR; + else + content = CAIRO_CONTENT_COLOR_ALPHA; + + cairo_surface_t *surface; + if (constructor) + surface = constructor (output_file, w, h); + else if (constructor2) + surface = constructor2 (output_file, w, h, content); + else + fail (FALSE, "Unknown output format `%s'", extension); + + 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 +view_cairo_t::draw (cairo_t *cr) +{ + cairo_save (cr); + + cairo_font_extents_t font_extents; + cairo_font_extents (cr, &font_extents); + cairo_translate (cr, margin.l, margin.t); + for (unsigned int i = 0; i < lines->len; i++) + { + line_t &l = g_array_index (lines, line_t, i); + + if (i) + cairo_translate (cr, 0, line_space); + + cairo_translate (cr, 0, font_extents.ascent); + + 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 (cairo_surface_get_type (cairo_get_target (cr)) == CAIRO_SURFACE_TYPE_IMAGE) { + /* cairo_show_glyphs() doesn't support subpixel positioining */ + cairo_glyph_path (cr, l.glyphs, l.num_glyphs); + cairo_fill (cr); + } else + cairo_show_text_glyphs (cr, + l.utf8, l.utf8_len, + l.glyphs, l.num_glyphs, + l.clusters, l.num_clusters, + l.cluster_flags); + + cairo_translate (cr, 0, font_extents.height - font_extents.ascent); + } + + cairo_restore (cr); +} diff --git a/util/view-cairo.hh b/util/view-cairo.hh new file mode 100644 index 000000000..4b09d6fbd --- /dev/null +++ b/util/view-cairo.hh @@ -0,0 +1,63 @@ +/* + * 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 "options.hh" + +#include +#include + +#ifndef VIEW_CAIRO_HH +#define VIEW_CAIRO_HH + +struct view_cairo_t : output_options_t, view_options_t { + view_cairo_t (option_parser_t *parser) + : output_options_t (parser), + view_options_t (parser) {} + ~view_cairo_t (void) { + if (debug) + cairo_debug_reset_static_data (); + } + + void init (const font_options_t *font_opts); + void consume_line (hb_buffer_t *buffer, + const char *text, + unsigned int text_len); + void finish (const font_options_t *font_opts); + + protected: + + void render (const font_options_t *font_opts); + cairo_scaled_font_t *create_scaled_font (const font_options_t *font_opts); + void get_surface_size (cairo_scaled_font_t *scaled_font, double *w, double *h); + cairo_t *create_context (double w, double h); + void draw (cairo_t *cr); + double line_width (unsigned int i); + + GArray *lines; + unsigned int upem; +}; + +#endif