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 | |
31 | static int opt_annotate = 0; |
32 | |
33 | typedef 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 | |
44 | static gpointer |
45 | pangocairo_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 | |
84 | static void |
85 | pangocairo_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 | |
98 | static PangoContext * |
99 | pangocairo_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 | |
111 | typedef struct |
112 | { |
113 | gpointer backend; |
114 | |
115 | cairo_surface_t *cairo; |
116 | } CairoSurface; |
117 | |
118 | static gpointer |
119 | pangocairo_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 | |
138 | static void |
139 | pangocairo_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 | |
151 | enum { |
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 | |
167 | static 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 | |
186 | static 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 | |
191 | static void |
192 | render_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 | |
735 | static void |
736 | transform_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 | |
763 | static void |
764 | pangocairo_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 |
796 | static cairo_status_t |
797 | write_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 | |
809 | static void |
810 | pangocairo_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 | |
822 | static gpointer |
823 | pangocairo_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 | |
838 | static void |
839 | pangocairo_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 | |
848 | static gpointer |
849 | pangocairo_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 | |
865 | static gboolean |
866 | parse_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 | |
909 | static GOptionGroup * |
910 | pangocairo_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 | |
932 | const 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 | |