ttdriver.c (22120B)
1 /**************************************************************************** 2 * 3 * ttdriver.c 4 * 5 * TrueType font driver implementation (body). 6 * 7 * Copyright (C) 1996-2025 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 <freetype/internal/ftdebug.h> 20 #include <freetype/internal/ftstream.h> 21 #include <freetype/internal/sfnt.h> 22 #include <freetype/internal/services/svfntfmt.h> 23 24 #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT 25 #include <freetype/ftmm.h> 26 #include <freetype/internal/services/svmm.h> 27 #include <freetype/internal/services/svmetric.h> 28 #endif 29 30 #include <freetype/internal/services/svtteng.h> 31 #include <freetype/internal/services/svttglyf.h> 32 #include <freetype/internal/services/svprop.h> 33 #include <freetype/ftdriver.h> 34 35 #include "ttdriver.h" 36 #include "ttgload.h" 37 #include "ttpload.h" 38 39 #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT 40 #include "ttgxvar.h" 41 #endif 42 43 #include "tterrors.h" 44 45 46 /************************************************************************** 47 * 48 * The macro FT_COMPONENT is used in trace mode. It is an implicit 49 * parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log 50 * messages during execution. 51 */ 52 #undef FT_COMPONENT 53 #define FT_COMPONENT ttdriver 54 55 56 /* 57 * PROPERTY SERVICE 58 * 59 */ 60 FT_CALLBACK_DEF( FT_Error ) 61 tt_property_set( FT_Module module, /* TT_Driver */ 62 const char* property_name, 63 const void* value, 64 FT_Bool value_is_string ) 65 { 66 FT_Error error = FT_Err_Ok; 67 TT_Driver driver = (TT_Driver)module; 68 69 #ifndef FT_CONFIG_OPTION_ENVIRONMENT_PROPERTIES 70 FT_UNUSED( value_is_string ); 71 #endif 72 73 74 if ( !ft_strcmp( property_name, "interpreter-version" ) ) 75 { 76 FT_UInt interpreter_version; 77 78 79 #ifdef FT_CONFIG_OPTION_ENVIRONMENT_PROPERTIES 80 if ( value_is_string ) 81 { 82 const char* s = (const char*)value; 83 84 85 interpreter_version = (FT_UInt)ft_strtol( s, NULL, 10 ); 86 } 87 else 88 #endif 89 { 90 FT_UInt* iv = (FT_UInt*)value; 91 92 93 interpreter_version = *iv; 94 } 95 96 switch ( interpreter_version ) 97 { 98 case TT_INTERPRETER_VERSION_35: 99 driver->interpreter_version = TT_INTERPRETER_VERSION_35; 100 break; 101 102 case TT_INTERPRETER_VERSION_38: 103 case TT_INTERPRETER_VERSION_40: 104 #ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL 105 driver->interpreter_version = TT_INTERPRETER_VERSION_40; 106 break; 107 #endif 108 109 default: 110 error = FT_ERR( Unimplemented_Feature ); 111 } 112 113 return error; 114 } 115 116 FT_TRACE2(( "tt_property_set: missing property `%s'\n", 117 property_name )); 118 return FT_THROW( Missing_Property ); 119 } 120 121 122 FT_CALLBACK_DEF( FT_Error ) 123 tt_property_get( FT_Module module, /* TT_Driver */ 124 const char* property_name, 125 void* value ) 126 { 127 FT_Error error = FT_Err_Ok; 128 TT_Driver driver = (TT_Driver)module; 129 130 FT_UInt interpreter_version = driver->interpreter_version; 131 132 133 if ( !ft_strcmp( property_name, "interpreter-version" ) ) 134 { 135 FT_UInt* val = (FT_UInt*)value; 136 137 138 *val = interpreter_version; 139 140 return error; 141 } 142 143 FT_TRACE2(( "tt_property_get: missing property `%s'\n", 144 property_name )); 145 return FT_THROW( Missing_Property ); 146 } 147 148 149 FT_DEFINE_SERVICE_PROPERTIESREC( 150 tt_service_properties, 151 152 tt_property_set, /* FT_Properties_SetFunc set_property */ 153 tt_property_get /* FT_Properties_GetFunc get_property */ 154 ) 155 156 157 /*************************************************************************/ 158 /*************************************************************************/ 159 /*************************************************************************/ 160 /**** ****/ 161 /**** ****/ 162 /**** F A C E S ****/ 163 /**** ****/ 164 /**** ****/ 165 /*************************************************************************/ 166 /*************************************************************************/ 167 /*************************************************************************/ 168 169 170 /************************************************************************** 171 * 172 * @Function: 173 * tt_get_kerning 174 * 175 * @Description: 176 * A driver method used to return the kerning vector between two 177 * glyphs of the same face. 178 * 179 * @Input: 180 * face :: 181 * A handle to the source face object. 182 * 183 * left_glyph :: 184 * The index of the left glyph in the kern pair. 185 * 186 * right_glyph :: 187 * The index of the right glyph in the kern pair. 188 * 189 * @Output: 190 * kerning :: 191 * The kerning vector. This is in font units for 192 * scalable formats, and in pixels for fixed-sizes 193 * formats. 194 * 195 * @Return: 196 * FreeType error code. 0 means success. 197 * 198 * @Note: 199 * Only horizontal layouts (left-to-right & right-to-left) are 200 * supported by this function. Other layouts, or more sophisticated 201 * kernings, are out of scope of this method (the basic driver 202 * interface is meant to be simple). 203 * 204 * They can be implemented by format-specific interfaces. 205 */ 206 FT_CALLBACK_DEF( FT_Error ) 207 tt_get_kerning( FT_Face face, /* TT_Face */ 208 FT_UInt left_glyph, 209 FT_UInt right_glyph, 210 FT_Vector* kerning ) 211 { 212 TT_Face ttface = (TT_Face)face; 213 SFNT_Service sfnt = (SFNT_Service)ttface->sfnt; 214 215 216 kerning->x = 0; 217 kerning->y = 0; 218 219 if ( sfnt ) 220 { 221 /* Use 'kern' table if available since that can be faster; otherwise */ 222 /* use GPOS kerning pairs if available. */ 223 if ( ttface->kern_avail_bits ) 224 kerning->x = sfnt->get_kerning( ttface, 225 left_glyph, 226 right_glyph ); 227 #ifdef TT_CONFIG_OPTION_GPOS_KERNING 228 else if ( ttface->num_gpos_lookups_kerning ) 229 kerning->x = sfnt->get_gpos_kerning( ttface, 230 left_glyph, 231 right_glyph ); 232 #endif 233 } 234 235 return 0; 236 } 237 238 239 FT_CALLBACK_DEF( FT_Error ) 240 tt_get_advances( FT_Face face, /* TT_Face */ 241 FT_UInt start, 242 FT_UInt count, 243 FT_Int32 flags, 244 FT_Fixed *advances ) 245 { 246 FT_UInt nn; 247 TT_Face ttface = (TT_Face)face; 248 249 250 /* XXX: TODO: check for sbits */ 251 252 if ( flags & FT_LOAD_VERTICAL_LAYOUT ) 253 { 254 #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT 255 /* no fast retrieval for blended MM fonts without VVAR table */ 256 if ( ( FT_IS_NAMED_INSTANCE( face ) || FT_IS_VARIATION( face ) ) && 257 !( ttface->variation_support & TT_FACE_FLAG_VAR_VADVANCE ) ) 258 return FT_THROW( Unimplemented_Feature ); 259 #endif 260 261 for ( nn = 0; nn < count; nn++ ) 262 { 263 FT_Short tsb; 264 FT_UShort ah; 265 266 267 /* since we don't need `tsb', we use zero for `yMax' parameter */ 268 TT_Get_VMetrics( ttface, start + nn, 0, &tsb, &ah ); 269 advances[nn] = ah; 270 } 271 } 272 else 273 { 274 #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT 275 /* no fast retrieval for blended MM fonts without HVAR table */ 276 if ( ( FT_IS_NAMED_INSTANCE( face ) || FT_IS_VARIATION( face ) ) && 277 !( ttface->variation_support & TT_FACE_FLAG_VAR_HADVANCE ) ) 278 return FT_THROW( Unimplemented_Feature ); 279 #endif 280 281 for ( nn = 0; nn < count; nn++ ) 282 { 283 FT_Short lsb; 284 FT_UShort aw; 285 286 287 TT_Get_HMetrics( ttface, start + nn, &lsb, &aw ); 288 advances[nn] = aw; 289 } 290 } 291 292 return FT_Err_Ok; 293 } 294 295 296 /*************************************************************************/ 297 /*************************************************************************/ 298 /*************************************************************************/ 299 /**** ****/ 300 /**** ****/ 301 /**** S I Z E S ****/ 302 /**** ****/ 303 /**** ****/ 304 /*************************************************************************/ 305 /*************************************************************************/ 306 /*************************************************************************/ 307 308 309 #ifdef TT_CONFIG_OPTION_EMBEDDED_BITMAPS 310 311 FT_CALLBACK_DEF( FT_Error ) 312 tt_size_select( FT_Size size, 313 FT_ULong strike_index ) 314 { 315 TT_Face ttface = (TT_Face)size->face; 316 TT_Size ttsize = (TT_Size)size; 317 FT_Error error = FT_Err_Ok; 318 319 320 ttsize->strike_index = strike_index; 321 322 if ( FT_IS_SCALABLE( size->face ) ) 323 { 324 /* use the scaled metrics, even when tt_size_reset fails */ 325 FT_Select_Metrics( size->face, strike_index ); 326 327 tt_size_reset( ttsize ); /* ignore return value */ 328 } 329 else 330 { 331 SFNT_Service sfnt = (SFNT_Service)ttface->sfnt; 332 FT_Size_Metrics* size_metrics = &size->metrics; 333 334 335 error = sfnt->load_strike_metrics( ttface, 336 strike_index, 337 size_metrics ); 338 if ( error ) 339 ttsize->strike_index = 0xFFFFFFFFUL; 340 } 341 342 return error; 343 } 344 345 #endif /* TT_CONFIG_OPTION_EMBEDDED_BITMAPS */ 346 347 348 FT_CALLBACK_DEF( FT_Error ) 349 tt_size_request( FT_Size size, 350 FT_Size_Request req ) 351 { 352 TT_Size ttsize = (TT_Size)size; 353 FT_Error error = FT_Err_Ok; 354 355 356 #ifdef TT_CONFIG_OPTION_EMBEDDED_BITMAPS 357 358 if ( FT_HAS_FIXED_SIZES( size->face ) ) 359 { 360 TT_Face ttface = (TT_Face)size->face; 361 SFNT_Service sfnt = (SFNT_Service)ttface->sfnt; 362 FT_ULong strike_index; 363 364 365 error = sfnt->set_sbit_strike( ttface, req, &strike_index ); 366 367 if ( error ) 368 ttsize->strike_index = 0xFFFFFFFFUL; 369 else 370 return tt_size_select( size, strike_index ); 371 } 372 373 #endif /* TT_CONFIG_OPTION_EMBEDDED_BITMAPS */ 374 375 { 376 FT_Error err = FT_Request_Metrics( size->face, req ); 377 378 379 if ( err ) 380 { 381 error = err; 382 goto Exit; 383 } 384 } 385 386 if ( FT_IS_SCALABLE( size->face ) ) 387 { 388 error = tt_size_reset( ttsize ); 389 390 #ifdef TT_USE_BYTECODE_INTERPRETER 391 /* for the `MPS' bytecode instruction we need the point size */ 392 if ( !error ) 393 { 394 FT_UInt resolution = 395 ttsize->metrics->x_ppem > ttsize->metrics->y_ppem 396 ? req->horiResolution 397 : req->vertResolution; 398 399 400 /* if we don't have a resolution value, assume 72dpi */ 401 if ( req->type == FT_SIZE_REQUEST_TYPE_SCALES || 402 !resolution ) 403 resolution = 72; 404 405 ttsize->point_size = FT_MulDiv( ttsize->ttmetrics.ppem, 406 64 * 72, 407 resolution ); 408 } 409 #endif 410 } 411 412 Exit: 413 return error; 414 } 415 416 417 /************************************************************************** 418 * 419 * @Function: 420 * tt_glyph_load 421 * 422 * @Description: 423 * A driver method used to load a glyph within a given glyph slot. 424 * 425 * @Input: 426 * slot :: 427 * A handle to the target slot object where the glyph 428 * will be loaded. 429 * 430 * size :: 431 * A handle to the source face size at which the glyph 432 * must be scaled, loaded, etc. 433 * 434 * glyph_index :: 435 * The index of the glyph in the font file. 436 * 437 * load_flags :: 438 * A flag indicating what to load for this glyph. The 439 * FT_LOAD_XXX constants can be used to control the 440 * glyph loading process (e.g., whether the outline 441 * should be scaled, whether to load bitmaps or not, 442 * whether to hint the outline, etc). 443 * 444 * @Return: 445 * FreeType error code. 0 means success. 446 */ 447 FT_CALLBACK_DEF( FT_Error ) 448 tt_glyph_load( FT_GlyphSlot slot, /* TT_GlyphSlot */ 449 FT_Size size, /* TT_Size */ 450 FT_UInt glyph_index, 451 FT_Int32 load_flags ) 452 { 453 TT_GlyphSlot ttslot = (TT_GlyphSlot)slot; 454 TT_Size ttsize = (TT_Size)size; 455 FT_Face face = ttslot->face; 456 FT_Error error; 457 458 459 if ( !slot ) 460 return FT_THROW( Invalid_Slot_Handle ); 461 462 if ( !size ) 463 return FT_THROW( Invalid_Size_Handle ); 464 465 if ( !face ) 466 return FT_THROW( Invalid_Face_Handle ); 467 468 #ifdef FT_CONFIG_OPTION_INCREMENTAL 469 if ( glyph_index >= (FT_UInt)face->num_glyphs && 470 !face->internal->incremental_interface ) 471 #else 472 if ( glyph_index >= (FT_UInt)face->num_glyphs ) 473 #endif 474 return FT_THROW( Invalid_Argument ); 475 476 if ( load_flags & FT_LOAD_NO_HINTING ) 477 { 478 /* both FT_LOAD_NO_HINTING and FT_LOAD_NO_AUTOHINT */ 479 /* are necessary to disable hinting for tricky fonts */ 480 481 if ( FT_IS_TRICKY( face ) ) 482 load_flags &= ~FT_LOAD_NO_HINTING; 483 484 if ( load_flags & FT_LOAD_NO_AUTOHINT ) 485 load_flags |= FT_LOAD_NO_HINTING; 486 } 487 488 if ( load_flags & ( FT_LOAD_NO_RECURSE | FT_LOAD_NO_SCALE ) ) 489 { 490 load_flags |= FT_LOAD_NO_BITMAP | FT_LOAD_NO_SCALE; 491 492 if ( !FT_IS_TRICKY( face ) ) 493 load_flags |= FT_LOAD_NO_HINTING; 494 } 495 496 /* use hinted metrics only if we load a glyph with hinting */ 497 ttsize->metrics = ( load_flags & FT_LOAD_NO_HINTING ) 498 ? &size->metrics 499 : &ttsize->hinted_metrics; 500 501 /* now fill in the glyph slot with outline/bitmap/layered */ 502 error = TT_Load_Glyph( ttsize, ttslot, glyph_index, load_flags ); 503 504 /* force drop-out mode to 2 - irrelevant now */ 505 /* slot->outline.dropout_mode = 2; */ 506 507 return error; 508 } 509 510 511 /*************************************************************************/ 512 /*************************************************************************/ 513 /*************************************************************************/ 514 /**** ****/ 515 /**** ****/ 516 /**** D R I V E R I N T E R F A C E ****/ 517 /**** ****/ 518 /**** ****/ 519 /*************************************************************************/ 520 /*************************************************************************/ 521 /*************************************************************************/ 522 523 #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT 524 525 FT_DEFINE_SERVICE_MULTIMASTERSREC( 526 tt_service_gx_multi_masters, 527 528 NULL, /* FT_Get_MM_Func get_mm */ 529 NULL, /* FT_Set_MM_Design_Func set_mm_design */ 530 TT_Set_MM_Blend, /* FT_Set_MM_Blend_Func set_mm_blend */ 531 TT_Get_MM_Blend, /* FT_Get_MM_Blend_Func get_mm_blend */ 532 TT_Get_MM_Var, /* FT_Get_MM_Var_Func get_mm_var */ 533 TT_Set_Var_Design, /* FT_Set_Var_Design_Func set_var_design */ 534 TT_Get_Var_Design, /* FT_Get_Var_Design_Func get_var_design */ 535 TT_Set_Named_Instance, /* FT_Set_Named_Instance_Func set_named_instance */ 536 TT_Get_Default_Named_Instance, 537 /* FT_Get_Default_Named_Instance_Func get_default_named_instance */ 538 NULL, /* FT_Set_MM_WeightVector_Func set_mm_weightvector */ 539 NULL, /* FT_Get_MM_WeightVector_Func get_mm_weightvector */ 540 541 tt_construct_ps_name, /* FT_Construct_PS_Name_Func construct_ps_name */ 542 tt_var_load_delta_set_index_mapping, 543 /* FT_Var_Load_Delta_Set_Idx_Map_Func load_delta_set_idx_map */ 544 tt_var_load_item_variation_store, 545 /* FT_Var_Load_Item_Var_Store_Func load_item_variation_store */ 546 tt_var_get_item_delta, /* FT_Var_Get_Item_Delta_Func get_item_delta */ 547 tt_var_done_item_variation_store, 548 /* FT_Var_Done_Item_Var_Store_Func done_item_variation_store */ 549 tt_var_done_delta_set_index_map, 550 /* FT_Var_Done_Delta_Set_Idx_Map_Func done_delta_set_index_map */ 551 tt_get_var_blend, /* FT_Get_Var_Blend_Func get_var_blend */ 552 tt_done_blend /* FT_Done_Blend_Func done_blend */ 553 ) 554 555 FT_DEFINE_SERVICE_METRICSVARIATIONSREC( 556 tt_service_metrics_variations, 557 558 tt_hadvance_adjust, /* FT_HAdvance_Adjust_Func hadvance_adjust */ 559 NULL, /* FT_LSB_Adjust_Func lsb_adjust */ 560 NULL, /* FT_RSB_Adjust_Func rsb_adjust */ 561 562 tt_vadvance_adjust, /* FT_VAdvance_Adjust_Func vadvance_adjust */ 563 NULL, /* FT_TSB_Adjust_Func tsb_adjust */ 564 NULL, /* FT_BSB_Adjust_Func bsb_adjust */ 565 NULL, /* FT_VOrg_Adjust_Func vorg_adjust */ 566 567 tt_apply_mvar, /* FT_Metrics_Adjust_Func metrics_adjust */ 568 tt_size_reset_height /* FT_Size_Reset_Func size_reset */ 569 ) 570 571 #endif /* TT_CONFIG_OPTION_GX_VAR_SUPPORT */ 572 573 574 static const FT_Service_TrueTypeEngineRec tt_service_truetype_engine = 575 { 576 #ifdef TT_USE_BYTECODE_INTERPRETER 577 578 FT_TRUETYPE_ENGINE_TYPE_PATENTED 579 580 #else /* !TT_USE_BYTECODE_INTERPRETER */ 581 582 FT_TRUETYPE_ENGINE_TYPE_NONE 583 584 #endif /* TT_USE_BYTECODE_INTERPRETER */ 585 }; 586 587 588 FT_DEFINE_SERVICE_TTGLYFREC( 589 tt_service_truetype_glyf, 590 591 (TT_Glyf_GetLocationFunc)tt_face_get_location /* get_location */ 592 ) 593 594 595 #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT 596 FT_DEFINE_SERVICEDESCREC6( 597 tt_services, 598 599 FT_SERVICE_ID_FONT_FORMAT, FT_FONT_FORMAT_TRUETYPE, 600 FT_SERVICE_ID_MULTI_MASTERS, &tt_service_gx_multi_masters, 601 FT_SERVICE_ID_METRICS_VARIATIONS, &tt_service_metrics_variations, 602 FT_SERVICE_ID_TRUETYPE_ENGINE, &tt_service_truetype_engine, 603 FT_SERVICE_ID_TT_GLYF, &tt_service_truetype_glyf, 604 FT_SERVICE_ID_PROPERTIES, &tt_service_properties ) 605 #else 606 FT_DEFINE_SERVICEDESCREC4( 607 tt_services, 608 609 FT_SERVICE_ID_FONT_FORMAT, FT_FONT_FORMAT_TRUETYPE, 610 FT_SERVICE_ID_TRUETYPE_ENGINE, &tt_service_truetype_engine, 611 FT_SERVICE_ID_TT_GLYF, &tt_service_truetype_glyf, 612 FT_SERVICE_ID_PROPERTIES, &tt_service_properties ) 613 #endif 614 615 616 FT_CALLBACK_DEF( FT_Module_Interface ) 617 tt_get_interface( FT_Module driver, /* TT_Driver */ 618 const char* tt_interface ) 619 { 620 FT_Library library; 621 FT_Module_Interface result; 622 FT_Module sfntd; 623 SFNT_Service sfnt; 624 625 626 result = ft_service_list_lookup( tt_services, tt_interface ); 627 if ( result ) 628 return result; 629 630 if ( !driver ) 631 return NULL; 632 library = driver->library; 633 if ( !library ) 634 return NULL; 635 636 /* only return the default interface from the SFNT module */ 637 sfntd = FT_Get_Module( library, "sfnt" ); 638 if ( sfntd ) 639 { 640 sfnt = (SFNT_Service)( sfntd->clazz->module_interface ); 641 if ( sfnt ) 642 return sfnt->get_interface( driver, tt_interface ); 643 } 644 645 return 0; 646 } 647 648 649 /* The FT_DriverInterface structure is defined in ftdriver.h. */ 650 651 #ifdef TT_USE_BYTECODE_INTERPRETER 652 #define TT_HINTER_FLAG FT_MODULE_DRIVER_HAS_HINTER 653 #else 654 #define TT_HINTER_FLAG 0 655 #endif 656 657 #ifdef TT_CONFIG_OPTION_EMBEDDED_BITMAPS 658 #define TT_SIZE_SELECT tt_size_select 659 #else 660 #define TT_SIZE_SELECT 0 661 #endif 662 663 FT_DEFINE_DRIVER( 664 tt_driver_class, 665 666 FT_MODULE_FONT_DRIVER | 667 FT_MODULE_DRIVER_SCALABLE | 668 TT_HINTER_FLAG, 669 670 sizeof ( TT_DriverRec ), 671 672 "truetype", /* driver name */ 673 0x10000L, /* driver version == 1.0 */ 674 0x20000L, /* driver requires FreeType 2.0 or above */ 675 676 NULL, /* module-specific interface */ 677 678 tt_driver_init, /* FT_Module_Constructor module_init */ 679 tt_driver_done, /* FT_Module_Destructor module_done */ 680 tt_get_interface, /* FT_Module_Requester get_interface */ 681 682 sizeof ( TT_FaceRec ), 683 sizeof ( TT_SizeRec ), 684 sizeof ( FT_GlyphSlotRec ), 685 686 tt_face_init, /* FT_Face_InitFunc init_face */ 687 tt_face_done, /* FT_Face_DoneFunc done_face */ 688 tt_size_init, /* FT_Size_InitFunc init_size */ 689 tt_size_done, /* FT_Size_DoneFunc done_size */ 690 tt_slot_init, /* FT_Slot_InitFunc init_slot */ 691 NULL, /* FT_Slot_DoneFunc done_slot */ 692 693 tt_glyph_load, /* FT_Slot_LoadFunc load_glyph */ 694 695 tt_get_kerning, /* FT_Face_GetKerningFunc get_kerning */ 696 NULL, /* FT_Face_AttachFunc attach_file */ 697 tt_get_advances, /* FT_Face_GetAdvancesFunc get_advances */ 698 699 tt_size_request, /* FT_Size_RequestFunc request_size */ 700 TT_SIZE_SELECT /* FT_Size_SelectFunc select_size */ 701 ) 702 703 704 /* END */