/* * xft.c * * Copyright(c) 2007-2023 Jianjun Jiang <8192542@qq.com> * Mobile phone: +86-18665388956 * QQ: 8192542 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ #include "xft.h" /* * type */ typedef int64_t XCG_FT_Int64; typedef uint64_t XCG_FT_UInt64; typedef int32_t XCG_FT_Int32; typedef uint32_t XCG_FT_UInt32; #define XCG_FT_BOOL(x) ((XCG_FT_Bool)(x)) #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif /* * math */ #define XCG_FT_MIN(a, b) ((a) < (b) ? (a) : (b)) #define XCG_FT_MAX(a, b) ((a) > (b) ? (a) : (b)) #define XCG_FT_ABS(a) ((a) < 0 ? -(a) : (a)) #define XCG_FT_HYPOT(x, y) (x = XCG_FT_ABS(x), y = XCG_FT_ABS(y), x > y ? x + (3 * y >> 3) : y + (3 * x >> 3)) #define XCG_FT_ANGLE_PI (180L << 16) #define XCG_FT_ANGLE_2PI (XCG_FT_ANGLE_PI * 2) #define XCG_FT_ANGLE_PI2 (XCG_FT_ANGLE_PI / 2) #define XCG_FT_ANGLE_PI4 (XCG_FT_ANGLE_PI / 4) #define XCG_FT_MSB(x) (31 - __builtin_clz(x)) #define XCG_FT_PAD_FLOOR(x, n) ((x) & ~((n)-1)) #define XCG_FT_PAD_ROUND(x, n) XCG_FT_PAD_FLOOR((x) + ((n) / 2), n) #define XCG_FT_PAD_CEIL(x, n) XCG_FT_PAD_FLOOR((x) + ((n) - 1), n) #define XCG_FT_MOVE_SIGN(x, s) \ do { \ if(x < 0) { \ x = -x; \ s = -s; \ } \ } while(0) XCG_FT_Long XCG_FT_MulFix(XCG_FT_Long a, XCG_FT_Long b) { XCG_FT_Int s = 1; XCG_FT_Long c; XCG_FT_MOVE_SIGN(a, s); XCG_FT_MOVE_SIGN(b, s); c = (XCG_FT_Long)(((XCG_FT_Int64)a * b + 0x8000L) >> 16); return (s > 0) ? c : -c; } XCG_FT_Long XCG_FT_MulDiv(XCG_FT_Long a, XCG_FT_Long b, XCG_FT_Long c) { XCG_FT_Int s = 1; XCG_FT_Long d; XCG_FT_MOVE_SIGN(a, s); XCG_FT_MOVE_SIGN(b, s); XCG_FT_MOVE_SIGN(c, s); d = (XCG_FT_Long)(c > 0 ? ((XCG_FT_Int64)a * b + (c >> 1)) / c : 0x7FFFFFFFL); return (s > 0) ? d : -d; } XCG_FT_Long XCG_FT_DivFix(XCG_FT_Long a, XCG_FT_Long b) { XCG_FT_Int s = 1; XCG_FT_Long q; XCG_FT_MOVE_SIGN(a, s); XCG_FT_MOVE_SIGN(b, s); q = (XCG_FT_Long)(b > 0 ? (((XCG_FT_UInt64)a << 16) + (b >> 1)) / b : 0x7FFFFFFFL); return (s < 0 ? -q : q); } #define XCG_FT_TRIG_SCALE (0xDBD95B16UL) #define XCG_FT_TRIG_SAFE_MSB (29) #define XCG_FT_TRIG_MAX_ITERS (23) static const XCG_FT_Fixed ft_trig_arctan_table[] = { 1740967L, 919879L, 466945L, 234379L, 117304L, 58666L, 29335L, 14668L, 7334L, 3667L, 1833L, 917L, 458L, 229L, 115L, 57L, 29L, 14L, 7L, 4L, 2L, 1L }; static XCG_FT_Fixed ft_trig_downscale(XCG_FT_Fixed val) { XCG_FT_Fixed s; XCG_FT_Int64 v; s = val; val = XCG_FT_ABS(val); v = (val * (XCG_FT_Int64)XCG_FT_TRIG_SCALE) + 0x100000000UL; val = (XCG_FT_Fixed)(v >> 32); return (s >= 0) ? val : -val; } static XCG_FT_Int ft_trig_prenorm(XCG_FT_Vector *vec) { XCG_FT_Pos x, y; XCG_FT_Int shift; x = vec->x; y = vec->y; shift = XCG_FT_MSB(XCG_FT_ABS(x) | XCG_FT_ABS(y)); if(shift <= XCG_FT_TRIG_SAFE_MSB) { shift = XCG_FT_TRIG_SAFE_MSB - shift; vec->x = (XCG_FT_Pos)((XCG_FT_ULong)x << shift); vec->y = (XCG_FT_Pos)((XCG_FT_ULong)y << shift); } else { shift -= XCG_FT_TRIG_SAFE_MSB; vec->x = x >> shift; vec->y = y >> shift; shift = -shift; } return shift; } static void ft_trig_pseudo_rotate(XCG_FT_Vector *vec, XCG_FT_Angle theta) { XCG_FT_Int i; XCG_FT_Fixed x, y, xtemp, b; const XCG_FT_Fixed *arctanptr; x = vec->x; y = vec->y; while(theta < -XCG_FT_ANGLE_PI4) { xtemp = y; y = -x; x = xtemp; theta += XCG_FT_ANGLE_PI2; } while(theta > XCG_FT_ANGLE_PI4) { xtemp = -y; y = x; x = xtemp; theta -= XCG_FT_ANGLE_PI2; } arctanptr = ft_trig_arctan_table; for(i = 1, b = 1; i < XCG_FT_TRIG_MAX_ITERS; b <<= 1, i++) { XCG_FT_Fixed v1 = ((y + b) >> i); XCG_FT_Fixed v2 = ((x + b) >> i); if(theta < 0) { xtemp = x + v1; y = y - v2; x = xtemp; theta += *arctanptr++; } else { xtemp = x - v1; y = y + v2; x = xtemp; theta -= *arctanptr++; } } vec->x = x; vec->y = y; } static void ft_trig_pseudo_polarize(XCG_FT_Vector *vec) { XCG_FT_Angle theta; XCG_FT_Int i; XCG_FT_Fixed x, y, xtemp, b; const XCG_FT_Fixed *arctanptr; x = vec->x; y = vec->y; if(y > x) { if(y > -x) { theta = XCG_FT_ANGLE_PI2; xtemp = y; y = -x; x = xtemp; } else { theta = y > 0 ? XCG_FT_ANGLE_PI : -XCG_FT_ANGLE_PI; x = -x; y = -y; } } else { if(y < -x) { theta = -XCG_FT_ANGLE_PI2; xtemp = -y; y = x; x = xtemp; } else { theta = 0; } } arctanptr = ft_trig_arctan_table; for(i = 1, b = 1; i < XCG_FT_TRIG_MAX_ITERS; b <<= 1, i++) { XCG_FT_Fixed v1 = ((y + b) >> i); XCG_FT_Fixed v2 = ((x + b) >> i); if(y > 0) { xtemp = x + v1; y = y - v2; x = xtemp; theta += *arctanptr++; } else { xtemp = x - v1; y = y + v2; x = xtemp; theta -= *arctanptr++; } } if(theta >= 0) theta = XCG_FT_PAD_ROUND(theta, 32); else theta = -XCG_FT_PAD_ROUND(-theta, 32); vec->x = x; vec->y = theta; } XCG_FT_Fixed XCG_FT_Cos(XCG_FT_Angle angle) { XCG_FT_Vector v; v.x = XCG_FT_TRIG_SCALE >> 8; v.y = 0; ft_trig_pseudo_rotate(&v, angle); return (v.x + 0x80L) >> 8; } XCG_FT_Fixed XCG_FT_Sin(XCG_FT_Angle angle) { return XCG_FT_Cos(XCG_FT_ANGLE_PI2 - angle); } XCG_FT_Fixed XCG_FT_Tan(XCG_FT_Angle angle) { XCG_FT_Vector v; v.x = XCG_FT_TRIG_SCALE >> 8; v.y = 0; ft_trig_pseudo_rotate(&v, angle); return XCG_FT_DivFix(v.y, v.x); } XCG_FT_Angle XCG_FT_Atan2(XCG_FT_Fixed dx, XCG_FT_Fixed dy) { XCG_FT_Vector v; if(dx == 0 && dy == 0) return 0; v.x = dx; v.y = dy; ft_trig_prenorm(&v); ft_trig_pseudo_polarize(&v); return v.y; } void XCG_FT_Vector_Unit(XCG_FT_Vector *vec, XCG_FT_Angle angle) { vec->x = XCG_FT_TRIG_SCALE >> 8; vec->y = 0; ft_trig_pseudo_rotate(vec, angle); vec->x = (vec->x + 0x80L) >> 8; vec->y = (vec->y + 0x80L) >> 8; } void XCG_FT_Vector_Rotate(XCG_FT_Vector *vec, XCG_FT_Angle angle) { XCG_FT_Int shift; XCG_FT_Vector v = *vec; if(v.x == 0 && v.y == 0) return; shift = ft_trig_prenorm(&v); ft_trig_pseudo_rotate(&v, angle); v.x = ft_trig_downscale(v.x); v.y = ft_trig_downscale(v.y); if(shift > 0) { XCG_FT_Int32 half = (XCG_FT_Int32)1L << (shift - 1); vec->x = (v.x + half - (v.x < 0)) >> shift; vec->y = (v.y + half - (v.y < 0)) >> shift; } else { shift = -shift; vec->x = (XCG_FT_Pos)((XCG_FT_ULong)v.x << shift); vec->y = (XCG_FT_Pos)((XCG_FT_ULong)v.y << shift); } } XCG_FT_Fixed XCG_FT_Vector_Length(XCG_FT_Vector *vec) { XCG_FT_Int shift; XCG_FT_Vector v; v = *vec; if(v.x == 0) { return XCG_FT_ABS(v.y); } else if(v.y == 0) { return XCG_FT_ABS(v.x); } shift = ft_trig_prenorm(&v); ft_trig_pseudo_polarize(&v); v.x = ft_trig_downscale(v.x); if(shift > 0) return (v.x + (1 << (shift - 1))) >> shift; return (XCG_FT_Fixed)((XCG_FT_UInt32)v.x << -shift); } void XCG_FT_Vector_Polarize(XCG_FT_Vector *vec, XCG_FT_Fixed *length, XCG_FT_Angle *angle) { XCG_FT_Int shift; XCG_FT_Vector v; v = *vec; if(v.x == 0 && v.y == 0) return; shift = ft_trig_prenorm(&v); ft_trig_pseudo_polarize(&v); v.x = ft_trig_downscale(v.x); *length = (shift >= 0) ? (v.x >> shift) : (XCG_FT_Fixed)((XCG_FT_UInt32)v.x << -shift); *angle = v.y; } void XCG_FT_Vector_From_Polar(XCG_FT_Vector *vec, XCG_FT_Fixed length, XCG_FT_Angle angle) { vec->x = length; vec->y = 0; XCG_FT_Vector_Rotate(vec, angle); } XCG_FT_Angle XCG_FT_Angle_Diff(XCG_FT_Angle angle1, XCG_FT_Angle angle2) { XCG_FT_Angle delta = angle2 - angle1; while(delta <= -XCG_FT_ANGLE_PI) delta += XCG_FT_ANGLE_2PI; while(delta > XCG_FT_ANGLE_PI) delta -= XCG_FT_ANGLE_2PI; return delta; } /* * raster */ typedef long TCoord; typedef long TPos; typedef long TArea; typedef ptrdiff_t XCG_FT_PtrDist; #define xcg_ft_setjmp setjmp #define xcg_ft_longjmp longjmp #define xcg_ft_jmp_buf jmp_buf #define ErrRaster_Invalid_Mode -2 #define ErrRaster_Invalid_Outline -1 #define ErrRaster_Invalid_Argument -3 #define ErrRaster_Memory_Overflow -4 #define ErrRaster_OutOfMemory -6 #define XCG_FT_MINIMUM_POOL_SIZE 8192 #define XCG_FT_MAX_GRAY_SPANS 256 #define RAS_ARG PWorker worker #define RAS_ARG_ PWorker worker, #define RAS_VAR worker #define RAS_VAR_ worker, #define ras (*worker) #define PIXEL_BITS 8 #define ONE_PIXEL (1L << PIXEL_BITS) #define TRUNC(x) (TCoord)((x) >> PIXEL_BITS) #define FRACT(x) (TCoord)((x) & (ONE_PIXEL - 1)) #if PIXEL_BITS >= 6 #define UPSCALE(x) ((x) * (ONE_PIXEL >> 6)) #define DOWNSCALE(x) ((x) >> (PIXEL_BITS - 6)) #else #define UPSCALE(x) ((x) >> (6 - PIXEL_BITS)) #define DOWNSCALE(x) ((x) * (64 >> PIXEL_BITS)) #endif #define XCG_FT_DIV_MOD(type, dividend, divisor, quotient, remainder) \ do { \ (quotient) = (type)((dividend) / (divisor)); \ (remainder) = (type)((dividend) % (divisor)); \ if((remainder) < 0) \ { \ (quotient)--; \ (remainder) += (type)(divisor); \ } \ } while(0) typedef struct TCell_ * PCell; typedef struct TCell_ { int x; int cover; TArea area; PCell next; } TCell; typedef struct TWorker_ { TCoord ex, ey; TPos min_ex, max_ex; TPos min_ey, max_ey; TPos count_ex, count_ey; TArea area; int cover; int invalid; PCell cells; XCG_FT_PtrDist max_cells; XCG_FT_PtrDist num_cells; TPos x, y; XCG_FT_Outline outline; XCG_FT_BBox clip_box; XCG_FT_Span gray_spans[XCG_FT_MAX_GRAY_SPANS]; int num_gray_spans; int skip_spans; XCG_FT_Raster_Span_Func render_span; void *render_span_data; int band_size; int band_shoot; xcg_ft_jmp_buf jump_buffer; void *buffer; long buffer_size; PCell *ycells; TPos ycount; } TWorker, *PWorker; static void gray_init_cells(RAS_ARG_ void* buffer, long byte_size) { ras.buffer = buffer; ras.buffer_size = byte_size; ras.ycells = (PCell*)buffer; ras.cells = NULL; ras.max_cells = 0; ras.num_cells = 0; ras.area = 0; ras.cover = 0; ras.invalid = 1; } static void gray_compute_cbox( RAS_ARG) { XCG_FT_Outline *outline = &ras.outline; XCG_FT_Vector *vec = outline->points; XCG_FT_Vector *limit = vec + outline->n_points; if(outline->n_points <= 0) { ras.min_ex = ras.max_ex = 0; ras.min_ey = ras.max_ey = 0; return; } ras.min_ex = ras.max_ex = vec->x; ras.min_ey = ras.max_ey = vec->y; vec++; for(; vec < limit; vec++) { TPos x = vec->x; TPos y = vec->y; if(x < ras.min_ex) ras.min_ex = x; if(x > ras.max_ex) ras.max_ex = x; if(y < ras.min_ey) ras.min_ey = y; if(y > ras.max_ey) ras.max_ey = y; } ras.min_ex = ras.min_ex >> 6; ras.min_ey = ras.min_ey >> 6; ras.max_ex = (ras.max_ex + 63) >> 6; ras.max_ey = (ras.max_ey + 63) >> 6; } static PCell gray_find_cell(RAS_ARG) { PCell *pcell, cell; TPos x = ras.ex; if(x > ras.count_ex) x = ras.count_ex; pcell = &ras.ycells[ras.ey]; for(;;) { cell = *pcell; if(cell == NULL || cell->x > x) break; if(cell->x == x) goto Exit; pcell = &cell->next; } if(ras.num_cells >= ras.max_cells) xcg_ft_longjmp(ras.jump_buffer, 1); cell = ras.cells + ras.num_cells++; cell->x = x; cell->area = 0; cell->cover = 0; cell->next = *pcell; *pcell = cell; Exit: return cell; } static void gray_record_cell( RAS_ARG) { if( ras.area | ras.cover) { PCell cell = gray_find_cell( RAS_VAR); cell->area += ras.area; cell->cover += ras.cover; } } static void gray_set_cell( RAS_ARG_ TCoord ex, TCoord ey) { ey -= ras.min_ey; if(ex > ras.max_ex) ex = ras.max_ex; ex -= ras.min_ex; if(ex < 0) ex = -1; if(ex != ras.ex || ey != ras.ey) { if(!ras.invalid) gray_record_cell( RAS_VAR); ras.area = 0; ras.cover = 0; ras.ex = ex; ras.ey = ey; } ras.invalid = ((unsigned int)ey >= (unsigned int)ras.count_ey || ex >= ras.count_ex); } static void gray_start_cell( RAS_ARG_ TCoord ex, TCoord ey) { if(ex > ras.max_ex) ex = (TCoord)( ras.max_ex); if(ex < ras.min_ex) ex = (TCoord)( ras.min_ex - 1); ras.area = 0; ras.cover = 0; ras.ex = ex - ras.min_ex; ras.ey = ey - ras.min_ey; ras.invalid = 0; gray_set_cell( RAS_VAR_ ex, ey); } static void gray_render_scanline( RAS_ARG_ TCoord ey, TPos x1, TCoord y1, TPos x2, TCoord y2) { TCoord ex1, ex2, fx1, fx2, first, dy, delta, mod; TPos p, dx; int incr; ex1 = TRUNC(x1); ex2 = TRUNC(x2); if(y1 == y2) { gray_set_cell( RAS_VAR_ ex2, ey); return; } fx1 = FRACT(x1); fx2 = FRACT(x2); if(ex1 == ex2) goto End; dx = x2 - x1; dy = y2 - y1; if(dx > 0) { p = ( ONE_PIXEL - fx1) * dy; first = ONE_PIXEL; incr = 1; } else { p = fx1 * dy; first = 0; incr = -1; dx = -dx; } XCG_FT_DIV_MOD(TCoord, p, dx, delta, mod); ras.area += (TArea)(fx1 + first) * delta; ras.cover += delta; y1 += delta; ex1 += incr; gray_set_cell( RAS_VAR_ ex1, ey); if(ex1 != ex2) { TCoord lift, rem; p = ONE_PIXEL * dy; XCG_FT_DIV_MOD(TCoord, p, dx, lift, rem); do { delta = lift; mod += rem; if(mod >= (TCoord)dx) { mod -= (TCoord)dx; delta++; } ras.area += (TArea)( ONE_PIXEL * delta); ras.cover += delta; y1 += delta; ex1 += incr; gray_set_cell( RAS_VAR_ ex1, ey); } while(ex1 != ex2); } fx1 = ONE_PIXEL - first; End: dy = y2 - y1; ras.area += (TArea)((fx1 + fx2) * dy); ras.cover += dy; } static void gray_render_line( RAS_ARG_ TPos to_x, TPos to_y) { TCoord ey1, ey2, fy1, fy2, first, delta, mod; TPos p, dx, dy, x, x2; int incr; ey1 = TRUNC(ras.y); ey2 = TRUNC(to_y); if((ey1 >= ras.max_ey && ey2 >= ras.max_ey) || (ey1 < ras.min_ey && ey2 < ras.min_ey)) goto End; fy1 = FRACT(ras.y); fy2 = FRACT(to_y); if(ey1 == ey2) { gray_render_scanline( RAS_VAR_ ey1, ras.x, fy1, to_x, fy2); goto End; } dx = to_x - ras.x; dy = to_y - ras.y; if(dx == 0) { TCoord ex = TRUNC(ras.x); TCoord two_fx = FRACT( ras.x ) << 1; TPos area, max_ey1; if(dy > 0) { first = ONE_PIXEL; } else { first = 0; } delta = first - fy1; ras.area += (TArea)two_fx * delta; ras.cover += delta; delta = first + first - ONE_PIXEL; area = (TArea)two_fx * delta; max_ey1 = ras.count_ey + ras.min_ey; if(dy < 0) { if(ey1 > max_ey1) { ey1 = (max_ey1 > ey2) ? max_ey1 : ey2; gray_set_cell(&ras, ex, ey1); } else { ey1--; gray_set_cell(&ras, ex, ey1); } while(ey1 > ey2 && ey1 >= ras.min_ey) { ras.area += area; ras.cover += delta; ey1--; gray_set_cell(&ras, ex, ey1); } if(ey1 != ey2) { ey1 = ey2; gray_set_cell(&ras, ex, ey1); } } else { if(ey1 < ras.min_ey) { ey1 = (ras.min_ey < ey2) ? ras.min_ey : ey2; gray_set_cell(&ras, ex, ey1); } else { ey1++; gray_set_cell(&ras, ex, ey1); } while(ey1 < ey2 && ey1 < max_ey1) { ras.area += area; ras.cover += delta; ey1++; gray_set_cell(&ras, ex, ey1); } if(ey1 != ey2) { ey1 = ey2; gray_set_cell(&ras, ex, ey1); } } delta = (int)(fy2 - ONE_PIXEL + first); ras.area += (TArea)two_fx * delta; ras.cover += delta; goto End; } if(dy > 0) { p = ( ONE_PIXEL - fy1) * dx; first = ONE_PIXEL; incr = 1; } else { p = fy1 * dx; first = 0; incr = -1; dy = -dy; } XCG_FT_DIV_MOD(TCoord, p, dy, delta, mod); x = ras.x + delta; gray_render_scanline( RAS_VAR_ ey1, ras.x, fy1, x, (TCoord)first); ey1 += incr; gray_set_cell( RAS_VAR_ TRUNC( x ), ey1); if(ey1 != ey2) { TCoord lift, rem; p = ONE_PIXEL * dx; XCG_FT_DIV_MOD(TCoord, p, dy, lift, rem); do { delta = lift; mod += rem; if(mod >= (TCoord)dy) { mod -= (TCoord)dy; delta++; } x2 = x + delta; gray_render_scanline( RAS_VAR_ ey1, x, ONE_PIXEL - first, x2, first); x = x2; ey1 += incr; gray_set_cell( RAS_VAR_ TRUNC( x ), ey1); } while(ey1 != ey2); } gray_render_scanline( RAS_VAR_ ey1, x, ONE_PIXEL - first, to_x, fy2); End: ras.x = to_x; ras.y = to_y; } static void gray_split_conic(XCG_FT_Vector * base) { TPos a, b; base[4].x = base[2].x; b = base[1].x; a = base[3].x = (base[2].x + b) / 2; b = base[1].x = (base[0].x + b) / 2; base[2].x = (a + b) / 2; base[4].y = base[2].y; b = base[1].y; a = base[3].y = (base[2].y + b) / 2; b = base[1].y = (base[0].y + b) / 2; base[2].y = (a + b) / 2; } static void gray_render_conic( RAS_ARG_ const XCG_FT_Vector * control, const XCG_FT_Vector * to) { XCG_FT_Vector bez_stack[16 * 2 + 1]; XCG_FT_Vector *arc = bez_stack; TPos dx, dy; int draw, split; arc[0].x = UPSCALE(to->x); arc[0].y = UPSCALE(to->y); arc[1].x = UPSCALE(control->x); arc[1].y = UPSCALE(control->y); arc[2].x = ras.x; arc[2].y = ras.y; if(( TRUNC( arc[0].y ) >= ras.max_ey && TRUNC( arc[1].y ) >= ras.max_ey && TRUNC( arc[2].y ) >= ras.max_ey) || ( TRUNC( arc[0].y ) < ras.min_ey && TRUNC( arc[1].y ) < ras.min_ey && TRUNC( arc[2].y ) < ras.min_ey)) { ras.x = arc[0].x; ras.y = arc[0].y; return; } dx = XCG_FT_ABS(arc[2].x + arc[0].x - 2 * arc[1].x); dy = XCG_FT_ABS(arc[2].y + arc[0].y - 2 * arc[1].y); if(dx < dy) dx = dy; draw = 1; while(dx > ONE_PIXEL / 4) { dx >>= 2; draw <<= 1; } do { split = 1; while((draw & split) == 0) { gray_split_conic(arc); arc += 2; split <<= 1; } gray_render_line( RAS_VAR_ arc[0].x, arc[0].y); arc -= 2; } while(--draw); } static void gray_split_cubic(XCG_FT_Vector * base) { TPos a, b, c, d; base[6].x = base[3].x; c = base[1].x; d = base[2].x; base[1].x = a = (base[0].x + c) / 2; base[5].x = b = (base[3].x + d) / 2; c = (c + d) / 2; base[2].x = a = (a + c) / 2; base[4].x = b = (b + c) / 2; base[3].x = (a + b) / 2; base[6].y = base[3].y; c = base[1].y; d = base[2].y; base[1].y = a = (base[0].y + c) / 2; base[5].y = b = (base[3].y + d) / 2; c = (c + d) / 2; base[2].y = a = (a + c) / 2; base[4].y = b = (b + c) / 2; base[3].y = (a + b) / 2; } static void gray_render_cubic(RAS_ARG_ const XCG_FT_Vector * control1, const XCG_FT_Vector * control2, const XCG_FT_Vector * to) { XCG_FT_Vector bez_stack[16 * 3 + 1]; XCG_FT_Vector * arc = bez_stack; TPos dx, dy, dx_, dy_; TPos dx1, dy1, dx2, dy2; TPos L, s, s_limit; arc[0].x = UPSCALE(to->x); arc[0].y = UPSCALE(to->y); arc[1].x = UPSCALE(control2->x); arc[1].y = UPSCALE(control2->y); arc[2].x = UPSCALE(control1->x); arc[2].y = UPSCALE(control1->y); arc[3].x = ras.x; arc[3].y = ras.y; if(( TRUNC( arc[0].y ) >= ras.max_ey && TRUNC( arc[1].y ) >= ras.max_ey && TRUNC( arc[2].y ) >= ras.max_ey && TRUNC( arc[3].y ) >= ras.max_ey) || ( TRUNC( arc[0].y ) < ras.min_ey && TRUNC( arc[1].y ) < ras.min_ey && TRUNC( arc[2].y ) < ras.min_ey && TRUNC( arc[3].y ) < ras.min_ey)) { ras.x = arc[0].x; ras.y = arc[0].y; return; } for(;;) { dx = dx_ = arc[3].x - arc[0].x; dy = dy_ = arc[3].y - arc[0].y; L = XCG_FT_HYPOT(dx_, dy_); if(L >= (1 << 23)) goto Split; s_limit = L * (TPos)( ONE_PIXEL / 6); dx1 = arc[1].x - arc[0].x; dy1 = arc[1].y - arc[0].y; s = XCG_FT_ABS(dy * dx1 - dx * dy1); if(s > s_limit) goto Split; dx2 = arc[2].x - arc[0].x; dy2 = arc[2].y - arc[0].y; s = XCG_FT_ABS(dy * dx2 - dx * dy2); if(s > s_limit) goto Split; if(dx1 * (dx1 - dx) + dy1 * (dy1 - dy) > 0 || dx2 * (dx2 - dx) + dy2 * (dy2 - dy) > 0) goto Split; gray_render_line( RAS_VAR_ arc[0].x, arc[0].y); if(arc == bez_stack) return; arc -= 3; continue; Split: gray_split_cubic(arc); arc += 3; } } static int gray_move_to(const XCG_FT_Vector *to, PWorker worker) { TPos x, y; if(!ras.invalid) gray_record_cell(worker); x = UPSCALE(to->x); y = UPSCALE(to->y); gray_start_cell(worker, TRUNC(x), TRUNC(y)); ras.x = x; ras.y = y; return 0; } static void gray_hline( RAS_ARG_ TCoord x, TCoord y, TPos area, int acount) { int coverage; coverage = (int)(area >> ( PIXEL_BITS * 2 + 1 - 8)); if(coverage < 0) coverage = -coverage; if( ras.outline.flags & XCG_FT_OUTLINE_EVEN_ODD_FILL) { coverage &= 511; if(coverage > 256) coverage = 512 - coverage; else if(coverage == 256) coverage = 255; } else { if(coverage >= 256) coverage = 255; } y += (TCoord)ras.min_ey; x += (TCoord)ras.min_ex; if(x >= (1 << 23)) x = (1 << 23) - 1; if(y >= (1 << 23)) y = (1 << 23) - 1; if(coverage) { XCG_FT_Span *span; int count; int skip; count = ras.num_gray_spans; span = ras.gray_spans + count - 1; if(count > 0 && span->y == y && span->x + span->len == x && span->coverage == coverage) { span->len = span->len + acount; return; } if(count >= XCG_FT_MAX_GRAY_SPANS) { if( ras.render_span && count > ras.skip_spans) { skip = ras.skip_spans > 0 ? ras.skip_spans : 0; ras.render_span( ras.num_gray_spans - skip, ras.gray_spans + skip, ras.render_span_data); } ras.skip_spans -= ras.num_gray_spans; ras.num_gray_spans = 0; span = ras.gray_spans; } else span++; span->x = x; span->len = acount; span->y = y; span->coverage = (unsigned char)coverage; ras.num_gray_spans++; } } static void gray_sweep(RAS_ARG) { int yindex; if( ras.num_cells == 0) return; for(yindex = 0; yindex < ras.ycount; yindex++) { PCell cell = ras.ycells[yindex]; TCoord cover = 0; TCoord x = 0; for(; cell != NULL; cell = cell->next) { TArea area; if(cell->x > x && cover != 0) gray_hline( RAS_VAR_ x, yindex, cover * ( ONE_PIXEL * 2), cell->x - x); cover += cell->cover; area = cover * ( ONE_PIXEL * 2) - cell->area; if(area != 0 && cell->x >= 0) gray_hline( RAS_VAR_ cell->x, yindex, area, 1); x = cell->x + 1; } if( ras.count_ex > x && cover != 0) gray_hline( RAS_VAR_ x, yindex, cover * ( ONE_PIXEL * 2), ras.count_ex - x); } } static int XCG_FT_Outline_Decompose(const XCG_FT_Outline * outline, void * user) { #undef SCALED #define SCALED( x ) (x) XCG_FT_Vector v_last; XCG_FT_Vector v_control; XCG_FT_Vector v_start; XCG_FT_Vector * point; XCG_FT_Vector * limit; char * tags; int n; int first; int error; char tag; if(!outline) return ErrRaster_Invalid_Outline; first = 0; for(n = 0; n < outline->n_contours; n++) { int last; last = outline->contours[n]; if(last < 0) goto Invalid_Outline; limit = outline->points + last; v_start = outline->points[first]; v_start.x = SCALED(v_start.x); v_start.y = SCALED(v_start.y); v_last = outline->points[last]; v_last.x = SCALED(v_last.x); v_last.y = SCALED(v_last.y); v_control = v_start; point = outline->points + first; tags = outline->tags + first; tag = XCG_FT_CURVE_TAG(tags[0]); if(tag == XCG_FT_CURVE_TAG_CUBIC) goto Invalid_Outline; if(tag == XCG_FT_CURVE_TAG_CONIC) { if( XCG_FT_CURVE_TAG( outline->tags[last] ) == XCG_FT_CURVE_TAG_ON) { v_start = v_last; limit--; } else { v_start.x = (v_start.x + v_last.x) / 2; v_start.y = (v_start.y + v_last.y) / 2; v_last = v_start; } point--; tags--; } error = gray_move_to(&v_start, user); if(error) goto Exit; while(point < limit) { point++; tags++; tag = XCG_FT_CURVE_TAG(tags[0]); switch(tag) { case XCG_FT_CURVE_TAG_ON: { XCG_FT_Vector vec; vec.x = SCALED(point->x); vec.y = SCALED(point->y); gray_render_line(user, UPSCALE(vec.x), UPSCALE(vec.y)); continue; } case XCG_FT_CURVE_TAG_CONIC: { v_control.x = SCALED(point->x); v_control.y = SCALED(point->y); Do_Conic: if(point < limit) { XCG_FT_Vector vec; XCG_FT_Vector v_middle; point++; tags++; tag = XCG_FT_CURVE_TAG(tags[0]); vec.x = SCALED(point->x); vec.y = SCALED(point->y); if(tag == XCG_FT_CURVE_TAG_ON) { gray_render_conic(user, &v_control, &vec); continue; } if(tag != XCG_FT_CURVE_TAG_CONIC) goto Invalid_Outline; v_middle.x = (v_control.x + vec.x) / 2; v_middle.y = (v_control.y + vec.y) / 2; gray_render_conic(user, &v_control, &v_middle); v_control = vec; goto Do_Conic; } gray_render_conic(user, &v_control, &v_start); goto Close; } default: { XCG_FT_Vector vec1, vec2; if(point + 1 > limit || XCG_FT_CURVE_TAG( tags[1] ) != XCG_FT_CURVE_TAG_CUBIC) goto Invalid_Outline; point += 2; tags += 2; vec1.x = SCALED(point[-2].x); vec1.y = SCALED(point[-2].y); vec2.x = SCALED(point[-1].x); vec2.y = SCALED(point[-1].y); if(point <= limit) { XCG_FT_Vector vec; vec.x = SCALED(point->x); vec.y = SCALED(point->y); gray_render_cubic(user, &vec1, &vec2, &vec); continue; } gray_render_cubic(user, &vec1, &vec2, &v_start); goto Close; } } } gray_render_line(user, UPSCALE(v_start.x), UPSCALE(v_start.y)); Close: first = last + 1; } return 0; Exit: return error; Invalid_Outline: return ErrRaster_Invalid_Outline; } typedef struct TBand_ { TPos min, max; } TBand; static int gray_convert_glyph_inner(RAS_ARG) { volatile int error = 0; if( xcg_ft_setjmp( ras.jump_buffer) == 0) { error = XCG_FT_Outline_Decompose(&ras.outline, &ras); if(!ras.invalid) gray_record_cell( RAS_VAR); } else { error = ErrRaster_Memory_Overflow; } return error; } static int gray_convert_glyph(RAS_ARG) { TBand bands[40]; TBand *volatile band; int volatile n, num_bands; TPos volatile min, max, max_y; XCG_FT_BBox *clip; int skip; ras.num_gray_spans = 0; gray_compute_cbox( RAS_VAR); clip = &ras.clip_box; if( ras.max_ex <= clip->xMin || ras.min_ex >= clip->xMax || ras.max_ey <= clip->yMin || ras.min_ey >= clip->yMax) return 0; if( ras.min_ex < clip->xMin) ras.min_ex = clip->xMin; if( ras.min_ey < clip->yMin) ras.min_ey = clip->yMin; if( ras.max_ex > clip->xMax) ras.max_ex = clip->xMax; if( ras.max_ey > clip->yMax) ras.max_ey = clip->yMax; ras.count_ex = ras.max_ex - ras.min_ex; ras.count_ey = ras.max_ey - ras.min_ey; num_bands = (int)(( ras.max_ey - ras.min_ey) / ras.band_size); if(num_bands == 0) num_bands = 1; if(num_bands >= 39) num_bands = 39; ras.band_shoot = 0; min = ras.min_ey; max_y = ras.max_ey; for(n = 0; n < num_bands; n++, min = max) { max = min + ras.band_size; if(n == num_bands - 1 || max > max_y) max = max_y; bands[0].min = min; bands[0].max = max; band = bands; while(band >= bands) { TPos bottom, top, middle; int error; { PCell cells_max; int yindex; int cell_start, cell_end, cell_mod; ras.ycells = (PCell*)ras.buffer; ras.ycount = band->max - band->min; cell_start = sizeof(PCell) * ras.ycount; cell_mod = cell_start % sizeof(TCell); if(cell_mod > 0) cell_start += sizeof(TCell) - cell_mod; cell_end = ras.buffer_size; cell_end -= cell_end % sizeof(TCell); cells_max = (PCell)((char*)ras.buffer + cell_end); ras.cells = (PCell)((char*)ras.buffer + cell_start); if( ras.cells >= cells_max) goto ReduceBands; ras.max_cells = (int)(cells_max - ras.cells); if( ras.max_cells < 2) goto ReduceBands; for(yindex = 0; yindex < ras.ycount; yindex++) ras.ycells[yindex] = NULL; } ras.num_cells = 0; ras.invalid = 1; ras.min_ey = band->min; ras.max_ey = band->max; ras.count_ey = band->max - band->min; error = gray_convert_glyph_inner( RAS_VAR); if(!error) { gray_sweep( RAS_VAR); band--; continue; } else if(error != ErrRaster_Memory_Overflow) return 1; ReduceBands: bottom = band->min; top = band->max; middle = bottom + ((top - bottom) >> 1); if(middle == bottom) { return ErrRaster_OutOfMemory; } if(bottom - top >= ras.band_size) ras.band_shoot++; band[1].min = bottom; band[1].max = middle; band[0].min = middle; band[0].max = top; band++; } } if( ras.render_span && ras.num_gray_spans > ras.skip_spans) { skip = ras.skip_spans > 0 ? ras.skip_spans : 0; ras.render_span( ras.num_gray_spans - skip, ras.gray_spans + skip, ras.render_span_data); } ras.skip_spans -= ras.num_gray_spans; if( ras.band_shoot > 8 && ras.band_size > 16) ras.band_size = ras.band_size / 2; return 0; } static int gray_raster_render(RAS_ARG_ void * buffer, long buffer_size, const XCG_FT_Raster_Params * params) { const XCG_FT_Outline *outline = (const XCG_FT_Outline*)params->source; if(outline == NULL) return ErrRaster_Invalid_Outline; if(outline->n_points == 0 || outline->n_contours <= 0) return 0; if(!outline->contours || !outline->points) return ErrRaster_Invalid_Outline; if(outline->n_points != outline->contours[outline->n_contours - 1] + 1) return ErrRaster_Invalid_Outline; if(!(params->flags & XCG_FT_RASTER_FLAG_AA)) return ErrRaster_Invalid_Mode; if(!(params->flags & XCG_FT_RASTER_FLAG_DIRECT)) return ErrRaster_Invalid_Mode; if(params->flags & XCG_FT_RASTER_FLAG_CLIP) { ras.clip_box = params->clip_box; } else { ras.clip_box.xMin = -(1 << 23); ras.clip_box.yMin = -(1 << 23); ras.clip_box.xMax = (1 << 23) - 1; ras.clip_box.yMax = (1 << 23) - 1; } gray_init_cells( RAS_VAR_ buffer, buffer_size); ras.outline = *outline; ras.num_cells = 0; ras.invalid = 1; ras.band_size = (int)(buffer_size / (long)(sizeof(TCell) * 8)); ras.render_span = (XCG_FT_Raster_Span_Func)params->gray_spans; ras.render_span_data = params->user; return gray_convert_glyph( RAS_VAR); } void XCG_FT_Raster_Render(const XCG_FT_Raster_Params * params) { char stack[XCG_FT_MINIMUM_POOL_SIZE]; size_t length = XCG_FT_MINIMUM_POOL_SIZE; TWorker worker; worker.skip_spans = 0; int rendered_spans = 0; int error = gray_raster_render(&worker, stack, length, params); while(error == ErrRaster_OutOfMemory) { if(worker.skip_spans < 0) rendered_spans += -worker.skip_spans; worker.skip_spans = rendered_spans; length *= 2; void *heap = malloc(length); error = gray_raster_render(&worker, heap, length, params); free(heap); } } /* * stroker */ #define XCG_FT_SMALL_CONIC_THRESHOLD (XCG_FT_ANGLE_PI / 6) #define XCG_FT_SMALL_CUBIC_THRESHOLD (XCG_FT_ANGLE_PI / 8) #define XCG_FT_IS_SMALL(x) ((x) > -2 && (x) < 2) static XCG_FT_Pos ft_pos_abs(XCG_FT_Pos x) { return x >= 0 ? x : -x; } static void ft_conic_split(XCG_FT_Vector *base) { XCG_FT_Pos a, b; base[4].x = base[2].x; a = base[0].x + base[1].x; b = base[1].x + base[2].x; base[3].x = b >> 1; base[2].x = (a + b) >> 2; base[1].x = a >> 1; base[4].y = base[2].y; a = base[0].y + base[1].y; b = base[1].y + base[2].y; base[3].y = b >> 1; base[2].y = (a + b) >> 2; base[1].y = a >> 1; } static XCG_FT_Bool ft_conic_is_small_enough(XCG_FT_Vector *base, XCG_FT_Angle *angle_in, XCG_FT_Angle *angle_out) { XCG_FT_Vector d1, d2; XCG_FT_Angle theta; XCG_FT_Int close1, close2; d1.x = base[1].x - base[2].x; d1.y = base[1].y - base[2].y; d2.x = base[0].x - base[1].x; d2.y = base[0].y - base[1].y; close1 = XCG_FT_IS_SMALL(d1.x) && XCG_FT_IS_SMALL(d1.y); close2 = XCG_FT_IS_SMALL(d2.x) && XCG_FT_IS_SMALL(d2.y); if(close1) { if(close2) { } else { *angle_in = *angle_out = XCG_FT_Atan2(d2.x, d2.y); } } else { if(close2) { *angle_in = *angle_out = XCG_FT_Atan2(d1.x, d1.y); } else { *angle_in = XCG_FT_Atan2(d1.x, d1.y); *angle_out = XCG_FT_Atan2(d2.x, d2.y); } } theta = ft_pos_abs(XCG_FT_Angle_Diff(*angle_in, *angle_out)); return XCG_FT_BOOL(theta < XCG_FT_SMALL_CONIC_THRESHOLD); } static void ft_cubic_split(XCG_FT_Vector * base) { XCG_FT_Pos a, b, c; base[6].x = base[3].x; a = base[0].x + base[1].x; b = base[1].x + base[2].x; c = base[2].x + base[3].x; base[5].x = c >> 1; c += b; base[4].x = c >> 2; base[1].x = a >> 1; a += b; base[2].x = a >> 2; base[3].x = (a + c) >> 3; base[6].y = base[3].y; a = base[0].y + base[1].y; b = base[1].y + base[2].y; c = base[2].y + base[3].y; base[5].y = c >> 1; c += b; base[4].y = c >> 2; base[1].y = a >> 1; a += b; base[2].y = a >> 2; base[3].y = (a + c) >> 3; } static XCG_FT_Angle ft_angle_mean(XCG_FT_Angle angle1, XCG_FT_Angle angle2) { return angle1 + XCG_FT_Angle_Diff(angle1, angle2) / 2; } static XCG_FT_Bool ft_cubic_is_small_enough(XCG_FT_Vector *base, XCG_FT_Angle *angle_in, XCG_FT_Angle *angle_mid, XCG_FT_Angle *angle_out) { XCG_FT_Vector d1, d2, d3; XCG_FT_Angle theta1, theta2; XCG_FT_Int close1, close2, close3; d1.x = base[2].x - base[3].x; d1.y = base[2].y - base[3].y; d2.x = base[1].x - base[2].x; d2.y = base[1].y - base[2].y; d3.x = base[0].x - base[1].x; d3.y = base[0].y - base[1].y; close1 = XCG_FT_IS_SMALL(d1.x) && XCG_FT_IS_SMALL(d1.y); close2 = XCG_FT_IS_SMALL(d2.x) && XCG_FT_IS_SMALL(d2.y); close3 = XCG_FT_IS_SMALL(d3.x) && XCG_FT_IS_SMALL(d3.y); if(close1) { if(close2) { if(close3) { } else { *angle_in = *angle_mid = *angle_out = XCG_FT_Atan2(d3.x, d3.y); } } else { if(close3) { *angle_in = *angle_mid = *angle_out = XCG_FT_Atan2(d2.x, d2.y); } else { *angle_in = *angle_mid = XCG_FT_Atan2(d2.x, d2.y); *angle_out = XCG_FT_Atan2(d3.x, d3.y); } } } else { if(close2) { if(close3) { *angle_in = *angle_mid = *angle_out = XCG_FT_Atan2(d1.x, d1.y); } else { *angle_in = XCG_FT_Atan2(d1.x, d1.y); *angle_out = XCG_FT_Atan2(d3.x, d3.y); *angle_mid = ft_angle_mean(*angle_in, *angle_out); } } else { if(close3) { *angle_in = XCG_FT_Atan2(d1.x, d1.y); *angle_mid = *angle_out = XCG_FT_Atan2(d2.x, d2.y); } else { *angle_in = XCG_FT_Atan2(d1.x, d1.y); *angle_mid = XCG_FT_Atan2(d2.x, d2.y); *angle_out = XCG_FT_Atan2(d3.x, d3.y); } } } theta1 = ft_pos_abs(XCG_FT_Angle_Diff(*angle_in, *angle_mid)); theta2 = ft_pos_abs(XCG_FT_Angle_Diff(*angle_mid, *angle_out)); return XCG_FT_BOOL(theta1 < XCG_FT_SMALL_CUBIC_THRESHOLD && theta2 < XCG_FT_SMALL_CUBIC_THRESHOLD); } typedef enum XCG_FT_StrokeTags_ { XCG_FT_STROKE_TAG_ON = 1, XCG_FT_STROKE_TAG_CUBIC = 2, XCG_FT_STROKE_TAG_BEGIN = 4, XCG_FT_STROKE_TAG_END = 8, } XCG_FT_StrokeTags; #define XCG_FT_STROKE_TAG_BEGIN_END (XCG_FT_STROKE_TAG_BEGIN | XCG_FT_STROKE_TAG_END) typedef struct XCG_FT_StrokeBorderRec_ { XCG_FT_UInt num_points; XCG_FT_UInt max_points; XCG_FT_Vector * points; XCG_FT_Byte * tags; XCG_FT_Bool movable; XCG_FT_Int start; XCG_FT_Bool valid; } XCG_FT_StrokeBorderRec, *XCG_FT_StrokeBorder; XCG_FT_Error XCG_FT_Outline_Check(XCG_FT_Outline * outline) { if(outline) { XCG_FT_Int n_points = outline->n_points; XCG_FT_Int n_contours = outline->n_contours; XCG_FT_Int end0, end; XCG_FT_Int n; if(n_points == 0 && n_contours == 0) return 0; if(n_points <= 0 || n_contours <= 0) goto Bad; end0 = end = -1; for(n = 0; n < n_contours; n++) { end = outline->contours[n]; if(end <= end0 || end >= n_points) goto Bad; end0 = end; } if(end != n_points - 1) goto Bad; return 0; } Bad: return -1; } void XCG_FT_Outline_Get_CBox(const XCG_FT_Outline * outline, XCG_FT_BBox * acbox) { XCG_FT_Pos xMin, yMin, xMax, yMax; if(outline && acbox) { if(outline->n_points == 0) { xMin = 0; yMin = 0; xMax = 0; yMax = 0; } else { XCG_FT_Vector *vec = outline->points; XCG_FT_Vector *limit = vec + outline->n_points; xMin = xMax = vec->x; yMin = yMax = vec->y; vec++; for(; vec < limit; vec++) { XCG_FT_Pos x, y; x = vec->x; if(x < xMin) xMin = x; if(x > xMax) xMax = x; y = vec->y; if(y < yMin) yMin = y; if(y > yMax) yMax = y; } } acbox->xMin = xMin; acbox->xMax = xMax; acbox->yMin = yMin; acbox->yMax = yMax; } } static XCG_FT_Error ft_stroke_border_grow(XCG_FT_StrokeBorder border, XCG_FT_UInt new_points) { XCG_FT_UInt old_max = border->max_points; XCG_FT_UInt new_max = border->num_points + new_points; XCG_FT_Error error = 0; if(new_max > old_max) { XCG_FT_UInt cur_max = old_max; while(cur_max < new_max) cur_max += (cur_max >> 1) + 16; border->points = (XCG_FT_Vector*)realloc(border->points, cur_max * sizeof(XCG_FT_Vector)); border->tags = (XCG_FT_Byte*)realloc(border->tags, cur_max * sizeof(XCG_FT_Byte)); if(!border->points || !border->tags) goto Exit; border->max_points = cur_max; } Exit: return error; } static void ft_stroke_border_close(XCG_FT_StrokeBorder border, XCG_FT_Bool reverse) { XCG_FT_UInt start = border->start; XCG_FT_UInt count = border->num_points; if(count <= start + 1U) border->num_points = start; else { border->num_points = --count; border->points[start] = border->points[count]; border->tags[start] = border->tags[count]; if(reverse) { { XCG_FT_Vector *vec1 = border->points + start + 1; XCG_FT_Vector *vec2 = border->points + count - 1; for(; vec1 < vec2; vec1++, vec2--) { XCG_FT_Vector tmp; tmp = *vec1; *vec1 = *vec2; *vec2 = tmp; } } { XCG_FT_Byte *tag1 = border->tags + start + 1; XCG_FT_Byte *tag2 = border->tags + count - 1; for(; tag1 < tag2; tag1++, tag2--) { XCG_FT_Byte tmp; tmp = *tag1; *tag1 = *tag2; *tag2 = tmp; } } } border->tags[start] |= XCG_FT_STROKE_TAG_BEGIN; border->tags[count - 1] |= XCG_FT_STROKE_TAG_END; } border->start = -1; border->movable = FALSE; } static XCG_FT_Error ft_stroke_border_lineto(XCG_FT_StrokeBorder border, XCG_FT_Vector * to, XCG_FT_Bool movable) { XCG_FT_Error error = 0; if(border->movable) { border->points[border->num_points - 1] = *to; } else { if(border->num_points > 0&& XCG_FT_IS_SMALL(border->points[border->num_points - 1].x - to->x) && XCG_FT_IS_SMALL(border->points[border->num_points - 1].y - to->y)) return error; error = ft_stroke_border_grow(border, 1); if(!error) { XCG_FT_Vector *vec = border->points + border->num_points; XCG_FT_Byte *tag = border->tags + border->num_points; vec[0] = *to; tag[0] = XCG_FT_STROKE_TAG_ON; border->num_points += 1; } } border->movable = movable; return error; } static XCG_FT_Error ft_stroke_border_conicto(XCG_FT_StrokeBorder border, XCG_FT_Vector * control, XCG_FT_Vector * to) { XCG_FT_Error error; error = ft_stroke_border_grow(border, 2); if(!error) { XCG_FT_Vector *vec = border->points + border->num_points; XCG_FT_Byte *tag = border->tags + border->num_points; vec[0] = *control; vec[1] = *to; tag[0] = 0; tag[1] = XCG_FT_STROKE_TAG_ON; border->num_points += 2; } border->movable = FALSE; return error; } static XCG_FT_Error ft_stroke_border_cubicto(XCG_FT_StrokeBorder border, XCG_FT_Vector * control1, XCG_FT_Vector * control2, XCG_FT_Vector * to) { XCG_FT_Error error; error = ft_stroke_border_grow(border, 3); if(!error) { XCG_FT_Vector *vec = border->points + border->num_points; XCG_FT_Byte *tag = border->tags + border->num_points; vec[0] = *control1; vec[1] = *control2; vec[2] = *to; tag[0] = XCG_FT_STROKE_TAG_CUBIC; tag[1] = XCG_FT_STROKE_TAG_CUBIC; tag[2] = XCG_FT_STROKE_TAG_ON; border->num_points += 3; } border->movable = FALSE; return error; } #define XCG_FT_ARC_CUBIC_ANGLE (XCG_FT_ANGLE_PI / 2) static XCG_FT_Error ft_stroke_border_arcto(XCG_FT_StrokeBorder border, XCG_FT_Vector *center, XCG_FT_Fixed radius, XCG_FT_Angle angle_start, XCG_FT_Angle angle_diff) { XCG_FT_Fixed coef; XCG_FT_Vector a0, a1, a2, a3; XCG_FT_Int i, arcs = 1; XCG_FT_Error error = 0; while(angle_diff > XCG_FT_ARC_CUBIC_ANGLE * arcs || -angle_diff > XCG_FT_ARC_CUBIC_ANGLE * arcs) arcs++; coef = XCG_FT_Tan(angle_diff / (4 * arcs)); coef += coef / 3; XCG_FT_Vector_From_Polar(&a0, radius, angle_start); a1.x = XCG_FT_MulFix(-a0.y, coef); a1.y = XCG_FT_MulFix(a0.x, coef); a0.x += center->x; a0.y += center->y; a1.x += a0.x; a1.y += a0.y; for(i = 1; i <= arcs; i++) { XCG_FT_Vector_From_Polar(&a3, radius, angle_start + i * angle_diff / arcs); a2.x = XCG_FT_MulFix(a3.y, coef); a2.y = XCG_FT_MulFix(-a3.x, coef); a3.x += center->x; a3.y += center->y; a2.x += a3.x; a2.y += a3.y; error = ft_stroke_border_cubicto(border, &a1, &a2, &a3); if(error) break; a1.x = a3.x - a2.x + a3.x; a1.y = a3.y - a2.y + a3.y; } return error; } static XCG_FT_Error ft_stroke_border_moveto(XCG_FT_StrokeBorder border, XCG_FT_Vector * to) { if(border->start >= 0) ft_stroke_border_close(border, FALSE); border->start = border->num_points; border->movable = FALSE; return ft_stroke_border_lineto(border, to, FALSE); } static void ft_stroke_border_init(XCG_FT_StrokeBorder border) { border->points = NULL; border->tags = NULL; border->num_points = 0; border->max_points = 0; border->start = -1; border->valid = FALSE; } static void ft_stroke_border_reset(XCG_FT_StrokeBorder border) { border->num_points = 0; border->start = -1; border->valid = FALSE; } static void ft_stroke_border_done(XCG_FT_StrokeBorder border) { free(border->points); free(border->tags); border->num_points = 0; border->max_points = 0; border->start = -1; border->valid = FALSE; } static XCG_FT_Error ft_stroke_border_get_counts(XCG_FT_StrokeBorder border, XCG_FT_UInt * anum_points, XCG_FT_UInt * anum_contours) { XCG_FT_Error error = 0; XCG_FT_UInt num_points = 0; XCG_FT_UInt num_contours = 0; XCG_FT_UInt count = border->num_points; XCG_FT_Vector *point = border->points; XCG_FT_Byte *tags = border->tags; XCG_FT_Int in_contour = 0; for(; count > 0; count--, num_points++, point++, tags++) { if(tags[0] & XCG_FT_STROKE_TAG_BEGIN) { if(in_contour != 0) goto Fail; in_contour = 1; } else if(in_contour == 0) goto Fail; if(tags[0] & XCG_FT_STROKE_TAG_END) { in_contour = 0; num_contours++; } } if(in_contour != 0) goto Fail; border->valid = TRUE; Exit: *anum_points = num_points; *anum_contours = num_contours; return error; Fail: num_points = 0; num_contours = 0; goto Exit; } static void ft_stroke_border_export(XCG_FT_StrokeBorder border, XCG_FT_Outline * outline) { if(outline->points != NULL && border->points != NULL) memcpy(outline->points + outline->n_points, border->points, border->num_points * sizeof(XCG_FT_Vector)); if(outline->tags) { XCG_FT_UInt count = border->num_points; XCG_FT_Byte *read = border->tags; XCG_FT_Byte *write = (XCG_FT_Byte*)outline->tags + outline->n_points; for(; count > 0; count--, read++, write++) { if(*read & XCG_FT_STROKE_TAG_ON) *write = XCG_FT_CURVE_TAG_ON; else if(*read & XCG_FT_STROKE_TAG_CUBIC) *write = XCG_FT_CURVE_TAG_CUBIC; else *write = XCG_FT_CURVE_TAG_CONIC; } } if(outline->contours) { XCG_FT_UInt count = border->num_points; XCG_FT_Byte *tags = border->tags; XCG_FT_Int *write = outline->contours + outline->n_contours; XCG_FT_Int idx = (XCG_FT_Int)outline->n_points; for(; count > 0; count--, tags++, idx++) { if(*tags & XCG_FT_STROKE_TAG_END) { *write++ = idx; outline->n_contours++; } } } outline->n_points = (int)(outline->n_points + border->num_points); XCG_FT_Outline_Check(outline); } #define XCG_FT_SIDE_TO_ROTATE(s) (XCG_FT_ANGLE_PI2 - (s) * XCG_FT_ANGLE_PI) typedef struct XCG_FT_StrokerRec_ { XCG_FT_Angle angle_in; XCG_FT_Angle angle_out; XCG_FT_Vector center; XCG_FT_Fixed line_length; XCG_FT_Bool first_point; XCG_FT_Bool subpath_open; XCG_FT_Angle subpath_angle; XCG_FT_Vector subpath_start; XCG_FT_Fixed subpath_line_length; XCG_FT_Bool handle_wide_strokes; XCG_FT_Stroker_LineCap line_cap; XCG_FT_Stroker_LineJoin line_join; XCG_FT_Stroker_LineJoin line_join_saved; XCG_FT_Fixed miter_limit; XCG_FT_Fixed radius; XCG_FT_StrokeBorderRec borders[2]; } XCG_FT_StrokerRec; XCG_FT_Error XCG_FT_Stroker_New(XCG_FT_Stroker * astroker) { XCG_FT_Error error = 0; XCG_FT_Stroker stroker = NULL; stroker = (XCG_FT_StrokerRec*)calloc(1, sizeof(XCG_FT_StrokerRec)); if(stroker) { ft_stroke_border_init(&stroker->borders[0]); ft_stroke_border_init(&stroker->borders[1]); } *astroker = stroker; return error; } void XCG_FT_Stroker_Rewind(XCG_FT_Stroker stroker) { if(stroker) { ft_stroke_border_reset(&stroker->borders[0]); ft_stroke_border_reset(&stroker->borders[1]); } } void XCG_FT_Stroker_Set(XCG_FT_Stroker stroker, XCG_FT_Fixed radius, XCG_FT_Stroker_LineCap line_cap, XCG_FT_Stroker_LineJoin line_join, XCG_FT_Fixed miter_limit) { stroker->radius = radius; stroker->line_cap = line_cap; stroker->line_join = line_join; stroker->miter_limit = miter_limit; if(stroker->miter_limit < 0x10000) stroker->miter_limit = 0x10000; stroker->line_join_saved = line_join; XCG_FT_Stroker_Rewind(stroker); } void XCG_FT_Stroker_Done(XCG_FT_Stroker stroker) { if(stroker) { ft_stroke_border_done(&stroker->borders[0]); ft_stroke_border_done(&stroker->borders[1]); free(stroker); } } static XCG_FT_Error ft_stroker_arcto(XCG_FT_Stroker stroker, XCG_FT_Int side) { XCG_FT_Angle total, rotate; XCG_FT_Fixed radius = stroker->radius; XCG_FT_Error error = 0; XCG_FT_StrokeBorder border = stroker->borders + side; rotate = XCG_FT_SIDE_TO_ROTATE(side); total = XCG_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); if(total == XCG_FT_ANGLE_PI) total = -rotate * 2; error = ft_stroke_border_arcto(border, &stroker->center, radius, stroker->angle_in + rotate, total); border->movable = FALSE; return error; } static XCG_FT_Error ft_stroker_cap(XCG_FT_Stroker stroker, XCG_FT_Angle angle, XCG_FT_Int side) { XCG_FT_Error error = 0; if(stroker->line_cap == XCG_FT_STROKER_LINECAP_ROUND) { stroker->angle_in = angle; stroker->angle_out = angle + XCG_FT_ANGLE_PI; error = ft_stroker_arcto(stroker, side); } else { XCG_FT_Vector middle, delta; XCG_FT_Fixed radius = stroker->radius; XCG_FT_StrokeBorder border = stroker->borders + side; XCG_FT_Vector_From_Polar(&middle, radius, angle); delta.x = side ? middle.y : -middle.y; delta.y = side ? -middle.x : middle.x; if(stroker->line_cap == XCG_FT_STROKER_LINECAP_SQUARE) { middle.x += stroker->center.x; middle.y += stroker->center.y; } else { middle.x = stroker->center.x; middle.y = stroker->center.y; } delta.x += middle.x; delta.y += middle.y; error = ft_stroke_border_lineto(border, &delta, FALSE); if(error) goto Exit; delta.x = middle.x - delta.x + middle.x; delta.y = middle.y - delta.y + middle.y; error = ft_stroke_border_lineto(border, &delta, FALSE); } Exit: return error; } static XCG_FT_Error ft_stroker_inside(XCG_FT_Stroker stroker, XCG_FT_Int side, XCG_FT_Fixed line_length) { XCG_FT_StrokeBorder border = stroker->borders + side; XCG_FT_Angle phi, theta, rotate; XCG_FT_Fixed length; XCG_FT_Vector sigma = { 0, 0 }; XCG_FT_Vector delta; XCG_FT_Error error = 0; XCG_FT_Bool intersect; rotate = XCG_FT_SIDE_TO_ROTATE(side); theta = XCG_FT_Angle_Diff(stroker->angle_in, stroker->angle_out) / 2; if(!border->movable || line_length == 0 || theta > 0x59C000 || theta < -0x59C000) intersect = FALSE; else { XCG_FT_Fixed min_length; XCG_FT_Vector_Unit(&sigma, theta); min_length = ft_pos_abs(XCG_FT_MulDiv(stroker->radius, sigma.y, sigma.x)); intersect = XCG_FT_BOOL(min_length && stroker->line_length >= min_length && line_length >= min_length); } if(!intersect) { XCG_FT_Vector_From_Polar(&delta, stroker->radius, stroker->angle_out + rotate); delta.x += stroker->center.x; delta.y += stroker->center.y; border->movable = FALSE; } else { phi = stroker->angle_in + theta + rotate; length = XCG_FT_DivFix(stroker->radius, sigma.x); XCG_FT_Vector_From_Polar(&delta, length, phi); delta.x += stroker->center.x; delta.y += stroker->center.y; } error = ft_stroke_border_lineto(border, &delta, FALSE); return error; } static XCG_FT_Error ft_stroker_outside(XCG_FT_Stroker stroker, XCG_FT_Int side, XCG_FT_Fixed line_length) { XCG_FT_StrokeBorder border = stroker->borders + side; XCG_FT_Error error; XCG_FT_Angle rotate; if(stroker->line_join == XCG_FT_STROKER_LINEJOIN_ROUND) error = ft_stroker_arcto(stroker, side); else { XCG_FT_Fixed radius = stroker->radius; XCG_FT_Vector sigma = { 0, 0 }; XCG_FT_Angle theta = 0, phi = 0; XCG_FT_Bool bevel, fixed_bevel; rotate = XCG_FT_SIDE_TO_ROTATE(side); bevel = XCG_FT_BOOL(stroker->line_join == XCG_FT_STROKER_LINEJOIN_BEVEL); fixed_bevel = XCG_FT_BOOL(stroker->line_join != XCG_FT_STROKER_LINEJOIN_MITER_VARIABLE); if(!bevel) { theta = XCG_FT_Angle_Diff(stroker->angle_in, stroker->angle_out) / 2; if(theta == XCG_FT_ANGLE_PI2) theta = -rotate; phi = stroker->angle_in + theta + rotate; XCG_FT_Vector_From_Polar(&sigma, stroker->miter_limit, theta); if(sigma.x < 0x10000L) { if(fixed_bevel || ft_pos_abs(theta) > 57) bevel = TRUE; } } if(bevel) { if(fixed_bevel) { XCG_FT_Vector delta; XCG_FT_Vector_From_Polar(&delta, radius, stroker->angle_out + rotate); delta.x += stroker->center.x; delta.y += stroker->center.y; border->movable = FALSE; error = ft_stroke_border_lineto(border, &delta, FALSE); } else { XCG_FT_Vector middle, delta; XCG_FT_Fixed coef; XCG_FT_Vector_From_Polar(&middle, XCG_FT_MulFix(radius, stroker->miter_limit), phi); coef = XCG_FT_DivFix(0x10000L - sigma.x, sigma.y); delta.x = XCG_FT_MulFix(middle.y, coef); delta.y = XCG_FT_MulFix(-middle.x, coef); middle.x += stroker->center.x; middle.y += stroker->center.y; delta.x += middle.x; delta.y += middle.y; error = ft_stroke_border_lineto(border, &delta, FALSE); if(error) goto Exit; delta.x = middle.x - delta.x + middle.x; delta.y = middle.y - delta.y + middle.y; error = ft_stroke_border_lineto(border, &delta, FALSE); if(error) goto Exit; if(line_length == 0) { XCG_FT_Vector_From_Polar(&delta, radius, stroker->angle_out + rotate); delta.x += stroker->center.x; delta.y += stroker->center.y; error = ft_stroke_border_lineto(border, &delta, FALSE); } } } else { XCG_FT_Fixed length; XCG_FT_Vector delta; length = XCG_FT_MulDiv(stroker->radius, stroker->miter_limit, sigma.x); XCG_FT_Vector_From_Polar(&delta, length, phi); delta.x += stroker->center.x; delta.y += stroker->center.y; error = ft_stroke_border_lineto(border, &delta, FALSE); if(error) goto Exit; if(line_length == 0) { XCG_FT_Vector_From_Polar(&delta, stroker->radius, stroker->angle_out + rotate); delta.x += stroker->center.x; delta.y += stroker->center.y; error = ft_stroke_border_lineto(border, &delta, FALSE); } } } Exit: return error; } static XCG_FT_Error ft_stroker_process_corner(XCG_FT_Stroker stroker, XCG_FT_Fixed line_length) { XCG_FT_Error error = 0; XCG_FT_Angle turn; XCG_FT_Int inside_side; turn = XCG_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); if(turn == 0) goto Exit; inside_side = 0; if(turn < 0) inside_side = 1; error = ft_stroker_inside(stroker, inside_side, line_length); if(error) goto Exit; error = ft_stroker_outside(stroker, 1 - inside_side, line_length); Exit: return error; } static XCG_FT_Error ft_stroker_subpath_start(XCG_FT_Stroker stroker, XCG_FT_Angle start_angle, XCG_FT_Fixed line_length) { XCG_FT_Vector delta; XCG_FT_Vector point; XCG_FT_Error error; XCG_FT_StrokeBorder border; XCG_FT_Vector_From_Polar(&delta, stroker->radius, start_angle + XCG_FT_ANGLE_PI2); point.x = stroker->center.x + delta.x; point.y = stroker->center.y + delta.y; border = stroker->borders; error = ft_stroke_border_moveto(border, &point); if(error) goto Exit; point.x = stroker->center.x - delta.x; point.y = stroker->center.y - delta.y; border++; error = ft_stroke_border_moveto(border, &point); stroker->subpath_angle = start_angle; stroker->first_point = FALSE; stroker->subpath_line_length = line_length; Exit: return error; } XCG_FT_Error XCG_FT_Stroker_LineTo(XCG_FT_Stroker stroker, XCG_FT_Vector * to) { XCG_FT_Error error = 0; XCG_FT_StrokeBorder border; XCG_FT_Vector delta; XCG_FT_Angle angle; XCG_FT_Int side; XCG_FT_Fixed line_length; delta.x = to->x - stroker->center.x; delta.y = to->y - stroker->center.y; if(delta.x == 0 && delta.y == 0) goto Exit; line_length = XCG_FT_Vector_Length(&delta); angle = XCG_FT_Atan2(delta.x, delta.y); XCG_FT_Vector_From_Polar(&delta, stroker->radius, angle + XCG_FT_ANGLE_PI2); if(stroker->first_point) { error = ft_stroker_subpath_start(stroker, angle, line_length); if(error) goto Exit; } else { stroker->angle_out = angle; error = ft_stroker_process_corner(stroker, line_length); if(error) goto Exit; } for(border = stroker->borders, side = 1; side >= 0; side--, border++) { XCG_FT_Vector point; point.x = to->x + delta.x; point.y = to->y + delta.y; error = ft_stroke_border_lineto(border, &point, TRUE); if(error) goto Exit; delta.x = -delta.x; delta.y = -delta.y; } stroker->angle_in = angle; stroker->center = *to; stroker->line_length = line_length; Exit: return error; } XCG_FT_Error XCG_FT_Stroker_ConicTo(XCG_FT_Stroker stroker, XCG_FT_Vector * control, XCG_FT_Vector * to) { XCG_FT_Error error = 0; XCG_FT_Vector bez_stack[34]; XCG_FT_Vector *arc; XCG_FT_Vector *limit = bez_stack + 30; XCG_FT_Bool first_arc = TRUE; if(XCG_FT_IS_SMALL(stroker->center.x - control->x) && XCG_FT_IS_SMALL(stroker->center.y - control->y) && XCG_FT_IS_SMALL(control->x - to->x) && XCG_FT_IS_SMALL(control->y - to->y)) { stroker->center = *to; goto Exit; } arc = bez_stack; arc[0] = *to; arc[1] = *control; arc[2] = stroker->center; while(arc >= bez_stack) { XCG_FT_Angle angle_in, angle_out; angle_in = angle_out = stroker->angle_in; if(arc < limit && !ft_conic_is_small_enough(arc, &angle_in, &angle_out)) { if(stroker->first_point) stroker->angle_in = angle_in; ft_conic_split(arc); arc += 2; continue; } if(first_arc) { first_arc = FALSE; if(stroker->first_point) error = ft_stroker_subpath_start(stroker, angle_in, 0); else { stroker->angle_out = angle_in; error = ft_stroker_process_corner(stroker, 0); } } else if(ft_pos_abs(XCG_FT_Angle_Diff(stroker->angle_in, angle_in)) > XCG_FT_SMALL_CONIC_THRESHOLD / 4) { stroker->center = arc[2]; stroker->angle_out = angle_in; stroker->line_join = XCG_FT_STROKER_LINEJOIN_ROUND; error = ft_stroker_process_corner(stroker, 0); stroker->line_join = stroker->line_join_saved; } if(error) goto Exit; { XCG_FT_Vector ctrl, end; XCG_FT_Angle theta, phi, rotate, alpha0 = 0; XCG_FT_Fixed length; XCG_FT_StrokeBorder border; XCG_FT_Int side; theta = XCG_FT_Angle_Diff(angle_in, angle_out) / 2; phi = angle_in + theta; length = XCG_FT_DivFix(stroker->radius, XCG_FT_Cos(theta)); if(stroker->handle_wide_strokes) alpha0 = XCG_FT_Atan2(arc[0].x - arc[2].x, arc[0].y - arc[2].y); for(border = stroker->borders, side = 0; side <= 1; side++, border++) { rotate = XCG_FT_SIDE_TO_ROTATE(side); XCG_FT_Vector_From_Polar(&ctrl, length, phi + rotate); ctrl.x += arc[1].x; ctrl.y += arc[1].y; XCG_FT_Vector_From_Polar(&end, stroker->radius, angle_out + rotate); end.x += arc[0].x; end.y += arc[0].y; if(stroker->handle_wide_strokes) { XCG_FT_Vector start; XCG_FT_Angle alpha1; start = border->points[border->num_points - 1]; alpha1 = XCG_FT_Atan2(end.x - start.x, end.y - start.y); if(ft_pos_abs(XCG_FT_Angle_Diff(alpha0, alpha1)) > XCG_FT_ANGLE_PI / 2) { XCG_FT_Angle beta, gamma; XCG_FT_Vector bvec, delta; XCG_FT_Fixed blen, sinA, sinB, alen; beta = XCG_FT_Atan2(arc[2].x - start.x, arc[2].y - start.y); gamma = XCG_FT_Atan2(arc[0].x - end.x, arc[0].y - end.y); bvec.x = end.x - start.x; bvec.y = end.y - start.y; blen = XCG_FT_Vector_Length(&bvec); sinA = ft_pos_abs(XCG_FT_Sin(alpha1 - gamma)); sinB = ft_pos_abs(XCG_FT_Sin(beta - gamma)); alen = XCG_FT_MulDiv(blen, sinA, sinB); XCG_FT_Vector_From_Polar(&delta, alen, beta); delta.x += start.x; delta.y += start.y; border->movable = FALSE; error = ft_stroke_border_lineto(border, &delta, FALSE); if(error) goto Exit; error = ft_stroke_border_lineto(border, &end, FALSE); if(error) goto Exit; error = ft_stroke_border_conicto(border, &ctrl, &start); if(error) goto Exit; error = ft_stroke_border_lineto(border, &end, FALSE); if(error) goto Exit; continue; } } error = ft_stroke_border_conicto(border, &ctrl, &end); if(error) goto Exit; } } arc -= 2; stroker->angle_in = angle_out; } stroker->center = *to; stroker->line_length = 0; Exit: return error; } XCG_FT_Error XCG_FT_Stroker_CubicTo(XCG_FT_Stroker stroker, XCG_FT_Vector *control1, XCG_FT_Vector *control2, XCG_FT_Vector *to) { XCG_FT_Error error = 0; XCG_FT_Vector bez_stack[37]; XCG_FT_Vector *arc; XCG_FT_Vector *limit = bez_stack + 32; XCG_FT_Bool first_arc = TRUE; if(XCG_FT_IS_SMALL(stroker->center.x - control1->x) && XCG_FT_IS_SMALL(stroker->center.y - control1->y) && XCG_FT_IS_SMALL(control1->x - control2->x) && XCG_FT_IS_SMALL(control1->y - control2->y) && XCG_FT_IS_SMALL(control2->x - to->x) && XCG_FT_IS_SMALL(control2->y - to->y)) { stroker->center = *to; goto Exit; } arc = bez_stack; arc[0] = *to; arc[1] = *control2; arc[2] = *control1; arc[3] = stroker->center; while(arc >= bez_stack) { XCG_FT_Angle angle_in, angle_mid, angle_out; angle_in = angle_out = angle_mid = stroker->angle_in; if(arc < limit && !ft_cubic_is_small_enough(arc, &angle_in, &angle_mid, &angle_out)) { if(stroker->first_point) stroker->angle_in = angle_in; ft_cubic_split(arc); arc += 3; continue; } if(first_arc) { first_arc = FALSE; if(stroker->first_point) error = ft_stroker_subpath_start(stroker, angle_in, 0); else { stroker->angle_out = angle_in; error = ft_stroker_process_corner(stroker, 0); } } else if(ft_pos_abs(XCG_FT_Angle_Diff(stroker->angle_in, angle_in)) > XCG_FT_SMALL_CUBIC_THRESHOLD / 4) { stroker->center = arc[3]; stroker->angle_out = angle_in; stroker->line_join = XCG_FT_STROKER_LINEJOIN_ROUND; error = ft_stroker_process_corner(stroker, 0); stroker->line_join = stroker->line_join_saved; } if(error) goto Exit; { XCG_FT_Vector ctrl1, ctrl2, end; XCG_FT_Angle theta1, phi1, theta2, phi2, rotate, alpha0 = 0; XCG_FT_Fixed length1, length2; XCG_FT_StrokeBorder border; XCG_FT_Int side; theta1 = XCG_FT_Angle_Diff(angle_in, angle_mid) / 2; theta2 = XCG_FT_Angle_Diff(angle_mid, angle_out) / 2; phi1 = ft_angle_mean(angle_in, angle_mid); phi2 = ft_angle_mean(angle_mid, angle_out); length1 = XCG_FT_DivFix(stroker->radius, XCG_FT_Cos(theta1)); length2 = XCG_FT_DivFix(stroker->radius, XCG_FT_Cos(theta2)); if(stroker->handle_wide_strokes) alpha0 = XCG_FT_Atan2(arc[0].x - arc[3].x, arc[0].y - arc[3].y); for(border = stroker->borders, side = 0; side <= 1; side++, border++) { rotate = XCG_FT_SIDE_TO_ROTATE(side); XCG_FT_Vector_From_Polar(&ctrl1, length1, phi1 + rotate); ctrl1.x += arc[2].x; ctrl1.y += arc[2].y; XCG_FT_Vector_From_Polar(&ctrl2, length2, phi2 + rotate); ctrl2.x += arc[1].x; ctrl2.y += arc[1].y; XCG_FT_Vector_From_Polar(&end, stroker->radius, angle_out + rotate); end.x += arc[0].x; end.y += arc[0].y; if(stroker->handle_wide_strokes) { XCG_FT_Vector start; XCG_FT_Angle alpha1; start = border->points[border->num_points - 1]; alpha1 = XCG_FT_Atan2(end.x - start.x, end.y - start.y); if(ft_pos_abs(XCG_FT_Angle_Diff(alpha0, alpha1)) > XCG_FT_ANGLE_PI / 2) { XCG_FT_Angle beta, gamma; XCG_FT_Vector bvec, delta; XCG_FT_Fixed blen, sinA, sinB, alen; beta = XCG_FT_Atan2(arc[3].x - start.x, arc[3].y - start.y); gamma = XCG_FT_Atan2(arc[0].x - end.x, arc[0].y - end.y); bvec.x = end.x - start.x; bvec.y = end.y - start.y; blen = XCG_FT_Vector_Length(&bvec); sinA = ft_pos_abs(XCG_FT_Sin(alpha1 - gamma)); sinB = ft_pos_abs(XCG_FT_Sin(beta - gamma)); alen = XCG_FT_MulDiv(blen, sinA, sinB); XCG_FT_Vector_From_Polar(&delta, alen, beta); delta.x += start.x; delta.y += start.y; border->movable = FALSE; error = ft_stroke_border_lineto(border, &delta, FALSE); if(error) goto Exit; error = ft_stroke_border_lineto(border, &end, FALSE); if(error) goto Exit; error = ft_stroke_border_cubicto(border, &ctrl2, &ctrl1, &start); if(error) goto Exit; error = ft_stroke_border_lineto(border, &end, FALSE); if(error) goto Exit; continue; } } error = ft_stroke_border_cubicto(border, &ctrl1, &ctrl2, &end); if(error) goto Exit; } } arc -= 3; stroker->angle_in = angle_out; } stroker->center = *to; stroker->line_length = 0; Exit: return error; } XCG_FT_Error XCG_FT_Stroker_BeginSubPath(XCG_FT_Stroker stroker, XCG_FT_Vector * to, XCG_FT_Bool open) { stroker->first_point = TRUE; stroker->center = *to; stroker->subpath_open = open; stroker->handle_wide_strokes = XCG_FT_BOOL(stroker->line_join != XCG_FT_STROKER_LINEJOIN_ROUND || (stroker->subpath_open && stroker->line_cap == XCG_FT_STROKER_LINECAP_BUTT)); stroker->subpath_start = *to; stroker->angle_in = 0; return 0; } static XCG_FT_Error ft_stroker_add_reverse_left(XCG_FT_Stroker stroker, XCG_FT_Bool open) { XCG_FT_StrokeBorder right = stroker->borders + 0; XCG_FT_StrokeBorder left = stroker->borders + 1; XCG_FT_Int new_points; XCG_FT_Error error = 0; new_points = left->num_points - left->start; if(new_points > 0) { error = ft_stroke_border_grow(right, (XCG_FT_UInt)new_points); if(error) goto Exit; { XCG_FT_Vector *dst_point = right->points + right->num_points; XCG_FT_Byte *dst_tag = right->tags + right->num_points; XCG_FT_Vector *src_point = left->points + left->num_points - 1; XCG_FT_Byte *src_tag = left->tags + left->num_points - 1; while(src_point >= left->points + left->start) { *dst_point = *src_point; *dst_tag = *src_tag; if(open) dst_tag[0] &= ~XCG_FT_STROKE_TAG_BEGIN_END; else { XCG_FT_Byte ttag = (XCG_FT_Byte)(dst_tag[0] & XCG_FT_STROKE_TAG_BEGIN_END); if(ttag == XCG_FT_STROKE_TAG_BEGIN || ttag == XCG_FT_STROKE_TAG_END) dst_tag[0] ^= XCG_FT_STROKE_TAG_BEGIN_END; } src_point--; src_tag--; dst_point++; dst_tag++; } } left->num_points = left->start; right->num_points += new_points; right->movable = FALSE; left->movable = FALSE; } Exit: return error; } XCG_FT_Error XCG_FT_Stroker_EndSubPath(XCG_FT_Stroker stroker) { XCG_FT_Error error = 0; if(stroker->subpath_open) { XCG_FT_StrokeBorder right = stroker->borders; error = ft_stroker_cap(stroker, stroker->angle_in, 0); if(error) goto Exit; error = ft_stroker_add_reverse_left(stroker, TRUE); if(error) goto Exit; stroker->center = stroker->subpath_start; error = ft_stroker_cap(stroker, stroker->subpath_angle + XCG_FT_ANGLE_PI, 0); if(error) goto Exit; ft_stroke_border_close(right, FALSE); } else { XCG_FT_Angle turn; XCG_FT_Int inside_side; if(stroker->center.x != stroker->subpath_start.x || stroker->center.y != stroker->subpath_start.y) { error = XCG_FT_Stroker_LineTo(stroker, &stroker->subpath_start); if(error) goto Exit; } stroker->angle_out = stroker->subpath_angle; turn = XCG_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); if(turn != 0) { inside_side = 0; if(turn < 0) inside_side = 1; error = ft_stroker_inside(stroker, inside_side, stroker->subpath_line_length); if(error) goto Exit; error = ft_stroker_outside(stroker, 1 - inside_side, stroker->subpath_line_length); if(error) goto Exit; } ft_stroke_border_close(stroker->borders + 0, FALSE); ft_stroke_border_close(stroker->borders + 1, TRUE); } Exit: return error; } XCG_FT_Error XCG_FT_Stroker_GetBorderCounts(XCG_FT_Stroker stroker, XCG_FT_StrokerBorder border, XCG_FT_UInt * anum_points, XCG_FT_UInt * anum_contours) { XCG_FT_UInt num_points = 0, num_contours = 0; XCG_FT_Error error; if(!stroker || border > 1) { error = -1; goto Exit; } error = ft_stroke_border_get_counts(stroker->borders + border, &num_points, &num_contours); Exit: if(anum_points) *anum_points = num_points; if(anum_contours) *anum_contours = num_contours; return error; } XCG_FT_Error XCG_FT_Stroker_GetCounts(XCG_FT_Stroker stroker, XCG_FT_UInt * anum_points, XCG_FT_UInt * anum_contours) { XCG_FT_UInt count1, count2, num_points = 0; XCG_FT_UInt count3, count4, num_contours = 0; XCG_FT_Error error; error = ft_stroke_border_get_counts(stroker->borders + 0, &count1, &count2); if(error) goto Exit; error = ft_stroke_border_get_counts(stroker->borders + 1, &count3, &count4); if(error) goto Exit; num_points = count1 + count3; num_contours = count2 + count4; Exit: *anum_points = num_points; *anum_contours = num_contours; return error; } void XCG_FT_Stroker_ExportBorder(XCG_FT_Stroker stroker, XCG_FT_StrokerBorder border, XCG_FT_Outline * outline) { if(border == XCG_FT_STROKER_BORDER_LEFT || border == XCG_FT_STROKER_BORDER_RIGHT) { XCG_FT_StrokeBorder sborder = &stroker->borders[border]; if(sborder->valid) ft_stroke_border_export(sborder, outline); } } void XCG_FT_Stroker_Export(XCG_FT_Stroker stroker, XCG_FT_Outline * outline) { XCG_FT_Stroker_ExportBorder(stroker, XCG_FT_STROKER_BORDER_LEFT, outline); XCG_FT_Stroker_ExportBorder(stroker, XCG_FT_STROKER_BORDER_RIGHT, outline); } XCG_FT_Error XCG_FT_Stroker_ParseOutline(XCG_FT_Stroker stroker, const XCG_FT_Outline *outline) { XCG_FT_Vector v_last; XCG_FT_Vector v_control; XCG_FT_Vector v_start; XCG_FT_Vector * point; XCG_FT_Vector * limit; char * tags; XCG_FT_Error error; XCG_FT_Int n; XCG_FT_UInt first; XCG_FT_Int tag; if(!outline || !stroker) return -1; XCG_FT_Stroker_Rewind(stroker); first = 0; for(n = 0; n < outline->n_contours; n++) { XCG_FT_UInt last; last = outline->contours[n]; limit = outline->points + last; if(last <= first) { first = last + 1; continue; } v_start = outline->points[first]; v_last = outline->points[last]; v_control = v_start; point = outline->points + first; tags = outline->tags + first; tag = XCG_FT_CURVE_TAG(tags[0]); if(tag == XCG_FT_CURVE_TAG_CUBIC) goto Invalid_Outline; if(tag == XCG_FT_CURVE_TAG_CONIC) { if(XCG_FT_CURVE_TAG(outline->tags[last]) == XCG_FT_CURVE_TAG_ON) { v_start = v_last; limit--; } else { v_start.x = (v_start.x + v_last.x) / 2; v_start.y = (v_start.y + v_last.y) / 2; } point--; tags--; } error = XCG_FT_Stroker_BeginSubPath(stroker, &v_start, outline->contours_flag[n]); if(error) goto Exit; while(point < limit) { point++; tags++; tag = XCG_FT_CURVE_TAG(tags[0]); switch(tag) { case XCG_FT_CURVE_TAG_ON: { XCG_FT_Vector vec; vec.x = point->x; vec.y = point->y; error = XCG_FT_Stroker_LineTo(stroker, &vec); if(error) goto Exit; continue; } case XCG_FT_CURVE_TAG_CONIC: v_control.x = point->x; v_control.y = point->y; Do_Conic: if(point < limit) { XCG_FT_Vector vec; XCG_FT_Vector v_middle; point++; tags++; tag = XCG_FT_CURVE_TAG(tags[0]); vec = point[0]; if(tag == XCG_FT_CURVE_TAG_ON) { error = XCG_FT_Stroker_ConicTo(stroker, &v_control, &vec); if(error) goto Exit; continue; } if(tag != XCG_FT_CURVE_TAG_CONIC) goto Invalid_Outline; v_middle.x = (v_control.x + vec.x) / 2; v_middle.y = (v_control.y + vec.y) / 2; error = XCG_FT_Stroker_ConicTo(stroker, &v_control, &v_middle); if(error) goto Exit; v_control = vec; goto Do_Conic; } error = XCG_FT_Stroker_ConicTo(stroker, &v_control, &v_start); goto Close; default: { XCG_FT_Vector vec1, vec2; if(point + 1 > limit || XCG_FT_CURVE_TAG(tags[1]) != XCG_FT_CURVE_TAG_CUBIC) goto Invalid_Outline; point += 2; tags += 2; vec1 = point[-2]; vec2 = point[-1]; if(point <= limit) { XCG_FT_Vector vec; vec = point[0]; error = XCG_FT_Stroker_CubicTo(stroker, &vec1, &vec2, &vec); if(error) goto Exit; continue; } error = XCG_FT_Stroker_CubicTo(stroker, &vec1, &vec2, &v_start); goto Close; } } } Close: if(error) goto Exit; if(stroker->first_point) { stroker->subpath_open = TRUE; error = ft_stroker_subpath_start(stroker, 0, 0); if(error) goto Exit; } error = XCG_FT_Stroker_EndSubPath(stroker); if(error) goto Exit; first = last + 1; } return 0; Exit: return error; Invalid_Outline: return -2; }