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