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