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