1 /****************************************************************************
   2  *
   3  * psblues.c
   4  *
   5  *   Adobe's code for handling Blue Zones (body).
   6  *
   7  * Copyright 2009-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 "psblues.h"
  43 #include "pshints.h"
  44 #include "psfont.h"
  45 
  46 
  47   /**************************************************************************
  48    *
  49    * The macro FT_COMPONENT is used in trace mode.  It is an implicit
  50    * parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log
  51    * messages during execution.
  52    */
  53 #undef  FT_COMPONENT
  54 #define FT_COMPONENT  cf2blues
  55 
  56 
  57   /*
  58    * For blue values, the FreeType parser produces an array of integers,
  59    * while the Adobe CFF engine produces an array of fixed.
  60    * Define a macro to convert FreeType to fixed.
  61    */
  62 #define cf2_blueToFixed( x )  cf2_intToFixed( x )
  63 
  64 
  65   FT_LOCAL_DEF( void )
  66   cf2_blues_init( CF2_Blues  blues,
  67                   CF2_Font   font )
  68   {
  69     /* pointer to parsed font object */
  70     PS_Decoder*  decoder = font->decoder;
  71 
  72     CF2_Fixed  zoneHeight;
  73     CF2_Fixed  maxZoneHeight = 0;
  74     CF2_Fixed  csUnitsPerPixel;
  75 
  76     size_t  numBlueValues;
  77     size_t  numOtherBlues;
  78     size_t  numFamilyBlues;
  79     size_t  numFamilyOtherBlues;
  80 
  81     FT_Pos*  blueValues;
  82     FT_Pos*  otherBlues;
  83     FT_Pos*  familyBlues;
  84     FT_Pos*  familyOtherBlues;
  85 
  86     size_t     i;
  87     CF2_Fixed  emBoxBottom, emBoxTop;
  88 
  89 #if 0
  90     CF2_Int  unitsPerEm = font->unitsPerEm;
  91 
  92 
  93     if ( unitsPerEm == 0 )
  94       unitsPerEm = 1000;
  95 #endif
  96 
  97     FT_ZERO( blues );
  98     blues->scale = font->innerTransform.d;
  99 
 100     cf2_getBlueMetrics( decoder,
 101                         &blues->blueScale,
 102                         &blues->blueShift,
 103                         &blues->blueFuzz );
 104 
 105     cf2_getBlueValues( decoder, &numBlueValues, &blueValues );
 106     cf2_getOtherBlues( decoder, &numOtherBlues, &otherBlues );
 107     cf2_getFamilyBlues( decoder, &numFamilyBlues, &familyBlues );
 108     cf2_getFamilyOtherBlues( decoder, &numFamilyOtherBlues, &familyOtherBlues );
 109 
 110     /*
 111      * synthetic em box hint heuristic
 112      *
 113      * Apply this when ideographic dictionary (LanguageGroup 1) has no
 114      * real alignment zones.  Adobe tools generate dummy zones at -250 and
 115      * 1100 for a 1000 unit em.  Fonts with ICF-based alignment zones
 116      * should not enable the heuristic.  When the heuristic is enabled,
 117      * the font's blue zones are ignored.
 118      *
 119      */
 120 
 121     /* get em box from OS/2 typoAscender/Descender                      */
 122     /* TODO: FreeType does not parse these metrics.  Skip them for now. */
 123 #if 0
 124     FCM_getHorizontalLineMetrics( &e,
 125                                   font->font,
 126                                   &ascender,
 127                                   &descender,
 128                                   &linegap );
 129     if ( ascender - descender == unitsPerEm )
 130     {
 131       emBoxBottom = cf2_intToFixed( descender );
 132       emBoxTop    = cf2_intToFixed( ascender );
 133     }
 134     else
 135 #endif
 136     {
 137       emBoxBottom = CF2_ICF_Bottom;
 138       emBoxTop    = CF2_ICF_Top;
 139     }
 140 
 141     if ( cf2_getLanguageGroup( decoder ) == 1                   &&
 142          ( numBlueValues == 0                                 ||
 143            ( numBlueValues == 4                             &&
 144              cf2_blueToFixed( blueValues[0] ) < emBoxBottom &&
 145              cf2_blueToFixed( blueValues[1] ) < emBoxBottom &&
 146              cf2_blueToFixed( blueValues[2] ) > emBoxTop    &&
 147              cf2_blueToFixed( blueValues[3] ) > emBoxTop    ) ) )
 148     {
 149       /*
 150        * Construct hint edges suitable for synthetic ghost hints at top
 151        * and bottom of em box.  +-CF2_MIN_COUNTER allows for unhinted
 152        * features above or below the last hinted edge.  This also gives a
 153        * net 1 pixel boost to the height of ideographic glyphs.
 154        *
 155        * Note: Adjust synthetic hints outward by epsilon (0x.0001) to
 156        *       avoid interference.  E.g., some fonts have real hints at
 157        *       880 and -120.
 158        */
 159 
 160       blues->emBoxBottomEdge.csCoord = emBoxBottom - CF2_FIXED_EPSILON;
 161       blues->emBoxBottomEdge.dsCoord = cf2_fixedRound(
 162                                          FT_MulFix(
 163                                            blues->emBoxBottomEdge.csCoord,
 164                                            blues->scale ) ) -
 165                                        CF2_MIN_COUNTER;
 166       blues->emBoxBottomEdge.scale   = blues->scale;
 167       blues->emBoxBottomEdge.flags   = CF2_GhostBottom |
 168                                        CF2_Locked |
 169                                        CF2_Synthetic;
 170 
 171       blues->emBoxTopEdge.csCoord = emBoxTop + CF2_FIXED_EPSILON +
 172                                     2 * font->darkenY;
 173       blues->emBoxTopEdge.dsCoord = cf2_fixedRound(
 174                                       FT_MulFix(
 175                                         blues->emBoxTopEdge.csCoord,
 176                                         blues->scale ) ) +
 177                                     CF2_MIN_COUNTER;
 178       blues->emBoxTopEdge.scale   = blues->scale;
 179       blues->emBoxTopEdge.flags   = CF2_GhostTop |
 180                                     CF2_Locked |
 181                                     CF2_Synthetic;
 182 
 183       blues->doEmBoxHints = TRUE;    /* enable the heuristic */
 184 
 185       return;
 186     }
 187 
 188     /* copy `BlueValues' and `OtherBlues' to a combined array of top and */
 189     /* bottom zones                                                      */
 190     for ( i = 0; i < numBlueValues; i += 2 )
 191     {
 192       blues->zone[blues->count].csBottomEdge =
 193         cf2_blueToFixed( blueValues[i] );
 194       blues->zone[blues->count].csTopEdge =
 195         cf2_blueToFixed( blueValues[i + 1] );
 196 
 197       zoneHeight = SUB_INT32( blues->zone[blues->count].csTopEdge,
 198                               blues->zone[blues->count].csBottomEdge );
 199 
 200       if ( zoneHeight < 0 )
 201       {
 202         FT_TRACE4(( "cf2_blues_init: ignoring negative zone height\n" ));
 203         continue;   /* reject this zone */
 204       }
 205 
 206       if ( zoneHeight > maxZoneHeight )
 207       {
 208         /* take maximum before darkening adjustment      */
 209         /* so overshoot suppression point doesn't change */
 210         maxZoneHeight = zoneHeight;
 211       }
 212 
 213       /* adjust both edges of top zone upward by twice darkening amount */
 214       if ( i != 0 )
 215       {
 216         blues->zone[blues->count].csTopEdge    += 2 * font->darkenY;
 217         blues->zone[blues->count].csBottomEdge += 2 * font->darkenY;
 218       }
 219 
 220       /* first `BlueValue' is bottom zone; others are top */
 221       if ( i == 0 )
 222       {
 223         blues->zone[blues->count].bottomZone =
 224           TRUE;
 225         blues->zone[blues->count].csFlatEdge =
 226           blues->zone[blues->count].csTopEdge;
 227       }
 228       else
 229       {
 230         blues->zone[blues->count].bottomZone =
 231           FALSE;
 232         blues->zone[blues->count].csFlatEdge =
 233           blues->zone[blues->count].csBottomEdge;
 234       }
 235 
 236       blues->count += 1;
 237     }
 238 
 239     for ( i = 0; i < numOtherBlues; i += 2 )
 240     {
 241       blues->zone[blues->count].csBottomEdge =
 242         cf2_blueToFixed( otherBlues[i] );
 243       blues->zone[blues->count].csTopEdge =
 244         cf2_blueToFixed( otherBlues[i + 1] );
 245 
 246       zoneHeight = SUB_INT32( blues->zone[blues->count].csTopEdge,
 247                               blues->zone[blues->count].csBottomEdge );
 248 
 249       if ( zoneHeight < 0 )
 250       {
 251         FT_TRACE4(( "cf2_blues_init: ignoring negative zone height\n" ));
 252         continue;   /* reject this zone */
 253       }
 254 
 255       if ( zoneHeight > maxZoneHeight )
 256       {
 257         /* take maximum before darkening adjustment      */
 258         /* so overshoot suppression point doesn't change */
 259         maxZoneHeight = zoneHeight;
 260       }
 261 
 262       /* Note: bottom zones are not adjusted for darkening amount */
 263 
 264       /* all OtherBlues are bottom zone */
 265       blues->zone[blues->count].bottomZone =
 266         TRUE;
 267       blues->zone[blues->count].csFlatEdge =
 268         blues->zone[blues->count].csTopEdge;
 269 
 270       blues->count += 1;
 271     }
 272 
 273     /* Adjust for FamilyBlues */
 274 
 275     /* Search for the nearest flat edge in `FamilyBlues' or                */
 276     /* `FamilyOtherBlues'.  According to the Black Book, any matching edge */
 277     /* must be within one device pixel                                     */
 278 
 279     csUnitsPerPixel = FT_DivFix( cf2_intToFixed( 1 ), blues->scale );
 280 
 281     /* loop on all zones in this font */
 282     for ( i = 0; i < blues->count; i++ )
 283     {
 284       size_t     j;
 285       CF2_Fixed  minDiff;
 286       CF2_Fixed  flatFamilyEdge, diff;
 287       /* value for this font */
 288       CF2_Fixed  flatEdge = blues->zone[i].csFlatEdge;
 289 
 290 
 291       if ( blues->zone[i].bottomZone )
 292       {
 293         /* In a bottom zone, the top edge is the flat edge.             */
 294         /* Search `FamilyOtherBlues' for bottom zones; look for closest */
 295         /* Family edge that is within the one pixel threshold.          */
 296 
 297         minDiff = CF2_FIXED_MAX;
 298 
 299         for ( j = 0; j < numFamilyOtherBlues; j += 2 )
 300         {
 301           /* top edge */
 302           flatFamilyEdge = cf2_blueToFixed( familyOtherBlues[j + 1] );
 303 
 304           diff = cf2_fixedAbs( SUB_INT32( flatEdge, flatFamilyEdge ) );
 305 
 306           if ( diff < minDiff && diff < csUnitsPerPixel )
 307           {
 308             blues->zone[i].csFlatEdge = flatFamilyEdge;
 309             minDiff                   = diff;
 310 
 311             if ( diff == 0 )
 312               break;
 313           }
 314         }
 315 
 316         /* check the first member of FamilyBlues, which is a bottom zone */
 317         if ( numFamilyBlues >= 2 )
 318         {
 319           /* top edge */
 320           flatFamilyEdge = cf2_blueToFixed( familyBlues[1] );
 321 
 322           diff = cf2_fixedAbs( SUB_INT32( flatEdge, flatFamilyEdge ) );
 323 
 324           if ( diff < minDiff && diff < csUnitsPerPixel )
 325             blues->zone[i].csFlatEdge = flatFamilyEdge;
 326         }
 327       }
 328       else
 329       {
 330         /* In a top zone, the bottom edge is the flat edge.                */
 331         /* Search `FamilyBlues' for top zones; skip first zone, which is a */
 332         /* bottom zone; look for closest Family edge that is within the    */
 333         /* one pixel threshold                                             */
 334 
 335         minDiff = CF2_FIXED_MAX;
 336 
 337         for ( j = 2; j < numFamilyBlues; j += 2 )
 338         {
 339           /* bottom edge */
 340           flatFamilyEdge = cf2_blueToFixed( familyBlues[j] );
 341 
 342           /* adjust edges of top zone upward by twice darkening amount */
 343           flatFamilyEdge += 2 * font->darkenY;      /* bottom edge */
 344 
 345           diff = cf2_fixedAbs( SUB_INT32( flatEdge, flatFamilyEdge ) );
 346 
 347           if ( diff < minDiff && diff < csUnitsPerPixel )
 348           {
 349             blues->zone[i].csFlatEdge = flatFamilyEdge;
 350             minDiff                   = diff;
 351 
 352             if ( diff == 0 )
 353               break;
 354           }
 355         }
 356       }
 357     }
 358 
 359     /* TODO: enforce separation of zones, including BlueFuzz */
 360 
 361     /* Adjust BlueScale; similar to AdjustBlueScale() in coretype */
 362     /* `bcsetup.c'.                                               */
 363 
 364     if ( maxZoneHeight > 0 )
 365     {
 366       if ( blues->blueScale > FT_DivFix( cf2_intToFixed( 1 ),
 367                                          maxZoneHeight ) )
 368       {
 369         /* clamp at maximum scale */
 370         blues->blueScale = FT_DivFix( cf2_intToFixed( 1 ),
 371                                       maxZoneHeight );
 372       }
 373 
 374       /*
 375        * TODO: Revisit the bug fix for 613448.  The minimum scale
 376        *       requirement catches a number of library fonts.  For
 377        *       example, with default BlueScale (.039625) and 0.4 minimum,
 378        *       the test below catches any font with maxZoneHeight < 10.1.
 379        *       There are library fonts ranging from 2 to 10 that get
 380        *       caught, including e.g., Eurostile LT Std Medium with
 381        *       maxZoneHeight of 6.
 382        *
 383        */
 384 #if 0
 385       if ( blueScale < .4 / maxZoneHeight )
 386       {
 387         tetraphilia_assert( 0 );
 388         /* clamp at minimum scale, per bug 0613448 fix */
 389         blueScale = .4 / maxZoneHeight;
 390       }
 391 #endif
 392 
 393     }
 394 
 395     /*
 396      * Suppress overshoot and boost blue zones at small sizes.  Boost
 397      * amount varies linearly from 0.5 pixel near 0 to 0 pixel at
 398      * blueScale cutoff.
 399      * Note: This boost amount is different from the coretype heuristic.
 400      *
 401      */
 402 
 403     if ( blues->scale < blues->blueScale )
 404     {
 405       blues->suppressOvershoot = TRUE;
 406 
 407       /* Change rounding threshold for `dsFlatEdge'.                    */
 408       /* Note: constant changed from 0.5 to 0.6 to avoid a problem with */
 409       /*       10ppem Arial                                             */
 410 
 411       blues->boost = cf2_doubleToFixed( .6 ) -
 412                        FT_MulDiv( cf2_doubleToFixed ( .6 ),
 413                                   blues->scale,
 414                                   blues->blueScale );
 415       if ( blues->boost > 0x7FFF )
 416       {
 417         /* boost must remain less than 0.5, or baseline could go negative */
 418         blues->boost = 0x7FFF;
 419       }
 420     }
 421 
 422     /* boost and darkening have similar effects; don't do both */
 423     if ( font->stemDarkened )
 424       blues->boost = 0;
 425 
 426     /* set device space alignment for each zone;    */
 427     /* apply boost amount before rounding flat edge */
 428 
 429     for ( i = 0; i < blues->count; i++ )
 430     {
 431       if ( blues->zone[i].bottomZone )
 432         blues->zone[i].dsFlatEdge = cf2_fixedRound(
 433                                       FT_MulFix(
 434                                         blues->zone[i].csFlatEdge,
 435                                         blues->scale ) -
 436                                       blues->boost );
 437       else
 438         blues->zone[i].dsFlatEdge = cf2_fixedRound(
 439                                       FT_MulFix(
 440                                         blues->zone[i].csFlatEdge,
 441                                         blues->scale ) +
 442                                       blues->boost );
 443     }
 444   }
 445 
 446 
 447   /*
 448    * Check whether `stemHint' is captured by one of the blue zones.
 449    *
 450    * Zero, one or both edges may be valid; only valid edges can be
 451    * captured.  For compatibility with CoolType, search top and bottom
 452    * zones in the same pass (see `BlueLock').  If a hint is captured,
 453    * return true and position the edge(s) in one of 3 ways:
 454    *
 455    * 1) If `BlueScale' suppresses overshoot, position the captured edge
 456    *    at the flat edge of the zone.
 457    * 2) If overshoot is not suppressed and `BlueShift' requires
 458    *    overshoot, position the captured edge a minimum of 1 device pixel
 459    *    from the flat edge.
 460    * 3) If overshoot is not suppressed or required, position the captured
 461    *    edge at the nearest device pixel.
 462    *
 463    */
 464   FT_LOCAL_DEF( FT_Bool )
 465   cf2_blues_capture( const CF2_Blues  blues,
 466                      CF2_Hint         bottomHintEdge,
 467                      CF2_Hint         topHintEdge )
 468   {
 469     /* TODO: validate? */
 470     CF2_Fixed  csFuzz = blues->blueFuzz;
 471 
 472     /* new position of captured edge */
 473     CF2_Fixed  dsNew;
 474 
 475     /* amount that hint is moved when positioned */
 476     CF2_Fixed  dsMove = 0;
 477 
 478     FT_Bool   captured = FALSE;
 479     CF2_UInt  i;
 480 
 481 
 482     /* assert edge flags are consistent */
 483     FT_ASSERT( !cf2_hint_isTop( bottomHintEdge ) &&
 484                !cf2_hint_isBottom( topHintEdge ) );
 485 
 486     /* TODO: search once without blue fuzz for compatibility with coretype? */
 487     for ( i = 0; i < blues->count; i++ )
 488     {
 489       if ( blues->zone[i].bottomZone           &&
 490            cf2_hint_isBottom( bottomHintEdge ) )
 491       {
 492         if ( SUB_INT32( blues->zone[i].csBottomEdge, csFuzz ) <=
 493                bottomHintEdge->csCoord                           &&
 494              bottomHintEdge->csCoord <=
 495                ADD_INT32( blues->zone[i].csTopEdge, csFuzz )     )
 496         {
 497           /* bottom edge captured by bottom zone */
 498 
 499           if ( blues->suppressOvershoot )
 500             dsNew = blues->zone[i].dsFlatEdge;
 501 
 502           else if ( SUB_INT32( blues->zone[i].csTopEdge,
 503                                bottomHintEdge->csCoord ) >=
 504                       blues->blueShift )
 505           {
 506             /* guarantee minimum of 1 pixel overshoot */
 507             dsNew = FT_MIN(
 508                       cf2_fixedRound( bottomHintEdge->dsCoord ),
 509                       blues->zone[i].dsFlatEdge - cf2_intToFixed( 1 ) );
 510           }
 511 
 512           else
 513           {
 514             /* simply round captured edge */
 515             dsNew = cf2_fixedRound( bottomHintEdge->dsCoord );
 516           }
 517 
 518           dsMove   = SUB_INT32( dsNew, bottomHintEdge->dsCoord );
 519           captured = TRUE;
 520 
 521           break;
 522         }
 523       }
 524 
 525       if ( !blues->zone[i].bottomZone && cf2_hint_isTop( topHintEdge ) )
 526       {
 527         if ( SUB_INT32( blues->zone[i].csBottomEdge, csFuzz ) <=
 528                topHintEdge->csCoord                              &&
 529              topHintEdge->csCoord <=
 530                ADD_INT32( blues->zone[i].csTopEdge, csFuzz )     )
 531         {
 532           /* top edge captured by top zone */
 533 
 534           if ( blues->suppressOvershoot )
 535             dsNew = blues->zone[i].dsFlatEdge;
 536 
 537           else if ( SUB_INT32( topHintEdge->csCoord,
 538                                blues->zone[i].csBottomEdge ) >=
 539                       blues->blueShift )
 540           {
 541             /* guarantee minimum of 1 pixel overshoot */
 542             dsNew = FT_MAX(
 543                       cf2_fixedRound( topHintEdge->dsCoord ),
 544                       blues->zone[i].dsFlatEdge + cf2_intToFixed( 1 ) );
 545           }
 546 
 547           else
 548           {
 549             /* simply round captured edge */
 550             dsNew = cf2_fixedRound( topHintEdge->dsCoord );
 551           }
 552 
 553           dsMove   = SUB_INT32( dsNew, topHintEdge->dsCoord );
 554           captured = TRUE;
 555 
 556           break;
 557         }
 558       }
 559     }
 560 
 561     if ( captured )
 562     {
 563       /* move both edges and flag them `locked' */
 564       if ( cf2_hint_isValid( bottomHintEdge ) )
 565       {
 566         bottomHintEdge->dsCoord = ADD_INT32( bottomHintEdge->dsCoord,
 567                                              dsMove );
 568         cf2_hint_lock( bottomHintEdge );
 569       }
 570 
 571       if ( cf2_hint_isValid( topHintEdge ) )
 572       {
 573         topHintEdge->dsCoord = ADD_INT32( topHintEdge->dsCoord, dsMove );
 574         cf2_hint_lock( topHintEdge );
 575       }
 576     }
 577 
 578     return captured;
 579   }
 580 
 581 
 582 /* END */