1 /* Pango
2 * paps.c: A postscript printing program using pango.
3 *
4 * Copyright (C) 2002, 2005 Dov Grobgeld <dov.grobgeld@gmail.com>
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 */
22
23 #include <pango/pango.h>
24 #include <pango/pangoft2.h>
25 #include <pango/pangocairo.h>
26 #include <cairo/cairo.h>
27 #include <cairo/cairo-ps.h>
28 #include <cairo/cairo-pdf.h>
29 #include <cairo/cairo-svg.h>
30 #include <errno.h>
31 #include <langinfo.h>
32 #include <stdlib.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <time.h>
36 #include <locale.h>
37 #include <math.h>
38 #include <wchar.h>
39 #include <libgen.h>
40 #include <config.h>
41
42 #if ENABLE_NLS
43 #include <libintl.h>
44
45 #define _(str) gettext(str)
46 #ifdef gettext_noop
47 #define N_(str) gettext_noop(str)
48 #else
49 #define N_(str) (str)
50 #endif
51
52 #else /* NLS is disabled */
53 #define _(str) (str)
54 #define N_(str) (str)
55 #endif
56
57 #define BUFSIZE 1024
58 #define DEFAULT_FONT_FAMILY "Monospace"
59 #define DEFAULT_FONT_SIZE "12"
60 #define HEADER_FONT_FAMILY "Monospace Bold"
61 #define HEADER_FONT_SCALE "12"
62 #define MAKE_FONT_NAME(f,s) f " " s
63
64 /*
65 * Cairo sets limit on the comment line for cairo_ps_surface_dsc_comment() to
66 * 255 characters, including the initial percent characters.
67 */
68 #define CAIRO_COMMENT_MAX 255
69
70 #define MARGIN_LEFT 36
71 #define MARGIN_RIGHT 36
72 #define MARGIN_TOP 36
73 #define MARGIN_BOTTOM 36
74
75
76 typedef enum {
77 PAPER_TYPE_A4 = 0,
78 PAPER_TYPE_US_LETTER = 1,
79 PAPER_TYPE_US_LEGAL = 2,
80 PAPER_TYPE_A3 = 3
81 } paper_type_t ;
82
83 typedef enum {
84 FORMAT_POSTSCRIPT = 0,
85 FORMAT_PDF = 1,
86 FORMAT_SVG = 2
87 } output_format_t ;
88
89 typedef struct {
90 double width;
91 double height;
92 } paper_size_t;
93
94 static const paper_size_t paper_sizes[] = {
95 { 595.28, 841.89}, /* A4 */
96 { 612, 792}, /* US letter */
97 { 612, 1008}, /* US legal */
98 { 842, 1190} /* A3 */
99 };
100
101 typedef struct {
102 int column_width;
103 int column_height;
104 int num_columns;
105 int gutter_width; /* These are all in postscript points=1/72 inch... */
106 int top_margin;
107 int bottom_margin;
108 int left_margin;
109 int right_margin;
110 double page_width;
111 double page_height;
112 int header_ypos;
113 int header_sep;
114 int header_height;
115 int footer_height;
116 gdouble scale_x;
117 gdouble scale_y;
118 gboolean do_draw_header;
119 gboolean do_draw_footer;
120 gboolean do_duplex;
121 gboolean do_tumble;
122 gboolean do_landscape;
123 gboolean do_justify;
124 gboolean do_separation_line;
125 gboolean do_draw_contour;
126 gboolean do_show_wrap;
127 gboolean do_use_markup;
128 gboolean do_stretch_chars;
129 PangoDirection pango_dir;
130 const gchar *title;
131 const gchar *header_font_desc;
132 gdouble lpi;
133 gdouble cpi;
134 } page_layout_t;
135
136 typedef struct {
137 PangoLayoutLine *pango_line;
138 PangoRectangle logical_rect;
139 PangoRectangle ink_rect;
140 int formfeed;
141 gboolean wrapped; // Whether the paragraph was character wrapped
142 } LineLink;
143
144 typedef struct _Paragraph Paragraph;
145
146 /* Structure representing a paragraph
147 */
148 struct _Paragraph {
149 const char *text;
150 int length;
151 int height; /* Height, in pixels */
152 int formfeed;
153 gboolean wrapped;
154 gboolean clipped; // Whether the line was clipped. Used for CPI.
155 PangoLayout *layout;
156 };
157
158 /* Information passed in user data when drawing outlines */
159 static GList *split_paragraphs_into_lines (page_layout_t *page_layout,
160 GList *paragraphs);
161 static char *read_file (FILE *file,
162 gchar *encoding);
163 static GList *split_text_into_paragraphs (cairo_t *cr,
164 PangoContext *pango_context,
165 page_layout_t *page_layout,
166 int paint_width,
167 const char *text);
168 static int output_pages (cairo_surface_t * surface,
169 cairo_t *cr,
170 GList *pango_lines,
171 page_layout_t *page_layout,
172 gboolean need_header,
173 PangoContext *pango_context);
174 static void eject_column (cairo_t *cr,
175 page_layout_t *page_layout,
176 int column_idx);
177 static void eject_page (cairo_t *cr);
178 static void start_page (cairo_surface_t *surface,
179 cairo_t *cr,
180 page_layout_t *page_layout);
181 static void draw_line_to_page (cairo_t *cr,
182 int column_idx,
183 int column_pos,
184 page_layout_t *page_layout,
185 PangoLayoutLine *line,
186 gboolean draw_wrap_character);
187 static int draw_page_header_line_to_page(cairo_t *cr,
188 gboolean is_footer,
189 page_layout_t *page_layout,
190 PangoContext *ctx,
191 int page);
192 static void postscript_dsc_comments (cairo_surface_t *surface,
193 page_layout_t *page_layout);
194
195 FILE *output_fh;
196 static paper_type_t paper_type = PAPER_TYPE_A4;
197 static gboolean output_format_set = FALSE;
198 static output_format_t output_format = FORMAT_POSTSCRIPT;
199 static PangoGravity gravity = PANGO_GRAVITY_AUTO;
200 static PangoGravityHint gravity_hint = PANGO_GRAVITY_HINT_NATURAL;
201 static PangoWrapMode opt_wrap = PANGO_WRAP_WORD_CHAR;
202 static cairo_font_face_t *paps_glyph_face = NULL; /* Special face for paps characters, e.g. newline */
203 static double glyph_font_size = -1;
204
205 /* Render function for paps glyphs */
206 static cairo_status_t
paps_render_glyph(cairo_scaled_font_t * scaled_font G_GNUC_UNUSED,unsigned long glyph,cairo_t * cr,cairo_text_extents_t * extents)207 paps_render_glyph(cairo_scaled_font_t *scaled_font G_GNUC_UNUSED,
208 unsigned long glyph,
209 cairo_t *cr,
210 cairo_text_extents_t *extents)
211 {
212 char ch = (unsigned char)glyph;
213
214 if (ch == 'R' || ch == 'L')
215 {
216 // A newline sign that I created with MetaPost
217 cairo_save(cr);
218 cairo_scale(cr,0.005,-0.005); // TBD - figure out the scaling.
219 if (ch == 'L')
220 {
221 cairo_scale(cr,-1,1);
222 // cairo_translate(cr,-120,0); // Keep glyph protruding to the right.
223 }
224 cairo_translate(cr, 20,-50);
225 cairo_move_to(cr, 0, 175);
226 cairo_curve_to(cr, 25.69278, 175, 53.912, 177.59557, 71.25053, 158.75053);
227 cairo_curve_to(cr, 103.52599, 123.67075, 64.54437, 77.19373, 34.99985, 34.99985);
228 cairo_set_line_width(cr, 25);
229 cairo_stroke(cr);
230
231 cairo_move_to(cr,0,0);
232 cairo_line_to(cr,75,0);
233 cairo_line_to(cr,0,75);
234 cairo_close_path(cr);
235 cairo_fill(cr);
236 cairo_restore(cr);
237 }
238 return CAIRO_STATUS_SUCCESS;
239 }
240
241 static gboolean
_paps_arg_paper_cb(const char * option_name,const char * value,gpointer data)242 _paps_arg_paper_cb(const char *option_name,
243 const char *value,
244 gpointer data)
245 {
246 gboolean retval = TRUE;
247
248 if (value && *value)
249 {
250 if (g_ascii_strcasecmp(value, "legal") == 0)
251 paper_type = PAPER_TYPE_US_LEGAL;
252 else if (g_ascii_strcasecmp(value, "letter") == 0)
253 paper_type = PAPER_TYPE_US_LETTER;
254 else if (g_ascii_strcasecmp(value, "a4") == 0)
255 paper_type = PAPER_TYPE_A4;
256 else if (g_ascii_strcasecmp(value, "a3") == 0)
257 paper_type = PAPER_TYPE_A3;
258 else {
259 retval = FALSE;
260 fprintf(stderr, _("Unknown page size name: %s.\n"), value);
261 }
262 }
263 else
264 {
265 fprintf(stderr, _("You must specify page size.\n"));
266 retval = FALSE;
267 }
268
269 return retval;
270 }
271
272 static gboolean
parse_enum(GType type,int * value,const char * name,const char * arg,gpointer data G_GNUC_UNUSED,GError ** error)273 parse_enum (GType type,
274 int *value,
275 const char *name,
276 const char *arg,
277 gpointer data G_GNUC_UNUSED,
278 GError **error)
279 {
280 char *possible_values = NULL;
281 gboolean ret;
282
283 ret = pango_parse_enum (type,
284 arg,
285 value,
286 FALSE,
287 &possible_values);
288
289 if (!ret && error)
290 {
291 g_set_error(error,
292 G_OPTION_ERROR,
293 G_OPTION_ERROR_BAD_VALUE,
294 _("Argument for %1$s must be one of %2$s"),
295 name,
296 possible_values);
297 }
298
299 g_free (possible_values);
300
301 return ret;
302 }
303
304 static gboolean
parse_wrap(const char * name,const char * arg,gpointer data,GError ** error)305 parse_wrap (const char *name,
306 const char *arg,
307 gpointer data,
308 GError **error)
309 {
310 return (parse_enum (PANGO_TYPE_WRAP_MODE, (int*)(void*)&opt_wrap,
311 name, arg, data, error));
312 }
313
314 static gboolean
parse_gravity_hint(const char * name,const char * arg,gpointer data,GError ** error)315 parse_gravity_hint (const char *name,
316 const char *arg,
317 gpointer data,
318 GError **error)
319 {
320 return (parse_enum (PANGO_TYPE_GRAVITY_HINT, (int*)(void*)&gravity_hint,
321 name, arg, data, error));
322 }
323
324 static gboolean
parse_gravity(const char * name,const char * arg,gpointer data,GError ** error)325 parse_gravity (const char *name,
326 const char *arg,
327 gpointer data,
328 GError **error)
329 {
330 return (parse_enum (PANGO_TYPE_GRAVITY, (int*)(void*)&gravity,
331 name, arg, data, error));
332 }
333
334
335 static gboolean
_paps_arg_format_cb(const char * option_name,const char * value,gpointer data)336 _paps_arg_format_cb(const char *option_name,
337 const char *value,
338 gpointer data)
339 {
340 gboolean retval = TRUE;
341
342 if (value && *value)
343 {
344 output_format_set = TRUE;
345 if (g_ascii_strcasecmp(value, "pdf") == 0)
346 output_format = FORMAT_PDF;
347 else if (g_ascii_strcasecmp(value, "ps") == 0
348 || g_ascii_strcasecmp(value, "postscript") == 0)
349 output_format = FORMAT_POSTSCRIPT;
350 else if (g_ascii_strcasecmp(value, "svg") == 0)
351 output_format = FORMAT_SVG;
352 else {
353 retval = FALSE;
354 fprintf(stderr, _("Unknown output format: %s.\n"), value);
355 }
356 }
357 else
358 {
359 fprintf(stderr, _("You must specify a output format.\n"));
360 retval = FALSE;
361 }
362
363 return retval;
364 }
365
366 static gboolean
_paps_arg_lpi_cb(const gchar * option_name,const gchar * value,gpointer data)367 _paps_arg_lpi_cb(const gchar *option_name,
368 const gchar *value,
369 gpointer data)
370 {
371 gboolean retval = TRUE;
372 gchar *p = NULL;
373 page_layout_t *page_layout = (page_layout_t*)data;
374
375 if (value && *value)
376 {
377 errno = 0;
378 page_layout->lpi = g_strtod(value, &p);
379 if ((p && *p) || errno == ERANGE)
380 {
381 fprintf(stderr, _("Given LPI value was invalid.\n"));
382 retval = FALSE;
383 }
384 }
385 else
386 {
387 fprintf(stderr, _("You must specify the amount of lines per inch.\n"));
388 retval = FALSE;
389 }
390
391 return retval;
392 }
393
394 static gboolean
_paps_arg_cpi_cb(const gchar * option_name,const gchar * value,gpointer data)395 _paps_arg_cpi_cb(const gchar *option_name,
396 const gchar *value,
397 gpointer data)
398 {
399 gboolean retval = TRUE;
400 gchar *p = NULL;
401 page_layout_t *page_layout = (page_layout_t*)data;
402
403 if (value && *value)
404 {
405 errno = 0;
406 page_layout->cpi = g_strtod(value, &p);
407 if ((p && *p) || errno == ERANGE)
408 {
409 fprintf(stderr, _("Given CPI value was invalid.\n"));
410 retval = FALSE;
411 }
412 }
413 else
414 {
415 fprintf(stderr, _("You must specify the amount of characters per inch.\n"));
416 retval = FALSE;
417 }
418
419 return retval;
420 }
421
422
423 /*
424 * Return codeset name of the environment's locale. Use UTF8 by default
425 */
426 static char*
get_encoding()427 get_encoding()
428 {
429 static char *encoding = NULL;
430
431 if (encoding == NULL)
432 encoding = nl_langinfo(CODESET);
433
434 return encoding;
435 }
436
paps_cairo_write_func(void * closure G_GNUC_UNUSED,const unsigned char * data,unsigned int length)437 static cairo_status_t paps_cairo_write_func(void *closure G_GNUC_UNUSED,
438 const unsigned char *data,
439 unsigned int length)
440 {
441 fwrite(data,length,1,output_fh);
442 return CAIRO_STATUS_SUCCESS;
443 }
444
main(int argc,char * argv[])445 int main(int argc, char *argv[])
446 {
447 gboolean do_landscape = FALSE, do_rtl = FALSE, do_justify = FALSE, do_draw_header = FALSE, do_draw_footer=FALSE;
448 gboolean do_stretch_chars = FALSE;
449 gboolean do_use_markup = FALSE;
450 gboolean do_show_wrap = FALSE; /* Whether to show wrap characters */
451 int num_columns = 1;
452 int top_margin = MARGIN_TOP, bottom_margin = MARGIN_BOTTOM,
453 right_margin = MARGIN_RIGHT, left_margin = MARGIN_LEFT;
454
455 gboolean do_fatal_warnings = FALSE;
456 const gchar *font = MAKE_FONT_NAME (DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE);
457 gchar *encoding = NULL;
458 gchar *output = NULL;
459 gchar *htitle = NULL;
460 page_layout_t page_layout;
461 GOptionContext *ctxt = g_option_context_new("[text file]");
462 GOptionEntry entries[] = {
463 {"landscape", 0, 0, G_OPTION_ARG_NONE, &do_landscape,
464 N_("Landscape output. (Default: portrait)"), NULL},
465 {"columns", 0, 0, G_OPTION_ARG_INT, &num_columns,
466 N_("Number of columns output. (Default: 1)"), "NUM"},
467 {"font", 0, 0, G_OPTION_ARG_STRING, &font,
468 N_("Set font. (Default: Monospace 12)"), "DESC"},
469 {"output", 'o', 0, G_OPTION_ARG_STRING, &output,
470 N_("Output file. (Default: stdout)"), "DESC"},
471 {"rtl", 0, 0, G_OPTION_ARG_NONE, &do_rtl,
472 N_("Do right-to-left text layout."), NULL},
473 {"justify", 0, 0, G_OPTION_ARG_NONE, &do_justify,
474 N_("Justify the layout."), NULL},
475 {"wrap", 0, 0, G_OPTION_ARG_CALLBACK, &parse_wrap,
476 N_("Text wrapping mode [word, char, word-char]. (Default: word-char)"), "WRAP"},
477 {"show-wrap", 0, 0, G_OPTION_ARG_NONE, &do_show_wrap,
478 N_("Show characters for wrapping."), NULL},
479 {"paper", 0, 0, G_OPTION_ARG_CALLBACK, _paps_arg_paper_cb,
480 N_("Set paper size [legal, letter, a3, a4]. (Default: a4)"), "PAPER"},
481 {"gravity", 0, 0, G_OPTION_ARG_CALLBACK, &parse_gravity,
482 N_("Base glyph rotation [south, west, north, east, auto]. (Defaut: auto)"), "GRAVITY"},
483 {"gravity-hint", 0, 0, G_OPTION_ARG_CALLBACK, &parse_gravity_hint,
484 N_("Base glyph orientation [natural, strong, line]. (Default: natural)"), "HINT"},
485 {"format", 0, 0, G_OPTION_ARG_CALLBACK, _paps_arg_format_cb,
486 N_("Set output format [pdf, svg, ps]. (Default: ps)"), "FORMAT"},
487 {"bottom-margin", 0, 0, G_OPTION_ARG_INT, &bottom_margin,
488 N_("Set bottom margin in postscript point units (1/72 inch). (Default: 36)"), "NUM"},
489 {"top-margin", 0, 0, G_OPTION_ARG_INT, &top_margin,
490 N_("Set top margin. (Default: 36)"), "NUM"},
491 {"right-margin", 0, 0, G_OPTION_ARG_INT, &right_margin,
492 N_("Set right margin. (Default: 36)"), "NUM"},
493 {"left-margin", 0, 0, G_OPTION_ARG_INT, &left_margin,
494 N_("Set left margin. (Default: 36)"), "NUM"},
495 {"header", 0, 0, G_OPTION_ARG_NONE, &do_draw_header,
496 N_("Draw page header for each page."), NULL},
497 {"footer", 0, 0, G_OPTION_ARG_NONE, &do_draw_footer,
498 "Draw page footer for each page.", NULL},
499 {"title", 0, 0, G_OPTION_ARG_STRING, &htitle,
500 N_("Title string for page header (Default: filename/stdin)."), "TITLE"},
501 {"markup", 0, 0, G_OPTION_ARG_NONE, &do_use_markup,
502 N_("Interpret input text as pango markup."), NULL},
503 {"encoding", 0, 0, G_OPTION_ARG_STRING, &encoding,
504 N_("Assume encoding of input text. (Default: UTF-8)"), "ENCODING"},
505 {"lpi", 0, 0, G_OPTION_ARG_CALLBACK, _paps_arg_lpi_cb,
506 N_("Set the amount of lines per inch."), "REAL"},
507 {"cpi", 0, 0, G_OPTION_ARG_CALLBACK, _paps_arg_cpi_cb,
508 N_("Set the amount of characters per inch."), "REAL"},
509 /*
510 * not fixed for cairo backend: disable
511 *
512 {"stretch-chars", 0, 0, G_OPTION_ARG_NONE, &do_stretch_chars,
513 N_("Stretch characters in y-direction to fill lines."), NULL},
514 */
515 {"g-fatal-warnings", 0, 0, G_OPTION_ARG_NONE, &do_fatal_warnings,
516 N_("Make all glib warnings fatal."), "REAL"},
517
518 {NULL}
519
520 };
521 GError *error = NULL;
522 FILE *IN = NULL;
523 GList *paragraphs;
524 GList *pango_lines;
525 PangoContext *pango_context;
526 PangoFontDescription *font_description;
527 PangoDirection pango_dir = PANGO_DIRECTION_LTR;
528 PangoFontMap *fontmap;
529 PangoFontset *fontset;
530 PangoFontMetrics *metrics;
531 int gutter_width = 40;
532 int total_gutter_width;
533 double page_width = paper_sizes[0].width;
534 double page_height = paper_sizes[0].height;
535 int do_tumble = -1; /* -1 means not initialized */
536 int do_duplex = -1;
537 const gchar *header_font_desc = MAKE_FONT_NAME (HEADER_FONT_FAMILY, HEADER_FONT_SCALE);
538 const gchar *filename_in;
539 gchar *text;
540 int header_sep = 20;
541 int max_width = 0, w;
542 GOptionGroup *options;
543 cairo_t *cr;
544 cairo_surface_t *surface = NULL;
545 double surface_page_width = 0, surface_page_height = 0;
546
547 /* Set locale from environment */
548 (void) setlocale(LC_ALL, "");
549
550 /* Setup i18n */
551 textdomain(GETTEXT_PACKAGE);
552 bindtextdomain(GETTEXT_PACKAGE, DATADIR "/locale");
553 bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
554
555 /* Setup the paps glyph face */
556 paps_glyph_face = cairo_user_font_face_create();
557 cairo_user_font_face_set_render_glyph_func(paps_glyph_face, paps_render_glyph);
558
559 /* Init page_layout_t parameters set by the option parsing */
560 page_layout.cpi = page_layout.lpi = 0;
561
562 options = g_option_group_new("main","","",&page_layout, NULL);
563 g_option_group_add_entries(options, entries);
564 g_option_group_set_translation_domain(options, GETTEXT_PACKAGE);
565 g_option_context_set_main_group(ctxt, options);
566 #if 0
567 g_option_context_add_main_entries(ctxt, entries, NULL);
568 #endif
569
570 /* Parse command line */
571 if (!g_option_context_parse(ctxt, &argc, &argv, &error))
572 {
573 fprintf(stderr, _("Command line error: %s\n"), error->message);
574 exit(1);
575 }
576
577 if (do_fatal_warnings)
578 g_log_set_always_fatal(G_LOG_LEVEL_MASK);
579
580 if (do_rtl)
581 pango_dir = PANGO_DIRECTION_RTL;
582
583 if (argc > 1)
584 {
585 filename_in = argv[1];
586 IN = fopen(filename_in, "r");
587 if (!IN)
588 {
589 fprintf(stderr, _("Failed to open %s!\n"), filename_in);
590 exit(1);
591 }
592 }
593 else
594 {
595 filename_in = "stdin";
596 IN = stdin;
597 }
598
599 // For now always write to stdout
600 if (output == NULL)
601 output_fh = stdout;
602 else
603 {
604 output_fh = fopen(output,"wb");
605 if (!output_fh)
606 {
607 fprintf(stderr, _("Failed to open %s for writing!\n"), output);
608 exit(1);
609 }
610 }
611
612 /* Page layout */
613 page_width = paper_sizes[(int)paper_type].width;
614 page_height = paper_sizes[(int)paper_type].height;
615
616 /* Deduce output format from file name if not explicitely set */
617 if (!output_format_set && output != NULL)
618 {
619 if (g_str_has_suffix(output, ".svg") || g_str_has_suffix(output, ".SVG"))
620 output_format = FORMAT_SVG;
621 else if (g_str_has_suffix(output, ".pdf") || g_str_has_suffix(output, ".PDF"))
622 output_format = FORMAT_PDF;
623 /* Otherwise keep postscript default */
624 }
625
626 /* Swap width and height for landscape except for postscript */
627 surface_page_width = page_width;
628 surface_page_height = page_height;
629 if (output_format != FORMAT_POSTSCRIPT && do_landscape)
630 {
631 surface_page_width = page_height;
632 surface_page_height = page_width;
633 }
634
635 if (output_format == FORMAT_POSTSCRIPT)
636 surface = cairo_ps_surface_create_for_stream(&paps_cairo_write_func,
637 NULL,
638 surface_page_width,
639 surface_page_height);
640 else if (output_format == FORMAT_PDF)
641 surface = cairo_pdf_surface_create_for_stream(&paps_cairo_write_func,
642 NULL,
643 surface_page_width,
644 surface_page_height);
645 else
646 surface = cairo_svg_surface_create_for_stream(&paps_cairo_write_func,
647 NULL,
648 surface_page_width,
649 surface_page_height);
650
651 cr = cairo_create(surface);
652
653 pango_context = pango_cairo_create_context(cr);
654 pango_cairo_context_set_resolution(pango_context, 72.0); /* Native postscript resolution */
655
656 /* Setup pango */
657 pango_context_set_base_dir (pango_context, pango_dir);
658 pango_context_set_language (pango_context, pango_language_get_default ());
659 pango_context_set_base_gravity (pango_context, gravity);
660 pango_context_set_gravity_hint (pango_context, gravity_hint);
661
662 /* create the font description */
663 font_description = pango_font_description_from_string (font);
664 if ((pango_font_description_get_set_fields (font_description) & PANGO_FONT_MASK_FAMILY) == 0)
665 pango_font_description_set_family (font_description, DEFAULT_FONT_FAMILY);
666 if ((pango_font_description_get_set_fields (font_description) & PANGO_FONT_MASK_SIZE) == 0)
667 pango_font_description_set_size (font_description, atoi(DEFAULT_FONT_SIZE) * PANGO_SCALE);
668
669 // Keep the font size for the wrap character.
670 glyph_font_size = pango_font_description_get_size(font_description) / PANGO_SCALE;
671 pango_context_set_font_description (pango_context, font_description);
672
673 if (num_columns <= 0) {
674 fprintf(stderr, _("%s: Invalid input: --columns=%d, using default.\n"), g_get_prgname (), num_columns);
675 num_columns = 1;
676 }
677
678 if (num_columns == 1)
679 total_gutter_width = 0;
680 else
681 total_gutter_width = gutter_width * (num_columns - 1);
682 if (do_landscape)
683 {
684 double tmp;
685 tmp = page_width;
686 page_width = page_height;
687 page_height = tmp;
688 if (do_tumble < 0)
689 do_tumble = TRUE;
690 if (do_duplex < 0)
691 do_duplex = TRUE;
692 }
693 else
694 {
695 if (do_tumble < 0)
696 do_tumble = TRUE;
697 if (do_duplex < 0)
698 do_duplex = TRUE;
699 }
700
701 page_layout.page_width = page_width;
702 page_layout.page_height = page_height;
703 page_layout.num_columns = num_columns;
704 page_layout.left_margin = left_margin;
705 page_layout.right_margin = right_margin;
706 page_layout.gutter_width = gutter_width;
707 page_layout.top_margin = top_margin;
708 page_layout.bottom_margin = bottom_margin;
709 page_layout.header_ypos = page_layout.top_margin;
710 page_layout.header_height = 0;
711 page_layout.footer_height = 0;
712 page_layout.do_show_wrap = do_show_wrap;
713 page_layout.scale_x = 1.0L;
714 page_layout.scale_y = 1.0L;
715 if (do_draw_header)
716 page_layout.header_sep = 0; // header_sep;
717 else
718 page_layout.header_sep = 0;
719
720 page_layout.column_height = (int)page_height
721 - page_layout.top_margin
722 - page_layout.header_sep
723 - page_layout.bottom_margin;
724 page_layout.column_width = ((int)page_layout.page_width
725 - page_layout.left_margin - page_layout.right_margin
726 - total_gutter_width) / page_layout.num_columns;
727 page_layout.do_separation_line = TRUE;
728 page_layout.do_landscape = do_landscape;
729 page_layout.do_justify = do_justify;
730 page_layout.do_stretch_chars = do_stretch_chars;
731 page_layout.do_use_markup = do_use_markup;
732 page_layout.do_tumble = do_tumble;
733 page_layout.do_duplex = do_duplex;
734 page_layout.pango_dir = pango_dir;
735 if (htitle)
736 page_layout.title = htitle;
737 else
738 page_layout.title = basename((char *)filename_in);
739 page_layout.header_font_desc = header_font_desc;
740
741 /* calculate x-coordinate scale */
742 if (page_layout.cpi > 0.0L)
743 {
744 gint font_size;
745
746 fontmap = pango_ft2_font_map_new ();
747 fontset = pango_font_map_load_fontset (fontmap, pango_context, font_description, pango_language_get_default());
748 metrics = pango_fontset_get_metrics (fontset);
749 max_width = pango_font_metrics_get_approximate_char_width (metrics);
750 w = pango_font_metrics_get_approximate_digit_width (metrics);
751 if (w > max_width)
752 max_width = w;
753 page_layout.scale_x = 1 / page_layout.cpi * 72.0 * (gdouble)PANGO_SCALE / (gdouble)max_width;
754 pango_font_metrics_unref (metrics);
755 g_object_unref (G_OBJECT (fontmap));
756
757 font_size = pango_font_description_get_size (font_description);
758 // update the font size to that width
759 pango_font_description_set_size (font_description, (int)(font_size * page_layout.scale_x));
760 glyph_font_size = font_size * page_layout.scale_x / PANGO_SCALE;
761 pango_context_set_font_description (pango_context, font_description);
762 }
763
764 page_layout.scale_x = page_layout.scale_y = 1.0;
765
766 if (encoding == NULL)
767 encoding = get_encoding();
768
769 text = read_file(IN, encoding);
770
771 if (output_format == FORMAT_POSTSCRIPT)
772 postscript_dsc_comments(surface, &page_layout);
773
774 paragraphs = split_text_into_paragraphs(cr,
775 pango_context,
776 &page_layout,
777 page_layout.column_width,
778 text);
779 pango_lines = split_paragraphs_into_lines(&page_layout, paragraphs);
780
781 cairo_scale(cr, page_layout.scale_x, page_layout.scale_y);
782
783 output_pages(surface, cr, pango_lines, &page_layout, do_draw_header, pango_context);
784
785 cairo_destroy (cr);
786 cairo_surface_finish (surface);
787 cairo_surface_destroy(surface);
788 g_option_context_free(ctxt);
789
790 return 0;
791 }
792
793
794 /* Read an entire file into a string
795 */
796 static char *
read_file(FILE * file,gchar * encoding)797 read_file (FILE *file,
798 gchar *encoding)
799 {
800 GString *inbuf;
801 char *text;
802 char buffer[BUFSIZE];
803 GIConv cvh = NULL;
804 gsize inc_seq_bytes = 0;
805
806
807 if (encoding != NULL)
808 {
809 cvh = g_iconv_open ("UTF-8", encoding);
810 if (cvh == (GIConv)-1)
811 {
812 fprintf(stderr, _("%s: Invalid encoding: %s\n"), g_get_prgname (), encoding);
813 exit(1);
814 }
815 }
816
817 inbuf = g_string_new (NULL);
818 while (1)
819 {
820 char *ib, *ob, obuffer[BUFSIZE * 6], *bp;
821 gsize iblen, ibleft, oblen;
822
823 bp = fgets (buffer+inc_seq_bytes, BUFSIZE-inc_seq_bytes-1, file);
824 if (inc_seq_bytes)
825 inc_seq_bytes = 0;
826
827 if (ferror (file))
828 {
829 fprintf(stderr, _("%s: Error reading file.\n"), g_get_prgname ());
830 g_string_free (inbuf, TRUE);
831 exit(1);
832 }
833 else if (bp == NULL)
834 break;
835
836 if (cvh != NULL)
837 {
838 ib = buffer;
839 iblen = strlen (ib);
840 ob = bp = obuffer;
841 oblen = BUFSIZE * 6 - 1;
842 if (g_iconv (cvh, &ib, &iblen, &ob, &oblen) == (gsize)-1)
843 {
844 /*
845 * EINVAL - incomplete sequence at the end of the buffer. Move the
846 * incomplete sequence bytes to the beginning of the buffer for
847 * the next round of conversion.
848 */
849 if (errno == EINVAL)
850 {
851 inc_seq_bytes = iblen;
852 memmove (buffer, ib, inc_seq_bytes);
853 }
854 else
855 {
856 fprintf (stderr, _("%1$s: Error while converting input from '%2$s' to UTF-8.\n"),
857 g_get_prgname(), encoding);
858 exit(1);
859 }
860 }
861 obuffer[BUFSIZE * 6 - 1 - oblen] = 0;
862 }
863 g_string_append (inbuf, bp);
864 }
865
866 fclose (file);
867
868 /* Add a trailing new line if it is missing */
869 if (inbuf->len && inbuf->str[inbuf->len-1] != '\n')
870 g_string_append(inbuf, "\n");
871
872 text = inbuf->str;
873 g_string_free (inbuf, FALSE);
874
875 if (encoding != NULL && cvh != NULL)
876 g_iconv_close(cvh);
877
878 return text;
879 }
880
881
882 /* Take a UTF8 string and break it into paragraphs on \n characters
883 */
884 static GList *
split_text_into_paragraphs(cairo_t * cr,PangoContext * pango_context,page_layout_t * page_layout,int paint_width,const char * text)885 split_text_into_paragraphs (cairo_t *cr,
886 PangoContext *pango_context,
887 page_layout_t *page_layout,
888 int paint_width, /* In pixels */
889 const char *text)
890 {
891 const char *p = text;
892 char *next;
893 gunichar wc;
894 GList *result = NULL;
895 const char *last_para = text;
896
897 /* If we are using markup we treat the entire text as a single paragraph.
898 * I tested it and found that this is much slower than the split and
899 * assign method used below. Otherwise we might as well use this single
900 * chunk method always.
901 */
902 if (page_layout->do_use_markup)
903 {
904 Paragraph *para = g_new (Paragraph, 1);
905 para->wrapped = FALSE; /* No wrapped chars for markups */
906 para->clipped = FALSE;
907 para->text = text;
908 para->length = strlen(text);
909 para->layout = pango_layout_new (pango_context);
910 pango_layout_set_markup (para->layout, para->text, para->length);
911 pango_layout_set_justify (para->layout, page_layout->do_justify);
912 pango_layout_set_alignment (para->layout,
913 page_layout->pango_dir == PANGO_DIRECTION_LTR
914 ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT);
915
916 pango_layout_set_width (para->layout, paint_width * PANGO_SCALE);
917 pango_layout_set_wrap (para->layout, opt_wrap);
918
919 para->height = 0;
920
921 result = g_list_prepend (result, para);
922 }
923 else
924 {
925
926 while (p != NULL && *p)
927 {
928 wc = g_utf8_get_char (p);
929 next = g_utf8_next_char (p);
930 if (wc == (gunichar)-1)
931 {
932 fprintf (stderr, _("%s: Invalid character in input\n"), g_get_prgname ());
933 wc = 0;
934 }
935 if (!*p || !wc || wc == '\r' || wc == '\n' || wc == '\f')
936 {
937 Paragraph *para = g_new (Paragraph, 1);
938 para->wrapped = FALSE;
939 para->clipped = FALSE;
940 para->text = last_para;
941 para->length = p - last_para;
942 /* handle dos line breaks */
943 if (wc == '\r' && *next == '\n')
944 next = g_utf8_next_char(next);
945 para->layout = pango_layout_new (pango_context);
946 if (page_layout->cpi > 0.0L)
947 {
948 /* figuring out the correct width from the pango_font_metrics_get_approximate_width()
949 * is really hard and pango_layout_set_wrap() doesn't work properly then.
950 * Those are not reliable to render the characters exactly according to the given CPI.
951 * So re-calculate the width to wrap up to be comfortable with CPI.
952 */
953 wchar_t *wtext = NULL, *wnewtext = NULL;
954 gchar *newtext = NULL;
955 gsize len, col, i, wwidth = 0;
956 PangoRectangle ink_rect, logical_rect;
957
958 wtext = (wchar_t *)g_utf8_to_ucs4 (para->text, para->length, NULL, NULL, NULL);
959 if (wtext == NULL)
960 {
961 fprintf (stderr, _("%s: Unable to convert UTF-8 to UCS-4.\n"), g_get_prgname ());
962 fail:
963 g_free (wtext);
964 g_free (wnewtext);
965 g_free (newtext);
966 exit (1);
967 }
968 len = g_utf8_strlen (para->text, para->length);
969 /* the amount of characters that can be put on the line against CPI */
970 col = (int)(page_layout->column_width / 72.0 * page_layout->cpi);
971 if (len > col)
972 {
973 /* need to wrap them up */
974 wnewtext = g_new (wchar_t, wcslen (wtext) + 1);
975 para->clipped = TRUE;
976 if (wnewtext == NULL)
977 {
978 fprintf (stderr, _("%s: Unable to allocate the memory.\n"), g_get_prgname ());
979 goto fail;
980 }
981 for (i = 0; i < len; i++)
982 {
983 gssize w = wcwidth (wtext[i]);
984
985 if (w >= 0)
986 wwidth += w;
987 if (wwidth > col)
988 break;
989 wnewtext[i] = wtext[i];
990 }
991 wnewtext[i] = 0L;
992
993 newtext = g_ucs4_to_utf8 ((const gunichar *)wnewtext, i, NULL, NULL, NULL);
994 if (newtext == NULL)
995 {
996 fprintf (stderr, _("%s: Unable to convert UCS-4 to UTF-8.\n"), g_get_prgname ());
997 goto fail;
998 }
999 pango_layout_set_text (para->layout, newtext, -1);
1000 pango_layout_get_extents (para->layout, &ink_rect, &logical_rect);
1001 paint_width = logical_rect.width / PANGO_SCALE;
1002 g_free (wnewtext);
1003 g_free (newtext);
1004
1005 para->length = i;
1006 next = g_utf8_offset_to_pointer (para->text, para->length);
1007 wc = g_utf8_get_char (g_utf8_prev_char (next));
1008 }
1009 else
1010 {
1011 pango_layout_set_text (para->layout, para->text, para->length);
1012 }
1013
1014 g_free (wtext);
1015
1016 pango_layout_set_width (para->layout, -1);
1017 }
1018 else
1019 {
1020 pango_layout_set_text (para->layout, para->text, para->length);
1021 pango_layout_set_width (para->layout, paint_width * PANGO_SCALE);
1022
1023 pango_layout_set_wrap (para->layout, opt_wrap);
1024
1025 if (opt_wrap == PANGO_WRAP_CHAR)
1026 para->wrapped = TRUE;
1027
1028 /* Should we support truncation as well? */
1029 }
1030
1031 pango_layout_set_justify (para->layout, page_layout->do_justify);
1032 pango_layout_set_alignment (para->layout,
1033 page_layout->pango_dir == PANGO_DIRECTION_LTR
1034 ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT);
1035
1036 para->height = 0;
1037
1038 last_para = next;
1039
1040 if (wc == '\f')
1041 para->formfeed = 1;
1042 else
1043 para->formfeed = 0;
1044
1045 result = g_list_prepend (result, para);
1046 }
1047 if (!wc) /* incomplete character at end */
1048 break;
1049 p = next;
1050 }
1051 }
1052
1053 return g_list_reverse (result);
1054 }
1055
1056
1057
1058 /* Split a list of paragraphs into a list of lines.
1059 */
1060 GList *
split_paragraphs_into_lines(page_layout_t * page_layout,GList * paragraphs)1061 split_paragraphs_into_lines(page_layout_t *page_layout,
1062 GList *paragraphs)
1063 {
1064 GList *line_list = NULL;
1065 int max_height = 0;
1066 /* Read the file */
1067
1068 /* Now split all the pagraphs into lines */
1069 GList *par_list;
1070
1071 par_list = paragraphs;
1072 while(par_list)
1073 {
1074 int para_num_lines, i;
1075 LineLink *line_link;
1076 Paragraph *para = par_list->data;
1077
1078 para_num_lines = pango_layout_get_line_count(para->layout);
1079
1080 for (i=0; i<para_num_lines; i++)
1081 {
1082 PangoRectangle logical_rect, ink_rect;
1083
1084 line_link = g_new(LineLink, 1);
1085 line_link->formfeed = 0;
1086 line_link->wrapped = (para->wrapped && i < para_num_lines - 1) || (para->clipped);
1087 line_link->pango_line = pango_layout_get_line(para->layout, i);
1088 pango_layout_line_get_extents(line_link->pango_line,
1089 &ink_rect, &logical_rect);
1090 line_link->logical_rect = logical_rect;
1091 if (para->formfeed && i == (para_num_lines - 1))
1092 line_link->formfeed = 1;
1093 line_link->ink_rect = ink_rect;
1094 line_list = g_list_prepend(line_list, line_link);
1095 if (logical_rect.height > max_height)
1096 max_height = logical_rect.height;
1097 }
1098
1099 par_list = par_list->next;
1100 }
1101
1102 /*
1103 * not fixed for cairo backend: disable
1104 *
1105 if (page_layout->do_stretch_chars && page_layout->lpi > 0.0L)
1106 page_layout->scale_y = 1.0 / page_layout->lpi * 72.0 * PANGO_SCALE / max_height;
1107 */
1108
1109 return g_list_reverse(line_list);
1110
1111 }
1112
1113
1114 /*
1115 * Define PostScript document header information.
1116 */
1117 void
postscript_dsc_comments(cairo_surface_t * surface,page_layout_t * pl)1118 postscript_dsc_comments(cairo_surface_t *surface, page_layout_t *pl)
1119 {
1120 char buf[CAIRO_COMMENT_MAX];
1121 int x, y;
1122
1123 /*
1124 * Title
1125 */
1126 snprintf(buf, CAIRO_COMMENT_MAX, "%%%%Title: %s", pl->title);
1127 cairo_ps_surface_dsc_comment (surface, buf);
1128
1129 /*
1130 * Orientation
1131 */
1132 if (pl->do_landscape)
1133 {
1134 cairo_ps_surface_dsc_comment (surface, "%%Orientation: Landscape");
1135 x = (int)pl->page_height;
1136 y = (int)pl->page_width;
1137 }
1138 else
1139 {
1140 cairo_ps_surface_dsc_comment (surface, "%%Orientation: Portrait");
1141 x = (int)pl->page_width;
1142 y = (int)pl->page_height;
1143 }
1144
1145 /*
1146 * Redefine BoundingBox to cover the whole paper. Cairo creates the entry
1147 * based on the text only. This may affect further processing, such as with
1148 * convert(1).
1149 */
1150 snprintf(buf, CAIRO_COMMENT_MAX, "%%%%BoundingBox: 0 0 %d %d", x, y);
1151 cairo_ps_surface_dsc_comment (surface, buf);
1152
1153 /*
1154 * Duplex
1155 */
1156 if (pl->do_duplex)
1157 {
1158 cairo_ps_surface_dsc_comment(surface, "%%Requirements: duplex");
1159 cairo_ps_surface_dsc_begin_setup(surface);
1160
1161 if (pl->do_tumble)
1162 cairo_ps_surface_dsc_comment(surface, "%%IncludeFeature: *Duplex DuplexTumble");
1163 else
1164 cairo_ps_surface_dsc_comment(surface, "%%IncludeFeature: *Duplex DuplexNoTumble");
1165 }
1166 }
1167
1168
1169 int
output_pages(cairo_surface_t * surface,cairo_t * cr,GList * pango_lines,page_layout_t * page_layout,gboolean need_header,PangoContext * pango_context)1170 output_pages(cairo_surface_t *surface,
1171 cairo_t *cr,
1172 GList *pango_lines,
1173 page_layout_t *page_layout,
1174 gboolean need_header,
1175 PangoContext *pango_context)
1176 {
1177 int column_idx = 0;
1178 int column_y_pos = 0;
1179 int page_idx = 1;
1180 int pango_column_height = page_layout->column_height * PANGO_SCALE;
1181 int height = 0;
1182 LineLink *prev_line_link = NULL;
1183
1184 start_page(surface, cr, page_layout);
1185
1186 if (need_header)
1187 draw_page_header_line_to_page(cr, TRUE, page_layout, pango_context, page_idx);
1188
1189 while(pango_lines)
1190 {
1191 LineLink *line_link = pango_lines->data;
1192 PangoLayoutLine *line = line_link->pango_line;
1193 gboolean draw_wrap_character = page_layout->do_show_wrap && line_link->wrapped;
1194
1195 /* Check if we need to move to next column */
1196 if ((column_y_pos + line_link->logical_rect.height
1197 >= pango_column_height) ||
1198 (prev_line_link && prev_line_link->formfeed))
1199 {
1200 column_idx++;
1201 column_y_pos = 0;
1202 if (column_idx == page_layout->num_columns)
1203 {
1204 column_idx = 0;
1205 eject_page(cr);
1206 page_idx++;
1207 start_page(surface, cr, page_layout);
1208
1209 if (need_header)
1210 draw_page_header_line_to_page(cr, TRUE, page_layout, pango_context, page_idx);
1211 }
1212 else
1213 {
1214 eject_column(cr,
1215 page_layout,
1216 column_idx
1217 );
1218 }
1219 }
1220 if (page_layout->lpi > 0.0L)
1221 height = (int)(1.0 / page_layout->lpi * 72.0 * PANGO_SCALE);
1222 else
1223 height = line_link->logical_rect.height;
1224 draw_line_to_page(cr,
1225 column_idx,
1226 column_y_pos+height,
1227 page_layout,
1228 line,
1229 draw_wrap_character);
1230 column_y_pos += height;
1231 pango_lines = pango_lines->next;
1232 prev_line_link = line_link;
1233 }
1234 eject_page(cr);
1235 return page_idx;
1236 }
1237
eject_column(cairo_t * cr,page_layout_t * page_layout,int column_idx)1238 void eject_column(cairo_t *cr,
1239 page_layout_t *page_layout,
1240 int column_idx)
1241 {
1242 double x_pos, y_top, y_bot, total_gutter;
1243
1244 #if 0
1245 fprintf(stderr, "do_separation_line column_idx = %d %d\n", page_layout->do_separation_line, column_idx);
1246 #endif
1247 if (!page_layout->do_separation_line)
1248 return;
1249
1250 if (page_layout->pango_dir == PANGO_DIRECTION_RTL)
1251 column_idx = (page_layout->num_columns - column_idx);
1252
1253 if (column_idx == 1)
1254 total_gutter = 1.0 * page_layout->gutter_width /2;
1255 else
1256 total_gutter = (column_idx - 0.5) * page_layout->gutter_width;
1257
1258 x_pos = page_layout->left_margin
1259 + page_layout->column_width * column_idx
1260 + total_gutter;
1261
1262 y_top = page_layout->top_margin + page_layout->header_height + page_layout->header_sep / 2;
1263 y_bot = page_layout->page_height - page_layout->bottom_margin - page_layout->footer_height;
1264
1265 cairo_move_to(cr,x_pos, y_top);
1266 cairo_line_to(cr,x_pos, y_bot);
1267 cairo_set_line_width(cr, 0.1);
1268 cairo_stroke(cr);
1269 }
1270
eject_page(cairo_t * cr)1271 void eject_page(cairo_t *cr)
1272 {
1273 cairo_show_page(cr);
1274 }
1275
start_page(cairo_surface_t * surface,cairo_t * cr,page_layout_t * page_layout)1276 void start_page(cairo_surface_t *surface,
1277 cairo_t *cr,
1278 page_layout_t *page_layout)
1279 {
1280 cairo_identity_matrix(cr);
1281
1282 if (output_format == FORMAT_POSTSCRIPT)
1283 cairo_ps_surface_dsc_begin_page_setup (surface);
1284
1285 if (page_layout->do_landscape)
1286 {
1287 if (output_format == FORMAT_POSTSCRIPT)
1288 {
1289 cairo_ps_surface_dsc_comment (surface, "%%PageOrientation: Landscape");
1290 cairo_translate(cr, 0, page_layout->page_width);
1291 cairo_rotate(cr, 3*M_PI/2);
1292 }
1293 }
1294 else
1295 {
1296 if (output_format == FORMAT_POSTSCRIPT)
1297 cairo_ps_surface_dsc_comment (surface, "%%PageOrientation: Portrait");
1298 }
1299 }
1300
1301 void
draw_line_to_page(cairo_t * cr,int column_idx,int column_pos,page_layout_t * page_layout,PangoLayoutLine * line,gboolean draw_wrap_character)1302 draw_line_to_page(cairo_t *cr,
1303 int column_idx,
1304 int column_pos,
1305 page_layout_t *page_layout,
1306 PangoLayoutLine *line,
1307 gboolean draw_wrap_character)
1308 {
1309 /* Assume square aspect ratio for now */
1310 double y_pos = page_layout->top_margin
1311 + page_layout->header_sep
1312 + column_pos / PANGO_SCALE;
1313 double x_pos = page_layout->left_margin
1314 + column_idx * (page_layout->column_width
1315 + page_layout->gutter_width);
1316 PangoRectangle ink_rect, logical_rect;
1317
1318 /* Do RTL column layout for RTL direction */
1319 if (page_layout->pango_dir == PANGO_DIRECTION_RTL)
1320 {
1321 x_pos = page_layout->left_margin
1322 + (page_layout->num_columns-1-column_idx)
1323 * (page_layout->column_width + page_layout->gutter_width);
1324 }
1325
1326 pango_layout_line_get_extents(line,
1327 &ink_rect,
1328 &logical_rect);
1329
1330 if (page_layout->pango_dir == PANGO_DIRECTION_RTL) {
1331 x_pos += page_layout->column_width - logical_rect.width / PANGO_SCALE;
1332 }
1333
1334 cairo_move_to(cr, x_pos, y_pos);
1335 pango_cairo_show_layout_line(cr, line);
1336
1337 if (draw_wrap_character)
1338 {
1339 cairo_set_font_face(cr, paps_glyph_face);
1340 cairo_set_font_size(cr, glyph_font_size);
1341
1342 if (page_layout->pango_dir == PANGO_DIRECTION_LTR)
1343 {
1344 cairo_move_to(cr, x_pos + page_layout->column_width, y_pos);
1345 cairo_show_text(cr, "R");
1346 }
1347 else
1348 {
1349 double left_margin = page_layout->left_margin
1350 + (page_layout->num_columns-1-column_idx)
1351 * (page_layout->column_width + page_layout->gutter_width);
1352
1353 cairo_move_to(cr, left_margin, y_pos);
1354 cairo_show_text(cr, "L");
1355 }
1356 }
1357 }
1358
1359 /*
1360 * Provide date string from current locale converted to UTF-8.
1361 */
1362 char *
get_date(char * date,int maxlen)1363 get_date(char *date, int maxlen)
1364 {
1365 time_t t;
1366 GIConv cvh = NULL;
1367 GString *inbuf;
1368 char *ib, *ob, obuffer[BUFSIZE * 6], *bp;
1369 gsize iblen, oblen;
1370 static char *date_utf8 = NULL;
1371
1372 if (date_utf8 == NULL) {
1373 t = time(NULL);
1374 strftime(date, maxlen, "%c", localtime(&t));
1375
1376 cvh = g_iconv_open("UTF-8", get_encoding());
1377 if (cvh == (GIConv)-1) {
1378 fprintf(stderr, _("%s: Invalid encoding: %s\n"), g_get_prgname(), get_encoding());
1379 exit(1);
1380 }
1381
1382 inbuf = g_string_new(NULL);
1383 ib = bp = date;
1384 iblen = strlen(ib);
1385 ob = bp = obuffer;
1386 oblen = BUFSIZE * 6 - 1;
1387
1388 if (g_iconv(cvh, &ib, &iblen, &ob, &oblen) == (gsize)-1) {
1389 fprintf(stderr, _("%1$s: Error while converting date string from '%2$s' to UTF-8.\n"),
1390 g_get_prgname(), get_encoding());
1391 /* Return the unconverted string. */
1392 g_string_free(inbuf, FALSE);
1393 g_iconv_close(cvh);
1394 return date;
1395 }
1396
1397 obuffer[BUFSIZE * 6 - 1 - oblen] = 0;
1398 g_string_append(inbuf, bp);
1399
1400 date_utf8 = inbuf->str;
1401 g_string_free(inbuf, FALSE);
1402 g_iconv_close(cvh);
1403 }
1404
1405 return date_utf8;
1406 }
1407
1408 int
draw_page_header_line_to_page(cairo_t * cr,gboolean is_footer,page_layout_t * page_layout,PangoContext * ctx,int page)1409 draw_page_header_line_to_page(cairo_t *cr,
1410 gboolean is_footer,
1411 page_layout_t *page_layout,
1412 PangoContext *ctx,
1413 int page)
1414 {
1415 PangoLayout *layout = pango_layout_new(ctx);
1416 PangoLayoutLine *line;
1417 PangoRectangle ink_rect, logical_rect, pagenum_rect;
1418 /* Assume square aspect ratio for now */
1419 double x_pos, y_pos;
1420 gchar *header, date[256];
1421 int height;
1422 gdouble line_pos;
1423
1424 /* Reset gravity?? */
1425 #if 0
1426 header = g_strdup_printf("<span font_desc=\"%s\">%s</span>\n"
1427 "<span font_desc=\"%s\">%s</span>\n"
1428 "<span font_desc=\"%s\">%d</span>",
1429 page_layout->header_font_desc,
1430 page_layout->title,
1431 page_layout->header_font_desc,
1432 get_date(date, 255),
1433 page_layout->header_font_desc,
1434 page);
1435 #endif
1436 header = g_strdup_printf("<span font_desc=\"%s\">%d</span>\n",
1437 page_layout->header_font_desc,
1438 page);
1439
1440 pango_layout_set_markup(layout, header, -1);
1441 g_free(header);
1442
1443 /* output a left edge of header/footer */
1444 line = pango_layout_get_line(layout, 0);
1445 pango_layout_line_get_extents(line,
1446 &ink_rect,
1447 &logical_rect);
1448 x_pos = page_layout->left_margin + (page_layout->page_width-page_layout->left_margin-page_layout->right_margin)*0.5 - 0.5*logical_rect.width/PANGO_SCALE;
1449 height = logical_rect.height / PANGO_SCALE /3.0;
1450
1451 /* The header is placed right after the margin */
1452 if (is_footer)
1453 {
1454 y_pos = page_layout->page_height - page_layout->bottom_margin*0.5;
1455 page_layout->footer_height = height;
1456 }
1457 else
1458 {
1459 y_pos = page_layout->top_margin + height;
1460 page_layout->header_height = height;
1461 }
1462
1463 cairo_move_to(cr, x_pos,y_pos);
1464 pango_cairo_show_layout_line(cr,line);
1465
1466 /* output a right edge of header/footer */
1467 line = pango_layout_get_line(layout, 2);
1468 pango_layout_line_get_extents(line,
1469 &ink_rect,
1470 &logical_rect);
1471 pagenum_rect = logical_rect;
1472 x_pos = page_layout->page_width - page_layout->right_margin - (logical_rect.width / PANGO_SCALE );
1473 cairo_move_to(cr, x_pos,y_pos);
1474 pango_cairo_show_layout_line(cr,line);
1475
1476 /* output a "center" of header/footer */
1477 line = pango_layout_get_line(layout, 1);
1478 pango_layout_line_get_extents(line,
1479 &ink_rect,
1480 &logical_rect);
1481 x_pos = page_layout->page_width - page_layout->right_margin -
1482 ((logical_rect.width + pagenum_rect.width) / PANGO_SCALE + page_layout->gutter_width);
1483 cairo_move_to(cr, x_pos,y_pos);
1484 pango_cairo_show_layout_line(cr,line);
1485
1486 g_object_unref(layout);
1487
1488 /* header separator */
1489 #if 0
1490 line_pos = page_layout->top_margin + page_layout->header_height + page_layout->header_sep / 2;
1491 cairo_move_to(cr, page_layout->left_margin, line_pos);
1492 cairo_line_to(cr,page_layout->page_width - page_layout->right_margin, line_pos);
1493 cairo_set_line_width(cr,0.1); // TBD
1494 cairo_stroke(cr);
1495 #endif
1496
1497 return logical_rect.height;
1498 }
1499