1/* Pango
2 * pangocairo-render.c: Rendering routines to Cairo surfaces
3 *
4 * Copyright (C) 2004 Red Hat, Inc.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
20 */
21
22#include "config.h"
23
24#include <math.h>
25
26#include "pango-font-private.h"
27#include "pangocairo-private.h"
28#include "pango-glyph-item.h"
29#include "pango-impl-utils.h"
30
31typedef struct _PangoCairoRendererClass PangoCairoRendererClass;
32
33#define PANGO_CAIRO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PANGO_TYPE_CAIRO_RENDERER, PangoCairoRendererClass))
34#define PANGO_IS_CAIRO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PANGO_TYPE_CAIRO_RENDERER))
35#define PANGO_CAIRO_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PANGO_TYPE_CAIRO_RENDERER, PangoCairoRendererClass))
36
37struct _PangoCairoRenderer
38{
39 PangoRenderer parent_instance;
40
41 cairo_t *cr;
42 gboolean do_path;
43 gboolean has_show_text_glyphs;
44 double x_offset, y_offset;
45
46 /* house-keeping options */
47 gboolean is_cached_renderer;
48 gboolean cr_had_current_point;
49};
50
51struct _PangoCairoRendererClass
52{
53 PangoRendererClass parent_class;
54};
55
56G_DEFINE_TYPE (PangoCairoRenderer, pango_cairo_renderer, PANGO_TYPE_RENDERER)
57
58static void
59set_color (PangoCairoRenderer *crenderer,
60 PangoRenderPart part)
61{
62 PangoColor *color = pango_renderer_get_color (renderer: (PangoRenderer *) (crenderer), part);
63 guint16 a = pango_renderer_get_alpha (renderer: (PangoRenderer *) (crenderer), part);
64 gdouble red, green, blue, alpha;
65
66 if (!a && !color)
67 return;
68
69 if (color)
70 {
71 red = color->red / 65535.;
72 green = color->green / 65535.;
73 blue = color->blue / 65535.;
74 alpha = 1.;
75 }
76 else
77 {
78 cairo_pattern_t *pattern = cairo_get_source (cr: crenderer->cr);
79
80 if (pattern && cairo_pattern_get_type (pattern) == CAIRO_PATTERN_TYPE_SOLID)
81 cairo_pattern_get_rgba (pattern, red: &red, green: &green, blue: &blue, alpha: &alpha);
82 else
83 {
84 red = 0.;
85 green = 0.;
86 blue = 0.;
87 alpha = 1.;
88 }
89 }
90
91 if (a)
92 alpha = a / 65535.;
93
94 cairo_set_source_rgba (cr: crenderer->cr, red, green, blue, alpha);
95}
96
97/* note: modifies crenderer->cr without doing cairo_save/restore() */
98static void
99_pango_cairo_renderer_draw_frame (PangoCairoRenderer *crenderer,
100 double x,
101 double y,
102 double width,
103 double height,
104 double line_width,
105 gboolean invalid)
106{
107 cairo_t *cr = crenderer->cr;
108
109 if (crenderer->do_path)
110 {
111 double d2 = line_width * .5, d = line_width;
112
113 /* we draw an outer box in one winding direction and an inner one in the
114 * opposite direction. This works for both cairo windings rules.
115 *
116 * what we really want is cairo_stroke_to_path(), but that's not
117 * implemented in cairo yet.
118 */
119
120 /* outer */
121 cairo_rectangle (cr, x: x-d2, y: y-d2, width: width+d, height: height+d);
122
123 /* inner */
124 if (invalid)
125 {
126 /* delicacies of computing the joint... this is REALLY slow */
127
128 double alpha, tan_alpha2, cos_alpha;
129 double sx, sy;
130
131 alpha = atan2 (y: height, x: width);
132
133 tan_alpha2 = tan (x: alpha * .5);
134 if (tan_alpha2 < 1e-5 || (sx = d2 / tan_alpha2, 2. * sx > width - d))
135 sx = (width - d) * .5;
136
137 cos_alpha = cos (x: alpha);
138 if (cos_alpha < 1e-5 || (sy = d2 / cos_alpha, 2. * sy > height - d))
139 sy = (height - d) * .5;
140
141 /* top triangle */
142 cairo_new_sub_path (cr);
143 cairo_line_to (cr, x: x+width-sx, y: y+d2);
144 cairo_line_to (cr, x: x+sx, y: y+d2);
145 cairo_line_to (cr, x: x+.5*width, y: y+.5*height-sy);
146 cairo_close_path (cr);
147
148 /* bottom triangle */
149 cairo_new_sub_path (cr);
150 cairo_line_to (cr, x: x+width-sx, y: y+height-d2);
151 cairo_line_to (cr, x: x+.5*width, y: y+.5*height+sy);
152 cairo_line_to (cr, x: x+sx, y: y+height-d2);
153 cairo_close_path (cr);
154
155
156 alpha = G_PI_2 - alpha;
157 tan_alpha2 = tan (x: alpha * .5);
158 if (tan_alpha2 < 1e-5 || (sy = d2 / tan_alpha2, 2. * sy > height - d))
159 sy = (height - d) * .5;
160
161 cos_alpha = cos (x: alpha);
162 if (cos_alpha < 1e-5 || (sx = d2 / cos_alpha, 2. * sx > width - d))
163 sx = (width - d) * .5;
164
165 /* left triangle */
166 cairo_new_sub_path (cr);
167 cairo_line_to (cr, x: x+d2, y: y+sy);
168 cairo_line_to (cr, x: x+d2, y: y+height-sy);
169 cairo_line_to (cr, x: x+.5*width-sx, y: y+.5*height);
170 cairo_close_path (cr);
171
172 /* right triangle */
173 cairo_new_sub_path (cr);
174 cairo_line_to (cr, x: x+width-d2, y: y+sy);
175 cairo_line_to (cr, x: x+.5*width+sx, y: y+.5*height);
176 cairo_line_to (cr, x: x+width-d2, y: y+height-sy);
177 cairo_close_path (cr);
178 }
179 else
180 cairo_rectangle (cr, x: x+width-d2, y: y+d2, width: - (width-d), height: height-d);
181 }
182 else
183 {
184 cairo_rectangle (cr, x, y, width, height);
185
186 if (invalid)
187 {
188 /* draw an X */
189
190 cairo_new_sub_path (cr);
191 cairo_move_to (cr, x, y);
192 cairo_rel_line_to (cr, dx: width, dy: height);
193
194 cairo_new_sub_path (cr);
195 cairo_move_to (cr, x: x + width, y);
196 cairo_rel_line_to (cr, dx: -width, dy: height);
197
198 cairo_set_line_cap (cr, line_cap: CAIRO_LINE_CAP_BUTT);
199 }
200
201 cairo_set_line_width (cr, width: line_width);
202 cairo_set_line_join (cr, line_join: CAIRO_LINE_JOIN_MITER);
203 cairo_set_miter_limit (cr, limit: 2.);
204 cairo_stroke (cr);
205 }
206}
207
208static void
209_pango_cairo_renderer_draw_box_glyph (PangoCairoRenderer *crenderer,
210 PangoGlyphInfo *gi,
211 double cx,
212 double cy,
213 gboolean invalid)
214{
215 cairo_save (cr: crenderer->cr);
216
217 _pango_cairo_renderer_draw_frame (crenderer,
218 x: cx + 1.5,
219 y: cy + 1.5 - PANGO_UNKNOWN_GLYPH_HEIGHT,
220 width: (double)gi->geometry.width / PANGO_SCALE - 3.0,
221 PANGO_UNKNOWN_GLYPH_HEIGHT - 3.0,
222 line_width: 1.0,
223 invalid);
224
225 cairo_restore (cr: crenderer->cr);
226}
227
228static void
229_pango_cairo_renderer_draw_unknown_glyph (PangoCairoRenderer *crenderer,
230 PangoFont *font,
231 PangoGlyphInfo *gi,
232 double cx,
233 double cy)
234{
235 char buf[7];
236 double x0, y0;
237 int row, col;
238 int rows, cols;
239 double width, lsb;
240 char hexbox_string[2] = { 0, 0 };
241 PangoCairoFontHexBoxInfo *hbi;
242 gunichar ch;
243 gboolean invalid_input;
244 const char *p;
245 const char *name;
246
247 cairo_save (cr: crenderer->cr);
248
249 ch = gi->glyph & ~PANGO_GLYPH_UNKNOWN_FLAG;
250 invalid_input = G_UNLIKELY (gi->glyph == PANGO_GLYPH_INVALID_INPUT || ch > 0x10FFFF);
251
252 hbi = _pango_cairo_font_get_hex_box_info (cfont: (PangoCairoFont *)font);
253 if (!hbi || !_pango_cairo_font_install (font: (PangoFont *)(hbi->font), cr: crenderer->cr))
254 {
255 _pango_cairo_renderer_draw_box_glyph (crenderer, gi, cx, cy, invalid: invalid_input);
256 goto done;
257 }
258
259 if (G_UNLIKELY (invalid_input))
260 {
261 rows = hbi->rows;
262 cols = 1;
263 }
264 else if (ch == 0x2423 ||
265 g_unichar_type (c: ch) == G_UNICODE_SPACE_SEPARATOR)
266 {
267 /* We never want to show a hex box or other drawing for
268 * space. If we want space to be visible, we replace 0x20
269 * by 0x2423 (visible space).
270 *
271 * Since we don't want to rely on glyph availability,
272 * we render a centered dot ourselves.
273 */
274 double x = cx + 0.5 *((double)gi->geometry.width / PANGO_SCALE);
275 double y = cy + hbi->box_descent - 0.5 * hbi->box_height;
276
277 cairo_new_sub_path (cr: crenderer->cr);
278 cairo_arc (cr: crenderer->cr, xc: x, yc: y, radius: 1.5 * hbi->line_width, angle1: 0, angle2: 2 * G_PI);
279 cairo_close_path (cr: crenderer->cr);
280 cairo_fill (cr: crenderer->cr);
281 goto done;
282 }
283 else if (ch == '\t')
284 {
285 /* Since we don't want to rely on glyph availability,
286 * we render an arrow like ↦ ourselves.
287 */
288 double y = cy + hbi->box_descent - 0.5 * hbi->box_height;
289 double width = (double)gi->geometry.width / PANGO_SCALE;
290 double offset = 0.2 * width;
291 double x = cx + offset;
292 double al = width - 2 * offset; /* arrow length */
293 double tl = MIN (hbi->digit_width, 0.75 * al); /* tip length */
294 double tw2 = 2.5 * hbi->line_width; /* tip width / 2 */
295 double lw2 = 0.5 * hbi->line_width; /* line width / 2 */
296
297 cairo_move_to (cr: crenderer->cr, x: x - lw2, y: y - tw2);
298 cairo_line_to (cr: crenderer->cr, x: x + lw2, y: y - tw2);
299 cairo_line_to (cr: crenderer->cr, x: x + lw2, y: y - lw2);
300 cairo_line_to (cr: crenderer->cr, x: x + al - tl, y: y - lw2);
301 cairo_line_to (cr: crenderer->cr, x: x + al - tl, y: y - tw2);
302 cairo_line_to (cr: crenderer->cr, x: x + al, y);
303 cairo_line_to (cr: crenderer->cr, x: x + al - tl, y: y + tw2);
304 cairo_line_to (cr: crenderer->cr, x: x + al - tl, y: y + lw2);
305 cairo_line_to (cr: crenderer->cr, x: x + lw2, y: y + lw2);
306 cairo_line_to (cr: crenderer->cr, x: x + lw2, y: y + tw2);
307 cairo_line_to (cr: crenderer->cr, x: x - lw2, y: y + tw2);
308 cairo_close_path (cr: crenderer->cr);
309 cairo_fill (cr: crenderer->cr);
310 goto done;
311 }
312 else if (ch == '\n' || ch == 0x2028 || ch == 0x2029)
313 {
314 /* Since we don't want to rely on glyph availability,
315 * we render an arrow like ↵ ourselves.
316 */
317 double width = (double)gi->geometry.width / PANGO_SCALE;
318 double offset = 0.2 * width;
319 double al = width - 2 * offset; /* arrow length */
320 double tl = MIN (hbi->digit_width, 0.75 * al); /* tip length */
321 double ah = al - 0.5 * tl; /* arrow height */
322 double tw2 = 2.5 * hbi->line_width; /* tip width / 2 */
323 double x = cx + offset;
324 double y = cy - (hbi->box_height - al) / 2;
325 double lw2 = 0.5 * hbi->line_width; /* line width / 2 */
326
327 cairo_move_to (cr: crenderer->cr, x, y);
328 cairo_line_to (cr: crenderer->cr, x: x + tl, y: y - tw2);
329 cairo_line_to (cr: crenderer->cr, x: x + tl, y: y - lw2);
330 cairo_line_to (cr: crenderer->cr, x: x + al - lw2, y: y - lw2);
331 cairo_line_to (cr: crenderer->cr, x: x + al - lw2, y: y - ah);
332 cairo_line_to (cr: crenderer->cr, x: x + al + lw2, y: y - ah);
333 cairo_line_to (cr: crenderer->cr, x: x + al + lw2, y: y + lw2);
334 cairo_line_to (cr: crenderer->cr, x: x + tl, y: y + lw2);
335 cairo_line_to (cr: crenderer->cr, x: x + tl, y: y + tw2);
336 cairo_close_path (cr: crenderer->cr);
337 cairo_fill (cr: crenderer->cr);
338 goto done;
339 }
340 else if ((name = pango_get_ignorable_size (ch, rows: &rows, cols: &cols)))
341 {
342 /* Nothing else to do, we render 'default ignorable' chars
343 * as hex box with their nick.
344 */
345 }
346 else
347 {
348 /* Everything else gets a traditional hex box. */
349 rows = hbi->rows;
350 cols = (ch > 0xffff ? 6 : 4) / rows;
351 g_snprintf (string: buf, n: sizeof(buf), format: (ch > 0xffff) ? "%06X" : "%04X", ch);
352 name = buf;
353 }
354
355 width = (3 * hbi->pad_x + cols * (hbi->digit_width + hbi->pad_x));
356 lsb = ((double)gi->geometry.width / PANGO_SCALE - width) * .5;
357 lsb = floor (x: lsb / hbi->pad_x) * hbi->pad_x;
358
359 _pango_cairo_renderer_draw_frame (crenderer,
360 x: cx + lsb + .5 * hbi->pad_x,
361 y: cy + hbi->box_descent - hbi->box_height + hbi->pad_y * 0.5,
362 width: width - hbi->pad_x,
363 height: (hbi->box_height - hbi->pad_y),
364 line_width: hbi->line_width,
365 invalid: invalid_input);
366
367 if (invalid_input)
368 goto done;
369
370 x0 = cx + lsb + hbi->pad_x * 2;
371 y0 = cy + hbi->box_descent - hbi->pad_y * 2 - ((hbi->rows - rows) * hbi->digit_height / 2);
372
373 for (row = 0, p = name; row < rows; row++)
374 {
375 double y = y0 - (rows - 1 - row) * (hbi->digit_height + hbi->pad_y);
376 for (col = 0; col < cols; col++, p++)
377 {
378 double x = x0 + col * (hbi->digit_width + hbi->pad_x);
379
380 if (!p)
381 goto done;
382
383 cairo_move_to (cr: crenderer->cr, x, y);
384
385 hexbox_string[0] = p[0];
386
387 if (crenderer->do_path)
388 cairo_text_path (cr: crenderer->cr, utf8: hexbox_string);
389 else
390 cairo_show_text (cr: crenderer->cr, utf8: hexbox_string);
391 }
392 }
393
394done:
395 cairo_restore (cr: crenderer->cr);
396}
397
398#ifndef STACK_BUFFER_SIZE
399#define STACK_BUFFER_SIZE (512 * sizeof (int))
400#endif
401
402#define STACK_ARRAY_LENGTH(T) (STACK_BUFFER_SIZE / sizeof(T))
403
404static void
405pango_cairo_renderer_show_text_glyphs (PangoRenderer *renderer,
406 const char *text,
407 int text_len,
408 PangoGlyphString *glyphs,
409 cairo_text_cluster_t *clusters,
410 int num_clusters,
411 gboolean backward,
412 PangoFont *font,
413 int x,
414 int y)
415{
416 PangoCairoRenderer *crenderer = (PangoCairoRenderer *) (renderer);
417
418 int i, count;
419 int x_position = 0;
420 cairo_glyph_t *cairo_glyphs;
421 cairo_glyph_t stack_glyphs[STACK_ARRAY_LENGTH (cairo_glyph_t)];
422 double base_x = crenderer->x_offset + (double)x / PANGO_SCALE;
423 double base_y = crenderer->y_offset + (double)y / PANGO_SCALE;
424
425 cairo_save (cr: crenderer->cr);
426 if (!crenderer->do_path)
427 set_color (crenderer, part: PANGO_RENDER_PART_FOREGROUND);
428
429 if (!_pango_cairo_font_install (font, cr: crenderer->cr))
430 {
431 for (i = 0; i < glyphs->num_glyphs; i++)
432 {
433 PangoGlyphInfo *gi = &glyphs->glyphs[i];
434
435 if (gi->glyph != PANGO_GLYPH_EMPTY)
436 {
437 double cx = base_x + (double)(x_position + gi->geometry.x_offset) / PANGO_SCALE;
438 double cy = gi->geometry.y_offset == 0 ?
439 base_y :
440 base_y + (double)(gi->geometry.y_offset) / PANGO_SCALE;
441
442 _pango_cairo_renderer_draw_unknown_glyph (crenderer, font, gi, cx, cy);
443 }
444 x_position += gi->geometry.width;
445 }
446
447 goto done;
448 }
449
450 if (glyphs->num_glyphs > (int) G_N_ELEMENTS (stack_glyphs))
451 cairo_glyphs = g_new (cairo_glyph_t, glyphs->num_glyphs);
452 else
453 cairo_glyphs = stack_glyphs;
454
455 count = 0;
456 for (i = 0; i < glyphs->num_glyphs; i++)
457 {
458 PangoGlyphInfo *gi = &glyphs->glyphs[i];
459
460 if (gi->glyph != PANGO_GLYPH_EMPTY)
461 {
462 double cx = base_x + (double)(x_position + gi->geometry.x_offset) / PANGO_SCALE;
463 double cy = gi->geometry.y_offset == 0 ?
464 base_y :
465 base_y + (double)(gi->geometry.y_offset) / PANGO_SCALE;
466
467 if (gi->glyph & PANGO_GLYPH_UNKNOWN_FLAG)
468 {
469 if (gi->glyph == (0x20 | PANGO_GLYPH_UNKNOWN_FLAG))
470 ; /* no hex boxes for space, please */
471 else
472 _pango_cairo_renderer_draw_unknown_glyph (crenderer, font, gi, cx, cy);
473 }
474 else
475 {
476 cairo_glyphs[count].index = gi->glyph;
477 cairo_glyphs[count].x = cx;
478 cairo_glyphs[count].y = cy;
479 count++;
480 }
481 }
482 x_position += gi->geometry.width;
483 }
484
485 if (G_UNLIKELY (crenderer->do_path))
486 cairo_glyph_path (cr: crenderer->cr, glyphs: cairo_glyphs, num_glyphs: count);
487 else
488 if (G_UNLIKELY (clusters))
489 cairo_show_text_glyphs (cr: crenderer->cr,
490 utf8: text, utf8_len: text_len,
491 glyphs: cairo_glyphs, num_glyphs: count,
492 clusters, num_clusters,
493 cluster_flags: backward ? CAIRO_TEXT_CLUSTER_FLAG_BACKWARD : 0);
494 else
495 cairo_show_glyphs (cr: crenderer->cr, glyphs: cairo_glyphs, num_glyphs: count);
496
497 if (cairo_glyphs != stack_glyphs)
498 g_free (mem: cairo_glyphs);
499
500done:
501 cairo_restore (cr: crenderer->cr);
502}
503
504static void
505pango_cairo_renderer_draw_glyphs (PangoRenderer *renderer,
506 PangoFont *font,
507 PangoGlyphString *glyphs,
508 int x,
509 int y)
510{
511 pango_cairo_renderer_show_text_glyphs (renderer,
512 NULL, text_len: 0,
513 glyphs,
514 NULL, num_clusters: 0,
515 FALSE,
516 font,
517 x, y);
518}
519
520static void
521pango_cairo_renderer_draw_glyph_item (PangoRenderer *renderer,
522 const char *text,
523 PangoGlyphItem *glyph_item,
524 int x,
525 int y)
526{
527 PangoCairoRenderer *crenderer = (PangoCairoRenderer *) (renderer);
528 PangoFont *font = glyph_item->item->analysis.font;
529 PangoGlyphString *glyphs = glyph_item->glyphs;
530 PangoItem *item = glyph_item->item;
531 gboolean backward = (item->analysis.level & 1) != 0;
532
533 PangoGlyphItemIter iter;
534 cairo_text_cluster_t *cairo_clusters;
535 cairo_text_cluster_t stack_clusters[STACK_ARRAY_LENGTH (cairo_text_cluster_t)];
536 int num_clusters;
537
538 if (!crenderer->has_show_text_glyphs || crenderer->do_path)
539 {
540 pango_cairo_renderer_show_text_glyphs (renderer,
541 NULL, text_len: 0,
542 glyphs,
543 NULL, num_clusters: 0,
544 FALSE,
545 font,
546 x, y);
547 return;
548 }
549
550 if (glyphs->num_glyphs > (int) G_N_ELEMENTS (stack_clusters))
551 cairo_clusters = g_new (cairo_text_cluster_t, glyphs->num_glyphs);
552 else
553 cairo_clusters = stack_clusters;
554
555 num_clusters = 0;
556 if (pango_glyph_item_iter_init_start (iter: &iter, glyph_item, text))
557 {
558 do {
559 int num_bytes, num_glyphs, i;
560
561 num_bytes = iter.end_index - iter.start_index;
562 num_glyphs = backward ? iter.start_glyph - iter.end_glyph : iter.end_glyph - iter.start_glyph;
563
564 if (num_bytes < 1)
565 g_warning ("pango_cairo_renderer_draw_glyph_item: bad cluster has num_bytes %d", num_bytes);
566 if (num_glyphs < 1)
567 g_warning ("pango_cairo_renderer_draw_glyph_item: bad cluster has num_glyphs %d", num_glyphs);
568
569 /* Discount empty and unknown glyphs */
570 for (i = MIN (iter.start_glyph, iter.end_glyph+1);
571 i < MAX (iter.start_glyph+1, iter.end_glyph);
572 i++)
573 {
574 PangoGlyphInfo *gi = &glyphs->glyphs[i];
575
576 if (gi->glyph == PANGO_GLYPH_EMPTY ||
577 gi->glyph & PANGO_GLYPH_UNKNOWN_FLAG)
578 num_glyphs--;
579 }
580
581 cairo_clusters[num_clusters].num_bytes = num_bytes;
582 cairo_clusters[num_clusters].num_glyphs = num_glyphs;
583 num_clusters++;
584 } while (pango_glyph_item_iter_next_cluster (iter: &iter));
585 }
586
587 pango_cairo_renderer_show_text_glyphs (renderer,
588 text: text + item->offset, text_len: item->length,
589 glyphs,
590 clusters: cairo_clusters, num_clusters,
591 backward,
592 font,
593 x, y);
594
595 if (cairo_clusters != stack_clusters)
596 g_free (mem: cairo_clusters);
597}
598
599static void
600pango_cairo_renderer_draw_rectangle (PangoRenderer *renderer,
601 PangoRenderPart part,
602 int x,
603 int y,
604 int width,
605 int height)
606{
607 PangoCairoRenderer *crenderer = (PangoCairoRenderer *) (renderer);
608
609 if (!crenderer->do_path)
610 {
611 cairo_save (cr: crenderer->cr);
612
613 set_color (crenderer, part);
614 }
615
616 cairo_rectangle (cr: crenderer->cr,
617 x: crenderer->x_offset + (double)x / PANGO_SCALE,
618 y: crenderer->y_offset + (double)y / PANGO_SCALE,
619 width: (double)width / PANGO_SCALE, height: (double)height / PANGO_SCALE);
620
621 if (!crenderer->do_path)
622 {
623 cairo_fill (cr: crenderer->cr);
624
625 cairo_restore (cr: crenderer->cr);
626 }
627}
628
629static void
630pango_cairo_renderer_draw_trapezoid (PangoRenderer *renderer,
631 PangoRenderPart part,
632 double y1_,
633 double x11,
634 double x21,
635 double y2,
636 double x12,
637 double x22)
638{
639 PangoCairoRenderer *crenderer = (PangoCairoRenderer *) (renderer);
640 cairo_t *cr;
641 double x, y;
642
643 cr = crenderer->cr;
644
645 cairo_save (cr);
646
647 if (!crenderer->do_path)
648 set_color (crenderer, part);
649
650 x = crenderer->x_offset,
651 y = crenderer->y_offset;
652 cairo_user_to_device_distance (cr, dx: &x, dy: &y);
653 cairo_identity_matrix (cr);
654 cairo_translate (cr, tx: x, ty: y);
655
656 cairo_move_to (cr, x: x11, y: y1_);
657 cairo_line_to (cr, x: x21, y: y1_);
658 cairo_line_to (cr, x: x22, y: y2);
659 cairo_line_to (cr, x: x12, y: y2);
660 cairo_close_path (cr);
661
662 if (!crenderer->do_path)
663 cairo_fill (cr);
664
665 cairo_restore (cr);
666}
667
668/* Draws an error underline that looks like one of:
669 * H E H
670 * /\ /\ /\ /\ /\ -
671 * A/ \ / \ / \ A/ \ / \ |
672 * \ \ / \ / /D \ \ / \ |
673 * \ \/ C \/ / \ \/ C \ | height = HEIGHT_SQUARES * square
674 * \ /\ F / \ F /\ \ |
675 * \ / \ / \ / \ \G |
676 * \ / \ / \ / \ / |
677 * \/ \/ \/ \/ -
678 * B B
679 * |---|
680 * unit_width = (HEIGHT_SQUARES - 1) * square
681 *
682 * The x, y, width, height passed in give the desired bounding box;
683 * x/width are adjusted to make the underline a integer number of units
684 * wide.
685 */
686#define HEIGHT_SQUARES 2.5
687
688static void
689draw_error_underline (cairo_t *cr,
690 double x,
691 double y,
692 double width,
693 double height)
694{
695 double square = height / HEIGHT_SQUARES;
696 double unit_width = (HEIGHT_SQUARES - 1) * square;
697 double double_width = 2 * unit_width;
698 int width_units = (width + unit_width / 2) / unit_width;
699 double y_top, y_bottom;
700 double x_left, x_middle, x_right;
701 int i;
702
703 x += (width - width_units * unit_width) / 2;
704
705 y_top = y;
706 y_bottom = y + height;
707
708 /* Bottom of squiggle */
709 x_middle = x + unit_width;
710 x_right = x + double_width;
711 cairo_move_to (cr, x: x - square / 2, y: y_top + square / 2); /* A */
712 for (i = 0; i < width_units-2; i += 2)
713 {
714 cairo_line_to (cr, x: x_middle, y: y_bottom); /* B */
715 cairo_line_to (cr, x: x_right, y: y_top + square); /* C */
716
717 x_middle += double_width;
718 x_right += double_width;
719 }
720 cairo_line_to (cr, x: x_middle, y: y_bottom); /* B */
721
722 if (i + 1 == width_units)
723 cairo_line_to (cr, x: x_middle + square / 2, y: y_bottom - square / 2); /* G */
724 else if (i + 2 == width_units) {
725 cairo_line_to (cr, x: x_right + square / 2, y: y_top + square / 2); /* D */
726 cairo_line_to (cr, x: x_right, y: y_top); /* E */
727 }
728
729 /* Top of squiggle */
730 x_left = x_middle - unit_width;
731 for (; i >= 0; i -= 2)
732 {
733 cairo_line_to (cr, x: x_middle, y: y_bottom - square); /* F */
734 cairo_line_to (cr, x: x_left, y: y_top); /* H */
735
736 x_left -= double_width;
737 x_middle -= double_width;
738 }
739}
740
741static void
742pango_cairo_renderer_draw_error_underline (PangoRenderer *renderer,
743 int x,
744 int y,
745 int width,
746 int height)
747{
748 PangoCairoRenderer *crenderer = (PangoCairoRenderer *) (renderer);
749 cairo_t *cr = crenderer->cr;
750
751 if (!crenderer->do_path)
752 {
753 cairo_save (cr);
754
755 set_color (crenderer, part: PANGO_RENDER_PART_UNDERLINE);
756
757 cairo_new_path (cr);
758 }
759
760 draw_error_underline (cr,
761 x: crenderer->x_offset + (double)x / PANGO_SCALE,
762 y: crenderer->y_offset + (double)y / PANGO_SCALE,
763 width: (double)width / PANGO_SCALE, height: (double)height / PANGO_SCALE);
764
765 if (!crenderer->do_path)
766 {
767 cairo_fill (cr);
768
769 cairo_restore (cr);
770 }
771}
772
773static void
774pango_cairo_renderer_draw_shape (PangoRenderer *renderer,
775 PangoAttrShape *attr,
776 int x,
777 int y)
778{
779 PangoCairoRenderer *crenderer = (PangoCairoRenderer *) (renderer);
780 cairo_t *cr = crenderer->cr;
781 PangoLayout *layout;
782 PangoCairoShapeRendererFunc shape_renderer;
783 gpointer shape_renderer_data;
784 double base_x, base_y;
785
786 layout = pango_renderer_get_layout (renderer);
787
788 if (!layout)
789 return;
790
791 shape_renderer = pango_cairo_context_get_shape_renderer (context: pango_layout_get_context (layout),
792 data: &shape_renderer_data);
793
794 if (!shape_renderer)
795 return;
796
797 base_x = crenderer->x_offset + (double)x / PANGO_SCALE;
798 base_y = crenderer->y_offset + (double)y / PANGO_SCALE;
799
800 cairo_save (cr);
801 if (!crenderer->do_path)
802 set_color (crenderer, part: PANGO_RENDER_PART_FOREGROUND);
803
804 cairo_move_to (cr, x: base_x, y: base_y);
805
806 shape_renderer (cr, attr, crenderer->do_path, shape_renderer_data);
807
808 cairo_restore (cr);
809}
810
811static void
812pango_cairo_renderer_init (PangoCairoRenderer *renderer G_GNUC_UNUSED)
813{
814}
815
816static void
817pango_cairo_renderer_class_init (PangoCairoRendererClass *klass)
818{
819 PangoRendererClass *renderer_class = PANGO_RENDERER_CLASS (klass);
820
821 renderer_class->draw_glyphs = pango_cairo_renderer_draw_glyphs;
822 renderer_class->draw_glyph_item = pango_cairo_renderer_draw_glyph_item;
823 renderer_class->draw_rectangle = pango_cairo_renderer_draw_rectangle;
824 renderer_class->draw_trapezoid = pango_cairo_renderer_draw_trapezoid;
825 renderer_class->draw_error_underline = pango_cairo_renderer_draw_error_underline;
826 renderer_class->draw_shape = pango_cairo_renderer_draw_shape;
827}
828
829static PangoCairoRenderer *cached_renderer = NULL; /* MT-safe */
830G_LOCK_DEFINE_STATIC (cached_renderer);
831
832static PangoCairoRenderer *
833acquire_renderer (void)
834{
835 PangoCairoRenderer *renderer;
836
837 if (G_LIKELY (G_TRYLOCK (cached_renderer)))
838 {
839 if (G_UNLIKELY (!cached_renderer))
840 {
841 cached_renderer = g_object_new (PANGO_TYPE_CAIRO_RENDERER, NULL);
842 cached_renderer->is_cached_renderer = TRUE;
843 }
844
845 renderer = cached_renderer;
846 }
847 else
848 {
849 renderer = g_object_new (PANGO_TYPE_CAIRO_RENDERER, NULL);
850 }
851
852 return renderer;
853}
854
855static void
856release_renderer (PangoCairoRenderer *renderer)
857{
858 if (G_LIKELY (renderer->is_cached_renderer))
859 {
860 renderer->cr = NULL;
861 renderer->do_path = FALSE;
862 renderer->has_show_text_glyphs = FALSE;
863 renderer->x_offset = 0.;
864 renderer->y_offset = 0.;
865
866 G_UNLOCK (cached_renderer);
867 }
868 else
869 g_object_unref (object: renderer);
870}
871
872static void
873save_current_point (PangoCairoRenderer *renderer)
874{
875 renderer->cr_had_current_point = cairo_has_current_point (cr: renderer->cr);
876 cairo_get_current_point (cr: renderer->cr, x: &renderer->x_offset, y: &renderer->y_offset);
877
878 /* abuse save_current_point() to cache cairo_has_show_text_glyphs() result */
879 renderer->has_show_text_glyphs = cairo_surface_has_show_text_glyphs (surface: cairo_get_target (cr: renderer->cr));
880}
881
882static void
883restore_current_point (PangoCairoRenderer *renderer)
884{
885 if (renderer->cr_had_current_point)
886 /* XXX should do cairo_set_current_point() when we have that function */
887 cairo_move_to (cr: renderer->cr, x: renderer->x_offset, y: renderer->y_offset);
888 else
889 cairo_new_sub_path (cr: renderer->cr);
890}
891
892
893/* convenience wrappers using the default renderer */
894
895
896static void
897_pango_cairo_do_glyph_string (cairo_t *cr,
898 PangoFont *font,
899 PangoGlyphString *glyphs,
900 gboolean do_path)
901{
902 PangoCairoRenderer *crenderer = acquire_renderer ();
903 PangoRenderer *renderer = (PangoRenderer *) crenderer;
904
905 crenderer->cr = cr;
906 crenderer->do_path = do_path;
907 save_current_point (renderer: crenderer);
908
909 if (!do_path)
910 {
911 /* unset all part colors, since when drawing just a glyph string,
912 * prepare_run() isn't called.
913 */
914
915 pango_renderer_activate (renderer);
916
917 pango_renderer_set_color (renderer, part: PANGO_RENDER_PART_FOREGROUND, NULL);
918 pango_renderer_set_color (renderer, part: PANGO_RENDER_PART_BACKGROUND, NULL);
919 pango_renderer_set_color (renderer, part: PANGO_RENDER_PART_UNDERLINE, NULL);
920 pango_renderer_set_color (renderer, part: PANGO_RENDER_PART_STRIKETHROUGH, NULL);
921 pango_renderer_set_color (renderer, part: PANGO_RENDER_PART_OVERLINE, NULL);
922 }
923
924 pango_renderer_draw_glyphs (renderer, font, glyphs, x: 0, y: 0);
925
926 if (!do_path)
927 {
928 pango_renderer_deactivate (renderer);
929 }
930
931 restore_current_point (renderer: crenderer);
932
933 release_renderer (renderer: crenderer);
934}
935
936static void
937_pango_cairo_do_glyph_item (cairo_t *cr,
938 const char *text,
939 PangoGlyphItem *glyph_item,
940 gboolean do_path)
941{
942 PangoCairoRenderer *crenderer = acquire_renderer ();
943 PangoRenderer *renderer = (PangoRenderer *) crenderer;
944
945 crenderer->cr = cr;
946 crenderer->do_path = do_path;
947 save_current_point (renderer: crenderer);
948
949 if (!do_path)
950 {
951 /* unset all part colors, since when drawing just a glyph string,
952 * prepare_run() isn't called.
953 */
954
955 pango_renderer_activate (renderer);
956
957 pango_renderer_set_color (renderer, part: PANGO_RENDER_PART_FOREGROUND, NULL);
958 pango_renderer_set_color (renderer, part: PANGO_RENDER_PART_BACKGROUND, NULL);
959 pango_renderer_set_color (renderer, part: PANGO_RENDER_PART_UNDERLINE, NULL);
960 pango_renderer_set_color (renderer, part: PANGO_RENDER_PART_STRIKETHROUGH, NULL);
961 pango_renderer_set_color (renderer, part: PANGO_RENDER_PART_OVERLINE, NULL);
962 }
963
964 pango_renderer_draw_glyph_item (renderer, text, glyph_item, x: 0, y: 0);
965
966 if (!do_path)
967 {
968 pango_renderer_deactivate (renderer);
969 }
970
971 restore_current_point (renderer: crenderer);
972
973 release_renderer (renderer: crenderer);
974}
975
976static void
977_pango_cairo_do_layout_line (cairo_t *cr,
978 PangoLayoutLine *line,
979 gboolean do_path)
980{
981 PangoCairoRenderer *crenderer = acquire_renderer ();
982 PangoRenderer *renderer = (PangoRenderer *) crenderer;
983
984 crenderer->cr = cr;
985 crenderer->do_path = do_path;
986 save_current_point (renderer: crenderer);
987
988 pango_renderer_draw_layout_line (renderer, line, x: 0, y: 0);
989
990 restore_current_point (renderer: crenderer);
991
992 release_renderer (renderer: crenderer);
993}
994
995static void
996_pango_cairo_do_layout (cairo_t *cr,
997 PangoLayout *layout,
998 gboolean do_path)
999{
1000 PangoCairoRenderer *crenderer = acquire_renderer ();
1001 PangoRenderer *renderer = (PangoRenderer *) crenderer;
1002
1003 crenderer->cr = cr;
1004 crenderer->do_path = do_path;
1005 save_current_point (renderer: crenderer);
1006
1007 pango_renderer_draw_layout (renderer, layout, x: 0, y: 0);
1008
1009 restore_current_point (renderer: crenderer);
1010
1011 release_renderer (renderer: crenderer);
1012}
1013
1014static void
1015_pango_cairo_do_error_underline (cairo_t *cr,
1016 double x,
1017 double y,
1018 double width,
1019 double height,
1020 gboolean do_path)
1021{
1022 /* We don't use a renderer here, for a simple reason:
1023 * the only renderer we can get is the default renderer, that
1024 * is all implemented here, so we shortcircuit and make our
1025 * life way easier.
1026 */
1027
1028 if (!do_path)
1029 cairo_new_path (cr);
1030
1031 draw_error_underline (cr, x, y, width, height);
1032
1033 if (!do_path)
1034 cairo_fill (cr);
1035}
1036
1037
1038/* public wrapper of above to show or append path */
1039
1040
1041/**
1042 * pango_cairo_show_glyph_string:
1043 * @cr: a Cairo context
1044 * @font: a `PangoFont` from a `PangoCairoFontMap`
1045 * @glyphs: a `PangoGlyphString`
1046 *
1047 * Draws the glyphs in @glyphs in the specified cairo context.
1048 *
1049 * The origin of the glyphs (the left edge of the baseline) will
1050 * be drawn at the current point of the cairo context.
1051 *
1052 * Since: 1.10
1053 */
1054void
1055pango_cairo_show_glyph_string (cairo_t *cr,
1056 PangoFont *font,
1057 PangoGlyphString *glyphs)
1058{
1059 g_return_if_fail (cr != NULL);
1060 g_return_if_fail (glyphs != NULL);
1061
1062 _pango_cairo_do_glyph_string (cr, font, glyphs, FALSE);
1063}
1064
1065
1066/**
1067 * pango_cairo_show_glyph_item:
1068 * @cr: a Cairo context
1069 * @text: the UTF-8 text that @glyph_item refers to
1070 * @glyph_item: a `PangoGlyphItem`
1071 *
1072 * Draws the glyphs in @glyph_item in the specified cairo context,
1073 *
1074 * embedding the text associated with the glyphs in the output if the
1075 * output format supports it (PDF for example), otherwise it acts
1076 * similar to [func@show_glyph_string].
1077 *
1078 * The origin of the glyphs (the left edge of the baseline) will
1079 * be drawn at the current point of the cairo context.
1080 *
1081 * Note that @text is the start of the text for layout, which is then
1082 * indexed by `glyph_item->item->offset`.
1083 *
1084 * Since: 1.22
1085 */
1086void
1087pango_cairo_show_glyph_item (cairo_t *cr,
1088 const char *text,
1089 PangoGlyphItem *glyph_item)
1090{
1091 g_return_if_fail (cr != NULL);
1092 g_return_if_fail (text != NULL);
1093 g_return_if_fail (glyph_item != NULL);
1094
1095 _pango_cairo_do_glyph_item (cr, text, glyph_item, FALSE);
1096}
1097
1098/**
1099 * pango_cairo_show_layout_line:
1100 * @cr: a Cairo context
1101 * @line: a `PangoLayoutLine`
1102 *
1103 * Draws a `PangoLayoutLine` in the specified cairo context.
1104 *
1105 * The origin of the glyphs (the left edge of the line) will
1106 * be drawn at the current point of the cairo context.
1107 *
1108 * Since: 1.10
1109 */
1110void
1111pango_cairo_show_layout_line (cairo_t *cr,
1112 PangoLayoutLine *line)
1113{
1114 g_return_if_fail (cr != NULL);
1115 g_return_if_fail (line != NULL);
1116
1117 _pango_cairo_do_layout_line (cr, line, FALSE);
1118}
1119
1120/**
1121 * pango_cairo_show_layout:
1122 * @cr: a Cairo context
1123 * @layout: a Pango layout
1124 *
1125 * Draws a `PangoLayout` in the specified cairo context.
1126 *
1127 * The top-left corner of the `PangoLayout` will be drawn
1128 * at the current point of the cairo context.
1129 *
1130 * Since: 1.10
1131 */
1132void
1133pango_cairo_show_layout (cairo_t *cr,
1134 PangoLayout *layout)
1135{
1136 g_return_if_fail (cr != NULL);
1137 g_return_if_fail (PANGO_IS_LAYOUT (layout));
1138
1139 _pango_cairo_do_layout (cr, layout, FALSE);
1140}
1141
1142/**
1143 * pango_cairo_show_error_underline:
1144 * @cr: a Cairo context
1145 * @x: The X coordinate of one corner of the rectangle
1146 * @y: The Y coordinate of one corner of the rectangle
1147 * @width: Non-negative width of the rectangle
1148 * @height: Non-negative height of the rectangle
1149 *
1150 * Draw a squiggly line in the specified cairo context that approximately
1151 * covers the given rectangle in the style of an underline used to indicate a
1152 * spelling error.
1153 *
1154 * The width of the underline is rounded to an integer
1155 * number of up/down segments and the resulting rectangle is centered in the
1156 * original rectangle.
1157 *
1158 * Since: 1.14
1159 */
1160void
1161pango_cairo_show_error_underline (cairo_t *cr,
1162 double x,
1163 double y,
1164 double width,
1165 double height)
1166{
1167 g_return_if_fail (cr != NULL);
1168 g_return_if_fail ((width >= 0) && (height >= 0));
1169
1170 _pango_cairo_do_error_underline (cr, x, y, width, height, FALSE);
1171}
1172
1173/**
1174 * pango_cairo_glyph_string_path:
1175 * @cr: a Cairo context
1176 * @font: a `PangoFont` from a `PangoCairoFontMap`
1177 * @glyphs: a `PangoGlyphString`
1178 *
1179 * Adds the glyphs in @glyphs to the current path in the specified
1180 * cairo context.
1181 *
1182 * The origin of the glyphs (the left edge of the baseline)
1183 * will be at the current point of the cairo context.
1184 *
1185 * Since: 1.10
1186 */
1187void
1188pango_cairo_glyph_string_path (cairo_t *cr,
1189 PangoFont *font,
1190 PangoGlyphString *glyphs)
1191{
1192 g_return_if_fail (cr != NULL);
1193 g_return_if_fail (glyphs != NULL);
1194
1195 _pango_cairo_do_glyph_string (cr, font, glyphs, TRUE);
1196}
1197
1198/**
1199 * pango_cairo_layout_line_path:
1200 * @cr: a Cairo context
1201 * @line: a `PangoLayoutLine`
1202 *
1203 * Adds the text in `PangoLayoutLine` to the current path in the
1204 * specified cairo context.
1205 *
1206 * The origin of the glyphs (the left edge of the line) will be
1207 * at the current point of the cairo context.
1208 *
1209 * Since: 1.10
1210 */
1211void
1212pango_cairo_layout_line_path (cairo_t *cr,
1213 PangoLayoutLine *line)
1214{
1215 g_return_if_fail (cr != NULL);
1216 g_return_if_fail (line != NULL);
1217
1218 _pango_cairo_do_layout_line (cr, line, TRUE);
1219}
1220
1221/**
1222 * pango_cairo_layout_path:
1223 * @cr: a Cairo context
1224 * @layout: a Pango layout
1225 *
1226 * Adds the text in a `PangoLayout` to the current path in the
1227 * specified cairo context.
1228 *
1229 * The top-left corner of the `PangoLayout` will be at the
1230 * current point of the cairo context.
1231 *
1232 * Since: 1.10
1233 */
1234void
1235pango_cairo_layout_path (cairo_t *cr,
1236 PangoLayout *layout)
1237{
1238 g_return_if_fail (cr != NULL);
1239 g_return_if_fail (PANGO_IS_LAYOUT (layout));
1240
1241 _pango_cairo_do_layout (cr, layout, TRUE);
1242}
1243
1244/**
1245 * pango_cairo_error_underline_path:
1246 * @cr: a Cairo context
1247 * @x: The X coordinate of one corner of the rectangle
1248 * @y: The Y coordinate of one corner of the rectangle
1249 * @width: Non-negative width of the rectangle
1250 * @height: Non-negative height of the rectangle
1251 *
1252 * Add a squiggly line to the current path in the specified cairo context that
1253 * approximately covers the given rectangle in the style of an underline used
1254 * to indicate a spelling error.
1255 *
1256 * The width of the underline is rounded to an integer number of up/down
1257 * segments and the resulting rectangle is centered in the original rectangle.
1258 *
1259 * Since: 1.14
1260 */
1261void
1262pango_cairo_error_underline_path (cairo_t *cr,
1263 double x,
1264 double y,
1265 double width,
1266 double height)
1267{
1268 g_return_if_fail (cr != NULL);
1269 g_return_if_fail ((width >= 0) && (height >= 0));
1270
1271 _pango_cairo_do_error_underline (cr, x, y, width, height, TRUE);
1272}
1273

source code of gtk/subprojects/pango/pango/pangocairo-render.c