1 /**************************************************************************** 2 * 3 * ttgxvar.c 4 * 5 * TrueType GX Font Variation loader 6 * 7 * Copyright (C) 2004-2020 by 8 * David Turner, Robert Wilhelm, Werner Lemberg, and George Williams. 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 /************************************************************************** 20 * 21 * Apple documents the `fvar', `gvar', `cvar', and `avar' tables at 22 * 23 * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6[fgca]var.html 24 * 25 * The documentation for `gvar' is not intelligible; `cvar' refers you 26 * to `gvar' and is thus also incomprehensible. 27 * 28 * The documentation for `avar' appears correct, but Apple has no fonts 29 * with an `avar' table, so it is hard to test. 30 * 31 * Many thanks to John Jenkins (at Apple) in figuring this out. 32 * 33 * 34 * Apple's `kern' table has some references to tuple indices, but as 35 * there is no indication where these indices are defined, nor how to 36 * interpolate the kerning values (different tuples have different 37 * classes) this issue is ignored. 38 * 39 */ 40 41 42 #include <ft2build.h> 43 #include <freetype/internal/ftdebug.h> 44 #include FT_CONFIG_CONFIG_H 45 #include <freetype/internal/ftstream.h> 46 #include <freetype/internal/sfnt.h> 47 #include <freetype/tttags.h> 48 #include <freetype/ttnameid.h> 49 #include <freetype/ftmm.h> 50 #include <freetype/ftlist.h> 51 52 #include "ttpload.h" 53 #include "ttgxvar.h" 54 55 #include "tterrors.h" 56 57 58 #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT 59 60 61 #define FT_Stream_FTell( stream ) \ 62 (FT_ULong)( (stream)->cursor - (stream)->base ) 63 #define FT_Stream_SeekSet( stream, off ) \ 64 (stream)->cursor = \ 65 ( (off) < (FT_ULong)( (stream)->limit - (stream)->base ) ) \ 66 ? (stream)->base + (off) \ 67 : (stream)->limit 68 69 70 /* some macros we need */ 71 #define FT_fdot14ToFixed( x ) \ 72 ( (FT_Fixed)( (FT_ULong)(x) << 2 ) ) 73 #define FT_intToFixed( i ) \ 74 ( (FT_Fixed)( (FT_ULong)(i) << 16 ) ) 75 #define FT_fdot6ToFixed( i ) \ 76 ( (FT_Fixed)( (FT_ULong)(i) << 10 ) ) 77 #define FT_fixedToInt( x ) \ 78 ( (FT_Short)( ( (x) + 0x8000U ) >> 16 ) ) 79 #define FT_fixedToFdot6( x ) \ 80 ( (FT_Pos)( ( (x) + 0x200 ) >> 10 ) ) 81 82 83 /************************************************************************** 84 * 85 * The macro FT_COMPONENT is used in trace mode. It is an implicit 86 * parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log 87 * messages during execution. 88 */ 89 #undef FT_COMPONENT 90 #define FT_COMPONENT ttgxvar 91 92 93 /*************************************************************************/ 94 /*************************************************************************/ 95 /***** *****/ 96 /***** Internal Routines *****/ 97 /***** *****/ 98 /*************************************************************************/ 99 /*************************************************************************/ 100 101 102 /************************************************************************** 103 * 104 * The macro ALL_POINTS is used in `ft_var_readpackedpoints'. It 105 * indicates that there is a delta for every point without needing to 106 * enumerate all of them. 107 */ 108 109 /* ensure that value `0' has the same width as a pointer */ 110 #define ALL_POINTS (FT_UShort*)~(FT_PtrDist)0 111 112 113 #define GX_PT_POINTS_ARE_WORDS 0x80U 114 #define GX_PT_POINT_RUN_COUNT_MASK 0x7FU 115 116 117 /************************************************************************** 118 * 119 * @Function: 120 * ft_var_readpackedpoints 121 * 122 * @Description: 123 * Read a set of points to which the following deltas will apply. 124 * Points are packed with a run length encoding. 125 * 126 * @Input: 127 * stream :: 128 * The data stream. 129 * 130 * size :: 131 * The size of the table holding the data. 132 * 133 * @Output: 134 * point_cnt :: 135 * The number of points read. A zero value means that 136 * all points in the glyph will be affected, without 137 * enumerating them individually. 138 * 139 * @Return: 140 * An array of FT_UShort containing the affected points or the 141 * special value ALL_POINTS. 142 */ 143 static FT_UShort* ft_var_readpackedpoints(FT_Stream stream,FT_ULong size,FT_UInt * point_cnt)144 ft_var_readpackedpoints( FT_Stream stream, 145 FT_ULong size, 146 FT_UInt *point_cnt ) 147 { 148 FT_UShort *points = NULL; 149 FT_UInt n; 150 FT_UInt runcnt; 151 FT_UInt i, j; 152 FT_UShort first; 153 FT_Memory memory = stream->memory; 154 FT_Error error = FT_Err_Ok; 155 156 FT_UNUSED( error ); 157 158 159 *point_cnt = 0; 160 161 n = FT_GET_BYTE(); 162 if ( n == 0 ) 163 return ALL_POINTS; 164 165 if ( n & GX_PT_POINTS_ARE_WORDS ) 166 { 167 n &= GX_PT_POINT_RUN_COUNT_MASK; 168 n <<= 8; 169 n |= FT_GET_BYTE(); 170 } 171 172 if ( n > size ) 173 { 174 FT_TRACE1(( "ft_var_readpackedpoints: number of points too large\n" )); 175 return NULL; 176 } 177 178 /* in the nested loops below we increase `i' twice; */ 179 /* it is faster to simply allocate one more slot */ 180 /* than to add another test within the loop */ 181 if ( FT_NEW_ARRAY( points, n + 1 ) ) 182 return NULL; 183 184 *point_cnt = n; 185 186 first = 0; 187 i = 0; 188 while ( i < n ) 189 { 190 runcnt = FT_GET_BYTE(); 191 if ( runcnt & GX_PT_POINTS_ARE_WORDS ) 192 { 193 runcnt &= GX_PT_POINT_RUN_COUNT_MASK; 194 first += FT_GET_USHORT(); 195 points[i++] = first; 196 197 /* first point not included in run count */ 198 for ( j = 0; j < runcnt; j++ ) 199 { 200 first += FT_GET_USHORT(); 201 points[i++] = first; 202 if ( i >= n ) 203 break; 204 } 205 } 206 else 207 { 208 first += FT_GET_BYTE(); 209 points[i++] = first; 210 211 for ( j = 0; j < runcnt; j++ ) 212 { 213 first += FT_GET_BYTE(); 214 points[i++] = first; 215 if ( i >= n ) 216 break; 217 } 218 } 219 } 220 221 return points; 222 } 223 224 225 #define GX_DT_DELTAS_ARE_ZERO 0x80U 226 #define GX_DT_DELTAS_ARE_WORDS 0x40U 227 #define GX_DT_DELTA_RUN_COUNT_MASK 0x3FU 228 229 230 /************************************************************************** 231 * 232 * @Function: 233 * ft_var_readpackeddeltas 234 * 235 * @Description: 236 * Read a set of deltas. These are packed slightly differently than 237 * points. In particular there is no overall count. 238 * 239 * @Input: 240 * stream :: 241 * The data stream. 242 * 243 * size :: 244 * The size of the table holding the data. 245 * 246 * delta_cnt :: 247 * The number of deltas to be read. 248 * 249 * @Return: 250 * An array of FT_Fixed containing the deltas for the affected 251 * points. (This only gets the deltas for one dimension. It will 252 * generally be called twice, once for x, once for y. When used in 253 * cvt table, it will only be called once.) 254 * 255 * We use FT_Fixed to avoid accumulation errors while summing up all 256 * deltas (the rounding to integer values happens as the very last 257 * step). 258 */ 259 static FT_Fixed* ft_var_readpackeddeltas(FT_Stream stream,FT_ULong size,FT_UInt delta_cnt)260 ft_var_readpackeddeltas( FT_Stream stream, 261 FT_ULong size, 262 FT_UInt delta_cnt ) 263 { 264 FT_Fixed *deltas = NULL; 265 FT_UInt runcnt, cnt; 266 FT_UInt i, j; 267 FT_Memory memory = stream->memory; 268 FT_Error error = FT_Err_Ok; 269 270 FT_UNUSED( error ); 271 272 273 if ( delta_cnt > size ) 274 { 275 FT_TRACE1(( "ft_var_readpackeddeltas: number of points too large\n" )); 276 return NULL; 277 } 278 279 if ( FT_NEW_ARRAY( deltas, delta_cnt ) ) 280 return NULL; 281 282 i = 0; 283 while ( i < delta_cnt ) 284 { 285 runcnt = FT_GET_BYTE(); 286 cnt = runcnt & GX_DT_DELTA_RUN_COUNT_MASK; 287 288 if ( runcnt & GX_DT_DELTAS_ARE_ZERO ) 289 { 290 /* `runcnt' zeroes get added */ 291 for ( j = 0; j <= cnt && i < delta_cnt; j++ ) 292 deltas[i++] = 0; 293 } 294 else if ( runcnt & GX_DT_DELTAS_ARE_WORDS ) 295 { 296 /* `runcnt' shorts from the stack */ 297 for ( j = 0; j <= cnt && i < delta_cnt; j++ ) 298 deltas[i++] = FT_intToFixed( FT_GET_SHORT() ); 299 } 300 else 301 { 302 /* `runcnt' signed bytes from the stack */ 303 for ( j = 0; j <= cnt && i < delta_cnt; j++ ) 304 deltas[i++] = FT_intToFixed( FT_GET_CHAR() ); 305 } 306 307 if ( j <= cnt ) 308 { 309 /* bad format */ 310 FT_FREE( deltas ); 311 return NULL; 312 } 313 } 314 315 return deltas; 316 } 317 318 319 /************************************************************************** 320 * 321 * @Function: 322 * ft_var_load_avar 323 * 324 * @Description: 325 * Parse the `avar' table if present. It need not be, so we return 326 * nothing. 327 * 328 * @InOut: 329 * face :: 330 * The font face. 331 */ 332 static void ft_var_load_avar(TT_Face face)333 ft_var_load_avar( TT_Face face ) 334 { 335 FT_Stream stream = FT_FACE_STREAM( face ); 336 FT_Memory memory = stream->memory; 337 GX_Blend blend = face->blend; 338 GX_AVarSegment segment; 339 FT_Error error = FT_Err_Ok; 340 FT_Long version; 341 FT_Long axisCount; 342 FT_Int i, j; 343 FT_ULong table_len; 344 345 FT_UNUSED( error ); 346 347 348 FT_TRACE2(( "AVAR " )); 349 350 blend->avar_loaded = TRUE; 351 error = face->goto_table( face, TTAG_avar, stream, &table_len ); 352 if ( error ) 353 { 354 FT_TRACE2(( "is missing\n" )); 355 return; 356 } 357 358 if ( FT_FRAME_ENTER( table_len ) ) 359 return; 360 361 version = FT_GET_LONG(); 362 axisCount = FT_GET_LONG(); 363 364 if ( version != 0x00010000L ) 365 { 366 FT_TRACE2(( "bad table version\n" )); 367 goto Exit; 368 } 369 370 FT_TRACE2(( "loaded\n" )); 371 372 if ( axisCount != (FT_Long)blend->mmvar->num_axis ) 373 { 374 FT_TRACE2(( "ft_var_load_avar: number of axes in `avar' and `fvar'\n" 375 " table are different\n" )); 376 goto Exit; 377 } 378 379 if ( FT_NEW_ARRAY( blend->avar_segment, axisCount ) ) 380 goto Exit; 381 382 segment = &blend->avar_segment[0]; 383 for ( i = 0; i < axisCount; i++, segment++ ) 384 { 385 FT_TRACE5(( " axis %d:\n", i )); 386 387 segment->pairCount = FT_GET_USHORT(); 388 if ( (FT_ULong)segment->pairCount * 4 > table_len || 389 FT_NEW_ARRAY( segment->correspondence, segment->pairCount ) ) 390 { 391 /* Failure. Free everything we have done so far. We must do */ 392 /* it right now since loading the `avar' table is optional. */ 393 394 for ( j = i - 1; j >= 0; j-- ) 395 FT_FREE( blend->avar_segment[j].correspondence ); 396 397 FT_FREE( blend->avar_segment ); 398 blend->avar_segment = NULL; 399 goto Exit; 400 } 401 402 for ( j = 0; j < segment->pairCount; j++ ) 403 { 404 segment->correspondence[j].fromCoord = 405 FT_fdot14ToFixed( FT_GET_SHORT() ); 406 segment->correspondence[j].toCoord = 407 FT_fdot14ToFixed( FT_GET_SHORT() ); 408 409 FT_TRACE5(( " mapping %.5f to %.5f\n", 410 segment->correspondence[j].fromCoord / 65536.0, 411 segment->correspondence[j].toCoord / 65536.0 )); 412 } 413 414 FT_TRACE5(( "\n" )); 415 } 416 417 Exit: 418 FT_FRAME_EXIT(); 419 } 420 421 422 static FT_Error ft_var_load_item_variation_store(TT_Face face,FT_ULong offset,GX_ItemVarStore itemStore)423 ft_var_load_item_variation_store( TT_Face face, 424 FT_ULong offset, 425 GX_ItemVarStore itemStore ) 426 { 427 FT_Stream stream = FT_FACE_STREAM( face ); 428 FT_Memory memory = stream->memory; 429 430 FT_Error error; 431 FT_UShort format; 432 FT_ULong region_offset; 433 FT_UInt i, j, k; 434 FT_UInt shortDeltaCount; 435 436 GX_Blend blend = face->blend; 437 GX_ItemVarData varData; 438 439 FT_ULong* dataOffsetArray = NULL; 440 441 442 if ( FT_STREAM_SEEK( offset ) || 443 FT_READ_USHORT( format ) ) 444 goto Exit; 445 446 if ( format != 1 ) 447 { 448 FT_TRACE2(( "ft_var_load_item_variation_store: bad store format %d\n", 449 format )); 450 error = FT_THROW( Invalid_Table ); 451 goto Exit; 452 } 453 454 /* read top level fields */ 455 if ( FT_READ_ULONG( region_offset ) || 456 FT_READ_USHORT( itemStore->dataCount ) ) 457 goto Exit; 458 459 /* we need at least one entry in `itemStore->varData' */ 460 if ( !itemStore->dataCount ) 461 { 462 FT_TRACE2(( "ft_var_load_item_variation_store: missing varData\n" )); 463 error = FT_THROW( Invalid_Table ); 464 goto Exit; 465 } 466 467 /* make temporary copy of item variation data offsets; */ 468 /* we will parse region list first, then come back */ 469 if ( FT_NEW_ARRAY( dataOffsetArray, itemStore->dataCount ) ) 470 goto Exit; 471 472 for ( i = 0; i < itemStore->dataCount; i++ ) 473 { 474 if ( FT_READ_ULONG( dataOffsetArray[i] ) ) 475 goto Exit; 476 } 477 478 /* parse array of region records (region list) */ 479 if ( FT_STREAM_SEEK( offset + region_offset ) ) 480 goto Exit; 481 482 if ( FT_READ_USHORT( itemStore->axisCount ) || 483 FT_READ_USHORT( itemStore->regionCount ) ) 484 goto Exit; 485 486 if ( itemStore->axisCount != (FT_Long)blend->mmvar->num_axis ) 487 { 488 FT_TRACE2(( "ft_var_load_item_variation_store:" 489 " number of axes in item variation store\n" 490 " " 491 " and `fvar' table are different\n" )); 492 error = FT_THROW( Invalid_Table ); 493 goto Exit; 494 } 495 496 if ( FT_NEW_ARRAY( itemStore->varRegionList, itemStore->regionCount ) ) 497 goto Exit; 498 499 for ( i = 0; i < itemStore->regionCount; i++ ) 500 { 501 GX_AxisCoords axisCoords; 502 503 504 if ( FT_NEW_ARRAY( itemStore->varRegionList[i].axisList, 505 itemStore->axisCount ) ) 506 goto Exit; 507 508 axisCoords = itemStore->varRegionList[i].axisList; 509 510 for ( j = 0; j < itemStore->axisCount; j++ ) 511 { 512 FT_Short start, peak, end; 513 514 515 if ( FT_READ_SHORT( start ) || 516 FT_READ_SHORT( peak ) || 517 FT_READ_SHORT( end ) ) 518 goto Exit; 519 520 axisCoords[j].startCoord = FT_fdot14ToFixed( start ); 521 axisCoords[j].peakCoord = FT_fdot14ToFixed( peak ); 522 axisCoords[j].endCoord = FT_fdot14ToFixed( end ); 523 } 524 } 525 526 /* end of region list parse */ 527 528 /* use dataOffsetArray now to parse varData items */ 529 if ( FT_NEW_ARRAY( itemStore->varData, itemStore->dataCount ) ) 530 goto Exit; 531 532 for ( i = 0; i < itemStore->dataCount; i++ ) 533 { 534 varData = &itemStore->varData[i]; 535 536 if ( FT_STREAM_SEEK( offset + dataOffsetArray[i] ) ) 537 goto Exit; 538 539 if ( FT_READ_USHORT( varData->itemCount ) || 540 FT_READ_USHORT( shortDeltaCount ) || 541 FT_READ_USHORT( varData->regionIdxCount ) ) 542 goto Exit; 543 544 /* check some data consistency */ 545 if ( shortDeltaCount > varData->regionIdxCount ) 546 { 547 FT_TRACE2(( "bad short count %d or region count %d\n", 548 shortDeltaCount, 549 varData->regionIdxCount )); 550 error = FT_THROW( Invalid_Table ); 551 goto Exit; 552 } 553 554 if ( varData->regionIdxCount > itemStore->regionCount ) 555 { 556 FT_TRACE2(( "inconsistent regionCount %d in varData[%d]\n", 557 varData->regionIdxCount, 558 i )); 559 error = FT_THROW( Invalid_Table ); 560 goto Exit; 561 } 562 563 /* parse region indices */ 564 if ( FT_NEW_ARRAY( varData->regionIndices, 565 varData->regionIdxCount ) ) 566 goto Exit; 567 568 for ( j = 0; j < varData->regionIdxCount; j++ ) 569 { 570 if ( FT_READ_USHORT( varData->regionIndices[j] ) ) 571 goto Exit; 572 573 if ( varData->regionIndices[j] >= itemStore->regionCount ) 574 { 575 FT_TRACE2(( "bad region index %d\n", 576 varData->regionIndices[j] )); 577 error = FT_THROW( Invalid_Table ); 578 goto Exit; 579 } 580 } 581 582 /* Parse delta set. */ 583 /* */ 584 /* On input, deltas are (shortDeltaCount + regionIdxCount) bytes */ 585 /* each; on output, deltas are expanded to `regionIdxCount' shorts */ 586 /* each. */ 587 if ( FT_NEW_ARRAY( varData->deltaSet, 588 varData->regionIdxCount * varData->itemCount ) ) 589 goto Exit; 590 591 /* the delta set is stored as a 2-dimensional array of shorts; */ 592 /* sign-extend signed bytes to signed shorts */ 593 for ( j = 0; j < varData->itemCount * varData->regionIdxCount; ) 594 { 595 for ( k = 0; k < shortDeltaCount; k++, j++ ) 596 { 597 /* read the short deltas */ 598 FT_Short delta; 599 600 601 if ( FT_READ_SHORT( delta ) ) 602 goto Exit; 603 604 varData->deltaSet[j] = delta; 605 } 606 607 for ( ; k < varData->regionIdxCount; k++, j++ ) 608 { 609 /* read the (signed) byte deltas */ 610 FT_Char delta; 611 612 613 if ( FT_READ_CHAR( delta ) ) 614 goto Exit; 615 616 varData->deltaSet[j] = delta; 617 } 618 } 619 } 620 621 Exit: 622 FT_FREE( dataOffsetArray ); 623 624 return error; 625 } 626 627 628 static FT_Error ft_var_load_delta_set_index_mapping(TT_Face face,FT_ULong offset,GX_DeltaSetIdxMap map,GX_ItemVarStore itemStore)629 ft_var_load_delta_set_index_mapping( TT_Face face, 630 FT_ULong offset, 631 GX_DeltaSetIdxMap map, 632 GX_ItemVarStore itemStore ) 633 { 634 FT_Stream stream = FT_FACE_STREAM( face ); 635 FT_Memory memory = stream->memory; 636 637 FT_Error error; 638 639 FT_UShort format; 640 FT_UInt entrySize; 641 FT_UInt innerBitCount; 642 FT_UInt innerIndexMask; 643 FT_UInt i, j; 644 645 646 if ( FT_STREAM_SEEK( offset ) || 647 FT_READ_USHORT( format ) || 648 FT_READ_USHORT( map->mapCount ) ) 649 goto Exit; 650 651 if ( format & 0xFFC0 ) 652 { 653 FT_TRACE2(( "bad map format %d\n", format )); 654 error = FT_THROW( Invalid_Table ); 655 goto Exit; 656 } 657 658 /* bytes per entry: 1, 2, 3, or 4 */ 659 entrySize = ( ( format & 0x0030 ) >> 4 ) + 1; 660 innerBitCount = ( format & 0x000F ) + 1; 661 innerIndexMask = ( 1 << innerBitCount ) - 1; 662 663 if ( FT_NEW_ARRAY( map->innerIndex, map->mapCount ) ) 664 goto Exit; 665 666 if ( FT_NEW_ARRAY( map->outerIndex, map->mapCount ) ) 667 goto Exit; 668 669 for ( i = 0; i < map->mapCount; i++ ) 670 { 671 FT_UInt mapData = 0; 672 FT_UInt outerIndex, innerIndex; 673 674 675 /* read map data one unsigned byte at a time, big endian */ 676 for ( j = 0; j < entrySize; j++ ) 677 { 678 FT_Byte data; 679 680 681 if ( FT_READ_BYTE( data ) ) 682 goto Exit; 683 684 mapData = ( mapData << 8 ) | data; 685 } 686 687 outerIndex = mapData >> innerBitCount; 688 689 if ( outerIndex >= itemStore->dataCount ) 690 { 691 FT_TRACE2(( "outerIndex[%d] == %d out of range\n", 692 i, 693 outerIndex )); 694 error = FT_THROW( Invalid_Table ); 695 goto Exit; 696 } 697 698 map->outerIndex[i] = outerIndex; 699 700 innerIndex = mapData & innerIndexMask; 701 702 if ( innerIndex >= itemStore->varData[outerIndex].itemCount ) 703 { 704 FT_TRACE2(( "innerIndex[%d] == %d out of range\n", 705 i, 706 innerIndex )); 707 error = FT_THROW( Invalid_Table ); 708 goto Exit; 709 } 710 711 map->innerIndex[i] = innerIndex; 712 } 713 714 Exit: 715 return error; 716 } 717 718 719 /************************************************************************** 720 * 721 * @Function: 722 * ft_var_load_hvvar 723 * 724 * @Description: 725 * If `vertical' is zero, parse the `HVAR' table and set 726 * `blend->hvar_loaded' to TRUE. On success, `blend->hvar_checked' 727 * is set to TRUE. 728 * 729 * If `vertical' is not zero, parse the `VVAR' table and set 730 * `blend->vvar_loaded' to TRUE. On success, `blend->vvar_checked' 731 * is set to TRUE. 732 * 733 * Some memory may remain allocated on error; it is always freed in 734 * `tt_done_blend', however. 735 * 736 * @InOut: 737 * face :: 738 * The font face. 739 * 740 * @Return: 741 * FreeType error code. 0 means success. 742 */ 743 static FT_Error ft_var_load_hvvar(TT_Face face,FT_Bool vertical)744 ft_var_load_hvvar( TT_Face face, 745 FT_Bool vertical ) 746 { 747 FT_Stream stream = FT_FACE_STREAM( face ); 748 FT_Memory memory = stream->memory; 749 750 GX_Blend blend = face->blend; 751 752 GX_HVVarTable table; 753 754 FT_Error error; 755 FT_UShort majorVersion; 756 FT_ULong table_len; 757 FT_ULong table_offset; 758 FT_ULong store_offset; 759 FT_ULong widthMap_offset; 760 761 762 if ( vertical ) 763 { 764 blend->vvar_loaded = TRUE; 765 766 FT_TRACE2(( "VVAR " )); 767 768 error = face->goto_table( face, TTAG_VVAR, stream, &table_len ); 769 } 770 else 771 { 772 blend->hvar_loaded = TRUE; 773 774 FT_TRACE2(( "HVAR " )); 775 776 error = face->goto_table( face, TTAG_HVAR, stream, &table_len ); 777 } 778 779 if ( error ) 780 { 781 FT_TRACE2(( "is missing\n" )); 782 goto Exit; 783 } 784 785 table_offset = FT_STREAM_POS(); 786 787 /* skip minor version */ 788 if ( FT_READ_USHORT( majorVersion ) || 789 FT_STREAM_SKIP( 2 ) ) 790 goto Exit; 791 792 if ( majorVersion != 1 ) 793 { 794 FT_TRACE2(( "bad table version %d\n", majorVersion )); 795 error = FT_THROW( Invalid_Table ); 796 goto Exit; 797 } 798 799 if ( FT_READ_ULONG( store_offset ) || 800 FT_READ_ULONG( widthMap_offset ) ) 801 goto Exit; 802 803 if ( vertical ) 804 { 805 if ( FT_NEW( blend->vvar_table ) ) 806 goto Exit; 807 table = blend->vvar_table; 808 } 809 else 810 { 811 if ( FT_NEW( blend->hvar_table ) ) 812 goto Exit; 813 table = blend->hvar_table; 814 } 815 816 error = ft_var_load_item_variation_store( 817 face, 818 table_offset + store_offset, 819 &table->itemStore ); 820 if ( error ) 821 goto Exit; 822 823 if ( widthMap_offset ) 824 { 825 error = ft_var_load_delta_set_index_mapping( 826 face, 827 table_offset + widthMap_offset, 828 &table->widthMap, 829 &table->itemStore ); 830 if ( error ) 831 goto Exit; 832 } 833 834 FT_TRACE2(( "loaded\n" )); 835 error = FT_Err_Ok; 836 837 Exit: 838 if ( !error ) 839 { 840 if ( vertical ) 841 { 842 blend->vvar_checked = TRUE; 843 844 /* FreeType doesn't provide functions to quickly retrieve */ 845 /* TSB, BSB, or VORG values; we thus don't have to implement */ 846 /* support for those three item variation stores. */ 847 848 face->variation_support |= TT_FACE_FLAG_VAR_VADVANCE; 849 } 850 else 851 { 852 blend->hvar_checked = TRUE; 853 854 /* FreeType doesn't provide functions to quickly retrieve */ 855 /* LSB or RSB values; we thus don't have to implement */ 856 /* support for those two item variation stores. */ 857 858 face->variation_support |= TT_FACE_FLAG_VAR_HADVANCE; 859 } 860 } 861 862 return error; 863 } 864 865 866 static FT_Int ft_var_get_item_delta(TT_Face face,GX_ItemVarStore itemStore,FT_UInt outerIndex,FT_UInt innerIndex)867 ft_var_get_item_delta( TT_Face face, 868 GX_ItemVarStore itemStore, 869 FT_UInt outerIndex, 870 FT_UInt innerIndex ) 871 { 872 GX_ItemVarData varData; 873 FT_Short* deltaSet; 874 875 FT_UInt master, j; 876 FT_Fixed netAdjustment = 0; /* accumulated adjustment */ 877 FT_Fixed scaledDelta; 878 FT_Fixed delta; 879 880 881 /* See pseudo code from `Font Variations Overview' */ 882 /* in the OpenType specification. */ 883 884 varData = &itemStore->varData[outerIndex]; 885 deltaSet = &varData->deltaSet[varData->regionIdxCount * innerIndex]; 886 887 /* outer loop steps through master designs to be blended */ 888 for ( master = 0; master < varData->regionIdxCount; master++ ) 889 { 890 FT_Fixed scalar = 0x10000L; 891 FT_UInt regionIndex = varData->regionIndices[master]; 892 893 GX_AxisCoords axis = itemStore->varRegionList[regionIndex].axisList; 894 895 896 /* inner loop steps through axes in this region */ 897 for ( j = 0; j < itemStore->axisCount; j++, axis++ ) 898 { 899 /* compute the scalar contribution of this axis; */ 900 /* ignore invalid ranges */ 901 if ( axis->startCoord > axis->peakCoord || 902 axis->peakCoord > axis->endCoord ) 903 continue; 904 905 else if ( axis->startCoord < 0 && 906 axis->endCoord > 0 && 907 axis->peakCoord != 0 ) 908 continue; 909 910 /* peak of 0 means ignore this axis */ 911 else if ( axis->peakCoord == 0 ) 912 continue; 913 914 else if ( face->blend->normalizedcoords[j] == axis->peakCoord ) 915 continue; 916 917 /* ignore this region if coords are out of range */ 918 else if ( face->blend->normalizedcoords[j] <= axis->startCoord || 919 face->blend->normalizedcoords[j] >= axis->endCoord ) 920 { 921 scalar = 0; 922 break; 923 } 924 925 /* cumulative product of all the axis scalars */ 926 else if ( face->blend->normalizedcoords[j] < axis->peakCoord ) 927 scalar = 928 FT_MulDiv( scalar, 929 face->blend->normalizedcoords[j] - axis->startCoord, 930 axis->peakCoord - axis->startCoord ); 931 else 932 scalar = 933 FT_MulDiv( scalar, 934 axis->endCoord - face->blend->normalizedcoords[j], 935 axis->endCoord - axis->peakCoord ); 936 } /* per-axis loop */ 937 938 /* get the scaled delta for this region */ 939 delta = FT_intToFixed( deltaSet[master] ); 940 scaledDelta = FT_MulFix( scalar, delta ); 941 942 /* accumulate the adjustments from each region */ 943 netAdjustment = netAdjustment + scaledDelta; 944 945 } /* per-region loop */ 946 947 return FT_fixedToInt( netAdjustment ); 948 } 949 950 951 /************************************************************************** 952 * 953 * @Function: 954 * tt_hvadvance_adjust 955 * 956 * @Description: 957 * Apply `HVAR' advance width or `VVAR' advance height adjustment of 958 * a given glyph. 959 * 960 * @Input: 961 * gindex :: 962 * The glyph index. 963 * 964 * vertical :: 965 * If set, handle `VVAR' table. 966 * 967 * @InOut: 968 * face :: 969 * The font face. 970 * 971 * adelta :: 972 * Points to width or height value that gets modified. 973 */ 974 static FT_Error tt_hvadvance_adjust(TT_Face face,FT_UInt gindex,FT_Int * avalue,FT_Bool vertical)975 tt_hvadvance_adjust( TT_Face face, 976 FT_UInt gindex, 977 FT_Int *avalue, 978 FT_Bool vertical ) 979 { 980 FT_Error error = FT_Err_Ok; 981 FT_UInt innerIndex, outerIndex; 982 FT_Int delta; 983 984 GX_HVVarTable table; 985 986 987 if ( !face->doblend || !face->blend ) 988 goto Exit; 989 990 if ( vertical ) 991 { 992 if ( !face->blend->vvar_loaded ) 993 { 994 /* initialize vvar table */ 995 face->blend->vvar_error = ft_var_load_hvvar( face, 1 ); 996 } 997 998 if ( !face->blend->vvar_checked ) 999 { 1000 error = face->blend->vvar_error; 1001 goto Exit; 1002 } 1003 1004 table = face->blend->vvar_table; 1005 } 1006 else 1007 { 1008 if ( !face->blend->hvar_loaded ) 1009 { 1010 /* initialize hvar table */ 1011 face->blend->hvar_error = ft_var_load_hvvar( face, 0 ); 1012 } 1013 1014 if ( !face->blend->hvar_checked ) 1015 { 1016 error = face->blend->hvar_error; 1017 goto Exit; 1018 } 1019 1020 table = face->blend->hvar_table; 1021 } 1022 1023 /* advance width or height adjustments are always present in an */ 1024 /* `HVAR' or `VVAR' table; no need to test for this capability */ 1025 1026 if ( table->widthMap.innerIndex ) 1027 { 1028 FT_UInt idx = gindex; 1029 1030 1031 if ( idx >= table->widthMap.mapCount ) 1032 idx = table->widthMap.mapCount - 1; 1033 1034 /* trust that HVAR parser has checked indices */ 1035 outerIndex = table->widthMap.outerIndex[idx]; 1036 innerIndex = table->widthMap.innerIndex[idx]; 1037 } 1038 else 1039 { 1040 GX_ItemVarData varData; 1041 1042 1043 /* no widthMap data */ 1044 outerIndex = 0; 1045 innerIndex = gindex; 1046 1047 varData = &table->itemStore.varData[outerIndex]; 1048 if ( gindex >= varData->itemCount ) 1049 { 1050 FT_TRACE2(( "gindex %d out of range\n", gindex )); 1051 error = FT_THROW( Invalid_Argument ); 1052 goto Exit; 1053 } 1054 } 1055 1056 delta = ft_var_get_item_delta( face, 1057 &table->itemStore, 1058 outerIndex, 1059 innerIndex ); 1060 1061 FT_TRACE5(( "%s value %d adjusted by %d unit%s (%s)\n", 1062 vertical ? "vertical height" : "horizontal width", 1063 *avalue, 1064 delta, 1065 delta == 1 ? "" : "s", 1066 vertical ? "VVAR" : "HVAR" )); 1067 1068 *avalue += delta; 1069 1070 Exit: 1071 return error; 1072 } 1073 1074 1075 FT_LOCAL_DEF( FT_Error ) tt_hadvance_adjust(TT_Face face,FT_UInt gindex,FT_Int * avalue)1076 tt_hadvance_adjust( TT_Face face, 1077 FT_UInt gindex, 1078 FT_Int *avalue ) 1079 { 1080 return tt_hvadvance_adjust( face, gindex, avalue, 0 ); 1081 } 1082 1083 1084 FT_LOCAL_DEF( FT_Error ) tt_vadvance_adjust(TT_Face face,FT_UInt gindex,FT_Int * avalue)1085 tt_vadvance_adjust( TT_Face face, 1086 FT_UInt gindex, 1087 FT_Int *avalue ) 1088 { 1089 return tt_hvadvance_adjust( face, gindex, avalue, 1 ); 1090 } 1091 1092 1093 #define GX_VALUE_SIZE 8 1094 1095 /* all values are FT_Short or FT_UShort entities; */ 1096 /* we treat them consistently as FT_Short */ 1097 #define GX_VALUE_CASE( tag, dflt ) \ 1098 case MVAR_TAG_ ## tag : \ 1099 p = (FT_Short*)&face->dflt; \ 1100 break 1101 1102 #define GX_GASP_CASE( idx ) \ 1103 case MVAR_TAG_GASP_ ## idx : \ 1104 if ( idx < face->gasp.numRanges - 1 ) \ 1105 p = (FT_Short*)&face->gasp.gaspRanges[idx].maxPPEM; \ 1106 else \ 1107 p = NULL; \ 1108 break 1109 1110 1111 static FT_Short* ft_var_get_value_pointer(TT_Face face,FT_ULong mvar_tag)1112 ft_var_get_value_pointer( TT_Face face, 1113 FT_ULong mvar_tag ) 1114 { 1115 FT_Short* p; 1116 1117 1118 switch ( mvar_tag ) 1119 { 1120 GX_GASP_CASE( 0 ); 1121 GX_GASP_CASE( 1 ); 1122 GX_GASP_CASE( 2 ); 1123 GX_GASP_CASE( 3 ); 1124 GX_GASP_CASE( 4 ); 1125 GX_GASP_CASE( 5 ); 1126 GX_GASP_CASE( 6 ); 1127 GX_GASP_CASE( 7 ); 1128 GX_GASP_CASE( 8 ); 1129 GX_GASP_CASE( 9 ); 1130 1131 GX_VALUE_CASE( CPHT, os2.sCapHeight ); 1132 GX_VALUE_CASE( HASC, os2.sTypoAscender ); 1133 GX_VALUE_CASE( HCLA, os2.usWinAscent ); 1134 GX_VALUE_CASE( HCLD, os2.usWinDescent ); 1135 GX_VALUE_CASE( HCOF, horizontal.caret_Offset ); 1136 GX_VALUE_CASE( HCRN, horizontal.caret_Slope_Run ); 1137 GX_VALUE_CASE( HCRS, horizontal.caret_Slope_Rise ); 1138 GX_VALUE_CASE( HDSC, os2.sTypoDescender ); 1139 GX_VALUE_CASE( HLGP, os2.sTypoLineGap ); 1140 GX_VALUE_CASE( SBXO, os2.ySubscriptXOffset); 1141 GX_VALUE_CASE( SBXS, os2.ySubscriptXSize ); 1142 GX_VALUE_CASE( SBYO, os2.ySubscriptYOffset ); 1143 GX_VALUE_CASE( SBYS, os2.ySubscriptYSize ); 1144 GX_VALUE_CASE( SPXO, os2.ySuperscriptXOffset ); 1145 GX_VALUE_CASE( SPXS, os2.ySuperscriptXSize ); 1146 GX_VALUE_CASE( SPYO, os2.ySuperscriptYOffset ); 1147 GX_VALUE_CASE( SPYS, os2.ySuperscriptYSize ); 1148 GX_VALUE_CASE( STRO, os2.yStrikeoutPosition ); 1149 GX_VALUE_CASE( STRS, os2.yStrikeoutSize ); 1150 GX_VALUE_CASE( UNDO, postscript.underlinePosition ); 1151 GX_VALUE_CASE( UNDS, postscript.underlineThickness ); 1152 GX_VALUE_CASE( VASC, vertical.Ascender ); 1153 GX_VALUE_CASE( VCOF, vertical.caret_Offset ); 1154 GX_VALUE_CASE( VCRN, vertical.caret_Slope_Run ); 1155 GX_VALUE_CASE( VCRS, vertical.caret_Slope_Rise ); 1156 GX_VALUE_CASE( VDSC, vertical.Descender ); 1157 GX_VALUE_CASE( VLGP, vertical.Line_Gap ); 1158 GX_VALUE_CASE( XHGT, os2.sxHeight ); 1159 1160 default: 1161 /* ignore unknown tag */ 1162 p = NULL; 1163 } 1164 1165 return p; 1166 } 1167 1168 1169 /************************************************************************** 1170 * 1171 * @Function: 1172 * ft_var_load_mvar 1173 * 1174 * @Description: 1175 * Parse the `MVAR' table. 1176 * 1177 * Some memory may remain allocated on error; it is always freed in 1178 * `tt_done_blend', however. 1179 * 1180 * @InOut: 1181 * face :: 1182 * The font face. 1183 */ 1184 static void ft_var_load_mvar(TT_Face face)1185 ft_var_load_mvar( TT_Face face ) 1186 { 1187 FT_Stream stream = FT_FACE_STREAM( face ); 1188 FT_Memory memory = stream->memory; 1189 1190 GX_Blend blend = face->blend; 1191 GX_ItemVarStore itemStore; 1192 GX_Value value, limit; 1193 1194 FT_Error error; 1195 FT_UShort majorVersion; 1196 FT_ULong table_len; 1197 FT_ULong table_offset; 1198 FT_UShort store_offset; 1199 FT_ULong records_offset; 1200 1201 1202 FT_TRACE2(( "MVAR " )); 1203 1204 error = face->goto_table( face, TTAG_MVAR, stream, &table_len ); 1205 if ( error ) 1206 { 1207 FT_TRACE2(( "is missing\n" )); 1208 return; 1209 } 1210 1211 table_offset = FT_STREAM_POS(); 1212 1213 /* skip minor version */ 1214 if ( FT_READ_USHORT( majorVersion ) || 1215 FT_STREAM_SKIP( 2 ) ) 1216 return; 1217 1218 if ( majorVersion != 1 ) 1219 { 1220 FT_TRACE2(( "bad table version %d\n", majorVersion )); 1221 return; 1222 } 1223 1224 if ( FT_NEW( blend->mvar_table ) ) 1225 return; 1226 1227 /* skip reserved entry and value record size */ 1228 if ( FT_STREAM_SKIP( 4 ) || 1229 FT_READ_USHORT( blend->mvar_table->valueCount ) || 1230 FT_READ_USHORT( store_offset ) ) 1231 return; 1232 1233 records_offset = FT_STREAM_POS(); 1234 1235 error = ft_var_load_item_variation_store( 1236 face, 1237 table_offset + store_offset, 1238 &blend->mvar_table->itemStore ); 1239 if ( error ) 1240 return; 1241 1242 if ( FT_NEW_ARRAY( blend->mvar_table->values, 1243 blend->mvar_table->valueCount ) ) 1244 return; 1245 1246 if ( FT_STREAM_SEEK( records_offset ) || 1247 FT_FRAME_ENTER( blend->mvar_table->valueCount * GX_VALUE_SIZE ) ) 1248 return; 1249 1250 value = blend->mvar_table->values; 1251 limit = value + blend->mvar_table->valueCount; 1252 itemStore = &blend->mvar_table->itemStore; 1253 1254 for ( ; value < limit; value++ ) 1255 { 1256 value->tag = FT_GET_ULONG(); 1257 value->outerIndex = FT_GET_USHORT(); 1258 value->innerIndex = FT_GET_USHORT(); 1259 1260 if ( value->outerIndex >= itemStore->dataCount || 1261 value->innerIndex >= itemStore->varData[value->outerIndex] 1262 .itemCount ) 1263 { 1264 error = FT_THROW( Invalid_Table ); 1265 break; 1266 } 1267 } 1268 1269 FT_FRAME_EXIT(); 1270 1271 if ( error ) 1272 return; 1273 1274 FT_TRACE2(( "loaded\n" )); 1275 1276 value = blend->mvar_table->values; 1277 limit = value + blend->mvar_table->valueCount; 1278 1279 /* save original values of the data MVAR is going to modify */ 1280 for ( ; value < limit; value++ ) 1281 { 1282 FT_Short* p = ft_var_get_value_pointer( face, value->tag ); 1283 1284 1285 if ( p ) 1286 value->unmodified = *p; 1287 #ifdef FT_DEBUG_LEVEL_TRACE 1288 else 1289 FT_TRACE1(( "ft_var_load_mvar: Ignoring unknown tag `%c%c%c%c'\n", 1290 (FT_Char)( value->tag >> 24 ), 1291 (FT_Char)( value->tag >> 16 ), 1292 (FT_Char)( value->tag >> 8 ), 1293 (FT_Char)( value->tag ) )); 1294 #endif 1295 } 1296 1297 face->variation_support |= TT_FACE_FLAG_VAR_MVAR; 1298 } 1299 1300 1301 static FT_Error tt_size_reset_iterator(FT_ListNode node,void * user)1302 tt_size_reset_iterator( FT_ListNode node, 1303 void* user ) 1304 { 1305 TT_Size size = (TT_Size)node->data; 1306 1307 FT_UNUSED( user ); 1308 1309 1310 tt_size_reset( size, 1 ); 1311 1312 return FT_Err_Ok; 1313 } 1314 1315 1316 /************************************************************************** 1317 * 1318 * @Function: 1319 * tt_apply_mvar 1320 * 1321 * @Description: 1322 * Apply `MVAR' table adjustments. 1323 * 1324 * @InOut: 1325 * face :: 1326 * The font face. 1327 */ 1328 FT_LOCAL_DEF( void ) tt_apply_mvar(TT_Face face)1329 tt_apply_mvar( TT_Face face ) 1330 { 1331 GX_Blend blend = face->blend; 1332 GX_Value value, limit; 1333 FT_Short mvar_hasc_delta = 0; 1334 FT_Short mvar_hdsc_delta = 0; 1335 FT_Short mvar_hlgp_delta = 0; 1336 1337 1338 if ( !( face->variation_support & TT_FACE_FLAG_VAR_MVAR ) ) 1339 return; 1340 1341 value = blend->mvar_table->values; 1342 limit = value + blend->mvar_table->valueCount; 1343 1344 for ( ; value < limit; value++ ) 1345 { 1346 FT_Short* p = ft_var_get_value_pointer( face, value->tag ); 1347 FT_Int delta; 1348 1349 1350 delta = ft_var_get_item_delta( face, 1351 &blend->mvar_table->itemStore, 1352 value->outerIndex, 1353 value->innerIndex ); 1354 1355 if ( p ) 1356 { 1357 FT_TRACE5(( "value %c%c%c%c (%d unit%s) adjusted by %d unit%s (MVAR)\n", 1358 (FT_Char)( value->tag >> 24 ), 1359 (FT_Char)( value->tag >> 16 ), 1360 (FT_Char)( value->tag >> 8 ), 1361 (FT_Char)( value->tag ), 1362 value->unmodified, 1363 value->unmodified == 1 ? "" : "s", 1364 delta, 1365 delta == 1 ? "" : "s" )); 1366 1367 /* since we handle both signed and unsigned values as FT_Short, */ 1368 /* ensure proper overflow arithmetic */ 1369 *p = (FT_Short)( value->unmodified + (FT_Short)delta ); 1370 1371 /* Treat hasc, hdsc and hlgp specially, see below. */ 1372 if ( value->tag == MVAR_TAG_HASC ) 1373 mvar_hasc_delta = (FT_Short)delta; 1374 else if ( value->tag == MVAR_TAG_HDSC ) 1375 mvar_hdsc_delta = (FT_Short)delta; 1376 else if ( value->tag == MVAR_TAG_HLGP ) 1377 mvar_hlgp_delta = (FT_Short)delta; 1378 } 1379 } 1380 1381 /* adjust all derived values */ 1382 { 1383 FT_Face root = &face->root; 1384 1385 /* 1386 * Apply the deltas of hasc, hdsc and hlgp to the FT_Face's ascender, 1387 * descender and height attributes, no matter how they were originally 1388 * computed. 1389 * 1390 * (Code that ignores those and accesses the font's metrics values 1391 * directly is already served by the delta application code above.) 1392 * 1393 * The MVAR table supports variations for both typo and win metrics. 1394 * According to Behdad Esfahbod, the thinking of the working group was 1395 * that no one uses win metrics anymore for setting line metrics (the 1396 * specification even calls these metrics "horizontal clipping 1397 * ascent/descent", probably for their role on the Windows platform in 1398 * computing clipping boxes), and new fonts should use typo metrics, so 1399 * typo deltas should be applied to whatever sfnt_load_face decided the 1400 * line metrics should be. 1401 * 1402 * Before, the following led to different line metrics between default 1403 * outline and instances, visible when e.g. the default outlines were 1404 * used as the regular face and instances for everything else: 1405 * 1406 * 1. sfnt_load_face applied the hhea metrics by default. 1407 * 2. This code later applied the typo metrics by default, regardless of 1408 * whether they were actually changed or the font had the OS/2 table's 1409 * fsSelection's bit 7 (USE_TYPO_METRICS) set. 1410 */ 1411 FT_Short current_line_gap = root->height - root->ascender + 1412 root->descender; 1413 1414 1415 root->ascender = root->ascender + mvar_hasc_delta; 1416 root->descender = root->descender + mvar_hdsc_delta; 1417 root->height = root->ascender - root->descender + 1418 current_line_gap + mvar_hlgp_delta; 1419 1420 root->underline_position = face->postscript.underlinePosition - 1421 face->postscript.underlineThickness / 2; 1422 root->underline_thickness = face->postscript.underlineThickness; 1423 1424 /* iterate over all FT_Size objects and call `tt_size_reset' */ 1425 /* to propagate the metrics changes */ 1426 FT_List_Iterate( &root->sizes_list, 1427 tt_size_reset_iterator, 1428 NULL ); 1429 } 1430 } 1431 1432 1433 typedef struct GX_GVar_Head_ 1434 { 1435 FT_Long version; 1436 FT_UShort axisCount; 1437 FT_UShort globalCoordCount; 1438 FT_ULong offsetToCoord; 1439 FT_UShort glyphCount; 1440 FT_UShort flags; 1441 FT_ULong offsetToData; 1442 1443 } GX_GVar_Head; 1444 1445 1446 /************************************************************************** 1447 * 1448 * @Function: 1449 * ft_var_load_gvar 1450 * 1451 * @Description: 1452 * Parse the `gvar' table if present. If `fvar' is there, `gvar' had 1453 * better be there too. 1454 * 1455 * @InOut: 1456 * face :: 1457 * The font face. 1458 * 1459 * @Return: 1460 * FreeType error code. 0 means success. 1461 */ 1462 static FT_Error ft_var_load_gvar(TT_Face face)1463 ft_var_load_gvar( TT_Face face ) 1464 { 1465 FT_Stream stream = FT_FACE_STREAM( face ); 1466 FT_Memory memory = stream->memory; 1467 GX_Blend blend = face->blend; 1468 FT_Error error; 1469 FT_UInt i, j; 1470 FT_ULong table_len; 1471 FT_ULong gvar_start; 1472 FT_ULong offsetToData; 1473 FT_ULong offsets_len; 1474 GX_GVar_Head gvar_head; 1475 1476 static const FT_Frame_Field gvar_fields[] = 1477 { 1478 1479 #undef FT_STRUCTURE 1480 #define FT_STRUCTURE GX_GVar_Head 1481 1482 FT_FRAME_START( 20 ), 1483 FT_FRAME_LONG ( version ), 1484 FT_FRAME_USHORT( axisCount ), 1485 FT_FRAME_USHORT( globalCoordCount ), 1486 FT_FRAME_ULONG ( offsetToCoord ), 1487 FT_FRAME_USHORT( glyphCount ), 1488 FT_FRAME_USHORT( flags ), 1489 FT_FRAME_ULONG ( offsetToData ), 1490 FT_FRAME_END 1491 }; 1492 1493 1494 FT_TRACE2(( "GVAR " )); 1495 1496 if ( FT_SET_ERROR( face->goto_table( face, 1497 TTAG_gvar, 1498 stream, 1499 &table_len ) ) ) 1500 { 1501 FT_TRACE2(( "is missing\n" )); 1502 goto Exit; 1503 } 1504 1505 gvar_start = FT_STREAM_POS( ); 1506 if ( FT_STREAM_READ_FIELDS( gvar_fields, &gvar_head ) ) 1507 goto Exit; 1508 1509 if ( gvar_head.version != 0x00010000L ) 1510 { 1511 FT_TRACE1(( "bad table version\n" )); 1512 error = FT_THROW( Invalid_Table ); 1513 goto Exit; 1514 } 1515 1516 if ( gvar_head.axisCount != (FT_UShort)blend->mmvar->num_axis ) 1517 { 1518 FT_TRACE1(( "ft_var_load_gvar: number of axes in `gvar' and `cvar'\n" 1519 " table are different\n" )); 1520 error = FT_THROW( Invalid_Table ); 1521 goto Exit; 1522 } 1523 1524 /* rough sanity check, ignoring offsets */ 1525 if ( (FT_ULong)gvar_head.globalCoordCount * gvar_head.axisCount > 1526 table_len / 2 ) 1527 { 1528 FT_TRACE1(( "ft_var_load_gvar:" 1529 " invalid number of global coordinates\n" )); 1530 error = FT_THROW( Invalid_Table ); 1531 goto Exit; 1532 } 1533 1534 /* offsets can be either 2 or 4 bytes */ 1535 /* (one more offset than glyphs, to mark size of last) */ 1536 offsets_len = ( gvar_head.glyphCount + 1 ) * 1537 ( ( gvar_head.flags & 1 ) ? 4L : 2L ); 1538 1539 /* rough sanity check */ 1540 if (offsets_len > table_len ) 1541 { 1542 FT_TRACE1(( "ft_var_load_gvar: invalid number of glyphs\n" )); 1543 error = FT_THROW( Invalid_Table ); 1544 goto Exit; 1545 } 1546 1547 FT_TRACE2(( "loaded\n" )); 1548 1549 blend->gvar_size = table_len; 1550 offsetToData = gvar_start + gvar_head.offsetToData; 1551 1552 FT_TRACE5(( "gvar: there %s %d shared coordinate%s:\n", 1553 gvar_head.globalCoordCount == 1 ? "is" : "are", 1554 gvar_head.globalCoordCount, 1555 gvar_head.globalCoordCount == 1 ? "" : "s" )); 1556 1557 if ( FT_FRAME_ENTER( offsets_len ) ) 1558 goto Exit; 1559 1560 /* offsets (one more offset than glyphs, to mark size of last) */ 1561 if ( FT_NEW_ARRAY( blend->glyphoffsets, gvar_head.glyphCount + 1 ) ) 1562 goto Fail2; 1563 1564 if ( gvar_head.flags & 1 ) 1565 { 1566 FT_ULong limit = gvar_start + table_len; 1567 FT_ULong max_offset = 0; 1568 1569 1570 for ( i = 0; i <= gvar_head.glyphCount; i++ ) 1571 { 1572 blend->glyphoffsets[i] = offsetToData + FT_GET_ULONG(); 1573 1574 if ( max_offset <= blend->glyphoffsets[i] ) 1575 max_offset = blend->glyphoffsets[i]; 1576 else 1577 { 1578 FT_TRACE2(( "ft_var_load_gvar:" 1579 " glyph variation data offset %d not monotonic\n", 1580 i )); 1581 blend->glyphoffsets[i] = max_offset; 1582 } 1583 1584 /* use `<', not `<=' */ 1585 if ( limit < blend->glyphoffsets[i] ) 1586 { 1587 FT_TRACE2(( "ft_var_load_gvar:" 1588 " glyph variation data offset %d out of range\n", 1589 i )); 1590 blend->glyphoffsets[i] = limit; 1591 } 1592 } 1593 } 1594 else 1595 { 1596 FT_ULong limit = gvar_start + table_len; 1597 FT_ULong max_offset = 0; 1598 1599 1600 for ( i = 0; i <= gvar_head.glyphCount; i++ ) 1601 { 1602 blend->glyphoffsets[i] = offsetToData + FT_GET_USHORT() * 2; 1603 1604 if ( max_offset <= blend->glyphoffsets[i] ) 1605 max_offset = blend->glyphoffsets[i]; 1606 else 1607 { 1608 FT_TRACE2(( "ft_var_load_gvar:" 1609 " glyph variation data offset %d not monotonic\n", 1610 i )); 1611 blend->glyphoffsets[i] = max_offset; 1612 } 1613 1614 /* use `<', not `<=' */ 1615 if ( limit < blend->glyphoffsets[i] ) 1616 { 1617 FT_TRACE2(( "ft_var_load_gvar:" 1618 " glyph variation data offset %d out of range\n", 1619 i )); 1620 blend->glyphoffsets[i] = limit; 1621 } 1622 } 1623 } 1624 1625 blend->gv_glyphcnt = gvar_head.glyphCount; 1626 1627 FT_FRAME_EXIT(); 1628 1629 if ( gvar_head.globalCoordCount != 0 ) 1630 { 1631 if ( FT_STREAM_SEEK( gvar_start + gvar_head.offsetToCoord ) || 1632 FT_FRAME_ENTER( gvar_head.globalCoordCount * 1633 gvar_head.axisCount * 2L ) ) 1634 { 1635 FT_TRACE2(( "ft_var_load_gvar:" 1636 " glyph variation shared tuples missing\n" )); 1637 goto Fail; 1638 } 1639 1640 if ( FT_NEW_ARRAY( blend->tuplecoords, 1641 gvar_head.axisCount * gvar_head.globalCoordCount ) ) 1642 goto Fail2; 1643 1644 for ( i = 0; i < gvar_head.globalCoordCount; i++ ) 1645 { 1646 FT_TRACE5(( " [ " )); 1647 for ( j = 0; j < (FT_UInt)gvar_head.axisCount; j++ ) 1648 { 1649 blend->tuplecoords[i * gvar_head.axisCount + j] = 1650 FT_fdot14ToFixed( FT_GET_SHORT() ); 1651 FT_TRACE5(( "%.5f ", 1652 blend->tuplecoords[i * gvar_head.axisCount + j] / 65536.0 )); 1653 } 1654 FT_TRACE5(( "]\n" )); 1655 } 1656 1657 blend->tuplecount = gvar_head.globalCoordCount; 1658 1659 FT_TRACE5(( "\n" )); 1660 1661 FT_FRAME_EXIT(); 1662 } 1663 1664 Exit: 1665 return error; 1666 1667 Fail2: 1668 FT_FRAME_EXIT(); 1669 1670 Fail: 1671 FT_FREE( blend->glyphoffsets ); 1672 blend->gv_glyphcnt = 0; 1673 goto Exit; 1674 } 1675 1676 1677 /************************************************************************** 1678 * 1679 * @Function: 1680 * ft_var_apply_tuple 1681 * 1682 * @Description: 1683 * Figure out whether a given tuple (design) applies to the current 1684 * blend, and if so, what is the scaling factor. 1685 * 1686 * @Input: 1687 * blend :: 1688 * The current blend of the font. 1689 * 1690 * tupleIndex :: 1691 * A flag saying whether this is an intermediate 1692 * tuple or not. 1693 * 1694 * tuple_coords :: 1695 * The coordinates of the tuple in normalized axis 1696 * units. 1697 * 1698 * im_start_coords :: 1699 * The initial coordinates where this tuple starts 1700 * to apply (for intermediate coordinates). 1701 * 1702 * im_end_coords :: 1703 * The final coordinates after which this tuple no 1704 * longer applies (for intermediate coordinates). 1705 * 1706 * @Return: 1707 * An FT_Fixed value containing the scaling factor. 1708 */ 1709 static FT_Fixed ft_var_apply_tuple(GX_Blend blend,FT_UShort tupleIndex,FT_Fixed * tuple_coords,FT_Fixed * im_start_coords,FT_Fixed * im_end_coords)1710 ft_var_apply_tuple( GX_Blend blend, 1711 FT_UShort tupleIndex, 1712 FT_Fixed* tuple_coords, 1713 FT_Fixed* im_start_coords, 1714 FT_Fixed* im_end_coords ) 1715 { 1716 FT_UInt i; 1717 FT_Fixed apply = 0x10000L; 1718 1719 1720 for ( i = 0; i < blend->num_axis; i++ ) 1721 { 1722 FT_TRACE6(( " axis %d coordinate %.5f:\n", 1723 i, blend->normalizedcoords[i] / 65536.0 )); 1724 1725 /* It's not clear why (for intermediate tuples) we don't need */ 1726 /* to check against start/end -- the documentation says we don't. */ 1727 /* Similarly, it's unclear why we don't need to scale along the */ 1728 /* axis. */ 1729 1730 if ( tuple_coords[i] == 0 ) 1731 { 1732 FT_TRACE6(( " tuple coordinate is zero, ignore\n" )); 1733 continue; 1734 } 1735 1736 if ( blend->normalizedcoords[i] == 0 ) 1737 { 1738 FT_TRACE6(( " axis coordinate is zero, stop\n" )); 1739 apply = 0; 1740 break; 1741 } 1742 1743 if ( blend->normalizedcoords[i] == tuple_coords[i] ) 1744 { 1745 FT_TRACE6(( " tuple coordinate %.5f fits perfectly\n", 1746 tuple_coords[i] / 65536.0 )); 1747 /* `apply' does not change */ 1748 continue; 1749 } 1750 1751 if ( !( tupleIndex & GX_TI_INTERMEDIATE_TUPLE ) ) 1752 { 1753 /* not an intermediate tuple */ 1754 1755 if ( blend->normalizedcoords[i] < FT_MIN( 0, tuple_coords[i] ) || 1756 blend->normalizedcoords[i] > FT_MAX( 0, tuple_coords[i] ) ) 1757 { 1758 FT_TRACE6(( " tuple coordinate %.5f is exceeded, stop\n", 1759 tuple_coords[i] / 65536.0 )); 1760 apply = 0; 1761 break; 1762 } 1763 1764 FT_TRACE6(( " tuple coordinate %.5f fits\n", 1765 tuple_coords[i] / 65536.0 )); 1766 apply = FT_MulDiv( apply, 1767 blend->normalizedcoords[i], 1768 tuple_coords[i] ); 1769 } 1770 else 1771 { 1772 /* intermediate tuple */ 1773 1774 if ( blend->normalizedcoords[i] <= im_start_coords[i] || 1775 blend->normalizedcoords[i] >= im_end_coords[i] ) 1776 { 1777 FT_TRACE6(( " intermediate tuple range ]%.5f;%.5f[ is exceeded," 1778 " stop\n", 1779 im_start_coords[i] / 65536.0, 1780 im_end_coords[i] / 65536.0 )); 1781 apply = 0; 1782 break; 1783 } 1784 1785 FT_TRACE6(( " intermediate tuple range ]%.5f;%.5f[ fits\n", 1786 im_start_coords[i] / 65536.0, 1787 im_end_coords[i] / 65536.0 )); 1788 if ( blend->normalizedcoords[i] < tuple_coords[i] ) 1789 apply = FT_MulDiv( apply, 1790 blend->normalizedcoords[i] - im_start_coords[i], 1791 tuple_coords[i] - im_start_coords[i] ); 1792 else 1793 apply = FT_MulDiv( apply, 1794 im_end_coords[i] - blend->normalizedcoords[i], 1795 im_end_coords[i] - tuple_coords[i] ); 1796 } 1797 } 1798 1799 FT_TRACE6(( " apply factor is %.5f\n", apply / 65536.0 )); 1800 1801 return apply; 1802 } 1803 1804 1805 /* convert from design coordinates to normalized coordinates */ 1806 1807 static void ft_var_to_normalized(TT_Face face,FT_UInt num_coords,FT_Fixed * coords,FT_Fixed * normalized)1808 ft_var_to_normalized( TT_Face face, 1809 FT_UInt num_coords, 1810 FT_Fixed* coords, 1811 FT_Fixed* normalized ) 1812 { 1813 GX_Blend blend; 1814 FT_MM_Var* mmvar; 1815 FT_UInt i, j; 1816 FT_Var_Axis* a; 1817 GX_AVarSegment av; 1818 1819 1820 blend = face->blend; 1821 mmvar = blend->mmvar; 1822 1823 if ( num_coords > mmvar->num_axis ) 1824 { 1825 FT_TRACE2(( "ft_var_to_normalized:" 1826 " only using first %d of %d coordinates\n", 1827 mmvar->num_axis, num_coords )); 1828 num_coords = mmvar->num_axis; 1829 } 1830 1831 /* Axis normalization is a two-stage process. First we normalize */ 1832 /* based on the [min,def,max] values for the axis to be [-1,0,1]. */ 1833 /* Then, if there's an `avar' table, we renormalize this range. */ 1834 1835 a = mmvar->axis; 1836 for ( i = 0; i < num_coords; i++, a++ ) 1837 { 1838 FT_Fixed coord = coords[i]; 1839 1840 1841 FT_TRACE5(( " %d: %.5f\n", i, coord / 65536.0 )); 1842 if ( coord > a->maximum || coord < a->minimum ) 1843 { 1844 FT_TRACE1(( 1845 "ft_var_to_normalized: design coordinate %.5f\n" 1846 " is out of range [%.5f;%.5f]; clamping\n", 1847 coord / 65536.0, 1848 a->minimum / 65536.0, 1849 a->maximum / 65536.0 )); 1850 1851 if ( coord > a->maximum ) 1852 coord = a->maximum; 1853 else 1854 coord = a->minimum; 1855 } 1856 1857 if ( coord < a->def ) 1858 normalized[i] = -FT_DivFix( SUB_LONG( coord, a->def ), 1859 SUB_LONG( a->minimum, a->def ) ); 1860 else if ( coord > a->def ) 1861 normalized[i] = FT_DivFix( SUB_LONG( coord, a->def ), 1862 SUB_LONG( a->maximum, a->def ) ); 1863 else 1864 normalized[i] = 0; 1865 } 1866 1867 FT_TRACE5(( "\n" )); 1868 1869 for ( ; i < mmvar->num_axis; i++ ) 1870 normalized[i] = 0; 1871 1872 if ( blend->avar_segment ) 1873 { 1874 FT_TRACE5(( "normalized design coordinates" 1875 " before applying `avar' data:\n" )); 1876 1877 av = blend->avar_segment; 1878 for ( i = 0; i < mmvar->num_axis; i++, av++ ) 1879 { 1880 for ( j = 1; j < (FT_UInt)av->pairCount; j++ ) 1881 { 1882 if ( normalized[i] < av->correspondence[j].fromCoord ) 1883 { 1884 FT_TRACE5(( " %.5f\n", normalized[i] / 65536.0 )); 1885 1886 normalized[i] = 1887 FT_MulDiv( normalized[i] - av->correspondence[j - 1].fromCoord, 1888 av->correspondence[j].toCoord - 1889 av->correspondence[j - 1].toCoord, 1890 av->correspondence[j].fromCoord - 1891 av->correspondence[j - 1].fromCoord ) + 1892 av->correspondence[j - 1].toCoord; 1893 break; 1894 } 1895 } 1896 } 1897 } 1898 } 1899 1900 1901 /* convert from normalized coordinates to design coordinates */ 1902 1903 static void ft_var_to_design(TT_Face face,FT_UInt num_coords,FT_Fixed * coords,FT_Fixed * design)1904 ft_var_to_design( TT_Face face, 1905 FT_UInt num_coords, 1906 FT_Fixed* coords, 1907 FT_Fixed* design ) 1908 { 1909 GX_Blend blend; 1910 FT_MM_Var* mmvar; 1911 FT_Var_Axis* a; 1912 1913 FT_UInt i, j, nc; 1914 1915 1916 blend = face->blend; 1917 1918 nc = num_coords; 1919 if ( num_coords > blend->num_axis ) 1920 { 1921 FT_TRACE2(( "ft_var_to_design:" 1922 " only using first %d of %d coordinates\n", 1923 blend->num_axis, num_coords )); 1924 nc = blend->num_axis; 1925 } 1926 1927 for ( i = 0; i < nc; i++ ) 1928 design[i] = coords[i]; 1929 1930 for ( ; i < num_coords; i++ ) 1931 design[i] = 0; 1932 1933 if ( blend->avar_segment ) 1934 { 1935 GX_AVarSegment av = blend->avar_segment; 1936 1937 1938 FT_TRACE5(( "design coordinates" 1939 " after removing `avar' distortion:\n" )); 1940 1941 for ( i = 0; i < nc; i++, av++ ) 1942 { 1943 for ( j = 1; j < (FT_UInt)av->pairCount; j++ ) 1944 { 1945 if ( design[i] < av->correspondence[j].toCoord ) 1946 { 1947 design[i] = 1948 FT_MulDiv( design[i] - av->correspondence[j - 1].toCoord, 1949 av->correspondence[j].fromCoord - 1950 av->correspondence[j - 1].fromCoord, 1951 av->correspondence[j].toCoord - 1952 av->correspondence[j - 1].toCoord ) + 1953 av->correspondence[j - 1].fromCoord; 1954 1955 FT_TRACE5(( " %.5f\n", design[i] / 65536.0 )); 1956 break; 1957 } 1958 } 1959 } 1960 } 1961 1962 mmvar = blend->mmvar; 1963 a = mmvar->axis; 1964 1965 for ( i = 0; i < nc; i++, a++ ) 1966 { 1967 if ( design[i] < 0 ) 1968 design[i] = a->def + FT_MulFix( design[i], 1969 a->def - a->minimum ); 1970 else if ( design[i] > 0 ) 1971 design[i] = a->def + FT_MulFix( design[i], 1972 a->maximum - a->def ); 1973 else 1974 design[i] = a->def; 1975 } 1976 } 1977 1978 1979 /*************************************************************************/ 1980 /*************************************************************************/ 1981 /***** *****/ 1982 /***** MULTIPLE MASTERS SERVICE FUNCTIONS *****/ 1983 /***** *****/ 1984 /*************************************************************************/ 1985 /*************************************************************************/ 1986 1987 1988 typedef struct GX_FVar_Head_ 1989 { 1990 FT_Long version; 1991 FT_UShort offsetToData; 1992 FT_UShort axisCount; 1993 FT_UShort axisSize; 1994 FT_UShort instanceCount; 1995 FT_UShort instanceSize; 1996 1997 } GX_FVar_Head; 1998 1999 2000 typedef struct fvar_axis_ 2001 { 2002 FT_ULong axisTag; 2003 FT_Fixed minValue; 2004 FT_Fixed defaultValue; 2005 FT_Fixed maxValue; 2006 FT_UShort flags; 2007 FT_UShort nameID; 2008 2009 } GX_FVar_Axis; 2010 2011 2012 /************************************************************************** 2013 * 2014 * @Function: 2015 * TT_Get_MM_Var 2016 * 2017 * @Description: 2018 * Check that the font's `fvar' table is valid, parse it, and return 2019 * those data. It also loads (and parses) the `MVAR' table, if 2020 * possible. 2021 * 2022 * @InOut: 2023 * face :: 2024 * The font face. 2025 * TT_Get_MM_Var initializes the blend structure. 2026 * 2027 * @Output: 2028 * master :: 2029 * The `fvar' data (must be freed by caller). Can be NULL, 2030 * which makes this function simply load MM support. 2031 * 2032 * @Return: 2033 * FreeType error code. 0 means success. 2034 */ 2035 FT_LOCAL_DEF( FT_Error ) TT_Get_MM_Var(TT_Face face,FT_MM_Var ** master)2036 TT_Get_MM_Var( TT_Face face, 2037 FT_MM_Var* *master ) 2038 { 2039 FT_Stream stream = face->root.stream; 2040 FT_Memory memory = face->root.memory; 2041 FT_ULong table_len; 2042 FT_Error error = FT_Err_Ok; 2043 FT_ULong fvar_start = 0; 2044 FT_UInt i, j; 2045 FT_MM_Var* mmvar = NULL; 2046 FT_Fixed* next_coords; 2047 FT_Fixed* nsc; 2048 FT_String* next_name; 2049 FT_Var_Axis* a; 2050 FT_Fixed* c; 2051 FT_Var_Named_Style* ns; 2052 GX_FVar_Head fvar_head; 2053 FT_Bool usePsName = 0; 2054 FT_UInt num_instances; 2055 FT_UInt num_axes; 2056 FT_UShort* axis_flags; 2057 2058 FT_Offset mmvar_size; 2059 FT_Offset axis_flags_size; 2060 FT_Offset axis_size; 2061 FT_Offset namedstyle_size; 2062 FT_Offset next_coords_size; 2063 FT_Offset next_name_size; 2064 2065 FT_Bool need_init; 2066 2067 static const FT_Frame_Field fvar_fields[] = 2068 { 2069 2070 #undef FT_STRUCTURE 2071 #define FT_STRUCTURE GX_FVar_Head 2072 2073 FT_FRAME_START( 16 ), 2074 FT_FRAME_LONG ( version ), 2075 FT_FRAME_USHORT ( offsetToData ), 2076 FT_FRAME_SKIP_SHORT, 2077 FT_FRAME_USHORT ( axisCount ), 2078 FT_FRAME_USHORT ( axisSize ), 2079 FT_FRAME_USHORT ( instanceCount ), 2080 FT_FRAME_USHORT ( instanceSize ), 2081 FT_FRAME_END 2082 }; 2083 2084 static const FT_Frame_Field fvaraxis_fields[] = 2085 { 2086 2087 #undef FT_STRUCTURE 2088 #define FT_STRUCTURE GX_FVar_Axis 2089 2090 FT_FRAME_START( 20 ), 2091 FT_FRAME_ULONG ( axisTag ), 2092 FT_FRAME_LONG ( minValue ), 2093 FT_FRAME_LONG ( defaultValue ), 2094 FT_FRAME_LONG ( maxValue ), 2095 FT_FRAME_USHORT( flags ), 2096 FT_FRAME_USHORT( nameID ), 2097 FT_FRAME_END 2098 }; 2099 2100 2101 /* read the font data and set up the internal representation */ 2102 /* if not already done */ 2103 2104 need_init = !face->blend; 2105 2106 if ( need_init ) 2107 { 2108 FT_TRACE2(( "FVAR " )); 2109 2110 /* both `fvar' and `gvar' must be present */ 2111 if ( FT_SET_ERROR( face->goto_table( face, TTAG_gvar, 2112 stream, &table_len ) ) ) 2113 { 2114 /* CFF2 is an alternate to gvar here */ 2115 if ( FT_SET_ERROR( face->goto_table( face, TTAG_CFF2, 2116 stream, &table_len ) ) ) 2117 { 2118 FT_TRACE1(( "\n" 2119 "TT_Get_MM_Var: `gvar' or `CFF2' table is missing\n" )); 2120 goto Exit; 2121 } 2122 } 2123 2124 if ( FT_SET_ERROR( face->goto_table( face, TTAG_fvar, 2125 stream, &table_len ) ) ) 2126 { 2127 FT_TRACE1(( "is missing\n" )); 2128 goto Exit; 2129 } 2130 2131 fvar_start = FT_STREAM_POS( ); 2132 2133 /* the validity of the `fvar' header data was already checked */ 2134 /* in function `sfnt_init_face' */ 2135 if ( FT_STREAM_READ_FIELDS( fvar_fields, &fvar_head ) ) 2136 goto Exit; 2137 2138 usePsName = FT_BOOL( fvar_head.instanceSize == 2139 6 + 4 * fvar_head.axisCount ); 2140 2141 FT_TRACE2(( "loaded\n" )); 2142 2143 FT_TRACE5(( "%d variation ax%s\n", 2144 fvar_head.axisCount, 2145 fvar_head.axisCount == 1 ? "is" : "es" )); 2146 2147 if ( FT_NEW( face->blend ) ) 2148 goto Exit; 2149 2150 num_axes = fvar_head.axisCount; 2151 face->blend->num_axis = num_axes; 2152 } 2153 else 2154 num_axes = face->blend->num_axis; 2155 2156 /* `num_instances' holds the number of all named instances, */ 2157 /* including the default instance which might be missing */ 2158 /* in fvar's table of named instances */ 2159 num_instances = (FT_UInt)face->root.style_flags >> 16; 2160 2161 /* prepare storage area for MM data; this cannot overflow */ 2162 /* 32-bit arithmetic because of the size limits used in the */ 2163 /* `fvar' table validity check in `sfnt_init_face' */ 2164 2165 /* the various `*_size' variables, which we also use as */ 2166 /* offsets into the `mmvar' array, must be multiples of the */ 2167 /* pointer size (except the last one); without such an */ 2168 /* alignment there might be runtime errors due to */ 2169 /* misaligned addresses */ 2170 #undef ALIGN_SIZE 2171 #define ALIGN_SIZE( n ) \ 2172 ( ( (n) + sizeof (void*) - 1 ) & ~( sizeof (void*) - 1 ) ) 2173 2174 mmvar_size = ALIGN_SIZE( sizeof ( FT_MM_Var ) ); 2175 axis_flags_size = ALIGN_SIZE( num_axes * 2176 sizeof ( FT_UShort ) ); 2177 axis_size = ALIGN_SIZE( num_axes * 2178 sizeof ( FT_Var_Axis ) ); 2179 namedstyle_size = ALIGN_SIZE( num_instances * 2180 sizeof ( FT_Var_Named_Style ) ); 2181 next_coords_size = ALIGN_SIZE( num_instances * 2182 num_axes * 2183 sizeof ( FT_Fixed ) ); 2184 next_name_size = num_axes * 5; 2185 2186 if ( need_init ) 2187 { 2188 face->blend->mmvar_len = mmvar_size + 2189 axis_flags_size + 2190 axis_size + 2191 namedstyle_size + 2192 next_coords_size + 2193 next_name_size; 2194 2195 if ( FT_ALLOC( mmvar, face->blend->mmvar_len ) ) 2196 goto Exit; 2197 face->blend->mmvar = mmvar; 2198 2199 /* set up pointers and offsets into the `mmvar' array; */ 2200 /* the data gets filled in later on */ 2201 2202 mmvar->num_axis = 2203 num_axes; 2204 mmvar->num_designs = 2205 ~0U; /* meaningless in this context; each glyph */ 2206 /* may have a different number of designs */ 2207 /* (or tuples, as called by Apple) */ 2208 mmvar->num_namedstyles = 2209 num_instances; 2210 2211 /* alas, no public field in `FT_Var_Axis' for axis flags */ 2212 axis_flags = 2213 (FT_UShort*)( (char*)mmvar + mmvar_size ); 2214 mmvar->axis = 2215 (FT_Var_Axis*)( (char*)axis_flags + axis_flags_size ); 2216 mmvar->namedstyle = 2217 (FT_Var_Named_Style*)( (char*)mmvar->axis + axis_size ); 2218 2219 next_coords = (FT_Fixed*)( (char*)mmvar->namedstyle + 2220 namedstyle_size ); 2221 for ( i = 0; i < num_instances; i++ ) 2222 { 2223 mmvar->namedstyle[i].coords = next_coords; 2224 next_coords += num_axes; 2225 } 2226 2227 next_name = (FT_String*)( (char*)mmvar->namedstyle + 2228 namedstyle_size + next_coords_size ); 2229 for ( i = 0; i < num_axes; i++ ) 2230 { 2231 mmvar->axis[i].name = next_name; 2232 next_name += 5; 2233 } 2234 2235 /* now fill in the data */ 2236 2237 if ( FT_STREAM_SEEK( fvar_start + fvar_head.offsetToData ) ) 2238 goto Exit; 2239 2240 a = mmvar->axis; 2241 for ( i = 0; i < num_axes; i++ ) 2242 { 2243 GX_FVar_Axis axis_rec; 2244 2245 #ifdef FT_DEBUG_LEVEL_TRACE 2246 int invalid = 0; 2247 #endif 2248 2249 2250 if ( FT_STREAM_READ_FIELDS( fvaraxis_fields, &axis_rec ) ) 2251 goto Exit; 2252 a->tag = axis_rec.axisTag; 2253 a->minimum = axis_rec.minValue; 2254 a->def = axis_rec.defaultValue; 2255 a->maximum = axis_rec.maxValue; 2256 a->strid = axis_rec.nameID; 2257 2258 a->name[0] = (FT_String)( a->tag >> 24 ); 2259 a->name[1] = (FT_String)( ( a->tag >> 16 ) & 0xFF ); 2260 a->name[2] = (FT_String)( ( a->tag >> 8 ) & 0xFF ); 2261 a->name[3] = (FT_String)( ( a->tag ) & 0xFF ); 2262 a->name[4] = '\0'; 2263 2264 *axis_flags = axis_rec.flags; 2265 2266 if ( a->minimum > a->def || 2267 a->def > a->maximum ) 2268 { 2269 a->minimum = a->def; 2270 a->maximum = a->def; 2271 2272 #ifdef FT_DEBUG_LEVEL_TRACE 2273 invalid = 1; 2274 #endif 2275 } 2276 2277 #ifdef FT_DEBUG_LEVEL_TRACE 2278 if ( i == 0 ) 2279 FT_TRACE5(( " idx tag " 2280 /* " XXX `XXXX'" */ 2281 " minimum default maximum flags\n" )); 2282 /* " XXXX.XXXXX XXXX.XXXXX XXXX.XXXXX 0xXXXX" */ 2283 2284 FT_TRACE5(( " %3d `%s'" 2285 " %10.5f %10.5f %10.5f 0x%04X%s\n", 2286 i, 2287 a->name, 2288 a->minimum / 65536.0, 2289 a->def / 65536.0, 2290 a->maximum / 65536.0, 2291 *axis_flags, 2292 invalid ? " (invalid, disabled)" : "" )); 2293 #endif 2294 2295 a++; 2296 axis_flags++; 2297 } 2298 2299 FT_TRACE5(( "\n" )); 2300 2301 /* named instance coordinates are stored as design coordinates; */ 2302 /* we have to convert them to normalized coordinates also */ 2303 if ( FT_NEW_ARRAY( face->blend->normalized_stylecoords, 2304 num_axes * num_instances ) ) 2305 goto Exit; 2306 2307 if ( fvar_head.instanceCount && !face->blend->avar_loaded ) 2308 { 2309 FT_ULong offset = FT_STREAM_POS(); 2310 2311 2312 ft_var_load_avar( face ); 2313 2314 if ( FT_STREAM_SEEK( offset ) ) 2315 goto Exit; 2316 } 2317 2318 FT_TRACE5(( "%d instance%s\n", 2319 fvar_head.instanceCount, 2320 fvar_head.instanceCount == 1 ? "" : "s" )); 2321 2322 ns = mmvar->namedstyle; 2323 nsc = face->blend->normalized_stylecoords; 2324 for ( i = 0; i < fvar_head.instanceCount; i++, ns++ ) 2325 { 2326 /* PostScript names add 2 bytes to the instance record size */ 2327 if ( FT_FRAME_ENTER( ( usePsName ? 6L : 4L ) + 2328 4L * num_axes ) ) 2329 goto Exit; 2330 2331 ns->strid = FT_GET_USHORT(); 2332 (void) /* flags = */ FT_GET_USHORT(); 2333 2334 c = ns->coords; 2335 for ( j = 0; j < num_axes; j++, c++ ) 2336 *c = FT_GET_LONG(); 2337 2338 /* valid psid values are 6, [256;32767], and 0xFFFF */ 2339 if ( usePsName ) 2340 ns->psid = FT_GET_USHORT(); 2341 else 2342 ns->psid = 0xFFFF; 2343 2344 #ifdef FT_DEBUG_LEVEL_TRACE 2345 { 2346 SFNT_Service sfnt = (SFNT_Service)face->sfnt; 2347 2348 FT_String* strname = NULL; 2349 FT_String* psname = NULL; 2350 2351 FT_ULong pos; 2352 2353 2354 pos = FT_STREAM_POS(); 2355 2356 if ( ns->strid != 0xFFFF ) 2357 { 2358 (void)sfnt->get_name( face, 2359 (FT_UShort)ns->strid, 2360 &strname ); 2361 if ( strname && !ft_strcmp( strname, ".notdef" ) ) 2362 strname = NULL; 2363 } 2364 2365 if ( ns->psid != 0xFFFF ) 2366 { 2367 (void)sfnt->get_name( face, 2368 (FT_UShort)ns->psid, 2369 &psname ); 2370 if ( psname && !ft_strcmp( psname, ".notdef" ) ) 2371 psname = NULL; 2372 } 2373 2374 (void)FT_STREAM_SEEK( pos ); 2375 2376 FT_TRACE5(( " instance %d (%s%s%s, %s%s%s)\n", 2377 i, 2378 strname ? "name: `" : "", 2379 strname ? strname : "unnamed", 2380 strname ? "'" : "", 2381 psname ? "PS name: `" : "", 2382 psname ? psname : "no PS name", 2383 psname ? "'" : "" )); 2384 2385 FT_FREE( strname ); 2386 FT_FREE( psname ); 2387 } 2388 #endif /* FT_DEBUG_LEVEL_TRACE */ 2389 2390 ft_var_to_normalized( face, num_axes, ns->coords, nsc ); 2391 nsc += num_axes; 2392 2393 FT_FRAME_EXIT(); 2394 } 2395 2396 if ( num_instances != fvar_head.instanceCount ) 2397 { 2398 SFNT_Service sfnt = (SFNT_Service)face->sfnt; 2399 2400 FT_Int found, dummy1, dummy2; 2401 FT_UInt strid = ~0U; 2402 2403 2404 /* the default instance is missing in array the */ 2405 /* of named instances; try to synthesize an entry */ 2406 found = sfnt->get_name_id( face, 2407 TT_NAME_ID_TYPOGRAPHIC_SUBFAMILY, 2408 &dummy1, 2409 &dummy2 ); 2410 if ( found ) 2411 strid = TT_NAME_ID_TYPOGRAPHIC_SUBFAMILY; 2412 else 2413 { 2414 found = sfnt->get_name_id( face, 2415 TT_NAME_ID_FONT_SUBFAMILY, 2416 &dummy1, 2417 &dummy2 ); 2418 if ( found ) 2419 strid = TT_NAME_ID_FONT_SUBFAMILY; 2420 } 2421 2422 if ( found ) 2423 { 2424 found = sfnt->get_name_id( face, 2425 TT_NAME_ID_PS_NAME, 2426 &dummy1, 2427 &dummy2 ); 2428 if ( found ) 2429 { 2430 FT_TRACE5(( "TT_Get_MM_Var:" 2431 " Adding default instance to named instances\n" )); 2432 2433 ns = &mmvar->namedstyle[fvar_head.instanceCount]; 2434 2435 ns->strid = strid; 2436 ns->psid = TT_NAME_ID_PS_NAME; 2437 2438 a = mmvar->axis; 2439 c = ns->coords; 2440 for ( j = 0; j < num_axes; j++, a++, c++ ) 2441 *c = a->def; 2442 } 2443 } 2444 } 2445 2446 ft_var_load_mvar( face ); 2447 } 2448 2449 /* fill the output array if requested */ 2450 2451 if ( master ) 2452 { 2453 FT_UInt n; 2454 2455 2456 if ( FT_ALLOC( mmvar, face->blend->mmvar_len ) ) 2457 goto Exit; 2458 FT_MEM_COPY( mmvar, face->blend->mmvar, face->blend->mmvar_len ); 2459 2460 axis_flags = 2461 (FT_UShort*)( (char*)mmvar + mmvar_size ); 2462 mmvar->axis = 2463 (FT_Var_Axis*)( (char*)axis_flags + axis_flags_size ); 2464 mmvar->namedstyle = 2465 (FT_Var_Named_Style*)( (char*)mmvar->axis+ axis_size ); 2466 2467 next_coords = (FT_Fixed*)( (char*)mmvar->namedstyle + 2468 namedstyle_size ); 2469 for ( n = 0; n < mmvar->num_namedstyles; n++ ) 2470 { 2471 mmvar->namedstyle[n].coords = next_coords; 2472 next_coords += num_axes; 2473 } 2474 2475 a = mmvar->axis; 2476 next_name = (FT_String*)( (char*)mmvar->namedstyle + 2477 namedstyle_size + next_coords_size ); 2478 for ( n = 0; n < num_axes; n++ ) 2479 { 2480 a->name = next_name; 2481 2482 /* standard PostScript names for some standard apple tags */ 2483 if ( a->tag == TTAG_wght ) 2484 a->name = (char*)"Weight"; 2485 else if ( a->tag == TTAG_wdth ) 2486 a->name = (char*)"Width"; 2487 else if ( a->tag == TTAG_opsz ) 2488 a->name = (char*)"OpticalSize"; 2489 else if ( a->tag == TTAG_slnt ) 2490 a->name = (char*)"Slant"; 2491 2492 next_name += 5; 2493 a++; 2494 } 2495 2496 *master = mmvar; 2497 } 2498 2499 Exit: 2500 return error; 2501 } 2502 2503 2504 static FT_Error tt_set_mm_blend(TT_Face face,FT_UInt num_coords,FT_Fixed * coords,FT_Bool set_design_coords)2505 tt_set_mm_blend( TT_Face face, 2506 FT_UInt num_coords, 2507 FT_Fixed* coords, 2508 FT_Bool set_design_coords ) 2509 { 2510 FT_Error error = FT_Err_Ok; 2511 GX_Blend blend; 2512 FT_MM_Var* mmvar; 2513 FT_UInt i; 2514 2515 FT_Bool all_design_coords = FALSE; 2516 2517 FT_Memory memory = face->root.memory; 2518 2519 enum 2520 { 2521 mcvt_retain, 2522 mcvt_modify, 2523 mcvt_load 2524 2525 } manageCvt; 2526 2527 2528 face->doblend = FALSE; 2529 2530 if ( !face->blend ) 2531 { 2532 if ( FT_SET_ERROR( TT_Get_MM_Var( face, NULL ) ) ) 2533 goto Exit; 2534 } 2535 2536 blend = face->blend; 2537 mmvar = blend->mmvar; 2538 2539 if ( num_coords > mmvar->num_axis ) 2540 { 2541 FT_TRACE2(( "TT_Set_MM_Blend:" 2542 " only using first %d of %d coordinates\n", 2543 mmvar->num_axis, num_coords )); 2544 num_coords = mmvar->num_axis; 2545 } 2546 2547 FT_TRACE5(( "TT_Set_MM_Blend:\n" 2548 " normalized design coordinates:\n" )); 2549 2550 for ( i = 0; i < num_coords; i++ ) 2551 { 2552 FT_TRACE5(( " %.5f\n", coords[i] / 65536.0 )); 2553 if ( coords[i] < -0x00010000L || coords[i] > 0x00010000L ) 2554 { 2555 FT_TRACE1(( "TT_Set_MM_Blend: normalized design coordinate %.5f\n" 2556 " is out of range [-1;1]\n", 2557 coords[i] / 65536.0 )); 2558 error = FT_THROW( Invalid_Argument ); 2559 goto Exit; 2560 } 2561 } 2562 2563 FT_TRACE5(( "\n" )); 2564 2565 if ( !face->is_cff2 && !blend->glyphoffsets ) 2566 if ( FT_SET_ERROR( ft_var_load_gvar( face ) ) ) 2567 goto Exit; 2568 2569 if ( !blend->coords ) 2570 { 2571 if ( FT_NEW_ARRAY( blend->coords, mmvar->num_axis ) ) 2572 goto Exit; 2573 2574 /* the first time we have to compute all design coordinates */ 2575 all_design_coords = TRUE; 2576 } 2577 2578 if ( !blend->normalizedcoords ) 2579 { 2580 if ( FT_NEW_ARRAY( blend->normalizedcoords, mmvar->num_axis ) ) 2581 goto Exit; 2582 2583 manageCvt = mcvt_modify; 2584 2585 /* If we have not set the blend coordinates before this, then the */ 2586 /* cvt table will still be what we read from the `cvt ' table and */ 2587 /* we don't need to reload it. We may need to change it though... */ 2588 } 2589 else 2590 { 2591 FT_Bool have_diff = 0; 2592 FT_UInt j; 2593 FT_Fixed* c; 2594 FT_Fixed* n; 2595 2596 2597 manageCvt = mcvt_retain; 2598 2599 for ( i = 0; i < num_coords; i++ ) 2600 { 2601 if ( blend->normalizedcoords[i] != coords[i] ) 2602 { 2603 manageCvt = mcvt_load; 2604 have_diff = 1; 2605 break; 2606 } 2607 } 2608 2609 if ( FT_IS_NAMED_INSTANCE( FT_FACE( face ) ) ) 2610 { 2611 FT_UInt instance_index = (FT_UInt)face->root.face_index >> 16; 2612 2613 2614 c = blend->normalizedcoords + i; 2615 n = blend->normalized_stylecoords + 2616 ( instance_index - 1 ) * mmvar->num_axis + 2617 i; 2618 2619 for ( j = i; j < mmvar->num_axis; j++, n++, c++ ) 2620 if ( *c != *n ) 2621 have_diff = 1; 2622 } 2623 else 2624 { 2625 c = blend->normalizedcoords + i; 2626 for ( j = i; j < mmvar->num_axis; j++, c++ ) 2627 if ( *c != 0 ) 2628 have_diff = 1; 2629 } 2630 2631 /* return value -1 indicates `no change' */ 2632 if ( !have_diff ) 2633 { 2634 face->doblend = TRUE; 2635 2636 return -1; 2637 } 2638 2639 for ( ; i < mmvar->num_axis; i++ ) 2640 { 2641 if ( blend->normalizedcoords[i] != 0 ) 2642 { 2643 manageCvt = mcvt_load; 2644 break; 2645 } 2646 } 2647 2648 /* If we don't change the blend coords then we don't need to do */ 2649 /* anything to the cvt table. It will be correct. Otherwise we */ 2650 /* no longer have the original cvt (it was modified when we set */ 2651 /* the blend last time), so we must reload and then modify it. */ 2652 } 2653 2654 blend->num_axis = mmvar->num_axis; 2655 FT_MEM_COPY( blend->normalizedcoords, 2656 coords, 2657 num_coords * sizeof ( FT_Fixed ) ); 2658 2659 if ( set_design_coords ) 2660 ft_var_to_design( face, 2661 all_design_coords ? blend->num_axis : num_coords, 2662 blend->normalizedcoords, 2663 blend->coords ); 2664 2665 face->doblend = TRUE; 2666 2667 if ( face->cvt ) 2668 { 2669 switch ( manageCvt ) 2670 { 2671 case mcvt_load: 2672 /* The cvt table has been loaded already; every time we change the */ 2673 /* blend we may need to reload and remodify the cvt table. */ 2674 FT_FREE( face->cvt ); 2675 face->cvt = NULL; 2676 2677 error = tt_face_load_cvt( face, face->root.stream ); 2678 break; 2679 2680 case mcvt_modify: 2681 /* The original cvt table is in memory. All we need to do is */ 2682 /* apply the `cvar' table (if any). */ 2683 error = tt_face_vary_cvt( face, face->root.stream ); 2684 break; 2685 2686 case mcvt_retain: 2687 /* The cvt table is correct for this set of coordinates. */ 2688 break; 2689 } 2690 } 2691 2692 /* enforce recomputation of the PostScript name; */ 2693 FT_FREE( face->postscript_name ); 2694 face->postscript_name = NULL; 2695 2696 Exit: 2697 return error; 2698 } 2699 2700 2701 /************************************************************************** 2702 * 2703 * @Function: 2704 * TT_Set_MM_Blend 2705 * 2706 * @Description: 2707 * Set the blend (normalized) coordinates for this instance of the 2708 * font. Check that the `gvar' table is reasonable and does some 2709 * initial preparation. 2710 * 2711 * @InOut: 2712 * face :: 2713 * The font. 2714 * Initialize the blend structure with `gvar' data. 2715 * 2716 * @Input: 2717 * num_coords :: 2718 * The number of available coordinates. If it is 2719 * larger than the number of axes, ignore the excess 2720 * values. If it is smaller than the number of axes, 2721 * use the default value (0) for the remaining axes. 2722 * 2723 * coords :: 2724 * An array of `num_coords', each between [-1,1]. 2725 * 2726 * @Return: 2727 * FreeType error code. 0 means success. 2728 */ 2729 FT_LOCAL_DEF( FT_Error ) TT_Set_MM_Blend(TT_Face face,FT_UInt num_coords,FT_Fixed * coords)2730 TT_Set_MM_Blend( TT_Face face, 2731 FT_UInt num_coords, 2732 FT_Fixed* coords ) 2733 { 2734 FT_Error error; 2735 2736 2737 error = tt_set_mm_blend( face, num_coords, coords, 1 ); 2738 if ( error ) 2739 return error; 2740 2741 if ( num_coords ) 2742 face->root.face_flags |= FT_FACE_FLAG_VARIATION; 2743 else 2744 face->root.face_flags &= ~FT_FACE_FLAG_VARIATION; 2745 2746 return FT_Err_Ok; 2747 } 2748 2749 2750 /************************************************************************** 2751 * 2752 * @Function: 2753 * TT_Get_MM_Blend 2754 * 2755 * @Description: 2756 * Get the blend (normalized) coordinates for this instance of the 2757 * font. 2758 * 2759 * @InOut: 2760 * face :: 2761 * The font. 2762 * Initialize the blend structure with `gvar' data. 2763 * 2764 * @Input: 2765 * num_coords :: 2766 * The number of available coordinates. If it is 2767 * larger than the number of axes, set the excess 2768 * values to 0. 2769 * 2770 * coords :: 2771 * An array of `num_coords', each between [-1,1]. 2772 * 2773 * @Return: 2774 * FreeType error code. 0 means success. 2775 */ 2776 FT_LOCAL_DEF( FT_Error ) TT_Get_MM_Blend(TT_Face face,FT_UInt num_coords,FT_Fixed * coords)2777 TT_Get_MM_Blend( TT_Face face, 2778 FT_UInt num_coords, 2779 FT_Fixed* coords ) 2780 { 2781 FT_Error error = FT_Err_Ok; 2782 GX_Blend blend; 2783 FT_UInt i, nc; 2784 2785 2786 if ( !face->blend ) 2787 { 2788 if ( FT_SET_ERROR( TT_Get_MM_Var( face, NULL ) ) ) 2789 return error; 2790 } 2791 2792 blend = face->blend; 2793 2794 if ( !blend->coords ) 2795 { 2796 /* select default instance coordinates */ 2797 /* if no instance is selected yet */ 2798 if ( FT_SET_ERROR( tt_set_mm_blend( face, 0, NULL, 1 ) ) ) 2799 return error; 2800 } 2801 2802 nc = num_coords; 2803 if ( num_coords > blend->num_axis ) 2804 { 2805 FT_TRACE2(( "TT_Get_MM_Blend:" 2806 " only using first %d of %d coordinates\n", 2807 blend->num_axis, num_coords )); 2808 nc = blend->num_axis; 2809 } 2810 2811 if ( face->doblend ) 2812 { 2813 for ( i = 0; i < nc; i++ ) 2814 coords[i] = blend->normalizedcoords[i]; 2815 } 2816 else 2817 { 2818 for ( i = 0; i < nc; i++ ) 2819 coords[i] = 0; 2820 } 2821 2822 for ( ; i < num_coords; i++ ) 2823 coords[i] = 0; 2824 2825 return FT_Err_Ok; 2826 } 2827 2828 2829 /************************************************************************** 2830 * 2831 * @Function: 2832 * TT_Set_Var_Design 2833 * 2834 * @Description: 2835 * Set the coordinates for the instance, measured in the user 2836 * coordinate system. Parse the `avar' table (if present) to convert 2837 * from user to normalized coordinates. 2838 * 2839 * @InOut: 2840 * face :: 2841 * The font face. 2842 * Initialize the blend struct with `gvar' data. 2843 * 2844 * @Input: 2845 * num_coords :: 2846 * The number of available coordinates. If it is 2847 * larger than the number of axes, ignore the excess 2848 * values. If it is smaller than the number of axes, 2849 * use the default values for the remaining axes. 2850 * 2851 * coords :: 2852 * A coordinate array with `num_coords' elements. 2853 * 2854 * @Return: 2855 * FreeType error code. 0 means success. 2856 */ 2857 FT_LOCAL_DEF( FT_Error ) TT_Set_Var_Design(TT_Face face,FT_UInt num_coords,FT_Fixed * coords)2858 TT_Set_Var_Design( TT_Face face, 2859 FT_UInt num_coords, 2860 FT_Fixed* coords ) 2861 { 2862 FT_Error error = FT_Err_Ok; 2863 GX_Blend blend; 2864 FT_MM_Var* mmvar; 2865 FT_UInt i; 2866 FT_Memory memory = face->root.memory; 2867 2868 FT_Fixed* c; 2869 FT_Fixed* n; 2870 FT_Fixed* normalized = NULL; 2871 2872 FT_Bool have_diff = 0; 2873 2874 2875 if ( !face->blend ) 2876 { 2877 if ( FT_SET_ERROR( TT_Get_MM_Var( face, NULL ) ) ) 2878 goto Exit; 2879 } 2880 2881 blend = face->blend; 2882 mmvar = blend->mmvar; 2883 2884 if ( num_coords > mmvar->num_axis ) 2885 { 2886 FT_TRACE2(( "TT_Set_Var_Design:" 2887 " only using first %d of %d coordinates\n", 2888 mmvar->num_axis, num_coords )); 2889 num_coords = mmvar->num_axis; 2890 } 2891 2892 if ( !blend->coords ) 2893 { 2894 if ( FT_NEW_ARRAY( blend->coords, mmvar->num_axis ) ) 2895 goto Exit; 2896 } 2897 2898 c = blend->coords; 2899 n = coords; 2900 for ( i = 0; i < num_coords; i++, n++, c++ ) 2901 { 2902 if ( *c != *n ) 2903 { 2904 *c = *n; 2905 have_diff = 1; 2906 } 2907 } 2908 2909 if ( FT_IS_NAMED_INSTANCE( FT_FACE( face ) ) ) 2910 { 2911 FT_UInt instance_index; 2912 FT_Var_Named_Style* named_style; 2913 2914 2915 instance_index = (FT_UInt)face->root.face_index >> 16; 2916 named_style = mmvar->namedstyle + instance_index - 1; 2917 2918 n = named_style->coords + num_coords; 2919 for ( ; i < mmvar->num_axis; i++, n++, c++ ) 2920 { 2921 if ( *c != *n ) 2922 { 2923 *c = *n; 2924 have_diff = 1; 2925 } 2926 } 2927 } 2928 else 2929 { 2930 FT_Var_Axis* a; 2931 2932 2933 a = mmvar->axis + num_coords; 2934 for ( ; i < mmvar->num_axis; i++, a++, c++ ) 2935 { 2936 if ( *c != a->def ) 2937 { 2938 *c = a->def; 2939 have_diff = 1; 2940 } 2941 } 2942 } 2943 2944 /* return value -1 indicates `no change'; */ 2945 /* we can exit early if `normalizedcoords' is already computed */ 2946 if ( blend->normalizedcoords && !have_diff ) 2947 return -1; 2948 2949 if ( FT_NEW_ARRAY( normalized, mmvar->num_axis ) ) 2950 goto Exit; 2951 2952 if ( !face->blend->avar_loaded ) 2953 ft_var_load_avar( face ); 2954 2955 FT_TRACE5(( "TT_Set_Var_Design:\n" 2956 " normalized design coordinates:\n" )); 2957 ft_var_to_normalized( face, num_coords, blend->coords, normalized ); 2958 2959 error = tt_set_mm_blend( face, mmvar->num_axis, normalized, 0 ); 2960 if ( error ) 2961 goto Exit; 2962 2963 if ( num_coords ) 2964 face->root.face_flags |= FT_FACE_FLAG_VARIATION; 2965 else 2966 face->root.face_flags &= ~FT_FACE_FLAG_VARIATION; 2967 2968 Exit: 2969 FT_FREE( normalized ); 2970 return error; 2971 } 2972 2973 2974 /************************************************************************** 2975 * 2976 * @Function: 2977 * TT_Get_Var_Design 2978 * 2979 * @Description: 2980 * Get the design coordinates of the currently selected interpolated 2981 * font. 2982 * 2983 * @Input: 2984 * face :: 2985 * A handle to the source face. 2986 * 2987 * num_coords :: 2988 * The number of design coordinates to retrieve. If it 2989 * is larger than the number of axes, set the excess 2990 * values to~0. 2991 * 2992 * @Output: 2993 * coords :: 2994 * The design coordinates array. 2995 * 2996 * @Return: 2997 * FreeType error code. 0~means success. 2998 */ 2999 FT_LOCAL_DEF( FT_Error ) TT_Get_Var_Design(TT_Face face,FT_UInt num_coords,FT_Fixed * coords)3000 TT_Get_Var_Design( TT_Face face, 3001 FT_UInt num_coords, 3002 FT_Fixed* coords ) 3003 { 3004 FT_Error error = FT_Err_Ok; 3005 GX_Blend blend; 3006 FT_UInt i, nc; 3007 3008 3009 if ( !face->blend ) 3010 { 3011 if ( FT_SET_ERROR( TT_Get_MM_Var( face, NULL ) ) ) 3012 return error; 3013 } 3014 3015 blend = face->blend; 3016 3017 if ( !blend->coords ) 3018 { 3019 /* select default instance coordinates */ 3020 /* if no instance is selected yet */ 3021 if ( FT_SET_ERROR( tt_set_mm_blend( face, 0, NULL, 1 ) ) ) 3022 return error; 3023 } 3024 3025 nc = num_coords; 3026 if ( num_coords > blend->num_axis ) 3027 { 3028 FT_TRACE2(( "TT_Get_Var_Design:" 3029 " only using first %d of %d coordinates\n", 3030 blend->num_axis, num_coords )); 3031 nc = blend->num_axis; 3032 } 3033 3034 if ( face->doblend ) 3035 { 3036 for ( i = 0; i < nc; i++ ) 3037 coords[i] = blend->coords[i]; 3038 } 3039 else 3040 { 3041 for ( i = 0; i < nc; i++ ) 3042 coords[i] = 0; 3043 } 3044 3045 for ( ; i < num_coords; i++ ) 3046 coords[i] = 0; 3047 3048 return FT_Err_Ok; 3049 } 3050 3051 3052 /************************************************************************** 3053 * 3054 * @Function: 3055 * TT_Set_Named_Instance 3056 * 3057 * @Description: 3058 * Set the given named instance, also resetting any further 3059 * variation. 3060 * 3061 * @Input: 3062 * face :: 3063 * A handle to the source face. 3064 * 3065 * instance_index :: 3066 * The instance index, starting with value 1. 3067 * Value 0 indicates to not use an instance. 3068 * 3069 * @Return: 3070 * FreeType error code. 0~means success. 3071 */ 3072 FT_LOCAL_DEF( FT_Error ) TT_Set_Named_Instance(TT_Face face,FT_UInt instance_index)3073 TT_Set_Named_Instance( TT_Face face, 3074 FT_UInt instance_index ) 3075 { 3076 FT_Error error; 3077 GX_Blend blend; 3078 FT_MM_Var* mmvar; 3079 3080 FT_UInt num_instances; 3081 3082 3083 if ( !face->blend ) 3084 { 3085 if ( FT_SET_ERROR( TT_Get_MM_Var( face, NULL ) ) ) 3086 goto Exit; 3087 } 3088 3089 blend = face->blend; 3090 mmvar = blend->mmvar; 3091 3092 num_instances = (FT_UInt)face->root.style_flags >> 16; 3093 3094 /* `instance_index' starts with value 1, thus `>' */ 3095 if ( instance_index > num_instances ) 3096 { 3097 error = FT_ERR( Invalid_Argument ); 3098 goto Exit; 3099 } 3100 3101 if ( instance_index > 0 ) 3102 { 3103 FT_Memory memory = face->root.memory; 3104 SFNT_Service sfnt = (SFNT_Service)face->sfnt; 3105 3106 FT_Var_Named_Style* named_style; 3107 FT_String* style_name; 3108 3109 3110 named_style = mmvar->namedstyle + instance_index - 1; 3111 3112 error = sfnt->get_name( face, 3113 (FT_UShort)named_style->strid, 3114 &style_name ); 3115 if ( error ) 3116 goto Exit; 3117 3118 /* set (or replace) style name */ 3119 FT_FREE( face->root.style_name ); 3120 face->root.style_name = style_name; 3121 3122 /* finally, select the named instance */ 3123 error = TT_Set_Var_Design( face, 3124 mmvar->num_axis, 3125 named_style->coords ); 3126 if ( error ) 3127 { 3128 /* internal error code -1 means `no change' */ 3129 if ( error == -1 ) 3130 error = FT_Err_Ok; 3131 goto Exit; 3132 } 3133 } 3134 else 3135 error = TT_Set_Var_Design( face, 0, NULL ); 3136 3137 face->root.face_index = ( instance_index << 16 ) | 3138 ( face->root.face_index & 0xFFFFL ); 3139 face->root.face_flags &= ~FT_FACE_FLAG_VARIATION; 3140 3141 Exit: 3142 return error; 3143 } 3144 3145 3146 /*************************************************************************/ 3147 /*************************************************************************/ 3148 /***** *****/ 3149 /***** GX VAR PARSING ROUTINES *****/ 3150 /***** *****/ 3151 /*************************************************************************/ 3152 /*************************************************************************/ 3153 3154 3155 static FT_Error tt_cvt_ready_iterator(FT_ListNode node,void * user)3156 tt_cvt_ready_iterator( FT_ListNode node, 3157 void* user ) 3158 { 3159 TT_Size size = (TT_Size)node->data; 3160 3161 FT_UNUSED( user ); 3162 3163 3164 size->cvt_ready = -1; 3165 3166 return FT_Err_Ok; 3167 } 3168 3169 3170 /************************************************************************** 3171 * 3172 * @Function: 3173 * tt_face_vary_cvt 3174 * 3175 * @Description: 3176 * Modify the loaded cvt table according to the `cvar' table and the 3177 * font's blend. 3178 * 3179 * @InOut: 3180 * face :: 3181 * A handle to the target face object. 3182 * 3183 * @Input: 3184 * stream :: 3185 * A handle to the input stream. 3186 * 3187 * @Return: 3188 * FreeType error code. 0 means success. 3189 * 3190 * Most errors are ignored. It is perfectly valid not to have a 3191 * `cvar' table even if there is a `gvar' and `fvar' table. 3192 */ 3193 FT_LOCAL_DEF( FT_Error ) tt_face_vary_cvt(TT_Face face,FT_Stream stream)3194 tt_face_vary_cvt( TT_Face face, 3195 FT_Stream stream ) 3196 { 3197 FT_Error error; 3198 FT_Memory memory = stream->memory; 3199 3200 FT_Face root = &face->root; 3201 3202 FT_ULong table_start; 3203 FT_ULong table_len; 3204 3205 FT_UInt tupleCount; 3206 FT_ULong offsetToData; 3207 3208 FT_ULong here; 3209 FT_UInt i, j; 3210 3211 FT_Fixed* tuple_coords = NULL; 3212 FT_Fixed* im_start_coords = NULL; 3213 FT_Fixed* im_end_coords = NULL; 3214 3215 GX_Blend blend = face->blend; 3216 3217 FT_UInt point_count; 3218 FT_UInt spoint_count = 0; 3219 3220 FT_UShort* sharedpoints = NULL; 3221 FT_UShort* localpoints = NULL; 3222 FT_UShort* points; 3223 3224 FT_Fixed* deltas = NULL; 3225 FT_Fixed* cvt_deltas = NULL; 3226 3227 3228 FT_TRACE2(( "CVAR " )); 3229 3230 if ( !blend ) 3231 { 3232 FT_TRACE2(( "\n" 3233 "tt_face_vary_cvt: no blend specified\n" )); 3234 error = FT_Err_Ok; 3235 goto Exit; 3236 } 3237 3238 if ( !face->cvt ) 3239 { 3240 FT_TRACE2(( "\n" 3241 "tt_face_vary_cvt: no `cvt ' table\n" )); 3242 error = FT_Err_Ok; 3243 goto Exit; 3244 } 3245 3246 error = face->goto_table( face, TTAG_cvar, stream, &table_len ); 3247 if ( error ) 3248 { 3249 FT_TRACE2(( "is missing\n" )); 3250 3251 error = FT_Err_Ok; 3252 goto Exit; 3253 } 3254 3255 if ( FT_FRAME_ENTER( table_len ) ) 3256 { 3257 error = FT_Err_Ok; 3258 goto Exit; 3259 } 3260 3261 table_start = FT_Stream_FTell( stream ); 3262 if ( FT_GET_LONG() != 0x00010000L ) 3263 { 3264 FT_TRACE2(( "bad table version\n" )); 3265 3266 error = FT_Err_Ok; 3267 goto FExit; 3268 } 3269 3270 FT_TRACE2(( "loaded\n" )); 3271 3272 if ( FT_NEW_ARRAY( tuple_coords, blend->num_axis ) || 3273 FT_NEW_ARRAY( im_start_coords, blend->num_axis ) || 3274 FT_NEW_ARRAY( im_end_coords, blend->num_axis ) ) 3275 goto FExit; 3276 3277 tupleCount = FT_GET_USHORT(); 3278 offsetToData = FT_GET_USHORT(); 3279 3280 /* rough sanity test */ 3281 if ( offsetToData + ( tupleCount & GX_TC_TUPLE_COUNT_MASK ) * 4 > 3282 table_len ) 3283 { 3284 FT_TRACE2(( "tt_face_vary_cvt:" 3285 " invalid CVT variation array header\n" )); 3286 3287 error = FT_THROW( Invalid_Table ); 3288 goto FExit; 3289 } 3290 3291 offsetToData += table_start; 3292 3293 if ( tupleCount & GX_TC_TUPLES_SHARE_POINT_NUMBERS ) 3294 { 3295 here = FT_Stream_FTell( stream ); 3296 3297 FT_Stream_SeekSet( stream, offsetToData ); 3298 3299 sharedpoints = ft_var_readpackedpoints( stream, 3300 table_len, 3301 &spoint_count ); 3302 offsetToData = FT_Stream_FTell( stream ); 3303 3304 FT_Stream_SeekSet( stream, here ); 3305 } 3306 3307 FT_TRACE5(( "cvar: there %s %d tuple%s:\n", 3308 ( tupleCount & GX_TC_TUPLE_COUNT_MASK ) == 1 ? "is" : "are", 3309 tupleCount & GX_TC_TUPLE_COUNT_MASK, 3310 ( tupleCount & GX_TC_TUPLE_COUNT_MASK ) == 1 ? "" : "s" )); 3311 3312 if ( FT_NEW_ARRAY( cvt_deltas, face->cvt_size ) ) 3313 goto FExit; 3314 3315 for ( i = 0; i < ( tupleCount & GX_TC_TUPLE_COUNT_MASK ); i++ ) 3316 { 3317 FT_UInt tupleDataSize; 3318 FT_UInt tupleIndex; 3319 FT_Fixed apply; 3320 3321 3322 FT_TRACE6(( " tuple %d:\n", i )); 3323 3324 tupleDataSize = FT_GET_USHORT(); 3325 tupleIndex = FT_GET_USHORT(); 3326 3327 if ( tupleIndex & GX_TI_EMBEDDED_TUPLE_COORD ) 3328 { 3329 for ( j = 0; j < blend->num_axis; j++ ) 3330 tuple_coords[j] = FT_fdot14ToFixed( FT_GET_SHORT() ); 3331 } 3332 else if ( ( tupleIndex & GX_TI_TUPLE_INDEX_MASK ) >= blend->tuplecount ) 3333 { 3334 FT_TRACE2(( "tt_face_vary_cvt:" 3335 " invalid tuple index\n" )); 3336 3337 error = FT_THROW( Invalid_Table ); 3338 goto FExit; 3339 } 3340 else 3341 { 3342 if ( !blend->tuplecoords ) 3343 { 3344 FT_TRACE2(( "tt_face_vary_cvt:" 3345 " no valid tuple coordinates available\n" )); 3346 3347 error = FT_THROW( Invalid_Table ); 3348 goto FExit; 3349 } 3350 3351 FT_MEM_COPY( 3352 tuple_coords, 3353 blend->tuplecoords + 3354 ( tupleIndex & GX_TI_TUPLE_INDEX_MASK ) * blend->num_axis, 3355 blend->num_axis * sizeof ( FT_Fixed ) ); 3356 } 3357 3358 if ( tupleIndex & GX_TI_INTERMEDIATE_TUPLE ) 3359 { 3360 for ( j = 0; j < blend->num_axis; j++ ) 3361 im_start_coords[j] = FT_fdot14ToFixed( FT_GET_SHORT() ); 3362 for ( j = 0; j < blend->num_axis; j++ ) 3363 im_end_coords[j] = FT_fdot14ToFixed( FT_GET_SHORT() ); 3364 } 3365 3366 apply = ft_var_apply_tuple( blend, 3367 (FT_UShort)tupleIndex, 3368 tuple_coords, 3369 im_start_coords, 3370 im_end_coords ); 3371 3372 if ( apply == 0 ) /* tuple isn't active for our blend */ 3373 { 3374 offsetToData += tupleDataSize; 3375 continue; 3376 } 3377 3378 here = FT_Stream_FTell( stream ); 3379 3380 FT_Stream_SeekSet( stream, offsetToData ); 3381 3382 if ( tupleIndex & GX_TI_PRIVATE_POINT_NUMBERS ) 3383 { 3384 localpoints = ft_var_readpackedpoints( stream, 3385 table_len, 3386 &point_count ); 3387 points = localpoints; 3388 } 3389 else 3390 { 3391 points = sharedpoints; 3392 point_count = spoint_count; 3393 } 3394 3395 deltas = ft_var_readpackeddeltas( stream, 3396 table_len, 3397 point_count == 0 ? face->cvt_size 3398 : point_count ); 3399 3400 if ( !points || 3401 !deltas || 3402 ( localpoints == ALL_POINTS && point_count != face->cvt_size ) ) 3403 ; /* failure, ignore it */ 3404 3405 else if ( localpoints == ALL_POINTS ) 3406 { 3407 #ifdef FT_DEBUG_LEVEL_TRACE 3408 int count = 0; 3409 #endif 3410 3411 3412 FT_TRACE7(( " CVT deltas:\n" )); 3413 3414 /* this means that there are deltas for every entry in cvt */ 3415 for ( j = 0; j < face->cvt_size; j++ ) 3416 { 3417 FT_Fixed old_cvt_delta; 3418 3419 3420 old_cvt_delta = cvt_deltas[j]; 3421 cvt_deltas[j] = old_cvt_delta + FT_MulFix( deltas[j], apply ); 3422 3423 #ifdef FT_DEBUG_LEVEL_TRACE 3424 if ( old_cvt_delta != cvt_deltas[j] ) 3425 { 3426 FT_TRACE7(( " %d: %f -> %f\n", 3427 j, 3428 ( FT_fdot6ToFixed( face->cvt[j] ) + 3429 old_cvt_delta ) / 65536.0, 3430 ( FT_fdot6ToFixed( face->cvt[j] ) + 3431 cvt_deltas[j] ) / 65536.0 )); 3432 count++; 3433 } 3434 #endif 3435 } 3436 3437 #ifdef FT_DEBUG_LEVEL_TRACE 3438 if ( !count ) 3439 FT_TRACE7(( " none\n" )); 3440 #endif 3441 } 3442 3443 else 3444 { 3445 #ifdef FT_DEBUG_LEVEL_TRACE 3446 int count = 0; 3447 #endif 3448 3449 3450 FT_TRACE7(( " CVT deltas:\n" )); 3451 3452 for ( j = 0; j < point_count; j++ ) 3453 { 3454 int pindex; 3455 FT_Fixed old_cvt_delta; 3456 3457 3458 pindex = points[j]; 3459 if ( (FT_ULong)pindex >= face->cvt_size ) 3460 continue; 3461 3462 old_cvt_delta = cvt_deltas[pindex]; 3463 cvt_deltas[pindex] = old_cvt_delta + FT_MulFix( deltas[j], apply ); 3464 3465 #ifdef FT_DEBUG_LEVEL_TRACE 3466 if ( old_cvt_delta != cvt_deltas[pindex] ) 3467 { 3468 FT_TRACE7(( " %d: %f -> %f\n", 3469 pindex, 3470 ( FT_fdot6ToFixed( face->cvt[pindex] ) + 3471 old_cvt_delta ) / 65536.0, 3472 ( FT_fdot6ToFixed( face->cvt[pindex] ) + 3473 cvt_deltas[pindex] ) / 65536.0 )); 3474 count++; 3475 } 3476 #endif 3477 } 3478 3479 #ifdef FT_DEBUG_LEVEL_TRACE 3480 if ( !count ) 3481 FT_TRACE7(( " none\n" )); 3482 #endif 3483 } 3484 3485 if ( localpoints != ALL_POINTS ) 3486 FT_FREE( localpoints ); 3487 FT_FREE( deltas ); 3488 3489 offsetToData += tupleDataSize; 3490 3491 FT_Stream_SeekSet( stream, here ); 3492 } 3493 3494 FT_TRACE5(( "\n" )); 3495 3496 for ( i = 0; i < face->cvt_size; i++ ) 3497 face->cvt[i] += FT_fixedToFdot6( cvt_deltas[i] ); 3498 3499 FExit: 3500 FT_FRAME_EXIT(); 3501 3502 Exit: 3503 if ( sharedpoints != ALL_POINTS ) 3504 FT_FREE( sharedpoints ); 3505 FT_FREE( tuple_coords ); 3506 FT_FREE( im_start_coords ); 3507 FT_FREE( im_end_coords ); 3508 FT_FREE( cvt_deltas ); 3509 3510 /* iterate over all FT_Size objects and set `cvt_ready' to -1 */ 3511 /* to trigger rescaling of all CVT values */ 3512 FT_List_Iterate( &root->sizes_list, 3513 tt_cvt_ready_iterator, 3514 NULL ); 3515 3516 return error; 3517 } 3518 3519 3520 /* Shift the original coordinates of all points between indices `p1' */ 3521 /* and `p2', using the same difference as given by index `ref'. */ 3522 3523 /* modeled after `af_iup_shift' */ 3524 3525 static void tt_delta_shift(int p1,int p2,int ref,FT_Vector * in_points,FT_Vector * out_points)3526 tt_delta_shift( int p1, 3527 int p2, 3528 int ref, 3529 FT_Vector* in_points, 3530 FT_Vector* out_points ) 3531 { 3532 int p; 3533 FT_Vector delta; 3534 3535 3536 delta.x = out_points[ref].x - in_points[ref].x; 3537 delta.y = out_points[ref].y - in_points[ref].y; 3538 3539 if ( delta.x == 0 && delta.y == 0 ) 3540 return; 3541 3542 for ( p = p1; p < ref; p++ ) 3543 { 3544 out_points[p].x += delta.x; 3545 out_points[p].y += delta.y; 3546 } 3547 3548 for ( p = ref + 1; p <= p2; p++ ) 3549 { 3550 out_points[p].x += delta.x; 3551 out_points[p].y += delta.y; 3552 } 3553 } 3554 3555 3556 /* Interpolate the original coordinates of all points with indices */ 3557 /* between `p1' and `p2', using `ref1' and `ref2' as the reference */ 3558 /* point indices. */ 3559 3560 /* modeled after `af_iup_interp', `_iup_worker_interpolate', and */ 3561 /* `Ins_IUP' with spec differences in handling ill-defined cases. */ 3562 static void tt_delta_interpolate(int p1,int p2,int ref1,int ref2,FT_Vector * in_points,FT_Vector * out_points)3563 tt_delta_interpolate( int p1, 3564 int p2, 3565 int ref1, 3566 int ref2, 3567 FT_Vector* in_points, 3568 FT_Vector* out_points ) 3569 { 3570 int p, i; 3571 3572 FT_Pos out, in1, in2, out1, out2, d1, d2; 3573 3574 3575 if ( p1 > p2 ) 3576 return; 3577 3578 /* handle both horizontal and vertical coordinates */ 3579 for ( i = 0; i <= 1; i++ ) 3580 { 3581 /* shift array pointers so that we can access `foo.y' as `foo.x' */ 3582 in_points = (FT_Vector*)( (FT_Pos*)in_points + i ); 3583 out_points = (FT_Vector*)( (FT_Pos*)out_points + i ); 3584 3585 if ( in_points[ref1].x > in_points[ref2].x ) 3586 { 3587 p = ref1; 3588 ref1 = ref2; 3589 ref2 = p; 3590 } 3591 3592 in1 = in_points[ref1].x; 3593 in2 = in_points[ref2].x; 3594 out1 = out_points[ref1].x; 3595 out2 = out_points[ref2].x; 3596 d1 = out1 - in1; 3597 d2 = out2 - in2; 3598 3599 /* If the reference points have the same coordinate but different */ 3600 /* delta, inferred delta is zero. Otherwise interpolate. */ 3601 if ( in1 != in2 || out1 == out2 ) 3602 { 3603 FT_Fixed scale = in1 != in2 ? FT_DivFix( out2 - out1, in2 - in1 ) 3604 : 0; 3605 3606 3607 for ( p = p1; p <= p2; p++ ) 3608 { 3609 out = in_points[p].x; 3610 3611 if ( out <= in1 ) 3612 out += d1; 3613 else if ( out >= in2 ) 3614 out += d2; 3615 else 3616 out = out1 + FT_MulFix( out - in1, scale ); 3617 3618 out_points[p].x = out; 3619 } 3620 } 3621 } 3622 } 3623 3624 3625 /* Interpolate points without delta values, similar to */ 3626 /* the `IUP' hinting instruction. */ 3627 3628 /* modeled after `Ins_IUP */ 3629 3630 static void tt_interpolate_deltas(FT_Outline * outline,FT_Vector * out_points,FT_Vector * in_points,FT_Bool * has_delta)3631 tt_interpolate_deltas( FT_Outline* outline, 3632 FT_Vector* out_points, 3633 FT_Vector* in_points, 3634 FT_Bool* has_delta ) 3635 { 3636 FT_Int first_point; 3637 FT_Int end_point; 3638 3639 FT_Int first_delta; 3640 FT_Int cur_delta; 3641 3642 FT_Int point; 3643 FT_Short contour; 3644 3645 3646 /* ignore empty outlines */ 3647 if ( !outline->n_contours ) 3648 return; 3649 3650 contour = 0; 3651 point = 0; 3652 3653 do 3654 { 3655 end_point = outline->contours[contour]; 3656 first_point = point; 3657 3658 /* search first point that has a delta */ 3659 while ( point <= end_point && !has_delta[point] ) 3660 point++; 3661 3662 if ( point <= end_point ) 3663 { 3664 first_delta = point; 3665 cur_delta = point; 3666 3667 point++; 3668 3669 while ( point <= end_point ) 3670 { 3671 /* search next point that has a delta */ 3672 /* and interpolate intermediate points */ 3673 if ( has_delta[point] ) 3674 { 3675 tt_delta_interpolate( cur_delta + 1, 3676 point - 1, 3677 cur_delta, 3678 point, 3679 in_points, 3680 out_points ); 3681 cur_delta = point; 3682 } 3683 3684 point++; 3685 } 3686 3687 /* shift contour if we only have a single delta */ 3688 if ( cur_delta == first_delta ) 3689 tt_delta_shift( first_point, 3690 end_point, 3691 cur_delta, 3692 in_points, 3693 out_points ); 3694 else 3695 { 3696 /* otherwise handle remaining points */ 3697 /* at the end and beginning of the contour */ 3698 tt_delta_interpolate( cur_delta + 1, 3699 end_point, 3700 cur_delta, 3701 first_delta, 3702 in_points, 3703 out_points ); 3704 3705 if ( first_delta > 0 ) 3706 tt_delta_interpolate( first_point, 3707 first_delta - 1, 3708 cur_delta, 3709 first_delta, 3710 in_points, 3711 out_points ); 3712 } 3713 } 3714 contour++; 3715 3716 } while ( contour < outline->n_contours ); 3717 } 3718 3719 3720 /************************************************************************** 3721 * 3722 * @Function: 3723 * TT_Vary_Apply_Glyph_Deltas 3724 * 3725 * @Description: 3726 * Apply the appropriate deltas to the current glyph. 3727 * 3728 * @Input: 3729 * face :: 3730 * A handle to the target face object. 3731 * 3732 * glyph_index :: 3733 * The index of the glyph being modified. 3734 * 3735 * n_points :: 3736 * The number of the points in the glyph, including 3737 * phantom points. 3738 * 3739 * @InOut: 3740 * outline :: 3741 * The outline to change. 3742 * 3743 * @Output: 3744 * unrounded :: 3745 * An array with `n_points' elements that is filled with unrounded 3746 * point coordinates (in 26.6 format). 3747 * 3748 * @Return: 3749 * FreeType error code. 0 means success. 3750 */ 3751 FT_LOCAL_DEF( FT_Error ) TT_Vary_Apply_Glyph_Deltas(TT_Face face,FT_UInt glyph_index,FT_Outline * outline,FT_Vector * unrounded,FT_UInt n_points)3752 TT_Vary_Apply_Glyph_Deltas( TT_Face face, 3753 FT_UInt glyph_index, 3754 FT_Outline* outline, 3755 FT_Vector* unrounded, 3756 FT_UInt n_points ) 3757 { 3758 FT_Error error; 3759 FT_Stream stream = face->root.stream; 3760 FT_Memory memory = stream->memory; 3761 3762 FT_Vector* points_org = NULL; /* coordinates in 16.16 format */ 3763 FT_Vector* points_out = NULL; /* coordinates in 16.16 format */ 3764 FT_Bool* has_delta = NULL; 3765 3766 FT_ULong glyph_start; 3767 3768 FT_UInt tupleCount; 3769 FT_ULong offsetToData; 3770 FT_ULong dataSize; 3771 3772 FT_ULong here; 3773 FT_UInt i, j; 3774 3775 FT_Fixed* tuple_coords = NULL; 3776 FT_Fixed* im_start_coords = NULL; 3777 FT_Fixed* im_end_coords = NULL; 3778 3779 GX_Blend blend = face->blend; 3780 3781 FT_UInt point_count; 3782 FT_UInt spoint_count = 0; 3783 3784 FT_UShort* sharedpoints = NULL; 3785 FT_UShort* localpoints = NULL; 3786 FT_UShort* points; 3787 3788 FT_Fixed* deltas_x = NULL; 3789 FT_Fixed* deltas_y = NULL; 3790 FT_Fixed* point_deltas_x = NULL; 3791 FT_Fixed* point_deltas_y = NULL; 3792 3793 3794 if ( !face->doblend || !blend ) 3795 return FT_THROW( Invalid_Argument ); 3796 3797 for ( i = 0; i < n_points; i++ ) 3798 { 3799 unrounded[i].x = INT_TO_F26DOT6( outline->points[i].x ); 3800 unrounded[i].y = INT_TO_F26DOT6( outline->points[i].y ); 3801 } 3802 3803 if ( glyph_index >= blend->gv_glyphcnt || 3804 blend->glyphoffsets[glyph_index] == 3805 blend->glyphoffsets[glyph_index + 1] ) 3806 { 3807 FT_TRACE2(( "TT_Vary_Apply_Glyph_Deltas:" 3808 " no variation data for glyph %d\n", glyph_index )); 3809 return FT_Err_Ok; 3810 } 3811 3812 if ( FT_NEW_ARRAY( points_org, n_points ) || 3813 FT_NEW_ARRAY( points_out, n_points ) || 3814 FT_NEW_ARRAY( has_delta, n_points ) ) 3815 goto Fail1; 3816 3817 dataSize = blend->glyphoffsets[glyph_index + 1] - 3818 blend->glyphoffsets[glyph_index]; 3819 3820 if ( FT_STREAM_SEEK( blend->glyphoffsets[glyph_index] ) || 3821 FT_FRAME_ENTER( dataSize ) ) 3822 goto Fail1; 3823 3824 glyph_start = FT_Stream_FTell( stream ); 3825 3826 /* each set of glyph variation data is formatted similarly to `cvar' */ 3827 3828 if ( FT_NEW_ARRAY( tuple_coords, blend->num_axis ) || 3829 FT_NEW_ARRAY( im_start_coords, blend->num_axis ) || 3830 FT_NEW_ARRAY( im_end_coords, blend->num_axis ) ) 3831 goto Fail2; 3832 3833 tupleCount = FT_GET_USHORT(); 3834 offsetToData = FT_GET_USHORT(); 3835 3836 /* rough sanity test */ 3837 if ( offsetToData > dataSize || 3838 ( tupleCount & GX_TC_TUPLE_COUNT_MASK ) * 4 > dataSize ) 3839 { 3840 FT_TRACE2(( "TT_Vary_Apply_Glyph_Deltas:" 3841 " invalid glyph variation array header\n" )); 3842 3843 error = FT_THROW( Invalid_Table ); 3844 goto Fail2; 3845 } 3846 3847 offsetToData += glyph_start; 3848 3849 if ( tupleCount & GX_TC_TUPLES_SHARE_POINT_NUMBERS ) 3850 { 3851 here = FT_Stream_FTell( stream ); 3852 3853 FT_Stream_SeekSet( stream, offsetToData ); 3854 3855 sharedpoints = ft_var_readpackedpoints( stream, 3856 blend->gvar_size, 3857 &spoint_count ); 3858 offsetToData = FT_Stream_FTell( stream ); 3859 3860 FT_Stream_SeekSet( stream, here ); 3861 } 3862 3863 FT_TRACE5(( "gvar: there %s %d tuple%s:\n", 3864 ( tupleCount & GX_TC_TUPLE_COUNT_MASK ) == 1 ? "is" : "are", 3865 tupleCount & GX_TC_TUPLE_COUNT_MASK, 3866 ( tupleCount & GX_TC_TUPLE_COUNT_MASK ) == 1 ? "" : "s" )); 3867 3868 if ( FT_NEW_ARRAY( point_deltas_x, n_points ) || 3869 FT_NEW_ARRAY( point_deltas_y, n_points ) ) 3870 goto Fail3; 3871 3872 for ( j = 0; j < n_points; j++ ) 3873 { 3874 points_org[j].x = FT_intToFixed( outline->points[j].x ); 3875 points_org[j].y = FT_intToFixed( outline->points[j].y ); 3876 } 3877 3878 for ( i = 0; i < ( tupleCount & GX_TC_TUPLE_COUNT_MASK ); i++ ) 3879 { 3880 FT_UInt tupleDataSize; 3881 FT_UInt tupleIndex; 3882 FT_Fixed apply; 3883 3884 3885 FT_TRACE6(( " tuple %d:\n", i )); 3886 3887 tupleDataSize = FT_GET_USHORT(); 3888 tupleIndex = FT_GET_USHORT(); 3889 3890 if ( tupleIndex & GX_TI_EMBEDDED_TUPLE_COORD ) 3891 { 3892 for ( j = 0; j < blend->num_axis; j++ ) 3893 tuple_coords[j] = FT_fdot14ToFixed( FT_GET_SHORT() ); 3894 } 3895 else if ( ( tupleIndex & GX_TI_TUPLE_INDEX_MASK ) >= blend->tuplecount ) 3896 { 3897 FT_TRACE2(( "TT_Vary_Apply_Glyph_Deltas:" 3898 " invalid tuple index\n" )); 3899 3900 error = FT_THROW( Invalid_Table ); 3901 goto Fail3; 3902 } 3903 else 3904 FT_MEM_COPY( 3905 tuple_coords, 3906 blend->tuplecoords + 3907 ( tupleIndex & GX_TI_TUPLE_INDEX_MASK ) * blend->num_axis, 3908 blend->num_axis * sizeof ( FT_Fixed ) ); 3909 3910 if ( tupleIndex & GX_TI_INTERMEDIATE_TUPLE ) 3911 { 3912 for ( j = 0; j < blend->num_axis; j++ ) 3913 im_start_coords[j] = FT_fdot14ToFixed( FT_GET_SHORT() ); 3914 for ( j = 0; j < blend->num_axis; j++ ) 3915 im_end_coords[j] = FT_fdot14ToFixed( FT_GET_SHORT() ); 3916 } 3917 3918 apply = ft_var_apply_tuple( blend, 3919 (FT_UShort)tupleIndex, 3920 tuple_coords, 3921 im_start_coords, 3922 im_end_coords ); 3923 3924 if ( apply == 0 ) /* tuple isn't active for our blend */ 3925 { 3926 offsetToData += tupleDataSize; 3927 continue; 3928 } 3929 3930 here = FT_Stream_FTell( stream ); 3931 3932 FT_Stream_SeekSet( stream, offsetToData ); 3933 3934 if ( tupleIndex & GX_TI_PRIVATE_POINT_NUMBERS ) 3935 { 3936 localpoints = ft_var_readpackedpoints( stream, 3937 blend->gvar_size, 3938 &point_count ); 3939 points = localpoints; 3940 } 3941 else 3942 { 3943 points = sharedpoints; 3944 point_count = spoint_count; 3945 } 3946 3947 deltas_x = ft_var_readpackeddeltas( stream, 3948 blend->gvar_size, 3949 point_count == 0 ? n_points 3950 : point_count ); 3951 deltas_y = ft_var_readpackeddeltas( stream, 3952 blend->gvar_size, 3953 point_count == 0 ? n_points 3954 : point_count ); 3955 3956 if ( !points || !deltas_y || !deltas_x ) 3957 ; /* failure, ignore it */ 3958 3959 else if ( points == ALL_POINTS ) 3960 { 3961 #ifdef FT_DEBUG_LEVEL_TRACE 3962 int count = 0; 3963 #endif 3964 3965 3966 FT_TRACE7(( " point deltas:\n" )); 3967 3968 /* this means that there are deltas for every point in the glyph */ 3969 for ( j = 0; j < n_points; j++ ) 3970 { 3971 FT_Fixed old_point_delta_x = point_deltas_x[j]; 3972 FT_Fixed old_point_delta_y = point_deltas_y[j]; 3973 3974 FT_Fixed point_delta_x = FT_MulFix( deltas_x[j], apply ); 3975 FT_Fixed point_delta_y = FT_MulFix( deltas_y[j], apply ); 3976 3977 3978 if ( j < n_points - 4 ) 3979 { 3980 point_deltas_x[j] = old_point_delta_x + point_delta_x; 3981 point_deltas_y[j] = old_point_delta_y + point_delta_y; 3982 } 3983 else 3984 { 3985 /* To avoid double adjustment of advance width or height, */ 3986 /* adjust phantom points only if there is no HVAR or VVAR */ 3987 /* support, respectively. */ 3988 if ( j == ( n_points - 4 ) && 3989 !( face->variation_support & 3990 TT_FACE_FLAG_VAR_LSB ) ) 3991 point_deltas_x[j] = old_point_delta_x + point_delta_x; 3992 3993 else if ( j == ( n_points - 3 ) && 3994 !( face->variation_support & 3995 TT_FACE_FLAG_VAR_HADVANCE ) ) 3996 point_deltas_x[j] = old_point_delta_x + point_delta_x; 3997 3998 else if ( j == ( n_points - 2 ) && 3999 !( face->variation_support & 4000 TT_FACE_FLAG_VAR_TSB ) ) 4001 point_deltas_y[j] = old_point_delta_y + point_delta_y; 4002 4003 else if ( j == ( n_points - 1 ) && 4004 !( face->variation_support & 4005 TT_FACE_FLAG_VAR_VADVANCE ) ) 4006 point_deltas_y[j] = old_point_delta_y + point_delta_y; 4007 } 4008 4009 #ifdef FT_DEBUG_LEVEL_TRACE 4010 if ( point_delta_x || point_delta_y ) 4011 { 4012 FT_TRACE7(( " %d: (%f, %f) -> (%f, %f)\n", 4013 j, 4014 ( FT_intToFixed( outline->points[j].x ) + 4015 old_point_delta_x ) / 65536.0, 4016 ( FT_intToFixed( outline->points[j].y ) + 4017 old_point_delta_y ) / 65536.0, 4018 ( FT_intToFixed( outline->points[j].x ) + 4019 point_deltas_x[j] ) / 65536.0, 4020 ( FT_intToFixed( outline->points[j].y ) + 4021 point_deltas_y[j] ) / 65536.0 )); 4022 count++; 4023 } 4024 #endif 4025 } 4026 4027 #ifdef FT_DEBUG_LEVEL_TRACE 4028 if ( !count ) 4029 FT_TRACE7(( " none\n" )); 4030 #endif 4031 } 4032 4033 else 4034 { 4035 #ifdef FT_DEBUG_LEVEL_TRACE 4036 int count = 0; 4037 #endif 4038 4039 4040 /* we have to interpolate the missing deltas similar to the */ 4041 /* IUP bytecode instruction */ 4042 for ( j = 0; j < n_points; j++ ) 4043 { 4044 has_delta[j] = FALSE; 4045 points_out[j] = points_org[j]; 4046 } 4047 4048 for ( j = 0; j < point_count; j++ ) 4049 { 4050 FT_UShort idx = points[j]; 4051 4052 4053 if ( idx >= n_points ) 4054 continue; 4055 4056 has_delta[idx] = TRUE; 4057 4058 points_out[idx].x += FT_MulFix( deltas_x[j], apply ); 4059 points_out[idx].y += FT_MulFix( deltas_y[j], apply ); 4060 } 4061 4062 /* no need to handle phantom points here, */ 4063 /* since solitary points can't be interpolated */ 4064 tt_interpolate_deltas( outline, 4065 points_out, 4066 points_org, 4067 has_delta ); 4068 4069 FT_TRACE7(( " point deltas:\n" )); 4070 4071 for ( j = 0; j < n_points; j++ ) 4072 { 4073 FT_Fixed old_point_delta_x = point_deltas_x[j]; 4074 FT_Fixed old_point_delta_y = point_deltas_y[j]; 4075 4076 FT_Pos point_delta_x = points_out[j].x - points_org[j].x; 4077 FT_Pos point_delta_y = points_out[j].y - points_org[j].y; 4078 4079 4080 if ( j < n_points - 4 ) 4081 { 4082 point_deltas_x[j] = old_point_delta_x + point_delta_x; 4083 point_deltas_y[j] = old_point_delta_y + point_delta_y; 4084 } 4085 else 4086 { 4087 /* To avoid double adjustment of advance width or height, */ 4088 /* adjust phantom points only if there is no HVAR or VVAR */ 4089 /* support, respectively. */ 4090 if ( j == ( n_points - 4 ) && 4091 !( face->variation_support & 4092 TT_FACE_FLAG_VAR_LSB ) ) 4093 point_deltas_x[j] = old_point_delta_x + point_delta_x; 4094 4095 else if ( j == ( n_points - 3 ) && 4096 !( face->variation_support & 4097 TT_FACE_FLAG_VAR_HADVANCE ) ) 4098 point_deltas_x[j] = old_point_delta_x + point_delta_x; 4099 4100 else if ( j == ( n_points - 2 ) && 4101 !( face->variation_support & 4102 TT_FACE_FLAG_VAR_TSB ) ) 4103 point_deltas_y[j] = old_point_delta_y + point_delta_y; 4104 4105 else if ( j == ( n_points - 1 ) && 4106 !( face->variation_support & 4107 TT_FACE_FLAG_VAR_VADVANCE ) ) 4108 point_deltas_y[j] = old_point_delta_y + point_delta_y; 4109 } 4110 4111 #ifdef FT_DEBUG_LEVEL_TRACE 4112 if ( point_delta_x || point_delta_y ) 4113 { 4114 FT_TRACE7(( " %d: (%f, %f) -> (%f, %f)\n", 4115 j, 4116 ( FT_intToFixed( outline->points[j].x ) + 4117 old_point_delta_x ) / 65536.0, 4118 ( FT_intToFixed( outline->points[j].y ) + 4119 old_point_delta_y ) / 65536.0, 4120 ( FT_intToFixed( outline->points[j].x ) + 4121 point_deltas_x[j] ) / 65536.0, 4122 ( FT_intToFixed( outline->points[j].y ) + 4123 point_deltas_y[j] ) / 65536.0 )); 4124 count++; 4125 } 4126 #endif 4127 } 4128 4129 #ifdef FT_DEBUG_LEVEL_TRACE 4130 if ( !count ) 4131 FT_TRACE7(( " none\n" )); 4132 #endif 4133 } 4134 4135 if ( localpoints != ALL_POINTS ) 4136 FT_FREE( localpoints ); 4137 FT_FREE( deltas_x ); 4138 FT_FREE( deltas_y ); 4139 4140 offsetToData += tupleDataSize; 4141 4142 FT_Stream_SeekSet( stream, here ); 4143 } 4144 4145 FT_TRACE5(( "\n" )); 4146 4147 for ( i = 0; i < n_points; i++ ) 4148 { 4149 unrounded[i].x += FT_fixedToFdot6( point_deltas_x[i] ); 4150 unrounded[i].y += FT_fixedToFdot6( point_deltas_y[i] ); 4151 4152 outline->points[i].x += FT_fixedToInt( point_deltas_x[i] ); 4153 outline->points[i].y += FT_fixedToInt( point_deltas_y[i] ); 4154 } 4155 4156 Fail3: 4157 FT_FREE( point_deltas_x ); 4158 FT_FREE( point_deltas_y ); 4159 4160 Fail2: 4161 if ( sharedpoints != ALL_POINTS ) 4162 FT_FREE( sharedpoints ); 4163 FT_FREE( tuple_coords ); 4164 FT_FREE( im_start_coords ); 4165 FT_FREE( im_end_coords ); 4166 4167 FT_FRAME_EXIT(); 4168 4169 Fail1: 4170 FT_FREE( points_org ); 4171 FT_FREE( points_out ); 4172 FT_FREE( has_delta ); 4173 4174 return error; 4175 } 4176 4177 4178 /************************************************************************** 4179 * 4180 * @Function: 4181 * tt_get_var_blend 4182 * 4183 * @Description: 4184 * An extended internal version of `TT_Get_MM_Blend' that returns 4185 * pointers instead of copying data, without any initialization of 4186 * the MM machinery in case it isn't loaded yet. 4187 */ 4188 FT_LOCAL_DEF( FT_Error ) tt_get_var_blend(TT_Face face,FT_UInt * num_coords,FT_Fixed ** coords,FT_Fixed ** normalizedcoords,FT_MM_Var ** mm_var)4189 tt_get_var_blend( TT_Face face, 4190 FT_UInt *num_coords, 4191 FT_Fixed* *coords, 4192 FT_Fixed* *normalizedcoords, 4193 FT_MM_Var* *mm_var ) 4194 { 4195 if ( face->blend ) 4196 { 4197 if ( num_coords ) 4198 *num_coords = face->blend->num_axis; 4199 if ( coords ) 4200 *coords = face->blend->coords; 4201 if ( normalizedcoords ) 4202 *normalizedcoords = face->blend->normalizedcoords; 4203 if ( mm_var ) 4204 *mm_var = face->blend->mmvar; 4205 } 4206 else 4207 { 4208 if ( num_coords ) 4209 *num_coords = 0; 4210 if ( coords ) 4211 *coords = NULL; 4212 if ( mm_var ) 4213 *mm_var = NULL; 4214 } 4215 4216 return FT_Err_Ok; 4217 } 4218 4219 4220 static void ft_var_done_item_variation_store(TT_Face face,GX_ItemVarStore itemStore)4221 ft_var_done_item_variation_store( TT_Face face, 4222 GX_ItemVarStore itemStore ) 4223 { 4224 FT_Memory memory = FT_FACE_MEMORY( face ); 4225 FT_UInt i; 4226 4227 4228 if ( itemStore->varData ) 4229 { 4230 for ( i = 0; i < itemStore->dataCount; i++ ) 4231 { 4232 FT_FREE( itemStore->varData[i].regionIndices ); 4233 FT_FREE( itemStore->varData[i].deltaSet ); 4234 } 4235 4236 FT_FREE( itemStore->varData ); 4237 } 4238 4239 if ( itemStore->varRegionList ) 4240 { 4241 for ( i = 0; i < itemStore->regionCount; i++ ) 4242 FT_FREE( itemStore->varRegionList[i].axisList ); 4243 4244 FT_FREE( itemStore->varRegionList ); 4245 } 4246 } 4247 4248 4249 /************************************************************************** 4250 * 4251 * @Function: 4252 * tt_done_blend 4253 * 4254 * @Description: 4255 * Free the blend internal data structure. 4256 */ 4257 FT_LOCAL_DEF( void ) tt_done_blend(TT_Face face)4258 tt_done_blend( TT_Face face ) 4259 { 4260 FT_Memory memory = FT_FACE_MEMORY( face ); 4261 GX_Blend blend = face->blend; 4262 4263 4264 if ( blend ) 4265 { 4266 FT_UInt i, num_axes; 4267 4268 4269 /* blend->num_axis might not be set up yet */ 4270 num_axes = blend->mmvar->num_axis; 4271 4272 FT_FREE( blend->coords ); 4273 FT_FREE( blend->normalizedcoords ); 4274 FT_FREE( blend->normalized_stylecoords ); 4275 FT_FREE( blend->mmvar ); 4276 4277 if ( blend->avar_segment ) 4278 { 4279 for ( i = 0; i < num_axes; i++ ) 4280 FT_FREE( blend->avar_segment[i].correspondence ); 4281 FT_FREE( blend->avar_segment ); 4282 } 4283 4284 if ( blend->hvar_table ) 4285 { 4286 ft_var_done_item_variation_store( face, 4287 &blend->hvar_table->itemStore ); 4288 4289 FT_FREE( blend->hvar_table->widthMap.innerIndex ); 4290 FT_FREE( blend->hvar_table->widthMap.outerIndex ); 4291 FT_FREE( blend->hvar_table ); 4292 } 4293 4294 if ( blend->vvar_table ) 4295 { 4296 ft_var_done_item_variation_store( face, 4297 &blend->vvar_table->itemStore ); 4298 4299 FT_FREE( blend->vvar_table->widthMap.innerIndex ); 4300 FT_FREE( blend->vvar_table->widthMap.outerIndex ); 4301 FT_FREE( blend->vvar_table ); 4302 } 4303 4304 if ( blend->mvar_table ) 4305 { 4306 ft_var_done_item_variation_store( face, 4307 &blend->mvar_table->itemStore ); 4308 4309 FT_FREE( blend->mvar_table->values ); 4310 FT_FREE( blend->mvar_table ); 4311 } 4312 4313 FT_FREE( blend->tuplecoords ); 4314 FT_FREE( blend->glyphoffsets ); 4315 FT_FREE( blend ); 4316 } 4317 } 4318 4319 #else /* !TT_CONFIG_OPTION_GX_VAR_SUPPORT */ 4320 4321 /* ANSI C doesn't like empty source files */ 4322 typedef int _tt_gxvar_dummy; 4323 4324 #endif /* !TT_CONFIG_OPTION_GX_VAR_SUPPORT */ 4325 4326 4327 /* END */ 4328