Git init
[external/pango1.0.git] / examples / cairotwisted.c
1 /* Example code to show how to use pangocairo to render text
2  * projected on a path.
3  *
4  *
5  * Written by Behdad Esfahbod, 2006..2007
6  *
7  * Permission to use, copy, modify, distribute, and sell this example
8  * for any purpose is hereby granted without fee.
9  * It is provided "as is" without express or implied warranty.
10  */
11
12 #include <math.h>
13 #include <stdlib.h>
14 #include <pango/pangocairo.h>
15
16 void fancy_cairo_stroke (cairo_t *cr);
17 void fancy_cairo_stroke_preserve (cairo_t *cr);
18
19 /* A fancy cairo_stroke[_preserve]() that draws points and control
20  * points, and connects them together.
21  */
22 static void
23 _fancy_cairo_stroke (cairo_t *cr, cairo_bool_t preserve)
24 {
25   int i;
26   double line_width;
27   cairo_path_t *path;
28   cairo_path_data_t *data;
29   const double dash[] = {10, 10};
30
31   cairo_save (cr);
32   cairo_set_source_rgb (cr, 1.0, 0.0, 0.0);
33
34   line_width = cairo_get_line_width (cr);
35   path = cairo_copy_path (cr);
36   cairo_new_path (cr);
37
38   cairo_save (cr);
39   cairo_set_line_width (cr, line_width / 3);
40   cairo_set_dash (cr, dash, G_N_ELEMENTS (dash), 0);
41   for (i=0; i < path->num_data; i += path->data[i].header.length) {
42     data = &path->data[i];
43     switch (data->header.type) {
44     case CAIRO_PATH_MOVE_TO:
45     case CAIRO_PATH_LINE_TO:
46         cairo_move_to (cr, data[1].point.x, data[1].point.y);
47         break;
48     case CAIRO_PATH_CURVE_TO:
49         cairo_line_to (cr, data[1].point.x, data[1].point.y);
50         cairo_move_to (cr, data[2].point.x, data[2].point.y);
51         cairo_line_to (cr, data[3].point.x, data[3].point.y);
52         break;
53     case CAIRO_PATH_CLOSE_PATH:
54         break;
55     default:
56         g_assert_not_reached ();
57     }
58   }
59   cairo_stroke (cr);
60   cairo_restore (cr);
61
62   cairo_save (cr);
63   cairo_set_line_width (cr, line_width * 4);
64   cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
65   for (i=0; i < path->num_data; i += path->data[i].header.length) {
66     data = &path->data[i];
67     switch (data->header.type) {
68     case CAIRO_PATH_MOVE_TO:
69         cairo_move_to (cr, data[1].point.x, data[1].point.y);
70         break;
71     case CAIRO_PATH_LINE_TO:
72         cairo_rel_line_to (cr, 0, 0);
73         cairo_move_to (cr, data[1].point.x, data[1].point.y);
74         break;
75     case CAIRO_PATH_CURVE_TO:
76         cairo_rel_line_to (cr, 0, 0);
77         cairo_move_to (cr, data[1].point.x, data[1].point.y);
78         cairo_rel_line_to (cr, 0, 0);
79         cairo_move_to (cr, data[2].point.x, data[2].point.y);
80         cairo_rel_line_to (cr, 0, 0);
81         cairo_move_to (cr, data[3].point.x, data[3].point.y);
82         break;
83     case CAIRO_PATH_CLOSE_PATH:
84         cairo_rel_line_to (cr, 0, 0);
85         break;
86     default:
87         g_assert_not_reached ();
88     }
89   }
90   cairo_rel_line_to (cr, 0, 0);
91   cairo_stroke (cr);
92   cairo_restore (cr);
93
94   for (i=0; i < path->num_data; i += path->data[i].header.length) {
95     data = &path->data[i];
96     switch (data->header.type) {
97     case CAIRO_PATH_MOVE_TO:
98         cairo_move_to (cr, data[1].point.x, data[1].point.y);
99         break;
100     case CAIRO_PATH_LINE_TO:
101         cairo_line_to (cr, data[1].point.x, data[1].point.y);
102         break;
103     case CAIRO_PATH_CURVE_TO:
104         cairo_curve_to (cr, data[1].point.x, data[1].point.y,
105                             data[2].point.x, data[2].point.y,
106                             data[3].point.x, data[3].point.y);
107         break;
108     case CAIRO_PATH_CLOSE_PATH:
109         cairo_close_path (cr);
110         break;
111     default:
112         g_assert_not_reached ();
113     }
114   }
115   cairo_stroke (cr);
116
117   if (preserve)
118     cairo_append_path (cr, path);
119
120   cairo_path_destroy (path);
121
122   cairo_restore (cr);
123 }
124
125 /* A fancy cairo_stroke() that draws points and control points, and
126  * connects them together.
127  */
128 void
129 fancy_cairo_stroke (cairo_t *cr)
130 {
131   _fancy_cairo_stroke (cr, FALSE);
132 }
133
134 /* A fancy cairo_stroke_preserve() that draws points and control
135  * points, and connects them together.
136  */
137 void
138 fancy_cairo_stroke_preserve (cairo_t *cr)
139 {
140   _fancy_cairo_stroke (cr, TRUE);
141 }
142
143
144 /* Returns Euclidean distance between two points */
145 static double
146 two_points_distance (cairo_path_data_t *a, cairo_path_data_t *b)
147 {
148   double dx, dy;
149
150   dx = b->point.x - a->point.x;
151   dy = b->point.y - a->point.y;
152
153   return sqrt (dx * dx + dy * dy);
154 }
155
156 /* Returns length of a Bezier curve.
157  * Seems like computing that analytically is not easy.  The
158  * code just flattens the curve using cairo and adds the length
159  * of segments.
160  */
161 static double
162 curve_length (double x0, double y0,
163               double x1, double y1,
164               double x2, double y2,
165               double x3, double y3)
166 {
167   cairo_surface_t *surface;
168   cairo_t *cr;
169   cairo_path_t *path;
170   cairo_path_data_t *data, current_point;
171   int i;
172   double length;
173
174   surface = cairo_image_surface_create (CAIRO_FORMAT_A8, 0, 0);
175   cr = cairo_create (surface);
176   cairo_surface_destroy (surface);
177
178   cairo_move_to (cr, x0, y0);
179   cairo_curve_to (cr, x1, y1, x2, y2, x3, y3);
180
181   length = 0;
182   path = cairo_copy_path_flat (cr);
183   for (i=0; i < path->num_data; i += path->data[i].header.length) {
184     data = &path->data[i];
185     switch (data->header.type) {
186
187     case CAIRO_PATH_MOVE_TO:
188         current_point = data[1];
189         break;
190
191     case CAIRO_PATH_LINE_TO:
192         length += two_points_distance (&current_point, &data[1]);
193         current_point = data[1];
194         break;
195
196     default:
197     case CAIRO_PATH_CURVE_TO:
198     case CAIRO_PATH_CLOSE_PATH:
199         g_assert_not_reached ();
200     }
201   }
202   cairo_path_destroy (path);
203
204   cairo_destroy (cr);
205
206   return length;
207 }
208
209
210 typedef double parametrization_t;
211
212 /* Compute parametrization info.  That is, for each part of the 
213  * cairo path, tags it with its length.
214  *
215  * Free returned value with g_free().
216  */
217 static parametrization_t *
218 parametrize_path (cairo_path_t *path)
219 {
220   int i;
221   cairo_path_data_t *data, last_move_to, current_point;
222   parametrization_t *parametrization;
223
224   parametrization = g_malloc (path->num_data * sizeof (parametrization[0]));
225
226   for (i=0; i < path->num_data; i += path->data[i].header.length) {
227     data = &path->data[i];
228     parametrization[i] = 0.0;
229     switch (data->header.type) {
230     case CAIRO_PATH_MOVE_TO:
231         last_move_to = data[1];
232         current_point = data[1];
233         break;
234     case CAIRO_PATH_CLOSE_PATH:
235         /* Make it look like it's a line_to to last_move_to */
236         data = (&last_move_to) - 1;
237         /* fall through */
238     case CAIRO_PATH_LINE_TO:
239         parametrization[i] = two_points_distance (&current_point, &data[1]);
240         current_point = data[1];
241         break;
242     case CAIRO_PATH_CURVE_TO:
243         /* naive curve-length, treating bezier as three line segments:
244         parametrization[i] = two_points_distance (&current_point, &data[1])
245                            + two_points_distance (&data[1], &data[2])
246                            + two_points_distance (&data[2], &data[3]);
247         */
248         parametrization[i] = curve_length (current_point.point.x, current_point.point.x,
249                                            data[1].point.x, data[1].point.y,
250                                            data[2].point.x, data[2].point.y,
251                                            data[3].point.x, data[3].point.y);
252
253         current_point = data[3];
254         break;
255     default:
256         g_assert_not_reached ();
257     }
258   }
259
260   return parametrization;
261 }
262
263
264 typedef void (*transform_point_func_t) (void *closure, double *x, double *y);
265
266 /* Project a path using a function.  Each point of the path (including
267  * Bezier control points) is passed to the function for transformation.
268  */
269 static void
270 transform_path (cairo_path_t *path, transform_point_func_t f, void *closure)
271 {
272   int i;
273   cairo_path_data_t *data;
274
275   for (i=0; i < path->num_data; i += path->data[i].header.length) {
276     data = &path->data[i];
277     switch (data->header.type) {
278     case CAIRO_PATH_CURVE_TO:
279       f (closure, &data[3].point.x, &data[3].point.y);
280       f (closure, &data[2].point.x, &data[2].point.y);
281     case CAIRO_PATH_MOVE_TO:
282     case CAIRO_PATH_LINE_TO:
283       f (closure, &data[1].point.x, &data[1].point.y);
284       break;
285     case CAIRO_PATH_CLOSE_PATH:
286       break;
287     default:
288         g_assert_not_reached ();
289     }
290   }
291 }
292
293
294 /* Simple struct to hold a path and its parametrization */
295 typedef struct {
296   cairo_path_t *path;
297   parametrization_t *parametrization;
298 } parametrized_path_t;
299
300
301 /* Project a point X,Y onto a parameterized path.  The final point is
302  * where you get if you walk on the path forward from the beginning for X
303  * units, then stop there and walk another Y units perpendicular to the
304  * path at that point.  In more detail:
305  *
306  * There's three pieces of math involved:
307  *
308  *   - The parametric form of the Line equation
309  *     http://en.wikipedia.org/wiki/Line
310  *
311  *   - The parametric form of the Cubic Bézier curve equation
312  *     http://en.wikipedia.org/wiki/B%C3%A9zier_curve
313  *
314  *   - The Gradient (aka multi-dimensional derivative) of the above
315  *     http://en.wikipedia.org/wiki/Gradient
316  *
317  * The parametric forms are used to answer the question of "where will I be
318  * if I walk a distance of X on this path".  The Gradient is used to answer
319  * the question of "where will I be if then I stop, rotate left for 90
320  * degrees and walk straight for a distance of Y".
321  */
322 static void
323 point_on_path (parametrized_path_t *param,
324                double *x, double *y)
325 {
326   int i;
327   double ratio, the_y = *y, the_x = *x, dx, dy;
328   cairo_path_data_t *data, last_move_to, current_point;
329   cairo_path_t *path = param->path;
330   parametrization_t *parametrization = param->parametrization;
331
332   for (i=0; i + path->data[i].header.length < path->num_data &&
333             (the_x > parametrization[i] ||
334              path->data[i].header.type == CAIRO_PATH_MOVE_TO);
335        i += path->data[i].header.length) {
336     the_x -= parametrization[i];
337     data = &path->data[i];
338     switch (data->header.type) {
339     case CAIRO_PATH_MOVE_TO:
340         current_point = data[1];
341         last_move_to = data[1];
342         break;
343     case CAIRO_PATH_LINE_TO:
344         current_point = data[1];
345         break;
346     case CAIRO_PATH_CURVE_TO:
347         current_point = data[3];
348         break;
349     case CAIRO_PATH_CLOSE_PATH:
350         break;
351     default:
352         g_assert_not_reached ();
353     }
354   }
355   data = &path->data[i];
356
357   switch (data->header.type) {
358
359   case CAIRO_PATH_MOVE_TO:
360       break;
361   case CAIRO_PATH_CLOSE_PATH:
362       /* Make it look like it's a line_to to last_move_to */
363       data = (&last_move_to) - 1;
364       /* fall through */
365   case CAIRO_PATH_LINE_TO:
366       {
367         ratio = the_x / parametrization[i];
368         /* Line polynomial */
369         *x = current_point.point.x * (1 - ratio) + data[1].point.x * ratio;
370         *y = current_point.point.y * (1 - ratio) + data[1].point.y * ratio;
371
372         /* Line gradient */
373         dx = -(current_point.point.x - data[1].point.x);
374         dy = -(current_point.point.y - data[1].point.y);
375
376         /*optimization for: ratio = the_y / sqrt (dx * dx + dy * dy);*/
377         ratio = the_y / parametrization[i];
378         *x += -dy * ratio;
379         *y +=  dx * ratio;
380       }
381       break;
382   case CAIRO_PATH_CURVE_TO:
383       {
384         /* FIXME the formulas here are not exactly what we want, because the
385          * Bezier parametrization is not uniform.  But I don't know how to do
386          * better.  The caller can do slightly better though, by flattening the
387          * Bezier and avoiding this branch completely.  That has its own cost
388          * though, as large y values magnify the flattening error drastically.
389          */
390
391         double ratio_1_0, ratio_0_1;
392         double ratio_2_0, ratio_0_2;
393         double ratio_3_0, ratio_2_1, ratio_1_2, ratio_0_3;
394         double _1__4ratio_1_0_3ratio_2_0, _2ratio_1_0_3ratio_2_0;
395
396         ratio = the_x / parametrization[i];
397
398         ratio_1_0 = ratio;
399         ratio_0_1 = 1 - ratio;
400
401         ratio_2_0 = ratio_1_0 * ratio_1_0; /*      ratio  *      ratio  */
402         ratio_0_2 = ratio_0_1 * ratio_0_1; /* (1 - ratio) * (1 - ratio) */
403
404         ratio_3_0 = ratio_2_0 * ratio_1_0; /*      ratio  *      ratio  *      ratio  */
405         ratio_2_1 = ratio_2_0 * ratio_0_1; /*      ratio  *      ratio  * (1 - ratio) */
406         ratio_1_2 = ratio_1_0 * ratio_0_2; /*      ratio  * (1 - ratio) * (1 - ratio) */
407         ratio_0_3 = ratio_0_1 * ratio_0_2; /* (1 - ratio) * (1 - ratio) * (1 - ratio) */
408
409         _1__4ratio_1_0_3ratio_2_0 = 1 - 4 * ratio_1_0 + 3 * ratio_2_0;
410         _2ratio_1_0_3ratio_2_0    =     2 * ratio_1_0 - 3 * ratio_2_0;
411
412         /* Bezier polynomial */
413         *x = current_point.point.x * ratio_0_3
414            + 3 *   data[1].point.x * ratio_1_2
415            + 3 *   data[2].point.x * ratio_2_1
416            +       data[3].point.x * ratio_3_0;
417         *y = current_point.point.y * ratio_0_3
418            + 3 *   data[1].point.y * ratio_1_2
419            + 3 *   data[2].point.y * ratio_2_1
420            +       data[3].point.y * ratio_3_0;
421
422         /* Bezier gradient */
423         dx =-3 * current_point.point.x * ratio_0_2
424            + 3 *       data[1].point.x * _1__4ratio_1_0_3ratio_2_0
425            + 3 *       data[2].point.x * _2ratio_1_0_3ratio_2_0
426            + 3 *       data[3].point.x * ratio_2_0;
427         dy =-3 * current_point.point.y * ratio_0_2
428            + 3 *       data[1].point.y * _1__4ratio_1_0_3ratio_2_0
429            + 3 *       data[2].point.y * _2ratio_1_0_3ratio_2_0
430            + 3 *       data[3].point.y * ratio_2_0;
431
432         ratio = the_y / sqrt (dx * dx + dy * dy);
433         *x += -dy * ratio;
434         *y +=  dx * ratio;
435       }
436       break;
437   default:
438       g_assert_not_reached ();
439   }
440 }
441
442 /* Projects the current path of cr onto the provided path. */
443 static void
444 map_path_onto (cairo_t *cr, cairo_path_t *path)
445 {
446   cairo_path_t *current_path;
447   parametrized_path_t param;
448
449   param.path = path;
450   param.parametrization = parametrize_path (path);
451
452   current_path = cairo_copy_path (cr);
453   cairo_new_path (cr);
454
455   transform_path (current_path,
456                   (transform_point_func_t) point_on_path, &param);
457
458   cairo_append_path (cr, current_path);
459
460   cairo_path_destroy (current_path);
461   g_free (param.parametrization);
462 }
463
464
465 typedef void (*draw_path_func_t) (cairo_t *cr);
466
467 static void
468 draw_text (cairo_t *cr,
469            double x,
470            double y,
471            const char *font,
472            const char *text)
473 {
474   PangoLayout *layout;
475   PangoLayoutLine *line;
476   PangoFontDescription *desc;
477   cairo_font_options_t *font_options;
478
479   font_options = cairo_font_options_create ();
480
481   cairo_font_options_set_hint_style (font_options, CAIRO_HINT_STYLE_NONE);
482   cairo_font_options_set_hint_metrics (font_options, CAIRO_HINT_METRICS_OFF);
483
484   cairo_set_font_options (cr, font_options);
485   cairo_font_options_destroy (font_options);
486
487   layout = pango_cairo_create_layout (cr);
488
489   desc = pango_font_description_from_string (font);
490   pango_layout_set_font_description (layout, desc);
491   pango_font_description_free (desc);
492
493   pango_layout_set_text (layout, text, -1);
494
495   /* Use pango_layout_get_line() instead of pango_layout_get_line_readonly()
496    * for older versions of pango
497    */
498   line = pango_layout_get_line_readonly (layout, 0);
499
500   cairo_move_to (cr, x, y);
501   pango_cairo_layout_line_path (cr, line);
502
503   g_object_unref (layout);
504 }
505
506 static void
507 draw_twisted (cairo_t *cr,
508               double x,
509               double y,
510               const char *font,
511               const char *text)
512 {
513   cairo_path_t *path;
514
515   cairo_save (cr);
516
517   /* Decrease tolerance a bit, since it's going to be magnified */
518   cairo_set_tolerance (cr, 0.01);
519
520   /* Using cairo_copy_path() here shows our deficiency in handling
521    * Bezier curves, specially around sharper curves.
522    *
523    * Using cairo_copy_path_flat() on the other hand, magnifies the
524    * flattening error with large off-path values.  We decreased
525    * tolerance for that reason.  Increase tolerance to see that
526    * artifact.
527    */
528   path = cairo_copy_path_flat (cr);
529 /*path = cairo_copy_path (cr);*/
530
531   cairo_new_path (cr);
532
533   draw_text (cr, x, y, font, text);
534   map_path_onto (cr, path);
535
536   cairo_path_destroy (path);
537
538   cairo_fill_preserve (cr);
539
540   cairo_save (cr);
541   cairo_set_source_rgb (cr, 0.1, 0.1, 0.1);
542   cairo_stroke (cr);
543   cairo_restore (cr);
544
545   cairo_restore (cr);
546 }
547
548 static void
549 draw_dream (cairo_t *cr)
550 {
551   cairo_move_to (cr, 50, 650);
552
553   cairo_rel_line_to (cr, 250, 50);
554   cairo_rel_curve_to (cr, 250, 50, 600, -50, 600, -250);
555   cairo_rel_curve_to (cr, 0, -400, -300, -100, -800, -300);
556
557   cairo_set_line_width (cr, 1.5);
558   cairo_set_source_rgba (cr, 0.3, 0.3, 1.0, 0.3);
559
560   fancy_cairo_stroke_preserve (cr);
561
562   draw_twisted (cr,
563                 0, 0,
564                 "Serif 72",
565                 "It was a dream... Oh Just a dream...");
566 }
567
568 static void
569 draw_wow (cairo_t *cr)
570 {
571   cairo_move_to (cr, 400, 780);
572
573   cairo_rel_curve_to (cr, 50, -50, 150, -50, 200, 0);
574
575   cairo_scale (cr, 1.0, 2.0);
576   cairo_set_line_width (cr, 2.0);
577   cairo_set_source_rgba (cr, 0.3, 1.0, 0.3, 1.0);
578
579   fancy_cairo_stroke_preserve (cr);
580
581   draw_twisted (cr,
582                 -20, -150,
583                 "Serif 60",
584                 "WOW!");
585 }
586
587 int main (int argc, char **argv)
588 {
589   cairo_t *cr;
590   char *filename;
591   cairo_status_t status;
592   cairo_surface_t *surface;
593
594   if (argc != 2)
595     {
596       g_printerr ("Usage: cairotwisted OUTPUT_FILENAME\n");
597       return 1;
598     }
599
600   filename = argv[1];
601
602   surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
603                                         1000, 800);
604   cr = cairo_create (surface);
605
606   cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
607   cairo_paint (cr);
608
609   draw_dream (cr);
610   draw_wow (cr);
611
612   cairo_destroy (cr);
613
614   status = cairo_surface_write_to_png (surface, filename);
615   cairo_surface_destroy (surface);
616
617   if (status != CAIRO_STATUS_SUCCESS)
618     {
619       g_printerr ("Could not save png to '%s'\n", filename);
620       return 1;
621     }
622
623   return 0;
624 }