(mtext__adjust_foramt): Extern it.
[m17n/m17n-lib.git] / example / mdump.c
1 /* mdump.c -- Dump text image
2    Copyright (C) 2003, 2004
3      National Institute of Advanced Industrial Science and Technology (AIST)
4      Registration Number H15PRO112
5
6    This file is part of the m17n library.
7
8    The m17n library is free software; you can redistribute it and/or
9    modify it under the terms of the GNU Lesser General Public License
10    as published by the Free Software Foundation; either version 2.1 of
11    the License, or (at your option) any later version.
12
13    The m17n library is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    Lesser General Public License for more details.
17
18    You should have received a copy of the GNU Lesser General Public
19    License along with the m17n library; if not, write to the Free
20    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
21    02111-1307, USA.  */
22
23 /***en
24     @page mdump dump text image
25
26     @section mdump-synopsis SYNOPSIS
27
28     mdump [ OPTION ... ] [ FILE ]
29
30     @section mdump-description DESCRIPTION
31
32     Dump a text as a Netpbm image.
33
34     The Netpbm image is written to a file created in the current
35     directory with the name "BASE.pbm" where BASE is the basename of
36     FILE.  If FILE is omitted, text is read from standard input, and
37     the image is dumped into the file "output.pbm".
38
39     The following OPTIONs are available.
40
41     <ul>
42
43     <li> -s SIZE
44
45     SIZE is the font size in point.  The default font size is 12 point.
46
47     <li> -d DPI
48
49     DPI is the resolution in dots per inch.  The default resolution is
50     300 dpi.
51
52     <li> -p PAPER
53
54     PAPER is the paper size: a4, a4r, a5, a5r, b5, b5r, letter, or
55     WxH.  In the last case, W and H are the width and height in
56     millimeter.  If this option is specified, PAPER limits the image
57     size.  If FILE is too large for a single page, multiple files with
58     the names "BASE.01.pbm", "BASE.02.pbm", etc. are created.
59
60     <li> -m MARGIN
61
62     MARGIN is the horizontal and vertical margin in millimeter.  The
63     default margin is 20 mm.  It is ignored when PAPER is not
64     specified.
65
66     <li> -c POS
67
68     POS is the character position of cursor to draw.  By default,
69     cursor is not drawn.
70
71     <li> -x
72
73     FILE is assumed to be an XML file generated by the serialize
74     facility of the m17n library, and FILE is deserialized before the
75     image is created.
76
77     <li> -w
78
79     Each line is broken at word boundary.
80
81     <li> -f FILTER
82
83     FILTER is a string containing a shell command line.  If this
84     option is specified, the Netpbm image is not written info a
85     file but is given to FILTER as standard input.  If FILTER
86     contains "%s", that part is replaced by a basename of FILE.
87     So, the default behaviour is the same as specifying "cat >
88     %s.pbm" as FILTER.
89
90     <li> -q
91
92     Quiet mode.  Don't print any messages.
93
94     <li> --version
95
96     Print the version number.
97
98     <li> -h, --help
99
100     Print this message.
101
102     </ul>
103 */
104
105 #ifndef FOR_DOXYGEN
106
107 #include <stdio.h>
108 #include <stdlib.h>
109 #include <string.h>
110 #include <libgen.h>
111
112 #include <X11/Xlib.h>
113
114 #include <m17n-gui.h>
115 #include <m17n-misc.h>
116
117 #define VERSION "1.0"
118
119 /* Enumuration of the supported paper types.  */
120 enum paper_type
121   {
122     PAPER_A4,
123     PAPER_A4R,
124     PAPER_A5,
125     PAPER_A5R,
126     PAPER_B5,
127     PAPER_B5R,
128     PAPER_LETTER,
129     PAPER_USER,
130     PAPER_NOLIMIT
131   };
132
133 /* Array of paper sizes for the supported paper types.  */
134 struct
135 {
136   int width, height;            /* in millimeter */
137 } paper_size[PAPER_NOLIMIT] = {
138   { 210, 297 },                 /* a4 */
139   { 297, 210 },                 /* a4r */
140   { 148, 210 },                 /* a5 */
141   { 210, 148 },                 /* a5r */
142   { 250, 176 },                 /* b5 */
143   { 176, 250 },                 /* b5r */
144   { 216, 279 },                 /* letter */
145 };
146
147
148 /* Print the usage of this program (the name is PROG), and exit with
149    EXIT_CODE.  */
150
151 void
152 help_exit (char *prog, int exit_code)
153 {
154   char *p = prog;
155
156   while (*p)
157     if (*p++ == '/')
158       prog = p;
159
160   printf ("Usage: %s [ OPTION ...] [ FILE ]\n", prog);
161   printf ("Dump a text as a Netpbm image into a PBM file.\n");
162   printf ("  The PBM file is created in the current directory\n");
163   printf ("    with the name \"BASE.pbm\" where BASE is the basename of FILE.\n");
164   printf ("  If FILE is omitted, text is read from standard input, and\n");
165   printf ("    dumped into the file \"output.pbm\".\n");
166   printf ("The following OPTIONs are available.\n");
167   printf ("  %-13s %s", "-s SIZE",
168           "Font size in point (default 12).\n");
169   printf ("  %-13s %s", "-d DPI",
170           "Resolution in dots per inch (defualt 300).\n");
171   printf ("  %-13s %s", "-p PAPER",
172           "Paper size; a4, a4r, a5, a5r, b5, b5r, letter, or WxH.\n");
173   printf ("  %-13s %s", "-m MARGIN",
174           "Marginal space in millimeter (default 20).\n");
175   printf ("  %-13s %s", "-c POS",
176           "Character position of cursor to draw (default no cursor)\n");
177   printf ("  %-13s %s", "-x",
178           "FILE is assumed to be an XML file.\n");
179   printf ("  %-13s %s", "-f FILTER",
180           "String containing a shell command line to be used as a filter.\n");
181   printf ("  %-13s %s", "-w", "Each line is broken at word boundary.\n");
182   printf ("  %-13s %s", "-q", "Quiet mode.  Don't print any messages.\n");
183   printf ("  %-13s %s", "--version", "Print the version number.\n");
184   printf ("  %-13s %s", "-h, --help", "Print this message.\n");
185   exit (exit_code);
186 }
187
188
189 /* Format MSG by FMT and print the result to the stderr, and exit.  */
190
191 #define FATAL_ERROR(fmt, arg)   \
192   do {                          \
193     fprintf (stderr, fmt, arg); \
194     exit (1);                   \
195   } while (0)
196
197
198 /* Move POS to the next line head in M-text MT whose length is LEN.
199    If POS is already on the last line, set POS to LEN.  */
200
201 #define NEXTLINE(pos, len)                      \
202   do {                                          \
203     pos = mtext_character (mt, pos, len, '\n'); \
204     if (pos < 0)                                \
205       pos = len;                                \
206     else                                        \
207       pos++;                                    \
208   } while (0)
209
210
211 /* Find the range of M-text MT that fits in one page of height HEIGHT
212    when drawn from the character position POS.  Set RECT->y to the
213    Y-offset of the first baseline.  */
214
215 int
216 find_page_end (MFrame *frame, int height, MText *mt, int pos,
217                MDrawControl *control, MDrawMetric *rect)
218 {
219   int len = mtext_len (mt);
220   int to = pos;
221   int y = 0, yoff;
222
223   while (to < len)
224     {
225       int next = to;
226
227       NEXTLINE (next, len);
228       mdraw_text_extents (frame, mt, to, next, control, NULL, NULL, rect);
229       if (to == pos)
230         yoff = rect->y;
231       if (y + rect->height > height)
232         {
233           MDrawGlyphInfo info;
234
235           while (to < next)
236             {
237               mdraw_glyph_info (frame, mt, to, to, control, &info);
238               if (y + info.this.height > height)
239                 break;
240               y += info.this.height;
241               to = info.line_to;
242             }
243           break;
244         }
245       y += rect->height;
246       to = next;
247     }
248
249   rect->y = yoff;
250   return to;
251 }
252
253
254 /* Table to convert a byte of LSBFirst to MSBFirst. */
255 char reverse_bit_order[256];
256
257 /* Initialize the above table.  */
258
259 void
260 init_reverse_bit_order ()
261 {
262   int i;
263
264   for (i = 0; i < 256; i++)
265     reverse_bit_order[i]
266       = (((i & 1) << 7) | ((i & 2) << 5) | ((i & 4) << 3) | ((i & 8) << 1)
267          | ((i & 16) >> 1) | ((i & 32) >> 3)
268          | ((i & 64) >> 5) | ((i & 128) >> 7));
269 }
270
271
272 /* Dump the image in IMAGE into a file whose name is generated from
273    FILENAME and PAGE_INDEX (if it is not zero).  */
274
275 void
276 dump_image (XImage *image, char *filename, char *filter,
277             int white_is_zero, int page_index, int quiet_mode)
278 {
279   FILE *fp;
280   int pbm_bytes_per_line;
281   char *data = image->data;
282   int x, y;
283
284   if (page_index)
285     {
286       char *name = alloca (strlen (filename) + 8);
287
288       sprintf (name, "%s.%02d", filename, page_index);
289       filename = name;
290     }
291
292   if (filter)
293     {
294       char *command = alloca (strlen (filename) + strlen (filter) + 1);
295
296       sprintf (command, filter, filename);
297       fp = popen (command, "w");
298       if (! fp)
299         FATAL_ERROR ("Can't run the command \"%s\"\n", command);
300       if (! quiet_mode)
301         printf ("Running \"%s\" ... ", command);
302     }
303   else
304     {
305       char *fullname = alloca (strlen (filename) + 5);
306
307       sprintf (fullname, "%s.pbm", filename);
308       fp = fopen (fullname, "w");
309       if (! fp)
310         FATAL_ERROR ("Can't write to \"%s\"\n", fullname);
311       if (! quiet_mode)
312         printf ("Writing %s ... ", fullname);
313     }
314
315   if (image->bitmap_bit_order != MSBFirst
316       || (image->byte_order != MSBFirst
317           && image->bitmap_unit != 8))
318     {
319       /* We must adjust image->data for PBM.  */
320       int bytes_per_unit = image->bitmap_unit / 8;
321
322       for (y = 0; y < image->height; y++, data += image->bytes_per_line)
323         {
324           char b;
325
326           if (image->byte_order != MSBFirst)
327             {
328               if (reverse_bit_order[0] == 0)
329                 init_reverse_bit_order ();
330               if (bytes_per_unit == 2)
331                 for (x = 0; x < image->bytes_per_line; x += 2)
332                   b = data[x], data[x] = data[x + 1], data[x + 1] = b;
333               else if (bytes_per_unit == 6)
334                 for (x = 0; x < image->bytes_per_line; x += 3)
335                   {
336                     b = data[x], data[x] = data[x + 3], data[x + 3] = b;
337                     x++;
338                     b = data[x], data[x] = data[x + 1], data[x + 1] = b;
339                   }
340             }
341
342           if (image->bitmap_bit_order != MSBFirst)
343             for (x = 0; x < image->bytes_per_line; x++)
344               data[x] = reverse_bit_order[(unsigned char) data[x]];
345           if (! white_is_zero)
346             for (x = 0; x < image->bytes_per_line; x++)
347               data[x] = ~data[x];
348         }
349       /* Reset DATA.  */
350       data = image->data;
351     }
352
353   /* Generate PBM (Portable Bitmap File Format) of P4 format.  */
354   fprintf (fp, "P4\n%d %d\n", image->width, image->height);
355   pbm_bytes_per_line = (image->width + 7) / 8;
356   for (y = 0; y < image->height; y++, data += image->bytes_per_line)
357     fwrite (data, 1, pbm_bytes_per_line, fp);
358
359   fclose (fp);
360   if (! quiet_mode)
361     printf (" done (%dx%d)\n", image->width, image->height);
362 }
363
364 extern int line_break (MText *mt, int pos, int from, int to, int line, int y);
365
366 int
367 main (int argc, char **argv)
368 {
369   Display *display;
370   int screen;
371   GC gc;
372   Pixmap pixmap;
373   XImage *image;
374   int fontsize = 120;
375   int paper = PAPER_NOLIMIT;
376   int dpi = 300;
377   int margin = 20;
378   int xml = 0;
379   FILE *fp = stdin;
380   int cursor_pos = -1;
381   int quiet_mode = 0;
382   int break_by_word = 0;
383   char *filter = NULL;
384   int i;
385   int paper_width, paper_height;
386   int page_index;
387
388   MFrame *frame;
389   MText *mt;
390   MDrawControl control;
391   MDrawMetric rect;
392   char *filename = "output";
393   int len, from;
394
395   /* Parse the command line arguments.  */
396   for (i = 1; i < argc; i++)
397     {
398       if (! strcmp (argv[i], "--help")
399                || ! strcmp (argv[i], "-h")
400                || ! strcmp (argv[i], "-?"))
401         help_exit (argv[0], 0);
402       else if (! strcmp (argv[i], "--version"))
403         {
404           printf ("mdump (m17n library) %s\n", VERSION);
405           printf ("Copyright (C) 2003, 2004 AIST, JAPAN\n");
406           exit (0);
407         }
408       else if (! strcmp (argv[i], "-s") && i + 1< argc)
409         {
410           fontsize = atoi (argv[++i]);
411           if (! fontsize)
412             FATAL_ERROR ("Invalid font size: %s\n", argv[i]);
413         }
414       else if (! strcmp (argv[i], "-p") && i + 1< argc)
415         {
416           int w, h;
417
418           i++;
419           if (! strcmp (argv[i], "a4"))
420             paper = PAPER_A4;
421           else if (! strcmp (argv[i], "a4r"))
422             paper = PAPER_A4R;
423           else if (! strcmp (argv[i], "a5"))
424             paper = PAPER_A5;
425           else if (! strcmp (argv[i], "a5r"))
426             paper = PAPER_A5R;
427           else if (! strcmp (argv[i], "b5"))
428             paper = PAPER_B5;
429           else if (! strcmp (argv[i], "b5r"))
430             paper = PAPER_B5R;
431           else if (! strcmp (argv[i], "letter"))
432             paper = PAPER_LETTER;
433           else if (sscanf (argv[i], "%dx%d", &w, &h) == 2
434                    && w > 0 && h > 0)
435             {
436               paper = PAPER_USER;
437               paper_size[paper].width = w;
438               paper_size[paper].height = h;
439             }
440           else
441             FATAL_ERROR ("Invalid paper type: %s\n", argv[i]);
442         }
443       else if (! strcmp (argv[i], "-d") && i + 1< argc)
444         {
445           dpi = atoi (argv[++i]);
446           if (! dpi)
447             FATAL_ERROR ("Invalid resolution: %s\n", argv[i]);
448         }
449       else if (! strcmp (argv[i], "-m") && i + 1< argc)
450         {
451           margin = atoi (argv[++i]);
452           if (margin < 0)
453             FATAL_ERROR ("Invalid margin: %s\n", argv[i]);
454         }
455       else if (! strcmp (argv[i], "-c") && i + 1< argc)
456         {
457           cursor_pos = atoi (argv[++i]);
458           if (cursor_pos < 0)
459             FATAL_ERROR ("Invalid cursor position: %s\n", argv[i]);
460         }
461       else if (! strcmp (argv[i], "-f") && i + 1< argc)
462         {
463           filter = argv[++i];
464         }
465       else if (! strcmp (argv[i], "-x"))
466         {
467           xml = 1;
468         }
469       else if (! strcmp (argv[i], "-w"))
470         {
471           break_by_word = 1;
472         }
473       else if (! strcmp (argv[i], "-q"))
474         {
475           quiet_mode = 1;
476         }
477       else if (argv[i][0] != '-')
478         {
479           fp = fopen (argv[i], "r");
480           if (! fp)
481             FATAL_ERROR ("Fail to open the file %s!\n", argv[i]);
482           filename = basename (argv[i]);
483         }
484       else
485         {
486           fprintf (stderr, "Unknown or invalid option: %s\n", argv[i]);
487           help_exit (argv[0], 1);
488         }
489     }
490
491   /* Initialize the m17n library.  */
492   M17N_INIT ();
493   if (merror_code != MERROR_NONE)
494     FATAL_ERROR ("%s\n", "Fail to initialize the m17n library.");
495
496   mt = mconv_decode_stream (Mcoding_utf_8, fp);
497   fclose (fp);
498   if (xml)
499     mt = mtext_deserialize (mt);
500   if (! mt)
501     FATAL_ERROR ("%s\n", "Fail to decode the input file or stream!");
502
503   len = mtext_len (mt);
504
505   if (paper == PAPER_NOLIMIT)
506     paper_width = paper_height = margin = 0;
507   else
508     {
509       paper_width = paper_size[paper].width * dpi / 25.4;
510       paper_height = paper_size[paper].height * dpi / 25.4;
511       margin = margin * dpi / 25.4;
512     }
513
514   display = XOpenDisplay (NULL);
515   screen = DefaultScreen (display);
516
517   {
518     MPlist *plist = mplist (), *p;
519     MFontset *fontset = mfontset ("truetype");
520     MFace *face = mface ();
521
522     mface_put_prop (face, Mfontset, fontset);
523     mface_put_prop (face, Msize, (void *) (fontsize * dpi / 100));
524     p = mplist_add (plist, msymbol ("display"), display);
525     p = mplist_add (p, msymbol ("depth"), (void *) 1);
526     p = mplist_add (p, Mface, face);
527     m17n_object_unref (face);
528     frame = mframe (plist);
529     m17n_object_unref (plist);
530     if (! frame)
531       FATAL_ERROR ("%s\n", "Can't open a frame (perhaps no font avairable)!");
532   }
533
534   memset (&control, 0, sizeof control);
535   control.as_image = 1;
536   control.two_dimensional = 1;
537   control.enable_bidi = 1;
538   if (cursor_pos >= 0)
539     {
540       control.with_cursor = 1;
541       if (cursor_pos > len)
542         cursor_pos = len;
543       control.cursor_pos = cursor_pos;
544       control.cursor_width = -1;
545     }
546   else
547     control.ignore_formatting_char = 1;
548   if (break_by_word)
549     control.line_break = line_break;
550
551   if (paper == PAPER_NOLIMIT)
552     {
553       control.max_line_width = 0;
554       mdraw_text_extents (frame, mt, 0, len, &control, NULL, NULL, &rect);
555       paper_width = rect.width;
556       paper_height = rect.height;
557     }
558   else
559     control.max_line_width = paper_width - margin * 2;
560
561   pixmap = XCreatePixmap (display, RootWindow (display, screen),
562                           paper_width, paper_height, 1);
563   gc = XCreateGC (display, pixmap, 0L, NULL);
564
565   from = 0;
566   page_index = 1;
567   while (from < len)
568     {
569       int to;
570
571       if (paper == PAPER_NOLIMIT)
572         to = len;
573       else
574         to = find_page_end (frame, paper_height - margin * 2, mt, from,
575                             &control, &rect);
576
577       XSetForeground (display, gc, WhitePixel (display, screen));
578       XFillRectangle (display, pixmap, gc, 0, 0, paper_width, paper_height);
579       mdraw_text_with_control (frame, (MDrawWindow) pixmap,
580                                margin, margin - rect.y, mt, from, to,
581                                &control);
582       XSetForeground (display, gc, BlackPixel (display, screen));
583 #if 0
584       XDrawRectangle (display, pixmap, gc, margin, margin,
585                       paper_width - margin * 2 - 1,
586                       paper_height - margin * 2 - 1);
587 #endif
588       image = XGetImage (display, pixmap, 0, 0, paper_width, paper_height,
589                          AllPlanes, XYPixmap);
590       XInitImage (image);
591       dump_image (image, filename, filter, !WhitePixel (display, screen),
592                   ((from > 0 || to < len) ? page_index : 0),
593                   quiet_mode);
594
595       from = to;
596       page_index++;
597     }
598
599   m17n_object_unref (frame);
600   m17n_object_unref (mt);
601   M17N_FINI ();
602   XCloseDisplay (display);
603   exit (0);
604 }
605 #endif /* not FOR_DOXYGEN */