1 | /* viewer-render.c: Common code for rendering in viewers |
2 | * |
3 | * Copyright (C) 1999, 2004 Red Hat Software |
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 | #include "config.h" |
22 | #include <errno.h> |
23 | #include <math.h> |
24 | #include <stdarg.h> |
25 | #include <stdlib.h> |
26 | #include <stdio.h> |
27 | #include <string.h> |
28 | |
29 | #include <glib.h> |
30 | #include <glib/gprintf.h> |
31 | #include <pango/pango.h> |
32 | |
33 | #include "viewer-render.h" |
34 | |
35 | gboolean opt_display = TRUE; |
36 | int opt_dpi = 96; |
37 | gboolean opt_pixels = FALSE; |
38 | gboolean opt_pango_units = FALSE; |
39 | const char *opt_font = "" ; |
40 | gboolean = FALSE; |
41 | const char *opt_output = NULL; |
42 | int opt_margin_t = 10; |
43 | int opt_margin_r = 10; |
44 | int opt_margin_b = 10; |
45 | int opt_margin_l = 10; |
46 | int opt_markup = FALSE; |
47 | gboolean opt_rtl = FALSE; |
48 | double opt_rotate = 0; |
49 | gboolean opt_auto_dir = TRUE; |
50 | const char *opt_text = NULL; |
51 | gboolean opt_waterfall = FALSE; |
52 | int opt_width = -1; |
53 | int opt_height = -1; |
54 | int opt_indent = 0; |
55 | int opt_spacing = 0; |
56 | double opt_line_spacing = -1.0; |
57 | gboolean opt_justify = 0; |
58 | gboolean opt_justify_last_line = 0; |
59 | int opt_runs = 1; |
60 | PangoAlignment opt_align = PANGO_ALIGN_LEFT; |
61 | PangoEllipsizeMode opt_ellipsize = PANGO_ELLIPSIZE_NONE; |
62 | PangoGravity opt_gravity = PANGO_GRAVITY_SOUTH; |
63 | PangoGravityHint opt_gravity_hint = PANGO_GRAVITY_HINT_NATURAL; |
64 | HintMode opt_hinting = HINT_DEFAULT; |
65 | HintMetrics opt_hint_metrics = HINT_METRICS_DEFAULT; |
66 | SubpixelOrder opt_subpixel_order = SUBPIXEL_DEFAULT; |
67 | Antialias opt_antialias = ANTIALIAS_DEFAULT; |
68 | gboolean opt_subpixel_positions = FALSE; |
69 | PangoWrapMode opt_wrap = PANGO_WRAP_WORD_CHAR; |
70 | gboolean opt_wrap_set = FALSE; |
71 | static const char *opt_pangorc = NULL; /* Unused */ |
72 | const PangoViewer *opt_viewer = NULL; |
73 | const char *opt_language = NULL; |
74 | gboolean opt_single_par = FALSE; |
75 | PangoColor opt_fg_color = {0, 0, 0}; |
76 | guint16 opt_fg_alpha = 65535; |
77 | gboolean opt_bg_set = FALSE; |
78 | PangoColor opt_bg_color = {65535, 65535, 65535}; |
79 | guint16 opt_bg_alpha = 65535; |
80 | gboolean opt_serialized = FALSE; |
81 | const char *opt_serialized_output; |
82 | const char *file_arg; |
83 | |
84 | /* Text (or markup) to render */ |
85 | static char *text; |
86 | |
87 | void |
88 | fail (const char *format, ...) |
89 | { |
90 | const char *msg; |
91 | |
92 | va_list vap; |
93 | va_start (vap, format); |
94 | msg = g_strdup_vprintf (format, args: vap); |
95 | g_printerr (format: "%s: %s\n" , g_get_prgname (), msg); |
96 | |
97 | exit (status: 1); |
98 | } |
99 | |
100 | static PangoLayout * |
101 | make_layout(PangoContext *context, |
102 | const char *text, |
103 | double size) |
104 | { |
105 | static PangoFontDescription *font_description; |
106 | PangoAlignment align; |
107 | PangoLayout *layout; |
108 | |
109 | if (opt_serialized) |
110 | { |
111 | char *data; |
112 | gsize len; |
113 | GBytes *bytes; |
114 | GError *error = NULL; |
115 | |
116 | if (!g_file_get_contents (filename: file_arg, contents: &data, length: &len, error: &error)) |
117 | fail (format: "%s\n" , error->message); |
118 | bytes = g_bytes_new_take (data, size: len); |
119 | layout = pango_layout_deserialize (context, bytes, flags: PANGO_LAYOUT_DESERIALIZE_CONTEXT, error: &error); |
120 | if (!layout) |
121 | fail (format: "%s\n" , error->message); |
122 | g_bytes_unref (bytes); |
123 | goto out; |
124 | } |
125 | |
126 | layout = pango_layout_new (context); |
127 | if (opt_markup) |
128 | pango_layout_set_markup (layout, markup: text, length: -1); |
129 | else |
130 | pango_layout_set_text (layout, text, length: -1); |
131 | |
132 | pango_layout_set_auto_dir (layout, auto_dir: opt_auto_dir); |
133 | pango_layout_set_ellipsize (layout, ellipsize: opt_ellipsize); |
134 | pango_layout_set_justify (layout, justify: opt_justify); |
135 | pango_layout_set_justify_last_line (layout, justify: opt_justify_last_line); |
136 | pango_layout_set_single_paragraph_mode (layout, setting: opt_single_par); |
137 | pango_layout_set_wrap (layout, wrap: opt_wrap); |
138 | |
139 | font_description = pango_font_description_from_string (str: opt_font); |
140 | if (size > 0) |
141 | pango_font_description_set_size (desc: font_description, size: size * PANGO_SCALE); |
142 | |
143 | if (opt_width >= 0) |
144 | { |
145 | if (opt_pango_units) |
146 | pango_layout_set_width (layout, width: opt_width); |
147 | else |
148 | pango_layout_set_width (layout, width: (opt_width * opt_dpi * PANGO_SCALE + 36) / 72); |
149 | } |
150 | |
151 | if (opt_height >= 0) |
152 | { |
153 | if (opt_pango_units) |
154 | pango_layout_set_width (layout, width: opt_height); |
155 | else |
156 | pango_layout_set_height (layout, height: (opt_height * opt_dpi * PANGO_SCALE + 36) / 72); |
157 | } |
158 | else |
159 | pango_layout_set_height (layout, height: opt_height); |
160 | |
161 | if (opt_indent != 0) |
162 | { |
163 | if (opt_pango_units) |
164 | pango_layout_set_indent (layout, indent: opt_indent); |
165 | else |
166 | pango_layout_set_indent (layout, indent: (opt_indent * opt_dpi * PANGO_SCALE + 36) / 72); |
167 | } |
168 | |
169 | if (opt_spacing != 0) |
170 | { |
171 | if (opt_pango_units) |
172 | pango_layout_set_spacing (layout, spacing: opt_spacing); |
173 | else |
174 | pango_layout_set_spacing (layout, spacing: (opt_spacing * opt_dpi * PANGO_SCALE + 36) / 72); |
175 | pango_layout_set_line_spacing (layout, factor: 0.0); |
176 | } |
177 | if (opt_line_spacing >= 0.0) |
178 | pango_layout_set_line_spacing (layout, factor: (float)opt_line_spacing); |
179 | |
180 | align = opt_align; |
181 | if (align != PANGO_ALIGN_CENTER && |
182 | pango_context_get_base_dir (context) != PANGO_DIRECTION_LTR) { |
183 | /* pango reverses left and right if base dir ir rtl. so we should |
184 | * reverse to cancel that. unfortunately it also does that for |
185 | * rtl paragraphs, so we cannot really get left/right. all we get |
186 | * is default/other-side. */ |
187 | align = PANGO_ALIGN_LEFT + PANGO_ALIGN_RIGHT - align; |
188 | } |
189 | pango_layout_set_alignment (layout, alignment: align); |
190 | |
191 | pango_layout_set_font_description (layout, desc: font_description); |
192 | |
193 | pango_font_description_free (desc: font_description); |
194 | |
195 | out: |
196 | if (opt_serialized_output) |
197 | { |
198 | GError *error = NULL; |
199 | |
200 | if (!pango_layout_write_to_file (layout, |
201 | flags: PANGO_LAYOUT_SERIALIZE_CONTEXT|PANGO_LAYOUT_SERIALIZE_OUTPUT, |
202 | filename: opt_serialized_output, |
203 | error: &error)) |
204 | fail (format: "%s\n" , error->message); |
205 | } |
206 | |
207 | return layout; |
208 | } |
209 | |
210 | gchar * |
211 | get_options_string (void) |
212 | { |
213 | PangoFontDescription *font_description = pango_font_description_from_string (str: opt_font); |
214 | gchar *font_name; |
215 | gchar *result; |
216 | |
217 | if (opt_waterfall) |
218 | pango_font_description_unset_fields (desc: font_description, to_unset: PANGO_FONT_MASK_SIZE); |
219 | |
220 | font_name = pango_font_description_to_string (desc: font_description); |
221 | result = g_strdup_printf (format: "%s: %s (%d dpi)" , opt_viewer->name, font_name, opt_dpi); |
222 | pango_font_description_free (desc: font_description); |
223 | g_free (mem: font_name); |
224 | |
225 | return result; |
226 | } |
227 | |
228 | static void |
229 | output_body (PangoLayout *layout, |
230 | RenderCallback render_cb, |
231 | gpointer cb_context, |
232 | gpointer cb_data, |
233 | int *width, |
234 | int *height, |
235 | gboolean supports_matrix) |
236 | { |
237 | PangoRectangle logical_rect; |
238 | int size, start_size, end_size, increment; |
239 | int x = 0, y = 0; |
240 | |
241 | if (!supports_matrix) |
242 | { |
243 | const PangoMatrix* matrix; |
244 | const PangoMatrix identity = PANGO_MATRIX_INIT; |
245 | PangoContext *context = pango_layout_get_context (layout); |
246 | matrix = pango_context_get_matrix (context); |
247 | if (matrix) |
248 | { |
249 | x += matrix->x0; |
250 | y += matrix->y0; |
251 | } |
252 | pango_context_set_matrix (context, matrix: &identity); |
253 | pango_layout_context_changed (layout); |
254 | } |
255 | |
256 | if (opt_waterfall) |
257 | { |
258 | start_size = 8; |
259 | end_size = 48; |
260 | increment = 4; |
261 | } |
262 | else |
263 | { |
264 | start_size = end_size = -1; |
265 | increment = 1; |
266 | } |
267 | |
268 | *width = 0; |
269 | *height = 0; |
270 | |
271 | for (size = start_size; size <= end_size; size += increment) |
272 | { |
273 | if (size > 0) |
274 | { |
275 | PangoFontDescription *desc = pango_font_description_copy (desc: pango_layout_get_font_description (layout)); |
276 | pango_font_description_set_size (desc, size: size * PANGO_SCALE); |
277 | pango_layout_set_font_description (layout, desc); |
278 | pango_font_description_free (desc); |
279 | } |
280 | |
281 | pango_layout_get_pixel_extents (layout, NULL, logical_rect: &logical_rect); |
282 | |
283 | if (render_cb) |
284 | (*render_cb) (layout, x, y+*height, cb_context, cb_data); |
285 | |
286 | *width = MAX (*width, |
287 | MAX (logical_rect.x + logical_rect.width, |
288 | PANGO_PIXELS (pango_layout_get_width (layout)))); |
289 | *height += MAX (logical_rect.y + logical_rect.height, |
290 | PANGO_PIXELS (pango_layout_get_height (layout))); |
291 | } |
292 | } |
293 | |
294 | static void |
295 | set_transform (PangoContext *context, |
296 | TransformCallback transform_cb, |
297 | gpointer cb_context, |
298 | gpointer cb_data, |
299 | PangoMatrix *matrix) |
300 | { |
301 | pango_context_set_matrix (context, matrix); |
302 | if (transform_cb) |
303 | (*transform_cb) (context, matrix, cb_context, cb_data); |
304 | } |
305 | |
306 | void |
307 | do_output (PangoContext *context, |
308 | RenderCallback render_cb, |
309 | TransformCallback transform_cb, |
310 | gpointer cb_context, |
311 | gpointer cb_data, |
312 | int *width_out, |
313 | int *height_out) |
314 | { |
315 | PangoLayout *layout; |
316 | PangoRectangle rect; |
317 | PangoMatrix matrix = PANGO_MATRIX_INIT; |
318 | PangoMatrix *orig_matrix; |
319 | gboolean supports_matrix; |
320 | int rotated_width, rotated_height; |
321 | int x = opt_margin_l; |
322 | int y = opt_margin_t; |
323 | int width, height; |
324 | |
325 | width = 0; |
326 | height = 0; |
327 | |
328 | orig_matrix = pango_matrix_copy (matrix: pango_context_get_matrix (context)); |
329 | /* If the backend sets an all-zero matrix on the context, |
330 | * means that it doesn't support transformations. |
331 | */ |
332 | supports_matrix = !orig_matrix || |
333 | (orig_matrix->xx != 0. || orig_matrix->xy != 0. || |
334 | orig_matrix->yx != 0. || orig_matrix->yy != 0. || |
335 | orig_matrix->x0 != 0. || orig_matrix->y0 != 0.); |
336 | |
337 | set_transform (context, transform_cb, cb_context, cb_data, NULL); |
338 | |
339 | pango_context_set_language (context, |
340 | language: opt_language ? pango_language_from_string (language: opt_language) |
341 | : pango_language_get_default ()); |
342 | pango_context_set_base_dir (context, |
343 | direction: opt_rtl ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR); |
344 | |
345 | if (opt_header) |
346 | { |
347 | char *options_string = get_options_string (); |
348 | pango_context_set_base_gravity (context, gravity: PANGO_GRAVITY_SOUTH); |
349 | layout = make_layout (context, text: options_string, size: 10); |
350 | pango_layout_get_extents (layout, NULL, logical_rect: &rect); |
351 | |
352 | width = MAX (width, PANGO_PIXELS (rect.width)); |
353 | height += PANGO_PIXELS (rect.height); |
354 | |
355 | if (render_cb) |
356 | (*render_cb) (layout, x, y, cb_context, cb_data); |
357 | |
358 | y += PANGO_PIXELS (rect.height); |
359 | |
360 | g_object_unref (object: layout); |
361 | g_free (mem: options_string); |
362 | } |
363 | |
364 | if (opt_rotate != 0) |
365 | { |
366 | if (supports_matrix) |
367 | pango_matrix_rotate (matrix: &matrix, degrees: opt_rotate); |
368 | else |
369 | g_printerr (format: "The backend does not support rotated text\n" ); |
370 | } |
371 | |
372 | pango_context_set_base_gravity (context, gravity: opt_gravity); |
373 | pango_context_set_gravity_hint (context, hint: opt_gravity_hint); |
374 | |
375 | layout = make_layout (context, text, size: -1); |
376 | if (opt_serialized && supports_matrix) |
377 | { |
378 | const PangoMatrix *context_matrix = pango_context_get_matrix (context: pango_layout_get_context (layout)); |
379 | matrix = context_matrix ? *context_matrix : (PangoMatrix) PANGO_MATRIX_INIT; |
380 | } |
381 | |
382 | set_transform (context, transform_cb, cb_context, cb_data, matrix: &matrix); |
383 | |
384 | output_body (layout, |
385 | NULL, NULL, NULL, |
386 | width: &rotated_width, height: &rotated_height, |
387 | supports_matrix); |
388 | |
389 | rect.x = rect.y = 0; |
390 | rect.width = rotated_width; |
391 | rect.height = rotated_height; |
392 | |
393 | pango_matrix_transform_pixel_rectangle (matrix: &matrix, rect: &rect); |
394 | |
395 | matrix.x0 = x - rect.x; |
396 | matrix.y0 = y - rect.y; |
397 | |
398 | set_transform (context, transform_cb, cb_context, cb_data, matrix: &matrix); |
399 | |
400 | if (render_cb) |
401 | output_body (layout, |
402 | render_cb, cb_context, cb_data, |
403 | width: &rotated_width, height: &rotated_height, |
404 | supports_matrix); |
405 | |
406 | width = MAX (width, rect.width); |
407 | height += rect.height; |
408 | |
409 | width += opt_margin_l + opt_margin_r; |
410 | height += opt_margin_t + opt_margin_b; |
411 | |
412 | if (width_out) |
413 | *width_out = width; |
414 | if (height_out) |
415 | *height_out = height; |
416 | |
417 | pango_context_set_matrix (context, matrix: orig_matrix); |
418 | pango_matrix_free (matrix: orig_matrix); |
419 | g_object_unref (object: layout); |
420 | } |
421 | |
422 | static gboolean |
423 | parse_enum (GType type, |
424 | int *value, |
425 | const char *name, |
426 | const char *arg, |
427 | gpointer data G_GNUC_UNUSED, |
428 | GError **error) |
429 | { |
430 | char *possible_values = NULL; |
431 | gboolean ret; |
432 | |
433 | G_GNUC_BEGIN_IGNORE_DEPRECATIONS |
434 | ret = pango_parse_enum (type, |
435 | str: arg, |
436 | value, |
437 | FALSE, |
438 | possible_values: &possible_values); |
439 | G_GNUC_END_IGNORE_DEPRECATIONS |
440 | |
441 | if (!ret && error) |
442 | { |
443 | g_set_error(err: error, |
444 | G_OPTION_ERROR, |
445 | code: G_OPTION_ERROR_BAD_VALUE, |
446 | format: "Argument for %s must be one of %s" , |
447 | name, |
448 | possible_values); |
449 | } |
450 | |
451 | g_free (mem: possible_values); |
452 | |
453 | return ret; |
454 | } |
455 | |
456 | static gboolean |
457 | parse_align (const char *name, |
458 | const char *arg, |
459 | gpointer data, |
460 | GError **error) |
461 | { |
462 | return parse_enum (type: PANGO_TYPE_ALIGNMENT, value: (int*)(void*)&opt_align, |
463 | name, arg, data, error); |
464 | } |
465 | |
466 | static gboolean |
467 | parse_ellipsis (const char *name, |
468 | const char *arg, |
469 | gpointer data, |
470 | GError **error) |
471 | { |
472 | return parse_enum (type: PANGO_TYPE_ELLIPSIZE_MODE, value: (int*)(void*)&opt_ellipsize, |
473 | name, arg, data, error); |
474 | } |
475 | |
476 | static gboolean |
477 | parse_gravity (const char *name, |
478 | const char *arg, |
479 | gpointer data, |
480 | GError **error) |
481 | { |
482 | return parse_enum (type: PANGO_TYPE_GRAVITY, value: (int*)(void*)&opt_gravity, |
483 | name, arg, data, error); |
484 | } |
485 | |
486 | static gboolean |
487 | parse_gravity_hint (const char *name, |
488 | const char *arg, |
489 | gpointer data, |
490 | GError **error) |
491 | { |
492 | return parse_enum (type: PANGO_TYPE_GRAVITY_HINT, value: (int*)(void*)&opt_gravity_hint, |
493 | name, arg, data, error); |
494 | } |
495 | |
496 | static gboolean |
497 | parse_hinting (const char *name G_GNUC_UNUSED, |
498 | const char *arg, |
499 | gpointer data G_GNUC_UNUSED, |
500 | GError **error) |
501 | { |
502 | gboolean ret = TRUE; |
503 | |
504 | if (strcmp (s1: arg, s2: "none" ) == 0) |
505 | opt_hinting = HINT_NONE; |
506 | else if (strcmp (s1: arg, s2: "auto" ) == 0) |
507 | opt_hinting = HINT_AUTO; |
508 | else if (strcmp (s1: arg, s2: "slight" ) == 0) |
509 | opt_hinting = HINT_SLIGHT; |
510 | else if (strcmp (s1: arg, s2: "medium" ) == 0) |
511 | opt_hinting = HINT_MEDIUM; |
512 | else if (strcmp (s1: arg, s2: "full" ) == 0) |
513 | opt_hinting = HINT_FULL; |
514 | else |
515 | { |
516 | g_set_error(err: error, |
517 | G_OPTION_ERROR, |
518 | code: G_OPTION_ERROR_BAD_VALUE, |
519 | format: "Argument for --hinting must be one of none/auto/slight/medium/full" ); |
520 | ret = FALSE; |
521 | } |
522 | |
523 | return ret; |
524 | } |
525 | |
526 | static gboolean |
527 | parse_subpixel_order (const char *name, |
528 | const char *arg, |
529 | gpointer data, |
530 | GError **error) |
531 | { |
532 | gboolean ret = TRUE; |
533 | |
534 | if (strcmp (s1: arg, s2: "rgb" ) == 0) |
535 | opt_subpixel_order = SUBPIXEL_RGB; |
536 | else if (strcmp (s1: arg, s2: "bgr" ) == 0) |
537 | opt_subpixel_order = SUBPIXEL_BGR; |
538 | else if (strcmp (s1: arg, s2: "vrgb" ) == 0) |
539 | opt_subpixel_order = SUBPIXEL_VRGB; |
540 | else if (strcmp (s1: arg, s2: "vbgr" ) == 0) |
541 | opt_subpixel_order = SUBPIXEL_VBGR; |
542 | else |
543 | { |
544 | g_set_error (err: error, |
545 | G_OPTION_ERROR, |
546 | code: G_OPTION_ERROR_BAD_VALUE, |
547 | format: "Argument for --subpixel-order must be one of rgb/bgr/vrgb/vbgr" ); |
548 | ret = FALSE; |
549 | } |
550 | |
551 | return ret; |
552 | } |
553 | |
554 | static gboolean |
555 | parse_hint_metrics (const char *name, |
556 | const char *arg, |
557 | gpointer data, |
558 | GError **error) |
559 | { |
560 | gboolean ret = TRUE; |
561 | |
562 | if (strcmp (s1: arg, s2: "on" ) == 0) |
563 | opt_hint_metrics = HINT_METRICS_ON; |
564 | else if (strcmp (s1: arg, s2: "off" ) == 0) |
565 | opt_hint_metrics = HINT_METRICS_OFF; |
566 | else |
567 | { |
568 | g_set_error (err: error, |
569 | G_OPTION_ERROR, |
570 | code: G_OPTION_ERROR_BAD_VALUE, |
571 | format: "Argument for --hint-metrics must be one of on/off" ); |
572 | ret = FALSE; |
573 | } |
574 | |
575 | return ret; |
576 | } |
577 | |
578 | static gboolean |
579 | parse_antialias (const char *name, |
580 | const char *arg, |
581 | gpointer data, |
582 | GError **error) |
583 | { |
584 | gboolean ret = TRUE; |
585 | |
586 | if (strcmp (s1: arg, s2: "none" ) == 0) |
587 | opt_antialias = ANTIALIAS_NONE; |
588 | else if (strcmp (s1: arg, s2: "gray" ) == 0) |
589 | opt_antialias = ANTIALIAS_GRAY; |
590 | else if (strcmp (s1: arg, s2: "subpixel" ) == 0) |
591 | opt_antialias = ANTIALIAS_SUBPIXEL; |
592 | else |
593 | { |
594 | g_set_error (err: error, |
595 | G_OPTION_ERROR, |
596 | code: G_OPTION_ERROR_BAD_VALUE, |
597 | format: "Argument for --antialias must be one of none/gray/subpixel" ); |
598 | ret = FALSE; |
599 | } |
600 | |
601 | return ret; |
602 | } |
603 | static gboolean |
604 | parse_wrap (const char *name, |
605 | const char *arg, |
606 | gpointer data, |
607 | GError **error) |
608 | { |
609 | gboolean ret; |
610 | if ((ret = parse_enum (PANGO_TYPE_WRAP_MODE, (int*)(void*)&opt_wrap, |
611 | name, arg, data, error))) |
612 | { |
613 | opt_wrap_set = TRUE; |
614 | } |
615 | return ret; |
616 | } |
617 | |
618 | static gboolean |
619 | parse_rgba_color (PangoColor *color, |
620 | guint16 *alpha, |
621 | const char *name, |
622 | const char *arg, |
623 | gpointer data G_GNUC_UNUSED, |
624 | GError **error) |
625 | { |
626 | gboolean ret; |
627 | char buf[32]; |
628 | int len; |
629 | |
630 | len = strlen (s: arg); |
631 | /* handle alpha */ |
632 | if (*arg == '#' && (len == 5 || len == 9 || len == 17)) |
633 | { |
634 | int width, bits; |
635 | unsigned int a; |
636 | |
637 | bits = len - 1; |
638 | width = bits >> 2; |
639 | |
640 | strcpy (dest: buf, src: arg); |
641 | arg = buf; |
642 | |
643 | if (!sscanf (s: buf + len - width, format: "%x" , &a)) |
644 | { |
645 | ret = FALSE; |
646 | goto err; |
647 | } |
648 | buf[len - width] = '\0'; |
649 | |
650 | a <<= (16 - bits); |
651 | while (bits < 16) |
652 | { |
653 | a |= (a >> bits); |
654 | bits *= 2; |
655 | } |
656 | *alpha = a; |
657 | } |
658 | else |
659 | *alpha = 65535; |
660 | |
661 | ret = pango_color_parse (color, spec: arg); |
662 | |
663 | err: |
664 | if (!ret && error) |
665 | { |
666 | g_set_error(err: error, |
667 | G_OPTION_ERROR, |
668 | code: G_OPTION_ERROR_BAD_VALUE, |
669 | format: "Argument for %s must be a color name like red, or CSS-style #rrggbb / #rrggbbaa" , |
670 | name); |
671 | } |
672 | |
673 | return ret; |
674 | } |
675 | |
676 | static gboolean |
677 | parse_foreground (const char *name, |
678 | const char *arg, |
679 | gpointer data, |
680 | GError **error) |
681 | { |
682 | return parse_rgba_color (color: &opt_fg_color, alpha: &opt_fg_alpha, |
683 | name, arg, data, error); |
684 | } |
685 | |
686 | static gboolean |
687 | parse_background (const char *name, |
688 | const char *arg, |
689 | gpointer data, |
690 | GError **error) |
691 | { |
692 | opt_bg_set = TRUE; |
693 | |
694 | if (0 == strcmp (s1: "transparent" , s2: arg)) |
695 | { |
696 | opt_bg_alpha = 0; |
697 | return TRUE; |
698 | } |
699 | |
700 | return parse_rgba_color (color: &opt_bg_color, alpha: &opt_bg_alpha, |
701 | name, arg, data, error); |
702 | } |
703 | |
704 | static gboolean |
705 | parse_margin (const char *name G_GNUC_UNUSED, |
706 | const char *arg, |
707 | gpointer data G_GNUC_UNUSED, |
708 | GError **error) |
709 | { |
710 | switch (sscanf (s: arg, format: "%d%*[ ,]%d%*[ ,]%d%*[ ,]%d" , &opt_margin_t, &opt_margin_r, &opt_margin_b, &opt_margin_l)) |
711 | { |
712 | default: |
713 | case 0: |
714 | { |
715 | g_set_error(err: error, |
716 | G_OPTION_ERROR, |
717 | code: G_OPTION_ERROR_BAD_VALUE, |
718 | format: "Argument for --margin must be one to four space-separated numbers" ); |
719 | return FALSE; |
720 | } |
721 | case 1: opt_margin_r = opt_margin_t; |
722 | G_GNUC_FALLTHROUGH; |
723 | case 2: opt_margin_b = opt_margin_t; |
724 | G_GNUC_FALLTHROUGH; |
725 | case 3: opt_margin_l = opt_margin_r; |
726 | } |
727 | return TRUE; |
728 | } |
729 | |
730 | |
731 | static gchar * |
732 | backends_to_string (void) |
733 | { |
734 | GString *backends = g_string_new (NULL); |
735 | const PangoViewer **viewer; |
736 | |
737 | for (viewer = viewers; *viewer; viewer++) |
738 | if ((*viewer)->id) |
739 | { |
740 | g_string_append (string: backends, val: (*viewer)->id); |
741 | g_string_append_c (backends, '/'); |
742 | } |
743 | g_string_truncate (string: backends, MAX (0, (gint)backends->len - 1)); |
744 | |
745 | return g_string_free(string: backends,FALSE); |
746 | } |
747 | |
748 | static int |
749 | backends_get_count (void) |
750 | { |
751 | const PangoViewer **viewer; |
752 | int i = 0; |
753 | |
754 | for (viewer = viewers; *viewer; viewer++) |
755 | if ((*viewer)->id) |
756 | i++; |
757 | |
758 | return i; |
759 | } |
760 | |
761 | |
762 | static gchar * |
763 | backend_description (void) |
764 | { |
765 | GString *description = g_string_new(init: "Pango backend to use for rendering " ); |
766 | int backends_count = backends_get_count (); |
767 | |
768 | if (backends_count > 1) |
769 | g_string_append_printf(string: description,format: "(default: %s)" , (*viewers)->id); |
770 | else if (backends_count == 1) |
771 | g_string_append_printf(string: description,format: "(only available: %s)" , (*viewers)->id); |
772 | else |
773 | g_string_append_printf(string: description,format: "(no backends found!)" ); |
774 | |
775 | return g_string_free(string: description,FALSE); |
776 | |
777 | } |
778 | |
779 | static gboolean |
780 | parse_backend (const char *name G_GNUC_UNUSED, |
781 | const char *arg, |
782 | gpointer data G_GNUC_UNUSED, |
783 | GError **error) |
784 | { |
785 | gboolean ret = TRUE; |
786 | const PangoViewer **viewer; |
787 | |
788 | for (viewer = viewers; *viewer; viewer++) |
789 | if (!g_ascii_strcasecmp (s1: (*viewer)->id, s2: arg)) |
790 | break; |
791 | |
792 | if (*viewer) |
793 | opt_viewer = *viewer; |
794 | else |
795 | { |
796 | gchar *backends = backends_to_string (); |
797 | |
798 | g_set_error(err: error, |
799 | G_OPTION_ERROR, |
800 | code: G_OPTION_ERROR_BAD_VALUE, |
801 | format: "Available --backend options are: %s" , |
802 | backends); |
803 | g_free(mem: backends); |
804 | ret = FALSE; |
805 | } |
806 | |
807 | return ret; |
808 | } |
809 | |
810 | |
811 | static G_GNUC_NORETURN gboolean |
812 | show_version(const char *name G_GNUC_UNUSED, |
813 | const char *arg G_GNUC_UNUSED, |
814 | gpointer data G_GNUC_UNUSED, |
815 | GError **error G_GNUC_UNUSED) |
816 | { |
817 | g_printf(format: "%s (%s) %s\n" , g_get_prgname (), PACKAGE_NAME, PACKAGE_VERSION); |
818 | |
819 | if (PANGO_VERSION != pango_version()) |
820 | g_printf(format: "Linked Pango library has a different version: %s\n" , pango_version_string ()); |
821 | |
822 | exit(status: 0); |
823 | } |
824 | |
825 | void |
826 | parse_options (int argc, char *argv[]) |
827 | { |
828 | gchar *backend_options = backends_to_string (); |
829 | GOptionFlags backend_flag = backends_get_count () > 1 ? 0 : G_OPTION_FLAG_HIDDEN; |
830 | gchar *backend_desc = backend_description (); |
831 | GOptionEntry entries[] = |
832 | { |
833 | {"no-auto-dir" , 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_auto_dir, |
834 | "No layout direction according to contents" , NULL}, |
835 | {"backend" , 0, backend_flag, G_OPTION_ARG_CALLBACK, &parse_backend, |
836 | backend_desc, backend_options}, |
837 | {"background" , 0, 0, G_OPTION_ARG_CALLBACK, &parse_background, |
838 | "Set the background color" , "red/#rrggbb/#rrggbbaa/transparent" }, |
839 | {"no-display" , 'q', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_display, |
840 | "Do not display (just write to file or whatever)" , NULL}, |
841 | {"dpi" , 0, 0, G_OPTION_ARG_INT, &opt_dpi, |
842 | "Set the resolution" , "number" }, |
843 | {"align" , 0, 0, G_OPTION_ARG_CALLBACK, &parse_align, |
844 | "Text alignment" , "left/center/right" }, |
845 | {"ellipsize" , 0, 0, G_OPTION_ARG_CALLBACK, &parse_ellipsis, |
846 | "Ellipsization mode" , "start/middle/end" }, |
847 | {"font" , 0, 0, G_OPTION_ARG_STRING, &opt_font, |
848 | "Set the font description" , "description" }, |
849 | {"foreground" , 0, 0, G_OPTION_ARG_CALLBACK, &parse_foreground, |
850 | "Set the text color" , "red/#rrggbb/#rrggbbaa" }, |
851 | {"gravity" , 0, 0, G_OPTION_ARG_CALLBACK, &parse_gravity, |
852 | "Base gravity: glyph rotation" , "south/east/north/west/auto" }, |
853 | {"gravity-hint" , 0, 0, G_OPTION_ARG_CALLBACK, &parse_gravity_hint, |
854 | "Gravity hint" , "natural/strong/line" }, |
855 | {"header" , 0, 0, G_OPTION_ARG_NONE, &opt_header, |
856 | "Display the options in the output" , NULL}, |
857 | {"height" , 0, 0, G_OPTION_ARG_INT, &opt_height, |
858 | "Height in points (positive) or number of lines (negative) for ellipsizing" , "+points/-numlines" }, |
859 | {"hinting" , 0, 0, G_OPTION_ARG_CALLBACK, &parse_hinting, |
860 | "Hinting style" , "none/auto/slight/medium/full" }, |
861 | {"antialias" , 0, 0, G_OPTION_ARG_CALLBACK, &parse_antialias, |
862 | "Antialiasing" , "none/gray/subpixel" }, |
863 | {"hint-metrics" , 0, 0, G_OPTION_ARG_CALLBACK, &parse_hint_metrics, |
864 | "Hint metrics" , "on/off" }, |
865 | { "subpixel-positions" , 0, 0, G_OPTION_ARG_NONE, &opt_subpixel_positions, |
866 | "Subpixel positioning" , NULL}, |
867 | {"subpixel-order" , 0, 0, G_OPTION_ARG_CALLBACK, &parse_subpixel_order, |
868 | "Subpixel order" , "rgb/bgr/vrgb/vbgr" }, |
869 | {"indent" , 0, 0, G_OPTION_ARG_INT, &opt_indent, |
870 | "Width in points to indent paragraphs" , "points" }, |
871 | {"spacing" , 0, 0, G_OPTION_ARG_INT, &opt_spacing, |
872 | "Spacing in points between lines" , "points" }, |
873 | {"line-spacing" , 0, 0, G_OPTION_ARG_DOUBLE, &opt_line_spacing, |
874 | "Spread factor for line height" , "factor" }, |
875 | {"justify" , 0, 0, G_OPTION_ARG_NONE, &opt_justify, |
876 | "Stretch paragraph lines to be justified" , NULL}, |
877 | {"justify-last-line" , 0, 0, G_OPTION_ARG_NONE, &opt_justify_last_line, |
878 | "Justify the last line of the paragraph" , NULL}, |
879 | {"language" , 0, 0, G_OPTION_ARG_STRING, &opt_language, |
880 | "Language to use for font selection" , "en_US/etc" }, |
881 | {"margin" , 0, 0, G_OPTION_ARG_CALLBACK, &parse_margin, |
882 | "Set the margin on the output in pixels" , "CSS-style numbers in pixels" }, |
883 | {"markup" , 0, 0, G_OPTION_ARG_NONE, &opt_markup, |
884 | "Interpret text as Pango markup" , NULL}, |
885 | {"output" , 'o', 0, G_OPTION_ARG_STRING, &opt_output, |
886 | "Save rendered image to output file" , "file" }, |
887 | {"pangorc" , 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &opt_pangorc, |
888 | "Deprecated" , "file" }, |
889 | {"pixels" , 0, 0, G_OPTION_ARG_NONE, &opt_pixels, |
890 | "Use pixel units instead of points (sets dpi to 72)" , NULL}, |
891 | {"pango-units" , 0, 0, G_OPTION_ARG_NONE, &opt_pango_units, |
892 | "Use Pango units instead of points" , NULL}, |
893 | {"rtl" , 0, 0, G_OPTION_ARG_NONE, &opt_rtl, |
894 | "Set base direction to right-to-left" , NULL}, |
895 | {"rotate" , 0, 0, G_OPTION_ARG_DOUBLE, &opt_rotate, |
896 | "Angle at which to rotate results" , "degrees" }, |
897 | {"runs" , 'n', 0, G_OPTION_ARG_INT, &opt_runs, |
898 | "Run Pango layout engine this many times" , "integer" }, |
899 | {"single-par" , 0, 0, G_OPTION_ARG_NONE, &opt_single_par, |
900 | "Enable single-paragraph mode" , NULL}, |
901 | {"text" , 't', 0, G_OPTION_ARG_STRING, &opt_text, |
902 | "Text to display (instead of a file)" , "string" }, |
903 | {"version" , 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, &show_version, |
904 | "Show version numbers" , NULL}, |
905 | {"waterfall" , 0, 0, G_OPTION_ARG_NONE, &opt_waterfall, |
906 | "Create a waterfall display" , NULL}, |
907 | {"width" , 'w', 0, G_OPTION_ARG_INT, &opt_width, |
908 | "Width in points to which to wrap lines or ellipsize" , "points" }, |
909 | {"wrap" , 0, 0, G_OPTION_ARG_CALLBACK, &parse_wrap, |
910 | "Text wrapping mode (needs a width to be set)" , "word/char/word-char" }, |
911 | {"serialized" , 0, 0, G_OPTION_ARG_NONE, &opt_serialized, |
912 | "Create layout from a serialized file" , "FILE" }, |
913 | {"serialize-to" , 0, 0, G_OPTION_ARG_FILENAME, &opt_serialized_output, |
914 | "Serialize result to a file" , "FILE" }, |
915 | {NULL} |
916 | }; |
917 | GError *error = NULL; |
918 | GError *parse_error = NULL; |
919 | GOptionContext *context; |
920 | size_t len; |
921 | const PangoViewer **viewer; |
922 | |
923 | context = g_option_context_new (parameter_string: "- FILE" ); |
924 | g_option_context_add_main_entries (context, entries, NULL); |
925 | |
926 | for (viewer = viewers; *viewer; viewer++) |
927 | if ((*viewer)->get_option_group) |
928 | { |
929 | GOptionGroup *group = (*viewer)->get_option_group (*viewer); |
930 | if (group) |
931 | g_option_context_add_group (context, group); |
932 | } |
933 | |
934 | if (!g_option_context_parse (context, argc: &argc, argv: &argv, error: &parse_error)) |
935 | { |
936 | if (parse_error != NULL) |
937 | fail(format: "%s" , parse_error->message); |
938 | else |
939 | fail(format: "Option parse error" ); |
940 | exit(status: 1); |
941 | } |
942 | g_option_context_free(context); |
943 | g_free(mem: backend_options); |
944 | g_free(mem: backend_desc); |
945 | |
946 | if (opt_pixels) |
947 | opt_dpi = 72; |
948 | |
949 | if ((opt_text && argc != 1) || (!opt_text && argc != 2)) |
950 | { |
951 | if (opt_text && argc != 1) |
952 | fail (format: "When specifying --text, no file should be given" ); |
953 | |
954 | g_printerr (format: "Usage: %s [OPTION...] FILE\n" , g_get_prgname ()); |
955 | exit (status: 1); |
956 | } |
957 | |
958 | if (opt_serialized && argc != 2) |
959 | { |
960 | g_printerr (format: "Usage: %s [OPTION...] FILE\n" , g_get_prgname ()); |
961 | exit (status: 1); |
962 | } |
963 | |
964 | /* set up the backend */ |
965 | if (!opt_viewer) |
966 | { |
967 | opt_viewer = *viewers; |
968 | if (!opt_viewer) |
969 | fail (format: "No viewer backend found" ); |
970 | } |
971 | |
972 | /* Get the text |
973 | */ |
974 | if (opt_serialized) |
975 | { |
976 | file_arg = argv[1]; |
977 | text = g_strdup (str: "" ); |
978 | len = 0; |
979 | } |
980 | else if (opt_text) |
981 | { |
982 | text = g_strdup (str: opt_text); |
983 | len = strlen (s: text); |
984 | } |
985 | else |
986 | { |
987 | if (!g_file_get_contents (filename: argv[1], contents: &text, length: &len, error: &error)) |
988 | fail (format: "%s\n" , error->message); |
989 | } |
990 | |
991 | /* Strip one trailing newline |
992 | */ |
993 | if (len > 0 && text[len - 1] == '\n') |
994 | len--; |
995 | if (len > 0 && text[len - 1] == '\r') |
996 | len--; |
997 | text[len] = '\0'; |
998 | |
999 | /* Make sure we have valid markup |
1000 | */ |
1001 | if (opt_markup && |
1002 | !pango_parse_markup (markup_text: text, length: -1, accel_marker: 0, NULL, NULL, NULL, error: &error)) |
1003 | fail (format: "Cannot parse input as markup: %s" , error->message); |
1004 | } |
1005 | |
1006 | |
1007 | void |
1008 | finalize (void) |
1009 | { |
1010 | g_free (mem: text); |
1011 | } |
1012 | |