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