1/* viewer-pangocairo.c: PangoCairo viewer backend.
2 *
3 * Copyright (C) 1999,2004,2005 Red Hat, Inc.
4 * Copyright (C) 2001 Sun Microsystems
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 "viewer-render.h"
25#include "viewer-cairo.h"
26
27#include <pango/pangocairo.h>
28
29#include <hb-ot.h>
30
31static int opt_annotate = 0;
32
33typedef struct
34{
35 const CairoViewerIface *iface;
36
37 gpointer backend;
38
39 PangoFontMap *fontmap;
40 cairo_font_options_t *font_options;
41 gboolean subpixel_positions;
42} CairoViewer;
43
44static gpointer
45pangocairo_view_create (const PangoViewer *klass G_GNUC_UNUSED)
46{
47 CairoViewer *instance;
48
49 instance = g_slice_new (CairoViewer);
50
51 instance->backend = cairo_viewer_iface_create (iface_out: &instance->iface);
52
53 instance->fontmap = pango_cairo_font_map_new ();
54 pango_cairo_font_map_set_resolution (PANGO_CAIRO_FONT_MAP (instance->fontmap), dpi: opt_dpi);
55
56 instance->font_options = cairo_font_options_create ();
57 if (opt_hinting != HINT_DEFAULT)
58 {
59 if (opt_hinting == HINT_NONE)
60 cairo_font_options_set_hint_style (options: instance->font_options, hint_style: CAIRO_HINT_STYLE_NONE);
61 else if (opt_hinting == HINT_SLIGHT ||
62 opt_hinting == HINT_AUTO)
63 cairo_font_options_set_hint_style (options: instance->font_options, hint_style: CAIRO_HINT_STYLE_SLIGHT);
64 else if (opt_hinting == HINT_MEDIUM)
65 cairo_font_options_set_hint_style (options: instance->font_options, hint_style: CAIRO_HINT_STYLE_MEDIUM);
66 else if (opt_hinting == HINT_FULL)
67 cairo_font_options_set_hint_style (options: instance->font_options, hint_style: CAIRO_HINT_STYLE_FULL);
68 }
69
70 if (opt_subpixel_order != SUBPIXEL_DEFAULT)
71 cairo_font_options_set_subpixel_order (options: instance->font_options, subpixel_order: (cairo_subpixel_order_t)opt_subpixel_order);
72
73 if (opt_hint_metrics != HINT_METRICS_DEFAULT)
74 cairo_font_options_set_hint_metrics (options: instance->font_options, hint_metrics: (cairo_hint_metrics_t)opt_hint_metrics);
75
76 if (opt_antialias != ANTIALIAS_DEFAULT)
77 cairo_font_options_set_antialias (options: instance->font_options, antialias: (cairo_antialias_t)opt_antialias);
78
79 instance->subpixel_positions = opt_subpixel_positions;
80
81 return instance;
82}
83
84static void
85pangocairo_view_destroy (gpointer instance)
86{
87 CairoViewer *c = (CairoViewer *) instance;
88
89 cairo_font_options_destroy (options: c->font_options);
90
91 g_object_unref (object: c->fontmap);
92
93 c->iface->backend_class->destroy (c->backend);
94
95 g_slice_free (CairoViewer, c);
96}
97
98static PangoContext *
99pangocairo_view_get_context (gpointer instance)
100{
101 CairoViewer *c = (CairoViewer *) instance;
102 PangoContext *context;
103
104 context = pango_font_map_create_context (fontmap: c->fontmap);
105 pango_cairo_context_set_font_options (context, options: c->font_options);
106 pango_context_set_round_glyph_positions (context, round_positions: !c->subpixel_positions);
107
108 return context;
109}
110
111typedef struct
112{
113 gpointer backend;
114
115 cairo_surface_t *cairo;
116} CairoSurface;
117
118static gpointer
119pangocairo_view_create_surface (gpointer instance,
120 int width,
121 int height)
122{
123 CairoViewer *c = (CairoViewer *) instance;
124 CairoSurface *surface;
125
126 surface = g_slice_new (CairoSurface);
127
128 surface->backend = c->iface->backend_class->create_surface (c->backend,
129 width, height);
130
131 surface->cairo = c->iface->create_surface (c->backend,
132 surface->backend,
133 width, height);
134
135 return surface;
136}
137
138static void
139pangocairo_view_destroy_surface (gpointer instance,
140 gpointer surface)
141{
142 CairoViewer *c = (CairoViewer *) instance;
143 CairoSurface *c_surface = (CairoSurface *) surface;
144
145 c->iface->backend_class->destroy_surface (c->backend, c_surface->backend);
146 cairo_surface_destroy (surface: c_surface->cairo);
147
148 g_slice_free (CairoSurface, surface);
149}
150
151enum {
152 ANNOTATE_GRAVITY_ROOF = 1,
153 ANNOTATE_BLOCK_PROGRESSION = 2,
154 ANNOTATE_BASELINES = 4,
155 ANNOTATE_LAYOUT_EXTENTS = 8,
156 ANNOTATE_LINE_EXTENTS = 16,
157 ANNOTATE_RUN_EXTENTS = 32,
158 ANNOTATE_CLUSTER_EXTENTS = 64,
159 ANNOTATE_CHAR_EXTENTS = 128,
160 ANNOTATE_GLYPH_EXTENTS = 256,
161 ANNOTATE_CARET_POSITIONS = 512,
162 ANNOTATE_CARET_SLOPE = 1024,
163 ANNOTATE_RUN_BASELINES = 2048,
164 ANNOTATE_LAST = 4096,
165};
166
167static struct {
168 int value;
169 const char *name;
170 const char *short_name;
171} annotate_options[] = {
172 { ANNOTATE_GRAVITY_ROOF, "gravity-roof", "gravity" },
173 { ANNOTATE_BLOCK_PROGRESSION, "block-progression", "progression" },
174 { ANNOTATE_BASELINES, "baselines", "baselines" },
175 { ANNOTATE_RUN_BASELINES, "run-baselines", "run-baselines" },
176 { ANNOTATE_LAYOUT_EXTENTS, "layout-extents", "layout" },
177 { ANNOTATE_LINE_EXTENTS, "line-extents", "line" },
178 { ANNOTATE_RUN_EXTENTS, "run-extents", "run" },
179 { ANNOTATE_CLUSTER_EXTENTS, "cluster-extents", "cluster" },
180 { ANNOTATE_CHAR_EXTENTS, "char-extents", "char" },
181 { ANNOTATE_GLYPH_EXTENTS, "glyph-extents", "glyph" },
182 { ANNOTATE_CARET_POSITIONS, "caret-positions", "caret" },
183 { ANNOTATE_CARET_SLOPE, "caret-slope", "slope" },
184};
185
186static const char *annotate_arg_help =
187 "Annotate the output. Comma-separated list of\n"
188 "\t\t\t\t\t\t gravity, progression, baselines, layout, line,\n"
189 "\t\t\t\t\t\t run, cluster, char, glyph, caret, slope\n";
190
191static void
192render_callback (PangoLayout *layout,
193 int x,
194 int y,
195 gpointer context,
196 gpointer state)
197{
198 cairo_t *cr = (cairo_t *) context;
199 int annotate = (GPOINTER_TO_INT (state) + opt_annotate) % ANNOTATE_LAST;
200
201 cairo_save (cr);
202 cairo_translate (cr, tx: x, ty: y);
203
204 if (annotate != 0)
205 {
206 cairo_pattern_t *pattern;
207 PangoRectangle ink, logical;
208 double lw = cairo_get_line_width (cr);
209 PangoLayoutIter* iter;
210
211 pango_layout_get_extents (layout, ink_rect: &ink, logical_rect: &logical);
212
213 if (annotate & ANNOTATE_GRAVITY_ROOF)
214 {
215 /* draw resolved gravity "roof" in blue */
216 cairo_save (cr);
217 cairo_translate (cr,
218 tx: (double)logical.x / PANGO_SCALE,
219 ty: (double)logical.y / PANGO_SCALE);
220 cairo_scale (cr,
221 sx: (double)logical.width / PANGO_SCALE * 0.5,
222 sy: (double)logical.height / PANGO_SCALE * 0.5);
223 cairo_translate (cr, tx: 1.0, ty: 1.0);
224 cairo_rotate (cr,
225 angle: pango_gravity_to_rotation (
226 gravity: pango_context_get_gravity (
227 context: pango_layout_get_context (layout))));
228 cairo_move_to (cr, x: -1.0, y: -1.0);
229 cairo_rel_line_to (cr, dx: +1.0, dy: -0.2); /* / */
230 cairo_rel_line_to (cr, dx: +1.0, dy: +0.2); /* \ */
231 cairo_close_path (cr); /* - */
232 pattern = cairo_pattern_create_linear (x0: 0, y0: -1.0, x1: 0, y1: -1.2);
233 cairo_pattern_add_color_stop_rgba (pattern, offset: 0.0, red: 0.0, green: 0.0, blue: 1.0, alpha: 0.0);
234 cairo_pattern_add_color_stop_rgba (pattern, offset: 1.0, red: 0.0, green: 0.0, blue: 1.0, alpha: 0.15);
235 cairo_set_source (cr, source: pattern);
236 cairo_fill (cr);
237 /* once more, without close_path this time */
238 cairo_move_to (cr, x: -1.0, y: -1.0);
239 cairo_rel_line_to (cr, dx: +1.0, dy: -0.2); /* / */
240 cairo_rel_line_to (cr, dx: +1.0, dy: +0.2); /* \ */
241 /* silly line_width is not locked :(. get rid of scale. */
242 cairo_restore (cr);
243 cairo_save (cr);
244 cairo_set_source_rgba (cr, red: 0.0, green: 0.0, blue: 0.7, alpha: 0.2);
245 cairo_stroke (cr);
246 cairo_restore (cr);
247 }
248
249 if (annotate & ANNOTATE_BLOCK_PROGRESSION)
250 {
251 /* draw block progression arrow in green */
252 cairo_save (cr);
253 cairo_translate (cr,
254 tx: (double)logical.x / PANGO_SCALE,
255 ty: (double)logical.y / PANGO_SCALE);
256 cairo_scale (cr,
257 sx: (double)logical.width / PANGO_SCALE * 0.5,
258 sy: (double)logical.height / PANGO_SCALE * 0.5);
259 cairo_translate (cr, tx: 1.0, ty: 1.0);
260 cairo_move_to (cr, x: -0.4, y: -0.7);
261 cairo_rel_line_to (cr, dx: +0.8, dy: 0.0); /* -- */
262 cairo_rel_line_to (cr, dx: 0.0, dy: +0.9); /* | */
263 cairo_rel_line_to (cr, dx: +0.4, dy: 0.0); /* - */
264 cairo_rel_line_to (cr, dx: -0.8, dy: +0.5); /* / */
265 cairo_rel_line_to (cr, dx: -0.8, dy: -0.5); /* \ */
266 cairo_rel_line_to (cr, dx: +0.4, dy: 0.0); /* - */
267 cairo_close_path (cr); /* | */
268 pattern = cairo_pattern_create_linear (x0: 0, y0: -0.7, x1: 0, y1: 0.7);
269 cairo_pattern_add_color_stop_rgba (pattern, offset: 0.0, red: 0.0, green: 1.0, blue: 0.0, alpha: 0.0);
270 cairo_pattern_add_color_stop_rgba (pattern, offset: 1.0, red: 0.0, green: 1.0, blue: 0.0, alpha: 0.15);
271 cairo_set_source (cr, source: pattern);
272 cairo_fill_preserve (cr);
273 /* silly line_width is not locked :(. get rid of scale. */
274 cairo_restore (cr);
275 cairo_save (cr);
276 cairo_set_source_rgba (cr, red: 0.0, green: 0.7, blue: 0.0, alpha: 0.2);
277 cairo_stroke (cr);
278 cairo_restore (cr);
279 }
280
281 if (annotate & ANNOTATE_BASELINES)
282 {
283 /* draw baselines with line direction arrow in orange */
284 cairo_save (cr);
285 cairo_set_source_rgba (cr, red: 1.0, green: 0.5, blue: 0.0, alpha: 0.5);
286 iter = pango_layout_get_iter (layout);
287 do
288 {
289 PangoLayoutLine *line = pango_layout_iter_get_line (iter);
290 double width = (double)logical.width / PANGO_SCALE;
291
292 y = pango_layout_iter_get_baseline (iter);
293 cairo_save (cr);
294 cairo_translate (cr,
295 tx: (double)logical.x / PANGO_SCALE + width * 0.5,
296 ty: (double)y / PANGO_SCALE);
297 if (line->resolved_dir)
298 cairo_scale (cr, sx: -1, sy: 1);
299 cairo_move_to (cr, x: -width * .5, y: -lw*0.2);
300 cairo_rel_line_to (cr, dx: +width * .9, dy: -lw*0.3);
301 cairo_rel_line_to (cr, dx: 0, dy: -lw);
302 cairo_rel_line_to (cr, dx: +width * .1, dy: +lw*1.5);
303 cairo_rel_line_to (cr, dx: -width * .1, dy: +lw*1.5);
304 cairo_rel_line_to (cr, dx: 0, dy: -lw);
305 cairo_rel_line_to (cr, dx: -width * .9, dy: -lw*0.3);
306 cairo_close_path (cr);
307 cairo_fill (cr);
308 cairo_restore (cr);
309 }
310 while (pango_layout_iter_next_line (iter));
311 pango_layout_iter_free (iter);
312 cairo_restore (cr);
313 }
314
315#if HB_VERSION_ATLEAST(4,0,0)
316 if (annotate & ANNOTATE_RUN_BASELINES)
317 {
318 hb_ot_layout_baseline_tag_t baseline_tag = 0;
319 /* draw baselines for runs in blue */
320 cairo_save (cr);
321 cairo_set_source_rgba (cr, 0.0, 0.0, 1.0, 0.5);
322
323 iter = pango_layout_get_iter (layout);
324 do
325 {
326 PangoLayoutRun *run;
327 PangoRectangle rect;
328 hb_font_t *hb_font;
329 hb_ot_layout_baseline_tag_t baselines[] = {
330 HB_OT_LAYOUT_BASELINE_TAG_ROMAN,
331 HB_OT_LAYOUT_BASELINE_TAG_HANGING,
332 HB_OT_LAYOUT_BASELINE_TAG_IDEO_FACE_BOTTOM_OR_LEFT,
333 HB_OT_LAYOUT_BASELINE_TAG_IDEO_FACE_TOP_OR_RIGHT,
334 HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT,
335 HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_CENTRAL,
336 HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_TOP_OR_RIGHT,
337 HB_OT_LAYOUT_BASELINE_TAG_MATH
338 };
339 hb_direction_t dir;
340 hb_tag_t script, lang;
341 hb_position_t coord;
342
343 run = pango_layout_iter_get_run (iter);
344 if (!run)
345 {
346 baseline_tag = 0;
347 continue;
348 }
349
350 if (baseline_tag == 0)
351 {
352 hb_script_t script = (hb_script_t) g_unicode_script_to_iso15924 (run->item->analysis.script);
353
354 if (run->item->analysis.flags & PANGO_ANALYSIS_FLAG_CENTERED_BASELINE)
355 baseline_tag = HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_CENTRAL;
356 else
357 baseline_tag = hb_ot_layout_get_horizontal_baseline_tag_for_script (script);
358 }
359
360 y = pango_layout_iter_get_run_baseline (iter);
361 pango_layout_iter_get_run_extents (iter, NULL, &rect);
362
363 hb_font = pango_font_get_hb_font (run->item->analysis.font);
364 if (run->item->analysis.flags & PANGO_ANALYSIS_FLAG_CENTERED_BASELINE)
365 dir = HB_DIRECTION_TTB;
366 else
367 dir = HB_DIRECTION_LTR;
368 script = (hb_script_t) g_unicode_script_to_iso15924 (run->item->analysis.script);
369 lang = HB_TAG_NONE;
370
371 for (int i = 0; i < G_N_ELEMENTS (baselines); i++)
372 {
373 char buf[5] = { 0, };
374
375 hb_tag_to_string (baselines[i], buf);
376
377 cairo_save (cr);
378
379 if (baselines[i] == baseline_tag)
380 cairo_set_source_rgba (cr, 1.0, 0.0, 0.0, 0.5);
381 else
382 cairo_set_source_rgba (cr, 0.0, 0.0, 1.0, 0.5);
383
384 if (hb_ot_layout_get_baseline (hb_font,
385 baselines[i],
386 dir,
387 script,
388 lang,
389 &coord))
390 {
391 g_print ("baseline %s, value %d\n", buf, coord);
392 cairo_set_dash (cr, NULL, 0, 0);
393 }
394 else
395 {
396 double dashes[] = { 4 * lw, 4 * lw };
397
398 hb_ot_layout_get_baseline_with_fallback (hb_font,
399 baselines[i],
400 dir,
401 script,
402 lang,
403 &coord);
404
405 g_print ("baseline %s, fallback value %d\n", buf, coord);
406 cairo_set_dash (cr, dashes, 2, 0);
407 cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
408 }
409 cairo_move_to (cr,
410 (double)rect.x / PANGO_SCALE,
411 (double)(y - coord) / PANGO_SCALE);
412 cairo_rel_line_to (cr, (double)rect.width / PANGO_SCALE, 0);
413 cairo_stroke (cr);
414 cairo_set_source_rgb (cr, 0, 0, 0);
415 cairo_move_to (cr,
416 (double)rect.x / PANGO_SCALE - 5,
417 (double)(y - coord) / PANGO_SCALE - 5);
418 cairo_show_text (cr, buf);
419
420 cairo_restore (cr);
421 }
422 }
423 while (pango_layout_iter_next_run (iter));
424 pango_layout_iter_free (iter);
425 cairo_restore (cr);
426 }
427#endif
428
429 if (annotate & ANNOTATE_LAYOUT_EXTENTS)
430 {
431 /* draw the logical rect in red */
432 cairo_save (cr);
433 cairo_set_source_rgba (cr, red: 1.0, green: 0.0, blue: 0.0, alpha: 0.5);
434
435 cairo_rectangle (cr,
436 x: (double)logical.x / PANGO_SCALE - lw / 2,
437 y: (double)logical.y / PANGO_SCALE - lw / 2,
438 width: (double)logical.width / PANGO_SCALE + lw,
439 height: (double)logical.height / PANGO_SCALE + lw);
440 cairo_stroke (cr);
441 cairo_restore (cr);
442
443 /* draw the ink rect in green */
444 cairo_save (cr);
445 cairo_set_source_rgba (cr, red: 0.0, green: 1.0, blue: 0.0, alpha: 0.5);
446 cairo_rectangle (cr,
447 x: (double)ink.x / PANGO_SCALE - lw / 2,
448 y: (double)ink.y / PANGO_SCALE - lw / 2,
449 width: (double)ink.width / PANGO_SCALE + lw,
450 height: (double)ink.height / PANGO_SCALE + lw);
451 cairo_stroke (cr);
452 cairo_restore (cr);
453 }
454
455 if (annotate & ANNOTATE_LINE_EXTENTS)
456 {
457 /* draw the logical rects for lines in red */
458 cairo_save (cr);
459 cairo_set_source_rgba (cr, red: 1.0, green: 0.0, blue: 0.0, alpha: 0.5);
460
461 iter = pango_layout_get_iter (layout);
462 do
463 {
464 PangoRectangle rect;
465
466 pango_layout_iter_get_line_extents (iter, NULL, logical_rect: &rect);
467 cairo_rectangle (cr,
468 x: (double)rect.x / PANGO_SCALE - lw / 2,
469 y: (double)rect.y / PANGO_SCALE - lw / 2,
470 width: (double)rect.width / PANGO_SCALE + lw,
471 height: (double)rect.height / PANGO_SCALE + lw);
472 cairo_stroke (cr);
473 }
474 while (pango_layout_iter_next_line (iter));
475 pango_layout_iter_free (iter);
476 cairo_restore (cr);
477 }
478
479 if (annotate & ANNOTATE_RUN_EXTENTS)
480 {
481 /* draw the logical rects for runs in blue */
482 cairo_save (cr);
483 cairo_set_source_rgba (cr, red: 0.0, green: 0.0, blue: 1.0, alpha: 0.5);
484
485 iter = pango_layout_get_iter (layout);
486 do
487 {
488 PangoLayoutRun *run;
489 PangoRectangle rect;
490
491 run = pango_layout_iter_get_run (iter);
492 if (!run)
493 continue;
494
495 pango_layout_iter_get_run_extents (iter, NULL, logical_rect: &rect);
496 cairo_rectangle (cr,
497 x: (double)rect.x / PANGO_SCALE - lw / 2,
498 y: (double)rect.y / PANGO_SCALE - lw / 2,
499 width: (double)rect.width / PANGO_SCALE + lw,
500 height: (double)rect.height / PANGO_SCALE + lw);
501 cairo_stroke (cr);
502 }
503 while (pango_layout_iter_next_run (iter));
504 pango_layout_iter_free (iter);
505 cairo_restore (cr);
506 }
507
508 if (annotate & ANNOTATE_CLUSTER_EXTENTS)
509 {
510 /* draw the logical rects for clusters in purple */
511 cairo_save (cr);
512 cairo_set_source_rgba (cr, red: 1.0, green: 0.0, blue: 1.0, alpha: 0.5);
513
514 iter = pango_layout_get_iter (layout);
515 do
516 {
517 PangoRectangle rect;
518
519 pango_layout_iter_get_cluster_extents (iter, NULL, logical_rect: &rect);
520 cairo_rectangle (cr,
521 x: (double)rect.x / PANGO_SCALE - lw / 2,
522 y: (double)rect.y / PANGO_SCALE - lw / 2,
523 width: (double)rect.width / PANGO_SCALE + lw,
524 height: (double)rect.height / PANGO_SCALE + lw);
525 cairo_stroke (cr);
526 }
527 while (pango_layout_iter_next_cluster (iter));
528 pango_layout_iter_free (iter);
529 cairo_restore (cr);
530 }
531
532 if (annotate & ANNOTATE_CHAR_EXTENTS)
533 {
534 /* draw the logical rects for chars in orange */
535 cairo_save (cr);
536 cairo_set_source_rgba (cr, red: 1.0, green: 0.5, blue: 0.0, alpha: 0.5);
537
538 iter = pango_layout_get_iter (layout);
539 do
540 {
541 PangoRectangle rect;
542
543 pango_layout_iter_get_char_extents (iter, logical_rect: &rect);
544 cairo_rectangle (cr,
545 x: (double)rect.x / PANGO_SCALE - lw / 2,
546 y: (double)rect.y / PANGO_SCALE - lw / 2,
547 width: (double)rect.width / PANGO_SCALE + lw,
548 height: (double)rect.height / PANGO_SCALE + lw);
549 cairo_stroke (cr);
550 }
551 while (pango_layout_iter_next_cluster (iter));
552 pango_layout_iter_free (iter);
553 cairo_restore (cr);
554 }
555
556 if (annotate & ANNOTATE_GLYPH_EXTENTS)
557 {
558 /* draw the glyph_extents in blue */
559 cairo_save (cr);
560 cairo_set_source_rgba (cr, red: 0.0, green: 0.0, blue: 1.0, alpha: 0.5);
561
562 iter = pango_layout_get_iter (layout);
563 do
564 {
565 PangoLayoutRun *run;
566 PangoRectangle rect;
567 int x_pos, y_pos;
568
569 run = pango_layout_iter_get_run (iter);
570 if (!run)
571 continue;
572
573 pango_layout_iter_get_run_extents (iter, NULL, logical_rect: &rect);
574
575 x_pos = rect.x;
576 y_pos = pango_layout_iter_get_run_baseline (iter);
577
578 for (int i = 0; i < run->glyphs->num_glyphs; i++)
579 {
580 PangoRectangle extents;
581
582 pango_font_get_glyph_extents (font: run->item->analysis.font,
583 glyph: run->glyphs->glyphs[i].glyph,
584 ink_rect: &extents, NULL);
585
586 rect.x = x_pos + run->glyphs->glyphs[i].geometry.x_offset + extents.x;
587 rect.y = y_pos + run->glyphs->glyphs[i].geometry.y_offset + extents.y;
588 rect.width = extents.width;
589 rect.height = extents.height;
590
591 cairo_rectangle (cr,
592 x: (double)rect.x / PANGO_SCALE - lw / 2,
593 y: (double)rect.y / PANGO_SCALE - lw / 2,
594 width: (double)rect.width / PANGO_SCALE + lw,
595 height: (double)rect.height / PANGO_SCALE + lw);
596 cairo_stroke (cr);
597
598 cairo_arc (cr,
599 xc: (double) (x_pos + run->glyphs->glyphs[i].geometry.x_offset) / PANGO_SCALE,
600 yc: (double) (y_pos + run->glyphs->glyphs[i].geometry.y_offset) / PANGO_SCALE,
601 radius: 3.0, angle1: 0, angle2: 2*G_PI);
602 cairo_fill (cr);
603
604 x_pos += run->glyphs->glyphs[i].geometry.width;
605 }
606 }
607 while (pango_layout_iter_next_run (iter));
608 pango_layout_iter_free (iter);
609 cairo_restore (cr);
610 }
611
612 if (annotate & ANNOTATE_CARET_POSITIONS)
613 {
614 const PangoLogAttr *attrs;
615 int n_attrs;
616 int offset;
617 int num = 0;
618
619 /* draw the caret positions in purple */
620 cairo_save (cr);
621 cairo_set_source_rgba (cr, red: 1.0, green: 0.0, blue: 1.0, alpha: 0.5);
622
623 attrs = pango_layout_get_log_attrs_readonly (layout, n_attrs: &n_attrs);
624
625 iter = pango_layout_get_iter (layout);
626 do
627 {
628 PangoRectangle rect;
629 PangoLayoutRun *run;
630 const char *text, *start, *p;
631 int x, y;
632 gboolean trailing;
633
634 pango_layout_iter_get_run_extents (iter, NULL, logical_rect: &rect);
635 run = pango_layout_iter_get_run_readonly (iter);
636
637 if (!run)
638 continue;
639
640 text = pango_layout_get_text (layout);
641 start = text + run->item->offset;
642
643 offset = g_utf8_strlen (p: text, max: start - text);
644
645 y = pango_layout_iter_get_run_baseline (iter);
646
647 trailing = FALSE;
648 p = start;
649 for (int i = 0; i <= run->item->num_chars; i++)
650 {
651 if (attrs[offset + i].is_cursor_position)
652 {
653 pango_glyph_string_index_to_x_full (glyphs: run->glyphs,
654 text: text + run->item->offset,
655 length: run->item->length,
656 analysis: &run->item->analysis,
657 attrs: (PangoLogAttr *)attrs + offset,
658 index_: p - start,
659 trailing,
660 x_pos: &x);
661 x += rect.x;
662
663 cairo_set_source_rgba (cr, red: 1.0, green: 0.0, blue: 1.0, alpha: 0.5);
664 cairo_arc (cr, xc: x / PANGO_SCALE, yc: y / PANGO_SCALE, radius: 3.0, angle1: 0, angle2: 2*G_PI);
665 cairo_close_path (cr);
666 cairo_fill (cr);
667
668 char *s = g_strdup_printf (format: "%d", num);
669 cairo_set_source_rgb (cr, red: 0, green: 0, blue: 0);
670 cairo_move_to (cr, x: x / PANGO_SCALE - 5, y: y / PANGO_SCALE + 15);
671 cairo_show_text (cr, utf8: s);
672 g_free (mem: s);
673 }
674
675 if (i < run->item->num_chars)
676 {
677 num++;
678 p = g_utf8_next_char (p);
679 }
680 else
681 trailing = TRUE;
682
683 }
684 }
685 while (pango_layout_iter_next_run (iter));
686 pango_layout_iter_free (iter);
687 cairo_restore (cr);
688 }
689
690 if (annotate & ANNOTATE_CARET_SLOPE)
691 {
692 const char *text = pango_layout_get_text (layout);
693 int length = g_utf8_strlen (p: text, max: -1);
694 const PangoLogAttr *attrs;
695 int n_attrs;
696 const char *p;
697 int i;
698
699 /* draw the caret slope in gray */
700 cairo_save (cr);
701 cairo_set_source_rgba (cr, red: 0.0, green: 0.0, blue: 0.0, alpha: 0.5);
702
703 attrs = pango_layout_get_log_attrs_readonly (layout, n_attrs: &n_attrs);
704
705 for (i = 0, p = text; i <= length; i++, p = g_utf8_next_char (p))
706 {
707 PangoRectangle rect;
708
709 if (!attrs[i].is_cursor_position)
710 continue;
711
712 pango_layout_get_caret_pos (layout, index_: p - text, strong_pos: &rect, NULL);
713
714 cairo_move_to (cr,
715 x: (double)rect.x / PANGO_SCALE + (double)rect.width / PANGO_SCALE - lw / 2,
716 y: (double)rect.y / PANGO_SCALE - lw / 2);
717 cairo_line_to (cr,
718 x: (double)rect.x / PANGO_SCALE - lw / 2,
719 y: (double)rect.y / PANGO_SCALE + (double)rect.height / PANGO_SCALE - lw / 2);
720 cairo_stroke (cr);
721 }
722
723 cairo_restore (cr);
724 }
725 }
726
727 cairo_move_to (cr, x: 0, y: 0);
728 pango_cairo_show_layout (cr, layout);
729
730 cairo_restore (cr);
731
732 cairo_surface_flush (surface: cairo_get_target (cr));
733}
734
735static void
736transform_callback (PangoContext *context,
737 PangoMatrix *matrix,
738 gpointer cr_context,
739 gpointer state G_GNUC_UNUSED)
740{
741 cairo_t *cr = (cairo_t *)cr_context;
742 cairo_matrix_t cairo_matrix;
743
744 if (matrix)
745 {
746 cairo_matrix.xx = matrix->xx;
747 cairo_matrix.yx = matrix->yx;
748 cairo_matrix.xy = matrix->xy;
749 cairo_matrix.yy = matrix->yy;
750 cairo_matrix.x0 = matrix->x0;
751 cairo_matrix.y0 = matrix->y0;
752 }
753 else
754 {
755 cairo_matrix_init_identity (matrix: &cairo_matrix);
756 }
757
758 cairo_set_matrix (cr, matrix: &cairo_matrix);
759
760 pango_cairo_update_context (cr, context);
761}
762
763static void
764pangocairo_view_render (gpointer instance,
765 gpointer surface,
766 PangoContext *context,
767 int *width,
768 int *height,
769 gpointer state)
770{
771 CairoViewer *c = (CairoViewer *) instance;
772 cairo_t *cr;
773 CairoSurface *c_surface = (CairoSurface *) surface;
774
775 g_assert (surface);
776
777 cr = cairo_create (target: c_surface->cairo);
778
779 transform_callback (context, NULL, cr_context: cr, state);
780
781 c->iface->paint_background (instance, cr);
782
783 cairo_set_operator (cr, op: CAIRO_OPERATOR_OVER);
784 cairo_set_source_rgba (cr,
785 red: opt_fg_color.red / 65535.,
786 green: opt_fg_color.green / 65535.,
787 blue: opt_fg_color.blue / 65535.,
788 alpha: opt_fg_alpha / 65535.);
789
790 do_output (context, render_cb: render_callback, transform_cb: transform_callback, cb_context: cr, cb_data: state, width, height);
791
792 cairo_destroy (cr);
793}
794
795#ifdef HAVE_CAIRO_PNG
796static cairo_status_t
797write_func (void *closure,
798 const unsigned char *data,
799 unsigned int length)
800{
801 FILE *stream = (FILE *) closure;
802 unsigned int l;
803
804 l = fwrite (ptr: data, size: 1, n: length, s: stream);
805
806 return l == length ? CAIRO_STATUS_SUCCESS : CAIRO_STATUS_WRITE_ERROR;
807}
808
809static void
810pangocairo_view_write (gpointer instance G_GNUC_UNUSED,
811 gpointer surface,
812 FILE *stream,
813 int width G_GNUC_UNUSED,
814 int height G_GNUC_UNUSED)
815{
816 CairoSurface *c_surface = (CairoSurface *) surface;
817
818 cairo_surface_write_to_png_stream (surface: c_surface->cairo, write_func, closure: stream);
819}
820#endif
821
822static gpointer
823pangocairo_view_create_window (gpointer instance,
824 const char *title,
825 int width,
826 int height)
827{
828 CairoViewer *c = (CairoViewer *) instance;
829
830 if (!c->iface->backend_class->create_window)
831 return NULL;
832
833 return c->iface->backend_class->create_window (c->backend,
834 title,
835 width, height);
836}
837
838static void
839pangocairo_view_destroy_window (gpointer instance,
840 gpointer window)
841{
842 CairoViewer *c = (CairoViewer *) instance;
843
844 c->iface->backend_class->destroy_window (c->backend,
845 window);
846}
847
848static gpointer
849pangocairo_view_display (gpointer instance,
850 gpointer surface,
851 gpointer window,
852 int width, int height,
853 gpointer state)
854{
855 CairoViewer *c = (CairoViewer *) instance;
856 CairoSurface *c_surface = (CairoSurface *) surface;
857
858 return c->iface->backend_class->display (c->backend,
859 c_surface->backend,
860 window,
861 width, height,
862 state);
863}
864
865static gboolean
866parse_annotate_arg (const char *option_name,
867 const char *value,
868 gpointer data,
869 GError **error)
870{
871 guint64 num;
872
873 if (!g_ascii_string_to_unsigned (str: value, base: 10, min: 0, max: ANNOTATE_LAST - 1, out_num: &num, NULL))
874 {
875 char **parts;
876 int i, j;
877
878 parts = g_strsplit (string: value, delimiter: ",", max_tokens: 0);
879 num = 0;
880 for (i = 0; parts[i]; i++)
881 {
882 for (j = 0; j < G_N_ELEMENTS (annotate_options); j++)
883 {
884 if (strcmp (s1: parts[i], s2: annotate_options[j].name) == 0 ||
885 strcmp (s1: parts[i], s2: annotate_options[j].short_name) == 0)
886 {
887 num |= annotate_options[j].value;
888 break;
889 }
890 }
891
892 if (j == G_N_ELEMENTS (annotate_options))
893 {
894 g_set_error (err: error,
895 G_OPTION_ERROR, code: G_OPTION_ERROR_FAILED,
896 format: "%s is not an allowed value for %s. "
897 "See --help-cairo", parts[i], option_name);
898 return FALSE;
899 }
900 }
901
902 g_strfreev (str_array: parts);
903 }
904
905 opt_annotate = num;
906 return TRUE;
907}
908
909static GOptionGroup *
910pangocairo_view_get_option_group (const PangoViewer *klass G_GNUC_UNUSED)
911{
912 GOptionEntry entries[] =
913 {
914 {"annotate", 0, 0, G_OPTION_ARG_CALLBACK, parse_annotate_arg, annotate_arg_help, "FLAGS"},
915 {NULL}
916 };
917 GOptionGroup *group;
918
919 group = g_option_group_new (name: "cairo",
920 description: "Cairo backend options:",
921 help_description: "Options understood by the cairo backend",
922 NULL,
923 NULL);
924
925 g_option_group_add_entries (group, entries);
926
927 cairo_viewer_add_options (group);
928
929 return group;
930}
931
932const PangoViewer pangocairo_viewer = {
933 "PangoCairo",
934 "cairo",
935#ifdef HAVE_CAIRO_PNG
936 "png",
937#else
938 NULL,
939#endif
940 pangocairo_view_create,
941 pangocairo_view_destroy,
942 pangocairo_view_get_context,
943 pangocairo_view_create_surface,
944 pangocairo_view_destroy_surface,
945 pangocairo_view_render,
946#ifdef HAVE_CAIRO_PNG
947 pangocairo_view_write,
948#else
949 NULL,
950#endif
951 pangocairo_view_create_window,
952 pangocairo_view_destroy_window,
953 pangocairo_view_display,
954 NULL,
955 NULL,
956 pangocairo_view_get_option_group
957};
958

source code of gtk/subprojects/pango/utils/viewer-pangocairo.c