1 | /* Pango/Font Rendering |
2 | * |
3 | * Demonstrates various aspects of font rendering, |
4 | * such as hinting, antialiasing and grid alignment. |
5 | * |
6 | * The demo lets you explore font rendering options |
7 | * interactively to get a feeling for they affect the |
8 | * shape and positioning of the glyphs. |
9 | */ |
10 | |
11 | #include <gtk/gtk.h> |
12 | |
13 | static GtkWidget *window = NULL; |
14 | static GtkWidget *font_button = NULL; |
15 | static GtkWidget *entry = NULL; |
16 | static GtkWidget *image = NULL; |
17 | static GtkWidget *hinting = NULL; |
18 | static GtkWidget *anti_alias = NULL; |
19 | static GtkWidget *hint_metrics = NULL; |
20 | static GtkWidget *up_button = NULL; |
21 | static GtkWidget *down_button = NULL; |
22 | static GtkWidget *text_radio = NULL; |
23 | static GtkWidget *show_grid = NULL; |
24 | static GtkWidget *show_extents = NULL; |
25 | static GtkWidget *show_pixels = NULL; |
26 | static GtkWidget *show_outlines = NULL; |
27 | |
28 | static PangoContext *context; |
29 | |
30 | static int scale = 7; |
31 | static double pixel_alpha = 1.0; |
32 | static double outline_alpha = 0.0; |
33 | |
34 | static void |
35 | update_image (void) |
36 | { |
37 | const char *text; |
38 | PangoFontDescription *desc; |
39 | PangoLayout *layout; |
40 | PangoRectangle ink, logical; |
41 | int baseline; |
42 | cairo_surface_t *surface; |
43 | cairo_t *cr; |
44 | GdkPixbuf *pixbuf; |
45 | GdkPixbuf *pixbuf2; |
46 | const char *hint; |
47 | cairo_font_options_t *fopt; |
48 | cairo_hint_style_t hintstyle; |
49 | cairo_hint_metrics_t hintmetrics; |
50 | cairo_antialias_t antialias; |
51 | cairo_path_t *path; |
52 | |
53 | if (!context) |
54 | context = gtk_widget_create_pango_context (widget: image); |
55 | |
56 | text = gtk_editable_get_text (GTK_EDITABLE (entry)); |
57 | desc = gtk_font_chooser_get_font_desc (GTK_FONT_CHOOSER (font_button)); |
58 | |
59 | fopt = cairo_font_options_copy (original: pango_cairo_context_get_font_options (context)); |
60 | |
61 | hint = gtk_combo_box_get_active_id (GTK_COMBO_BOX (hinting)); |
62 | hintstyle = CAIRO_HINT_STYLE_DEFAULT; |
63 | if (hint) |
64 | { |
65 | if (strcmp (s1: hint, s2: "none" ) == 0) |
66 | hintstyle = CAIRO_HINT_STYLE_NONE; |
67 | else if (strcmp (s1: hint, s2: "slight" ) == 0) |
68 | hintstyle = CAIRO_HINT_STYLE_SLIGHT; |
69 | else if (strcmp (s1: hint, s2: "medium" ) == 0) |
70 | hintstyle = CAIRO_HINT_STYLE_MEDIUM; |
71 | else if (strcmp (s1: hint, s2: "full" ) == 0) |
72 | hintstyle = CAIRO_HINT_STYLE_FULL; |
73 | } |
74 | cairo_font_options_set_hint_style (options: fopt, hint_style: hintstyle); |
75 | |
76 | if (gtk_check_button_get_active (GTK_CHECK_BUTTON (hint_metrics))) |
77 | hintmetrics = CAIRO_HINT_METRICS_ON; |
78 | else |
79 | hintmetrics = CAIRO_HINT_METRICS_OFF; |
80 | cairo_font_options_set_hint_metrics (options: fopt, hint_metrics: hintmetrics); |
81 | |
82 | if (gtk_check_button_get_active (GTK_CHECK_BUTTON (anti_alias))) |
83 | antialias = CAIRO_ANTIALIAS_GRAY; |
84 | else |
85 | antialias = CAIRO_ANTIALIAS_NONE; |
86 | cairo_font_options_set_antialias (options: fopt, antialias); |
87 | |
88 | pango_context_set_round_glyph_positions (context, round_positions: hintmetrics == CAIRO_HINT_METRICS_ON); |
89 | pango_cairo_context_set_font_options (context, options: fopt); |
90 | cairo_font_options_destroy (options: fopt); |
91 | pango_context_changed (context); |
92 | |
93 | if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (text_radio))) |
94 | { |
95 | layout = pango_layout_new (context); |
96 | pango_layout_set_font_description (layout, desc); |
97 | pango_layout_set_text (layout, text, length: -1); |
98 | pango_layout_get_extents (layout, ink_rect: &ink, logical_rect: &logical); |
99 | baseline = pango_layout_get_baseline (layout); |
100 | |
101 | pango_extents_to_pixels (inclusive: &ink, NULL); |
102 | |
103 | surface = cairo_image_surface_create (format: CAIRO_FORMAT_ARGB32, width: ink.width + 20, height: ink.height + 20); |
104 | cr = cairo_create (target: surface); |
105 | cairo_set_source_rgb (cr, red: 1, green: 1, blue: 1); |
106 | cairo_paint (cr); |
107 | |
108 | cairo_set_source_rgba (cr, red: 0, green: 0, blue: 0, alpha: pixel_alpha); |
109 | |
110 | cairo_move_to (cr, x: 10, y: 10); |
111 | pango_cairo_show_layout (cr, layout); |
112 | |
113 | pango_cairo_layout_path (cr, layout); |
114 | path = cairo_copy_path (cr); |
115 | |
116 | cairo_destroy (cr); |
117 | g_object_unref (object: layout); |
118 | |
119 | pixbuf = gdk_pixbuf_get_from_surface (surface, src_x: 0, src_y: 0, width: cairo_image_surface_get_width (surface), height: cairo_image_surface_get_height (surface)); |
120 | pixbuf2 = gdk_pixbuf_scale_simple (src: pixbuf, dest_width: gdk_pixbuf_get_width (pixbuf) * scale, dest_height: gdk_pixbuf_get_height (pixbuf) * scale, interp_type: GDK_INTERP_NEAREST); |
121 | |
122 | g_object_unref (object: pixbuf); |
123 | cairo_surface_destroy (surface); |
124 | |
125 | surface = cairo_image_surface_create_for_data (data: gdk_pixbuf_get_pixels (pixbuf: pixbuf2), |
126 | format: CAIRO_FORMAT_ARGB32, |
127 | width: gdk_pixbuf_get_width (pixbuf: pixbuf2), |
128 | height: gdk_pixbuf_get_height (pixbuf: pixbuf2), |
129 | stride: gdk_pixbuf_get_rowstride (pixbuf: pixbuf2)); |
130 | |
131 | cr = cairo_create (target: surface); |
132 | cairo_set_line_width (cr, width: 1); |
133 | |
134 | if (gtk_check_button_get_active (GTK_CHECK_BUTTON (show_grid))) |
135 | { |
136 | int i; |
137 | cairo_set_source_rgba (cr, red: 0.2, green: 0, blue: 0, alpha: 0.2); |
138 | for (i = 1; i < ink.height + 20; i++) |
139 | { |
140 | cairo_move_to (cr, x: 0, y: scale * i - 0.5); |
141 | cairo_line_to (cr, x: scale * (ink.width + 20), y: scale * i - 0.5); |
142 | cairo_stroke (cr); |
143 | } |
144 | for (i = 1; i < ink.width + 20; i++) |
145 | { |
146 | cairo_move_to (cr, x: scale * i - 0.5, y: 0); |
147 | cairo_line_to (cr, x: scale * i - 0.5, y: scale * (ink.height + 20)); |
148 | cairo_stroke (cr); |
149 | } |
150 | } |
151 | |
152 | if (gtk_check_button_get_active (GTK_CHECK_BUTTON (show_extents))) |
153 | { |
154 | cairo_set_source_rgb (cr, red: 0, green: 0, blue: 1); |
155 | |
156 | cairo_rectangle (cr, |
157 | x: scale * (10 + pango_units_to_double (i: logical.x)) - 0.5, |
158 | y: scale * (10 + pango_units_to_double (i: logical.y)) - 0.5, |
159 | width: scale * pango_units_to_double (i: logical.width) + 1, |
160 | height: scale * pango_units_to_double (i: logical.height) + 1); |
161 | cairo_stroke (cr); |
162 | cairo_move_to (cr, x: scale * (10 + pango_units_to_double (i: logical.x)) - 0.5, |
163 | y: scale * (10 + pango_units_to_double (i: baseline)) - 0.5); |
164 | cairo_line_to (cr, x: scale * (10 + pango_units_to_double (i: logical.x + logical.width)) + 1, |
165 | y: scale * (10 + pango_units_to_double (i: baseline)) - 0.5); |
166 | cairo_stroke (cr); |
167 | cairo_set_source_rgb (cr, red: 1, green: 0, blue: 0); |
168 | cairo_rectangle (cr, |
169 | x: scale * (10 + ink.x) - 0.5, |
170 | y: scale * (10 + ink.y) - 0.5, |
171 | width: scale * ink.width + 1, |
172 | height: scale * ink.height + 1); |
173 | cairo_stroke (cr); |
174 | } |
175 | |
176 | for (int i = 0; i < path->num_data; i += path->data[i].header.length) |
177 | { |
178 | cairo_path_data_t *data = &path->data[i]; |
179 | switch (data->header.type) |
180 | { |
181 | case CAIRO_PATH_CURVE_TO: |
182 | data[3].point.x *= scale; data[3].point.y *= scale; |
183 | data[2].point.x *= scale; data[2].point.y *= scale; |
184 | data[1].point.x *= scale; data[1].point.y *= scale; |
185 | break; |
186 | case CAIRO_PATH_LINE_TO: |
187 | case CAIRO_PATH_MOVE_TO: |
188 | data[1].point.x *= scale; data[1].point.y *= scale; |
189 | break; |
190 | case CAIRO_PATH_CLOSE_PATH: |
191 | break; |
192 | default: |
193 | g_assert_not_reached (); |
194 | } |
195 | } |
196 | |
197 | cairo_set_source_rgba (cr, red: 0, green: 0, blue: 0, alpha: outline_alpha); |
198 | cairo_move_to (cr, x: scale * 20 - 0.5, y: scale * 20 - 0.5); |
199 | cairo_append_path (cr, path); |
200 | cairo_stroke (cr); |
201 | |
202 | cairo_surface_destroy (surface); |
203 | cairo_destroy (cr); |
204 | |
205 | cairo_path_destroy (path); |
206 | } |
207 | else |
208 | { |
209 | PangoLayoutIter *iter; |
210 | PangoLayoutRun *run; |
211 | PangoGlyphInfo *g; |
212 | int i, j; |
213 | GString *str; |
214 | gunichar ch; |
215 | |
216 | if (*text == '\0') |
217 | text = " " ; |
218 | |
219 | ch = g_utf8_get_char (p: text); |
220 | |
221 | str = g_string_new (init: "" ); |
222 | |
223 | for (i = 0; i < 4; i++) |
224 | { |
225 | g_string_append_unichar (string: str, wc: ch); |
226 | g_string_append_unichar (string: str, wc: 0x200c); |
227 | } |
228 | |
229 | layout = pango_layout_new (context); |
230 | pango_layout_set_font_description (layout, desc); |
231 | pango_layout_set_text (layout, text: str->str, length: -1); |
232 | g_string_free (string: str, TRUE); |
233 | pango_layout_get_extents (layout, ink_rect: &ink, logical_rect: &logical); |
234 | pango_extents_to_pixels (inclusive: &logical, NULL); |
235 | |
236 | surface = cairo_image_surface_create (format: CAIRO_FORMAT_ARGB32, width: logical.width * 3 / 2, height: 4*logical.height); |
237 | cr = cairo_create (target: surface); |
238 | cairo_set_source_rgb (cr, red: 1, green: 1, blue: 1); |
239 | cairo_paint (cr); |
240 | |
241 | iter = pango_layout_get_iter (layout); |
242 | run = pango_layout_iter_get_run (iter); |
243 | |
244 | cairo_set_source_rgb (cr, red: 0, green: 0, blue: 0); |
245 | for (i = 0; i < 4; i++) |
246 | { |
247 | g = &(run->glyphs->glyphs[2*i]); |
248 | g->geometry.width = PANGO_UNITS_ROUND (g->geometry.width * 3 / 2); |
249 | } |
250 | |
251 | for (j = 0; j < 4; j++) |
252 | { |
253 | for (i = 0; i < 4; i++) |
254 | { |
255 | g = &(run->glyphs->glyphs[2*i]); |
256 | g->geometry.x_offset = i * (PANGO_SCALE / 4); |
257 | g->geometry.y_offset = j * (PANGO_SCALE / 4); |
258 | } |
259 | |
260 | cairo_move_to (cr, x: 0, y: j * logical.height); |
261 | pango_cairo_show_layout (cr, layout); |
262 | } |
263 | |
264 | cairo_destroy (cr); |
265 | pango_layout_iter_free (iter); |
266 | g_object_unref (object: layout); |
267 | |
268 | pixbuf = gdk_pixbuf_get_from_surface (surface, src_x: 0, src_y: 0, width: cairo_image_surface_get_width (surface), height: cairo_image_surface_get_height (surface)); |
269 | pixbuf2 = gdk_pixbuf_scale_simple (src: pixbuf, dest_width: gdk_pixbuf_get_width (pixbuf) * scale, dest_height: gdk_pixbuf_get_height (pixbuf) * scale, interp_type: GDK_INTERP_NEAREST); |
270 | g_object_unref (object: pixbuf); |
271 | cairo_surface_destroy (surface); |
272 | } |
273 | |
274 | gtk_picture_set_pixbuf (self: GTK_PICTURE (ptr: image), pixbuf: pixbuf2); |
275 | |
276 | g_object_unref (object: pixbuf2); |
277 | |
278 | pango_font_description_free (desc); |
279 | } |
280 | |
281 | static gboolean fading = FALSE; |
282 | static double start_pixel_alpha; |
283 | static double end_pixel_alpha; |
284 | static double start_outline_alpha; |
285 | static double end_outline_alpha; |
286 | static gint64 start_time; |
287 | static gint64 end_time; |
288 | |
289 | static double |
290 | ease_out_cubic (double t) |
291 | { |
292 | double p = t - 1; |
293 | return p * p * p + 1; |
294 | } |
295 | |
296 | static gboolean |
297 | change_alpha (GtkWidget *widget, |
298 | GdkFrameClock *clock, |
299 | gpointer user_data) |
300 | { |
301 | gint64 now = g_get_monotonic_time (); |
302 | double t; |
303 | |
304 | t = ease_out_cubic (t: (now - start_time) / (double) (end_time - start_time)); |
305 | |
306 | pixel_alpha = start_pixel_alpha + (end_pixel_alpha - start_pixel_alpha) * t; |
307 | outline_alpha = start_outline_alpha + (end_outline_alpha - start_outline_alpha) * t; |
308 | |
309 | update_image (); |
310 | |
311 | if (now >= end_time) |
312 | { |
313 | fading = FALSE; |
314 | return G_SOURCE_REMOVE; |
315 | } |
316 | |
317 | return G_SOURCE_CONTINUE; |
318 | } |
319 | |
320 | static void |
321 | start_alpha_fade (void) |
322 | { |
323 | gboolean pixels; |
324 | gboolean outlines; |
325 | |
326 | if (fading) |
327 | return; |
328 | |
329 | pixels = gtk_check_button_get_active (GTK_CHECK_BUTTON (show_pixels)); |
330 | outlines = gtk_check_button_get_active (GTK_CHECK_BUTTON (show_outlines)); |
331 | |
332 | start_pixel_alpha = pixel_alpha; |
333 | if (pixels && outlines) |
334 | end_pixel_alpha = 0.5; |
335 | else if (pixels) |
336 | end_pixel_alpha = 1; |
337 | else |
338 | end_pixel_alpha = 0; |
339 | |
340 | start_outline_alpha = outline_alpha; |
341 | if (outlines) |
342 | end_outline_alpha = 1.0; |
343 | else |
344 | end_outline_alpha = 0.0; |
345 | |
346 | start_time = g_get_monotonic_time (); |
347 | end_time = start_time + G_TIME_SPAN_SECOND / 2; |
348 | |
349 | fading = TRUE; |
350 | gtk_widget_add_tick_callback (widget: window, callback: change_alpha, NULL, NULL); |
351 | } |
352 | |
353 | static void |
354 | update_buttons (void) |
355 | { |
356 | gtk_widget_set_sensitive (widget: up_button, sensitive: scale < 32); |
357 | gtk_widget_set_sensitive (widget: down_button, sensitive: scale > 1); |
358 | } |
359 | |
360 | static gboolean |
361 | scale_up (GtkWidget *widget, |
362 | GVariant *args, |
363 | gpointer user_data) |
364 | { |
365 | scale += 1; |
366 | update_buttons (); |
367 | update_image (); |
368 | return TRUE; |
369 | } |
370 | |
371 | static gboolean |
372 | scale_down (GtkWidget *widget, |
373 | GVariant *args, |
374 | gpointer user_data) |
375 | { |
376 | scale -= 1; |
377 | update_buttons (); |
378 | update_image (); |
379 | return TRUE; |
380 | } |
381 | |
382 | GtkWidget * |
383 | do_fontrendering (GtkWidget *do_widget) |
384 | { |
385 | if (!window) |
386 | { |
387 | GtkBuilder *builder; |
388 | |
389 | builder = gtk_builder_new_from_resource (resource_path: "/fontrendering/fontrendering.ui" ); |
390 | window = GTK_WIDGET (gtk_builder_get_object (builder, "window" )); |
391 | gtk_window_set_display (GTK_WINDOW (window), |
392 | display: gtk_widget_get_display (widget: do_widget)); |
393 | g_object_add_weak_pointer (G_OBJECT (window), weak_pointer_location: (gpointer *)&window); |
394 | font_button = GTK_WIDGET (gtk_builder_get_object (builder, "font_button" )); |
395 | up_button = GTK_WIDGET (gtk_builder_get_object (builder, "up_button" )); |
396 | down_button = GTK_WIDGET (gtk_builder_get_object (builder, "down_button" )); |
397 | entry = GTK_WIDGET (gtk_builder_get_object (builder, "entry" )); |
398 | image = GTK_WIDGET (gtk_builder_get_object (builder, "image" )); |
399 | hinting = GTK_WIDGET (gtk_builder_get_object (builder, "hinting" )); |
400 | anti_alias = GTK_WIDGET (gtk_builder_get_object (builder, "antialias" )); |
401 | hint_metrics = GTK_WIDGET (gtk_builder_get_object (builder, "hint_metrics" )); |
402 | text_radio = GTK_WIDGET (gtk_builder_get_object (builder, "text_radio" )); |
403 | show_grid = GTK_WIDGET (gtk_builder_get_object (builder, "show_grid" )); |
404 | show_extents = GTK_WIDGET (gtk_builder_get_object (builder, "show_extents" )); |
405 | show_pixels = GTK_WIDGET (gtk_builder_get_object (builder, "show_pixels" )); |
406 | show_outlines = GTK_WIDGET (gtk_builder_get_object (builder, "show_outlines" )); |
407 | |
408 | g_signal_connect (up_button, "clicked" , G_CALLBACK (scale_up), NULL); |
409 | g_signal_connect (down_button, "clicked" , G_CALLBACK (scale_down), NULL); |
410 | g_signal_connect (entry, "notify::text" , G_CALLBACK (update_image), NULL); |
411 | g_signal_connect (font_button, "notify::font-desc" , G_CALLBACK (update_image), NULL); |
412 | g_signal_connect (hinting, "notify::active" , G_CALLBACK (update_image), NULL); |
413 | g_signal_connect (anti_alias, "notify::active" , G_CALLBACK (update_image), NULL); |
414 | g_signal_connect (hint_metrics, "notify::active" , G_CALLBACK (update_image), NULL); |
415 | g_signal_connect (text_radio, "notify::active" , G_CALLBACK (update_image), NULL); |
416 | g_signal_connect (show_grid, "notify::active" , G_CALLBACK (update_image), NULL); |
417 | g_signal_connect (show_extents, "notify::active" , G_CALLBACK (update_image), NULL); |
418 | g_signal_connect (show_pixels, "notify::active" , G_CALLBACK (start_alpha_fade), NULL); |
419 | g_signal_connect (show_outlines, "notify::active" , G_CALLBACK (start_alpha_fade), NULL); |
420 | |
421 | update_image (); |
422 | |
423 | g_object_unref (object: builder); |
424 | } |
425 | |
426 | if (!gtk_widget_get_visible (widget: window)) |
427 | gtk_widget_show (widget: window); |
428 | else |
429 | gtk_window_destroy (GTK_WINDOW (window)); |
430 | |
431 | return window; |
432 | } |
433 | |