1 /* viewer-render.c: Common code for rendering in viewers
3 * Copyright (C) 1999, 2004 Red Hat Software
4 * Copyright (C) 2001 Sun Microsystems
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.
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.
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.
30 #include <glib/gprintf.h>
31 #include <pango/pango.h>
33 #include "viewer-render.h"
35 gboolean opt_display = TRUE;
37 const char *opt_font = "";
38 gboolean opt_header = FALSE;
39 const char *opt_output = NULL;
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;
50 gboolean opt_justify = 0;
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;
69 /* Text (or markup) to render */
73 fail (const char *format, ...)
78 va_start (vap, format);
79 msg = g_strdup_vprintf (format, vap);
80 g_printerr ("%s: %s\n", g_get_prgname (), msg);
86 make_layout(PangoContext *context,
90 static PangoFontDescription *font_description;
94 layout = pango_layout_new (context);
96 pango_layout_set_markup (layout, text, -1);
98 pango_layout_set_text (layout, text, -1);
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);
106 font_description = pango_font_description_from_string (opt_font);
108 pango_font_description_set_size (font_description, size * PANGO_SCALE);
111 pango_layout_set_width (layout, (opt_width * opt_dpi * PANGO_SCALE + 36) / 72);
114 pango_layout_set_height (layout, (opt_height * opt_dpi * PANGO_SCALE + 36) / 72);
116 pango_layout_set_height (layout, opt_height);
119 pango_layout_set_indent (layout, (opt_indent * opt_dpi * PANGO_SCALE + 36) / 72);
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;
130 pango_layout_set_alignment (layout, align);
132 pango_layout_set_font_description (layout, font_description);
134 pango_font_description_free (font_description);
140 get_options_string (void)
142 PangoFontDescription *font_description = pango_font_description_from_string (opt_font);
147 pango_font_description_unset_fields (font_description, PANGO_FONT_MASK_SIZE);
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);
158 output_body (PangoLayout *layout,
159 RenderCallback render_cb,
164 gboolean supports_matrix)
166 PangoRectangle logical_rect;
167 int size, start_size, end_size, increment;
170 if (!supports_matrix)
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);
181 pango_context_set_matrix (context, &identity);
182 pango_layout_context_changed (layout);
193 start_size = end_size = -1;
200 for (size = start_size; size <= end_size; size += increment)
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);
210 pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
213 (*render_cb) (layout, x, y+*height, cb_context, cb_data);
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)));
224 set_transform (PangoContext *context,
225 TransformCallback transform_cb,
230 pango_context_set_matrix (context, matrix);
232 (*transform_cb) (context, matrix, cb_context, cb_data);
236 do_output (PangoContext *context,
237 RenderCallback render_cb,
238 TransformCallback transform_cb,
246 PangoMatrix matrix = PANGO_MATRIX_INIT;
247 PangoMatrix *orig_matrix;
248 gboolean supports_matrix;
249 int rotated_width, rotated_height;
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.
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.);
266 set_transform (context, transform_cb, cb_context, cb_data, NULL);
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);
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);
281 width = MAX (width, PANGO_PIXELS (rect.width));
282 height += PANGO_PIXELS (rect.height);
285 (*render_cb) (layout, x, y, cb_context, cb_data);
287 y += PANGO_PIXELS (rect.height);
289 g_object_unref (layout);
290 g_free (options_string);
296 pango_matrix_rotate (&matrix, opt_rotate);
298 g_printerr ("The backend does not support rotated text\n");
301 pango_context_set_base_gravity (context, opt_gravity);
302 pango_context_set_gravity_hint (context, opt_gravity_hint);
304 layout = make_layout (context, text, -1);
306 set_transform (context, transform_cb, cb_context, cb_data, &matrix);
310 &rotated_width, &rotated_height,
314 rect.width = rotated_width;
315 rect.height = rotated_height;
317 pango_matrix_transform_pixel_rectangle (&matrix, &rect);
319 matrix.x0 = x - rect.x;
320 matrix.y0 = y - rect.y;
322 set_transform (context, transform_cb, cb_context, cb_data, &matrix);
326 render_cb, cb_context, cb_data,
327 &rotated_width, &rotated_height,
330 width = MAX (width, rect.width);
331 height += rect.height;
333 width += 2 * opt_margin;
334 height += 2 * opt_margin;
339 *height_out = height;
341 pango_context_set_matrix (context, orig_matrix);
342 pango_matrix_free (orig_matrix);
343 g_object_unref (layout);
347 parse_enum (GType type,
351 gpointer data G_GNUC_UNUSED,
354 char *possible_values = NULL;
357 ret = pango_parse_enum (type,
367 G_OPTION_ERROR_BAD_VALUE,
368 "Argument for %s must be one of %s",
373 g_free (possible_values);
379 parse_align (const char *name,
384 return parse_enum (PANGO_TYPE_ALIGNMENT, (int*)(void*)&opt_align,
385 name, arg, data, error);
389 parse_ellipsis (const char *name,
394 return parse_enum (PANGO_TYPE_ELLIPSIZE_MODE, (int*)(void*)&opt_ellipsize,
395 name, arg, data, error);
399 parse_gravity (const char *name,
404 return parse_enum (PANGO_TYPE_GRAVITY, (int*)(void*)&opt_gravity,
405 name, arg, data, error);
409 parse_gravity_hint (const char *name,
414 return parse_enum (PANGO_TYPE_GRAVITY_HINT, (int*)(void*)&opt_gravity_hint,
415 name, arg, data, error);
419 parse_hinting (const char *name G_GNUC_UNUSED,
421 gpointer data G_GNUC_UNUSED,
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;
436 G_OPTION_ERROR_BAD_VALUE,
437 "Argument for --hinting must be one of none/auto/full");
445 parse_wrap (const char *name,
451 if ((ret = parse_enum (PANGO_TYPE_WRAP_MODE, (int*)(void*)&opt_wrap,
452 name, arg, data, error)))
460 parse_rgba_color (PangoColor *color,
464 gpointer data G_GNUC_UNUSED,
473 if (*arg == '#' && (len == 5 || len == 9 || len == 17))
484 if (!sscanf (buf + len - width, "%x", &a))
489 buf[len - width] = '\0';
502 ret = pango_color_parse (color, arg);
509 G_OPTION_ERROR_BAD_VALUE,
510 "Argument for %s must be a color name like red, or CSS-style #rrggbb / #rrggbbaa",
518 parse_foreground (const char *name,
523 return parse_rgba_color (&opt_fg_color, &opt_fg_alpha,
524 name, arg, data, error);
528 parse_background (const char *name,
535 if (0 == strcmp ("transparent", arg))
541 return parse_rgba_color (&opt_bg_color, &opt_bg_alpha,
542 name, arg, data, error);
546 backends_to_string (void)
548 GString *backends = g_string_new (NULL);
549 const PangoViewer **viewer;
551 for (viewer = viewers; *viewer; viewer++)
554 g_string_append (backends, (*viewer)->id);
555 g_string_append_c (backends, '/');
557 g_string_truncate (backends, MAX (0, (gint)backends->len - 1));
559 return g_string_free(backends,FALSE);
563 backends_get_count (void)
565 const PangoViewer **viewer;
568 for (viewer = viewers; *viewer; viewer++)
577 backend_description (void)
579 GString *description = g_string_new("Pango backend to use for rendering ");
580 int backends_count = backends_get_count ();
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);
587 g_string_append_printf(description,"(no backends found!)");
589 return g_string_free(description,FALSE);
594 parse_backend (const char *name G_GNUC_UNUSED,
596 gpointer data G_GNUC_UNUSED,
600 const PangoViewer **viewer;
602 for (viewer = viewers; *viewer; viewer++)
603 if (!g_ascii_strcasecmp ((*viewer)->id, arg))
607 opt_viewer = *viewer;
610 gchar *backends = backends_to_string ();
614 G_OPTION_ERROR_BAD_VALUE,
615 "Available --backend options are: %s",
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)
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);
634 if (PANGO_VERSION != pango_version())
635 g_printf("Linked Pango library has a different version: %s\n", pango_version_string ());
641 parse_options (int argc, char *argv[])
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[] =
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"},
710 GError *error = NULL;
711 GError *parse_error = NULL;
712 GOptionContext *context;
714 const PangoViewer **viewer;
716 context = g_option_context_new ("- FILE");
717 g_option_context_add_main_entries (context, entries, NULL);
719 for (viewer = viewers; *viewer; viewer++)
720 if ((*viewer)->get_option_group)
722 GOptionGroup *group = (*viewer)->get_option_group (*viewer);
724 g_option_context_add_group (context, group);
727 if (!g_option_context_parse (context, &argc, &argv, &parse_error))
729 if (parse_error != NULL)
730 fail("%s", parse_error->message);
732 fail("Option parse error");
735 g_option_context_free(context);
736 g_free(backend_options);
737 g_free(backend_desc);
739 if ((opt_text && argc != 1) || (!opt_text && argc != 2))
741 if (opt_text && argc != 1)
742 fail ("When specifying --text, no file should be given");
744 g_printerr ("Usage: %s [OPTION...] FILE\n", g_get_prgname ());
748 /* set up the backend */
751 opt_viewer = *viewers;
753 fail ("No viewer backend found");
760 text = g_strdup (opt_text);
765 if (!g_file_get_contents (argv[1], &text, &len, &error))
766 fail ("%s\n", error->message);
769 /* Strip one trailing newline
771 if (len > 0 && text[len - 1] == '\n')
773 if (len > 0 && text[len - 1] == '\r')
777 /* Make sure we have valid markup
780 !pango_parse_markup (text, -1, 0, NULL, NULL, NULL, &error))
781 fail ("Cannot parse input as markup: %s", error->message);
783 /* Setup PANGO_RC_FILE
786 if (g_file_test ("./pangorc", G_FILE_TEST_IS_REGULAR))
787 opt_pangorc = "./pangorc";
789 g_setenv ("PANGO_RC_FILE", opt_pangorc, TRUE);