1 /* Example code to show how to use pangocairo to render text
5 * Written by Behdad Esfahbod, 2006..2007
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.
14 #include <pango/pangocairo.h>
16 void fancy_cairo_stroke (cairo_t *cr);
17 void fancy_cairo_stroke_preserve (cairo_t *cr);
19 /* A fancy cairo_stroke[_preserve]() that draws points and control
20 * points, and connects them together.
23 _fancy_cairo_stroke (cairo_t *cr, cairo_bool_t preserve)
28 cairo_path_data_t *data;
29 const double dash[] = {10, 10};
32 cairo_set_source_rgb (cr, 1.0, 0.0, 0.0);
34 line_width = cairo_get_line_width (cr);
35 path = cairo_copy_path (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);
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);
53 case CAIRO_PATH_CLOSE_PATH:
56 g_assert_not_reached ();
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);
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);
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);
83 case CAIRO_PATH_CLOSE_PATH:
84 cairo_rel_line_to (cr, 0, 0);
87 g_assert_not_reached ();
90 cairo_rel_line_to (cr, 0, 0);
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);
100 case CAIRO_PATH_LINE_TO:
101 cairo_line_to (cr, data[1].point.x, data[1].point.y);
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);
108 case CAIRO_PATH_CLOSE_PATH:
109 cairo_close_path (cr);
112 g_assert_not_reached ();
118 cairo_append_path (cr, path);
120 cairo_path_destroy (path);
125 /* A fancy cairo_stroke() that draws points and control points, and
126 * connects them together.
129 fancy_cairo_stroke (cairo_t *cr)
131 _fancy_cairo_stroke (cr, FALSE);
134 /* A fancy cairo_stroke_preserve() that draws points and control
135 * points, and connects them together.
138 fancy_cairo_stroke_preserve (cairo_t *cr)
140 _fancy_cairo_stroke (cr, TRUE);
144 /* Returns Euclidean distance between two points */
146 two_points_distance (cairo_path_data_t *a, cairo_path_data_t *b)
150 dx = b->point.x - a->point.x;
151 dy = b->point.y - a->point.y;
153 return sqrt (dx * dx + dy * dy);
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
162 curve_length (double x0, double y0,
163 double x1, double y1,
164 double x2, double y2,
165 double x3, double y3)
167 cairo_surface_t *surface;
170 cairo_path_data_t *data, current_point;
174 surface = cairo_image_surface_create (CAIRO_FORMAT_A8, 0, 0);
175 cr = cairo_create (surface);
176 cairo_surface_destroy (surface);
178 cairo_move_to (cr, x0, y0);
179 cairo_curve_to (cr, x1, y1, x2, y2, x3, y3);
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) {
187 case CAIRO_PATH_MOVE_TO:
188 current_point = data[1];
191 case CAIRO_PATH_LINE_TO:
192 length += two_points_distance (¤t_point, &data[1]);
193 current_point = data[1];
197 case CAIRO_PATH_CURVE_TO:
198 case CAIRO_PATH_CLOSE_PATH:
199 g_assert_not_reached ();
202 cairo_path_destroy (path);
210 typedef double parametrization_t;
212 /* Compute parametrization info. That is, for each part of the
213 * cairo path, tags it with its length.
215 * Free returned value with g_free().
217 static parametrization_t *
218 parametrize_path (cairo_path_t *path)
221 cairo_path_data_t *data, last_move_to, current_point;
222 parametrization_t *parametrization;
224 parametrization = g_malloc (path->num_data * sizeof (parametrization[0]));
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];
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;
238 case CAIRO_PATH_LINE_TO:
239 parametrization[i] = two_points_distance (¤t_point, &data[1]);
240 current_point = data[1];
242 case CAIRO_PATH_CURVE_TO:
243 /* naive curve-length, treating bezier as three line segments:
244 parametrization[i] = two_points_distance (¤t_point, &data[1])
245 + two_points_distance (&data[1], &data[2])
246 + two_points_distance (&data[2], &data[3]);
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);
253 current_point = data[3];
256 g_assert_not_reached ();
260 return parametrization;
264 typedef void (*transform_point_func_t) (void *closure, double *x, double *y);
266 /* Project a path using a function. Each point of the path (including
267 * Bezier control points) is passed to the function for transformation.
270 transform_path (cairo_path_t *path, transform_point_func_t f, void *closure)
273 cairo_path_data_t *data;
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);
285 case CAIRO_PATH_CLOSE_PATH:
288 g_assert_not_reached ();
294 /* Simple struct to hold a path and its parametrization */
297 parametrization_t *parametrization;
298 } parametrized_path_t;
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:
306 * There's three pieces of math involved:
308 * - The parametric form of the Line equation
309 * http://en.wikipedia.org/wiki/Line
311 * - The parametric form of the Cubic Bézier curve equation
312 * http://en.wikipedia.org/wiki/B%C3%A9zier_curve
314 * - The Gradient (aka multi-dimensional derivative) of the above
315 * http://en.wikipedia.org/wiki/Gradient
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".
323 point_on_path (parametrized_path_t *param,
324 double *x, double *y)
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;
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];
343 case CAIRO_PATH_LINE_TO:
344 current_point = data[1];
346 case CAIRO_PATH_CURVE_TO:
347 current_point = data[3];
349 case CAIRO_PATH_CLOSE_PATH:
352 g_assert_not_reached ();
355 data = &path->data[i];
357 switch (data->header.type) {
359 case CAIRO_PATH_MOVE_TO:
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;
365 case CAIRO_PATH_LINE_TO:
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;
373 dx = -(current_point.point.x - data[1].point.x);
374 dy = -(current_point.point.y - data[1].point.y);
376 /*optimization for: ratio = the_y / sqrt (dx * dx + dy * dy);*/
377 ratio = the_y / parametrization[i];
382 case CAIRO_PATH_CURVE_TO:
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.
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;
396 ratio = the_x / parametrization[i];
399 ratio_0_1 = 1 - ratio;
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) */
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) */
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;
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;
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;
432 ratio = the_y / sqrt (dx * dx + dy * dy);
438 g_assert_not_reached ();
442 /* Projects the current path of cr onto the provided path. */
444 map_path_onto (cairo_t *cr, cairo_path_t *path)
446 cairo_path_t *current_path;
447 parametrized_path_t param;
450 param.parametrization = parametrize_path (path);
452 current_path = cairo_copy_path (cr);
455 transform_path (current_path,
456 (transform_point_func_t) point_on_path, ¶m);
458 cairo_append_path (cr, current_path);
460 cairo_path_destroy (current_path);
461 g_free (param.parametrization);
465 typedef void (*draw_path_func_t) (cairo_t *cr);
468 draw_text (cairo_t *cr,
475 PangoLayoutLine *line;
476 PangoFontDescription *desc;
477 cairo_font_options_t *font_options;
479 font_options = cairo_font_options_create ();
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);
484 cairo_set_font_options (cr, font_options);
485 cairo_font_options_destroy (font_options);
487 layout = pango_cairo_create_layout (cr);
489 desc = pango_font_description_from_string (font);
490 pango_layout_set_font_description (layout, desc);
491 pango_font_description_free (desc);
493 pango_layout_set_text (layout, text, -1);
495 /* Use pango_layout_get_line() instead of pango_layout_get_line_readonly()
496 * for older versions of pango
498 line = pango_layout_get_line_readonly (layout, 0);
500 cairo_move_to (cr, x, y);
501 pango_cairo_layout_line_path (cr, line);
503 g_object_unref (layout);
507 draw_twisted (cairo_t *cr,
517 /* Decrease tolerance a bit, since it's going to be magnified */
518 cairo_set_tolerance (cr, 0.01);
520 /* Using cairo_copy_path() here shows our deficiency in handling
521 * Bezier curves, specially around sharper curves.
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
528 path = cairo_copy_path_flat (cr);
529 /*path = cairo_copy_path (cr);*/
533 draw_text (cr, x, y, font, text);
534 map_path_onto (cr, path);
536 cairo_path_destroy (path);
538 cairo_fill_preserve (cr);
541 cairo_set_source_rgb (cr, 0.1, 0.1, 0.1);
549 draw_dream (cairo_t *cr)
551 cairo_move_to (cr, 50, 650);
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);
557 cairo_set_line_width (cr, 1.5);
558 cairo_set_source_rgba (cr, 0.3, 0.3, 1.0, 0.3);
560 fancy_cairo_stroke_preserve (cr);
565 "It was a dream... Oh Just a dream...");
569 draw_wow (cairo_t *cr)
571 cairo_move_to (cr, 400, 780);
573 cairo_rel_curve_to (cr, 50, -50, 150, -50, 200, 0);
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);
579 fancy_cairo_stroke_preserve (cr);
587 int main (int argc, char **argv)
591 cairo_status_t status;
592 cairo_surface_t *surface;
596 g_printerr ("Usage: cairotwisted OUTPUT_FILENAME\n");
602 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
604 cr = cairo_create (surface);
606 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
614 status = cairo_surface_write_to_png (surface, filename);
615 cairo_surface_destroy (surface);
617 if (status != CAIRO_STATUS_SUCCESS)
619 g_printerr ("Could not save png to '%s'\n", filename);