[util] Refactor hb-view completely

Now we can use the same code to do other utils...
This commit is contained in:
Behdad Esfahbod 2011-09-13 13:30:39 -04:00
parent bc4b07b05e
commit b9b10ad78b
9 changed files with 938 additions and 344 deletions

View File

@ -49,6 +49,8 @@ m4_define([hb_libtool_current],
HB_LIBTOOL_VERSION_INFO=hb_libtool_current:hb_libtool_revision:hb_libtool_age HB_LIBTOOL_VERSION_INFO=hb_libtool_current:hb_libtool_revision:hb_libtool_age
AC_SUBST(HB_LIBTOOL_VERSION_INFO) AC_SUBST(HB_LIBTOOL_VERSION_INFO)
GTK_DOC_CHECK([1.15],[--flavour no-tmpl])
# Functions and headers # Functions and headers
AC_CHECK_FUNCS(mprotect sysconf getpagesize mmap) AC_CHECK_FUNCS(mprotect sysconf getpagesize mmap)
AC_CHECK_HEADERS(unistd.h sys/mman.h) AC_CHECK_HEADERS(unistd.h sys/mman.h)

View File

@ -18,6 +18,8 @@ hb_view_SOURCES = \
common.hh \ common.hh \
options.cc \ options.cc \
options.hh \ options.hh \
view-cairo.cc \
view-cairo.hh \
$(NULL) $(NULL)
hb_view_CPPFLAGS = \ hb_view_CPPFLAGS = \
-I$(top_srcdir)/src/ \ -I$(top_srcdir)/src/ \

View File

@ -27,14 +27,17 @@
#include "common.hh" #include "common.hh"
void void
fail (const char *format, ...) fail (hb_bool_t suggest_help, const char *format, ...)
{ {
const char *msg; const char *msg;
va_list vap; va_list vap;
va_start (vap, format); va_start (vap, format);
msg = g_strdup_vprintf (format, vap); 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); exit (1);
} }

View File

@ -45,7 +45,7 @@
#include <glib/gprintf.h> #include <glib/gprintf.h>
void fail (const char *format, ...) G_GNUC_NORETURN; void fail (hb_bool_t suggest_help, const char *format, ...) G_GNUC_NORETURN;
#endif #endif

View File

@ -26,234 +26,50 @@
*/ */
#include "common.hh" #include "common.hh"
#include <cairo-ft.h>
#include <hb-ft.h>
#include "options.hh" #include "options.hh"
#include "view-cairo.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);
}
int int
main (int argc, char **argv) main (int argc, char **argv)
{ {
static FT_Library ft_library;
static FT_Face ft_face;
cairo_status_t status;
setlocale (LC_ALL, ""); setlocale (LC_ALL, "");
parse_options (argc, argv); option_parser_t options ("[FONT-FILE] [TEXT]");
FT_Init_FreeType (&ft_library); shape_options_t shaper (&options);
if (FT_New_Face (ft_library, font_opts->font_file, font_opts->face_index, &ft_face)) { font_options_t font_opts (&options);
fprintf (stderr, "Failed to open font file `%s'\n", font_opts->font_file); text_options_t input (&options);
exit (1);
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 (); output.finish (&font_opts);
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);
}
return 0; return 0;
} }

View File

@ -26,14 +26,107 @@
#include "options.hh" #include "options.hh"
#if HAVE_FREETYPE
#include <hb-ft.h>
#endif
view_options_t view_opts[1];
shape_options_t shape_opts[1];
font_options_t font_opts[1];
const char *text; bool debug = FALSE;
const char *out_file = "/dev/stdout";
hb_bool_t 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<option_group_t *>(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<gpointer>(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 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 void
view_options_t::add_options (GOptionContext *context) view_options_t::add_options (option_parser_t *parser)
{ {
GOptionEntry entries[] = GOptionEntry entries[] =
{ {
@ -290,7 +337,7 @@ 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"}, {"margin", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_margin, "Margin around output (default: "G_STRINGIFY(DEFAULT_MARGIN)")","one to four numbers"},
{NULL} {NULL}
}; };
option_context_add_entries (context, entries, parser->add_group (entries,
"view", "view",
"View options:", "View options:",
"Options controlling the output rendering", "Options controlling the output rendering",
@ -298,7 +345,7 @@ view_options_t::add_options (GOptionContext *context)
} }
void void
shape_options_t::add_options (GOptionContext *context) shape_options_t::add_options (option_parser_t *parser)
{ {
GOptionEntry entries[] = GOptionEntry entries[] =
{ {
@ -309,7 +356,7 @@ shape_options_t::add_options (GOptionContext *context)
{"features", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_features, "Font features to apply to text", "TODO"}, {"features", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_features, "Font features to apply to text", "TODO"},
{NULL} {NULL}
}; };
option_context_add_entries (context, entries, parser->add_group (entries,
"shape", "shape",
"Shape options:", "Shape options:",
"Options controlling the shaping process", "Options controlling the shaping process",
@ -317,57 +364,141 @@ shape_options_t::add_options (GOptionContext *context)
} }
void void
font_options_t::add_options (GOptionContext *context) font_options_t::add_options (option_parser_t *parser)
{ {
GOptionEntry entries[] = GOptionEntry entries[] =
{ {
{"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"}, {"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"}, {"font-size", 0, 0, G_OPTION_ARG_DOUBLE, &this->font_size, "Font size (default: "G_STRINGIFY(DEFAULT_FONT_SIZE)")","size"},
{NULL} {NULL}
}; };
option_context_add_entries (context, entries, parser->add_group (entries,
"font", "font",
"Font options:", "Font options:",
"Options controlling the font", "Options controlling the font",
NULL); this);
} }
void void
parse_options (int argc, char *argv[]) text_options_t::add_options (option_parser_t *parser)
{ {
GOptionEntry entries[] = GOptionEntry entries[] =
{ {
{"version", 0, G_OPTION_FLAG_NO_ARG, {"text", 0, 0, G_OPTION_ARG_STRING, &this->text, "Set input text", "string"},
G_OPTION_ARG_CALLBACK, (gpointer) &show_version, "Show version numbers", NULL}, {"text-file", 0, 0, G_OPTION_ARG_STRING, &this->text_file, "Set input text file-name", "filename"},
{"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"},
{NULL} {NULL}
}; };
GError *parse_error = NULL; parser->add_group (entries,
GOptionContext *context; "text",
"Text options:",
context = g_option_context_new ("- FONT-FILE TEXT"); "Options controlling the input text",
this);
g_option_context_add_main_entries (context, entries, NULL); }
view_opts->add_options (context);
shape_opts->add_options (context); void
font_opts->add_options (context); output_options_t::add_options (option_parser_t *parser)
{
if (!g_option_context_parse (context, &argc, &argv, &parse_error)) GOptionEntry entries[] =
{ {
if (parse_error != NULL) {"output", 0, 0, G_OPTION_ARG_STRING, &this->output_file, "Set output file-name (default: stdout)","filename"},
fail ("%s", parse_error->message); {"format", 0, 0, G_OPTION_ARG_STRING, &this->output_format, "Set output format", "format"},
else {NULL}
fail ("Option parse error"); };
exit(1); parser->add_group (entries,
} "output",
g_option_context_free(context); "Output options:",
"Options controlling the output",
if (argc != 3) { this);
g_printerr ("Usage: %s [OPTION...] FONT-FILE TEXT\n", g_get_prgname ()); }
exit (1);
}
font_opts->font_file = argv[1]; hb_font_t *
text = argv[2]; 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;
} }

View File

@ -30,42 +30,94 @@
#define OPTIONS_HH #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_MARGIN 18
#define DEFAULT_FORE "#000000" #define DEFAULT_FORE "#000000"
#define DEFAULT_BACK "#FFFFFF" #define DEFAULT_BACK "#FFFFFF"
extern struct view_options_t struct view_options_t : option_group_t
{ {
view_options_t (void) { view_options_t (option_parser_t *parser) {
memset (this, 0, sizeof (*this)); annotate = false;
fore = DEFAULT_FORE; fore = DEFAULT_FORE;
back = DEFAULT_BACK; back = DEFAULT_BACK;
line_space = 0;
margin.t = margin.r = margin.b = margin.l = DEFAULT_MARGIN; 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 *fore;
const char *back; const char *back;
double line_space; double line_space;
struct margin_t { struct margin_t {
double t, r, b, l; double t, r, b, l;
} margin; } margin;
} view_opts[1]; };
extern struct shape_options_t struct shape_options_t : option_group_t
{ {
shape_options_t (void) { shape_options_t (option_parser_t *parser) {
memset (this, 0, sizeof (*this)); direction = language = script = NULL;
features = NULL;
num_features = 0;
shapers = NULL;
add_options (parser);
} }
~shape_options_t (void) { ~shape_options_t (void) {
free (features); free (features);
g_free (shapers); g_free (shapers);
} }
void add_options (GOptionContext *context); void add_options (option_parser_t *parser);
void setup_buffer (hb_buffer_t *buffer) { void setup_buffer (hb_buffer_t *buffer) {
hb_buffer_set_direction (buffer, hb_direction_from_string (direction, -1)); 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)); 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); setup_buffer (buffer);
return hb_shape_full (font, buffer, features, num_features, NULL, shapers); return hb_shape_full (font, buffer, features, num_features, NULL, shapers);
} }
@ -84,31 +139,113 @@ extern struct shape_options_t
hb_feature_t *features; hb_feature_t *features;
unsigned int num_features; unsigned int num_features;
char **shapers; char **shapers;
} shape_opts[1]; };
#define DEFAULT_FONT_SIZE 36 #define DEFAULT_FONT_SIZE 36
extern struct font_options_t struct font_options_t : option_group_t
{ {
font_options_t (void) { font_options_t (option_parser_t *parser) {
memset (this, 0, sizeof (*this)); font_file = NULL;
face_index = 0;
font_size = DEFAULT_FONT_SIZE; 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; const char *font_file;
int face_index; int face_index;
double font_size; double font_size;
} font_opts[1];
private:
mutable hb_font_t *font;
};
extern const char *text; struct text_options_t : option_group_t
extern const char *out_file; {
extern hb_bool_t debug; 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 #endif

440
util/view-cairo.cc Normal file
View File

@ -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 <cairo-svg.h>
#endif
#ifdef CAIRO_HAS_PDF_SURFACE
# include <cairo-pdf.h>
#endif
#ifdef CAIRO_HAS_PS_SURFACE
# include <cairo-ps.h>
# 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);
}

63
util/view-cairo.hh Normal file
View File

@ -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 <cairo-ft.h>
#include <hb-ft.h>
#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