fbd1bbf5a58089d062d96469c0b9636816be8387
[platform/upstream/groff.git] / src / devices / grolbp / lbp.cpp
1 // -*- C++ -*-
2 /* Copyright (C) 1994-2014 Free Software Foundation, Inc.
3      Written by Francisco Andrés Verdú <pandres@dragonet.es> with many ideas
4      taken from the other groff drivers.
5
6 This file is part of groff.
7
8 groff is free software; you can redistribute it and/or modify it under
9 the terms of the GNU General Public License as published by the Free
10 Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 groff is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16 for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>. */
20
21 /*
22 TODO
23
24  - Add X command to include bitmaps
25 */
26
27 #include "driver.h"
28 #include "lbp.h"
29 #include "charset.h"
30 #include "paper.h"
31
32 #include "nonposix.h"
33
34 extern "C" const char *Version_string;
35
36 static int user_papersize = -1;         // papersize
37 static int orientation = -1;            // orientation
38 static double user_paperlength = 0;     // Custom Paper size
39 static double user_paperwidth = 0;
40 static int ncopies = 1;                 // Number of copies
41
42 #define DEFAULT_LINEWIDTH_FACTOR 40     // 0.04em
43 static int linewidth_factor = DEFAULT_LINEWIDTH_FACTOR;
44
45 static int set_papersize(const char *paperformat);
46
47 class lbp_font : public font {
48 public:
49   ~lbp_font();
50   void handle_unknown_font_command(const char *command, const char *arg,
51                                    const char *filename, int lineno);
52   static lbp_font *load_lbp_font(const char *);
53   char *lbpname;
54   char is_scalable;
55 private:
56   lbp_font(const char *);
57 };
58
59 class lbp_printer : public printer {
60 public:
61   lbp_printer(int, double, double);
62   ~lbp_printer();
63   void set_char(glyph *, font *, const environment *, int, const char *name);
64   void draw(int code, int *p, int np, const environment *env);
65   void begin_page(int);
66   void end_page(int page_length);
67   font *make_font(const char *);
68   void end_of_line();
69 private:
70   void set_line_thickness(int size,const environment *env);
71   void vdmstart();
72   void vdmflush(); // the name vdmend was already used in lbp.h
73   void setfillmode(int mode);
74   void polygon( int hpos,int vpos,int np,int *p);
75   char *font_name(const lbp_font *f, const int siz);
76
77   int fill_pattern;
78   int fill_mode;
79   int cur_hpos;
80   int cur_vpos;
81   lbp_font *cur_font;
82   int cur_size;
83   unsigned short cur_symbol_set;
84   int line_thickness;
85   int req_linethickness; // requested line thickness
86   int papersize;
87   int paperlength;      // custom paper size
88   int paperwidth;
89 };
90
91 lbp_font::lbp_font(const char *nm)
92 : font(nm)
93 {
94 }
95
96 lbp_font::~lbp_font()
97 {
98 }
99
100 lbp_font *lbp_font::load_lbp_font(const char *s)
101 {
102   lbp_font *f = new lbp_font(s);
103   f->lbpname = NULL;
104   f->is_scalable = 1; // Default is that fonts are scalable
105   if (!f->load()) {
106     delete f;
107     return 0;
108   }
109   return f;
110 }
111
112
113 void lbp_font::handle_unknown_font_command(const char *command,
114                                            const char *arg,
115                                            const char *filename, int lineno)
116 {
117   if (strcmp(command, "lbpname") == 0) {
118     if (arg == 0)
119       fatal_with_file_and_line(filename, lineno,
120                                "`%1' command requires an argument",
121                                command);
122     this->lbpname = new char[strlen(arg) + 1];
123     strcpy(this->lbpname, arg);
124     // we recognize bitmapped fonts by the first character of its name
125     if (arg[0] == 'N')
126       this->is_scalable = 0;
127     // fprintf(stderr, "Loading font \"%s\" \n", arg);
128   }
129   // fprintf(stderr, "Loading font  %s \"%s\" in %s at %d\n",
130   //         command, arg, filename, lineno);
131 }
132
133 static void wp54charset()
134 {
135   unsigned int i;
136   lbpputs("\033[714;100;29;0;32;120.}");
137   for (i = 0; i < sizeof(symset); i++)
138     lbpputc(symset[i]);
139   lbpputs("\033[100;0 D");
140   return;
141 }
142
143 lbp_printer::lbp_printer(int ps, double pw, double pl)
144 : fill_pattern(1),
145   fill_mode(0),
146   cur_hpos(-1),
147   cur_font(0),
148   cur_size(0),
149   cur_symbol_set(0),
150   req_linethickness(-1)
151 {
152   SET_BINARY(fileno(stdout));
153   lbpinit(stdout);
154   lbpputs("\033c\033;\033[2&z\033[7 I\033[?32h\033[?33h\033[11h");
155   wp54charset(); // Define the new symbol set
156   lbpputs("\033[7 I\033[?32h\033[?33h\033[11h");
157   // Paper size handling
158   if (orientation < 0)
159     orientation = 0;    // Default orientation is portrait
160   papersize = 14;       // Default paper size is A4
161   if (font::papersize) {
162     papersize = set_papersize(font::papersize);
163     paperlength = font::paperlength;
164     paperwidth = font::paperwidth;
165   }
166   if (ps >= 0) {
167     papersize = ps;
168     paperlength = int(pl * font::res + 0.5);
169     paperwidth = int(pw * font::res + 0.5);
170   }
171   if (papersize < 80)   // standard paper
172     lbpprintf("\033[%dp", (papersize | orientation));
173   else                  // Custom paper
174     lbpprintf("\033[%d;%d;%dp", (papersize | orientation),
175               paperlength, paperwidth);
176   // Number of copies
177   lbpprintf("\033[%dv\n", ncopies);
178   lbpputs("\033[0u\033[1u\033P1y Grolbp\033\\");
179   lbpmoveabs(0, 0);
180   lbpputs("\033[0t\033[2t");
181   lbpputs("\033('$2\033)' 1");  // Primary symbol set IBML
182                                 // Secondary symbol set IBMR1
183   cur_symbol_set = 0;
184 }
185
186 lbp_printer::~lbp_printer()
187 {
188   lbpputs("\033P1y\033\\");
189   lbpputs("\033c\033<");
190 }
191
192 inline void lbp_printer::set_line_thickness(int size,const environment *env)
193 {
194       if (size == 0)
195         line_thickness = 1;
196       else {
197         if (size < 0)
198                 // line_thickness =
199                 //   (env->size * (font::res/72)) * (linewidth_factor/1000)
200                 // we ought to check for overflow
201                 line_thickness =
202                   env->size * linewidth_factor * font::res / 72000;
203         else // size > 0
204                 line_thickness = size;
205       } // else from if (size == 0)
206       if (line_thickness < 1)
207         line_thickness = 1;
208       if (vdminited())
209         vdmlinewidth(line_thickness);
210       req_linethickness = size; // an size requested
211       /*  fprintf(stderr, "thickness: %d == %d, size %d, %d \n",
212         size, line_thickness, env->size,req_linethickness); */
213    return;
214 } // lbp_printer::set_line_thickness
215
216 void lbp_printer::begin_page(int)
217 {
218 }
219
220 void lbp_printer::end_page(int)
221 {
222   if (vdminited())
223     vdmflush();
224   lbpputc('\f');
225   cur_hpos = -1;
226 }
227
228 void lbp_printer::end_of_line()
229 {
230   cur_hpos = -1;                // force absolute motion
231 }
232
233 char *lbp_printer::font_name(const lbp_font *f, const int siz)
234 {
235   static char bfont_name[255];  // The resulting font name
236   char type,    // Italic, Roman, Bold
237        ori,     // Normal or Rotated
238        *nam;    // The font name without other data.
239   int cpi;      // The font size in characters per inch
240                 // (bitmapped fonts are monospaced).
241   /* Bitmap font selection is ugly in this printer, so don't expect
242      this function to be elegant. */
243   bfont_name[0] = 0x00;
244   if (orientation)      // Landscape
245     ori = 'R';
246   else                  // Portrait
247     ori = 'N';
248   type = f->lbpname[strlen(f->lbpname) - 1];
249   nam = new char[strlen(f->lbpname) - 2];
250   strncpy(nam, &(f->lbpname[1]), strlen(f->lbpname) - 2);
251   nam[strlen(f->lbpname) - 2] = 0x00;
252   // fprintf(stderr, "Bitmap font '%s' %d %c %c \n", nam, siz, type, ori);
253   /* Since these fonts are available only at certain sizes,
254      10 and 17 cpi for courier,  12 and 17 cpi for elite,
255      we adjust the resulting size. */
256   cpi = 17;
257   // Fortunately there are only two bitmapped fonts shipped with the printer.
258   if (!strcasecmp(nam, "courier")) {
259     // Courier font
260     if (siz >= 12)
261       cpi = 10;
262     else cpi = 17;
263   }
264   if (!strcasecmp(nam, "elite")) {
265     if (siz >= 10)
266       cpi = 12;
267     else cpi = 17;
268   }
269   // Now that we have all the data, let's generate the font name.
270   if ((type != 'B') && (type != 'I')) // Roman font
271     sprintf(bfont_name, "%c%s%d", ori, nam, cpi);
272   else
273     sprintf(bfont_name, "%c%s%d%c", ori, nam, cpi, type);
274   return bfont_name;
275 }
276
277 void lbp_printer::set_char(glyph *g, font *f, const environment *env,
278                            int w, const char *)
279 {
280   int code = f->get_code(g);
281   unsigned char ch = code & 0xff;
282   unsigned short symbol_set = code >> 8;
283   if (f != cur_font) {
284     lbp_font *psf = (lbp_font *)f;
285     // fprintf(stderr, "Loading font %s \"%d\" \n", psf->lbpname, env->size);
286     if (psf->is_scalable) {
287       // Scalable font selection is different from bitmaped
288       lbpprintf("\033Pz%s.IBML\033\\\033[%d C", psf->lbpname,
289                 (int)((env->size * font::res) / 72));
290     }
291     else
292       // bitmapped font
293       lbpprintf("\033Pz%s.IBML\033\\\n", font_name(psf, env->size));
294     lbpputs("\033)' 1");        // Select IBML and IBMR1 symbol set
295     cur_font = psf;
296     cur_symbol_set = 0;
297      // Update the line thickness if needed
298     if ((req_linethickness < 0 ) && (env->size != cur_size))
299         set_line_thickness(req_linethickness,env);
300     cur_size = env->size;
301   }
302   if (symbol_set != cur_symbol_set) {
303     if (cur_symbol_set == 3)
304       // if current symbol set is Symbol we must restore the font
305       lbpprintf("\033Pz%s.IBML\033\\\033[%d C", cur_font->lbpname,
306                 (int)((env->size * font::res) / 72));
307     switch (symbol_set) {
308     case 0:
309       lbpputs("\033('$2\033)' 1");      // Select IBML and IBMR1 symbol sets
310       break;
311     case 1:
312       lbpputs("\033(d\033)' 1");        // Select wp54 symbol set
313       break;
314     case 2:
315       lbpputs("\033('$2\033)'!0");      // Select IBMP symbol set
316       break;
317     case 3:
318       lbpprintf("\033PzSymbol.SYML\033\\\033[%d C",
319                 (int)((env->size * font::res) / 72));
320       lbpputs("\033(\"!!0\033)\"!!1");  // Select symbol font
321       break;
322     case 4:
323       lbpputs("\033)\"! 1\033(\"!$2");  // Select PS symbol set
324       break;
325     }
326     cur_symbol_set = symbol_set;
327   }
328   if (env->size != cur_size) {
329     if (!cur_font->is_scalable)
330       lbpprintf("\033Pz%s.IBML\033\\\n", font_name(cur_font, env->size));
331     else
332       lbpprintf("\033[%d C", (int)((env->size * font::res) / 72));
333     cur_size = env->size;
334      // Update the line thickness if needed
335     if (req_linethickness < 0 ) 
336         set_line_thickness(req_linethickness,env);
337   }
338   if ((env->hpos != cur_hpos) || (env->vpos != cur_vpos)) {
339     // lbpmoveabs(env->hpos - ((5 * 300) / 16), env->vpos);
340     lbpmoveabs(env->hpos - 64, env->vpos - 64);
341     cur_vpos = env->vpos;
342     cur_hpos = env->hpos;
343   }
344   if ((ch & 0x7F) < 32)
345     lbpputs("\033[1.v");
346   lbpputc(ch);
347   cur_hpos += w;
348 }
349
350 void lbp_printer::vdmstart()
351 {
352   FILE *f;
353   static int changed_origin = 0;
354   errno = 0;
355   f = tmpfile();
356   // f = fopen("/tmp/gtmp","w+");
357   if (f == NULL)
358     perror("Opening temporary file");
359   vdminit(f);
360   if (!changed_origin) {        // we should change the origin only one time
361     changed_origin = 1;
362     vdmorigin(-63, 0);
363   }
364   vdmlinewidth(line_thickness);
365 }
366
367 void
368 lbp_printer::vdmflush()
369 {
370   char buffer[1024];
371   int bytes_read = 1;
372   vdmend();
373   fflush(lbpoutput);
374   /* let's copy the vdm code to the output */
375   rewind(vdmoutput);
376   do {
377     bytes_read = fread(buffer, 1, sizeof(buffer), vdmoutput);
378     bytes_read = fwrite(buffer, 1, bytes_read, lbpoutput);
379   } while (bytes_read == sizeof(buffer));
380   fclose(vdmoutput);    // This will also delete the file,
381                         // since it is created by tmpfile()
382   vdmoutput = NULL;
383 }
384
385 inline void lbp_printer::setfillmode(int mode)
386 {
387   if (mode != fill_mode) {
388     if (mode != 1)
389       vdmsetfillmode(mode, 1, 0);
390     else
391       vdmsetfillmode(mode, 1, 1);       // To get black we must use white
392                                         // inverted
393       fill_mode = mode;
394   }
395 }
396
397 inline void lbp_printer::polygon(int hpos, int vpos, int np, int *p)
398 {
399   int *points, i;
400   points = new int[np + 2];
401   points[0] = hpos;
402   points[1] = vpos;
403   // fprintf(stderr, "Poligon (%d,%d) ", points[0], points[1]);
404   for (i = 0; i < np; i++)
405     points[i + 2] = p[i];
406   // for (i = 0; i < np; i++) fprintf(stderr, " %d ", p[i]);
407   // fprintf(stderr, "\n");
408   vdmpolygon((np /2) + 1, points);
409 }
410
411 void lbp_printer::draw(int code, int *p, int np, const environment *env)
412 {
413   if ((req_linethickness < 0 ) && (env->size != cur_size))
414                 set_line_thickness(req_linethickness,env);
415
416   switch (code) {
417   case 't':
418     if (np == 0)
419       line_thickness = 1;
420     else { // troff gratuitously adds an extra 0
421       if (np != 1 && np != 2) {
422         error("0 or 1 argument required for thickness");
423         break;
424       }
425     set_line_thickness(p[0],env);
426     }
427     break;
428   case 'l':     // Line
429     if (np != 2) {
430       error("2 arguments required for line");
431       break;
432     }
433     if (!vdminited())
434       vdmstart();
435     vdmline(env->hpos, env->vpos, p[0], p[1]);
436 /*     fprintf(stderr, "\nline: %d,%d - %d,%d thickness %d == %d\n",
437              env->hpos - 64,env->vpos -64, env->hpos - 64 + p[0],
438              env->vpos -64 + p[1], env->size, line_thickness);*/
439     break;
440   case 'R':     // Rule
441     if (np != 2) {
442       error("2 arguments required for Rule");
443       break;
444     }
445     if (vdminited()) {
446       setfillmode(fill_pattern); // Solid Rule
447       vdmrectangle(env->hpos, env->vpos, p[0], p[1]);
448     }
449     else {
450       lbpruleabs(env->hpos - 64, env->vpos -64, p[0], p[1]);
451       cur_vpos = p[1];
452       cur_hpos = p[0];
453     }
454     // fprintf(stderr, "\nrule: thickness %d == %d\n",
455     //         env->size, line_thickness);
456     break;
457   case 'P':     // Filled Polygon
458     if (!vdminited())
459       vdmstart();
460     setfillmode(fill_pattern);
461     polygon(env->hpos, env->vpos, np, p);
462     break;
463   case 'p':     // Empty Polygon
464     if (!vdminited())
465       vdmstart();
466     setfillmode(0);
467     polygon(env->hpos, env->vpos, np, p);
468     break;
469   case 'C':     // Filled Circle
470     if (!vdminited())
471       vdmstart();
472     // fprintf(stderr, "Circle (%d,%d) Fill %d\n",
473     //         env->hpos, env->vpos, fill_pattern);
474     setfillmode(fill_pattern);
475     vdmcircle(env->hpos + (p[0]/2), env->vpos, p[0]/2);
476     break;
477   case 'c':     // Empty Circle
478     if (!vdminited())
479       vdmstart();
480     setfillmode(0);
481     vdmcircle(env->hpos + (p[0]/2), env->vpos, p[0]/2);
482     break;
483   case 'E':     // Filled Ellipse
484     if (!vdminited())
485       vdmstart();
486     setfillmode(fill_pattern);
487     vdmellipse(env->hpos + (p[0]/2), env->vpos, p[0]/2, p[1]/2, 0);
488     break;
489   case 'e':      // Empty Ellipse
490     if (!vdminited())
491       vdmstart();
492     setfillmode(0);
493     vdmellipse(env->hpos + (p[0]/2), env->vpos, p[0]/2, p[1]/2, 0);
494     break;
495   case 'a':     // Arc
496     if (!vdminited())
497       vdmstart();
498     setfillmode(0);
499     // VDM draws arcs clockwise and pic counterclockwise
500     // We must compensate for that, exchanging the starting and
501     // ending points
502     vdmvarc(env->hpos + p[0], env->vpos+p[1],
503             int(sqrt(double((p[0]*p[0]) + (p[1]*p[1])))),
504             p[2], p[3],
505             (-p[0]), (-p[1]), 1, 2);
506     break;
507   case '~':     // Spline
508     if (!vdminited())
509       vdmstart();
510     setfillmode(0);
511     vdmspline(np/2, env->hpos, env->vpos, p);
512     break;
513   case 'f':
514     if (np != 1 && np != 2) {
515       error("1 argument required for fill");
516       break;
517     }
518     // fprintf(stderr, "Fill %d\n", p[0]);
519     if ((p[0] == 1) || (p[0] >= 1000)) { // Black
520       fill_pattern = 1;
521       break;
522     }
523     if (p[0] == 0) { // White
524       fill_pattern = 0;
525       break;
526     }
527     if ((p[0] > 1) && (p[0] < 1000))
528       {
529         if (p[0] >= 990)  fill_pattern = -23;
530         else if (p[0] >= 700)  fill_pattern = -28;
531         else if (p[0] >= 500)  fill_pattern = -27;
532         else if (p[0] >= 400)  fill_pattern = -26;
533         else if (p[0] >= 300)  fill_pattern = -25;
534         else if (p[0] >= 200)  fill_pattern = -22;
535         else if (p[0] >= 100)  fill_pattern = -24;
536         else fill_pattern = -21;
537       }
538     break;
539   case 'F':
540     // not implemented yet
541     break;
542   default:
543     error("unrecognised drawing command `%1'", char(code));
544     break;
545   }
546   return;
547 }
548
549 font *lbp_printer::make_font(const char *nm)
550 {
551   return lbp_font::load_lbp_font(nm);
552 }
553
554 printer *make_printer()
555 {
556   return new lbp_printer(user_papersize, user_paperwidth, user_paperlength);
557 }
558
559 static struct {
560   const char *name;
561   int code;
562 } lbp_papersizes[] =
563   {{ "A4", 14 },
564    { "letter", 30 },
565    { "legal", 32 },
566    { "executive", 40 },
567   };
568
569 static int set_papersize(const char *paperformat)
570 {
571   unsigned int i;
572   // First test for a standard (i.e. supported directly by the printer)
573   // paper size
574   for (i = 0 ; i < sizeof(lbp_papersizes) / sizeof(lbp_papersizes[0]); i++)
575   {
576     if (strcasecmp(lbp_papersizes[i].name,paperformat) == 0)
577       return lbp_papersizes[i].code;
578   }
579   // Otherwise, we assume a custom paper size
580   return 82;
581 }
582
583 static void handle_unknown_desc_command(const char *command, const char *arg,
584                                         const char *filename, int lineno)
585 {
586   // orientation command
587   if (strcasecmp(command, "orientation") == 0) {
588     // We give priority to command line options
589     if (orientation > 0)
590       return;
591     if (arg == 0)
592       error_with_file_and_line(filename, lineno,
593                                "`orientation' command requires an argument");
594     else {
595       if (strcasecmp(arg, "portrait") == 0)
596         orientation = 0;
597       else {
598         if (strcasecmp(arg, "landscape") == 0)
599           orientation = 1;
600         else
601           error_with_file_and_line(filename, lineno,
602                                    "invalid argument to `orientation' command");
603       }
604     }
605   }
606 }
607
608 static struct option long_options[] = {
609   { "orientation", required_argument, NULL, 'o' },
610   { "version", no_argument, NULL, 'v' },
611   { "copies", required_argument, NULL, 'c' },
612   { "landscape", no_argument, NULL, 'l' },
613   { "papersize", required_argument, NULL, 'p' },
614   { "linewidth", required_argument, NULL, 'w' },
615   { "fontdir", required_argument, NULL, 'F' },
616   { "help", no_argument, NULL, 'h' },
617   { NULL, 0, 0, 0 }
618 };
619
620 static void usage(FILE *stream)
621 {
622   fprintf(stream,
623           "usage: %s [-lvh] [-c n] [-p paper_size] [-F dir] [-o or]\n"
624           "       [-w width] [files ...]\n"
625           "\n"
626           "  -o --orientation=[portrait|landscape]\n"
627           "  -v --version\n"
628           "  -c --copies=numcopies\n"
629           "  -l --landscape\n"
630           "  -p --papersize=paper_size\n"
631           "  -w --linewidth=width\n"
632           "  -F --fontdir=dir\n"
633           "  -h --help\n",
634           program_name);
635 }
636
637 int main(int argc, char **argv)
638 {
639   if (program_name == NULL)
640     program_name = strsave(argv[0]);
641   font::set_unknown_desc_command_handler(handle_unknown_desc_command);
642   // command line parsing
643   int c = 0;
644   int option_index = 0;
645   while (c >= 0) {
646     c = getopt_long (argc, argv, "c:F:hI:lo:p:vw:",
647                      long_options, &option_index);
648     switch (c) {
649     case 'F':
650       font::command_line_font_dir(optarg);
651       break;
652     case 'I':
653       // ignore include path arguments
654       break;
655     case 'p':
656       {
657         const char *s;
658         if (!font::scan_papersize(optarg, &s,
659                                   &user_paperlength, &user_paperwidth))
660           error("invalid paper size `%1' ignored", optarg);
661         else
662           user_papersize = set_papersize(s);
663         break;
664       }
665     case 'l':
666       orientation = 1;
667       break;
668     case 'v':
669       printf("GNU grolbp (groff) version %s\n", Version_string);
670       exit(0);
671       break;
672     case 'o':
673       if (strcasecmp(optarg, "portrait") == 0)
674         orientation = 0;
675       else {
676         if (strcasecmp(optarg, "landscape") == 0)
677           orientation = 1;
678         else
679           error("unknown orientation '%1'", optarg);
680       }
681       break;
682     case 'c':
683       {
684         char *ptr;
685         long n = strtol(optarg, &ptr, 10);
686         if ((n <= 0) && (ptr == optarg))
687           error("argument for -c must be a positive integer");
688         else if (n <= 0 || n > 32767)
689           error("out of range argument for -c");
690         else
691           ncopies = unsigned(n);
692         break;
693       }
694     case 'w':
695       {
696         char *ptr;
697         long n = strtol(optarg, &ptr, 10);
698         if (n == 0 && ptr == optarg)
699           error("argument for -w must be a non-negative integer");
700         else if (n < 0 || n > INT_MAX)
701           error("out of range argument for -w");
702         else
703           linewidth_factor = int(n);
704         break;
705       }
706     case 'h':
707       usage(stdout);
708       exit(0);
709       break;
710     case '?':
711       usage(stderr);
712       exit(1);
713       break;
714     }
715   }
716   if (optind >= argc)
717     do_file("-");
718   while (optind < argc)
719     do_file(argv[optind++]);
720   if (lbpoutput)
721     lbpputs("\033c\033<");
722   return 0;
723 }