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