998e8dda93
Use the fallback implementation for lround() only on pre-2013 Visual Studio, and ensure we are clear about the types of the parameters for lround() and scalbnf(), since Visual Studio can be quite picky on ambiguous parameter types. Also, use g_ascii_strcasecmp() rather than strcasecmp() as we are already using GLib for this code and we are assured that g_ascii_strcasemp() is available. For scalbnf() on pre-2013 Visaul Studio, a fallback implementation is needed, but use another forced-included header for those compilers, which will be added later. Also use (char)27 on Visual Studio builds as '\e' is not a recognized escape sequence, which will do the same thing.
532 lines
15 KiB
C++
532 lines
15 KiB
C++
/*
|
|
* 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 <cairo-ft.h>
|
|
#include <hb-ft.h>
|
|
|
|
#include "helper-cairo-ansi.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_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;
|
|
|
|
static inline
|
|
void free_ft_library (void)
|
|
{
|
|
FT_Done_FreeType (ft_library);
|
|
}
|
|
|
|
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;
|
|
FT_Face ft_face = 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
|
|
}
|
|
FT_New_Face (ft_library,
|
|
font_opts->font_file,
|
|
font_opts->face_index,
|
|
&ft_face);
|
|
}
|
|
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
|
|
cairo_face = cairo_ft_font_face_create_for_ft_face (ft_face, 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_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;
|
|
}
|
|
|
|
|
|
struct finalize_closure_t {
|
|
void (*callback)(finalize_closure_t *);
|
|
cairo_surface_t *surface;
|
|
cairo_write_func_t write_func;
|
|
void *closure;
|
|
};
|
|
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)
|
|
{
|
|
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 void
|
|
finalize_png (finalize_closure_t *closure)
|
|
{
|
|
cairo_status_t status;
|
|
status = cairo_surface_write_to_png_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_png_surface_create_for_stream (cairo_write_func_t write_func,
|
|
void *closure,
|
|
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 *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;
|
|
|
|
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
|
|
NULL
|
|
};
|
|
|
|
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) = NULL;
|
|
cairo_surface_t *(*constructor2) (cairo_write_func_t write_func,
|
|
void *closure,
|
|
double width,
|
|
double height,
|
|
cairo_content_t content) = NULL;
|
|
|
|
const char *extension = out_opts->output_format;
|
|
if (!extension) {
|
|
#if HAVE_ISATTY
|
|
if (isatty (fileno (out_opts->get_file_handle ())))
|
|
extension = "ansi";
|
|
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);
|
|
else
|
|
fail (false, "Unknown output format `%s'; supported formats are: %s%s",
|
|
extension,
|
|
g_strjoinv ("/", const_cast<char**> (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, NULL);
|
|
hb_glyph_position_t *hb_position = hb_buffer_get_glyph_positions (buffer, NULL);
|
|
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;
|
|
}
|
|
}
|
|
}
|