view-cairo.cc revision b9b10ad78b1f977494a3a42b58f8040fe16505a3
1/* 2 * Copyright © 2011 Google, Inc. 3 * 4 * This is part of HarfBuzz, a text shaping library. 5 * 6 * Permission is hereby granted, without written agreement and without 7 * license or royalty fees, to use, copy, modify, and distribute this 8 * software and its documentation for any purpose, provided that the 9 * above copyright notice and the following two paragraphs appear in 10 * all copies of this software. 11 * 12 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR 13 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 14 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN 15 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 16 * DAMAGE. 17 * 18 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, 19 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 20 * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS 21 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO 22 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 23 * 24 * Google Author(s): Behdad Esfahbod 25 */ 26 27#include "view-cairo.hh" 28 29 30#ifdef CAIRO_HAS_SVG_SURFACE 31# include <cairo-svg.h> 32#endif 33#ifdef CAIRO_HAS_PDF_SURFACE 34# include <cairo-pdf.h> 35#endif 36#ifdef CAIRO_HAS_PS_SURFACE 37# include <cairo-ps.h> 38# if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,6,0) 39# define HAS_EPS 1 40 41static cairo_surface_t * 42_cairo_eps_surface_create (const char *filename, 43 double width, 44 double height) 45{ 46 cairo_surface_t *surface; 47 48 surface = cairo_ps_surface_create (filename, width, height); 49 cairo_ps_surface_set_eps (surface, TRUE); 50 51 return surface; 52} 53 54# else 55# undef HAS_EPS 56# endif 57#endif 58 59struct line_t { 60 cairo_glyph_t *glyphs; 61 unsigned int num_glyphs; 62 char *utf8; 63 unsigned int utf8_len; 64 cairo_text_cluster_t *clusters; 65 unsigned int num_clusters; 66 cairo_text_cluster_flags_t cluster_flags; 67 68 void finish (void) { 69 if (glyphs) 70 cairo_glyph_free (glyphs); 71 if (clusters) 72 cairo_text_cluster_free (clusters); 73 if (utf8) 74 g_free (utf8); 75 } 76}; 77 78void 79view_cairo_t::init (const font_options_t *font_opts) 80{ 81 lines = g_array_new (FALSE, FALSE, sizeof (line_t)); 82 upem = hb_face_get_upem (hb_font_get_face (font_opts->get_font ())); 83} 84 85void 86view_cairo_t::consume_line (hb_buffer_t *buffer, 87 const char *text, 88 unsigned int text_len) 89{ 90 line_t l = {0}; 91 92 l.num_glyphs = hb_buffer_get_length (buffer); 93 hb_glyph_info_t *hb_glyph = hb_buffer_get_glyph_infos (buffer, NULL); 94 hb_glyph_position_t *hb_position = hb_buffer_get_glyph_positions (buffer, NULL); 95 l.glyphs = cairo_glyph_allocate (l.num_glyphs + 1); 96 l.utf8 = g_strndup (text, text_len); 97 l.utf8_len = text_len; 98 l.num_clusters = 1; 99 for (unsigned int i = 1; i < l.num_glyphs; i++) 100 if (hb_glyph[i].cluster != hb_glyph[i-1].cluster) 101 l.num_clusters++; 102 l.clusters = cairo_text_cluster_allocate (l.num_clusters); 103 104 if ((l.num_glyphs && !l.glyphs) || 105 (l.utf8_len && !l.utf8) || 106 (l.num_clusters && !l.clusters)) 107 { 108 l.finish (); 109 return; 110 } 111 112 hb_position_t x = 0, y = 0; 113 int i; 114 for (i = 0; i < (int) l.num_glyphs; i++) 115 { 116 l.glyphs[i].index = hb_glyph[i].codepoint; 117 l.glyphs[i].x = ( hb_position->x_offset + x) / double (upem); 118 l.glyphs[i].y = (-hb_position->y_offset + y) / double (upem); 119 x += hb_position->x_advance; 120 y += -hb_position->y_advance; 121 122 hb_position++; 123 } 124 l.glyphs[i].index = 0; 125 l.glyphs[i].x = x; 126 l.glyphs[i].y = y; 127 128 memset ((void *) l.clusters, 0, l.num_clusters * sizeof (l.clusters[0])); 129 bool backward = HB_DIRECTION_IS_BACKWARD (hb_buffer_get_direction (buffer)); 130 l.cluster_flags = backward ? CAIRO_TEXT_CLUSTER_FLAG_BACKWARD : (cairo_text_cluster_flags_t) 0; 131 g_assert (l.num_glyphs); 132 unsigned int cluster = 0; 133 l.clusters[cluster].num_glyphs++; 134 if (backward) { 135 for (i = l.num_glyphs - 2; i >= 0; i--) { 136 if (hb_glyph[i].cluster != hb_glyph[i+1].cluster) { 137 g_assert (hb_glyph[i].cluster > hb_glyph[i+1].cluster); 138 l.clusters[cluster].num_bytes += hb_glyph[i].cluster - hb_glyph[i+1].cluster; 139 cluster++; 140 } 141 l.clusters[cluster].num_glyphs++; 142 } 143 l.clusters[cluster].num_bytes += text_len - hb_glyph[0].cluster; 144 } else { 145 for (i = 1; i < (int) l.num_glyphs; i++) { 146 if (hb_glyph[i].cluster != hb_glyph[i-1].cluster) { 147 g_assert (hb_glyph[i].cluster > hb_glyph[i-1].cluster); 148 l.clusters[cluster].num_bytes += hb_glyph[i].cluster - hb_glyph[i-1].cluster; 149 cluster++; 150 } 151 l.clusters[cluster].num_glyphs++; 152 } 153 l.clusters[cluster].num_bytes += text_len - hb_glyph[i - 1].cluster; 154 } 155 156 g_array_append_val (lines, l); 157} 158 159void 160view_cairo_t::finish (const font_options_t *font_opts) 161{ 162 render (font_opts); 163 164 for (unsigned int i = 0; i < lines->len; i++) { 165 line_t &line = g_array_index (lines, line_t, i); 166 line.finish (); 167 } 168 169 g_array_unref (lines); 170} 171 172double 173view_cairo_t::line_width (unsigned int i) 174{ 175 line_t &line = g_array_index (lines, line_t, i); 176 return line.glyphs[line.num_glyphs].x / double (upem); 177} 178 179void 180view_cairo_t::get_surface_size (cairo_scaled_font_t *scaled_font, 181 double *w, double *h) 182{ 183 cairo_font_extents_t font_extents; 184 185 cairo_scaled_font_extents (scaled_font, &font_extents); 186 187 *h = font_extents.ascent + font_extents.descent + ((int) lines->len - 1) * font_extents.height; 188 *w = 0; 189 for (unsigned int i = 0; i < lines->len; i++) 190 *w = MAX (*w, line_width (i)); 191 192 *w += margin.l + margin.r; 193 *h += margin.t + margin.b; 194} 195 196cairo_scaled_font_t * 197view_cairo_t::create_scaled_font (const font_options_t *font_opts) 198{ 199 hb_font_t *font = hb_font_reference (font_opts->get_font ()); 200 201 cairo_font_face_t *cairo_face = cairo_ft_font_face_create_for_ft_face (hb_ft_font_get_face (font), 0); 202 cairo_matrix_t ctm, font_matrix; 203 cairo_font_options_t *font_options; 204 205 cairo_matrix_init_identity (&ctm); 206 cairo_matrix_init_scale (&font_matrix, font_opts->font_size, font_opts->font_size); 207 font_options = cairo_font_options_create (); 208 cairo_font_options_set_hint_style (font_options, CAIRO_HINT_STYLE_NONE); 209 cairo_font_options_set_hint_metrics (font_options, CAIRO_HINT_METRICS_OFF); 210 211 cairo_scaled_font_t *scaled_font = cairo_scaled_font_create (cairo_face, &font_matrix, &ctm, font_options); 212 213 cairo_font_options_destroy (font_options); 214 cairo_font_face_destroy (cairo_face); 215 216 static cairo_user_data_key_t key; 217 if (cairo_scaled_font_set_user_data (scaled_font, &key, (void *) font, (cairo_destroy_func_t) hb_font_destroy)) 218 hb_font_destroy (font); 219 220 return scaled_font; 221} 222 223struct finalize_closure_t { 224 void (*callback)(finalize_closure_t *); 225 cairo_surface_t *surface; 226 const char *filename; 227}; 228static cairo_user_data_key_t finalize_closure_key; 229 230#ifdef CAIRO_HAS_PNG_FUNCTIONS 231 232static void 233finalize_png (finalize_closure_t *closure) 234{ 235 cairo_status_t status; 236 status = cairo_surface_write_to_png (closure->surface, closure->filename); 237 if (status != CAIRO_STATUS_SUCCESS) 238 fail (FALSE, "Failed to write output to `%s': %s", 239 closure->filename, cairo_status_to_string (status)); 240} 241 242static cairo_surface_t * 243_cairo_png_surface_create (const char *filename, 244 double width, 245 double height, 246 cairo_content_t content) 247{ 248 cairo_surface_t *surface; 249 int w = ceil (width); 250 int h = ceil (height); 251 252 switch (content) { 253 case CAIRO_CONTENT_ALPHA: 254 surface = cairo_image_surface_create (CAIRO_FORMAT_A8, w, h); 255 break; 256 default: 257 case CAIRO_CONTENT_COLOR: 258 surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h); 259 break; 260 case CAIRO_CONTENT_COLOR_ALPHA: 261 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h); 262 break; 263 } 264 cairo_status_t status = cairo_surface_status (surface); 265 if (status != CAIRO_STATUS_SUCCESS) 266 fail (FALSE, "Failed to create cairo surface: %s", 267 cairo_status_to_string (status)); 268 269 finalize_closure_t *closure = g_new0 (finalize_closure_t, 1); 270 closure->callback = finalize_png; 271 closure->surface = surface; 272 closure->filename = filename; 273 274 if (cairo_surface_set_user_data (surface, &finalize_closure_key, (void *) closure, (cairo_destroy_func_t) g_free)) 275 g_free ((void *) closure); 276 277 return surface; 278} 279 280#endif 281 282void 283view_cairo_t::render (const font_options_t *font_opts) 284{ 285 cairo_scaled_font_t *scaled_font = create_scaled_font (font_opts); 286 double w, h; 287 get_surface_size (scaled_font, &w, &h); 288 cairo_t *cr = create_context (w, h); 289 cairo_set_scaled_font (cr, scaled_font); 290 cairo_scaled_font_destroy (scaled_font); 291 292 draw (cr); 293 294 finalize_closure_t *closure = (finalize_closure_t *) 295 cairo_surface_get_user_data (cairo_get_target (cr), 296 &finalize_closure_key); 297 if (closure) 298 closure->callback (closure); 299 300 cairo_status_t status = cairo_status (cr); 301 if (status != CAIRO_STATUS_SUCCESS) 302 fail (FALSE, "Failed: %s", 303 cairo_status_to_string (status)); 304 cairo_destroy (cr); 305} 306 307cairo_t * 308view_cairo_t::create_context (double w, double h) 309{ 310 cairo_surface_t *(*constructor) (const char *filename, 311 double width, 312 double height) = NULL; 313 cairo_surface_t *(*constructor2) (const char *filename, 314 double width, 315 double height, 316 cairo_content_t content) = NULL; 317 318 const char *extension = output_format; 319 if (!extension) 320 extension = "png"; 321 if (0) 322 ; 323 #ifdef CAIRO_HAS_PNG_FUNCTIONS 324 else if (0 == strcasecmp (extension, "png")) 325 constructor2 = _cairo_png_surface_create; 326 #endif 327 #ifdef CAIRO_HAS_SVG_SURFACE 328 else if (0 == strcasecmp (extension, "svg")) 329 constructor = cairo_svg_surface_create; 330 #endif 331 #ifdef CAIRO_HAS_PDF_SURFACE 332 else if (0 == strcasecmp (extension, "pdf")) 333 constructor = cairo_pdf_surface_create; 334 #endif 335 #ifdef CAIRO_HAS_PS_SURFACE 336 else if (0 == strcasecmp (extension, "ps")) 337 constructor = cairo_ps_surface_create; 338 #ifdef HAS_EPS 339 else if (0 == strcasecmp (extension, "eps")) 340 constructor = _cairo_eps_surface_create; 341 #endif 342 #endif 343 344 345 unsigned int fr, fg, fb, fa, br, bg, bb, ba; 346 br = bg = bb = ba = 255; 347 sscanf (back + (*back=='#'), "%2x%2x%2x%2x", &br, &bg, &bb, &ba); 348 fr = fg = fb = 0; fa = 255; 349 sscanf (fore + (*fore=='#'), "%2x%2x%2x%2x", &fr, &fg, &fb, &fa); 350 351 cairo_content_t content; 352 if (!annotate && ba == 255 && br == bg && bg == bb && fr == fg && fg == fb) 353 content = CAIRO_CONTENT_ALPHA; 354 else if (ba == 255) 355 content = CAIRO_CONTENT_COLOR; 356 else 357 content = CAIRO_CONTENT_COLOR_ALPHA; 358 359 cairo_surface_t *surface; 360 if (constructor) 361 surface = constructor (output_file, w, h); 362 else if (constructor2) 363 surface = constructor2 (output_file, w, h, content); 364 else 365 fail (FALSE, "Unknown output format `%s'", extension); 366 367 cairo_t *cr = cairo_create (surface); 368 content = cairo_surface_get_content (surface); 369 370 switch (content) { 371 case CAIRO_CONTENT_ALPHA: 372 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); 373 cairo_set_source_rgba (cr, 1., 1., 1., br / 255.); 374 cairo_paint (cr); 375 cairo_set_source_rgba (cr, 1., 1., 1., (fr / 255.) * (fa / 255.) + (br / 255) * (1 - (fa / 255.))); 376 break; 377 default: 378 case CAIRO_CONTENT_COLOR: 379 case CAIRO_CONTENT_COLOR_ALPHA: 380 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); 381 cairo_set_source_rgba (cr, br / 255., bg / 255., bb / 255., ba / 255.); 382 cairo_paint (cr); 383 cairo_set_operator (cr, CAIRO_OPERATOR_OVER); 384 cairo_set_source_rgba (cr, fr / 255., fg / 255., fb / 255., fa / 255.); 385 break; 386 } 387 388 cairo_surface_destroy (surface); 389 return cr; 390} 391 392void 393view_cairo_t::draw (cairo_t *cr) 394{ 395 cairo_save (cr); 396 397 cairo_font_extents_t font_extents; 398 cairo_font_extents (cr, &font_extents); 399 cairo_translate (cr, margin.l, margin.t); 400 for (unsigned int i = 0; i < lines->len; i++) 401 { 402 line_t &l = g_array_index (lines, line_t, i); 403 404 if (i) 405 cairo_translate (cr, 0, line_space); 406 407 cairo_translate (cr, 0, font_extents.ascent); 408 409 if (annotate) { 410 cairo_save (cr); 411 412 /* Draw actual glyph origins */ 413 cairo_set_source_rgba (cr, 1., 0., 0., .5); 414 cairo_set_line_width (cr, 5); 415 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); 416 for (unsigned i = 0; i < l.num_glyphs; i++) { 417 cairo_move_to (cr, l.glyphs[i].x, l.glyphs[i].y); 418 cairo_rel_line_to (cr, 0, 0); 419 } 420 cairo_stroke (cr); 421 422 cairo_restore (cr); 423 } 424 425 if (cairo_surface_get_type (cairo_get_target (cr)) == CAIRO_SURFACE_TYPE_IMAGE) { 426 /* cairo_show_glyphs() doesn't support subpixel positioining */ 427 cairo_glyph_path (cr, l.glyphs, l.num_glyphs); 428 cairo_fill (cr); 429 } else 430 cairo_show_text_glyphs (cr, 431 l.utf8, l.utf8_len, 432 l.glyphs, l.num_glyphs, 433 l.clusters, l.num_clusters, 434 l.cluster_flags); 435 436 cairo_translate (cr, 0, font_extents.height - font_extents.ascent); 437 } 438 439 cairo_restore (cr); 440} 441