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