freetype-commit
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Git][freetype/freetype][split-conic-dda] 4 commits: Support architectur


From: David Turner (@david.freetype)
Subject: [Git][freetype/freetype][split-conic-dda] 4 commits: Support architectures where `long` is smaller than pointers.
Date: Thu, 15 Jul 2021 11:28:45 +0000

David Turner pushed to branch split-conic-dda at FreeType / FreeType

Commits:

6 changed files:

Changes:

  • ChangeLog
    1
    +2021-07-15  David Turner  <david@freetype.org>
    
    2
    +
    
    3
    +	[smooth] Implement Bezier quadratic arc flattenning with DDA
    
    4
    +
    
    5
    +	Benchmarking shows that this provides a very slighty performance
    
    6
    +	boost when rendering fonts with lots of quadratic bezier arcs,
    
    7
    +	compared to the recursive arc splitting, but only when SSE2 is
    
    8
    +	available, or on 64-bit CPUs.
    
    9
    +
    
    10
    +	* src/smooth/ftgrays.c (gray_render_conic): New implementation
    
    11
    +	based on DDA and optionally SSE2.
    
    12
    +
    
    13
    +2021-07-15  David Turner  <david@freetype.org>
    
    14
    +
    
    15
    +	[smooth] Minor speedup to smooth rasterizer
    
    16
    +
    
    17
    +	This speeds up the smooth rasterizer by avoiding a conditional
    
    18
    +	branches in the hot path.
    
    19
    +
    
    20
    +	* src/smooth/ftgrays.c: Define a null cell used to both as a
    
    21
    +	sentinel for all linked-lists, and to accumulate coverage and
    
    22
    +	area values for "out-of-bounds" cell positions without a
    
    23
    +	conditional check.
    
    24
    +
    
    25
    +2021-07-15  David Turner  <david@freetype.org>
    
    26
    +
    
    27
    +	Replaces download-test-fonts.sh with download-test-fonts.py which
    
    28
    +	does the same work, and also avoids downloading anything if the
    
    29
    +	files are already installed with the right content.
    
    30
    +
    
    31
    +	Now uses the first 8 byte of each file's sha256 hash for the digest.
    
    32
    +
    
    33
    +	* tests/scripts/download-test-fonts.sh: Removed
    
    34
    +	* tests/scripts/download-test-fonts.py: New script
    
    35
    +	* tests/README.md: Updated
    
    36
    +
    
    37
    +2021-07-15  Alex Richardson  <Alexander.Richardson@cl.cam.ac.uk>
    
    38
    +
    
    39
    +	Support architectures where `long` is smaller than pointers.
    
    40
    +
    
    41
    +	I am currently trying to compile FreeType for CHERI-extended ISAs
    
    42
    +	(CHERI-RISC-V and Arm's Morello), but I am getting compiler warnings
    
    43
    +	from the `FT_UINT_TO_POINTER` macro.  When compiling with the CHERI
    
    44
    +	Clang compiler, not using `uinptr_t` for casts between integers an
    
    45
    +	pointers results in the following `-Werror` build failures:
    
    46
    +
    
    47
    +	```
    
    48
    +	In file included from .../src/truetype/truetype.c:22:
    
    49
    +	  .../src/truetype/ttgload.c:1925:22: error:
    
    50
    +	    cast from provenance-free integer type to pointer type will
    
    51
    +	    give pointer that can not be dereferenced
    
    52
    +	    [-Werror,-Wcheri-capability-misuse]
    
    53
    +	  node->data = "" glyph_index );
    
    54
    +	               ^
    
    55
    +	  .../include/freetype/internal/compiler-macros.h:79:34: note:
    
    56
    +	    expanded from macro 'FT_UINT_TO_POINTER'
    
    57
    +	```
    
    58
    +
    
    59
    +	* include/freetype/internal/compiler-macros.h (FT_UINT_TO_POINTER):
    
    60
    +	The ISO C standard compliant fix for this would be to use
    
    61
    +	`uintptr_t` from `stdint.h`, but I am not sure if this is supported
    
    62
    +	by the minimum compiler version.  Therefore, use the
    
    63
    +	compiler-defined `__UINTPTR_TYPE__` macro (supported in GCC 4.6+ and
    
    64
    +	Clang since about 3.0) before checking for `_WIN64` and falling back
    
    65
    +	to `unsigned long`.
    
    66
    +
    
    1 67
     2021-07-13  Oleg Oshmyan  <chortos@inbox.lv>
    
    2 68
     
    
    3 69
     	[base] Fix `FT_Open_Face`'s handling of user-supplied streams.
    

  • include/freetype/internal/compiler-macros.h
    ... ... @@ -71,12 +71,18 @@ FT_BEGIN_HEADER
    71 71
        */
    
    72 72
     #define FT_DUMMY_STMNT  FT_BEGIN_STMNT FT_END_STMNT
    
    73 73
     
    
    74
    -#ifdef _WIN64
    
    74
    +#ifdef __UINTPTR_TYPE__
    
    75
    +  /*
    
    76
    +   * GCC and Clang both provide a `__UINTPTR_TYPE__` that can be used to
    
    77
    +   * avoid a dependency on `stdint.h`.
    
    78
    +   */
    
    79
    +#  define FT_UINT_TO_POINTER( x )  (void *)(__UINTPTR_TYPE__)(x)
    
    80
    +#elif defined( _WIN64 )
    
    75 81
       /* only 64bit Windows uses the LLP64 data model, i.e., */
    
    76 82
       /* 32-bit integers, 64-bit pointers.                   */
    
    77
    -#define FT_UINT_TO_POINTER( x )  (void *)(unsigned __int64)(x)
    
    83
    +#  define FT_UINT_TO_POINTER( x )  (void *)(unsigned __int64)(x)
    
    78 84
     #else
    
    79
    -#define FT_UINT_TO_POINTER( x )  (void *)(unsigned long)(x)
    
    85
    +#  define FT_UINT_TO_POINTER( x )  (void *)(unsigned long)(x)
    
    80 86
     #endif
    
    81 87
     
    
    82 88
       /*
    

  • src/smooth/ftgrays.c
    ... ... @@ -479,19 +479,24 @@ typedef ptrdiff_t FT_PtrDist;
    479 479
       {
    
    480 480
         ft_jmp_buf  jump_buffer;
    
    481 481
     
    
    482
    -    TCoord  min_ex, max_ex;
    
    482
    +    TCoord  min_ex, max_ex;  /* min and max integer pixel coordinates */
    
    483 483
         TCoord  min_ey, max_ey;
    
    484
    +    TCoord  count_ey;        /* same as (max_ey - min_ey) */
    
    484 485
     
    
    485
    -    PCell       cell;
    
    486
    -    PCell*      ycells;
    
    487
    -    PCell       cells;
    
    488
    -    FT_PtrDist  max_cells;
    
    489
    -    FT_PtrDist  num_cells;
    
    486
    +    PCell       cell;        /* current cell                             */
    
    487
    +    PCell       cell_free;   /* call allocation next free slot           */
    
    488
    +    PCell       cell_limit;  /* cell allocation limit                    */
    
    490 489
     
    
    491
    -    TPos    x,  y;
    
    490
    +    PCell*      ycells;      /* array of cell linked-lists, one per      */
    
    491
    +							 /* vertical coordinate in the current band. */
    
    492 492
     
    
    493
    -    FT_Outline  outline;
    
    494
    -    TPixmap     target;
    
    493
    +    PCell       cells;       /* cell storage area     */
    
    494
    +    FT_PtrDist  max_cells;   /* cell storage capacity */
    
    495
    +
    
    496
    +    TPos        x,  y;       /* last point position */
    
    497
    +
    
    498
    +    FT_Outline  outline;     /* input outline */
    
    499
    +    TPixmap     target;      /* target pixmap */
    
    495 500
     
    
    496 501
         FT_Raster_Span_Func  render_span;
    
    497 502
         void*                render_span_data;
    
    ... ... @@ -502,21 +507,34 @@ typedef ptrdiff_t FT_PtrDist;
    502 507
     #pragma warning( pop )
    
    503 508
     #endif
    
    504 509
     
    
    505
    -
    
    506 510
     #ifndef FT_STATIC_RASTER
    
    507 511
     #define ras  (*worker)
    
    508 512
     #else
    
    509 513
       static gray_TWorker  ras;
    
    510 514
     #endif
    
    511 515
     
    
    512
    -#define FT_INTEGRATE( ras, a, b )                                       \
    
    513
    -           if ( ras.cell )                                              \
    
    514
    -             ras.cell->cover += (a), ras.cell->area += (a) * (TArea)(b)
    
    516
    +/* Return a pointer to the "null cell", used as a sentinel at the end   */
    
    517
    +/* of all ycells[] linked lists. Its x coordinate should be maximal     */
    
    518
    +/* to ensure no NULL checks are necessary when looking for an insertion */
    
    519
    +/* point in gray_set_cell(). Other loops should check the cell pointer  */
    
    520
    +/* with CELL_IS_NULL() to detect the end of the list.                   */
    
    521
    +#define NULL_CELL_PTR(ras)  (ras).cells
    
    522
    +
    
    523
    +/* The |x| value of the null cell. Must be the largest possible */
    
    524
    +/* integer value stored in a TCell.x field.                     */
    
    525
    +#define CELL_MAX_X_VALUE    INT_MAX
    
    526
    +
    
    527
    +/* Return true iff |cell| points to the null cell. */
    
    528
    +#define CELL_IS_NULL(cell)  ((cell)->x == CELL_MAX_X_VALUE)
    
    529
    +
    
    530
    +
    
    531
    +#define FT_INTEGRATE( ras, a, b )                                     \
    
    532
    +           ras.cell->cover += (a), ras.cell->area += (a) * (TArea)(b)
    
    515 533
     
    
    516 534
     
    
    517 535
       typedef struct gray_TRaster_
    
    518 536
       {
    
    519
    -    void*         memory;
    
    537
    +    void*  memory;
    
    520 538
     
    
    521 539
       } gray_TRaster, *gray_PRaster;
    
    522 540
     
    
    ... ... @@ -538,7 +556,7 @@ typedef ptrdiff_t FT_PtrDist;
    538 556
     
    
    539 557
           printf( "%3d:", y );
    
    540 558
     
    
    541
    -      for ( ; cell != NULL; cell = cell->next )
    
    559
    +      for ( ; !CELL_IS_NULL(cell); cell = cell->next )
    
    542 560
             printf( " (%3d, c:%4d, a:%6d)",
    
    543 561
                     cell->x, cell->cover, cell->area );
    
    544 562
           printf( "\n" );
    
    ... ... @@ -566,11 +584,12 @@ typedef ptrdiff_t FT_PtrDist;
    566 584
         /* Note that if a cell is to the left of the clipping region, it is    */
    
    567 585
         /* actually set to the (min_ex-1) horizontal position.                 */
    
    568 586
     
    
    569
    -    if ( ey >= ras.max_ey || ey < ras.min_ey || ex >= ras.max_ex )
    
    570
    -      ras.cell = NULL;
    
    587
    +    TCoord ey_index = ey - ras.min_ey;
    
    588
    +    if ( ey_index < 0 || ey_index >= ras.count_ey || ex >= ras.max_ex )
    
    589
    +      ras.cell = NULL_CELL_PTR(ras);
    
    571 590
         else
    
    572 591
         {
    
    573
    -      PCell*  pcell = ras.ycells + ey - ras.min_ey;
    
    592
    +      PCell*  pcell = ras.ycells + ey_index;
    
    574 593
           PCell   cell;
    
    575 594
     
    
    576 595
     
    
    ... ... @@ -580,7 +599,7 @@ typedef ptrdiff_t FT_PtrDist;
    580 599
           {
    
    581 600
             cell = *pcell;
    
    582 601
     
    
    583
    -        if ( !cell || cell->x > ex )
    
    602
    +        if ( cell->x > ex )
    
    584 603
               break;
    
    585 604
     
    
    586 605
             if ( cell->x == ex )
    
    ... ... @@ -589,11 +608,11 @@ typedef ptrdiff_t FT_PtrDist;
    589 608
             pcell = &cell->next;
    
    590 609
           }
    
    591 610
     
    
    592
    -      if ( ras.num_cells >= ras.max_cells )
    
    611
    +      /* insert new cell */
    
    612
    +      cell = ras.cell_free++;
    
    613
    +      if (cell >= ras.cell_limit)
    
    593 614
             ft_longjmp( ras.jump_buffer, 1 );
    
    594 615
     
    
    595
    -      /* insert new cell */
    
    596
    -      cell        = ras.cells + ras.num_cells++;
    
    597 616
           cell->x     = ex;
    
    598 617
           cell->area  = 0;
    
    599 618
           cell->cover = 0;
    
    ... ... @@ -974,6 +993,188 @@ typedef ptrdiff_t FT_PtrDist;
    974 993
     
    
    975 994
     #endif
    
    976 995
     
    
    996
    +/* Benchmarking shows that using DDA to flatten the quadratic bezier
    
    997
    + * arcs is slightly faster in the following cases:
    
    998
    + *
    
    999
    + *   - When the host CPU is 64-bit.
    
    1000
    + *   - When SSE2 SIMD registers and instructions are available (even on x86).
    
    1001
    + *
    
    1002
    + * For other cases, using binary splits is actually slightly faster.
    
    1003
    + */
    
    1004
    +#if defined(__SSE2__) || defined(__x86_64__) || defined(__aarch64__) || defined(_M_AMD64) || defined(_M_ARM64)
    
    1005
    +#define BEZIER_USE_DDA  1
    
    1006
    +#else
    
    1007
    +#define BEZIER_USE_DDA  0
    
    1008
    +#endif
    
    1009
    +
    
    1010
    +#if BEZIER_USE_DDA
    
    1011
    +
    
    1012
    +#include <emmintrin.h>
    
    1013
    +
    
    1014
    +  static void
    
    1015
    +  gray_render_conic( RAS_ARG_ const FT_Vector*  control,
    
    1016
    +                              const FT_Vector*  to )
    
    1017
    +  {
    
    1018
    +    FT_Vector  p0, p1, p2;
    
    1019
    +
    
    1020
    +    p0.x = ras.x;
    
    1021
    +    p0.y = ras.y;
    
    1022
    +    p1.x = UPSCALE( control->x );
    
    1023
    +    p1.y = UPSCALE( control->y );
    
    1024
    +    p2.x = UPSCALE( to->x );
    
    1025
    +    p2.y = UPSCALE( to->y );
    
    1026
    +
    
    1027
    +    /* short-cut the arc that crosses the current band */
    
    1028
    +    if ( ( TRUNC( p0.y ) >= ras.max_ey &&
    
    1029
    +           TRUNC( p1.y ) >= ras.max_ey &&
    
    1030
    +           TRUNC( p2.y ) >= ras.max_ey ) ||
    
    1031
    +         ( TRUNC( p0.y ) <  ras.min_ey &&
    
    1032
    +           TRUNC( p1.y ) <  ras.min_ey &&
    
    1033
    +           TRUNC( p2.y ) <  ras.min_ey ) )
    
    1034
    +    {
    
    1035
    +      ras.x = p2.x;
    
    1036
    +      ras.y = p2.y;
    
    1037
    +      return;
    
    1038
    +    }
    
    1039
    +
    
    1040
    +    TPos dx = FT_ABS( p0.x + p2.x - 2 * p1.x );
    
    1041
    +    TPos dy = FT_ABS( p0.y + p2.y - 2 * p1.y );
    
    1042
    +    if ( dx < dy )
    
    1043
    +      dx = dy;
    
    1044
    +
    
    1045
    +    if ( dx <= ONE_PIXEL / 4 )
    
    1046
    +    {
    
    1047
    +      gray_render_line( RAS_VAR_ p2.x, p2.y );
    
    1048
    +      return;
    
    1049
    +    }
    
    1050
    +
    
    1051
    +    /* We can calculate the number of necessary bisections because  */
    
    1052
    +    /* each bisection predictably reduces deviation exactly 4-fold. */
    
    1053
    +    /* Even 32-bit deviation would vanish after 16 bisections.      */
    
    1054
    +    int shift = 0;
    
    1055
    +    do
    
    1056
    +    {
    
    1057
    +      dx   >>= 2;
    
    1058
    +      shift += 1;
    
    1059
    +    }
    
    1060
    +    while (dx > ONE_PIXEL / 4);
    
    1061
    +
    
    1062
    +    /*
    
    1063
    +     * The (P0,P1,P2) arc equation, for t in [0,1] range:
    
    1064
    +     *
    
    1065
    +     * P(t) = P0*(1-t)^2 + P1*2*t*(1-t) + P2*t^2
    
    1066
    +     *
    
    1067
    +     * P(t) = P0 + 2*(P1-P0)*t + (P0+P2-2*P1)*t^2
    
    1068
    +     *      = P0 + 2*B*t + A*t^2
    
    1069
    +     *
    
    1070
    +     *    for A = P0 + P2 - 2*P1
    
    1071
    +     *    and B = P1 - P0
    
    1072
    +     *
    
    1073
    +     * Let's consider the difference when advancing by a small
    
    1074
    +     * parameter h:
    
    1075
    +     *
    
    1076
    +     *    Q(h,t) = P(t+h) - P(t) = 2*B*h + A*h^2 + 2*A*h*t
    
    1077
    +     *
    
    1078
    +     * And then its own difference:
    
    1079
    +     *
    
    1080
    +     *    R(h,t) = Q(h,t+h) - Q(h,t) = 2*A*h*h = R (constant)
    
    1081
    +     *
    
    1082
    +     * Since R is always a constant, it is possible to compute
    
    1083
    +     * successive positions with:
    
    1084
    +     *
    
    1085
    +     *     P = P0
    
    1086
    +     *     Q = Q(h,0) = 2*B*h + A*h*h
    
    1087
    +     *     R = 2*A*h*h
    
    1088
    +     *
    
    1089
    +     *   loop:
    
    1090
    +     *     P += Q
    
    1091
    +     *     Q += R
    
    1092
    +     *     EMIT(P)
    
    1093
    +     *
    
    1094
    +     * To ensure accurate results, perform computations on 64-bit
    
    1095
    +     * values, after scaling them by 2^32:
    
    1096
    +     *
    
    1097
    +     *     R << 32   = 2 * A << (32 - N - N)
    
    1098
    +     *               = A << (33 - 2 *N)
    
    1099
    +     *
    
    1100
    +     *     Q << 32   = (2 * B << (32 - N)) + (A << (32 - N - N))
    
    1101
    +     *               = (B << (33 - N)) + (A << (32 - N - N))
    
    1102
    +     */
    
    1103
    +#ifdef __SSE2__
    
    1104
    +    /* Experience shows that for small shift values, SSE2 is actually slower. */
    
    1105
    +    if (shift > 2) {
    
    1106
    +      union {
    
    1107
    +        struct { FT_Int64 ax, ay, bx, by; } i;
    
    1108
    +        struct { __m128i a, b; } vec;
    
    1109
    +      } u;
    
    1110
    +
    
    1111
    +      u.i.ax = p0.x + p2.x - 2 * p1.x;
    
    1112
    +      u.i.ay = p0.y + p2.y - 2 * p1.y;
    
    1113
    +      u.i.bx = p1.x - p0.x;
    
    1114
    +      u.i.by = p1.y - p0.y;
    
    1115
    +
    
    1116
    +      __m128i a = _mm_load_si128(&u.vec.a);
    
    1117
    +      __m128i b = _mm_load_si128(&u.vec.b);
    
    1118
    +
    
    1119
    +      __m128i r = _mm_slli_epi64(a, 33 - 2 * shift);
    
    1120
    +      __m128i q = _mm_slli_epi64(b, 33 - shift);
    
    1121
    +      __m128i q2 = _mm_slli_epi64(a, 32 - 2 * shift);
    
    1122
    +      q = _mm_add_epi64(q2, q);
    
    1123
    +
    
    1124
    +      union {
    
    1125
    +        struct { FT_Int32  px_lo, px_hi, py_lo, py_hi; } i;
    
    1126
    +        __m128i vec;
    
    1127
    +      } v;
    
    1128
    +      v.i.px_lo = 0;
    
    1129
    +      v.i.px_hi = p0.x;
    
    1130
    +      v.i.py_lo = 0;
    
    1131
    +      v.i.py_hi = p0.y;
    
    1132
    +
    
    1133
    +      __m128i p = _mm_load_si128(&v.vec);
    
    1134
    +
    
    1135
    +      for (unsigned count = (1u << shift); count > 0; count--) {
    
    1136
    +        p = _mm_add_epi64(p, q);
    
    1137
    +        q = _mm_add_epi64(q, r);
    
    1138
    +
    
    1139
    +        _mm_store_si128(&v.vec, p);
    
    1140
    +
    
    1141
    +        gray_render_line( RAS_VAR_ v.i.px_hi, v.i.py_hi);
    
    1142
    +      }
    
    1143
    +      return;
    
    1144
    +    }
    
    1145
    +#endif  /* !__SSE2__ */
    
    1146
    +    FT_Int64 ax = p0.x + p2.x - 2 * p1.x;
    
    1147
    +    FT_Int64 ay = p0.y + p2.y - 2 * p1.y;
    
    1148
    +    FT_Int64 bx = p1.x - p0.x;
    
    1149
    +    FT_Int64 by = p1.y - p0.y;
    
    1150
    +
    
    1151
    +    FT_Int64 rx = ax << (33 - 2 * shift);
    
    1152
    +    FT_Int64 ry = ay << (33 - 2 * shift);
    
    1153
    +
    
    1154
    +    FT_Int64 qx = (bx << (33 - shift)) + (ax << (32 - 2 * shift));
    
    1155
    +    FT_Int64 qy = (by << (33 - shift)) + (ay << (32 - 2 * shift));
    
    1156
    +
    
    1157
    +    FT_Int64 px = (FT_Int64)p0.x << 32;
    
    1158
    +    FT_Int64 py = (FT_Int64)p0.y << 32;
    
    1159
    +
    
    1160
    +	FT_UInt count = 1u << shift;
    
    1161
    +
    
    1162
    +    for (; count > 0; count--) {
    
    1163
    +      px += qx;
    
    1164
    +      py += qy;
    
    1165
    +      qx += rx;
    
    1166
    +      qy += ry;
    
    1167
    +
    
    1168
    +      gray_render_line( RAS_VAR_ (FT_Pos)(px >> 32), (FT_Pos)(py >> 32));
    
    1169
    +    }
    
    1170
    +  }
    
    1171
    +
    
    1172
    +#else  /* !BEZIER_USE_DDA */
    
    1173
    +
    
    1174
    +  /* Note that multiple attempts to speed up the function below
    
    1175
    +   * with SSE2 intrinsics, using various data layouts, have turned
    
    1176
    +   * out to be slower than the non-SIMD code below.
    
    1177
    +   */
    
    977 1178
       static void
    
    978 1179
       gray_split_conic( FT_Vector*  base )
    
    979 1180
       {
    
    ... ... @@ -1059,7 +1260,15 @@ typedef ptrdiff_t FT_PtrDist;
    1059 1260
         } while ( --draw );
    
    1060 1261
       }
    
    1061 1262
     
    
    1263
    +#endif  /* !BEZIER_USE_DDA */
    
    1062 1264
     
    
    1265
    +  /* For cubic bezier, binary splits are still faster than DDA
    
    1266
    +   * because the splits are adaptive to how quickly each sub-arc
    
    1267
    +   * approaches their chord trisection points.
    
    1268
    +   *
    
    1269
    +   * It might be useful to experiment with SSE2 to speed up
    
    1270
    +   * gray_split_cubic() though.
    
    1271
    +   */
    
    1063 1272
       static void
    
    1064 1273
       gray_split_cubic( FT_Vector*  base )
    
    1065 1274
       {
    
    ... ... @@ -1150,7 +1359,6 @@ typedef ptrdiff_t FT_PtrDist;
    1150 1359
         }
    
    1151 1360
       }
    
    1152 1361
     
    
    1153
    -
    
    1154 1362
       static int
    
    1155 1363
       gray_move_to( const FT_Vector*  to,
    
    1156 1364
                     gray_PWorker      worker )
    
    ... ... @@ -1218,7 +1426,7 @@ typedef ptrdiff_t FT_PtrDist;
    1218 1426
           unsigned char*  line = ras.target.origin - ras.target.pitch * y;
    
    1219 1427
     
    
    1220 1428
     
    
    1221
    -      for ( ; cell != NULL; cell = cell->next )
    
    1429
    +      for ( ; !CELL_IS_NULL(cell); cell = cell->next )
    
    1222 1430
           {
    
    1223 1431
             if ( cover != 0 && cell->x > x )
    
    1224 1432
             {
    
    ... ... @@ -1266,7 +1474,7 @@ typedef ptrdiff_t FT_PtrDist;
    1266 1474
           TArea   area;
    
    1267 1475
     
    
    1268 1476
     
    
    1269
    -      for ( ; cell != NULL; cell = cell->next )
    
    1477
    +      for ( ; !CELL_IS_NULL(cell); cell = cell->next )
    
    1270 1478
           {
    
    1271 1479
             if ( cover != 0 && cell->x > x )
    
    1272 1480
             {
    
    ... ... @@ -1646,8 +1854,8 @@ typedef ptrdiff_t FT_PtrDist;
    1646 1854
           FT_TRACE7(( "band [%d..%d]: %ld cell%s\n",
    
    1647 1855
                       ras.min_ey,
    
    1648 1856
                       ras.max_ey,
    
    1649
    -                  ras.num_cells,
    
    1650
    -                  ras.num_cells == 1 ? "" : "s" ));
    
    1857
    +                  ras.cell_free - ras.cells.,
    
    1858
    +                  ras.cell_free - ras.cells == 1 ? "" : "s" ));
    
    1651 1859
         }
    
    1652 1860
         else
    
    1653 1861
         {
    
    ... ... @@ -1690,8 +1898,18 @@ typedef ptrdiff_t FT_PtrDist;
    1690 1898
     
    
    1691 1899
         ras.cells     = buffer + n;
    
    1692 1900
         ras.max_cells = (FT_PtrDist)( FT_MAX_GRAY_POOL - n );
    
    1901
    +    ras.cell_limit = ras.cells + ras.max_cells;
    
    1693 1902
         ras.ycells    = (PCell*)buffer;
    
    1694 1903
     
    
    1904
    +	/* Initialize the null cell is at the start of the 'cells' array. */
    
    1905
    +	/* Note that this requires ras.cell_free initialization to skip   */
    
    1906
    +	/* over the first entry in the array.                             */
    
    1907
    +	PCell null_cell  = NULL_CELL_PTR(ras);
    
    1908
    +	null_cell->x     = CELL_MAX_X_VALUE;
    
    1909
    +	null_cell->area  = 0;
    
    1910
    +	null_cell->cover = 0;
    
    1911
    +	null_cell->next  = NULL;;
    
    1912
    +
    
    1695 1913
         for ( y = yMin; y < yMax; )
    
    1696 1914
         {
    
    1697 1915
           ras.min_ey = y;
    
    ... ... @@ -1705,15 +1923,17 @@ typedef ptrdiff_t FT_PtrDist;
    1705 1923
           do
    
    1706 1924
           {
    
    1707 1925
             TCoord  width = band[0] - band[1];
    
    1926
    +        TCoord  w;
    
    1708 1927
             int     error;
    
    1709 1928
     
    
    1929
    +        for (w = 0; w < width; ++w)
    
    1930
    +          ras.ycells[w] = null_cell;
    
    1710 1931
     
    
    1711
    -        FT_MEM_ZERO( ras.ycells, height * sizeof ( PCell ) );
    
    1712
    -
    
    1713
    -        ras.num_cells = 0;
    
    1714
    -        ras.cell      = NULL;
    
    1932
    +        ras.cell_free = ras.cells + 1;  /* NOTE: Skip over the null cell. */
    
    1933
    +        ras.cell      = null_cell;
    
    1715 1934
             ras.min_ey    = band[1];
    
    1716 1935
             ras.max_ey    = band[0];
    
    1936
    +        ras.count_ey  = width;
    
    1717 1937
     
    
    1718 1938
             error     = gray_convert_glyph_inner( RAS_VAR, continued );
    
    1719 1939
             continued = 1;
    

  • tests/README.md
    ... ... @@ -4,7 +4,7 @@
    4 4
     
    
    5 5
     ### Download test fonts
    
    6 6
     
    
    7
    -Run the `tests/scripts/download-fonts.sh` script, which will
    
    7
    +Run the `tests/scripts/download-fonts.py` script, which will
    
    8 8
     download test fonts to the `tests/data/` directory first.
    
    9 9
     
    
    10 10
     ### Build the test programs
    

  • tests/scripts/download-test-fonts.py
    1
    +#!/usr/bin/env python3
    
    2
    +
    
    3
    +"""Download test fonts used by the FreeType regression test programs.
    
    4
    +These will be copied to $FREETYPE/tests/data/ by default.
    
    5
    +"""
    
    6
    +
    
    7
    +import argparse
    
    8
    +import collections
    
    9
    +import hashlib
    
    10
    +import io
    
    11
    +import os
    
    12
    +import requests
    
    13
    +import sys
    
    14
    +import zipfile
    
    15
    +
    
    16
    +from typing import Callable, List, Optional, Tuple
    
    17
    +
    
    18
    +# The list of download items describing the font files to install.
    
    19
    +# Each download item is a dictionary with one of the following schemas:
    
    20
    +#
    
    21
    +# - File item:
    
    22
    +#
    
    23
    +#      file_url
    
    24
    +#        Type: URL string.
    
    25
    +#        Required: Yes.
    
    26
    +#        Description: URL to download the file from.
    
    27
    +#
    
    28
    +#      install_name
    
    29
    +#        Type: file name string
    
    30
    +#        Required: No
    
    31
    +#        Description: Installation name for the font file, only provided if it
    
    32
    +#          must be different from the original URL's basename.
    
    33
    +#
    
    34
    +#      hex_digest
    
    35
    +#        Type: hexadecimal string
    
    36
    +#        Required: No
    
    37
    +#        Description: Digest of the input font file.
    
    38
    +#
    
    39
    +# - Zip items:
    
    40
    +#
    
    41
    +#   These items correspond to one or more font files that are embedded in a
    
    42
    +#   remote zip archive. Each entry has the following fields:
    
    43
    +#
    
    44
    +#      zip_url
    
    45
    +#        Type: URL string.
    
    46
    +#        Required: Yes.
    
    47
    +#        Description: URL to download the zip archive from.
    
    48
    +#
    
    49
    +#      zip_files
    
    50
    +#        Type: List of file entries (see below)
    
    51
    +#        Required: Yes
    
    52
    +#        Description: A list of entries describing a single font file to be
    
    53
    +#          extracted from the archive
    
    54
    +#
    
    55
    +# Apart from that, some schemas are used for dictionaries used inside download
    
    56
    +# items:
    
    57
    +#
    
    58
    +# - File entries:
    
    59
    +#
    
    60
    +#   These are dictionaries describing a single font file to extract from an archive.
    
    61
    +#
    
    62
    +#      filename
    
    63
    +#        Type: file path string
    
    64
    +#        Required: Yes
    
    65
    +#        Description: Path of source file, relative to the archive's top-level directory.
    
    66
    +#
    
    67
    +#      install_name
    
    68
    +#        Type: file name string
    
    69
    +#        Required: No
    
    70
    +#        Description: Installation name for the font file, only provided if it must be
    
    71
    +#          different from the original filename value.
    
    72
    +#
    
    73
    +#      hex_digest
    
    74
    +#        Type: hexadecimal string
    
    75
    +#        Required: No
    
    76
    +#        Description: Digest of the input source file
    
    77
    +#
    
    78
    +_DOWNLOAD_ITEMS = [
    
    79
    +    {
    
    80
    +        "zip_url": "https://github.com/python-pillow/Pillow/files/6622147/As.I.Lay.Dying.zip",
    
    81
    +        "zip_files": [
    
    82
    +            {
    
    83
    +                "filename": "As I Lay Dying.ttf",
    
    84
    +                "install_name": "As.I.Lay.Dying.ttf",
    
    85
    +                "hex_digest": "ef146bbc2673b387",
    
    86
    +            },
    
    87
    +        ],
    
    88
    +    },
    
    89
    +]
    
    90
    +
    
    91
    +
    
    92
    +def digest_data(data: bytes):
    
    93
    +    """Compute the digest of a given input byte string, which are the first 8 bytes of its sha256 hash."""
    
    94
    +    m = hashlib.sha256()
    
    95
    +    m.update(data)
    
    96
    +    return m.digest()[:8]
    
    97
    +
    
    98
    +
    
    99
    +def check_existing(path: str, hex_digest: str):
    
    100
    +    """Return True if |path| exists and matches |hex_digest|."""
    
    101
    +    if not os.path.exists(path) or hex_digest is None:
    
    102
    +        return False
    
    103
    +
    
    104
    +    with open(path, "rb") as f:
    
    105
    +        existing_content = f.read()
    
    106
    +
    
    107
    +    return bytes.fromhex(hex_digest) == digest_data(existing_content)
    
    108
    +
    
    109
    +
    
    110
    +def install_file(content: bytes, dest_path: str):
    
    111
    +    """Write a byte string to a given destination file.
    
    112
    +
    
    113
    +    Args:
    
    114
    +      content: Input data, as a byte string
    
    115
    +      dest_path: Installation path
    
    116
    +    """
    
    117
    +    parent_path = os.path.dirname(dest_path)
    
    118
    +    if not os.path.exists(parent_path):
    
    119
    +        os.makedirs(parent_path)
    
    120
    +
    
    121
    +    with open(dest_path, "wb") as f:
    
    122
    +        f.write(content)
    
    123
    +
    
    124
    +
    
    125
    +def download_file(url: str, expected_digest: Optional[bytes] = None):
    
    126
    +    """Download a file from a given URL.
    
    127
    +
    
    128
    +    Args:
    
    129
    +      url: Input URL
    
    130
    +      expected_digest: Optional digest of the file
    
    131
    +        as a byte string
    
    132
    +    Returns:
    
    133
    +      URL content as binary string.
    
    134
    +    """
    
    135
    +    r = requests.get(url, allow_redirects=True)
    
    136
    +    content = r.content
    
    137
    +    if expected_digest is not None:
    
    138
    +        digest = digest_data(r.content)
    
    139
    +        if digest != expected_digest:
    
    140
    +            raise ValueError(
    
    141
    +                "%s has invalid digest %s (expected %s)"
    
    142
    +                % (url, digest.hex(), expected_digest.hex())
    
    143
    +            )
    
    144
    +
    
    145
    +    return content
    
    146
    +
    
    147
    +
    
    148
    +def extract_file_from_zip_archive(
    
    149
    +    archive: zipfile.ZipFile,
    
    150
    +    archive_name: str,
    
    151
    +    filepath: str,
    
    152
    +    expected_digest: Optional[bytes] = None,
    
    153
    +):
    
    154
    +    """Extract a file from a given zipfile.ZipFile archive.
    
    155
    +
    
    156
    +    Args:
    
    157
    +      archive: Input ZipFile objec.
    
    158
    +      archive_name: Archive name or URL, only used to generate a human-readable error
    
    159
    +        message.
    
    160
    +      filepath: Input filepath in archive.
    
    161
    +      expected_digest: Optional digest for the file.
    
    162
    +    Returns:
    
    163
    +      A new File instance corresponding to the extract file.
    
    164
    +    Raises:
    
    165
    +      ValueError if expected_digest is not None and does not match the extracted file.
    
    166
    +    """
    
    167
    +    file = archive.open(filepath)
    
    168
    +    if expected_digest is not None:
    
    169
    +        digest = digest_data(archive.open(filepath).read())
    
    170
    +        if digest != expected_digest:
    
    171
    +            raise ValueError(
    
    172
    +                "%s in zip archive at %s has invalid digest %s (expected %s)"
    
    173
    +                % (filepath, archive_name, digest.hex(), expected_digest.hex())
    
    174
    +            )
    
    175
    +    return file.read()
    
    176
    +
    
    177
    +
    
    178
    +def _get_and_install_file(
    
    179
    +    install_path: str,
    
    180
    +    hex_digest: Optional[str],
    
    181
    +    force_download: bool,
    
    182
    +    get_content: Callable[[], bytes],
    
    183
    +) -> bool:
    
    184
    +    if not force_download and hex_digest is not None and os.path.exists(install_path):
    
    185
    +        with open(install_path, "rb") as f:
    
    186
    +            content: bytes = f.read()
    
    187
    +        if bytes.fromhex(hex_digest) == digest_data(content):
    
    188
    +            return False
    
    189
    +
    
    190
    +    content = get_content()
    
    191
    +    install_file(content, install_path)
    
    192
    +    return True
    
    193
    +
    
    194
    +
    
    195
    +def download_and_install_item(
    
    196
    +    item: dict, install_dir: str, force_download: bool
    
    197
    +) -> List[Tuple[str, bool]]:
    
    198
    +    """Download and install one item.
    
    199
    +
    
    200
    +    Args:
    
    201
    +      item: Download item as a dictionary, see above for schema.
    
    202
    +      install_dir: Installation directory.
    
    203
    +      force_download: Set to True to force download and installation, even if
    
    204
    +        the font file is already installed with the right content.
    
    205
    +
    
    206
    +    Returns:
    
    207
    +      A list of (install_name, status) tuples, where 'install_name' is the file's
    
    208
    +      installation name under 'install_dir', and 'status' is a boolean that is True
    
    209
    +      to indicate that the file was downloaded and installed, or False to indicate that
    
    210
    +      the file is already installed with the right content.
    
    211
    +    """
    
    212
    +    if "file_url" in item:
    
    213
    +        file_url = item["file_url"]
    
    214
    +        install_name = item.get("install_name", os.path.basename(file_url))
    
    215
    +        install_path = os.path.join(install_dir, install_name)
    
    216
    +        hex_digest = item.get("hex_digest")
    
    217
    +
    
    218
    +        def get_content():
    
    219
    +            return download_file(file_url, hex_digest)
    
    220
    +
    
    221
    +        status = _get_and_install_file(
    
    222
    +            install_path, hex_digest, force_download, get_content
    
    223
    +        )
    
    224
    +        return [(install_name, status)]
    
    225
    +
    
    226
    +    if "zip_url" in item:
    
    227
    +        # One or more files from a zip archive.
    
    228
    +        archive_url = item["zip_url"]
    
    229
    +        archive = zipfile.ZipFile(io.BytesIO(download_file(archive_url)))
    
    230
    +
    
    231
    +        result = []
    
    232
    +        for f in item["zip_files"]:
    
    233
    +            filename = f["filename"]
    
    234
    +            install_name = f.get("install_name", filename)
    
    235
    +            hex_digest = f.get("hex_digest")
    
    236
    +
    
    237
    +            def get_content():
    
    238
    +                return extract_file_from_zip_archive(
    
    239
    +                    archive,
    
    240
    +                    archive_url,
    
    241
    +                    filename,
    
    242
    +                    bytes.fromhex(hex_digest) if hex_digest else None,
    
    243
    +                )
    
    244
    +
    
    245
    +            status = _get_and_install_file(
    
    246
    +                os.path.join(install_dir, install_name),
    
    247
    +                hex_digest,
    
    248
    +                force_download,
    
    249
    +                get_content,
    
    250
    +            )
    
    251
    +            result.append((install_name, status))
    
    252
    +
    
    253
    +        return result
    
    254
    +
    
    255
    +    else:
    
    256
    +        raise ValueError("Unknown download item schema: %s" % item)
    
    257
    +
    
    258
    +
    
    259
    +def main():
    
    260
    +    parser = argparse.ArgumentParser(description=__doc__)
    
    261
    +
    
    262
    +    # Assume this script is under tests/scripts/ and tests/data/
    
    263
    +    # is the default installation directory.
    
    264
    +    install_dir = os.path.normpath(
    
    265
    +        os.path.join(os.path.dirname(__file__), "..", "data")
    
    266
    +    )
    
    267
    +
    
    268
    +    parser.add_argument(
    
    269
    +        "--force",
    
    270
    +        action="store_true",
    
    271
    +        default=False,
    
    272
    +        help="Force download and installation of font files",
    
    273
    +    )
    
    274
    +
    
    275
    +    parser.add_argument(
    
    276
    +        "--install-dir",
    
    277
    +        default=install_dir,
    
    278
    +        help="Specify installation directory [%s]" % install_dir,
    
    279
    +    )
    
    280
    +
    
    281
    +    args = parser.parse_args()
    
    282
    +
    
    283
    +    for item in _DOWNLOAD_ITEMS:
    
    284
    +        for install_name, status in download_and_install_item(
    
    285
    +            item, args.install_dir, args.force
    
    286
    +        ):
    
    287
    +            print("%s %s" % (install_name, "INSTALLED" if status else "UP-TO-DATE"))
    
    288
    +
    
    289
    +    return 0
    
    290
    +
    
    291
    +
    
    292
    +if __name__ == "__main__":
    
    293
    +    sys.exit(main())

  • tests/scripts/download-test-fonts.sh deleted
    1
    -#!/usr/bin/bash
    
    2
    -# Download test fonts used by the FreeType regression test programs.
    
    3
    -# These will be copied to $FREETYPE/tests/data/
    
    4
    -# Each font file contains an 8-hexchar prefix corresponding to its md5sum
    
    5
    -
    
    6
    -set -e
    
    7
    -
    
    8
    -export LANG=C
    
    9
    -export LC_ALL=C
    
    10
    -
    
    11
    -PROGDIR=$(dirname "$0")
    
    12
    -PROGNAME=$(basename "$0")
    
    13
    -
    
    14
    -# Download a file from a given URL
    
    15
    -#
    
    16
    -# $1: URL
    
    17
    -# $2: Destination directory
    
    18
    -# $3: If not empty, destination file name. Default is to take
    
    19
    -# the URL's basename.
    
    20
    -#
    
    21
    -download_file () {
    
    22
    -  local URL=$1
    
    23
    -  local DST_DIR=$2
    
    24
    -  local DST_FILE=$3
    
    25
    -  if [[ -z "$DST_FILE" ]]; then
    
    26
    -    DST_FILE=$(basename "$URL")
    
    27
    -  fi
    
    28
    -  echo "URL: $URL"
    
    29
    -  wget -q -O "$DST_DIR/$DST_FILE" "$URL"
    
    30
    -}
    
    31
    -
    
    32
    -# $1: URL
    
    33
    -# $2: Destination directory
    
    34
    -# $3+: Optional file list, otherwise the full archive is extracted to $2
    
    35
    -download_and_extract_zip () {
    
    36
    -  local URL=$1
    
    37
    -  local DST_DIR=$2
    
    38
    -  shift
    
    39
    -  shift
    
    40
    -  TEMP_DST_DIR=$(mktemp -d)
    
    41
    -  TEMP_DST_NAME="a.zip"
    
    42
    -  download_file "$URL" "$TEMP_DST_DIR" "$TEMP_DST_NAME"
    
    43
    -  unzip -qo "$TEMP_DST_DIR/$TEMP_DST_NAME" -d "$DST_DIR" "$@"
    
    44
    -  rm -rf "$TEMP_DST_DIR"
    
    45
    -}
    
    46
    -
    
    47
    -# $1: File path
    
    48
    -# $2: Expected md5sum
    
    49
    -md5sum_check () {
    
    50
    -  local FILE=$1
    
    51
    -  local EXPECTED=$2
    
    52
    -  local HASH=$(md5sum "$FILE" | cut -d" " -f1)
    
    53
    -  if [[ "$EXPECTED" != "$HASH" ]]; then
    
    54
    -    echo "$FILE: Invalid md5sum $HASH expected $EXPECTED"
    
    55
    -    return 1
    
    56
    -  fi
    
    57
    -}
    
    58
    -
    
    59
    -INSTALL_DIR=$(cd $PROGDIR/.. && pwd)/data
    
    60
    -
    
    61
    -mkdir -p "$INSTALL_DIR"
    
    62
    -
    
    63
    -# See https://gitlab.freedesktop.org/freetype/freetype/-/issues/1063
    
    64
    -download_and_extract_zip "https://github.com/python-pillow/Pillow/files/6622147/As.I.Lay.Dying.zip" "$INSTALL_DIR"
    
    65
    -mv "$INSTALL_DIR/As I Lay Dying.ttf" "$INSTALL_DIR/As.I.Lay.Dying.ttf"
    
    66
    -md5sum_check "$INSTALL_DIR/As.I.Lay.Dying.ttf" e153d60e66199660f7cfe99ef4705ad7


  • reply via email to

    [Prev in Thread] Current Thread [Next in Thread]