1 /**************************************************************************** 2 * 3 * pshints.c 4 * 5 * Adobe's code for handling CFF hints (body). 6 * 7 * Copyright 2007-2014 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 "psft.h" 40 #include FT_INTERNAL_DEBUG_H 41 42 #include "psglue.h" 43 #include "psfont.h" 44 #include "pshints.h" 45 #include "psintrp.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 ) * ( SUB_INT32( y2, y1 ) >> 16 ) - 78 ( y1 >> 16 ) * ( SUB_INT32( 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 = SUB_INT32( 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 = ADD_INT32( hint->csCoord, 2 * font->darkenY ); 189 190 hint->csCoord = ADD_INT32( 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 (FT_Bool)( hint->flags != 0 ); 221 } 222 223 224 static FT_Bool 225 cf2_hint_isPair( const CF2_Hint hint ) 226 { 227 return (FT_Bool)( ( 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 (FT_Bool)( ( 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 (FT_Bool)( ( 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 (FT_Bool)( ( 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 (FT_Bool)( ( hint->flags & CF2_Locked ) != 0 ); 259 } 260 261 262 static FT_Bool 263 cf2_hint_isSynthetic( const CF2_Hint hint ) 264 { 265 return (FT_Bool)( ( 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 static void 303 cf2_hintmap_dump( CF2_HintMap hintmap ) 304 { 305 #ifdef FT_DEBUG_LEVEL_TRACE 306 CF2_UInt i; 307 308 309 FT_TRACE6(( " index csCoord dsCoord scale flags\n" )); 310 311 for ( i = 0; i < hintmap->count; i++ ) 312 { 313 CF2_Hint hint = &hintmap->edge[i]; 314 315 316 FT_TRACE6(( " %3d %7.2f %7.2f %5d %s%s%s%s\n", 317 hint->index, 318 hint->csCoord / 65536.0, 319 hint->dsCoord / ( hint->scale * 1.0 ), 320 hint->scale, 321 ( cf2_hint_isPair( hint ) ? "p" : "g" ), 322 ( cf2_hint_isTop( hint ) ? "t" : "b" ), 323 ( cf2_hint_isLocked( hint ) ? "L" : ""), 324 ( cf2_hint_isSynthetic( hint ) ? "S" : "" ) )); 325 } 326 #else 327 FT_UNUSED( hintmap ); 328 #endif 329 } 330 331 332 /* transform character space coordinate to device space using hint map */ 333 static CF2_Fixed 334 cf2_hintmap_map( CF2_HintMap hintmap, 335 CF2_Fixed csCoord ) 336 { 337 if ( hintmap->count == 0 || ! hintmap->hinted ) 338 { 339 /* there are no hints; use uniform scale and zero offset */ 340 return FT_MulFix( csCoord, hintmap->scale ); 341 } 342 else 343 { 344 /* start linear search from last hit */ 345 CF2_UInt i = hintmap->lastIndex; 346 347 348 FT_ASSERT( hintmap->lastIndex < CF2_MAX_HINT_EDGES ); 349 350 /* search up */ 351 while ( i < hintmap->count - 1 && 352 csCoord >= hintmap->edge[i + 1].csCoord ) 353 i += 1; 354 355 /* search down */ 356 while ( i > 0 && csCoord < hintmap->edge[i].csCoord ) 357 i -= 1; 358 359 hintmap->lastIndex = i; 360 361 if ( i == 0 && csCoord < hintmap->edge[0].csCoord ) 362 { 363 /* special case for points below first edge: use uniform scale */ 364 return ADD_INT32( FT_MulFix( SUB_INT32( csCoord, 365 hintmap->edge[0].csCoord ), 366 hintmap->scale ), 367 hintmap->edge[0].dsCoord ); 368 } 369 else 370 { 371 /* 372 * Note: entries with duplicate csCoord are allowed. 373 * Use edge[i], the highest entry where csCoord >= entry[i].csCoord 374 */ 375 return ADD_INT32( FT_MulFix( SUB_INT32( csCoord, 376 hintmap->edge[i].csCoord ), 377 hintmap->edge[i].scale ), 378 hintmap->edge[i].dsCoord ); 379 } 380 } 381 } 382 383 384 /* 385 * This hinting policy moves a hint pair in device space so that one of 386 * its two edges is on a device pixel boundary (its fractional part is 387 * zero). `cf2_hintmap_insertHint' guarantees no overlap in CS 388 * space. Ensure here that there is no overlap in DS. 389 * 390 * In the first pass, edges are adjusted relative to adjacent hints. 391 * Those that are below have already been adjusted. Those that are 392 * above have not yet been adjusted. If a hint above blocks an 393 * adjustment to an optimal position, we will try again in a second 394 * pass. The second pass is top-down. 395 * 396 */ 397 398 static void 399 cf2_hintmap_adjustHints( CF2_HintMap hintmap ) 400 { 401 size_t i, j; 402 403 404 cf2_arrstack_clear( hintmap->hintMoves ); /* working storage */ 405 406 /* 407 * First pass is bottom-up (font hint order) without look-ahead. 408 * Locked edges are already adjusted. 409 * Unlocked edges begin with dsCoord from `initialHintMap'. 410 * Save edges that are not optimally adjusted in `hintMoves' array, 411 * and process them in second pass. 412 */ 413 414 for ( i = 0; i < hintmap->count; i++ ) 415 { 416 FT_Bool isPair = cf2_hint_isPair( &hintmap->edge[i] ); 417 418 419 /* index of upper edge (same value for ghost hint) */ 420 j = isPair ? i + 1 : i; 421 422 FT_ASSERT( j < hintmap->count ); 423 FT_ASSERT( cf2_hint_isValid( &hintmap->edge[i] ) ); 424 FT_ASSERT( cf2_hint_isValid( &hintmap->edge[j] ) ); 425 FT_ASSERT( cf2_hint_isLocked( &hintmap->edge[i] ) == 426 cf2_hint_isLocked( &hintmap->edge[j] ) ); 427 428 if ( !cf2_hint_isLocked( &hintmap->edge[i] ) ) 429 { 430 /* hint edge is not locked, we can adjust it */ 431 CF2_Fixed fracDown = cf2_fixedFraction( hintmap->edge[i].dsCoord ); 432 CF2_Fixed fracUp = cf2_fixedFraction( hintmap->edge[j].dsCoord ); 433 434 /* calculate all four possibilities; moves down are negative */ 435 CF2_Fixed downMoveDown = 0 - fracDown; 436 CF2_Fixed upMoveDown = 0 - fracUp; 437 CF2_Fixed downMoveUp = ( fracDown == 0 ) 438 ? 0 439 : cf2_intToFixed( 1 ) - fracDown; 440 CF2_Fixed upMoveUp = ( fracUp == 0 ) 441 ? 0 442 : cf2_intToFixed( 1 ) - fracUp; 443 444 /* smallest move up */ 445 CF2_Fixed moveUp = FT_MIN( downMoveUp, upMoveUp ); 446 /* smallest move down */ 447 CF2_Fixed moveDown = FT_MAX( downMoveDown, upMoveDown ); 448 449 /* final amount to move edge or edge pair */ 450 CF2_Fixed move; 451 452 CF2_Fixed downMinCounter = CF2_MIN_COUNTER; 453 CF2_Fixed upMinCounter = CF2_MIN_COUNTER; 454 FT_Bool saveEdge = FALSE; 455 456 457 /* minimum counter constraint doesn't apply when adjacent edges */ 458 /* are synthetic */ 459 /* TODO: doesn't seem a big effect; for now, reduce the code */ 460 #if 0 461 if ( i == 0 || 462 cf2_hint_isSynthetic( &hintmap->edge[i - 1] ) ) 463 downMinCounter = 0; 464 465 if ( j >= hintmap->count - 1 || 466 cf2_hint_isSynthetic( &hintmap->edge[j + 1] ) ) 467 upMinCounter = 0; 468 #endif 469 470 /* is there room to move up? */ 471 /* there is if we are at top of array or the next edge is at or */ 472 /* beyond proposed move up? */ 473 if ( j >= hintmap->count - 1 || 474 hintmap->edge[j + 1].dsCoord >= 475 ADD_INT32( hintmap->edge[j].dsCoord, 476 moveUp + upMinCounter ) ) 477 { 478 /* there is room to move up; is there also room to move down? */ 479 if ( i == 0 || 480 hintmap->edge[i - 1].dsCoord <= 481 ADD_INT32( hintmap->edge[i].dsCoord, 482 moveDown - downMinCounter ) ) 483 { 484 /* move smaller absolute amount */ 485 move = ( -moveDown < moveUp ) ? moveDown : moveUp; /* optimum */ 486 } 487 else 488 move = moveUp; 489 } 490 else 491 { 492 /* is there room to move down? */ 493 if ( i == 0 || 494 hintmap->edge[i - 1].dsCoord <= 495 ADD_INT32( hintmap->edge[i].dsCoord, 496 moveDown - downMinCounter ) ) 497 { 498 move = moveDown; 499 /* true if non-optimum move */ 500 saveEdge = (FT_Bool)( moveUp < -moveDown ); 501 } 502 else 503 { 504 /* no room to move either way without overlapping or reducing */ 505 /* the counter too much */ 506 move = 0; 507 saveEdge = TRUE; 508 } 509 } 510 511 /* Identify non-moves and moves down that aren't optimal, and save */ 512 /* them for second pass. */ 513 /* Do this only if there is an unlocked edge above (which could */ 514 /* possibly move). */ 515 if ( saveEdge && 516 j < hintmap->count - 1 && 517 !cf2_hint_isLocked( &hintmap->edge[j + 1] ) ) 518 { 519 CF2_HintMoveRec savedMove; 520 521 522 savedMove.j = j; 523 /* desired adjustment in second pass */ 524 savedMove.moveUp = moveUp - move; 525 526 cf2_arrstack_push( hintmap->hintMoves, &savedMove ); 527 } 528 529 /* move the edge(s) */ 530 hintmap->edge[i].dsCoord = ADD_INT32( hintmap->edge[i].dsCoord, 531 move ); 532 if ( isPair ) 533 hintmap->edge[j].dsCoord = ADD_INT32( hintmap->edge[j].dsCoord, 534 move ); 535 } 536 537 /* assert there are no overlaps in device space */ 538 FT_ASSERT( i == 0 || 539 hintmap->edge[i - 1].dsCoord <= hintmap->edge[i].dsCoord ); 540 FT_ASSERT( i < j || 541 hintmap->edge[i].dsCoord <= hintmap->edge[j].dsCoord ); 542 543 /* adjust the scales, avoiding divide by zero */ 544 if ( i > 0 ) 545 { 546 if ( hintmap->edge[i].csCoord != hintmap->edge[i - 1].csCoord ) 547 hintmap->edge[i - 1].scale = 548 FT_DivFix( SUB_INT32( hintmap->edge[i].dsCoord, 549 hintmap->edge[i - 1].dsCoord ), 550 SUB_INT32( hintmap->edge[i].csCoord, 551 hintmap->edge[i - 1].csCoord ) ); 552 } 553 554 if ( isPair ) 555 { 556 if ( hintmap->edge[j].csCoord != hintmap->edge[j - 1].csCoord ) 557 hintmap->edge[j - 1].scale = 558 FT_DivFix( SUB_INT32( hintmap->edge[j].dsCoord, 559 hintmap->edge[j - 1].dsCoord ), 560 SUB_INT32( hintmap->edge[j].csCoord, 561 hintmap->edge[j - 1].csCoord ) ); 562 563 i += 1; /* skip upper edge on next loop */ 564 } 565 } 566 567 /* second pass tries to move non-optimal hints up, in case there is */ 568 /* room now */ 569 for ( i = cf2_arrstack_size( hintmap->hintMoves ); i > 0; i-- ) 570 { 571 CF2_HintMove hintMove = (CF2_HintMove) 572 cf2_arrstack_getPointer( hintmap->hintMoves, i - 1 ); 573 574 575 j = hintMove->j; 576 577 /* this was tested before the push, above */ 578 FT_ASSERT( j < hintmap->count - 1 ); 579 580 /* is there room to move up? */ 581 if ( hintmap->edge[j + 1].dsCoord >= 582 ADD_INT32( hintmap->edge[j].dsCoord, 583 hintMove->moveUp + CF2_MIN_COUNTER ) ) 584 { 585 /* there is more room now, move edge up */ 586 hintmap->edge[j].dsCoord = ADD_INT32( hintmap->edge[j].dsCoord, 587 hintMove->moveUp ); 588 589 if ( cf2_hint_isPair( &hintmap->edge[j] ) ) 590 { 591 FT_ASSERT( j > 0 ); 592 hintmap->edge[j - 1].dsCoord = 593 ADD_INT32( hintmap->edge[j - 1].dsCoord, hintMove->moveUp ); 594 } 595 } 596 } 597 } 598 599 600 /* insert hint edges into map, sorted by csCoord */ 601 static void 602 cf2_hintmap_insertHint( CF2_HintMap hintmap, 603 CF2_Hint bottomHintEdge, 604 CF2_Hint topHintEdge ) 605 { 606 CF2_UInt indexInsert; 607 608 /* set default values, then check for edge hints */ 609 FT_Bool isPair = TRUE; 610 CF2_Hint firstHintEdge = bottomHintEdge; 611 CF2_Hint secondHintEdge = topHintEdge; 612 613 614 /* one or none of the input params may be invalid when dealing with */ 615 /* edge hints; at least one edge must be valid */ 616 FT_ASSERT( cf2_hint_isValid( bottomHintEdge ) || 617 cf2_hint_isValid( topHintEdge ) ); 618 619 /* determine how many and which edges to insert */ 620 if ( !cf2_hint_isValid( bottomHintEdge ) ) 621 { 622 /* insert only the top edge */ 623 firstHintEdge = topHintEdge; 624 isPair = FALSE; 625 } 626 else if ( !cf2_hint_isValid( topHintEdge ) ) 627 { 628 /* insert only the bottom edge */ 629 isPair = FALSE; 630 } 631 632 /* paired edges must be in proper order */ 633 if ( isPair && 634 topHintEdge->csCoord < bottomHintEdge->csCoord ) 635 return; 636 637 /* linear search to find index value of insertion point */ 638 indexInsert = 0; 639 for ( ; indexInsert < hintmap->count; indexInsert++ ) 640 { 641 if ( hintmap->edge[indexInsert].csCoord >= firstHintEdge->csCoord ) 642 break; 643 } 644 645 FT_TRACE7(( " Got hint at %.2f (%.2f)\n", 646 firstHintEdge->csCoord / 65536.0, 647 firstHintEdge->dsCoord / 65536.0 )); 648 if ( isPair ) 649 FT_TRACE7(( " Got hint at %.2f (%.2f)\n", 650 secondHintEdge->csCoord / 65536.0, 651 secondHintEdge->dsCoord / 65536.0 )); 652 653 /* 654 * Discard any hints that overlap in character space. Most often, this 655 * is while building the initial map, where captured hints from all 656 * zones are combined. Define overlap to include hints that `touch' 657 * (overlap zero). Hiragino Sans/Gothic fonts have numerous hints that 658 * touch. Some fonts have non-ideographic glyphs that overlap our 659 * synthetic hints. 660 * 661 * Overlap also occurs when darkening stem hints that are close. 662 * 663 */ 664 if ( indexInsert < hintmap->count ) 665 { 666 /* we are inserting before an existing edge: */ 667 /* verify that an existing edge is not the same */ 668 if ( hintmap->edge[indexInsert].csCoord == firstHintEdge->csCoord ) 669 return; /* ignore overlapping stem hint */ 670 671 /* verify that a new pair does not straddle the next edge */ 672 if ( isPair && 673 hintmap->edge[indexInsert].csCoord <= secondHintEdge->csCoord ) 674 return; /* ignore overlapping stem hint */ 675 676 /* verify that we are not inserting between paired edges */ 677 if ( cf2_hint_isPairTop( &hintmap->edge[indexInsert] ) ) 678 return; /* ignore overlapping stem hint */ 679 } 680 681 /* recompute device space locations using initial hint map */ 682 if ( cf2_hintmap_isValid( hintmap->initialHintMap ) && 683 !cf2_hint_isLocked( firstHintEdge ) ) 684 { 685 if ( isPair ) 686 { 687 /* Use hint map to position the center of stem, and nominal scale */ 688 /* to position the two edges. This preserves the stem width. */ 689 CF2_Fixed midpoint = 690 cf2_hintmap_map( 691 hintmap->initialHintMap, 692 ADD_INT32( secondHintEdge->csCoord, 693 firstHintEdge->csCoord ) / 2 ); 694 CF2_Fixed halfWidth = 695 FT_MulFix( SUB_INT32( secondHintEdge->csCoord, 696 firstHintEdge->csCoord ) / 2, 697 hintmap->scale ); 698 699 700 firstHintEdge->dsCoord = SUB_INT32( midpoint, halfWidth ); 701 secondHintEdge->dsCoord = ADD_INT32( midpoint, halfWidth ); 702 } 703 else 704 firstHintEdge->dsCoord = cf2_hintmap_map( hintmap->initialHintMap, 705 firstHintEdge->csCoord ); 706 } 707 708 /* 709 * Discard any hints that overlap in device space; this can occur 710 * because locked hints have been moved to align with blue zones. 711 * 712 * TODO: Although we might correct this later during adjustment, we 713 * don't currently have a way to delete a conflicting hint once it has 714 * been inserted. See v2.030 MinionPro-Regular, 12 ppem darkened, 715 * initial hint map for second path, glyph 945 (the perispomeni (tilde) 716 * in U+1F6E, Greek omega with psili and perispomeni). Darkening is 717 * 25. Pair 667,747 initially conflicts in design space with top edge 718 * 660. This is because 667 maps to 7.87, and the top edge was 719 * captured by a zone at 8.0. The pair is later successfully inserted 720 * in a zone without the top edge. In this zone it is adjusted to 8.0, 721 * and no longer conflicts with the top edge in design space. This 722 * means it can be included in yet a later zone which does have the top 723 * edge hint. This produces a small mismatch between the first and 724 * last points of this path, even though the hint masks are the same. 725 * The density map difference is tiny (1/256). 726 * 727 */ 728 729 if ( indexInsert > 0 ) 730 { 731 /* we are inserting after an existing edge */ 732 if ( firstHintEdge->dsCoord < hintmap->edge[indexInsert - 1].dsCoord ) 733 return; 734 } 735 736 if ( indexInsert < hintmap->count ) 737 { 738 /* we are inserting before an existing edge */ 739 if ( isPair ) 740 { 741 if ( secondHintEdge->dsCoord > hintmap->edge[indexInsert].dsCoord ) 742 return; 743 } 744 else 745 { 746 if ( firstHintEdge->dsCoord > hintmap->edge[indexInsert].dsCoord ) 747 return; 748 } 749 } 750 751 /* make room to insert */ 752 { 753 CF2_UInt iSrc = hintmap->count - 1; 754 CF2_UInt iDst = isPair ? hintmap->count + 1 : hintmap->count; 755 756 CF2_UInt count = hintmap->count - indexInsert; 757 758 759 if ( iDst >= CF2_MAX_HINT_EDGES ) 760 { 761 FT_TRACE4(( "cf2_hintmap_insertHint: too many hintmaps\n" )); 762 return; 763 } 764 765 while ( count-- ) 766 hintmap->edge[iDst--] = hintmap->edge[iSrc--]; 767 768 /* insert first edge */ 769 hintmap->edge[indexInsert] = *firstHintEdge; /* copy struct */ 770 hintmap->count += 1; 771 772 FT_TRACE7(( " Inserting hint %.2f (%.2f)\n", 773 firstHintEdge->csCoord / 65536.0, 774 firstHintEdge->dsCoord / 65536.0 )); 775 776 if ( isPair ) 777 { 778 /* insert second edge */ 779 hintmap->edge[indexInsert + 1] = *secondHintEdge; /* copy struct */ 780 hintmap->count += 1; 781 782 FT_TRACE7(( " Inserting hint %.2f (%.2f)\n", 783 secondHintEdge->csCoord / 65536.0, 784 secondHintEdge->dsCoord / 65536.0 )); 785 786 } 787 } 788 789 return; 790 } 791 792 793 /* 794 * Build a map from hints and mask. 795 * 796 * This function may recur one level if `hintmap->initialHintMap' is not yet 797 * valid. 798 * If `initialMap' is true, simply build initial map. 799 * 800 * Synthetic hints are used in two ways. A hint at zero is inserted, if 801 * needed, in the initial hint map, to prevent translations from 802 * propagating across the origin. If synthetic em box hints are enabled 803 * for ideographic dictionaries, then they are inserted in all hint 804 * maps, including the initial one. 805 * 806 */ 807 FT_LOCAL_DEF( void ) 808 cf2_hintmap_build( CF2_HintMap hintmap, 809 CF2_ArrStack hStemHintArray, 810 CF2_ArrStack vStemHintArray, 811 CF2_HintMask hintMask, 812 CF2_Fixed hintOrigin, 813 FT_Bool initialMap ) 814 { 815 FT_Byte* maskPtr; 816 817 CF2_Font font = hintmap->font; 818 CF2_HintMaskRec tempHintMask; 819 820 size_t bitCount, i; 821 FT_Byte maskByte; 822 823 824 /* check whether initial map is constructed */ 825 if ( !initialMap && !cf2_hintmap_isValid( hintmap->initialHintMap ) ) 826 { 827 /* make recursive call with initialHintMap and temporary mask; */ 828 /* temporary mask will get all bits set, below */ 829 cf2_hintmask_init( &tempHintMask, hintMask->error ); 830 cf2_hintmap_build( hintmap->initialHintMap, 831 hStemHintArray, 832 vStemHintArray, 833 &tempHintMask, 834 hintOrigin, 835 TRUE ); 836 } 837 838 if ( !cf2_hintmask_isValid( hintMask ) ) 839 { 840 /* without a hint mask, assume all hints are active */ 841 cf2_hintmask_setAll( hintMask, 842 cf2_arrstack_size( hStemHintArray ) + 843 cf2_arrstack_size( vStemHintArray ) ); 844 if ( !cf2_hintmask_isValid( hintMask ) ) 845 { 846 if ( font->isT1 ) 847 { 848 /* no error, just continue unhinted */ 849 *hintMask->error = FT_Err_Ok; 850 hintmap->hinted = FALSE; 851 } 852 return; /* too many stem hints */ 853 } 854 } 855 856 /* begin by clearing the map */ 857 hintmap->count = 0; 858 hintmap->lastIndex = 0; 859 860 /* make a copy of the hint mask so we can modify it */ 861 tempHintMask = *hintMask; 862 maskPtr = cf2_hintmask_getMaskPtr( &tempHintMask ); 863 864 /* use the hStem hints only, which are first in the mask */ 865 bitCount = cf2_arrstack_size( hStemHintArray ); 866 867 /* Defense-in-depth. Should never return here. */ 868 if ( bitCount > hintMask->bitCount ) 869 return; 870 871 /* synthetic embox hints get highest priority */ 872 if ( font->blues.doEmBoxHints ) 873 { 874 CF2_HintRec dummy; 875 876 877 cf2_hint_initZero( &dummy ); /* invalid hint map element */ 878 879 /* ghost bottom */ 880 cf2_hintmap_insertHint( hintmap, 881 &font->blues.emBoxBottomEdge, 882 &dummy ); 883 /* ghost top */ 884 cf2_hintmap_insertHint( hintmap, 885 &dummy, 886 &font->blues.emBoxTopEdge ); 887 } 888 889 /* insert hints captured by a blue zone or already locked (higher */ 890 /* priority) */ 891 for ( i = 0, maskByte = 0x80; i < bitCount; i++ ) 892 { 893 if ( maskByte & *maskPtr ) 894 { 895 /* expand StemHint into two `CF2_Hint' elements */ 896 CF2_HintRec bottomHintEdge, topHintEdge; 897 898 899 cf2_hint_init( &bottomHintEdge, 900 hStemHintArray, 901 i, 902 font, 903 hintOrigin, 904 hintmap->scale, 905 TRUE /* bottom */ ); 906 cf2_hint_init( &topHintEdge, 907 hStemHintArray, 908 i, 909 font, 910 hintOrigin, 911 hintmap->scale, 912 FALSE /* top */ ); 913 914 if ( cf2_hint_isLocked( &bottomHintEdge ) || 915 cf2_hint_isLocked( &topHintEdge ) || 916 cf2_blues_capture( &font->blues, 917 &bottomHintEdge, 918 &topHintEdge ) ) 919 { 920 /* insert captured hint into map */ 921 cf2_hintmap_insertHint( hintmap, &bottomHintEdge, &topHintEdge ); 922 923 *maskPtr &= ~maskByte; /* turn off the bit for this hint */ 924 } 925 } 926 927 if ( ( i & 7 ) == 7 ) 928 { 929 /* move to next mask byte */ 930 maskPtr++; 931 maskByte = 0x80; 932 } 933 else 934 maskByte >>= 1; 935 } 936 937 /* initial hint map includes only captured hints plus maybe one at 0 */ 938 939 /* 940 * TODO: There is a problem here because we are trying to build a 941 * single hint map containing all captured hints. It is 942 * possible for there to be conflicts between captured hints, 943 * either because of darkening or because the hints are in 944 * separate hint zones (we are ignoring hint zones for the 945 * initial map). An example of the latter is MinionPro-Regular 946 * v2.030 glyph 883 (Greek Capital Alpha with Psili) at 15ppem. 947 * A stem hint for the psili conflicts with the top edge hint 948 * for the base character. The stem hint gets priority because 949 * of its sort order. In glyph 884 (Greek Capital Alpha with 950 * Psili and Oxia), the top of the base character gets a stem 951 * hint, and the psili does not. This creates different initial 952 * maps for the two glyphs resulting in different renderings of 953 * the base character. Will probably defer this either as not 954 * worth the cost or as a font bug. I don't think there is any 955 * good reason for an accent to be captured by an alignment 956 * zone. -darnold 2/12/10 957 */ 958 959 if ( initialMap ) 960 { 961 /* Apply a heuristic that inserts a point for (0,0), unless it's */ 962 /* already covered by a mapping. This locks the baseline for glyphs */ 963 /* that have no baseline hints. */ 964 965 if ( hintmap->count == 0 || 966 hintmap->edge[0].csCoord > 0 || 967 hintmap->edge[hintmap->count - 1].csCoord < 0 ) 968 { 969 /* all edges are above 0 or all edges are below 0; */ 970 /* construct a locked edge hint at 0 */ 971 972 CF2_HintRec edge, invalid; 973 974 975 cf2_hint_initZero( &edge ); 976 977 edge.flags = CF2_GhostBottom | 978 CF2_Locked | 979 CF2_Synthetic; 980 edge.scale = hintmap->scale; 981 982 cf2_hint_initZero( &invalid ); 983 cf2_hintmap_insertHint( hintmap, &edge, &invalid ); 984 } 985 } 986 else 987 { 988 /* insert remaining hints */ 989 990 maskPtr = cf2_hintmask_getMaskPtr( &tempHintMask ); 991 992 for ( i = 0, maskByte = 0x80; i < bitCount; i++ ) 993 { 994 if ( maskByte & *maskPtr ) 995 { 996 CF2_HintRec bottomHintEdge, topHintEdge; 997 998 999 cf2_hint_init( &bottomHintEdge, 1000 hStemHintArray, 1001 i, 1002 font, 1003 hintOrigin, 1004 hintmap->scale, 1005 TRUE /* bottom */ ); 1006 cf2_hint_init( &topHintEdge, 1007 hStemHintArray, 1008 i, 1009 font, 1010 hintOrigin, 1011 hintmap->scale, 1012 FALSE /* top */ ); 1013 1014 cf2_hintmap_insertHint( hintmap, &bottomHintEdge, &topHintEdge ); 1015 } 1016 1017 if ( ( i & 7 ) == 7 ) 1018 { 1019 /* move to next mask byte */ 1020 maskPtr++; 1021 maskByte = 0x80; 1022 } 1023 else 1024 maskByte >>= 1; 1025 } 1026 } 1027 1028 FT_TRACE6(( initialMap ? "flags: [p]air [g]host [t]op " 1029 "[b]ottom [L]ocked [S]ynthetic\n" 1030 "Initial hintmap\n" 1031 : "Hints:\n" )); 1032 cf2_hintmap_dump( hintmap ); 1033 1034 /* 1035 * Note: The following line is a convenient place to break when 1036 * debugging hinting. Examine `hintmap->edge' for the list of 1037 * enabled hints, then step over the call to see the effect of 1038 * adjustment. We stop here first on the recursive call that 1039 * creates the initial map, and then on each counter group and 1040 * hint zone. 1041 */ 1042 1043 /* adjust positions of hint edges that are not locked to blue zones */ 1044 cf2_hintmap_adjustHints( hintmap ); 1045 1046 FT_TRACE6(( "(adjusted)\n" )); 1047 cf2_hintmap_dump( hintmap ); 1048 1049 /* save the position of all hints that were used in this hint map; */ 1050 /* if we use them again, we'll locate them in the same position */ 1051 if ( !initialMap ) 1052 { 1053 for ( i = 0; i < hintmap->count; i++ ) 1054 { 1055 if ( !cf2_hint_isSynthetic( &hintmap->edge[i] ) ) 1056 { 1057 /* Note: include both valid and invalid edges */ 1058 /* Note: top and bottom edges are copied back separately */ 1059 CF2_StemHint stemhint = (CF2_StemHint) 1060 cf2_arrstack_getPointer( hStemHintArray, 1061 hintmap->edge[i].index ); 1062 1063 1064 if ( cf2_hint_isTop( &hintmap->edge[i] ) ) 1065 stemhint->maxDS = hintmap->edge[i].dsCoord; 1066 else 1067 stemhint->minDS = hintmap->edge[i].dsCoord; 1068 1069 stemhint->used = TRUE; 1070 } 1071 } 1072 } 1073 1074 /* hint map is ready to use */ 1075 hintmap->isValid = TRUE; 1076 1077 /* remember this mask has been used */ 1078 cf2_hintmask_setNew( hintMask, FALSE ); 1079 } 1080 1081 1082 FT_LOCAL_DEF( void ) 1083 cf2_glyphpath_init( CF2_GlyphPath glyphpath, 1084 CF2_Font font, 1085 CF2_OutlineCallbacks callbacks, 1086 CF2_Fixed scaleY, 1087 /* CF2_Fixed hShift, */ 1088 CF2_ArrStack hStemHintArray, 1089 CF2_ArrStack vStemHintArray, 1090 CF2_HintMask hintMask, 1091 CF2_Fixed hintOriginY, 1092 const CF2_Blues blues, 1093 const FT_Vector* fractionalTranslation ) 1094 { 1095 FT_ZERO( glyphpath ); 1096 1097 glyphpath->font = font; 1098 glyphpath->callbacks = callbacks; 1099 1100 cf2_arrstack_init( &glyphpath->hintMoves, 1101 font->memory, 1102 &font->error, 1103 sizeof ( CF2_HintMoveRec ) ); 1104 1105 cf2_hintmap_init( &glyphpath->initialHintMap, 1106 font, 1107 &glyphpath->initialHintMap, 1108 &glyphpath->hintMoves, 1109 scaleY ); 1110 cf2_hintmap_init( &glyphpath->firstHintMap, 1111 font, 1112 &glyphpath->initialHintMap, 1113 &glyphpath->hintMoves, 1114 scaleY ); 1115 cf2_hintmap_init( &glyphpath->hintMap, 1116 font, 1117 &glyphpath->initialHintMap, 1118 &glyphpath->hintMoves, 1119 scaleY ); 1120 1121 glyphpath->scaleX = font->innerTransform.a; 1122 glyphpath->scaleC = font->innerTransform.c; 1123 glyphpath->scaleY = font->innerTransform.d; 1124 1125 glyphpath->fractionalTranslation = *fractionalTranslation; 1126 1127 #if 0 1128 glyphpath->hShift = hShift; /* for fauxing */ 1129 #endif 1130 1131 glyphpath->hStemHintArray = hStemHintArray; 1132 glyphpath->vStemHintArray = vStemHintArray; 1133 glyphpath->hintMask = hintMask; /* ptr to current mask */ 1134 glyphpath->hintOriginY = hintOriginY; 1135 glyphpath->blues = blues; 1136 glyphpath->darken = font->darkened; /* TODO: should we make copies? */ 1137 glyphpath->xOffset = font->darkenX; 1138 glyphpath->yOffset = font->darkenY; 1139 glyphpath->miterLimit = 2 * FT_MAX( 1140 cf2_fixedAbs( glyphpath->xOffset ), 1141 cf2_fixedAbs( glyphpath->yOffset ) ); 1142 1143 /* .1 character space unit */ 1144 glyphpath->snapThreshold = cf2_doubleToFixed( 0.1 ); 1145 1146 glyphpath->moveIsPending = TRUE; 1147 glyphpath->pathIsOpen = FALSE; 1148 glyphpath->pathIsClosing = FALSE; 1149 glyphpath->elemIsQueued = FALSE; 1150 } 1151 1152 1153 FT_LOCAL_DEF( void ) 1154 cf2_glyphpath_finalize( CF2_GlyphPath glyphpath ) 1155 { 1156 cf2_arrstack_finalize( &glyphpath->hintMoves ); 1157 } 1158 1159 1160 /* 1161 * Hint point in y-direction and apply outerTransform. 1162 * Input `current' hint map (which is actually delayed by one element). 1163 * Input x,y point in Character Space. 1164 * Output x,y point in Device Space, including translation. 1165 */ 1166 static void 1167 cf2_glyphpath_hintPoint( CF2_GlyphPath glyphpath, 1168 CF2_HintMap hintmap, 1169 FT_Vector* ppt, 1170 CF2_Fixed x, 1171 CF2_Fixed y ) 1172 { 1173 FT_Vector pt; /* hinted point in upright DS */ 1174 1175 1176 pt.x = ADD_INT32( FT_MulFix( glyphpath->scaleX, x ), 1177 FT_MulFix( glyphpath->scaleC, y ) ); 1178 pt.y = cf2_hintmap_map( hintmap, y ); 1179 1180 ppt->x = ADD_INT32( 1181 FT_MulFix( glyphpath->font->outerTransform.a, pt.x ), 1182 ADD_INT32( 1183 FT_MulFix( glyphpath->font->outerTransform.c, pt.y ), 1184 glyphpath->fractionalTranslation.x ) ); 1185 ppt->y = ADD_INT32( 1186 FT_MulFix( glyphpath->font->outerTransform.b, pt.x ), 1187 ADD_INT32( 1188 FT_MulFix( glyphpath->font->outerTransform.d, pt.y ), 1189 glyphpath->fractionalTranslation.y ) ); 1190 } 1191 1192 1193 /* 1194 * From two line segments, (u1,u2) and (v1,v2), compute a point of 1195 * intersection on the corresponding lines. 1196 * Return false if no intersection is found, or if the intersection is 1197 * too far away from the ends of the line segments, u2 and v1. 1198 * 1199 */ 1200 static FT_Bool 1201 cf2_glyphpath_computeIntersection( CF2_GlyphPath glyphpath, 1202 const FT_Vector* u1, 1203 const FT_Vector* u2, 1204 const FT_Vector* v1, 1205 const FT_Vector* v2, 1206 FT_Vector* intersection ) 1207 { 1208 /* 1209 * Let `u' be a zero-based vector from the first segment, `v' from the 1210 * second segment. 1211 * Let `w 'be the zero-based vector from `u1' to `v1'. 1212 * `perp' is the `perpendicular dot product'; see 1213 * https://mathworld.wolfram.com/PerpDotProduct.html. 1214 * `s' is the parameter for the parametric line for the first segment 1215 * (`u'). 1216 * 1217 * See notation in 1218 * http://softsurfer.com/Archive/algorithm_0104/algorithm_0104B.htm. 1219 * Calculations are done in 16.16, but must handle the squaring of 1220 * line lengths in character space. We scale all vectors by 1/32 to 1221 * avoid overflow. This allows values up to 4095 to be squared. The 1222 * scale factor cancels in the divide. 1223 * 1224 * TODO: the scale factor could be computed from UnitsPerEm. 1225 * 1226 */ 1227 1228 #define cf2_perp( a, b ) \ 1229 ( FT_MulFix( a.x, b.y ) - FT_MulFix( a.y, b.x ) ) 1230 1231 /* round and divide by 32 */ 1232 #define CF2_CS_SCALE( x ) \ 1233 ( ( (x) + 0x10 ) >> 5 ) 1234 1235 FT_Vector u, v, w; /* scaled vectors */ 1236 CF2_Fixed denominator, s; 1237 1238 1239 u.x = CF2_CS_SCALE( SUB_INT32( u2->x, u1->x ) ); 1240 u.y = CF2_CS_SCALE( SUB_INT32( u2->y, u1->y ) ); 1241 v.x = CF2_CS_SCALE( SUB_INT32( v2->x, v1->x ) ); 1242 v.y = CF2_CS_SCALE( SUB_INT32( v2->y, v1->y ) ); 1243 w.x = CF2_CS_SCALE( SUB_INT32( v1->x, u1->x ) ); 1244 w.y = CF2_CS_SCALE( SUB_INT32( v1->y, u1->y ) ); 1245 1246 denominator = cf2_perp( u, v ); 1247 1248 if ( denominator == 0 ) 1249 return FALSE; /* parallel or coincident lines */ 1250 1251 s = FT_DivFix( cf2_perp( w, v ), denominator ); 1252 1253 intersection->x = ADD_INT32( u1->x, 1254 FT_MulFix( s, SUB_INT32( u2->x, u1->x ) ) ); 1255 intersection->y = ADD_INT32( u1->y, 1256 FT_MulFix( s, SUB_INT32( u2->y, u1->y ) ) ); 1257 1258 1259 /* 1260 * Special case snapping for horizontal and vertical lines. 1261 * This cleans up intersections and reduces problems with winding 1262 * order detection. 1263 * Sample case is sbc cd KozGoPr6N-Medium.otf 20 16685. 1264 * Note: these calculations are in character space. 1265 * 1266 */ 1267 1268 if ( u1->x == u2->x && 1269 cf2_fixedAbs( SUB_INT32( intersection->x, 1270 u1->x ) ) < glyphpath->snapThreshold ) 1271 intersection->x = u1->x; 1272 if ( u1->y == u2->y && 1273 cf2_fixedAbs( SUB_INT32( intersection->y, 1274 u1->y ) ) < glyphpath->snapThreshold ) 1275 intersection->y = u1->y; 1276 1277 if ( v1->x == v2->x && 1278 cf2_fixedAbs( SUB_INT32( intersection->x, 1279 v1->x ) ) < glyphpath->snapThreshold ) 1280 intersection->x = v1->x; 1281 if ( v1->y == v2->y && 1282 cf2_fixedAbs( SUB_INT32( intersection->y, 1283 v1->y ) ) < glyphpath->snapThreshold ) 1284 intersection->y = v1->y; 1285 1286 /* limit the intersection distance from midpoint of u2 and v1 */ 1287 if ( cf2_fixedAbs( intersection->x - ADD_INT32( u2->x, v1->x ) / 2 ) > 1288 glyphpath->miterLimit || 1289 cf2_fixedAbs( intersection->y - ADD_INT32( u2->y, v1->y ) / 2 ) > 1290 glyphpath->miterLimit ) 1291 return FALSE; 1292 1293 return TRUE; 1294 } 1295 1296 1297 /* 1298 * Push the cached element (glyphpath->prevElem*) to the outline 1299 * consumer. When a darkening offset is used, the end point of the 1300 * cached element may be adjusted to an intersection point or we may 1301 * synthesize a connecting line to the current element. If we are 1302 * closing a subpath, we may also generate a connecting line to the start 1303 * point. 1304 * 1305 * This is where Character Space (CS) is converted to Device Space (DS) 1306 * using a hint map. This calculation must use a HintMap that was valid 1307 * at the time the element was saved. For the first point in a subpath, 1308 * that is a saved HintMap. For most elements, it just means the caller 1309 * has delayed building a HintMap from the current HintMask. 1310 * 1311 * Transform each point with outerTransform and call the outline 1312 * callbacks. This is a general 3x3 transform: 1313 * 1314 * x' = a*x + c*y + tx, y' = b*x + d*y + ty 1315 * 1316 * but it uses 4 elements from CF2_Font and the translation part 1317 * from CF2_GlyphPath. 1318 * 1319 */ 1320 static void 1321 cf2_glyphpath_pushPrevElem( CF2_GlyphPath glyphpath, 1322 CF2_HintMap hintmap, 1323 FT_Vector* nextP0, 1324 FT_Vector nextP1, 1325 FT_Bool close ) 1326 { 1327 CF2_CallbackParamsRec params; 1328 1329 FT_Vector* prevP0; 1330 FT_Vector* prevP1; 1331 1332 FT_Vector intersection = { 0, 0 }; 1333 FT_Bool useIntersection = FALSE; 1334 1335 1336 FT_ASSERT( glyphpath->prevElemOp == CF2_PathOpLineTo || 1337 glyphpath->prevElemOp == CF2_PathOpCubeTo ); 1338 1339 if ( glyphpath->prevElemOp == CF2_PathOpLineTo ) 1340 { 1341 prevP0 = &glyphpath->prevElemP0; 1342 prevP1 = &glyphpath->prevElemP1; 1343 } 1344 else 1345 { 1346 prevP0 = &glyphpath->prevElemP2; 1347 prevP1 = &glyphpath->prevElemP3; 1348 } 1349 1350 /* optimization: if previous and next elements are offset by the same */ 1351 /* amount, then there will be no gap, and no need to compute an */ 1352 /* intersection. */ 1353 if ( prevP1->x != nextP0->x || prevP1->y != nextP0->y ) 1354 { 1355 /* previous element does not join next element: */ 1356 /* adjust end point of previous element to the intersection */ 1357 useIntersection = cf2_glyphpath_computeIntersection( glyphpath, 1358 prevP0, 1359 prevP1, 1360 nextP0, 1361 &nextP1, 1362 &intersection ); 1363 if ( useIntersection ) 1364 { 1365 /* modify the last point of the cached element (either line or */ 1366 /* curve) */ 1367 *prevP1 = intersection; 1368 } 1369 } 1370 1371 params.pt0 = glyphpath->currentDS; 1372 1373 switch( glyphpath->prevElemOp ) 1374 { 1375 case CF2_PathOpLineTo: 1376 params.op = CF2_PathOpLineTo; 1377 1378 /* note: pt2 and pt3 are unused */ 1379 1380 if ( close ) 1381 { 1382 /* use first hint map if closing */ 1383 cf2_glyphpath_hintPoint( glyphpath, 1384 &glyphpath->firstHintMap, 1385 ¶ms.pt1, 1386 glyphpath->prevElemP1.x, 1387 glyphpath->prevElemP1.y ); 1388 } 1389 else 1390 { 1391 cf2_glyphpath_hintPoint( glyphpath, 1392 hintmap, 1393 ¶ms.pt1, 1394 glyphpath->prevElemP1.x, 1395 glyphpath->prevElemP1.y ); 1396 } 1397 1398 /* output only non-zero length lines */ 1399 if ( params.pt0.x != params.pt1.x || params.pt0.y != params.pt1.y ) 1400 { 1401 glyphpath->callbacks->lineTo( glyphpath->callbacks, ¶ms ); 1402 1403 glyphpath->currentDS = params.pt1; 1404 } 1405 break; 1406 1407 case CF2_PathOpCubeTo: 1408 params.op = CF2_PathOpCubeTo; 1409 1410 /* TODO: should we intersect the interior joins (p1-p2 and p2-p3)? */ 1411 cf2_glyphpath_hintPoint( glyphpath, 1412 hintmap, 1413 ¶ms.pt1, 1414 glyphpath->prevElemP1.x, 1415 glyphpath->prevElemP1.y ); 1416 cf2_glyphpath_hintPoint( glyphpath, 1417 hintmap, 1418 ¶ms.pt2, 1419 glyphpath->prevElemP2.x, 1420 glyphpath->prevElemP2.y ); 1421 cf2_glyphpath_hintPoint( glyphpath, 1422 hintmap, 1423 ¶ms.pt3, 1424 glyphpath->prevElemP3.x, 1425 glyphpath->prevElemP3.y ); 1426 1427 glyphpath->callbacks->cubeTo( glyphpath->callbacks, ¶ms ); 1428 1429 glyphpath->currentDS = params.pt3; 1430 1431 break; 1432 } 1433 1434 if ( !useIntersection || close ) 1435 { 1436 /* insert connecting line between end of previous element and start */ 1437 /* of current one */ 1438 /* note: at the end of a subpath, we might do both, so use `nextP0' */ 1439 /* before we change it, below */ 1440 1441 if ( close ) 1442 { 1443 /* if we are closing the subpath, then nextP0 is in the first */ 1444 /* hint zone */ 1445 cf2_glyphpath_hintPoint( glyphpath, 1446 &glyphpath->firstHintMap, 1447 ¶ms.pt1, 1448 nextP0->x, 1449 nextP0->y ); 1450 } 1451 else 1452 { 1453 cf2_glyphpath_hintPoint( glyphpath, 1454 hintmap, 1455 ¶ms.pt1, 1456 nextP0->x, 1457 nextP0->y ); 1458 } 1459 1460 if ( params.pt1.x != glyphpath->currentDS.x || 1461 params.pt1.y != glyphpath->currentDS.y ) 1462 { 1463 /* length is nonzero */ 1464 params.op = CF2_PathOpLineTo; 1465 params.pt0 = glyphpath->currentDS; 1466 1467 /* note: pt2 and pt3 are unused */ 1468 glyphpath->callbacks->lineTo( glyphpath->callbacks, ¶ms ); 1469 1470 glyphpath->currentDS = params.pt1; 1471 } 1472 } 1473 1474 if ( useIntersection ) 1475 { 1476 /* return intersection point to caller */ 1477 *nextP0 = intersection; 1478 } 1479 } 1480 1481 1482 /* push a MoveTo element based on current point and offset of current */ 1483 /* element */ 1484 static void 1485 cf2_glyphpath_pushMove( CF2_GlyphPath glyphpath, 1486 FT_Vector start ) 1487 { 1488 CF2_CallbackParamsRec params; 1489 1490 1491 params.op = CF2_PathOpMoveTo; 1492 params.pt0 = glyphpath->currentDS; 1493 1494 /* Test if move has really happened yet; it would have called */ 1495 /* `cf2_hintmap_build' to set `isValid'. */ 1496 if ( !cf2_hintmap_isValid( &glyphpath->hintMap ) ) 1497 { 1498 /* we are here iff first subpath is missing a moveto operator: */ 1499 /* synthesize first moveTo to finish initialization of hintMap */ 1500 cf2_glyphpath_moveTo( glyphpath, 1501 glyphpath->start.x, 1502 glyphpath->start.y ); 1503 } 1504 1505 cf2_glyphpath_hintPoint( glyphpath, 1506 &glyphpath->hintMap, 1507 ¶ms.pt1, 1508 start.x, 1509 start.y ); 1510 1511 /* note: pt2 and pt3 are unused */ 1512 glyphpath->callbacks->moveTo( glyphpath->callbacks, ¶ms ); 1513 1514 glyphpath->currentDS = params.pt1; 1515 glyphpath->offsetStart0 = start; 1516 } 1517 1518 1519 /* 1520 * All coordinates are in character space. 1521 * On input, (x1, y1) and (x2, y2) give line segment. 1522 * On output, (x, y) give offset vector. 1523 * We use a piecewise approximation to trig functions. 1524 * 1525 * TODO: Offset true perpendicular and proper length 1526 * supply the y-translation for hinting here, too, 1527 * that adds yOffset unconditionally to *y. 1528 */ 1529 static void 1530 cf2_glyphpath_computeOffset( CF2_GlyphPath glyphpath, 1531 CF2_Fixed x1, 1532 CF2_Fixed y1, 1533 CF2_Fixed x2, 1534 CF2_Fixed y2, 1535 CF2_Fixed* x, 1536 CF2_Fixed* y ) 1537 { 1538 CF2_Fixed dx = SUB_INT32( x2, x1 ); 1539 CF2_Fixed dy = SUB_INT32( y2, y1 ); 1540 1541 1542 /* note: negative offsets don't work here; negate deltas to change */ 1543 /* quadrants, below */ 1544 if ( glyphpath->font->reverseWinding ) 1545 { 1546 dx = NEG_INT32( dx ); 1547 dy = NEG_INT32( dy ); 1548 } 1549 1550 *x = *y = 0; 1551 1552 if ( !glyphpath->darken ) 1553 return; 1554 1555 /* add momentum for this path element */ 1556 glyphpath->callbacks->windingMomentum = 1557 ADD_INT32( glyphpath->callbacks->windingMomentum, 1558 cf2_getWindingMomentum( x1, y1, x2, y2 ) ); 1559 1560 /* note: allow mixed integer and fixed multiplication here */ 1561 if ( dx >= 0 ) 1562 { 1563 if ( dy >= 0 ) 1564 { 1565 /* first quadrant, +x +y */ 1566 1567 if ( dx > MUL_INT32( 2, dy ) ) 1568 { 1569 /* +x */ 1570 *x = 0; 1571 *y = 0; 1572 } 1573 else if ( dy > MUL_INT32( 2, dx ) ) 1574 { 1575 /* +y */ 1576 *x = glyphpath->xOffset; 1577 *y = glyphpath->yOffset; 1578 } 1579 else 1580 { 1581 /* +x +y */ 1582 *x = FT_MulFix( cf2_doubleToFixed( 0.7 ), 1583 glyphpath->xOffset ); 1584 *y = FT_MulFix( cf2_doubleToFixed( 1.0 - 0.7 ), 1585 glyphpath->yOffset ); 1586 } 1587 } 1588 else 1589 { 1590 /* fourth quadrant, +x -y */ 1591 1592 if ( dx > MUL_INT32( -2, dy ) ) 1593 { 1594 /* +x */ 1595 *x = 0; 1596 *y = 0; 1597 } 1598 else if ( NEG_INT32( dy ) > MUL_INT32( 2, dx ) ) 1599 { 1600 /* -y */ 1601 *x = NEG_INT32( glyphpath->xOffset ); 1602 *y = glyphpath->yOffset; 1603 } 1604 else 1605 { 1606 /* +x -y */ 1607 *x = FT_MulFix( cf2_doubleToFixed( -0.7 ), 1608 glyphpath->xOffset ); 1609 *y = FT_MulFix( cf2_doubleToFixed( 1.0 - 0.7 ), 1610 glyphpath->yOffset ); 1611 } 1612 } 1613 } 1614 else 1615 { 1616 if ( dy >= 0 ) 1617 { 1618 /* second quadrant, -x +y */ 1619 1620 if ( NEG_INT32( dx ) > MUL_INT32( 2, dy ) ) 1621 { 1622 /* -x */ 1623 *x = 0; 1624 *y = MUL_INT32( 2, glyphpath->yOffset ); 1625 } 1626 else if ( dy > MUL_INT32( -2, dx ) ) 1627 { 1628 /* +y */ 1629 *x = glyphpath->xOffset; 1630 *y = glyphpath->yOffset; 1631 } 1632 else 1633 { 1634 /* -x +y */ 1635 *x = FT_MulFix( cf2_doubleToFixed( 0.7 ), 1636 glyphpath->xOffset ); 1637 *y = FT_MulFix( cf2_doubleToFixed( 1.0 + 0.7 ), 1638 glyphpath->yOffset ); 1639 } 1640 } 1641 else 1642 { 1643 /* third quadrant, -x -y */ 1644 1645 if ( NEG_INT32( dx ) > MUL_INT32( -2, dy ) ) 1646 { 1647 /* -x */ 1648 *x = 0; 1649 *y = MUL_INT32( 2, glyphpath->yOffset ); 1650 } 1651 else if ( NEG_INT32( dy ) > MUL_INT32( -2, dx ) ) 1652 { 1653 /* -y */ 1654 *x = NEG_INT32( glyphpath->xOffset ); 1655 *y = glyphpath->yOffset; 1656 } 1657 else 1658 { 1659 /* -x -y */ 1660 *x = FT_MulFix( cf2_doubleToFixed( -0.7 ), 1661 glyphpath->xOffset ); 1662 *y = FT_MulFix( cf2_doubleToFixed( 1.0 + 0.7 ), 1663 glyphpath->yOffset ); 1664 } 1665 } 1666 } 1667 } 1668 1669 1670 /* 1671 * The functions cf2_glyphpath_{moveTo,lineTo,curveTo,closeOpenPath} are 1672 * called by the interpreter with Character Space (CS) coordinates. Each 1673 * path element is placed into a queue of length one to await the 1674 * calculation of the following element. At that time, the darkening 1675 * offset of the following element is known and joins can be computed, 1676 * including possible modification of this element, before mapping to 1677 * Device Space (DS) and passing it on to the outline consumer. 1678 * 1679 */ 1680 FT_LOCAL_DEF( void ) 1681 cf2_glyphpath_moveTo( CF2_GlyphPath glyphpath, 1682 CF2_Fixed x, 1683 CF2_Fixed y ) 1684 { 1685 cf2_glyphpath_closeOpenPath( glyphpath ); 1686 1687 /* save the parameters of the move for later, when we'll know how to */ 1688 /* offset it; */ 1689 /* also save last move point */ 1690 glyphpath->currentCS.x = glyphpath->start.x = x; 1691 glyphpath->currentCS.y = glyphpath->start.y = y; 1692 1693 glyphpath->moveIsPending = TRUE; 1694 1695 /* ensure we have a valid map with current mask */ 1696 if ( !cf2_hintmap_isValid( &glyphpath->hintMap ) || 1697 cf2_hintmask_isNew( glyphpath->hintMask ) ) 1698 cf2_hintmap_build( &glyphpath->hintMap, 1699 glyphpath->hStemHintArray, 1700 glyphpath->vStemHintArray, 1701 glyphpath->hintMask, 1702 glyphpath->hintOriginY, 1703 FALSE ); 1704 1705 /* save a copy of current HintMap to use when drawing initial point */ 1706 glyphpath->firstHintMap = glyphpath->hintMap; /* structure copy */ 1707 } 1708 1709 1710 FT_LOCAL_DEF( void ) 1711 cf2_glyphpath_lineTo( CF2_GlyphPath glyphpath, 1712 CF2_Fixed x, 1713 CF2_Fixed y ) 1714 { 1715 CF2_Fixed xOffset, yOffset; 1716 FT_Vector P0, P1; 1717 FT_Bool newHintMap; 1718 1719 /* 1720 * New hints will be applied after cf2_glyphpath_pushPrevElem has run. 1721 * In case this is a synthesized closing line, any new hints should be 1722 * delayed until this path is closed (`cf2_hintmask_isNew' will be 1723 * called again before the next line or curve). 1724 */ 1725 1726 /* true if new hint map not on close */ 1727 newHintMap = cf2_hintmask_isNew( glyphpath->hintMask ) && 1728 !glyphpath->pathIsClosing; 1729 1730 /* 1731 * Zero-length lines may occur in the charstring. Because we cannot 1732 * compute darkening offsets or intersections from zero-length lines, 1733 * it is best to remove them and avoid artifacts. However, zero-length 1734 * lines in CS at the start of a new hint map can generate non-zero 1735 * lines in DS due to hint substitution. We detect a change in hint 1736 * map here and pass those zero-length lines along. 1737 */ 1738 1739 /* 1740 * Note: Find explicitly closed paths here with a conditional 1741 * breakpoint using 1742 * 1743 * !gp->pathIsClosing && gp->start.x == x && gp->start.y == y 1744 * 1745 */ 1746 1747 if ( glyphpath->currentCS.x == x && 1748 glyphpath->currentCS.y == y && 1749 !newHintMap ) 1750 /* 1751 * Ignore zero-length lines in CS where the hint map is the same 1752 * because the line in DS will also be zero length. 1753 * 1754 * Ignore zero-length lines when we synthesize a closing line because 1755 * the close will be handled in cf2_glyphPath_pushPrevElem. 1756 */ 1757 return; 1758 1759 cf2_glyphpath_computeOffset( glyphpath, 1760 glyphpath->currentCS.x, 1761 glyphpath->currentCS.y, 1762 x, 1763 y, 1764 &xOffset, 1765 &yOffset ); 1766 1767 /* construct offset points */ 1768 P0.x = ADD_INT32( glyphpath->currentCS.x, xOffset ); 1769 P0.y = ADD_INT32( glyphpath->currentCS.y, yOffset ); 1770 P1.x = ADD_INT32( x, xOffset ); 1771 P1.y = ADD_INT32( y, yOffset ); 1772 1773 if ( glyphpath->moveIsPending ) 1774 { 1775 /* emit offset 1st point as MoveTo */ 1776 cf2_glyphpath_pushMove( glyphpath, P0 ); 1777 1778 glyphpath->moveIsPending = FALSE; /* adjust state machine */ 1779 glyphpath->pathIsOpen = TRUE; 1780 1781 glyphpath->offsetStart1 = P1; /* record second point */ 1782 } 1783 1784 if ( glyphpath->elemIsQueued ) 1785 { 1786 FT_ASSERT( cf2_hintmap_isValid( &glyphpath->hintMap ) || 1787 glyphpath->hintMap.count == 0 ); 1788 1789 cf2_glyphpath_pushPrevElem( glyphpath, 1790 &glyphpath->hintMap, 1791 &P0, 1792 P1, 1793 FALSE ); 1794 } 1795 1796 /* queue the current element with offset points */ 1797 glyphpath->elemIsQueued = TRUE; 1798 glyphpath->prevElemOp = CF2_PathOpLineTo; 1799 glyphpath->prevElemP0 = P0; 1800 glyphpath->prevElemP1 = P1; 1801 1802 /* update current map */ 1803 if ( newHintMap ) 1804 cf2_hintmap_build( &glyphpath->hintMap, 1805 glyphpath->hStemHintArray, 1806 glyphpath->vStemHintArray, 1807 glyphpath->hintMask, 1808 glyphpath->hintOriginY, 1809 FALSE ); 1810 1811 glyphpath->currentCS.x = x; /* pre-offset current point */ 1812 glyphpath->currentCS.y = y; 1813 } 1814 1815 1816 FT_LOCAL_DEF( void ) 1817 cf2_glyphpath_curveTo( CF2_GlyphPath glyphpath, 1818 CF2_Fixed x1, 1819 CF2_Fixed y1, 1820 CF2_Fixed x2, 1821 CF2_Fixed y2, 1822 CF2_Fixed x3, 1823 CF2_Fixed y3 ) 1824 { 1825 CF2_Fixed xOffset1, yOffset1, xOffset3, yOffset3; 1826 FT_Vector P0, P1, P2, P3; 1827 1828 1829 /* TODO: ignore zero length portions of curve?? */ 1830 cf2_glyphpath_computeOffset( glyphpath, 1831 glyphpath->currentCS.x, 1832 glyphpath->currentCS.y, 1833 x1, 1834 y1, 1835 &xOffset1, 1836 &yOffset1 ); 1837 cf2_glyphpath_computeOffset( glyphpath, 1838 x2, 1839 y2, 1840 x3, 1841 y3, 1842 &xOffset3, 1843 &yOffset3 ); 1844 1845 /* add momentum from the middle segment */ 1846 glyphpath->callbacks->windingMomentum = 1847 ADD_INT32( glyphpath->callbacks->windingMomentum, 1848 cf2_getWindingMomentum( x1, y1, x2, y2 ) ); 1849 1850 /* construct offset points */ 1851 P0.x = ADD_INT32( glyphpath->currentCS.x, xOffset1 ); 1852 P0.y = ADD_INT32( glyphpath->currentCS.y, yOffset1 ); 1853 P1.x = ADD_INT32( x1, xOffset1 ); 1854 P1.y = ADD_INT32( y1, yOffset1 ); 1855 /* note: preserve angle of final segment by using offset3 at both ends */ 1856 P2.x = ADD_INT32( x2, xOffset3 ); 1857 P2.y = ADD_INT32( y2, yOffset3 ); 1858 P3.x = ADD_INT32( x3, xOffset3 ); 1859 P3.y = ADD_INT32( y3, yOffset3 ); 1860 1861 if ( glyphpath->moveIsPending ) 1862 { 1863 /* emit offset 1st point as MoveTo */ 1864 cf2_glyphpath_pushMove( glyphpath, P0 ); 1865 1866 glyphpath->moveIsPending = FALSE; 1867 glyphpath->pathIsOpen = TRUE; 1868 1869 glyphpath->offsetStart1 = P1; /* record second point */ 1870 } 1871 1872 if ( glyphpath->elemIsQueued ) 1873 { 1874 FT_ASSERT( cf2_hintmap_isValid( &glyphpath->hintMap ) || 1875 glyphpath->hintMap.count == 0 ); 1876 1877 cf2_glyphpath_pushPrevElem( glyphpath, 1878 &glyphpath->hintMap, 1879 &P0, 1880 P1, 1881 FALSE ); 1882 } 1883 1884 /* queue the current element with offset points */ 1885 glyphpath->elemIsQueued = TRUE; 1886 glyphpath->prevElemOp = CF2_PathOpCubeTo; 1887 glyphpath->prevElemP0 = P0; 1888 glyphpath->prevElemP1 = P1; 1889 glyphpath->prevElemP2 = P2; 1890 glyphpath->prevElemP3 = P3; 1891 1892 /* update current map */ 1893 if ( cf2_hintmask_isNew( glyphpath->hintMask ) ) 1894 cf2_hintmap_build( &glyphpath->hintMap, 1895 glyphpath->hStemHintArray, 1896 glyphpath->vStemHintArray, 1897 glyphpath->hintMask, 1898 glyphpath->hintOriginY, 1899 FALSE ); 1900 1901 glyphpath->currentCS.x = x3; /* pre-offset current point */ 1902 glyphpath->currentCS.y = y3; 1903 } 1904 1905 1906 FT_LOCAL_DEF( void ) 1907 cf2_glyphpath_closeOpenPath( CF2_GlyphPath glyphpath ) 1908 { 1909 if ( glyphpath->pathIsOpen ) 1910 { 1911 /* 1912 * A closing line in Character Space line is always generated below 1913 * with `cf2_glyphPath_lineTo'. It may be ignored later if it turns 1914 * out to be zero length in Device Space. 1915 */ 1916 glyphpath->pathIsClosing = TRUE; 1917 1918 cf2_glyphpath_lineTo( glyphpath, 1919 glyphpath->start.x, 1920 glyphpath->start.y ); 1921 1922 /* empty the final element from the queue and close the path */ 1923 if ( glyphpath->elemIsQueued ) 1924 cf2_glyphpath_pushPrevElem( glyphpath, 1925 &glyphpath->hintMap, 1926 &glyphpath->offsetStart0, 1927 glyphpath->offsetStart1, 1928 TRUE ); 1929 1930 /* reset state machine */ 1931 glyphpath->moveIsPending = TRUE; 1932 glyphpath->pathIsOpen = FALSE; 1933 glyphpath->pathIsClosing = FALSE; 1934 glyphpath->elemIsQueued = FALSE; 1935 } 1936 } 1937 1938 1939 /* END */ 1940