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 | |
31 | typedef 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 | |
37 | struct _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 | |
51 | struct _PangoCairoRendererClass |
52 | { |
53 | PangoRendererClass parent_class; |
54 | }; |
55 | |
56 | G_DEFINE_TYPE (PangoCairoRenderer, pango_cairo_renderer, PANGO_TYPE_RENDERER) |
57 | |
58 | static void |
59 | set_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() */ |
98 | static 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 | |
208 | static 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 | |
228 | static 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 | |
394 | done: |
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 | |
404 | static void |
405 | pango_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 | |
500 | done: |
501 | cairo_restore (cr: crenderer->cr); |
502 | } |
503 | |
504 | static void |
505 | pango_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 | |
520 | static void |
521 | pango_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 | |
599 | static void |
600 | pango_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 | |
629 | static void |
630 | pango_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 | |
688 | static void |
689 | draw_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 | |
741 | static void |
742 | pango_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 | |
773 | static void |
774 | pango_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 | |
811 | static void |
812 | pango_cairo_renderer_init (PangoCairoRenderer *renderer G_GNUC_UNUSED) |
813 | { |
814 | } |
815 | |
816 | static void |
817 | pango_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 | |
829 | static PangoCairoRenderer *cached_renderer = NULL; /* MT-safe */ |
830 | G_LOCK_DEFINE_STATIC (cached_renderer); |
831 | |
832 | static PangoCairoRenderer * |
833 | acquire_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 | |
855 | static void |
856 | release_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 | |
872 | static void |
873 | save_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 | |
882 | static void |
883 | restore_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 | |
896 | static 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 | |
936 | static 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 | |
976 | static 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 | |
995 | static 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 | |
1014 | static 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 | */ |
1054 | void |
1055 | pango_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 | */ |
1086 | void |
1087 | pango_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 | */ |
1110 | void |
1111 | pango_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 | */ |
1132 | void |
1133 | pango_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 | */ |
1160 | void |
1161 | pango_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 | */ |
1187 | void |
1188 | pango_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 | */ |
1211 | void |
1212 | pango_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 | */ |
1234 | void |
1235 | pango_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 | */ |
1261 | void |
1262 | pango_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 | |