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 "helper-cairo.hh"
28 
29 #include <cairo-ft.h>
30 #include <hb-ft.h>
31 
32 #include "helper-cairo-ansi.hh"
33 #ifdef CAIRO_HAS_SVG_SURFACE
34 #  include <cairo-svg.h>
35 #endif
36 #ifdef CAIRO_HAS_PDF_SURFACE
37 #  include <cairo-pdf.h>
38 #endif
39 #ifdef CAIRO_HAS_PS_SURFACE
40 #  include <cairo-ps.h>
41 #  if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,6,0)
42 #    define HAS_EPS 1
43 
44 static cairo_surface_t *
_cairo_eps_surface_create_for_stream(cairo_write_func_t write_func,void * closure,double width,double height)45 _cairo_eps_surface_create_for_stream (cairo_write_func_t  write_func,
46 				      void               *closure,
47 				      double              width,
48 				      double              height)
49 {
50   cairo_surface_t *surface;
51 
52   surface = cairo_ps_surface_create_for_stream (write_func, closure, width, height);
53   cairo_ps_surface_set_eps (surface, true);
54 
55   return surface;
56 }
57 
58 #  else
59 #    undef HAS_EPS
60 #  endif
61 #endif
62 
63 
64 static FT_Library ft_library;
65 
66 static inline
free_ft_library(void)67 void free_ft_library (void)
68 {
69   FT_Done_FreeType (ft_library);
70 }
71 
72 cairo_scaled_font_t *
helper_cairo_create_scaled_font(const font_options_t * font_opts)73 helper_cairo_create_scaled_font (const font_options_t *font_opts)
74 {
75   hb_font_t *font = hb_font_reference (font_opts->get_font ());
76 
77   cairo_font_face_t *cairo_face;
78   FT_Face ft_face = hb_ft_font_get_face (font);
79   if (!ft_face)
80   {
81     if (!ft_library)
82     {
83       FT_Init_FreeType (&ft_library);
84 #ifdef HAVE_ATEXIT
85       atexit (free_ft_library);
86 #endif
87     }
88     FT_New_Face (ft_library,
89 		 font_opts->font_file,
90 		 font_opts->face_index,
91 		 &ft_face);
92   }
93   if (!ft_face)
94   {
95     /* This allows us to get some boxes at least... */
96     cairo_face = cairo_toy_font_face_create ("@cairo:sans",
97 					     CAIRO_FONT_SLANT_NORMAL,
98 					     CAIRO_FONT_WEIGHT_NORMAL);
99   }
100   else
101     cairo_face = cairo_ft_font_face_create_for_ft_face (ft_face, 0);
102   cairo_matrix_t ctm, font_matrix;
103   cairo_font_options_t *font_options;
104 
105   cairo_matrix_init_identity (&ctm);
106   cairo_matrix_init_scale (&font_matrix,
107 			   font_opts->font_size_x,
108 			   font_opts->font_size_y);
109   font_options = cairo_font_options_create ();
110   cairo_font_options_set_hint_style (font_options, CAIRO_HINT_STYLE_NONE);
111   cairo_font_options_set_hint_metrics (font_options, CAIRO_HINT_METRICS_OFF);
112 
113   cairo_scaled_font_t *scaled_font = cairo_scaled_font_create (cairo_face,
114 							       &font_matrix,
115 							       &ctm,
116 							       font_options);
117 
118   cairo_font_options_destroy (font_options);
119   cairo_font_face_destroy (cairo_face);
120 
121   static cairo_user_data_key_t key;
122   if (cairo_scaled_font_set_user_data (scaled_font,
123 				       &key,
124 				       (void *) font,
125 				       (cairo_destroy_func_t) hb_font_destroy))
126     hb_font_destroy (font);
127 
128   return scaled_font;
129 }
130 
131 
132 struct finalize_closure_t {
133   void (*callback)(finalize_closure_t *);
134   cairo_surface_t *surface;
135   cairo_write_func_t write_func;
136   void *closure;
137 };
138 static cairo_user_data_key_t finalize_closure_key;
139 
140 
141 static void
finalize_ansi(finalize_closure_t * closure)142 finalize_ansi (finalize_closure_t *closure)
143 {
144   cairo_status_t status;
145   status = helper_cairo_surface_write_to_ansi_stream (closure->surface,
146 						      closure->write_func,
147 						      closure->closure);
148   if (status != CAIRO_STATUS_SUCCESS)
149     fail (false, "Failed to write output: %s",
150 	  cairo_status_to_string (status));
151 }
152 
153 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)154 _cairo_ansi_surface_create_for_stream (cairo_write_func_t write_func,
155 				       void *closure,
156 				       double width,
157 				       double height,
158 				       cairo_content_t content)
159 {
160   cairo_surface_t *surface;
161   int w = ceil (width);
162   int h = ceil (height);
163 
164   switch (content) {
165     case CAIRO_CONTENT_ALPHA:
166       surface = cairo_image_surface_create (CAIRO_FORMAT_A8, w, h);
167       break;
168     default:
169     case CAIRO_CONTENT_COLOR:
170       surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h);
171       break;
172     case CAIRO_CONTENT_COLOR_ALPHA:
173       surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
174       break;
175   }
176   cairo_status_t status = cairo_surface_status (surface);
177   if (status != CAIRO_STATUS_SUCCESS)
178     fail (false, "Failed to create cairo surface: %s",
179 	  cairo_status_to_string (status));
180 
181   finalize_closure_t *ansi_closure = g_new0 (finalize_closure_t, 1);
182   ansi_closure->callback = finalize_ansi;
183   ansi_closure->surface = surface;
184   ansi_closure->write_func = write_func;
185   ansi_closure->closure = closure;
186 
187   if (cairo_surface_set_user_data (surface,
188 				   &finalize_closure_key,
189 				   (void *) ansi_closure,
190 				   (cairo_destroy_func_t) g_free))
191     g_free ((void *) closure);
192 
193   return surface;
194 }
195 
196 
197 #ifdef CAIRO_HAS_PNG_FUNCTIONS
198 
199 static void
finalize_png(finalize_closure_t * closure)200 finalize_png (finalize_closure_t *closure)
201 {
202   cairo_status_t status;
203   status = cairo_surface_write_to_png_stream (closure->surface,
204 					      closure->write_func,
205 					      closure->closure);
206   if (status != CAIRO_STATUS_SUCCESS)
207     fail (false, "Failed to write output: %s",
208 	  cairo_status_to_string (status));
209 }
210 
211 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)212 _cairo_png_surface_create_for_stream (cairo_write_func_t write_func,
213 				      void *closure,
214 				      double width,
215 				      double height,
216 				      cairo_content_t content)
217 {
218   cairo_surface_t *surface;
219   int w = ceil (width);
220   int h = ceil (height);
221 
222   switch (content) {
223     case CAIRO_CONTENT_ALPHA:
224       surface = cairo_image_surface_create (CAIRO_FORMAT_A8, w, h);
225       break;
226     default:
227     case CAIRO_CONTENT_COLOR:
228       surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h);
229       break;
230     case CAIRO_CONTENT_COLOR_ALPHA:
231       surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
232       break;
233   }
234   cairo_status_t status = cairo_surface_status (surface);
235   if (status != CAIRO_STATUS_SUCCESS)
236     fail (false, "Failed to create cairo surface: %s",
237 	  cairo_status_to_string (status));
238 
239   finalize_closure_t *png_closure = g_new0 (finalize_closure_t, 1);
240   png_closure->callback = finalize_png;
241   png_closure->surface = surface;
242   png_closure->write_func = write_func;
243   png_closure->closure = closure;
244 
245   if (cairo_surface_set_user_data (surface,
246 				   &finalize_closure_key,
247 				   (void *) png_closure,
248 				   (cairo_destroy_func_t) g_free))
249     g_free ((void *) closure);
250 
251   return surface;
252 }
253 
254 #endif
255 
256 static cairo_status_t
stdio_write_func(void * closure,const unsigned char * data,unsigned int size)257 stdio_write_func (void                *closure,
258 		  const unsigned char *data,
259 		  unsigned int         size)
260 {
261   FILE *fp = (FILE *) closure;
262 
263   while (size) {
264     size_t ret = fwrite (data, 1, size, fp);
265     size -= ret;
266     data += ret;
267     if (size && ferror (fp))
268       fail (false, "Failed to write output: %s", strerror (errno));
269   }
270 
271   return CAIRO_STATUS_SUCCESS;
272 }
273 
274 const char *helper_cairo_supported_formats[] =
275 {
276   "ansi",
277   #ifdef CAIRO_HAS_PNG_FUNCTIONS
278   "png",
279   #endif
280   #ifdef CAIRO_HAS_SVG_SURFACE
281   "svg",
282   #endif
283   #ifdef CAIRO_HAS_PDF_SURFACE
284   "pdf",
285   #endif
286   #ifdef CAIRO_HAS_PS_SURFACE
287   "ps",
288    #ifdef HAS_EPS
289     "eps",
290    #endif
291   #endif
292   NULL
293 };
294 
295 cairo_t *
helper_cairo_create_context(double w,double h,view_options_t * view_opts,output_options_t * out_opts)296 helper_cairo_create_context (double w, double h,
297 			     view_options_t *view_opts,
298 			     output_options_t *out_opts)
299 {
300   cairo_surface_t *(*constructor) (cairo_write_func_t write_func,
301 				   void *closure,
302 				   double width,
303 				   double height) = NULL;
304   cairo_surface_t *(*constructor2) (cairo_write_func_t write_func,
305 				    void *closure,
306 				    double width,
307 				    double height,
308 				    cairo_content_t content) = NULL;
309 
310   const char *extension = out_opts->output_format;
311   if (!extension) {
312 #if HAVE_ISATTY
313     if (isatty (fileno (out_opts->get_file_handle ())))
314       extension = "ansi";
315     else
316 #endif
317     {
318 #ifdef CAIRO_HAS_PNG_FUNCTIONS
319       extension = "png";
320 #else
321       extension = "ansi";
322 #endif
323     }
324   }
325   if (0)
326     ;
327     else if (0 == strcasecmp (extension, "ansi"))
328       constructor2 = _cairo_ansi_surface_create_for_stream;
329   #ifdef CAIRO_HAS_PNG_FUNCTIONS
330     else if (0 == strcasecmp (extension, "png"))
331       constructor2 = _cairo_png_surface_create_for_stream;
332   #endif
333   #ifdef CAIRO_HAS_SVG_SURFACE
334     else if (0 == strcasecmp (extension, "svg"))
335       constructor = cairo_svg_surface_create_for_stream;
336   #endif
337   #ifdef CAIRO_HAS_PDF_SURFACE
338     else if (0 == strcasecmp (extension, "pdf"))
339       constructor = cairo_pdf_surface_create_for_stream;
340   #endif
341   #ifdef CAIRO_HAS_PS_SURFACE
342     else if (0 == strcasecmp (extension, "ps"))
343       constructor = cairo_ps_surface_create_for_stream;
344    #ifdef HAS_EPS
345     else if (0 == strcasecmp (extension, "eps"))
346       constructor = _cairo_eps_surface_create_for_stream;
347    #endif
348   #endif
349 
350 
351   unsigned int fr, fg, fb, fa, br, bg, bb, ba;
352   br = bg = bb = 0; ba = 255;
353   sscanf (view_opts->back + (*view_opts->back=='#'), "%2x%2x%2x%2x", &br, &bg, &bb, &ba);
354   fr = fg = fb = 0; fa = 255;
355   sscanf (view_opts->fore + (*view_opts->fore=='#'), "%2x%2x%2x%2x", &fr, &fg, &fb, &fa);
356 
357   cairo_content_t content;
358   if (!view_opts->annotate && ba == 255 && br == bg && bg == bb && fr == fg && fg == fb)
359     content = CAIRO_CONTENT_ALPHA;
360   else if (ba == 255)
361     content = CAIRO_CONTENT_COLOR;
362   else
363     content = CAIRO_CONTENT_COLOR_ALPHA;
364 
365   cairo_surface_t *surface;
366   FILE *f = out_opts->get_file_handle ();
367   if (constructor)
368     surface = constructor (stdio_write_func, f, w, h);
369   else if (constructor2)
370     surface = constructor2 (stdio_write_func, f, w, h, content);
371   else
372     fail (false, "Unknown output format `%s'; supported formats are: %s%s",
373 	  extension,
374 	  g_strjoinv ("/", const_cast<char**> (helper_cairo_supported_formats)),
375 	  out_opts->explicit_output_format ? "" :
376 	  "\nTry setting format using --output-format");
377 
378   cairo_t *cr = cairo_create (surface);
379   content = cairo_surface_get_content (surface);
380 
381   switch (content) {
382     case CAIRO_CONTENT_ALPHA:
383       cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
384       cairo_set_source_rgba (cr, 1., 1., 1., br / 255.);
385       cairo_paint (cr);
386       cairo_set_source_rgba (cr, 1., 1., 1.,
387 			     (fr / 255.) * (fa / 255.) + (br / 255) * (1 - (fa / 255.)));
388       break;
389     default:
390     case CAIRO_CONTENT_COLOR:
391     case CAIRO_CONTENT_COLOR_ALPHA:
392       cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
393       cairo_set_source_rgba (cr, br / 255., bg / 255., bb / 255., ba / 255.);
394       cairo_paint (cr);
395       cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
396       cairo_set_source_rgba (cr, fr / 255., fg / 255., fb / 255., fa / 255.);
397       break;
398   }
399 
400   cairo_surface_destroy (surface);
401   return cr;
402 }
403 
404 void
helper_cairo_destroy_context(cairo_t * cr)405 helper_cairo_destroy_context (cairo_t *cr)
406 {
407   finalize_closure_t *closure = (finalize_closure_t *)
408 				cairo_surface_get_user_data (cairo_get_target (cr),
409 							     &finalize_closure_key);
410   if (closure)
411     closure->callback (closure);
412 
413   cairo_status_t status = cairo_status (cr);
414   if (status != CAIRO_STATUS_SUCCESS)
415     fail (false, "Failed: %s",
416 	  cairo_status_to_string (status));
417   cairo_destroy (cr);
418 }
419 
420 
421 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)422 helper_cairo_line_from_buffer (helper_cairo_line_t *l,
423 			       hb_buffer_t         *buffer,
424 			       const char          *text,
425 			       unsigned int         text_len,
426 			       int                  scale_bits,
427 			       hb_bool_t            utf8_clusters)
428 {
429   memset (l, 0, sizeof (*l));
430 
431   l->num_glyphs = hb_buffer_get_length (buffer);
432   hb_glyph_info_t *hb_glyph = hb_buffer_get_glyph_infos (buffer, NULL);
433   hb_glyph_position_t *hb_position = hb_buffer_get_glyph_positions (buffer, NULL);
434   l->glyphs = cairo_glyph_allocate (l->num_glyphs + 1);
435 
436   if (text) {
437     l->utf8 = g_strndup (text, text_len);
438     l->utf8_len = text_len;
439     l->num_clusters = l->num_glyphs ? 1 : 0;
440     for (unsigned int i = 1; i < l->num_glyphs; i++)
441       if (hb_glyph[i].cluster != hb_glyph[i-1].cluster)
442 	l->num_clusters++;
443     l->clusters = cairo_text_cluster_allocate (l->num_clusters);
444   }
445 
446   if ((l->num_glyphs && !l->glyphs) ||
447       (l->utf8_len && !l->utf8) ||
448       (l->num_clusters && !l->clusters))
449   {
450     l->finish ();
451     return;
452   }
453 
454   hb_position_t x = 0, y = 0;
455   int i;
456   for (i = 0; i < (int) l->num_glyphs; i++)
457   {
458     l->glyphs[i].index = hb_glyph[i].codepoint;
459     l->glyphs[i].x = scalbn ( hb_position->x_offset + x, scale_bits);
460     l->glyphs[i].y = scalbn (-hb_position->y_offset + y, scale_bits);
461     x +=  hb_position->x_advance;
462     y += -hb_position->y_advance;
463 
464     hb_position++;
465   }
466   l->glyphs[i].index = -1;
467   l->glyphs[i].x = scalbn (x, scale_bits);
468   l->glyphs[i].y = scalbn (y, scale_bits);
469 
470   if (l->num_clusters) {
471     memset ((void *) l->clusters, 0, l->num_clusters * sizeof (l->clusters[0]));
472     hb_bool_t backward = HB_DIRECTION_IS_BACKWARD (hb_buffer_get_direction (buffer));
473     l->cluster_flags = backward ? CAIRO_TEXT_CLUSTER_FLAG_BACKWARD : (cairo_text_cluster_flags_t) 0;
474     unsigned int cluster = 0;
475     const char *start = l->utf8, *end;
476     l->clusters[cluster].num_glyphs++;
477     if (backward) {
478       for (i = l->num_glyphs - 2; i >= 0; i--) {
479 	if (hb_glyph[i].cluster != hb_glyph[i+1].cluster) {
480 	  g_assert (hb_glyph[i].cluster > hb_glyph[i+1].cluster);
481 	  if (utf8_clusters)
482 	    end = start + hb_glyph[i].cluster - hb_glyph[i+1].cluster;
483 	  else
484 	    end = g_utf8_offset_to_pointer (start, hb_glyph[i].cluster - hb_glyph[i+1].cluster);
485 	  l->clusters[cluster].num_bytes = end - start;
486 	  start = end;
487 	  cluster++;
488 	}
489 	l->clusters[cluster].num_glyphs++;
490       }
491       l->clusters[cluster].num_bytes = l->utf8 + text_len - start;
492     } else {
493       for (i = 1; i < (int) l->num_glyphs; i++) {
494 	if (hb_glyph[i].cluster != hb_glyph[i-1].cluster) {
495 	  g_assert (hb_glyph[i].cluster > hb_glyph[i-1].cluster);
496 	  if (utf8_clusters)
497 	    end = start + hb_glyph[i].cluster - hb_glyph[i-1].cluster;
498 	  else
499 	    end = g_utf8_offset_to_pointer (start, hb_glyph[i].cluster - hb_glyph[i-1].cluster);
500 	  l->clusters[cluster].num_bytes = end - start;
501 	  start = end;
502 	  cluster++;
503 	}
504 	l->clusters[cluster].num_glyphs++;
505       }
506       l->clusters[cluster].num_bytes = l->utf8 + text_len - start;
507     }
508   }
509 }
510