David Turner pushed to branch split-conic-dda at FreeType / FreeType
Commits:
-
f7c6a06c
by Alex Richardson at 2021-07-15T12:09:04+02:00
-
5ec7f588
by David Turner at 2021-07-15T13:21:07+02:00
-
86b9c934
by David Turner at 2021-07-15T13:25:48+02:00
-
56cc2ad4
by David Turner at 2021-07-15T13:25:58+02:00
6 changed files:
- ChangeLog
- include/freetype/internal/compiler-macros.h
- src/smooth/ftgrays.c
- tests/README.md
- + tests/scripts/download-test-fonts.py
- − tests/scripts/download-test-fonts.sh
Changes:
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.
|
... | ... | @@ -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 |
/*
|
... | ... | @@ -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;
|
... | ... | @@ -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
|
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())
|
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
|