1 /**************************************************************************** 2 * 3 * pfrsbit.c 4 * 5 * FreeType PFR bitmap loader (body). 6 * 7 * Copyright (C) 2002-2023 by 8 * David Turner, Robert Wilhelm, and Werner Lemberg. 9 * 10 * This file is part of the FreeType project, and may only be used, 11 * modified, and distributed under the terms of the FreeType project 12 * license, LICENSE.TXT. By continuing to use, modify, or distribute 13 * this file you indicate that you have read the license and 14 * understand and accept it fully. 15 * 16 */ 17 18 19 #include "pfrsbit.h" 20 #include "pfrload.h" 21 #include <freetype/internal/ftdebug.h> 22 #include <freetype/internal/ftstream.h> 23 24 #include "pfrerror.h" 25 26 #undef FT_COMPONENT 27 #define FT_COMPONENT pfr 28 29 30 /*************************************************************************/ 31 /*************************************************************************/ 32 /***** *****/ 33 /***** PFR BIT WRITER *****/ 34 /***** *****/ 35 /*************************************************************************/ 36 /*************************************************************************/ 37 38 typedef struct PFR_BitWriter_ 39 { 40 FT_Byte* line; /* current line start */ 41 FT_Int pitch; /* line size in bytes */ 42 FT_UInt width; /* width in pixels/bits */ 43 FT_UInt rows; /* number of remaining rows to scan */ 44 FT_UInt total; /* total number of bits to draw */ 45 46 } PFR_BitWriterRec, *PFR_BitWriter; 47 48 49 static void pfr_bitwriter_init(PFR_BitWriter writer,FT_Bitmap * target,FT_Bool decreasing)50 pfr_bitwriter_init( PFR_BitWriter writer, 51 FT_Bitmap* target, 52 FT_Bool decreasing ) 53 { 54 writer->line = target->buffer; 55 writer->pitch = target->pitch; 56 writer->width = target->width; 57 writer->rows = target->rows; 58 writer->total = writer->width * writer->rows; 59 60 if ( !decreasing ) 61 { 62 writer->line += writer->pitch * (FT_Int)( target->rows - 1 ); 63 writer->pitch = -writer->pitch; 64 } 65 } 66 67 68 static void pfr_bitwriter_decode_bytes(PFR_BitWriter writer,FT_Byte * p,FT_Byte * limit)69 pfr_bitwriter_decode_bytes( PFR_BitWriter writer, 70 FT_Byte* p, 71 FT_Byte* limit ) 72 { 73 FT_UInt n, reload; 74 FT_UInt left = writer->width; 75 FT_Byte* cur = writer->line; 76 FT_UInt mask = 0x80; 77 FT_UInt val = 0; 78 FT_UInt c = 0; 79 80 81 n = (FT_UInt)( limit - p ) * 8; 82 if ( n > writer->total ) 83 n = writer->total; 84 85 reload = n & 7; 86 87 for ( ; n > 0; n-- ) 88 { 89 if ( ( n & 7 ) == reload ) 90 val = *p++; 91 92 if ( val & 0x80 ) 93 c |= mask; 94 95 val <<= 1; 96 mask >>= 1; 97 98 if ( --left <= 0 ) 99 { 100 cur[0] = (FT_Byte)c; 101 left = writer->width; 102 mask = 0x80; 103 104 writer->line += writer->pitch; 105 cur = writer->line; 106 c = 0; 107 } 108 else if ( mask == 0 ) 109 { 110 cur[0] = (FT_Byte)c; 111 mask = 0x80; 112 c = 0; 113 cur++; 114 } 115 } 116 117 if ( mask != 0x80 ) 118 cur[0] = (FT_Byte)c; 119 } 120 121 122 static void pfr_bitwriter_decode_rle1(PFR_BitWriter writer,FT_Byte * p,FT_Byte * limit)123 pfr_bitwriter_decode_rle1( PFR_BitWriter writer, 124 FT_Byte* p, 125 FT_Byte* limit ) 126 { 127 FT_Int phase, count, counts[2]; 128 FT_UInt n, reload; 129 FT_UInt left = writer->width; 130 FT_Byte* cur = writer->line; 131 FT_UInt mask = 0x80; 132 FT_UInt c = 0; 133 134 135 n = writer->total; 136 137 phase = 1; 138 counts[0] = 0; 139 counts[1] = 0; 140 count = 0; 141 reload = 1; 142 143 for ( ; n > 0; n-- ) 144 { 145 if ( reload ) 146 { 147 do 148 { 149 if ( phase ) 150 { 151 FT_Int v; 152 153 154 if ( p >= limit ) 155 break; 156 157 v = *p++; 158 counts[0] = v >> 4; 159 counts[1] = v & 15; 160 phase = 0; 161 count = counts[0]; 162 } 163 else 164 { 165 phase = 1; 166 count = counts[1]; 167 } 168 169 } while ( count == 0 ); 170 } 171 172 if ( phase ) 173 c |= mask; 174 175 mask >>= 1; 176 177 if ( --left <= 0 ) 178 { 179 cur[0] = (FT_Byte)c; 180 left = writer->width; 181 mask = 0x80; 182 183 writer->line += writer->pitch; 184 cur = writer->line; 185 c = 0; 186 } 187 else if ( mask == 0 ) 188 { 189 cur[0] = (FT_Byte)c; 190 mask = 0x80; 191 c = 0; 192 cur++; 193 } 194 195 reload = ( --count <= 0 ); 196 } 197 198 if ( mask != 0x80 ) 199 cur[0] = (FT_Byte) c; 200 } 201 202 203 static void pfr_bitwriter_decode_rle2(PFR_BitWriter writer,FT_Byte * p,FT_Byte * limit)204 pfr_bitwriter_decode_rle2( PFR_BitWriter writer, 205 FT_Byte* p, 206 FT_Byte* limit ) 207 { 208 FT_Int phase, count; 209 FT_UInt n, reload; 210 FT_UInt left = writer->width; 211 FT_Byte* cur = writer->line; 212 FT_UInt mask = 0x80; 213 FT_UInt c = 0; 214 215 216 n = writer->total; 217 218 phase = 1; 219 count = 0; 220 reload = 1; 221 222 for ( ; n > 0; n-- ) 223 { 224 if ( reload ) 225 { 226 do 227 { 228 if ( p >= limit ) 229 break; 230 231 count = *p++; 232 phase = phase ^ 1; 233 234 } while ( count == 0 ); 235 } 236 237 if ( phase ) 238 c |= mask; 239 240 mask >>= 1; 241 242 if ( --left <= 0 ) 243 { 244 cur[0] = (FT_Byte)c; 245 c = 0; 246 mask = 0x80; 247 left = writer->width; 248 249 writer->line += writer->pitch; 250 cur = writer->line; 251 } 252 else if ( mask == 0 ) 253 { 254 cur[0] = (FT_Byte)c; 255 c = 0; 256 mask = 0x80; 257 cur++; 258 } 259 260 reload = ( --count <= 0 ); 261 } 262 263 if ( mask != 0x80 ) 264 cur[0] = (FT_Byte) c; 265 } 266 267 268 /*************************************************************************/ 269 /*************************************************************************/ 270 /***** *****/ 271 /***** BITMAP DATA DECODING *****/ 272 /***** *****/ 273 /*************************************************************************/ 274 /*************************************************************************/ 275 276 static void pfr_lookup_bitmap_data(FT_Byte * base,FT_Byte * limit,FT_UInt count,FT_UInt * flags,FT_UInt char_code,FT_ULong * found_offset,FT_ULong * found_size)277 pfr_lookup_bitmap_data( FT_Byte* base, 278 FT_Byte* limit, 279 FT_UInt count, 280 FT_UInt* flags, 281 FT_UInt char_code, 282 FT_ULong* found_offset, 283 FT_ULong* found_size ) 284 { 285 FT_UInt min, max, mid, char_len; 286 FT_Bool two = FT_BOOL( *flags & PFR_BITMAP_2BYTE_CHARCODE ); 287 FT_Byte* buff; 288 289 290 char_len = 4; 291 if ( two ) 292 char_len += 1; 293 if ( *flags & PFR_BITMAP_2BYTE_SIZE ) 294 char_len += 1; 295 if ( *flags & PFR_BITMAP_3BYTE_OFFSET ) 296 char_len += 1; 297 298 if ( !( *flags & PFR_BITMAP_CHARCODES_VALIDATED ) ) 299 { 300 FT_Byte* p; 301 FT_Byte* lim; 302 FT_UInt code; 303 FT_Long prev_code; 304 305 306 *flags |= PFR_BITMAP_VALID_CHARCODES; 307 prev_code = -1; 308 lim = base + count * char_len; 309 310 if ( lim > limit ) 311 { 312 FT_TRACE0(( "pfr_lookup_bitmap_data:" 313 " number of bitmap records too large,\n" )); 314 FT_TRACE0(( " " 315 " thus ignoring all bitmaps in this strike\n" )); 316 *flags &= ~PFR_BITMAP_VALID_CHARCODES; 317 } 318 else 319 { 320 /* check whether records are sorted by code */ 321 for ( p = base; p < lim; p += char_len ) 322 { 323 if ( two ) 324 code = FT_PEEK_USHORT( p ); 325 else 326 code = *p; 327 328 if ( (FT_Long)code <= prev_code ) 329 { 330 FT_TRACE0(( "pfr_lookup_bitmap_data:" 331 " bitmap records are not sorted,\n" )); 332 FT_TRACE0(( " " 333 " thus ignoring all bitmaps in this strike\n" )); 334 *flags &= ~PFR_BITMAP_VALID_CHARCODES; 335 break; 336 } 337 338 prev_code = code; 339 } 340 } 341 342 *flags |= PFR_BITMAP_CHARCODES_VALIDATED; 343 } 344 345 /* ignore bitmaps in case table is not valid */ 346 /* (this might be sanitized, but PFR is dead...) */ 347 if ( !( *flags & PFR_BITMAP_VALID_CHARCODES ) ) 348 goto Fail; 349 350 min = 0; 351 max = count; 352 mid = min + ( max - min ) / 2; 353 354 /* binary search */ 355 while ( min < max ) 356 { 357 FT_UInt code; 358 359 360 buff = base + mid * char_len; 361 362 if ( two ) 363 code = PFR_NEXT_USHORT( buff ); 364 else 365 code = PFR_NEXT_BYTE( buff ); 366 367 if ( char_code < code ) 368 max = mid; 369 else if ( char_code > code ) 370 min = mid + 1; 371 else 372 goto Found_It; 373 374 /* reasonable prediction in a continuous block */ 375 mid += char_code - code; 376 if ( mid >= max || mid < min ) 377 mid = min + ( max - min ) / 2; 378 } 379 380 Fail: 381 /* Not found */ 382 *found_size = 0; 383 *found_offset = 0; 384 return; 385 386 Found_It: 387 if ( *flags & PFR_BITMAP_2BYTE_SIZE ) 388 *found_size = PFR_NEXT_USHORT( buff ); 389 else 390 *found_size = PFR_NEXT_BYTE( buff ); 391 392 if ( *flags & PFR_BITMAP_3BYTE_OFFSET ) 393 *found_offset = PFR_NEXT_ULONG( buff ); 394 else 395 *found_offset = PFR_NEXT_USHORT( buff ); 396 } 397 398 399 /* load bitmap metrics. `*aadvance' must be set to the default value */ 400 /* before calling this function */ 401 /* */ 402 static FT_Error pfr_load_bitmap_metrics(FT_Byte ** pdata,FT_Byte * limit,FT_Long scaled_advance,FT_Long * axpos,FT_Long * aypos,FT_UInt * axsize,FT_UInt * aysize,FT_Long * aadvance,FT_UInt * aformat)403 pfr_load_bitmap_metrics( FT_Byte** pdata, 404 FT_Byte* limit, 405 FT_Long scaled_advance, 406 FT_Long *axpos, 407 FT_Long *aypos, 408 FT_UInt *axsize, 409 FT_UInt *aysize, 410 FT_Long *aadvance, 411 FT_UInt *aformat ) 412 { 413 FT_Error error = FT_Err_Ok; 414 FT_Byte flags; 415 FT_Byte b; 416 FT_Byte* p = *pdata; 417 FT_Long xpos, ypos, advance; 418 FT_UInt xsize, ysize; 419 420 421 PFR_CHECK( 1 ); 422 flags = PFR_NEXT_BYTE( p ); 423 424 xpos = 0; 425 ypos = 0; 426 xsize = 0; 427 ysize = 0; 428 advance = 0; 429 430 switch ( flags & 3 ) 431 { 432 case 0: 433 PFR_CHECK( 1 ); 434 b = PFR_NEXT_BYTE( p ); 435 xpos = (FT_Char)b >> 4; 436 ypos = ( (FT_Char)( b << 4 ) ) >> 4; 437 break; 438 439 case 1: 440 PFR_CHECK( 2 ); 441 xpos = PFR_NEXT_INT8( p ); 442 ypos = PFR_NEXT_INT8( p ); 443 break; 444 445 case 2: 446 PFR_CHECK( 4 ); 447 xpos = PFR_NEXT_SHORT( p ); 448 ypos = PFR_NEXT_SHORT( p ); 449 break; 450 451 case 3: 452 PFR_CHECK( 6 ); 453 xpos = PFR_NEXT_LONG( p ); 454 ypos = PFR_NEXT_LONG( p ); 455 break; 456 457 default: 458 ; 459 } 460 461 flags >>= 2; 462 switch ( flags & 3 ) 463 { 464 case 0: 465 /* blank image */ 466 xsize = 0; 467 ysize = 0; 468 break; 469 470 case 1: 471 PFR_CHECK( 1 ); 472 b = PFR_NEXT_BYTE( p ); 473 xsize = ( b >> 4 ) & 0xF; 474 ysize = b & 0xF; 475 break; 476 477 case 2: 478 PFR_CHECK( 2 ); 479 xsize = PFR_NEXT_BYTE( p ); 480 ysize = PFR_NEXT_BYTE( p ); 481 break; 482 483 case 3: 484 PFR_CHECK( 4 ); 485 xsize = PFR_NEXT_USHORT( p ); 486 ysize = PFR_NEXT_USHORT( p ); 487 break; 488 489 default: 490 ; 491 } 492 493 flags >>= 2; 494 switch ( flags & 3 ) 495 { 496 case 0: 497 advance = scaled_advance; 498 break; 499 500 case 1: 501 PFR_CHECK( 1 ); 502 advance = PFR_NEXT_INT8( p ) * 256; 503 break; 504 505 case 2: 506 PFR_CHECK( 2 ); 507 advance = PFR_NEXT_SHORT( p ); 508 break; 509 510 case 3: 511 PFR_CHECK( 3 ); 512 advance = PFR_NEXT_LONG( p ); 513 break; 514 515 default: 516 ; 517 } 518 519 *axpos = xpos; 520 *aypos = ypos; 521 *axsize = xsize; 522 *aysize = ysize; 523 *aadvance = advance; 524 *aformat = flags >> 2; 525 *pdata = p; 526 527 Exit: 528 return error; 529 530 Too_Short: 531 error = FT_THROW( Invalid_Table ); 532 FT_ERROR(( "pfr_load_bitmap_metrics: invalid glyph data\n" )); 533 goto Exit; 534 } 535 536 537 static FT_Error pfr_load_bitmap_bits(FT_Byte * p,FT_Byte * limit,FT_UInt format,FT_Bool decreasing,FT_Bitmap * target)538 pfr_load_bitmap_bits( FT_Byte* p, 539 FT_Byte* limit, 540 FT_UInt format, 541 FT_Bool decreasing, 542 FT_Bitmap* target ) 543 { 544 FT_Error error = FT_Err_Ok; 545 PFR_BitWriterRec writer; 546 547 548 if ( target->rows > 0 && target->width > 0 ) 549 { 550 pfr_bitwriter_init( &writer, target, decreasing ); 551 552 switch ( format ) 553 { 554 case 0: /* packed bits */ 555 pfr_bitwriter_decode_bytes( &writer, p, limit ); 556 break; 557 558 case 1: /* RLE1 */ 559 pfr_bitwriter_decode_rle1( &writer, p, limit ); 560 break; 561 562 case 2: /* RLE2 */ 563 pfr_bitwriter_decode_rle2( &writer, p, limit ); 564 break; 565 566 default: 567 ; 568 } 569 } 570 571 return error; 572 } 573 574 575 /*************************************************************************/ 576 /*************************************************************************/ 577 /***** *****/ 578 /***** BITMAP LOADING *****/ 579 /***** *****/ 580 /*************************************************************************/ 581 /*************************************************************************/ 582 583 FT_LOCAL_DEF( FT_Error ) pfr_slot_load_bitmap(PFR_Slot glyph,PFR_Size size,FT_UInt glyph_index,FT_Bool metrics_only)584 pfr_slot_load_bitmap( PFR_Slot glyph, 585 PFR_Size size, 586 FT_UInt glyph_index, 587 FT_Bool metrics_only ) 588 { 589 FT_Error error; 590 PFR_Face face = (PFR_Face) glyph->root.face; 591 FT_Stream stream = face->root.stream; 592 PFR_PhyFont phys = &face->phy_font; 593 FT_ULong gps_offset; 594 FT_ULong gps_size; 595 PFR_Char character; 596 PFR_Strike strike; 597 598 599 character = &phys->chars[glyph_index]; 600 601 /* look up a bitmap strike corresponding to the current */ 602 /* character dimensions */ 603 { 604 FT_UInt n; 605 606 607 strike = phys->strikes; 608 for ( n = 0; n < phys->num_strikes; n++ ) 609 { 610 if ( strike->x_ppm == (FT_UInt)size->root.metrics.x_ppem && 611 strike->y_ppm == (FT_UInt)size->root.metrics.y_ppem ) 612 goto Found_Strike; 613 614 strike++; 615 } 616 617 /* couldn't find it */ 618 return FT_THROW( Invalid_Argument ); 619 } 620 621 Found_Strike: 622 623 /* now look up the glyph's position within the file */ 624 { 625 FT_UInt char_len; 626 627 628 char_len = 4; 629 if ( strike->flags & PFR_BITMAP_2BYTE_CHARCODE ) 630 char_len += 1; 631 if ( strike->flags & PFR_BITMAP_2BYTE_SIZE ) 632 char_len += 1; 633 if ( strike->flags & PFR_BITMAP_3BYTE_OFFSET ) 634 char_len += 1; 635 636 /* access data directly in the frame to speed up lookups */ 637 if ( FT_STREAM_SEEK( phys->bct_offset + strike->bct_offset ) || 638 FT_FRAME_ENTER( char_len * strike->num_bitmaps ) ) 639 goto Exit; 640 641 pfr_lookup_bitmap_data( stream->cursor, 642 stream->limit, 643 strike->num_bitmaps, 644 &strike->flags, 645 character->char_code, 646 &gps_offset, 647 &gps_size ); 648 649 FT_FRAME_EXIT(); 650 651 if ( gps_size == 0 ) 652 { 653 /* could not find a bitmap program string for this glyph */ 654 error = FT_THROW( Invalid_Argument ); 655 goto Exit; 656 } 657 } 658 659 /* get the bitmap metrics */ 660 { 661 FT_Long xpos = 0, ypos = 0, advance = 0; 662 FT_UInt xsize = 0, ysize = 0, format = 0; 663 FT_Byte* p; 664 665 666 /* compute linear advance */ 667 advance = character->advance; 668 if ( phys->metrics_resolution != phys->outline_resolution ) 669 advance = FT_MulDiv( advance, 670 (FT_Long)phys->outline_resolution, 671 (FT_Long)phys->metrics_resolution ); 672 673 glyph->root.linearHoriAdvance = advance; 674 675 /* compute default advance, i.e., scaled advance; this can be */ 676 /* overridden in the bitmap header of certain glyphs */ 677 advance = FT_MulDiv( (FT_Fixed)size->root.metrics.x_ppem << 8, 678 character->advance, 679 (FT_Long)phys->metrics_resolution ); 680 681 if ( FT_STREAM_SEEK( face->header.gps_section_offset + gps_offset ) || 682 FT_FRAME_ENTER( gps_size ) ) 683 goto Exit; 684 685 p = stream->cursor; 686 error = pfr_load_bitmap_metrics( &p, stream->limit, 687 advance, 688 &xpos, &ypos, 689 &xsize, &ysize, 690 &advance, &format ); 691 if ( error ) 692 goto Exit1; 693 694 /* 695 * Before allocating the target bitmap, we check whether the given 696 * bitmap dimensions are valid, depending on the image format. 697 * 698 * Format 0: We have a stream of pixels (with 8 pixels per byte). 699 * 700 * (xsize * ysize + 7) / 8 <= gps_size 701 * 702 * Format 1: Run-length encoding; the high nibble holds the number of 703 * white bits, the low nibble the number of black bits. In 704 * other words, a single byte can represent at most 15 705 * pixels. 706 * 707 * xsize * ysize <= 15 * gps_size 708 * 709 * Format 2: Run-length encoding; the high byte holds the number of 710 * white bits, the low byte the number of black bits. In 711 * other words, two bytes can represent at most 255 pixels. 712 * 713 * xsize * ysize <= 255 * (gps_size + 1) / 2 714 */ 715 switch ( format ) 716 { 717 case 0: 718 if ( ( (FT_ULong)xsize * ysize + 7 ) / 8 > gps_size ) 719 error = FT_THROW( Invalid_Table ); 720 break; 721 case 1: 722 if ( (FT_ULong)xsize * ysize > 15 * gps_size ) 723 error = FT_THROW( Invalid_Table ); 724 break; 725 case 2: 726 if ( (FT_ULong)xsize * ysize > 255 * ( ( gps_size + 1 ) / 2 ) ) 727 error = FT_THROW( Invalid_Table ); 728 break; 729 default: 730 FT_ERROR(( "pfr_slot_load_bitmap: invalid image type\n" )); 731 error = FT_THROW( Invalid_Table ); 732 } 733 734 if ( error ) 735 { 736 if ( FT_ERR_EQ( error, Invalid_Table ) ) 737 FT_ERROR(( "pfr_slot_load_bitmap: invalid bitmap dimensions\n" )); 738 goto Exit1; 739 } 740 741 /* 742 * XXX: on 16bit systems we return an error for huge bitmaps 743 * that cause size truncation, because truncated 744 * size properties make bitmap glyphs broken. 745 */ 746 if ( xpos > FT_INT_MAX || 747 xpos < FT_INT_MIN || 748 ysize > FT_INT_MAX || 749 ypos > FT_INT_MAX - (FT_Long)ysize || 750 ypos + (FT_Long)ysize < FT_INT_MIN ) 751 { 752 FT_TRACE1(( "pfr_slot_load_bitmap:" 753 " huge bitmap glyph %ldx%ld over FT_GlyphSlot\n", 754 xpos, ypos )); 755 error = FT_THROW( Invalid_Pixel_Size ); 756 } 757 758 if ( !error ) 759 { 760 glyph->root.format = FT_GLYPH_FORMAT_BITMAP; 761 762 /* Set up glyph bitmap and metrics */ 763 764 /* XXX: needs casts to fit FT_Bitmap.{width|rows|pitch} */ 765 glyph->root.bitmap.width = xsize; 766 glyph->root.bitmap.rows = ysize; 767 glyph->root.bitmap.pitch = (FT_Int)( xsize + 7 ) >> 3; 768 glyph->root.bitmap.pixel_mode = FT_PIXEL_MODE_MONO; 769 770 /* XXX: needs casts to fit FT_Glyph_Metrics.{width|height} */ 771 glyph->root.metrics.width = (FT_Pos)xsize << 6; 772 glyph->root.metrics.height = (FT_Pos)ysize << 6; 773 glyph->root.metrics.horiBearingX = xpos * 64; 774 glyph->root.metrics.horiBearingY = ypos * 64; 775 glyph->root.metrics.horiAdvance = FT_PIX_ROUND( ( advance >> 2 ) ); 776 glyph->root.metrics.vertBearingX = - glyph->root.metrics.width >> 1; 777 glyph->root.metrics.vertBearingY = 0; 778 glyph->root.metrics.vertAdvance = size->root.metrics.height; 779 780 /* XXX: needs casts fit FT_GlyphSlotRec.bitmap_{left|top} */ 781 glyph->root.bitmap_left = (FT_Int)xpos; 782 glyph->root.bitmap_top = (FT_Int)( ypos + (FT_Long)ysize ); 783 784 if ( metrics_only ) 785 goto Exit1; 786 787 /* Allocate and read bitmap data */ 788 { 789 FT_ULong len = (FT_ULong)glyph->root.bitmap.pitch * ysize; 790 791 792 error = ft_glyphslot_alloc_bitmap( &glyph->root, len ); 793 if ( !error ) 794 error = pfr_load_bitmap_bits( 795 p, 796 stream->limit, 797 format, 798 FT_BOOL( face->header.color_flags & 799 PFR_FLAG_INVERT_BITMAP ), 800 &glyph->root.bitmap ); 801 } 802 } 803 804 Exit1: 805 FT_FRAME_EXIT(); 806 } 807 808 Exit: 809 return error; 810 } 811 812 813 /* END */ 814