1 /***************************************************************************/ 2 /* */ 3 /* cf2hints.c */ 4 /* */ 5 /* Adobe's code for handling CFF hints (body). */ 6 /* */ 7 /* Copyright 2007-2013 Adobe Systems Incorporated. */ 8 /* */ 9 /* This software, and all works of authorship, whether in source or */ 10 /* object code form as indicated by the copyright notice(s) included */ 11 /* herein (collectively, the "Work") is made available, and may only be */ 12 /* used, modified, and distributed under the FreeType Project License, */ 13 /* LICENSE.TXT. Additionally, subject to the terms and conditions of the */ 14 /* FreeType Project License, each contributor to the Work hereby grants */ 15 /* to any individual or legal entity exercising permissions granted by */ 16 /* the FreeType Project License and this section (hereafter, "You" or */ 17 /* "Your") a perpetual, worldwide, non-exclusive, no-charge, */ 18 /* royalty-free, irrevocable (except as stated in this section) patent */ 19 /* license to make, have made, use, offer to sell, sell, import, and */ 20 /* otherwise transfer the Work, where such license applies only to those */ 21 /* patent claims licensable by such contributor that are necessarily */ 22 /* infringed by their contribution(s) alone or by combination of their */ 23 /* contribution(s) with the Work to which such contribution(s) was */ 24 /* submitted. If You institute patent litigation against any entity */ 25 /* (including a cross-claim or counterclaim in a lawsuit) alleging that */ 26 /* the Work or a contribution incorporated within the Work constitutes */ 27 /* direct or contributory patent infringement, then any patent licenses */ 28 /* granted to You under this License for that Work shall terminate as of */ 29 /* the date such litigation is filed. */ 30 /* */ 31 /* By using, modifying, or distributing the Work you indicate that you */ 32 /* have read and understood the terms and conditions of the */ 33 /* FreeType Project License as well as those provided in this section, */ 34 /* and you accept them fully. */ 35 /* */ 36 /***************************************************************************/ 37 38 39 #include "cf2ft.h" 40 #include FT_INTERNAL_DEBUG_H 41 42 #include "cf2glue.h" 43 #include "cf2font.h" 44 #include "cf2hints.h" 45 #include "cf2intrp.h" 46 47 48 /*************************************************************************/ 49 /* */ 50 /* The macro FT_COMPONENT is used in trace mode. It is an implicit */ 51 /* parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log */ 52 /* messages during execution. */ 53 /* */ 54 #undef FT_COMPONENT 55 #define FT_COMPONENT trace_cf2hints 56 57 58 typedef struct CF2_HintMoveRec_ 59 { 60 size_t j; /* index of upper hint map edge */ 61 CF2_Fixed moveUp; /* adjustment to optimum position */ 62 63 } CF2_HintMoveRec, *CF2_HintMove; 64 65 66 /* Compute angular momentum for winding order detection. It is called */ 67 /* for all lines and curves, but not necessarily in element order. */ 68 static CF2_Int 69 cf2_getWindingMomentum( CF2_Fixed x1, 70 CF2_Fixed y1, 71 CF2_Fixed x2, 72 CF2_Fixed y2 ) 73 { 74 /* cross product of pt1 position from origin with pt2 position from */ 75 /* pt1; we reduce the precision so that the result fits into 32 bits */ 76 77 return ( x1 >> 16 ) * ( ( y2 - y1 ) >> 16 ) - 78 ( y1 >> 16 ) * ( ( x2 - x1 ) >> 16 ); 79 } 80 81 82 /* 83 * Construct from a StemHint; this is used as a parameter to 84 * `cf2_blues_capture'. 85 * `hintOrigin' is the character space displacement of a seac accent. 86 * Adjust stem hint for darkening here. 87 * 88 */ 89 static void 90 cf2_hint_init( CF2_Hint hint, 91 const CF2_ArrStack stemHintArray, 92 size_t indexStemHint, 93 const CF2_Font font, 94 CF2_Fixed hintOrigin, 95 CF2_Fixed scale, 96 FT_Bool bottom ) 97 { 98 CF2_Fixed width; 99 const CF2_StemHintRec* stemHint; 100 101 102 FT_ZERO( hint ); 103 104 stemHint = (const CF2_StemHintRec*)cf2_arrstack_getPointer( 105 stemHintArray, 106 indexStemHint ); 107 108 width = stemHint->max - stemHint->min; 109 110 if ( width == cf2_intToFixed( -21 ) ) 111 { 112 /* ghost bottom */ 113 114 if ( bottom ) 115 { 116 hint->csCoord = stemHint->max; 117 hint->flags = CF2_GhostBottom; 118 } 119 else 120 hint->flags = 0; 121 } 122 123 else if ( width == cf2_intToFixed( -20 ) ) 124 { 125 /* ghost top */ 126 127 if ( bottom ) 128 hint->flags = 0; 129 else 130 { 131 hint->csCoord = stemHint->min; 132 hint->flags = CF2_GhostTop; 133 } 134 } 135 136 else if ( width < 0 ) 137 { 138 /* inverted pair */ 139 140 /* 141 * Hints with negative widths were produced by an early version of a 142 * non-Adobe font tool. The Type 2 spec allows edge (ghost) hints 143 * with negative widths, but says 144 * 145 * All other negative widths have undefined meaning. 146 * 147 * CoolType has a silent workaround that negates the hint width; for 148 * permissive mode, we do the same here. 149 * 150 * Note: Such fonts cannot use ghost hints, but should otherwise work. 151 * Note: Some poor hints in our faux fonts can produce negative 152 * widths at some blends. For example, see a light weight of 153 * `u' in ASerifMM. 154 * 155 */ 156 if ( bottom ) 157 { 158 hint->csCoord = stemHint->max; 159 hint->flags = CF2_PairBottom; 160 } 161 else 162 { 163 hint->csCoord = stemHint->min; 164 hint->flags = CF2_PairTop; 165 } 166 } 167 168 else 169 { 170 /* normal pair */ 171 172 if ( bottom ) 173 { 174 hint->csCoord = stemHint->min; 175 hint->flags = CF2_PairBottom; 176 } 177 else 178 { 179 hint->csCoord = stemHint->max; 180 hint->flags = CF2_PairTop; 181 } 182 } 183 184 /* Now that ghost hints have been detected, adjust this edge for */ 185 /* darkening. Bottoms are not changed; tops are incremented by twice */ 186 /* `darkenY'. */ 187 if ( cf2_hint_isTop( hint ) ) 188 hint->csCoord += 2 * font->darkenY; 189 190 hint->csCoord += hintOrigin; 191 hint->scale = scale; 192 hint->index = indexStemHint; /* index in original stem hint array */ 193 194 /* if original stem hint has been used, use the same position */ 195 if ( hint->flags != 0 && stemHint->used ) 196 { 197 if ( cf2_hint_isTop( hint ) ) 198 hint->dsCoord = stemHint->maxDS; 199 else 200 hint->dsCoord = stemHint->minDS; 201 202 cf2_hint_lock( hint ); 203 } 204 else 205 hint->dsCoord = FT_MulFix( hint->csCoord, scale ); 206 } 207 208 209 /* initialize an invalid hint map element */ 210 static void 211 cf2_hint_initZero( CF2_Hint hint ) 212 { 213 FT_ZERO( hint ); 214 } 215 216 217 FT_LOCAL_DEF( FT_Bool ) 218 cf2_hint_isValid( const CF2_Hint hint ) 219 { 220 return hint->flags != 0; 221 } 222 223 224 static FT_Bool 225 cf2_hint_isPair( const CF2_Hint hint ) 226 { 227 return ( hint->flags & 228 ( CF2_PairBottom | CF2_PairTop ) ) != 0; 229 } 230 231 232 static FT_Bool 233 cf2_hint_isPairTop( const CF2_Hint hint ) 234 { 235 return ( hint->flags & CF2_PairTop ) != 0; 236 } 237 238 239 FT_LOCAL_DEF( FT_Bool ) 240 cf2_hint_isTop( const CF2_Hint hint ) 241 { 242 return ( hint->flags & 243 ( CF2_PairTop | CF2_GhostTop ) ) != 0; 244 } 245 246 247 FT_LOCAL_DEF( FT_Bool ) 248 cf2_hint_isBottom( const CF2_Hint hint ) 249 { 250 return ( hint->flags & 251 ( CF2_PairBottom | CF2_GhostBottom ) ) != 0; 252 } 253 254 255 static FT_Bool 256 cf2_hint_isLocked( const CF2_Hint hint ) 257 { 258 return ( hint->flags & CF2_Locked ) != 0; 259 } 260 261 262 static FT_Bool 263 cf2_hint_isSynthetic( const CF2_Hint hint ) 264 { 265 return ( hint->flags & CF2_Synthetic ) != 0; 266 } 267 268 269 FT_LOCAL_DEF( void ) 270 cf2_hint_lock( CF2_Hint hint ) 271 { 272 hint->flags |= CF2_Locked; 273 } 274 275 276 FT_LOCAL_DEF( void ) 277 cf2_hintmap_init( CF2_HintMap hintmap, 278 CF2_Font font, 279 CF2_HintMap initialMap, 280 CF2_ArrStack hintMoves, 281 CF2_Fixed scale ) 282 { 283 FT_ZERO( hintmap ); 284 285 /* copy parameters from font instance */ 286 hintmap->hinted = font->hinted; 287 hintmap->scale = scale; 288 hintmap->font = font; 289 hintmap->initialHintMap = initialMap; 290 /* will clear in `cf2_hintmap_adjustHints' */ 291 hintmap->hintMoves = hintMoves; 292 } 293 294 295 static FT_Bool 296 cf2_hintmap_isValid( const CF2_HintMap hintmap ) 297 { 298 return hintmap->isValid; 299 } 300 301 302 /* transform character space coordinate to device space using hint map */ 303 static CF2_Fixed 304 cf2_hintmap_map( CF2_HintMap hintmap, 305 CF2_Fixed csCoord ) 306 { 307 FT_ASSERT( hintmap->isValid ); /* must call Build before Map */ 308 FT_ASSERT( hintmap->lastIndex < CF2_MAX_HINT_EDGES ); 309 310 if ( hintmap->count == 0 || ! hintmap->hinted ) 311 { 312 /* there are no hints; use uniform scale and zero offset */ 313 return FT_MulFix( csCoord, hintmap->scale ); 314 } 315 else 316 { 317 /* start linear search from last hit */ 318 CF2_UInt i = hintmap->lastIndex; 319 320 321 /* search up */ 322 while ( i < hintmap->count - 1 && 323 csCoord >= hintmap->edge[i + 1].csCoord ) 324 i += 1; 325 326 /* search down */ 327 while ( i > 0 && csCoord < hintmap->edge[i].csCoord ) 328 i -= 1; 329 330 hintmap->lastIndex = i; 331 332 if ( i == 0 && csCoord < hintmap->edge[0].csCoord ) 333 { 334 /* special case for points below first edge: use uniform scale */ 335 return FT_MulFix( csCoord - hintmap->edge[0].csCoord, 336 hintmap->scale ) + 337 hintmap->edge[0].dsCoord; 338 } 339 else 340 { 341 /* 342 * Note: entries with duplicate csCoord are allowed. 343 * Use edge[i], the highest entry where csCoord >= entry[i].csCoord 344 */ 345 return FT_MulFix( csCoord - hintmap->edge[i].csCoord, 346 hintmap->edge[i].scale ) + 347 hintmap->edge[i].dsCoord; 348 } 349 } 350 } 351 352 353 /* 354 * This hinting policy moves a hint pair in device space so that one of 355 * its two edges is on a device pixel boundary (its fractional part is 356 * zero). `cf2_hintmap_insertHint' guarantees no overlap in CS 357 * space. Ensure here that there is no overlap in DS. 358 * 359 * In the first pass, edges are adjusted relative to adjacent hints. 360 * Those that are below have already been adjusted. Those that are 361 * above have not yet been adjusted. If a hint above blocks an 362 * adjustment to an optimal position, we will try again in a second 363 * pass. The second pass is top-down. 364 * 365 */ 366 367 static void 368 cf2_hintmap_adjustHints( CF2_HintMap hintmap ) 369 { 370 size_t i, j; 371 372 373 cf2_arrstack_clear( hintmap->hintMoves ); /* working storage */ 374 375 /* 376 * First pass is bottom-up (font hint order) without look-ahead. 377 * Locked edges are already adjusted. 378 * Unlocked edges begin with dsCoord from `initialHintMap'. 379 * Save edges that are not optimally adjusted in `hintMoves' array, 380 * and process them in second pass. 381 */ 382 383 for ( i = 0; i < hintmap->count; i++ ) 384 { 385 FT_Bool isPair = cf2_hint_isPair( &hintmap->edge[i] ); 386 387 388 /* index of upper edge (same value for ghost hint) */ 389 j = isPair ? i + 1 : i; 390 391 FT_ASSERT( j < hintmap->count ); 392 FT_ASSERT( cf2_hint_isValid( &hintmap->edge[i] ) ); 393 FT_ASSERT( cf2_hint_isValid( &hintmap->edge[j] ) ); 394 FT_ASSERT( cf2_hint_isLocked( &hintmap->edge[i] ) == 395 cf2_hint_isLocked( &hintmap->edge[j] ) ); 396 397 if ( !cf2_hint_isLocked( &hintmap->edge[i] ) ) 398 { 399 /* hint edge is not locked, we can adjust it */ 400 CF2_Fixed fracDown = cf2_fixedFraction( hintmap->edge[i].dsCoord ); 401 CF2_Fixed fracUp = cf2_fixedFraction( hintmap->edge[j].dsCoord ); 402 403 /* calculate all four possibilities; moves down are negative */ 404 CF2_Fixed downMoveDown = 0 - fracDown; 405 CF2_Fixed upMoveDown = 0 - fracUp; 406 CF2_Fixed downMoveUp = fracDown == 0 407 ? 0 408 : cf2_intToFixed( 1 ) - fracDown; 409 CF2_Fixed upMoveUp = fracUp == 0 410 ? 0 411 : cf2_intToFixed( 1 ) - fracUp; 412 413 /* smallest move up */ 414 CF2_Fixed moveUp = FT_MIN( downMoveUp, upMoveUp ); 415 /* smallest move down */ 416 CF2_Fixed moveDown = FT_MAX( downMoveDown, upMoveDown ); 417 418 /* final amount to move edge or edge pair */ 419 CF2_Fixed move; 420 421 CF2_Fixed downMinCounter = CF2_MIN_COUNTER; 422 CF2_Fixed upMinCounter = CF2_MIN_COUNTER; 423 FT_Bool saveEdge = FALSE; 424 425 426 /* minimum counter constraint doesn't apply when adjacent edges */ 427 /* are synthetic */ 428 /* TODO: doesn't seem a big effect; for now, reduce the code */ 429 #if 0 430 if ( i == 0 || 431 cf2_hint_isSynthetic( &hintmap->edge[i - 1] ) ) 432 downMinCounter = 0; 433 434 if ( j >= hintmap->count - 1 || 435 cf2_hint_isSynthetic( &hintmap->edge[j + 1] ) ) 436 upMinCounter = 0; 437 #endif 438 439 /* is there room to move up? */ 440 /* there is if we are at top of array or the next edge is at or */ 441 /* beyond proposed move up? */ 442 if ( j >= hintmap->count - 1 || 443 hintmap->edge[j + 1].dsCoord >= 444 hintmap->edge[j].dsCoord + moveUp + upMinCounter ) 445 { 446 /* there is room to move up; is there also room to move down? */ 447 if ( i == 0 || 448 hintmap->edge[i - 1].dsCoord <= 449 hintmap->edge[i].dsCoord + moveDown - downMinCounter ) 450 { 451 /* move smaller absolute amount */ 452 move = ( -moveDown < moveUp ) ? moveDown : moveUp; /* optimum */ 453 } 454 else 455 move = moveUp; 456 } 457 else 458 { 459 /* is there room to move down? */ 460 if ( i == 0 || 461 hintmap->edge[i - 1].dsCoord <= 462 hintmap->edge[i].dsCoord + moveDown - downMinCounter ) 463 { 464 move = moveDown; 465 saveEdge = moveUp < -moveDown; /* true if non-optimum move */ 466 } 467 else 468 { 469 /* no room to move either way without overlapping or reducing */ 470 /* the counter too much */ 471 move = 0; 472 saveEdge = TRUE; 473 } 474 } 475 476 /* Identify non-moves and moves down that aren't optimal, and save */ 477 /* them for second pass. */ 478 /* Do this only if there is an unlocked edge above (which could */ 479 /* possibly move). */ 480 if ( saveEdge && 481 j < hintmap->count - 1 && 482 !cf2_hint_isLocked( &hintmap->edge[j + 1] ) ) 483 { 484 CF2_HintMoveRec savedMove; 485 486 487 savedMove.j = j; 488 /* desired adjustment in second pass */ 489 savedMove.moveUp = moveUp - move; 490 491 cf2_arrstack_push( hintmap->hintMoves, &savedMove ); 492 } 493 494 /* move the edge(s) */ 495 hintmap->edge[i].dsCoord += move; 496 if ( isPair ) 497 hintmap->edge[j].dsCoord += move; 498 } 499 500 /* assert there are no overlaps in device space */ 501 FT_ASSERT( i == 0 || 502 hintmap->edge[i - 1].dsCoord <= hintmap->edge[i].dsCoord ); 503 FT_ASSERT( i < j || 504 hintmap->edge[i].dsCoord <= hintmap->edge[j].dsCoord ); 505 506 /* adjust the scales, avoiding divide by zero */ 507 if ( i > 0 ) 508 { 509 if ( hintmap->edge[i].csCoord != hintmap->edge[i - 1].csCoord ) 510 hintmap->edge[i - 1].scale = 511 FT_DivFix( 512 hintmap->edge[i].dsCoord - hintmap->edge[i - 1].dsCoord, 513 hintmap->edge[i].csCoord - hintmap->edge[i - 1].csCoord ); 514 } 515 516 if ( isPair ) 517 { 518 if ( hintmap->edge[j].csCoord != hintmap->edge[j - 1].csCoord ) 519 hintmap->edge[j - 1].scale = 520 FT_DivFix( 521 hintmap->edge[j].dsCoord - hintmap->edge[j - 1].dsCoord, 522 hintmap->edge[j].csCoord - hintmap->edge[j - 1].csCoord ); 523 524 i += 1; /* skip upper edge on next loop */ 525 } 526 } 527 528 /* second pass tries to move non-optimal hints up, in case there is */ 529 /* room now */ 530 for ( i = cf2_arrstack_size( hintmap->hintMoves ); i > 0; i-- ) 531 { 532 CF2_HintMove hintMove = (CF2_HintMove) 533 cf2_arrstack_getPointer( hintmap->hintMoves, i - 1 ); 534 535 536 j = hintMove->j; 537 538 /* this was tested before the push, above */ 539 FT_ASSERT( j < hintmap->count - 1 ); 540 541 /* is there room to move up? */ 542 if ( hintmap->edge[j + 1].dsCoord >= 543 hintmap->edge[j].dsCoord + hintMove->moveUp + CF2_MIN_COUNTER ) 544 { 545 /* there is more room now, move edge up */ 546 hintmap->edge[j].dsCoord += hintMove->moveUp; 547 548 if ( cf2_hint_isPair( &hintmap->edge[j] ) ) 549 { 550 FT_ASSERT( j > 0 ); 551 hintmap->edge[j - 1].dsCoord += hintMove->moveUp; 552 } 553 } 554 } 555 } 556 557 558 /* insert hint edges into map, sorted by csCoord */ 559 static void 560 cf2_hintmap_insertHint( CF2_HintMap hintmap, 561 CF2_Hint bottomHintEdge, 562 CF2_Hint topHintEdge ) 563 { 564 CF2_UInt indexInsert; 565 566 /* set default values, then check for edge hints */ 567 FT_Bool isPair = TRUE; 568 CF2_Hint firstHintEdge = bottomHintEdge; 569 CF2_Hint secondHintEdge = topHintEdge; 570 571 572 /* one or none of the input params may be invalid when dealing with */ 573 /* edge hints; at least one edge must be valid */ 574 FT_ASSERT( cf2_hint_isValid( bottomHintEdge ) || 575 cf2_hint_isValid( topHintEdge ) ); 576 577 /* determine how many and which edges to insert */ 578 if ( !cf2_hint_isValid( bottomHintEdge ) ) 579 { 580 /* insert only the top edge */ 581 firstHintEdge = topHintEdge; 582 isPair = FALSE; 583 } 584 else if ( !cf2_hint_isValid( topHintEdge ) ) 585 { 586 /* insert only the bottom edge */ 587 isPair = FALSE; 588 } 589 590 /* paired edges must be in proper order */ 591 FT_ASSERT( !isPair || 592 topHintEdge->csCoord >= bottomHintEdge->csCoord ); 593 594 /* linear search to find index value of insertion point */ 595 indexInsert = 0; 596 for ( ; indexInsert < hintmap->count; indexInsert++ ) 597 { 598 if ( hintmap->edge[indexInsert].csCoord > firstHintEdge->csCoord ) 599 break; 600 } 601 602 /* 603 * Discard any hints that overlap in character space. Most often, 604 * this is while building the initial map, but in theory, it can also 605 * occur because of darkening. 606 * 607 */ 608 if ( indexInsert < hintmap->count ) 609 { 610 /* we are inserting before an existing edge: */ 611 /* verify that a new pair does not straddle the next edge */ 612 if ( isPair && 613 hintmap->edge[indexInsert].csCoord < secondHintEdge->csCoord ) 614 return; /* ignore overlapping stem hint */ 615 616 /* verify that we are not inserting between paired edges */ 617 if ( cf2_hint_isPairTop( &hintmap->edge[indexInsert] ) ) 618 return; /* ignore overlapping stem hint */ 619 } 620 621 /* recompute device space locations using initial hint map */ 622 if ( cf2_hintmap_isValid( hintmap->initialHintMap ) && 623 !cf2_hint_isLocked( firstHintEdge ) ) 624 { 625 if ( isPair ) 626 { 627 /* Use hint map to position the center of stem, and nominal scale */ 628 /* to position the two edges. This preserves the stem width. */ 629 CF2_Fixed midpoint = cf2_hintmap_map( 630 hintmap->initialHintMap, 631 ( secondHintEdge->csCoord + 632 firstHintEdge->csCoord ) / 2 ); 633 CF2_Fixed halfWidth = FT_MulFix( 634 ( secondHintEdge->csCoord - 635 firstHintEdge->csCoord ) / 2, 636 hintmap->scale ); 637 638 639 firstHintEdge->dsCoord = midpoint - halfWidth; 640 secondHintEdge->dsCoord = midpoint + halfWidth; 641 } 642 else 643 firstHintEdge->dsCoord = cf2_hintmap_map( hintmap->initialHintMap, 644 firstHintEdge->csCoord ); 645 } 646 647 /* discard any hints that overlap in device space; this can occur */ 648 /* because locked hints have been moved to align with blue zones */ 649 if ( indexInsert > 0 ) 650 { 651 /* we are inserting after an existing edge */ 652 if ( firstHintEdge->dsCoord < hintmap->edge[indexInsert - 1].dsCoord ) 653 return; 654 } 655 656 if ( indexInsert < hintmap->count ) 657 { 658 /* we are inserting before an existing edge */ 659 if ( isPair ) 660 { 661 if ( secondHintEdge->dsCoord > hintmap->edge[indexInsert].dsCoord ) 662 return; 663 } 664 else 665 { 666 if ( firstHintEdge->dsCoord > hintmap->edge[indexInsert].dsCoord ) 667 return; 668 } 669 } 670 671 /* make room to insert */ 672 { 673 CF2_Int iSrc = hintmap->count - 1; 674 CF2_Int iDst = isPair ? hintmap->count + 1 : hintmap->count; 675 676 CF2_Int count = hintmap->count - indexInsert; 677 678 679 if ( iDst >= CF2_MAX_HINT_EDGES ) 680 { 681 FT_TRACE4(( "cf2_hintmap_insertHint: too many hintmaps\n" )); 682 return; 683 } 684 685 while ( count-- ) 686 hintmap->edge[iDst--] = hintmap->edge[iSrc--]; 687 688 /* insert first edge */ 689 hintmap->edge[indexInsert] = *firstHintEdge; /* copy struct */ 690 hintmap->count += 1; 691 692 if ( isPair ) 693 { 694 /* insert second edge */ 695 hintmap->edge[indexInsert + 1] = *secondHintEdge; /* copy struct */ 696 hintmap->count += 1; 697 } 698 } 699 700 return; 701 } 702 703 704 /* 705 * Build a map from hints and mask. 706 * 707 * This function may recur one level if `hintmap->initialHintMap' is not yet 708 * valid. 709 * If `initialMap' is true, simply build initial map. 710 * 711 * Synthetic hints are used in two ways. A hint at zero is inserted, if 712 * needed, in the initial hint map, to prevent translations from 713 * propagating across the origin. If synthetic em box hints are enabled 714 * for ideographic dictionaries, then they are inserted in all hint 715 * maps, including the initial one. 716 * 717 */ 718 FT_LOCAL_DEF( void ) 719 cf2_hintmap_build( CF2_HintMap hintmap, 720 CF2_ArrStack hStemHintArray, 721 CF2_ArrStack vStemHintArray, 722 CF2_HintMask hintMask, 723 CF2_Fixed hintOrigin, 724 FT_Bool initialMap ) 725 { 726 FT_Byte* maskPtr; 727 728 CF2_Font font = hintmap->font; 729 CF2_HintMaskRec tempHintMask; 730 731 size_t bitCount, i; 732 FT_Byte maskByte; 733 734 735 /* check whether initial map is constructed */ 736 if ( !initialMap && !cf2_hintmap_isValid( hintmap->initialHintMap ) ) 737 { 738 /* make recursive call with initialHintMap and temporary mask; */ 739 /* temporary mask will get all bits set, below */ 740 cf2_hintmask_init( &tempHintMask, hintMask->error ); 741 cf2_hintmap_build( hintmap->initialHintMap, 742 hStemHintArray, 743 vStemHintArray, 744 &tempHintMask, 745 hintOrigin, 746 TRUE ); 747 } 748 749 if ( !cf2_hintmask_isValid( hintMask ) ) 750 { 751 /* without a hint mask, assume all hints are active */ 752 cf2_hintmask_setAll( hintMask, 753 cf2_arrstack_size( hStemHintArray ) + 754 cf2_arrstack_size( vStemHintArray ) ); 755 } 756 757 /* begin by clearing the map */ 758 hintmap->count = 0; 759 hintmap->lastIndex = 0; 760 761 /* make a copy of the hint mask so we can modify it */ 762 tempHintMask = *hintMask; 763 maskPtr = cf2_hintmask_getMaskPtr( &tempHintMask ); 764 765 /* use the hStem hints only, which are first in the mask */ 766 /* TODO: compare this to cffhintmaskGetBitCount */ 767 bitCount = cf2_arrstack_size( hStemHintArray ); 768 769 /* synthetic embox hints get highest priority */ 770 if ( font->blues.doEmBoxHints ) 771 { 772 CF2_HintRec dummy; 773 774 775 cf2_hint_initZero( &dummy ); /* invalid hint map element */ 776 777 /* ghost bottom */ 778 cf2_hintmap_insertHint( hintmap, 779 &font->blues.emBoxBottomEdge, 780 &dummy ); 781 /* ghost top */ 782 cf2_hintmap_insertHint( hintmap, 783 &dummy, 784 &font->blues.emBoxTopEdge ); 785 } 786 787 /* insert hints captured by a blue zone or already locked (higher */ 788 /* priority) */ 789 for ( i = 0, maskByte = 0x80; i < bitCount; i++ ) 790 { 791 if ( maskByte & *maskPtr ) 792 { 793 /* expand StemHint into two `CF2_Hint' elements */ 794 CF2_HintRec bottomHintEdge, topHintEdge; 795 796 797 cf2_hint_init( &bottomHintEdge, 798 hStemHintArray, 799 i, 800 font, 801 hintOrigin, 802 hintmap->scale, 803 TRUE /* bottom */ ); 804 cf2_hint_init( &topHintEdge, 805 hStemHintArray, 806 i, 807 font, 808 hintOrigin, 809 hintmap->scale, 810 FALSE /* top */ ); 811 812 if ( cf2_hint_isLocked( &bottomHintEdge ) || 813 cf2_hint_isLocked( &topHintEdge ) || 814 cf2_blues_capture( &font->blues, 815 &bottomHintEdge, 816 &topHintEdge ) ) 817 { 818 /* insert captured hint into map */ 819 cf2_hintmap_insertHint( hintmap, &bottomHintEdge, &topHintEdge ); 820 821 *maskPtr &= ~maskByte; /* turn off the bit for this hint */ 822 } 823 } 824 825 if ( ( i & 7 ) == 7 ) 826 { 827 /* move to next mask byte */ 828 maskPtr++; 829 maskByte = 0x80; 830 } 831 else 832 maskByte >>= 1; 833 } 834 835 /* initial hint map includes only captured hints plus maybe one at 0 */ 836 837 /* 838 * TODO: There is a problem here because we are trying to build a 839 * single hint map containing all captured hints. It is 840 * possible for there to be conflicts between captured hints, 841 * either because of darkening or because the hints are in 842 * separate hint zones (we are ignoring hint zones for the 843 * initial map). An example of the latter is MinionPro-Regular 844 * v2.030 glyph 883 (Greek Capital Alpha with Psili) at 15ppem. 845 * A stem hint for the psili conflicts with the top edge hint 846 * for the base character. The stem hint gets priority because 847 * of its sort order. In glyph 884 (Greek Capital Alpha with 848 * Psili and Oxia), the top of the base character gets a stem 849 * hint, and the psili does not. This creates different initial 850 * maps for the two glyphs resulting in different renderings of 851 * the base character. Will probably defer this either as not 852 * worth the cost or as a font bug. I don't think there is any 853 * good reason for an accent to be captured by an alignment 854 * zone. -darnold 2/12/10 855 */ 856 857 if ( initialMap ) 858 { 859 /* Apply a heuristic that inserts a point for (0,0), unless it's */ 860 /* already covered by a mapping. This locks the baseline for glyphs */ 861 /* that have no baseline hints. */ 862 863 if ( hintmap->count == 0 || 864 hintmap->edge[0].csCoord > 0 || 865 hintmap->edge[hintmap->count - 1].csCoord < 0 ) 866 { 867 /* all edges are above 0 or all edges are below 0; */ 868 /* construct a locked edge hint at 0 */ 869 870 CF2_HintRec edge, invalid; 871 872 873 cf2_hint_initZero( &edge ); 874 875 edge.flags = CF2_GhostBottom | 876 CF2_Locked | 877 CF2_Synthetic; 878 edge.scale = hintmap->scale; 879 880 cf2_hint_initZero( &invalid ); 881 cf2_hintmap_insertHint( hintmap, &edge, &invalid ); 882 } 883 } 884 else 885 { 886 /* insert remaining hints */ 887 888 maskPtr = cf2_hintmask_getMaskPtr( &tempHintMask ); 889 890 for ( i = 0, maskByte = 0x80; i < bitCount; i++ ) 891 { 892 if ( maskByte & *maskPtr ) 893 { 894 CF2_HintRec bottomHintEdge, topHintEdge; 895 896 897 cf2_hint_init( &bottomHintEdge, 898 hStemHintArray, 899 i, 900 font, 901 hintOrigin, 902 hintmap->scale, 903 TRUE /* bottom */ ); 904 cf2_hint_init( &topHintEdge, 905 hStemHintArray, 906 i, 907 font, 908 hintOrigin, 909 hintmap->scale, 910 FALSE /* top */ ); 911 912 cf2_hintmap_insertHint( hintmap, &bottomHintEdge, &topHintEdge ); 913 } 914 915 if ( ( i & 7 ) == 7 ) 916 { 917 /* move to next mask byte */ 918 maskPtr++; 919 maskByte = 0x80; 920 } 921 else 922 maskByte >>= 1; 923 } 924 } 925 926 /* 927 * Note: The following line is a convenient place to break when 928 * debugging hinting. Examine `hintmap->edge' for the list of 929 * enabled hints, then step over the call to see the effect of 930 * adjustment. We stop here first on the recursive call that 931 * creates the initial map, and then on each counter group and 932 * hint zone. 933 */ 934 935 /* adjust positions of hint edges that are not locked to blue zones */ 936 cf2_hintmap_adjustHints( hintmap ); 937 938 /* save the position of all hints that were used in this hint map; */ 939 /* if we use them again, we'll locate them in the same position */ 940 if ( !initialMap ) 941 { 942 for ( i = 0; i < hintmap->count; i++ ) 943 { 944 if ( !cf2_hint_isSynthetic( &hintmap->edge[i] ) ) 945 { 946 /* Note: include both valid and invalid edges */ 947 /* Note: top and bottom edges are copied back separately */ 948 CF2_StemHint stemhint = (CF2_StemHint) 949 cf2_arrstack_getPointer( hStemHintArray, 950 hintmap->edge[i].index ); 951 952 953 if ( cf2_hint_isTop( &hintmap->edge[i] ) ) 954 stemhint->maxDS = hintmap->edge[i].dsCoord; 955 else 956 stemhint->minDS = hintmap->edge[i].dsCoord; 957 958 stemhint->used = TRUE; 959 } 960 } 961 } 962 963 /* hint map is ready to use */ 964 hintmap->isValid = TRUE; 965 966 /* remember this mask has been used */ 967 cf2_hintmask_setNew( hintMask, FALSE ); 968 } 969 970 971 FT_LOCAL_DEF( void ) 972 cf2_glyphpath_init( CF2_GlyphPath glyphpath, 973 CF2_Font font, 974 CF2_OutlineCallbacks callbacks, 975 CF2_Fixed scaleY, 976 /* CF2_Fixed hShift, */ 977 CF2_ArrStack hStemHintArray, 978 CF2_ArrStack vStemHintArray, 979 CF2_HintMask hintMask, 980 CF2_Fixed hintOriginY, 981 const CF2_Blues blues, 982 const FT_Vector* fractionalTranslation ) 983 { 984 FT_ZERO( glyphpath ); 985 986 glyphpath->font = font; 987 glyphpath->callbacks = callbacks; 988 989 cf2_arrstack_init( &glyphpath->hintMoves, 990 font->memory, 991 &font->error, 992 sizeof ( CF2_HintMoveRec ) ); 993 994 cf2_hintmap_init( &glyphpath->initialHintMap, 995 font, 996 &glyphpath->initialHintMap, 997 &glyphpath->hintMoves, 998 scaleY ); 999 cf2_hintmap_init( &glyphpath->firstHintMap, 1000 font, 1001 &glyphpath->initialHintMap, 1002 &glyphpath->hintMoves, 1003 scaleY ); 1004 cf2_hintmap_init( &glyphpath->hintMap, 1005 font, 1006 &glyphpath->initialHintMap, 1007 &glyphpath->hintMoves, 1008 scaleY ); 1009 1010 glyphpath->scaleX = font->innerTransform.a; 1011 glyphpath->scaleC = font->innerTransform.c; 1012 glyphpath->scaleY = font->innerTransform.d; 1013 1014 glyphpath->fractionalTranslation = *fractionalTranslation; 1015 1016 #if 0 1017 glyphpath->hShift = hShift; /* for fauxing */ 1018 #endif 1019 1020 glyphpath->hStemHintArray = hStemHintArray; 1021 glyphpath->vStemHintArray = vStemHintArray; 1022 glyphpath->hintMask = hintMask; /* ptr to current mask */ 1023 glyphpath->hintOriginY = hintOriginY; 1024 glyphpath->blues = blues; 1025 glyphpath->darken = font->darkened; /* TODO: should we make copies? */ 1026 glyphpath->xOffset = font->darkenX; 1027 glyphpath->yOffset = font->darkenY; 1028 glyphpath->miterLimit = 2 * FT_MAX( 1029 cf2_fixedAbs( glyphpath->xOffset ), 1030 cf2_fixedAbs( glyphpath->yOffset ) ); 1031 1032 /* .1 character space unit */ 1033 glyphpath->snapThreshold = cf2_floatToFixed( 0.1f ); 1034 1035 glyphpath->moveIsPending = TRUE; 1036 glyphpath->pathIsOpen = FALSE; 1037 glyphpath->elemIsQueued = FALSE; 1038 } 1039 1040 1041 FT_LOCAL_DEF( void ) 1042 cf2_glyphpath_finalize( CF2_GlyphPath glyphpath ) 1043 { 1044 cf2_arrstack_finalize( &glyphpath->hintMoves ); 1045 } 1046 1047 1048 /* 1049 * Hint point in y-direction and apply outerTransform. 1050 * Input `current' hint map (which is actually delayed by one element). 1051 * Input x,y point in Character Space. 1052 * Output x,y point in Device Space, including translation. 1053 */ 1054 static void 1055 cf2_glyphpath_hintPoint( CF2_GlyphPath glyphpath, 1056 CF2_HintMap hintmap, 1057 FT_Vector* ppt, 1058 CF2_Fixed x, 1059 CF2_Fixed y ) 1060 { 1061 FT_Vector pt; /* hinted point in upright DS */ 1062 1063 1064 pt.x = FT_MulFix( glyphpath->scaleX, x ) + 1065 FT_MulFix( glyphpath->scaleC, y ); 1066 pt.y = cf2_hintmap_map( hintmap, y ); 1067 1068 ppt->x = FT_MulFix( glyphpath->font->outerTransform.a, pt.x ) + 1069 FT_MulFix( glyphpath->font->outerTransform.c, pt.y ) + 1070 glyphpath->fractionalTranslation.x; 1071 ppt->y = FT_MulFix( glyphpath->font->outerTransform.b, pt.x ) + 1072 FT_MulFix( glyphpath->font->outerTransform.d, pt.y ) + 1073 glyphpath->fractionalTranslation.y; 1074 } 1075 1076 1077 /* 1078 * From two line segments, (u1,u2) and (v1,v2), compute a point of 1079 * intersection on the corresponding lines. 1080 * Return false if no intersection is found, or if the intersection is 1081 * too far away from the ends of the line segments, u2 and v1. 1082 * 1083 */ 1084 static FT_Bool 1085 cf2_glyphpath_computeIntersection( CF2_GlyphPath glyphpath, 1086 const FT_Vector* u1, 1087 const FT_Vector* u2, 1088 const FT_Vector* v1, 1089 const FT_Vector* v2, 1090 FT_Vector* intersection ) 1091 { 1092 /* 1093 * Let `u' be a zero-based vector from the first segment, `v' from the 1094 * second segment. 1095 * Let `w 'be the zero-based vector from `u1' to `v1'. 1096 * `perp' is the `perpendicular dot product'; see 1097 * http://mathworld.wolfram.com/PerpDotProduct.html. 1098 * `s' is the parameter for the parametric line for the first segment 1099 * (`u'). 1100 * 1101 * See notation in 1102 * http://softsurfer.com/Archive/algorithm_0104/algorithm_0104B.htm. 1103 * Calculations are done in 16.16, but must handle the squaring of 1104 * line lengths in character space. We scale all vectors by 1/32 to 1105 * avoid overflow. This allows values up to 4095 to be squared. The 1106 * scale factor cancels in the divide. 1107 * 1108 * TODO: the scale factor could be computed from UnitsPerEm. 1109 * 1110 */ 1111 1112 #define cf2_perp( a, b ) \ 1113 ( FT_MulFix( a.x, b.y ) - FT_MulFix( a.y, b.x ) ) 1114 1115 /* round and divide by 32 */ 1116 #define CF2_CS_SCALE( x ) \ 1117 ( ( (x) + 0x10 ) >> 5 ) 1118 1119 FT_Vector u, v, w; /* scaled vectors */ 1120 CF2_Fixed denominator, s; 1121 1122 1123 u.x = CF2_CS_SCALE( u2->x - u1->x ); 1124 u.y = CF2_CS_SCALE( u2->y - u1->y ); 1125 v.x = CF2_CS_SCALE( v2->x - v1->x ); 1126 v.y = CF2_CS_SCALE( v2->y - v1->y ); 1127 w.x = CF2_CS_SCALE( v1->x - u1->x ); 1128 w.y = CF2_CS_SCALE( v1->y - u1->y ); 1129 1130 denominator = cf2_perp( u, v ); 1131 1132 if ( denominator == 0 ) 1133 return FALSE; /* parallel or coincident lines */ 1134 1135 s = FT_DivFix( cf2_perp( w, v ), denominator ); 1136 1137 intersection->x = u1->x + FT_MulFix( s, u2->x - u1->x ); 1138 intersection->y = u1->y + FT_MulFix( s, u2->y - u1->y ); 1139 1140 /* 1141 * Special case snapping for horizontal and vertical lines. 1142 * This cleans up intersections and reduces problems with winding 1143 * order detection. 1144 * Sample case is sbc cd KozGoPr6N-Medium.otf 20 16685. 1145 * Note: these calculations are in character space. 1146 * 1147 */ 1148 1149 if ( u1->x == u2->x && 1150 cf2_fixedAbs( intersection->x - u1->x ) < glyphpath->snapThreshold ) 1151 intersection->x = u1->x; 1152 if ( u1->y == u2->y && 1153 cf2_fixedAbs( intersection->y - u1->y ) < glyphpath->snapThreshold ) 1154 intersection->y = u1->y; 1155 1156 if ( v1->x == v2->x && 1157 cf2_fixedAbs( intersection->x - v1->x ) < glyphpath->snapThreshold ) 1158 intersection->x = v1->x; 1159 if ( v1->y == v2->y && 1160 cf2_fixedAbs( intersection->y - v1->y ) < glyphpath->snapThreshold ) 1161 intersection->y = v1->y; 1162 1163 /* limit the intersection distance from midpoint of u2 and v1 */ 1164 if ( cf2_fixedAbs( intersection->x - ( u2->x + v1->x ) / 2 ) > 1165 glyphpath->miterLimit || 1166 cf2_fixedAbs( intersection->y - ( u2->y + v1->y ) / 2 ) > 1167 glyphpath->miterLimit ) 1168 return FALSE; 1169 1170 return TRUE; 1171 } 1172 1173 1174 /* 1175 * Push the cached element (glyphpath->prevElem*) to the outline 1176 * consumer. When a darkening offset is used, the end point of the 1177 * cached element may be adjusted to an intersection point or it may be 1178 * connected by a line to the current element. This calculation must 1179 * use a HintMap that was valid at the time the element was saved. For 1180 * the first point in a subpath, that is a saved HintMap. For most 1181 * elements, it just means the caller has delayed building a HintMap 1182 * from the current HintMask. 1183 * 1184 * Transform each point with outerTransform and call the outline 1185 * callbacks. This is a general 3x3 transform: 1186 * 1187 * x' = a*x + c*y + tx, y' = b*x + d*y + ty 1188 * 1189 * but it uses 4 elements from CF2_Font and the translation part 1190 * from CF2_GlyphPath. 1191 * 1192 */ 1193 static void 1194 cf2_glyphpath_pushPrevElem( CF2_GlyphPath glyphpath, 1195 CF2_HintMap hintmap, 1196 FT_Vector* nextP0, 1197 FT_Vector nextP1, 1198 FT_Bool close ) 1199 { 1200 CF2_CallbackParamsRec params; 1201 1202 FT_Vector* prevP0; 1203 FT_Vector* prevP1; 1204 1205 FT_Vector intersection = { 0, 0 }; 1206 FT_Bool useIntersection = FALSE; 1207 1208 1209 FT_ASSERT( glyphpath->prevElemOp == CF2_PathOpLineTo || 1210 glyphpath->prevElemOp == CF2_PathOpCubeTo ); 1211 1212 if ( glyphpath->prevElemOp == CF2_PathOpLineTo ) 1213 { 1214 prevP0 = &glyphpath->prevElemP0; 1215 prevP1 = &glyphpath->prevElemP1; 1216 } 1217 else 1218 { 1219 prevP0 = &glyphpath->prevElemP2; 1220 prevP1 = &glyphpath->prevElemP3; 1221 } 1222 1223 /* optimization: if previous and next elements are offset by the same */ 1224 /* amount, then there will be no gap, and no need to compute an */ 1225 /* intersection. */ 1226 if ( prevP1->x != nextP0->x || prevP1->y != nextP0->y ) 1227 { 1228 /* previous element does not join next element: */ 1229 /* adjust end point of previous element to the intersection */ 1230 useIntersection = cf2_glyphpath_computeIntersection( glyphpath, 1231 prevP0, 1232 prevP1, 1233 nextP0, 1234 &nextP1, 1235 &intersection ); 1236 if ( useIntersection ) 1237 { 1238 /* modify the last point of the cached element (either line or */ 1239 /* curve) */ 1240 *prevP1 = intersection; 1241 } 1242 } 1243 1244 params.pt0 = glyphpath->currentDS; 1245 1246 switch( glyphpath->prevElemOp ) 1247 { 1248 case CF2_PathOpLineTo: 1249 params.op = CF2_PathOpLineTo; 1250 1251 /* note: pt2 and pt3 are unused */ 1252 cf2_glyphpath_hintPoint( glyphpath, 1253 hintmap, 1254 ¶ms.pt1, 1255 glyphpath->prevElemP1.x, 1256 glyphpath->prevElemP1.y ); 1257 1258 glyphpath->callbacks->lineTo( glyphpath->callbacks, ¶ms ); 1259 1260 glyphpath->currentDS = params.pt1; 1261 1262 break; 1263 1264 case CF2_PathOpCubeTo: 1265 params.op = CF2_PathOpCubeTo; 1266 1267 /* TODO: should we intersect the interior joins (p1-p2 and p2-p3)? */ 1268 cf2_glyphpath_hintPoint( glyphpath, 1269 hintmap, 1270 ¶ms.pt1, 1271 glyphpath->prevElemP1.x, 1272 glyphpath->prevElemP1.y ); 1273 cf2_glyphpath_hintPoint( glyphpath, 1274 hintmap, 1275 ¶ms.pt2, 1276 glyphpath->prevElemP2.x, 1277 glyphpath->prevElemP2.y ); 1278 cf2_glyphpath_hintPoint( glyphpath, 1279 hintmap, 1280 ¶ms.pt3, 1281 glyphpath->prevElemP3.x, 1282 glyphpath->prevElemP3.y ); 1283 1284 glyphpath->callbacks->cubeTo( glyphpath->callbacks, ¶ms ); 1285 1286 glyphpath->currentDS = params.pt3; 1287 1288 break; 1289 } 1290 1291 if ( !useIntersection || close ) 1292 { 1293 /* insert connecting line between end of previous element and start */ 1294 /* of current one */ 1295 /* note: at the end of a subpath, we might do both, so use `nextP0' */ 1296 /* before we change it, below */ 1297 1298 cf2_glyphpath_hintPoint( glyphpath, 1299 hintmap, 1300 ¶ms.pt1, 1301 nextP0->x, 1302 nextP0->y ); 1303 1304 if ( params.pt1.x != glyphpath->currentDS.x || 1305 params.pt1.y != glyphpath->currentDS.y ) 1306 { 1307 /* length is nonzero */ 1308 params.op = CF2_PathOpLineTo; 1309 params.pt0 = glyphpath->currentDS; 1310 1311 /* note: pt2 and pt3 are unused */ 1312 glyphpath->callbacks->lineTo( glyphpath->callbacks, ¶ms ); 1313 1314 glyphpath->currentDS = params.pt1; 1315 } 1316 } 1317 1318 if ( useIntersection ) 1319 { 1320 /* return intersection point to caller */ 1321 *nextP0 = intersection; 1322 } 1323 } 1324 1325 1326 /* push a MoveTo element based on current point and offset of current */ 1327 /* element */ 1328 static void 1329 cf2_glyphpath_pushMove( CF2_GlyphPath glyphpath, 1330 FT_Vector start ) 1331 { 1332 CF2_CallbackParamsRec params; 1333 1334 1335 params.op = CF2_PathOpMoveTo; 1336 params.pt0 = glyphpath->currentDS; 1337 1338 /* Test if move has really happened yet; it would have called */ 1339 /* `cf2_hintmap_build' to set `isValid'. */ 1340 if ( !cf2_hintmap_isValid( &glyphpath->hintMap ) ) 1341 { 1342 /* we are here iff first subpath is missing a moveto operator: */ 1343 /* synthesize first moveTo to finish initialization of hintMap */ 1344 cf2_glyphpath_moveTo( glyphpath, 1345 glyphpath->start.x, 1346 glyphpath->start.y ); 1347 } 1348 1349 cf2_glyphpath_hintPoint( glyphpath, 1350 &glyphpath->hintMap, 1351 ¶ms.pt1, 1352 start.x, 1353 start.y ); 1354 1355 /* note: pt2 and pt3 are unused */ 1356 glyphpath->callbacks->moveTo( glyphpath->callbacks, ¶ms ); 1357 1358 glyphpath->currentDS = params.pt1; 1359 glyphpath->offsetStart0 = start; 1360 } 1361 1362 1363 /* 1364 * All coordinates are in character space. 1365 * On input, (x1, y1) and (x2, y2) give line segment. 1366 * On output, (x, y) give offset vector. 1367 * We use a piecewise approximation to trig functions. 1368 * 1369 * TODO: Offset true perpendicular and proper length 1370 * supply the y-translation for hinting here, too, 1371 * that adds yOffset unconditionally to *y. 1372 */ 1373 static void 1374 cf2_glyphpath_computeOffset( CF2_GlyphPath glyphpath, 1375 CF2_Fixed x1, 1376 CF2_Fixed y1, 1377 CF2_Fixed x2, 1378 CF2_Fixed y2, 1379 CF2_Fixed* x, 1380 CF2_Fixed* y ) 1381 { 1382 CF2_Fixed dx = x2 - x1; 1383 CF2_Fixed dy = y2 - y1; 1384 1385 1386 /* note: negative offsets don't work here; negate deltas to change */ 1387 /* quadrants, below */ 1388 if ( glyphpath->font->reverseWinding ) 1389 { 1390 dx = -dx; 1391 dy = -dy; 1392 } 1393 1394 *x = *y = 0; 1395 1396 if ( !glyphpath->darken ) 1397 return; 1398 1399 /* add momentum for this path element */ 1400 glyphpath->callbacks->windingMomentum += 1401 cf2_getWindingMomentum( x1, y1, x2, y2 ); 1402 1403 /* note: allow mixed integer and fixed multiplication here */ 1404 if ( dx >= 0 ) 1405 { 1406 if ( dy >= 0 ) 1407 { 1408 /* first quadrant, +x +y */ 1409 1410 if ( dx > 2 * dy ) 1411 { 1412 /* +x */ 1413 *x = 0; 1414 *y = 0; 1415 } 1416 else if ( dy > 2 * dx ) 1417 { 1418 /* +y */ 1419 *x = glyphpath->xOffset; 1420 *y = glyphpath->yOffset; 1421 } 1422 else 1423 { 1424 /* +x +y */ 1425 *x = FT_MulFix( cf2_floatToFixed( 0.7 ), 1426 glyphpath->xOffset ); 1427 *y = FT_MulFix( cf2_floatToFixed( 1.0 - 0.7 ), 1428 glyphpath->yOffset ); 1429 } 1430 } 1431 else 1432 { 1433 /* fourth quadrant, +x -y */ 1434 1435 if ( dx > -2 * dy ) 1436 { 1437 /* +x */ 1438 *x = 0; 1439 *y = 0; 1440 } 1441 else if ( -dy > 2 * dx ) 1442 { 1443 /* -y */ 1444 *x = -glyphpath->xOffset; 1445 *y = glyphpath->yOffset; 1446 } 1447 else 1448 { 1449 /* +x -y */ 1450 *x = FT_MulFix( cf2_floatToFixed( -0.7 ), 1451 glyphpath->xOffset ); 1452 *y = FT_MulFix( cf2_floatToFixed( 1.0 - 0.7 ), 1453 glyphpath->yOffset ); 1454 } 1455 } 1456 } 1457 else 1458 { 1459 if ( dy >= 0 ) 1460 { 1461 /* second quadrant, -x +y */ 1462 1463 if ( -dx > 2 * dy ) 1464 { 1465 /* -x */ 1466 *x = 0; 1467 *y = 2 * glyphpath->yOffset; 1468 } 1469 else if ( dy > -2 * dx ) 1470 { 1471 /* +y */ 1472 *x = glyphpath->xOffset; 1473 *y = glyphpath->yOffset; 1474 } 1475 else 1476 { 1477 /* -x +y */ 1478 *x = FT_MulFix( cf2_floatToFixed( 0.7 ), 1479 glyphpath->xOffset ); 1480 *y = FT_MulFix( cf2_floatToFixed( 1.0 + 0.7 ), 1481 glyphpath->yOffset ); 1482 } 1483 } 1484 else 1485 { 1486 /* third quadrant, -x -y */ 1487 1488 if ( -dx > -2 * dy ) 1489 { 1490 /* -x */ 1491 *x = 0; 1492 *y = 2 * glyphpath->yOffset; 1493 } 1494 else if ( -dy > -2 * dx ) 1495 { 1496 /* -y */ 1497 *x = -glyphpath->xOffset; 1498 *y = glyphpath->xOffset; 1499 } 1500 else 1501 { 1502 /* -x -y */ 1503 *x = FT_MulFix( cf2_floatToFixed( -0.7 ), 1504 glyphpath->xOffset ); 1505 *y = FT_MulFix( cf2_floatToFixed( 1.0 + 0.7 ), 1506 glyphpath->yOffset ); 1507 } 1508 } 1509 } 1510 } 1511 1512 1513 FT_LOCAL_DEF( void ) 1514 cf2_glyphpath_moveTo( CF2_GlyphPath glyphpath, 1515 CF2_Fixed x, 1516 CF2_Fixed y ) 1517 { 1518 cf2_glyphpath_closeOpenPath( glyphpath ); 1519 1520 /* save the parameters of the move for later, when we'll know how to */ 1521 /* offset it; */ 1522 /* also save last move point */ 1523 glyphpath->currentCS.x = glyphpath->start.x = x; 1524 glyphpath->currentCS.y = glyphpath->start.y = y; 1525 1526 glyphpath->moveIsPending = TRUE; 1527 1528 /* ensure we have a valid map with current mask */ 1529 if ( !cf2_hintmap_isValid( &glyphpath->hintMap ) || 1530 cf2_hintmask_isNew( glyphpath->hintMask ) ) 1531 cf2_hintmap_build( &glyphpath->hintMap, 1532 glyphpath->hStemHintArray, 1533 glyphpath->vStemHintArray, 1534 glyphpath->hintMask, 1535 glyphpath->hintOriginY, 1536 FALSE ); 1537 1538 /* save a copy of current HintMap to use when drawing initial point */ 1539 glyphpath->firstHintMap = glyphpath->hintMap; /* structure copy */ 1540 } 1541 1542 1543 FT_LOCAL_DEF( void ) 1544 cf2_glyphpath_lineTo( CF2_GlyphPath glyphpath, 1545 CF2_Fixed x, 1546 CF2_Fixed y ) 1547 { 1548 CF2_Fixed xOffset, yOffset; 1549 FT_Vector P0, P1; 1550 1551 1552 /* can't compute offset of zero length line, so ignore them */ 1553 if ( glyphpath->currentCS.x == x && glyphpath->currentCS.y == y ) 1554 return; 1555 1556 cf2_glyphpath_computeOffset( glyphpath, 1557 glyphpath->currentCS.x, 1558 glyphpath->currentCS.y, 1559 x, 1560 y, 1561 &xOffset, 1562 &yOffset ); 1563 1564 /* construct offset points */ 1565 P0.x = glyphpath->currentCS.x + xOffset; 1566 P0.y = glyphpath->currentCS.y + yOffset; 1567 P1.x = x + xOffset; 1568 P1.y = y + yOffset; 1569 1570 if ( glyphpath->moveIsPending ) 1571 { 1572 /* emit offset 1st point as MoveTo */ 1573 cf2_glyphpath_pushMove( glyphpath, P0 ); 1574 1575 glyphpath->moveIsPending = FALSE; /* adjust state machine */ 1576 glyphpath->pathIsOpen = TRUE; 1577 1578 glyphpath->offsetStart1 = P1; /* record second point */ 1579 } 1580 1581 if ( glyphpath->elemIsQueued ) 1582 { 1583 FT_ASSERT( cf2_hintmap_isValid( &glyphpath->hintMap ) ); 1584 1585 cf2_glyphpath_pushPrevElem( glyphpath, 1586 &glyphpath->hintMap, 1587 &P0, 1588 P1, 1589 FALSE ); 1590 } 1591 1592 /* queue the current element with offset points */ 1593 glyphpath->elemIsQueued = TRUE; 1594 glyphpath->prevElemOp = CF2_PathOpLineTo; 1595 glyphpath->prevElemP0 = P0; 1596 glyphpath->prevElemP1 = P1; 1597 1598 /* update current map */ 1599 if ( cf2_hintmask_isNew( glyphpath->hintMask ) ) 1600 cf2_hintmap_build( &glyphpath->hintMap, 1601 glyphpath->hStemHintArray, 1602 glyphpath->vStemHintArray, 1603 glyphpath->hintMask, 1604 glyphpath->hintOriginY, 1605 FALSE ); 1606 1607 glyphpath->currentCS.x = x; /* pre-offset current point */ 1608 glyphpath->currentCS.y = y; 1609 } 1610 1611 1612 FT_LOCAL_DEF( void ) 1613 cf2_glyphpath_curveTo( CF2_GlyphPath glyphpath, 1614 CF2_Fixed x1, 1615 CF2_Fixed y1, 1616 CF2_Fixed x2, 1617 CF2_Fixed y2, 1618 CF2_Fixed x3, 1619 CF2_Fixed y3 ) 1620 { 1621 CF2_Fixed xOffset1, yOffset1, xOffset3, yOffset3; 1622 FT_Vector P0, P1, P2, P3; 1623 1624 1625 /* TODO: ignore zero length portions of curve?? */ 1626 cf2_glyphpath_computeOffset( glyphpath, 1627 glyphpath->currentCS.x, 1628 glyphpath->currentCS.y, 1629 x1, 1630 y1, 1631 &xOffset1, 1632 &yOffset1 ); 1633 cf2_glyphpath_computeOffset( glyphpath, 1634 x2, 1635 y2, 1636 x3, 1637 y3, 1638 &xOffset3, 1639 &yOffset3 ); 1640 1641 /* add momentum from the middle segment */ 1642 glyphpath->callbacks->windingMomentum += 1643 cf2_getWindingMomentum( x1, y1, x2, y2 ); 1644 1645 /* construct offset points */ 1646 P0.x = glyphpath->currentCS.x + xOffset1; 1647 P0.y = glyphpath->currentCS.y + yOffset1; 1648 P1.x = x1 + xOffset1; 1649 P1.y = y1 + yOffset1; 1650 /* note: preserve angle of final segment by using offset3 at both ends */ 1651 P2.x = x2 + xOffset3; 1652 P2.y = y2 + yOffset3; 1653 P3.x = x3 + xOffset3; 1654 P3.y = y3 + yOffset3; 1655 1656 if ( glyphpath->moveIsPending ) 1657 { 1658 /* emit offset 1st point as MoveTo */ 1659 cf2_glyphpath_pushMove( glyphpath, P0 ); 1660 1661 glyphpath->moveIsPending = FALSE; 1662 glyphpath->pathIsOpen = TRUE; 1663 1664 glyphpath->offsetStart1 = P1; /* record second point */ 1665 } 1666 1667 if ( glyphpath->elemIsQueued ) 1668 { 1669 FT_ASSERT( cf2_hintmap_isValid( &glyphpath->hintMap ) ); 1670 1671 cf2_glyphpath_pushPrevElem( glyphpath, 1672 &glyphpath->hintMap, 1673 &P0, 1674 P1, 1675 FALSE ); 1676 } 1677 1678 /* queue the current element with offset points */ 1679 glyphpath->elemIsQueued = TRUE; 1680 glyphpath->prevElemOp = CF2_PathOpCubeTo; 1681 glyphpath->prevElemP0 = P0; 1682 glyphpath->prevElemP1 = P1; 1683 glyphpath->prevElemP2 = P2; 1684 glyphpath->prevElemP3 = P3; 1685 1686 /* update current map */ 1687 if ( cf2_hintmask_isNew( glyphpath->hintMask ) ) 1688 cf2_hintmap_build( &glyphpath->hintMap, 1689 glyphpath->hStemHintArray, 1690 glyphpath->vStemHintArray, 1691 glyphpath->hintMask, 1692 glyphpath->hintOriginY, 1693 FALSE ); 1694 1695 glyphpath->currentCS.x = x3; /* pre-offset current point */ 1696 glyphpath->currentCS.y = y3; 1697 } 1698 1699 1700 FT_LOCAL_DEF( void ) 1701 cf2_glyphpath_closeOpenPath( CF2_GlyphPath glyphpath ) 1702 { 1703 if ( glyphpath->pathIsOpen ) 1704 { 1705 FT_ASSERT( cf2_hintmap_isValid( &glyphpath->firstHintMap ) ); 1706 1707 /* since we need to apply an offset to the implicit lineto, we make */ 1708 /* it explicit here */ 1709 cf2_glyphpath_lineTo( glyphpath, 1710 glyphpath->start.x, 1711 glyphpath->start.y ); 1712 1713 /* Draw previous element (the explicit LineTo we just created, */ 1714 /* above) and connect it to the start point, but with the offset we */ 1715 /* saved from the first element. */ 1716 /* Use the saved HintMap, too. */ 1717 FT_ASSERT( glyphpath->elemIsQueued ); 1718 1719 cf2_glyphpath_pushPrevElem( glyphpath, 1720 &glyphpath->firstHintMap, 1721 &glyphpath->offsetStart0, 1722 glyphpath->offsetStart1, 1723 TRUE ); 1724 1725 /* reset state machine */ 1726 glyphpath->moveIsPending = TRUE; 1727 glyphpath->pathIsOpen = FALSE; 1728 glyphpath->elemIsQueued = FALSE; 1729 } 1730 } 1731 1732 1733 /* END */ 1734