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
16void fancy_cairo_stroke (cairo_t *cr);
17void 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 */
22static 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, red: 1.0, green: 0.0, blue: 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, width: line_width / 3);
40 cairo_set_dash (cr, dashes: dash, G_N_ELEMENTS (dash), offset: 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, x: data[1].point.x, y: data[1].point.y);
47 break;
48 case CAIRO_PATH_CURVE_TO:
49 cairo_line_to (cr, x: data[1].point.x, y: data[1].point.y);
50 cairo_move_to (cr, x: data[2].point.x, y: data[2].point.y);
51 cairo_line_to (cr, x: data[3].point.x, y: 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, width: line_width * 4);
64 cairo_set_line_cap (cr, line_cap: 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, x: data[1].point.x, y: data[1].point.y);
70 break;
71 case CAIRO_PATH_LINE_TO:
72 cairo_rel_line_to (cr, dx: 0, dy: 0);
73 cairo_move_to (cr, x: data[1].point.x, y: data[1].point.y);
74 break;
75 case CAIRO_PATH_CURVE_TO:
76 cairo_rel_line_to (cr, dx: 0, dy: 0);
77 cairo_move_to (cr, x: data[1].point.x, y: data[1].point.y);
78 cairo_rel_line_to (cr, dx: 0, dy: 0);
79 cairo_move_to (cr, x: data[2].point.x, y: data[2].point.y);
80 cairo_rel_line_to (cr, dx: 0, dy: 0);
81 cairo_move_to (cr, x: data[3].point.x, y: data[3].point.y);
82 break;
83 case CAIRO_PATH_CLOSE_PATH:
84 cairo_rel_line_to (cr, dx: 0, dy: 0);
85 break;
86 default:
87 g_assert_not_reached ();
88 }
89 }
90 cairo_rel_line_to (cr, dx: 0, dy: 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, x: data[1].point.x, y: data[1].point.y);
99 break;
100 case CAIRO_PATH_LINE_TO:
101 cairo_line_to (cr, x: data[1].point.x, y: data[1].point.y);
102 break;
103 case CAIRO_PATH_CURVE_TO:
104 cairo_curve_to (cr, x1: data[1].point.x, y1: data[1].point.y,
105 x2: data[2].point.x, y2: data[2].point.y,
106 x3: data[3].point.x, y3: 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 */
128void
129fancy_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 */
137void
138fancy_cairo_stroke_preserve (cairo_t *cr)
139{
140 _fancy_cairo_stroke (cr, TRUE);
141}
142
143
144/* Returns Euclidean distance between two points */
145static double
146two_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 (x: 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 */
161static double
162curve_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 = {{0,},};
171 int i;
172 double length;
173
174 surface = cairo_image_surface_create (format: CAIRO_FORMAT_A8, width: 0, height: 0);
175 cr = cairo_create (target: surface);
176 cairo_surface_destroy (surface);
177
178 cairo_move_to (cr, x: x0, y: 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 (a: &current_point, b: &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
210typedef 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 */
217static parametrization_t *
218parametrize_path (cairo_path_t *path)
219{
220 int i;
221 cairo_path_data_t *data, last_move_to = {{0,},}, current_point = {{0,},};
222 parametrization_t *parametrization;
223
224 parametrization = g_malloc (n_bytes: 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 G_GNUC_FALLTHROUGH;
238 case CAIRO_PATH_LINE_TO:
239 parametrization[i] = two_points_distance (a: &current_point, b: &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 (x0: current_point.point.x, y0: current_point.point.y,
249 x1: data[1].point.x, y1: data[1].point.y,
250 x2: data[2].point.x, y2: data[2].point.y,
251 x3: data[3].point.x, y3: 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
264typedef 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 */
269static void
270transform_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 G_GNUC_FALLTHROUGH;
282 case CAIRO_PATH_MOVE_TO:
283 case CAIRO_PATH_LINE_TO:
284 f (closure, &data[1].point.x, &data[1].point.y);
285 break;
286 case CAIRO_PATH_CLOSE_PATH:
287 break;
288 default:
289 g_assert_not_reached ();
290 }
291 }
292}
293
294
295/* Simple struct to hold a path and its parametrization */
296typedef struct {
297 cairo_path_t *path;
298 parametrization_t *parametrization;
299} parametrized_path_t;
300
301
302/* Project a point X,Y onto a parameterized path. The final point is
303 * where you get if you walk on the path forward from the beginning for X
304 * units, then stop there and walk another Y units perpendicular to the
305 * path at that point. In more detail:
306 *
307 * There's three pieces of math involved:
308 *
309 * - The parametric form of the Line equation
310 * http://en.wikipedia.org/wiki/Line
311 *
312 * - The parametric form of the Cubic Bézier curve equation
313 * http://en.wikipedia.org/wiki/B%C3%A9zier_curve
314 *
315 * - The Gradient (aka multi-dimensional derivative) of the above
316 * http://en.wikipedia.org/wiki/Gradient
317 *
318 * The parametric forms are used to answer the question of "where will I be
319 * if I walk a distance of X on this path". The Gradient is used to answer
320 * the question of "where will I be if then I stop, rotate left for 90
321 * degrees and walk straight for a distance of Y".
322 */
323static void
324point_on_path (parametrized_path_t *param,
325 double *x, double *y)
326{
327 int i;
328 double ratio, the_y = *y, the_x = *x, dx, dy;
329 cairo_path_data_t *data, last_move_to = {{0,},}, current_point = {{0,},};
330 cairo_path_t *path = param->path;
331 parametrization_t *parametrization = param->parametrization;
332
333 for (i=0; i + path->data[i].header.length < path->num_data &&
334 (the_x > parametrization[i] ||
335 path->data[i].header.type == CAIRO_PATH_MOVE_TO);
336 i += path->data[i].header.length) {
337 the_x -= parametrization[i];
338 data = &path->data[i];
339 switch (data->header.type) {
340 case CAIRO_PATH_MOVE_TO:
341 current_point = data[1];
342 last_move_to = data[1];
343 break;
344 case CAIRO_PATH_LINE_TO:
345 current_point = data[1];
346 break;
347 case CAIRO_PATH_CURVE_TO:
348 current_point = data[3];
349 break;
350 case CAIRO_PATH_CLOSE_PATH:
351 break;
352 default:
353 g_assert_not_reached ();
354 }
355 }
356 data = &path->data[i];
357
358 switch (data->header.type) {
359
360 case CAIRO_PATH_MOVE_TO:
361 break;
362 case CAIRO_PATH_CLOSE_PATH:
363 /* Make it look like it's a line_to to last_move_to */
364 data = (&last_move_to) - 1;
365 G_GNUC_FALLTHROUGH;
366 case CAIRO_PATH_LINE_TO:
367 {
368 ratio = the_x / parametrization[i];
369 /* Line polynomial */
370 *x = current_point.point.x * (1 - ratio) + data[1].point.x * ratio;
371 *y = current_point.point.y * (1 - ratio) + data[1].point.y * ratio;
372
373 /* Line gradient */
374 dx = -(current_point.point.x - data[1].point.x);
375 dy = -(current_point.point.y - data[1].point.y);
376
377 /*optimization for: ratio = the_y / sqrt (dx * dx + dy * dy);*/
378 ratio = the_y / parametrization[i];
379 *x += -dy * ratio;
380 *y += dx * ratio;
381 }
382 break;
383 case CAIRO_PATH_CURVE_TO:
384 {
385 /* FIXME the formulas here are not exactly what we want, because the
386 * Bezier parametrization is not uniform. But I don't know how to do
387 * better. The caller can do slightly better though, by flattening the
388 * Bezier and avoiding this branch completely. That has its own cost
389 * though, as large y values magnify the flattening error drastically.
390 */
391
392 double ratio_1_0, ratio_0_1;
393 double ratio_2_0, ratio_0_2;
394 double ratio_3_0, ratio_2_1, ratio_1_2, ratio_0_3;
395 double _1__4ratio_1_0_3ratio_2_0, _2ratio_1_0_3ratio_2_0;
396
397 ratio = the_x / parametrization[i];
398
399 ratio_1_0 = ratio;
400 ratio_0_1 = 1 - ratio;
401
402 ratio_2_0 = ratio_1_0 * ratio_1_0; /* ratio * ratio */
403 ratio_0_2 = ratio_0_1 * ratio_0_1; /* (1 - ratio) * (1 - ratio) */
404
405 ratio_3_0 = ratio_2_0 * ratio_1_0; /* ratio * ratio * ratio */
406 ratio_2_1 = ratio_2_0 * ratio_0_1; /* ratio * ratio * (1 - ratio) */
407 ratio_1_2 = ratio_1_0 * ratio_0_2; /* ratio * (1 - ratio) * (1 - ratio) */
408 ratio_0_3 = ratio_0_1 * ratio_0_2; /* (1 - ratio) * (1 - ratio) * (1 - ratio) */
409
410 _1__4ratio_1_0_3ratio_2_0 = 1 - 4 * ratio_1_0 + 3 * ratio_2_0;
411 _2ratio_1_0_3ratio_2_0 = 2 * ratio_1_0 - 3 * ratio_2_0;
412
413 /* Bezier polynomial */
414 *x = current_point.point.x * ratio_0_3
415 + 3 * data[1].point.x * ratio_1_2
416 + 3 * data[2].point.x * ratio_2_1
417 + data[3].point.x * ratio_3_0;
418 *y = current_point.point.y * ratio_0_3
419 + 3 * data[1].point.y * ratio_1_2
420 + 3 * data[2].point.y * ratio_2_1
421 + data[3].point.y * ratio_3_0;
422
423 /* Bezier gradient */
424 dx =-3 * current_point.point.x * ratio_0_2
425 + 3 * data[1].point.x * _1__4ratio_1_0_3ratio_2_0
426 + 3 * data[2].point.x * _2ratio_1_0_3ratio_2_0
427 + 3 * data[3].point.x * ratio_2_0;
428 dy =-3 * current_point.point.y * ratio_0_2
429 + 3 * data[1].point.y * _1__4ratio_1_0_3ratio_2_0
430 + 3 * data[2].point.y * _2ratio_1_0_3ratio_2_0
431 + 3 * data[3].point.y * ratio_2_0;
432
433 ratio = the_y / sqrt (x: dx * dx + dy * dy);
434 *x += -dy * ratio;
435 *y += dx * ratio;
436 }
437 break;
438 default:
439 g_assert_not_reached ();
440 }
441}
442
443/* Projects the current path of cr onto the provided path. */
444static void
445map_path_onto (cairo_t *cr, cairo_path_t *path)
446{
447 cairo_path_t *current_path;
448 parametrized_path_t param;
449
450 param.path = path;
451 param.parametrization = parametrize_path (path);
452
453 current_path = cairo_copy_path (cr);
454 cairo_new_path (cr);
455
456 transform_path (path: current_path,
457 f: (transform_point_func_t) point_on_path, closure: &param);
458
459 cairo_append_path (cr, path: current_path);
460
461 cairo_path_destroy (path: current_path);
462 g_free (mem: param.parametrization);
463}
464
465
466typedef void (*draw_path_func_t) (cairo_t *cr);
467
468static void
469draw_text (cairo_t *cr,
470 double x,
471 double y,
472 const char *font,
473 const char *text)
474{
475 PangoLayout *layout;
476 PangoLayoutLine *line;
477 PangoFontDescription *desc;
478 cairo_font_options_t *font_options;
479
480 font_options = cairo_font_options_create ();
481
482 cairo_font_options_set_hint_style (options: font_options, hint_style: CAIRO_HINT_STYLE_NONE);
483 cairo_font_options_set_hint_metrics (options: font_options, hint_metrics: CAIRO_HINT_METRICS_OFF);
484
485 cairo_set_font_options (cr, options: font_options);
486 cairo_font_options_destroy (options: font_options);
487
488 layout = pango_cairo_create_layout (cr);
489
490 desc = pango_font_description_from_string (str: font);
491 pango_layout_set_font_description (layout, desc);
492 pango_font_description_free (desc);
493
494 pango_layout_set_text (layout, text, length: -1);
495
496 /* Use pango_layout_get_line() instead of pango_layout_get_line_readonly()
497 * for older versions of pango
498 */
499 line = pango_layout_get_line_readonly (layout, line: 0);
500
501 cairo_move_to (cr, x, y);
502 pango_cairo_layout_line_path (cr, line);
503
504 g_object_unref (object: layout);
505}
506
507static void
508draw_twisted (cairo_t *cr,
509 double x,
510 double y,
511 const char *font,
512 const char *text)
513{
514 cairo_path_t *path;
515
516 cairo_save (cr);
517
518 /* Decrease tolerance a bit, since it's going to be magnified */
519 cairo_set_tolerance (cr, tolerance: 0.01);
520
521 /* Using cairo_copy_path() here shows our deficiency in handling
522 * Bezier curves, specially around sharper curves.
523 *
524 * Using cairo_copy_path_flat() on the other hand, magnifies the
525 * flattening error with large off-path values. We decreased
526 * tolerance for that reason. Increase tolerance to see that
527 * artifact.
528 */
529 path = cairo_copy_path_flat (cr);
530/*path = cairo_copy_path (cr);*/
531
532 cairo_new_path (cr);
533
534 draw_text (cr, x, y, font, text);
535 map_path_onto (cr, path);
536
537 cairo_path_destroy (path);
538
539 cairo_fill_preserve (cr);
540
541 cairo_save (cr);
542 cairo_set_source_rgb (cr, red: 0.1, green: 0.1, blue: 0.1);
543 cairo_stroke (cr);
544 cairo_restore (cr);
545
546 cairo_restore (cr);
547}
548
549static void
550draw_dream (cairo_t *cr)
551{
552 cairo_move_to (cr, x: 50, y: 650);
553
554 cairo_rel_line_to (cr, dx: 250, dy: 50);
555 cairo_rel_curve_to (cr, dx1: 250, dy1: 50, dx2: 600, dy2: -50, dx3: 600, dy3: -250);
556 cairo_rel_curve_to (cr, dx1: 0, dy1: -400, dx2: -300, dy2: -100, dx3: -800, dy3: -300);
557
558 cairo_set_line_width (cr, width: 1.5);
559 cairo_set_source_rgba (cr, red: 0.3, green: 0.3, blue: 1.0, alpha: 0.3);
560
561 fancy_cairo_stroke_preserve (cr);
562
563 draw_twisted (cr,
564 x: 0, y: 0,
565 font: "Serif 72",
566 text: "It was a dream... Oh Just a dream...");
567}
568
569static void
570draw_wow (cairo_t *cr)
571{
572 cairo_move_to (cr, x: 400, y: 780);
573
574 cairo_rel_curve_to (cr, dx1: 50, dy1: -50, dx2: 150, dy2: -50, dx3: 200, dy3: 0);
575
576 cairo_scale (cr, sx: 1.0, sy: 2.0);
577 cairo_set_line_width (cr, width: 2.0);
578 cairo_set_source_rgba (cr, red: 0.3, green: 1.0, blue: 0.3, alpha: 1.0);
579
580 fancy_cairo_stroke_preserve (cr);
581
582 draw_twisted (cr,
583 x: -20, y: -150,
584 font: "Serif 60",
585 text: "WOW!");
586}
587
588int main (int argc, char **argv)
589{
590 cairo_t *cr;
591 char *filename;
592 cairo_status_t status;
593 cairo_surface_t *surface;
594
595 if (argc != 2)
596 {
597 g_printerr (format: "Usage: cairotwisted OUTPUT_FILENAME\n");
598 return 1;
599 }
600
601 filename = argv[1];
602
603 surface = cairo_image_surface_create (format: CAIRO_FORMAT_ARGB32,
604 width: 1000, height: 800);
605 cr = cairo_create (target: surface);
606
607 cairo_set_source_rgb (cr, red: 1.0, green: 1.0, blue: 1.0);
608 cairo_paint (cr);
609
610 draw_dream (cr);
611 draw_wow (cr);
612
613 cairo_destroy (cr);
614
615 status = cairo_surface_write_to_png (surface, filename);
616 cairo_surface_destroy (surface);
617
618 if (status != CAIRO_STATUS_SUCCESS)
619 {
620 g_printerr (format: "Could not save png to '%s'\n", filename);
621 return 1;
622 }
623
624 return 0;
625}
626

source code of gtk/subprojects/pango/examples/cairotwisted.c