From 32546b5874ed5bb3f1f8abaf700d8012799ccf37 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 3 Oct 2022 18:25:42 +0200 Subject: [PATCH] Squashed 'external/lunasvg/' content from commit 7417baa0a git-subtree-dir: external/lunasvg git-subtree-split: 7417baa0aff477f361e44e2aa793fdb0c7aae352 --- .github/FUNDING.yml | 4 + .github/workflows/ci.yml | 31 + 3rdparty/plutovg/CMakeLists.txt | 14 + 3rdparty/plutovg/plutovg-blend.c | 826 +++++++++++ 3rdparty/plutovg/plutovg-dash.c | 114 ++ 3rdparty/plutovg/plutovg-geometry.c | 582 ++++++++ 3rdparty/plutovg/plutovg-paint.c | 374 +++++ 3rdparty/plutovg/plutovg-private.h | 168 +++ 3rdparty/plutovg/plutovg-rle.c | 413 ++++++ 3rdparty/plutovg/plutovg.c | 497 +++++++ 3rdparty/plutovg/plutovg.h | 288 ++++ 3rdparty/software/CMakeLists.txt | 11 + 3rdparty/software/FTL.TXT | 166 +++ 3rdparty/software/sw_ft_math.c | 461 ++++++ 3rdparty/software/sw_ft_math.h | 438 ++++++ 3rdparty/software/sw_ft_raster.c | 1423 ++++++++++++++++++ 3rdparty/software/sw_ft_raster.h | 607 ++++++++ 3rdparty/software/sw_ft_stroker.c | 1939 +++++++++++++++++++++++++ 3rdparty/software/sw_ft_stroker.h | 319 +++++ 3rdparty/software/sw_ft_types.h | 160 +++ CMakeLists.txt | 39 + LICENSE | 21 + README.md | 89 ++ example/CMakeLists.txt | 8 + example/stb_image_write.h | 511 +++++++ example/svg2png.cpp | 66 + include/CMakeLists.txt | 4 + include/lunasvg.h | 270 ++++ luna.png | Bin 0 -> 16536 bytes source/CMakeLists.txt | 28 + source/canvas.cpp | 270 ++++ source/canvas.h | 76 + source/clippathelement.cpp | 37 + source/clippathelement.h | 23 + source/defselement.cpp | 15 + source/defselement.h | 18 + source/element.cpp | 187 +++ source/element.h | 218 +++ source/gelement.cpp | 30 + source/gelement.h | 19 + source/geometryelement.cpp | 335 +++++ source/geometryelement.h | 122 ++ source/graphicselement.cpp | 17 + source/graphicselement.h | 18 + source/layoutcontext.cpp | 766 ++++++++++ source/layoutcontext.h | 395 +++++ source/lunasvg.cpp | 413 ++++++ source/markerelement.cpp | 98 ++ source/markerelement.h | 31 + source/maskelement.cpp | 77 + source/maskelement.h | 28 + source/paintelement.cpp | 433 ++++++ source/paintelement.h | 368 +++++ source/parser.cpp | 2073 +++++++++++++++++++++++++++ source/parser.h | 195 +++ source/parserutils.h | 267 ++++ source/property.cpp | 698 +++++++++ source/property.h | 370 +++++ source/stopelement.cpp | 29 + source/stopelement.h | 21 + source/styledelement.cpp | 177 +++ source/styledelement.h | 53 + source/styleelement.cpp | 15 + source/styleelement.h | 18 + source/svgelement.cpp | 121 ++ source/svgelement.h | 31 + source/symbolelement.cpp | 52 + source/symbolelement.h | 25 + source/useelement.cpp | 99 ++ source/useelement.h | 26 + 70 files changed, 18135 insertions(+) create mode 100644 .github/FUNDING.yml create mode 100644 .github/workflows/ci.yml create mode 100644 3rdparty/plutovg/CMakeLists.txt create mode 100644 3rdparty/plutovg/plutovg-blend.c create mode 100644 3rdparty/plutovg/plutovg-dash.c create mode 100644 3rdparty/plutovg/plutovg-geometry.c create mode 100644 3rdparty/plutovg/plutovg-paint.c create mode 100644 3rdparty/plutovg/plutovg-private.h create mode 100644 3rdparty/plutovg/plutovg-rle.c create mode 100644 3rdparty/plutovg/plutovg.c create mode 100644 3rdparty/plutovg/plutovg.h create mode 100755 3rdparty/software/CMakeLists.txt create mode 100644 3rdparty/software/FTL.TXT create mode 100644 3rdparty/software/sw_ft_math.c create mode 100644 3rdparty/software/sw_ft_math.h create mode 100644 3rdparty/software/sw_ft_raster.c create mode 100644 3rdparty/software/sw_ft_raster.h create mode 100644 3rdparty/software/sw_ft_stroker.c create mode 100644 3rdparty/software/sw_ft_stroker.h create mode 100644 3rdparty/software/sw_ft_types.h create mode 100755 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 example/CMakeLists.txt create mode 100644 example/stb_image_write.h create mode 100644 example/svg2png.cpp create mode 100755 include/CMakeLists.txt create mode 100644 include/lunasvg.h create mode 100644 luna.png create mode 100755 source/CMakeLists.txt create mode 100644 source/canvas.cpp create mode 100644 source/canvas.h create mode 100644 source/clippathelement.cpp create mode 100644 source/clippathelement.h create mode 100644 source/defselement.cpp create mode 100644 source/defselement.h create mode 100644 source/element.cpp create mode 100644 source/element.h create mode 100644 source/gelement.cpp create mode 100644 source/gelement.h create mode 100644 source/geometryelement.cpp create mode 100644 source/geometryelement.h create mode 100644 source/graphicselement.cpp create mode 100644 source/graphicselement.h create mode 100644 source/layoutcontext.cpp create mode 100644 source/layoutcontext.h create mode 100644 source/lunasvg.cpp create mode 100644 source/markerelement.cpp create mode 100644 source/markerelement.h create mode 100644 source/maskelement.cpp create mode 100644 source/maskelement.h create mode 100644 source/paintelement.cpp create mode 100644 source/paintelement.h create mode 100644 source/parser.cpp create mode 100644 source/parser.h create mode 100644 source/parserutils.h create mode 100644 source/property.cpp create mode 100644 source/property.h create mode 100644 source/stopelement.cpp create mode 100644 source/stopelement.h create mode 100644 source/styledelement.cpp create mode 100644 source/styledelement.h create mode 100644 source/styleelement.cpp create mode 100644 source/styleelement.h create mode 100644 source/svgelement.cpp create mode 100644 source/svgelement.h create mode 100644 source/symbolelement.cpp create mode 100644 source/symbolelement.h create mode 100644 source/useelement.cpp create mode 100644 source/useelement.h diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..b26586fb4 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +patreon: sammycage +custom: ['https://www.paypal.me/sammycage'] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..f91644d80 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,31 @@ +name: Build +on: [push, pull_request] + +jobs: + linux: + runs-on: ubuntu-latest + env: + CC: gcc-10 + CXX: g++-10 + steps: + - uses: actions/checkout@v2 + - run: cmake . -DLUNASVG_BUILD_EXAMPLES=ON + - run: make -j 2 + + macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - run: cmake . -DLUNASVG_BUILD_EXAMPLES=ON + - run: cmake --build . + + windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - run: cmake . -DLUNASVG_BUILD_EXAMPLES=ON + - run: cmake --build . + - uses: actions/upload-artifact@v2 + with: + name: svg2png-windows + path: example\*\svg2png.exe diff --git a/3rdparty/plutovg/CMakeLists.txt b/3rdparty/plutovg/CMakeLists.txt new file mode 100644 index 000000000..79cf3ee9e --- /dev/null +++ b/3rdparty/plutovg/CMakeLists.txt @@ -0,0 +1,14 @@ +target_sources(lunasvg +PRIVATE + "${CMAKE_CURRENT_LIST_DIR}/plutovg.c" + "${CMAKE_CURRENT_LIST_DIR}/plutovg-paint.c" + "${CMAKE_CURRENT_LIST_DIR}/plutovg-geometry.c" + "${CMAKE_CURRENT_LIST_DIR}/plutovg-blend.c" + "${CMAKE_CURRENT_LIST_DIR}/plutovg-rle.c" + "${CMAKE_CURRENT_LIST_DIR}/plutovg-dash.c" +) + +target_include_directories(lunasvg +PRIVATE + "${CMAKE_CURRENT_LIST_DIR}" +) diff --git a/3rdparty/plutovg/plutovg-blend.c b/3rdparty/plutovg/plutovg-blend.c new file mode 100644 index 000000000..8b018363c --- /dev/null +++ b/3rdparty/plutovg/plutovg-blend.c @@ -0,0 +1,826 @@ +#include "plutovg-private.h" + +#include +#include + +#define COLOR_TABLE_SIZE 1024 +typedef struct { + plutovg_spread_method_t spread; + plutovg_matrix_t matrix; + uint32_t colortable[COLOR_TABLE_SIZE]; + union { + struct { + double x1, y1; + double x2, y2; + } linear; + struct { + double cx, cy, cr; + double fx, fy, fr; + } radial; + }; +} gradient_data_t; + +typedef struct { + plutovg_matrix_t matrix; + uint8_t* data; + int width; + int height; + int stride; + int const_alpha; +} texture_data_t; + +typedef struct { + double dx; + double dy; + double l; + double off; +} linear_gradient_values_t; + +typedef struct { + double dx; + double dy; + double dr; + double sqrfr; + double a; + double inv2a; + int extended; +} radial_gradient_values_t; + +static inline uint32_t premultiply_color(const plutovg_color_t* color, double opacity) +{ + uint32_t alpha = (uint8_t)(color->a * opacity * 255); + uint32_t pr = (uint8_t)(color->r * alpha); + uint32_t pg = (uint8_t)(color->g * alpha); + uint32_t pb = (uint8_t)(color->b * alpha); + + return (alpha << 24) | (pr << 16) | (pg << 8) | (pb); +} + +static inline uint32_t combine_opacity(const plutovg_color_t* color, double opacity) +{ + uint32_t a = (uint8_t)(color->a * opacity * 255); + uint32_t r = (uint8_t)(color->r * 255); + uint32_t g = (uint8_t)(color->g * 255); + uint32_t b = (uint8_t)(color->b * 255); + + return (a << 24) | (r << 16) | (g << 8) | (b); +} + +static inline uint32_t premultiply_pixel(uint32_t color) +{ + uint32_t a = plutovg_alpha(color); + uint32_t r = plutovg_red(color); + uint32_t g = plutovg_green(color); + uint32_t b = plutovg_blue(color); + + uint32_t pr = (r * a) / 255; + uint32_t pg = (g * a) / 255; + uint32_t pb = (b * a) / 255; + return (a << 24) | (pr << 16) | (pg << 8) | (pb); +} + +static inline uint32_t interpolate_pixel(uint32_t x, uint32_t a, uint32_t y, uint32_t b) +{ + uint32_t t = (x & 0xff00ff) * a + (y & 0xff00ff) * b; + t = (t + ((t >> 8) & 0xff00ff) + 0x800080) >> 8; + t &= 0xff00ff; + x = ((x >> 8) & 0xff00ff) * a + ((y >> 8) & 0xff00ff) * b; + x = (x + ((x >> 8) & 0xff00ff) + 0x800080); + x &= 0xff00ff00; + x |= t; + return x; +} + +static inline uint32_t BYTE_MUL(uint32_t x, uint32_t a) +{ + uint32_t t = (x & 0xff00ff) * a; + t = (t + ((t >> 8) & 0xff00ff) + 0x800080) >> 8; + t &= 0xff00ff; + x = ((x >> 8) & 0xff00ff) * a; + x = (x + ((x >> 8) & 0xff00ff) + 0x800080); + x &= 0xff00ff00; + x |= t; + return x; +} + +static inline void memfill32(uint32_t* dest, uint32_t value, int length) +{ + for(int i = 0;i < length;i++) + dest[i] = value; +} + +static inline int gradient_clamp(const gradient_data_t* gradient, int ipos) +{ + if(gradient->spread == plutovg_spread_method_repeat) + { + ipos = ipos % COLOR_TABLE_SIZE; + ipos = ipos < 0 ? COLOR_TABLE_SIZE + ipos : ipos; + } + else if(gradient->spread == plutovg_spread_method_reflect) + { + const int limit = COLOR_TABLE_SIZE * 2; + ipos = ipos % limit; + ipos = ipos < 0 ? limit + ipos : ipos; + ipos = ipos >= COLOR_TABLE_SIZE ? limit - 1 - ipos : ipos; + } + else + { + if(ipos < 0) + ipos = 0; + else if(ipos >= COLOR_TABLE_SIZE) + ipos = COLOR_TABLE_SIZE - 1; + } + + return ipos; +} + +#define FIXPT_BITS 8 +#define FIXPT_SIZE (1 << FIXPT_BITS) +static inline uint32_t gradient_pixel_fixed(const gradient_data_t* gradient, int fixed_pos) +{ + int ipos = (fixed_pos + (FIXPT_SIZE / 2)) >> FIXPT_BITS; + return gradient->colortable[gradient_clamp(gradient, ipos)]; +} + +static inline uint32_t gradient_pixel(const gradient_data_t* gradient, double pos) +{ + int ipos = (int)(pos * (COLOR_TABLE_SIZE - 1) + 0.5); + return gradient->colortable[gradient_clamp(gradient, ipos)]; +} + +static void fetch_linear_gradient(uint32_t* buffer, const linear_gradient_values_t* v, const gradient_data_t* gradient, int y, int x, int length) +{ + double t, inc; + double rx = 0, ry = 0; + + if(v->l == 0.0) + { + t = inc = 0; + } + else + { + rx = gradient->matrix.m01 * (y + 0.5) + gradient->matrix.m00 * (x + 0.5) + gradient->matrix.m02; + ry = gradient->matrix.m11 * (y + 0.5) + gradient->matrix.m10 * (x + 0.5) + gradient->matrix.m12; + t = v->dx * rx + v->dy * ry + v->off; + inc = v->dx * gradient->matrix.m00 + v->dy * gradient->matrix.m10; + t *= (COLOR_TABLE_SIZE - 1); + inc *= (COLOR_TABLE_SIZE - 1); + } + + const uint32_t* end = buffer + length; + if(inc > -1e-5 && inc < 1e-5) + { + memfill32(buffer, gradient_pixel_fixed(gradient, (int)(t * FIXPT_SIZE)), length); + } + else + { + if(t + inc * length < (double)(INT_MAX >> (FIXPT_BITS + 1)) && t + inc * length > (double)(INT_MIN >> (FIXPT_BITS + 1))) + { + int t_fixed = (int)(t * FIXPT_SIZE); + int inc_fixed = (int)(inc * FIXPT_SIZE); + while(buffer < end) + { + *buffer = gradient_pixel_fixed(gradient, t_fixed); + t_fixed += inc_fixed; + ++buffer; + } + } + else + { + while(buffer < end) + { + *buffer = gradient_pixel(gradient, t / COLOR_TABLE_SIZE); + t += inc; + ++buffer; + } + } + } +} + +static void fetch_radial_gradient(uint32_t* buffer, const radial_gradient_values_t* v, const gradient_data_t* gradient, int y, int x, int length) +{ + if(v->a == 0.0) + { + memfill32(buffer, 0, length); + return; + } + + double rx = gradient->matrix.m01 * (y + 0.5) + gradient->matrix.m02 + gradient->matrix.m00 * (x + 0.5); + double ry = gradient->matrix.m11 * (y + 0.5) + gradient->matrix.m12 + gradient->matrix.m10 * (x + 0.5); + + rx -= gradient->radial.fx; + ry -= gradient->radial.fy; + + double inv_a = 1 / (2 * v->a); + double delta_rx = gradient->matrix.m00; + double delta_ry = gradient->matrix.m10; + + double b = 2 * (v->dr * gradient->radial.fr + rx * v->dx + ry * v->dy); + double delta_b = 2 * (delta_rx * v->dx + delta_ry * v->dy); + double b_delta_b = 2 * b * delta_b; + double delta_b_delta_b = 2 * delta_b * delta_b; + + double bb = b * b; + double delta_bb = delta_b * delta_b; + + b *= inv_a; + delta_b *= inv_a; + + double rxrxryry = rx * rx + ry * ry; + double delta_rxrxryry = delta_rx * delta_rx + delta_ry * delta_ry; + double rx_plus_ry = 2 * (rx * delta_rx + ry * delta_ry); + double delta_rx_plus_ry = 2 * delta_rxrxryry; + + inv_a *= inv_a; + + double det = (bb - 4 * v->a * (v->sqrfr - rxrxryry)) * inv_a; + double delta_det = (b_delta_b + delta_bb + 4 * v->a * (rx_plus_ry + delta_rxrxryry)) * inv_a; + double delta_delta_det = (delta_b_delta_b + 4 * v->a * delta_rx_plus_ry) * inv_a; + + const uint32_t* end = buffer + length; + if(v->extended) + { + while(buffer < end) + { + uint32_t result = 0; + if(det >= 0) + { + double w = sqrt(det) - b; + if(gradient->radial.fr + v->dr * w >= 0) + result = gradient_pixel(gradient, w); + } + + *buffer = result; + det += delta_det; + delta_det += delta_delta_det; + b += delta_b; + ++buffer; + } + } + else + { + while(buffer < end) + { + *buffer++ = gradient_pixel(gradient, sqrt(det) - b); + det += delta_det; + delta_det += delta_delta_det; + b += delta_b; + } + } +} + +static void composition_solid_source(uint32_t* dest, int length, uint32_t color, uint32_t alpha) +{ + if(alpha == 255) + { + memfill32(dest, color, length); + } + else + { + uint32_t ialpha = 255 - alpha; + color = BYTE_MUL(color, alpha); + for(int i = 0;i < length;i++) + dest[i] = color + BYTE_MUL(dest[i], ialpha); + } +} + +static void composition_solid_source_over(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) +{ + if(const_alpha != 255) color = BYTE_MUL(color, const_alpha); + uint32_t ialpha = 255 - plutovg_alpha(color); + for(int i = 0;i < length;i++) + dest[i] = color + BYTE_MUL(dest[i], ialpha); +} + +static void composition_solid_destination_in(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) +{ + uint32_t a = plutovg_alpha(color); + if(const_alpha != 255) a = BYTE_MUL(a, const_alpha) + 255 - const_alpha; + for(int i = 0;i < length;i++) + dest[i] = BYTE_MUL(dest[i], a); +} + +static void composition_solid_destination_out(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) +{ + uint32_t a = plutovg_alpha(~color); + if(const_alpha != 255) a = BYTE_MUL(a, const_alpha) + 255 - const_alpha; + for(int i = 0; i < length;i++) + dest[i] = BYTE_MUL(dest[i], a); +} + +static void composition_source(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) +{ + if(const_alpha == 255) + { + memcpy(dest, src, (size_t)(length) * sizeof(uint32_t)); + } + else + { + uint32_t ialpha = 255 - const_alpha; + for(int i = 0;i < length;i++) + dest[i] = interpolate_pixel(src[i], const_alpha, dest[i], ialpha); + } +} + +static void composition_source_over(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) +{ + uint32_t s, sia; + if(const_alpha == 255) + { + for(int i = 0;i < length;i++) + { + s = src[i]; + if(s >= 0xff000000) + dest[i] = s; + else if(s != 0) + { + sia = plutovg_alpha(~s); + dest[i] = s + BYTE_MUL(dest[i], sia); + } + } + } + else + { + for(int i = 0;i < length;i++) + { + s = BYTE_MUL(src[i], const_alpha); + sia = plutovg_alpha(~s); + dest[i] = s + BYTE_MUL(dest[i], sia); + } + } +} + +static void composition_destination_in(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) +{ + if(const_alpha == 255) + { + for(int i = 0; i < length;i++) + dest[i] = BYTE_MUL(dest[i], plutovg_alpha(src[i])); + } + else + { + uint32_t cia = 255 - const_alpha; + uint32_t a; + for(int i = 0;i < length;i++) + { + a = BYTE_MUL(plutovg_alpha(src[i]), const_alpha) + cia; + dest[i] = BYTE_MUL(dest[i], a); + } + } +} + +static void composition_destination_out(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) +{ + if(const_alpha == 255) + { + for(int i = 0;i < length;i++) + dest[i] = BYTE_MUL(dest[i], plutovg_alpha(~src[i])); + } + else + { + uint32_t cia = 255 - const_alpha; + uint32_t sia; + for(int i = 0;i < length;i++) + { + sia = BYTE_MUL(plutovg_alpha(~src[i]), const_alpha) + cia; + dest[i] = BYTE_MUL(dest[i], sia); + } + } +} + +typedef void(*composition_solid_function_t)(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha); +typedef void(*composition_function_t)(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha); + +static const composition_solid_function_t composition_solid_map[] = { + composition_solid_source, + composition_solid_source_over, + composition_solid_destination_in, + composition_solid_destination_out +}; + +static const composition_function_t composition_map[] = { + composition_source, + composition_source_over, + composition_destination_in, + composition_destination_out +}; + +static void blend_solid(plutovg_surface_t* surface, plutovg_operator_t op, const plutovg_rle_t* rle, uint32_t solid) +{ + composition_solid_function_t func = composition_solid_map[op]; + int count = rle->spans.size; + const plutovg_span_t* spans = rle->spans.data; + while(count--) + { + uint32_t* target = (uint32_t*)(surface->data + spans->y * surface->stride) + spans->x; + func(target, spans->len, solid, spans->coverage); + ++spans; + } +} + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define BUFFER_SIZE 1024 +static void blend_linear_gradient(plutovg_surface_t* surface, plutovg_operator_t op, const plutovg_rle_t* rle, const gradient_data_t* gradient) +{ + composition_function_t func = composition_map[op]; + unsigned int buffer[BUFFER_SIZE]; + + linear_gradient_values_t v; + v.dx = gradient->linear.x2 - gradient->linear.x1; + v.dy = gradient->linear.y2 - gradient->linear.y1; + v.l = v.dx * v.dx + v.dy * v.dy; + v.off = 0.0; + if(v.l != 0.0) + { + v.dx /= v.l; + v.dy /= v.l; + v.off = -v.dx * gradient->linear.x1 - v.dy * gradient->linear.y1; + } + + int count = rle->spans.size; + const plutovg_span_t* spans = rle->spans.data; + while(count--) + { + int length = spans->len; + int x = spans->x; + while(length) + { + int l = MIN(length, BUFFER_SIZE); + fetch_linear_gradient(buffer, &v, gradient, spans->y, x, l); + uint32_t* target = (uint32_t*)(surface->data + spans->y * surface->stride) + x; + func(target, l, buffer, spans->coverage); + x += l; + length -= l; + } + + ++spans; + } +} + +static void blend_radial_gradient(plutovg_surface_t* surface, plutovg_operator_t op, const plutovg_rle_t* rle, const gradient_data_t* gradient) +{ + composition_function_t func = composition_map[op]; + unsigned int buffer[BUFFER_SIZE]; + + radial_gradient_values_t v; + v.dx = gradient->radial.cx - gradient->radial.fx; + v.dy = gradient->radial.cy - gradient->radial.fy; + v.dr = gradient->radial.cr - gradient->radial.fr; + v.sqrfr = gradient->radial.fr * gradient->radial.fr; + v.a = v.dr * v.dr - v.dx * v.dx - v.dy * v.dy; + v.inv2a = 1.0 / (2.0 * v.a); + v.extended = gradient->radial.fr != 0.0 || v.a <= 0.0; + + int count = rle->spans.size; + const plutovg_span_t* spans = rle->spans.data; + while(count--) + { + int length = spans->len; + int x = spans->x; + while(length) + { + int l = MIN(length, BUFFER_SIZE); + fetch_radial_gradient(buffer, &v, gradient, spans->y, x, l); + uint32_t* target = (uint32_t*)(surface->data + spans->y * surface->stride) + x; + func(target, l, buffer, spans->coverage); + x += l; + length -= l; + } + + ++spans; + } +} + +#define CLAMP(v, lo, hi) ((v) < (lo) ? (lo) : (hi) < (v) ? (hi) : (v)) +#define FIXED_SCALE (1 << 16) +static void blend_transformed_argb(plutovg_surface_t* surface, plutovg_operator_t op, const plutovg_rle_t* rle, const texture_data_t* texture) +{ + composition_function_t func = composition_map[op]; + uint32_t buffer[BUFFER_SIZE]; + + int image_width = texture->width; + int image_height = texture->height; + + int fdx = (int)(texture->matrix.m00 * FIXED_SCALE); + int fdy = (int)(texture->matrix.m10 * FIXED_SCALE); + + int count = rle->spans.size; + const plutovg_span_t* spans = rle->spans.data; + while(count--) + { + uint32_t* target = (uint32_t*)(surface->data + spans->y * surface->stride) + spans->x; + + const double cx = spans->x + 0.5; + const double cy = spans->y + 0.5; + + int x = (int)((texture->matrix.m01 * cy + texture->matrix.m00 * cx + texture->matrix.m02) * FIXED_SCALE); + int y = (int)((texture->matrix.m11 * cy + texture->matrix.m10 * cx + texture->matrix.m12) * FIXED_SCALE); + + int length = spans->len; + const int coverage = (spans->coverage * texture->const_alpha) >> 8; + while(length) + { + int l = MIN(length, BUFFER_SIZE); + const uint32_t* end = buffer + l; + uint32_t* b = buffer; + while(b < end) + { + int px = CLAMP(x >> 16, 0, image_width - 1); + int py = CLAMP(y >> 16, 0, image_height - 1); + *b = ((const uint32_t*)(texture->data + py * texture->stride))[px]; + + x += fdx; + y += fdy; + ++b; + } + + func(target, l, buffer, coverage); + target += l; + length -= l; + } + + ++spans; + } +} + +static void blend_untransformed_argb(plutovg_surface_t* surface, plutovg_operator_t op, const plutovg_rle_t* rle, const texture_data_t* texture) +{ + composition_function_t func = composition_map[op]; + + const int image_width = texture->width; + const int image_height = texture->height; + + int xoff = (int)(texture->matrix.m02); + int yoff = (int)(texture->matrix.m12); + + int count = rle->spans.size; + const plutovg_span_t* spans = rle->spans.data; + while(count--) + { + int x = spans->x; + int length = spans->len; + int sx = xoff + x; + int sy = yoff + spans->y; + if(sy >= 0 && sy < image_height && sx < image_width) + { + if(sx < 0) + { + x -= sx; + length += sx; + sx = 0; + } + if(sx + length > image_width) length = image_width - sx; + if(length > 0) + { + const int coverage = (spans->coverage * texture->const_alpha) >> 8; + const uint32_t* src = (const uint32_t*)(texture->data + sy * texture->stride) + sx; + uint32_t* dest = (uint32_t*)(surface->data + spans->y * surface->stride) + x; + func(dest, length, src, coverage); + } + } + + ++spans; + } +} + +static void blend_untransformed_tiled_argb(plutovg_surface_t* surface, plutovg_operator_t op, const plutovg_rle_t* rle, const texture_data_t* texture) +{ + composition_function_t func = composition_map[op]; + + int image_width = texture->width; + int image_height = texture->height; + + int xoff = (int)(texture->matrix.m02) % image_width; + int yoff = (int)(texture->matrix.m12) % image_height; + + if(xoff < 0) + xoff += image_width; + if(yoff < 0) + yoff += image_height; + + int count = rle->spans.size; + const plutovg_span_t* spans = rle->spans.data; + while(count--) + { + int x = spans->x; + int length = spans->len; + int sx = (xoff + spans->x) % image_width; + int sy = (spans->y + yoff) % image_height; + if(sx < 0) + sx += image_width; + if(sy < 0) + sy += image_height; + + const int coverage = (spans->coverage * texture->const_alpha) >> 8; + while(length) + { + int l = MIN(image_width - sx, length); + if(BUFFER_SIZE < l) + l = BUFFER_SIZE; + const uint32_t* src = (const uint32_t*)(texture->data + sy * texture->stride) + sx; + uint32_t* dest = (uint32_t*)(surface->data + spans->y * surface->stride) + x; + func(dest, l, src, coverage); + x += l; + length -= l; + sx = 0; + } + + ++spans; + } +} + +static void blend_transformed_tiled_argb(plutovg_surface_t* surface, plutovg_operator_t op, const plutovg_rle_t* rle, const texture_data_t* texture) +{ + composition_function_t func = composition_map[op]; + uint32_t buffer[BUFFER_SIZE]; + + int image_width = texture->width; + int image_height = texture->height; + const int scanline_offset = texture->stride / 4; + + int fdx = (int)(texture->matrix.m00 * FIXED_SCALE); + int fdy = (int)(texture->matrix.m10 * FIXED_SCALE); + + int count = rle->spans.size; + const plutovg_span_t* spans = rle->spans.data; + while(count--) + { + uint32_t* target = (uint32_t*)(surface->data + spans->y * surface->stride) + spans->x; + const uint32_t* image_bits = (const uint32_t*)texture->data; + + const double cx = spans->x + 0.5; + const double cy = spans->y + 0.5; + + int x = (int)((texture->matrix.m01 * cy + texture->matrix.m00 * cx + texture->matrix.m02) * FIXED_SCALE); + int y = (int)((texture->matrix.m11 * cy + texture->matrix.m10 * cx + texture->matrix.m12) * FIXED_SCALE); + + const int coverage = (spans->coverage * texture->const_alpha) >> 8; + int length = spans->len; + while(length) + { + int l = MIN(length, BUFFER_SIZE); + const uint32_t* end = buffer + l; + uint32_t* b = buffer; + int px16 = x % (image_width << 16); + int py16 = y % (image_height << 16); + int px_delta = fdx % (image_width << 16); + int py_delta = fdy % (image_height << 16); + while(b < end) + { + if(px16 < 0) px16 += image_width << 16; + if(py16 < 0) py16 += image_height << 16; + int px = px16 >> 16; + int py = py16 >> 16; + int y_offset = py * scanline_offset; + + *b = image_bits[y_offset + px]; + x += fdx; + y += fdy; + px16 += px_delta; + if(px16 >= image_width << 16) + px16 -= image_width << 16; + py16 += py_delta; + if(py16 >= image_height << 16) + py16 -= image_height << 16; + ++b; + } + + func(target, l, buffer, coverage); + target += l; + length -= l; + } + + ++spans; + } +} + +void plutovg_blend(plutovg_t* pluto, const plutovg_rle_t* rle) +{ + plutovg_paint_t* source = pluto->state->source; + if(source->type == plutovg_paint_type_color) + plutovg_blend_color(pluto, rle, source->color); + else if(source->type == plutovg_paint_type_gradient) + plutovg_blend_gradient(pluto, rle, source->gradient); + else + plutovg_blend_texture(pluto, rle, source->texture); +} + +void plutovg_blend_color(plutovg_t* pluto, const plutovg_rle_t* rle, const plutovg_color_t* color) +{ + plutovg_state_t* state = pluto->state; + uint32_t solid = premultiply_color(color, state->opacity); + + uint32_t alpha = plutovg_alpha(solid); + if(alpha == 255 && state->op == plutovg_operator_src_over) + blend_solid(pluto->surface, plutovg_operator_src, rle, solid); + else + blend_solid(pluto->surface, state->op, rle, solid); +} + +void plutovg_blend_gradient(plutovg_t* pluto, const plutovg_rle_t* rle, const plutovg_gradient_t* gradient) +{ + plutovg_state_t* state = pluto->state; + gradient_data_t data; + int i, pos = 0, nstop = gradient->stops.size; + const plutovg_gradient_stop_t *curr, *next, *start, *last; + uint32_t curr_color, next_color, last_color; + uint32_t dist, idist; + double delta, t, incr, fpos; + double opacity = state->opacity * gradient->opacity; + + start = gradient->stops.data; + curr = start; + curr_color = combine_opacity(&curr->color, opacity); + + data.colortable[pos] = premultiply_pixel(curr_color); + ++pos; + incr = 1.0 / COLOR_TABLE_SIZE; + fpos = 1.5 * incr; + + while(fpos <= curr->offset) + { + data.colortable[pos] = data.colortable[pos - 1]; + ++pos; + fpos += incr; + } + + for(i = 0;i < nstop - 1;i++) + { + curr = (start + i); + next = (start + i + 1); + delta = 1.0 / (next->offset - curr->offset); + next_color = combine_opacity(&next->color, opacity); + while(fpos < next->offset && pos < COLOR_TABLE_SIZE) + { + t = (fpos - curr->offset) * delta; + dist = (uint32_t)(255 * t); + idist = 255 - dist; + data.colortable[pos] = premultiply_pixel(interpolate_pixel(curr_color, idist, next_color, dist)); + ++pos; + fpos += incr; + } + + curr_color = next_color; + } + + last = start + nstop - 1; + last_color = premultiply_color(&last->color, opacity); + for(;pos < COLOR_TABLE_SIZE;++pos) + data.colortable[pos] = last_color; + + data.spread = gradient->spread; + data.matrix = gradient->matrix; + plutovg_matrix_multiply(&data.matrix, &data.matrix, &state->matrix); + plutovg_matrix_invert(&data.matrix); + + if(gradient->type==plutovg_gradient_type_linear) + { + data.linear.x1 = gradient->values[0]; + data.linear.y1 = gradient->values[1]; + data.linear.x2 = gradient->values[2]; + data.linear.y2 = gradient->values[3]; + blend_linear_gradient(pluto->surface, state->op, rle, &data); + } + else + { + data.radial.cx = gradient->values[0]; + data.radial.cy = gradient->values[1]; + data.radial.cr = gradient->values[2]; + data.radial.fx = gradient->values[3]; + data.radial.fy = gradient->values[4]; + data.radial.fr = gradient->values[5]; + blend_radial_gradient(pluto->surface, state->op, rle, &data); + } +} + +void plutovg_blend_texture(plutovg_t* pluto, const plutovg_rle_t* rle, const plutovg_texture_t* texture) +{ + plutovg_state_t* state = pluto->state; + texture_data_t data; + data.data = texture->surface->data; + data.width = texture->surface->width; + data.height = texture->surface->height; + data.stride = texture->surface->stride; + data.const_alpha = (int)(state->opacity * texture->opacity * 256.0); + + data.matrix = texture->matrix; + plutovg_matrix_multiply(&data.matrix, &data.matrix, &state->matrix); + plutovg_matrix_invert(&data.matrix); + + const plutovg_matrix_t* matrix = &data.matrix; + int translating = (matrix->m00==1.0 && matrix->m10==0.0 && matrix->m01==0.0 && matrix->m11==1.0); + if(translating) + { + if(texture->type==plutovg_texture_type_plain) + blend_untransformed_argb(pluto->surface, state->op, rle, &data); + else + blend_untransformed_tiled_argb(pluto->surface, state->op, rle, &data); + } + else + { + if(texture->type==plutovg_texture_type_plain) + blend_transformed_argb(pluto->surface, state->op, rle, &data); + else + blend_transformed_tiled_argb(pluto->surface, state->op, rle, &data); + } +} diff --git a/3rdparty/plutovg/plutovg-dash.c b/3rdparty/plutovg/plutovg-dash.c new file mode 100644 index 000000000..40a802702 --- /dev/null +++ b/3rdparty/plutovg/plutovg-dash.c @@ -0,0 +1,114 @@ +#include "plutovg-private.h" + +#include + +plutovg_dash_t* plutovg_dash_create(double offset, const double* data, int size) +{ + if(data==NULL || size==0) + return NULL; + + plutovg_dash_t* dash = malloc(sizeof(plutovg_dash_t)); + dash->offset = offset; + dash->data = malloc((size_t)size * sizeof(double)); + dash->size = size; + memcpy(dash->data, data, (size_t)size * sizeof(double)); + return dash; +} + +plutovg_dash_t* plutovg_dash_clone(const plutovg_dash_t* dash) +{ + if(dash==NULL) + return NULL; + + return plutovg_dash_create(dash->offset, dash->data, dash->size); +} + +void plutovg_dash_destroy(plutovg_dash_t* dash) +{ + if(dash==NULL) + return; + + free(dash->data); + free(dash); +} + +plutovg_path_t* plutovg_dash_path(const plutovg_dash_t* dash, const plutovg_path_t* path) +{ + if(dash->data==NULL || dash->size==0) + return plutovg_path_clone(path); + + int toggle = 1; + int offset = 0; + double phase = dash->offset; + while(phase >= dash->data[offset]) + { + toggle = !toggle; + phase -= dash->data[offset]; + offset += 1; + if(offset == dash->size) offset = 0; + } + + plutovg_path_t* flat = plutovg_path_clone_flat(path); + plutovg_path_t* result = plutovg_path_create(); + plutovg_array_ensure(result->elements, flat->elements.size); + plutovg_array_ensure(result->points, flat->points.size); + + plutovg_path_element_t* elements = flat->elements.data; + plutovg_path_element_t* end = elements + flat->elements.size; + plutovg_point_t* points = flat->points.data; + while(elements < end) + { + int itoggle = toggle; + int ioffset = offset; + double iphase = phase; + + double x0 = points->x; + double y0 = points->y; + + if(itoggle) + plutovg_path_move_to(result, x0, y0); + + ++elements; + ++points; + + while(elements < end && *elements==plutovg_path_element_line_to) + { + double dx = points->x - x0; + double dy = points->y - y0; + double dist0 = sqrt(dx*dx + dy*dy); + double dist1 = 0; + + while(dist0 - dist1 > dash->data[ioffset] - iphase) + { + dist1 += dash->data[ioffset] - iphase; + double a = dist1 / dist0; + double x = x0 + a * dx; + double y = y0 + a * dy; + + if(itoggle) + plutovg_path_line_to(result, x, y); + else + plutovg_path_move_to(result, x, y); + + itoggle = !itoggle; + iphase = 0; + ioffset += 1; + if(ioffset == dash->size) ioffset = 0; + } + + iphase += dist0 - dist1; + + x0 = points->x; + y0 = points->y; + + if(itoggle) + plutovg_path_line_to(result, x0, y0); + + ++elements; + ++points; + } + } + + plutovg_path_destroy(flat); + return result; +} diff --git a/3rdparty/plutovg/plutovg-geometry.c b/3rdparty/plutovg/plutovg-geometry.c new file mode 100644 index 000000000..ec0cd2e63 --- /dev/null +++ b/3rdparty/plutovg/plutovg-geometry.c @@ -0,0 +1,582 @@ +#include "plutovg-private.h" + +#include + +void plutovg_rect_init(plutovg_rect_t* rect, double x, double y, double w, double h) +{ + rect->x = x; + rect->y = y; + rect->w = w; + rect->h = h; +} + +void plutovg_rect_init_zero(plutovg_rect_t* rect) +{ + rect->x = 0.0; + rect->y = 0.0; + rect->w = 0.0; + rect->h = 0.0; +} + +void plutovg_matrix_init(plutovg_matrix_t* matrix, double m00, double m10, double m01, double m11, double m02, double m12) +{ + matrix->m00 = m00; matrix->m10 = m10; + matrix->m01 = m01; matrix->m11 = m11; + matrix->m02 = m02; matrix->m12 = m12; +} + +void plutovg_matrix_init_identity(plutovg_matrix_t* matrix) +{ + matrix->m00 = 1.0; matrix->m10 = 0.0; + matrix->m01 = 0.0; matrix->m11 = 1.0; + matrix->m02 = 0.0; matrix->m12 = 0.0; +} + +void plutovg_matrix_init_translate(plutovg_matrix_t* matrix, double x, double y) +{ + plutovg_matrix_init(matrix, 1.0, 0.0, 0.0, 1.0, x, y); +} + +void plutovg_matrix_init_scale(plutovg_matrix_t* matrix, double x, double y) +{ + plutovg_matrix_init(matrix, x, 0.0, 0.0, y, 0.0, 0.0); +} + +void plutovg_matrix_init_shear(plutovg_matrix_t* matrix, double x, double y) +{ + plutovg_matrix_init(matrix, 1.0, tan(y), tan(x), 1.0, 0.0, 0.0); +} + +void plutovg_matrix_init_rotate(plutovg_matrix_t* matrix, double radians, double x, double y) +{ + double c = cos(radians); + double s = sin(radians); + + double cx = x * (1 - c) + y * s; + double cy = y * (1 - c) - x * s; + + plutovg_matrix_init(matrix, c, s, -s, c, cx, cy); +} + +void plutovg_matrix_translate(plutovg_matrix_t* matrix, double x, double y) +{ + plutovg_matrix_t m; + plutovg_matrix_init_translate(&m, x, y); + plutovg_matrix_multiply(matrix, &m, matrix); +} + +void plutovg_matrix_scale(plutovg_matrix_t* matrix, double x, double y) +{ + plutovg_matrix_t m; + plutovg_matrix_init_scale(&m, x, y); + plutovg_matrix_multiply(matrix, &m, matrix); +} + +void plutovg_matrix_shear(plutovg_matrix_t* matrix, double x, double y) +{ + plutovg_matrix_t m; + plutovg_matrix_init_shear(&m, x, y); + plutovg_matrix_multiply(matrix, &m, matrix); +} + +void plutovg_matrix_rotate(plutovg_matrix_t* matrix, double radians, double x, double y) +{ + plutovg_matrix_t m; + plutovg_matrix_init_rotate(&m, radians, x, y); + plutovg_matrix_multiply(matrix, &m, matrix); +} + +void plutovg_matrix_multiply(plutovg_matrix_t* matrix, const plutovg_matrix_t* a, const plutovg_matrix_t* b) +{ + double m00 = a->m00 * b->m00 + a->m10 * b->m01; + double m10 = a->m00 * b->m10 + a->m10 * b->m11; + double m01 = a->m01 * b->m00 + a->m11 * b->m01; + double m11 = a->m01 * b->m10 + a->m11 * b->m11; + double m02 = a->m02 * b->m00 + a->m12 * b->m01 + b->m02; + double m12 = a->m02 * b->m10 + a->m12 * b->m11 + b->m12; + + plutovg_matrix_init(matrix, m00, m10, m01, m11, m02, m12); +} + +int plutovg_matrix_invert(plutovg_matrix_t* matrix) +{ + double det = (matrix->m00 * matrix->m11 - matrix->m10 * matrix->m01); + if(det == 0.0) + return 0; + + double inv_det = 1.0 / det; + double m00 = matrix->m00 * inv_det; + double m10 = matrix->m10 * inv_det; + double m01 = matrix->m01 * inv_det; + double m11 = matrix->m11 * inv_det; + double m02 = (matrix->m01 * matrix->m12 - matrix->m11 * matrix->m02) * inv_det; + double m12 = (matrix->m10 * matrix->m02 - matrix->m00 * matrix->m12) * inv_det; + + plutovg_matrix_init(matrix, m11, -m10, -m01, m00, m02, m12); + return 1; +} + +void plutovg_matrix_map(const plutovg_matrix_t* matrix, double x, double y, double* _x, double* _y) +{ + *_x = x * matrix->m00 + y * matrix->m01 + matrix->m02; + *_y = x * matrix->m10 + y * matrix->m11 + matrix->m12; +} + +void plutovg_matrix_map_point(const plutovg_matrix_t* matrix, const plutovg_point_t* src, plutovg_point_t* dst) +{ + plutovg_matrix_map(matrix, src->x, src->y, &dst->x, &dst->y); +} + +void plutovg_matrix_map_rect(const plutovg_matrix_t* matrix, const plutovg_rect_t* src, plutovg_rect_t* dst) +{ + plutovg_point_t p[4]; + p[0].x = src->x; + p[0].y = src->y; + p[1].x = src->x + src->w; + p[1].y = src->y; + p[2].x = src->x + src->w; + p[2].y = src->y + src->h; + p[3].x = src->x; + p[3].y = src->y + src->h; + + plutovg_matrix_map_point(matrix, &p[0], &p[0]); + plutovg_matrix_map_point(matrix, &p[1], &p[1]); + plutovg_matrix_map_point(matrix, &p[2], &p[2]); + plutovg_matrix_map_point(matrix, &p[3], &p[3]); + + double l = p[0].x; + double t = p[0].y; + double r = p[0].x; + double b = p[0].y; + + for(int i = 0;i < 4;i++) + { + if(p[i].x < l) l = p[i].x; + if(p[i].x > r) r = p[i].x; + if(p[i].y < t) t = p[i].y; + if(p[i].y > b) b = p[i].y; + } + + dst->x = l; + dst->y = t; + dst->w = r - l; + dst->h = b - t; +} + +plutovg_path_t* plutovg_path_create(void) +{ + plutovg_path_t* path = malloc(sizeof(plutovg_path_t)); + path->ref = 1; + path->contours = 0; + path->start.x = 0.0; + path->start.y = 0.0; + plutovg_array_init(path->elements); + plutovg_array_init(path->points); + return path; +} + +plutovg_path_t* plutovg_path_reference(plutovg_path_t* path) +{ + ++path->ref; + return path; +} + +void plutovg_path_destroy(plutovg_path_t* path) +{ + if(path==NULL) + return; + + if(--path->ref==0) + { + free(path->elements.data); + free(path->points.data); + free(path); + } +} + +int plutovg_path_get_reference_count(const plutovg_path_t* path) +{ + return path->ref; +} + +void plutovg_path_move_to(plutovg_path_t* path, double x, double y) +{ + plutovg_array_ensure(path->elements, 1); + plutovg_array_ensure(path->points, 1); + + path->elements.data[path->elements.size] = plutovg_path_element_move_to; + plutovg_point_t* points = path->points.data + path->points.size; + points[0].x = x; + points[0].y = y; + + path->elements.size += 1; + path->points.size += 1; + path->contours += 1; + + path->start.x = x; + path->start.y = y; +} + +void plutovg_path_line_to(plutovg_path_t* path, double x, double y) +{ + plutovg_array_ensure(path->elements, 1); + plutovg_array_ensure(path->points, 1); + + path->elements.data[path->elements.size] = plutovg_path_element_line_to; + plutovg_point_t* points = path->points.data + path->points.size; + points[0].x = x; + points[0].y = y; + + path->elements.size += 1; + path->points.size += 1; +} + +void plutovg_path_quad_to(plutovg_path_t* path, double x1, double y1, double x2, double y2) +{ + double x, y; + plutovg_path_get_current_point(path, &x, &y); + + double cx = 2.0 / 3.0 * x1 + 1.0 / 3.0 * x; + double cy = 2.0 / 3.0 * y1 + 1.0 / 3.0 * y; + double cx1 = 2.0 / 3.0 * x1 + 1.0 / 3.0 * x2; + double cy1 = 2.0 / 3.0 * y1 + 1.0 / 3.0 * y2; + plutovg_path_cubic_to(path, cx, cy, cx1, cy1, x2, y2); +} + +void plutovg_path_cubic_to(plutovg_path_t* path, double x1, double y1, double x2, double y2, double x3, double y3) +{ + plutovg_array_ensure(path->elements, 1); + plutovg_array_ensure(path->points, 3); + + path->elements.data[path->elements.size] = plutovg_path_element_cubic_to; + plutovg_point_t* points = path->points.data + path->points.size; + points[0].x = x1; + points[0].y = y1; + points[1].x = x2; + points[1].y = y2; + points[2].x = x3; + points[2].y = y3; + + path->elements.size += 1; + path->points.size += 3; +} + +void plutovg_path_close(plutovg_path_t* path) +{ + if(path->elements.size == 0) + return; + + if(path->elements.data[path->elements.size - 1] == plutovg_path_element_close) + return; + + plutovg_array_ensure(path->elements, 1); + plutovg_array_ensure(path->points, 1); + + path->elements.data[path->elements.size] = plutovg_path_element_close; + plutovg_point_t* points = path->points.data + path->points.size; + points[0].x = path->start.x; + points[0].y = path->start.y; + + path->elements.size += 1; + path->points.size += 1; +} + +static inline void rel_to_abs(const plutovg_path_t* path, double* x, double* y) +{ + double _x, _y; + plutovg_path_get_current_point(path, &_x, &_y); + + *x += _x; + *y += _y; +} + +void plutovg_path_rel_move_to(plutovg_path_t* path, double x, double y) +{ + rel_to_abs(path, &x, &y); + plutovg_path_move_to(path, x, y); +} + +void plutovg_path_rel_line_to(plutovg_path_t* path, double x, double y) +{ + rel_to_abs(path, &x, &y); + plutovg_path_line_to(path, x, y); +} + +void plutovg_path_rel_quad_to(plutovg_path_t* path, double x1, double y1, double x2, double y2) +{ + rel_to_abs(path, &x1, &y1); + rel_to_abs(path, &x2, &y2); + plutovg_path_quad_to(path, x1, y1, x2, y2); +} + +void plutovg_path_rel_cubic_to(plutovg_path_t* path, double x1, double y1, double x2, double y2, double x3, double y3) +{ + rel_to_abs(path, &x1, &y1); + rel_to_abs(path, &x2, &y2); + rel_to_abs(path, &x3, &y3); + plutovg_path_cubic_to(path, x1, y1, x2, y2, x3, y3); +} + +void plutovg_path_add_rect(plutovg_path_t* path, double x, double y, double w, double h) +{ + plutovg_path_move_to(path, x, y); + plutovg_path_line_to(path, x + w, y); + plutovg_path_line_to(path, x + w, y + h); + plutovg_path_line_to(path, x, y + h); + plutovg_path_line_to(path, x, y); + plutovg_path_close(path); +} + +#define KAPPA 0.5522847498 +void plutovg_path_add_round_rect(plutovg_path_t* path, double x, double y, double w, double h, double rx, double ry) +{ + double right = x + w; + double bottom = y + h; + + double cpx = rx * KAPPA; + double cpy = ry * KAPPA; + + plutovg_path_move_to(path, x, y+ry); + plutovg_path_cubic_to(path, x, y+ry-cpy, x+rx-cpx, y, x+rx, y); + plutovg_path_line_to(path, right-rx, y); + plutovg_path_cubic_to(path, right-rx+cpx, y, right, y+ry-cpy, right, y+ry); + plutovg_path_line_to(path, right, bottom-ry); + plutovg_path_cubic_to(path, right, bottom-ry+cpy, right-rx+cpx, bottom, right-rx, bottom); + plutovg_path_line_to(path, x+rx, bottom); + plutovg_path_cubic_to(path, x+rx-cpx, bottom, x, bottom-ry+cpy, x, bottom-ry); + plutovg_path_line_to(path, x, y+ry); + plutovg_path_close(path); +} + +void plutovg_path_add_ellipse(plutovg_path_t* path, double cx, double cy, double rx, double ry) +{ + double left = cx - rx; + double top = cy - ry; + double right = cx + rx; + double bottom = cy + ry; + + double cpx = rx * KAPPA; + double cpy = ry * KAPPA; + + plutovg_path_move_to(path, cx, top); + plutovg_path_cubic_to(path, cx+cpx, top, right, cy-cpy, right, cy); + plutovg_path_cubic_to(path, right, cy+cpy, cx+cpx, bottom, cx, bottom); + plutovg_path_cubic_to(path, cx-cpx, bottom, left, cy+cpy, left, cy); + plutovg_path_cubic_to(path, left, cy-cpy, cx-cpx, top, cx, top); + plutovg_path_close(path); +} + +void plutovg_path_add_circle(plutovg_path_t* path, double cx, double cy, double r) +{ + plutovg_path_add_ellipse(path, cx, cy, r, r); +} + +void plutovg_path_add_path(plutovg_path_t* path, const plutovg_path_t* source, const plutovg_matrix_t* matrix) +{ + plutovg_array_ensure(path->elements, source->elements.size); + plutovg_array_ensure(path->points, source->points.size); + + plutovg_point_t* points = path->points.data + path->points.size; + const plutovg_point_t* data = source->points.data; + const plutovg_point_t* end = data + source->points.size; + while(data < end) + { + if(matrix) + plutovg_matrix_map_point(matrix, data, points); + else + memcpy(points, data, sizeof(plutovg_point_t)); + + points += 1; + data += 1; + } + + plutovg_path_element_t* elements = path->elements.data + path->elements.size; + memcpy(elements, source->elements.data, (size_t)source->elements.size * sizeof(plutovg_path_element_t)); + + path->elements.size += source->elements.size; + path->points.size += source->points.size; + path->contours += source->contours; + path->start = source->start; +} + +void plutovg_path_transform(plutovg_path_t* path, const plutovg_matrix_t* matrix) +{ + plutovg_point_t* points = path->points.data; + plutovg_point_t* end = points + path->points.size; + while(points < end) + { + plutovg_matrix_map_point(matrix, points, points); + points += 1; + } +} + +void plutovg_path_get_current_point(const plutovg_path_t* path, double* x, double* y) +{ + *x = 0.0; + *y = 0.0; + + if(path->points.size == 0) + return; + + *x = path->points.data[path->points.size - 1].x; + *y = path->points.data[path->points.size - 1].y; +} + +int plutovg_path_get_element_count(const plutovg_path_t* path) +{ + return path->elements.size; +} + +plutovg_path_element_t* plutovg_path_get_elements(const plutovg_path_t* path) +{ + return path->elements.data; +} + +int plutovg_path_get_point_count(const plutovg_path_t* path) +{ + return path->points.size; +} + +plutovg_point_t* plutovg_path_get_points(const plutovg_path_t* path) +{ + return path->points.data; +} + +void plutovg_path_clear(plutovg_path_t* path) +{ + path->elements.size = 0; + path->points.size = 0; + path->contours = 0; + path->start.x = 0.0; + path->start.y = 0.0; +} + +int plutovg_path_empty(const plutovg_path_t* path) +{ + return path->elements.size == 0; +} + +plutovg_path_t* plutovg_path_clone(const plutovg_path_t* path) +{ + plutovg_path_t* result = plutovg_path_create(); + plutovg_array_ensure(result->elements, path->elements.size); + plutovg_array_ensure(result->points, path->points.size); + + memcpy(result->elements.data, path->elements.data, (size_t)path->elements.size * sizeof(plutovg_path_element_t)); + memcpy(result->points.data, path->points.data, (size_t)path->points.size * sizeof(plutovg_point_t)); + + result->elements.size = path->elements.size; + result->points.size = path->points.size; + result->contours = path->contours; + result->start = path->start; + return result; +} + +typedef struct { + double x1; double y1; + double x2; double y2; + double x3; double y3; + double x4; double y4; +} bezier_t; + +static inline void split(const bezier_t* b, bezier_t* first, bezier_t* second) +{ + double c = (b->x2 + b->x3) * 0.5; + first->x2 = (b->x1 + b->x2) * 0.5; + second->x3 = (b->x3 + b->x4) * 0.5; + first->x1 = b->x1; + second->x4 = b->x4; + first->x3 = (first->x2 + c) * 0.5; + second->x2 = (second->x3 + c) * 0.5; + first->x4 = second->x1 = (first->x3 + second->x2) * 0.5; + + c = (b->y2 + b->y3) * 0.5; + first->y2 = (b->y1 + b->y2) * 0.5; + second->y3 = (b->y3 + b->y4) * 0.5; + first->y1 = b->y1; + second->y4 = b->y4; + first->y3 = (first->y2 + c) * 0.5; + second->y2 = (second->y3 + c) * 0.5; + first->y4 = second->y1 = (first->y3 + second->y2) * 0.5; +} + +static void flatten(plutovg_path_t* path, const plutovg_point_t* p0, const plutovg_point_t* p1, const plutovg_point_t* p2, const plutovg_point_t* p3) +{ + bezier_t beziers[32]; + beziers[0].x1 = p0->x; + beziers[0].y1 = p0->y; + beziers[0].x2 = p1->x; + beziers[0].y2 = p1->y; + beziers[0].x3 = p2->x; + beziers[0].y3 = p2->y; + beziers[0].x4 = p3->x; + beziers[0].y4 = p3->y; + + const double threshold = 0.25; + + bezier_t* b = beziers; + while(b >= beziers) + { + double y4y1 = b->y4 - b->y1; + double x4x1 = b->x4 - b->x1; + double l = fabs(x4x1) + fabs(y4y1); + double d; + if(l > 1.0) + { + d = fabs((x4x1)*(b->y1 - b->y2) - (y4y1)*(b->x1 - b->x2)) + fabs((x4x1)*(b->y1 - b->y3) - (y4y1)*(b->x1 - b->x3)); + } + else + { + d = fabs(b->x1 - b->x2) + fabs(b->y1 - b->y2) + fabs(b->x1 - b->x3) + fabs(b->y1 - b->y3); + l = 1.0; + } + + if(d < threshold*l || b == beziers + 31) + { + plutovg_path_line_to(path, b->x4, b->y4); + --b; + } + else + { + split(b, b+1, b); + ++b; + } + } +} + +plutovg_path_t* plutovg_path_clone_flat(const plutovg_path_t* path) +{ + plutovg_path_t* result = plutovg_path_create(); + plutovg_array_ensure(result->elements, path->elements.size); + plutovg_array_ensure(result->points, path->points.size); + plutovg_point_t* points = path->points.data; + + for(int i = 0;i < path->elements.size;i++) + { + switch(path->elements.data[i]) + { + case plutovg_path_element_move_to: + plutovg_path_move_to(result, points[0].x, points[0].y); + points += 1; + break; + case plutovg_path_element_line_to: + plutovg_path_line_to(result, points[0].x, points[0].y); + points += 1; + break; + case plutovg_path_element_close: + plutovg_path_line_to(result, points[0].x, points[0].y); + points += 1; + break; + case plutovg_path_element_cubic_to: + { + plutovg_point_t p0; + plutovg_path_get_current_point(result, &p0.x, &p0.y); + flatten(result, &p0, points, points + 1, points + 2); + points += 3; + break; + } + } + } + + return result; +} diff --git a/3rdparty/plutovg/plutovg-paint.c b/3rdparty/plutovg/plutovg-paint.c new file mode 100644 index 000000000..afae6e2b7 --- /dev/null +++ b/3rdparty/plutovg/plutovg-paint.c @@ -0,0 +1,374 @@ +#include "plutovg-private.h" + +void plutovg_color_init_rgb(plutovg_color_t* color, double r, double g, double b) +{ + plutovg_color_init_rgba(color, r, g, b, 1.0); +} + +void plutovg_color_init_rgba(plutovg_color_t* color, double r, double g, double b, double a) +{ + color->r = r; + color->g = g; + color->b = b; + color->a = a; +} + +plutovg_gradient_t* plutovg_gradient_create_linear(double x1, double y1, double x2, double y2) +{ + plutovg_gradient_t* gradient = malloc(sizeof(plutovg_gradient_t)); + gradient->ref = 1; + gradient->type = plutovg_gradient_type_linear; + gradient->spread = plutovg_spread_method_pad; + gradient->opacity = 1.0; + plutovg_array_init(gradient->stops); + plutovg_matrix_init_identity(&gradient->matrix); + plutovg_gradient_set_values_linear(gradient, x1, y1, x2, y2); + return gradient; +} + +plutovg_gradient_t* plutovg_gradient_create_radial(double cx, double cy, double cr, double fx, double fy, double fr) +{ + plutovg_gradient_t* gradient = malloc(sizeof(plutovg_gradient_t)); + gradient->ref = 1; + gradient->type = plutovg_gradient_type_radial; + gradient->spread = plutovg_spread_method_pad; + gradient->opacity = 1.0; + plutovg_array_init(gradient->stops); + plutovg_matrix_init_identity(&gradient->matrix); + plutovg_gradient_set_values_radial(gradient, cx, cy, cr, fx, fy, fr); + return gradient; +} + +plutovg_gradient_t* plutovg_gradient_reference(plutovg_gradient_t* gradient) +{ + ++gradient->ref; + return gradient; +} + +void plutovg_gradient_destroy(plutovg_gradient_t* gradient) +{ + if(gradient==NULL) + return; + + if(--gradient->ref==0) + { + free(gradient->stops.data); + free(gradient); + } +} + +int plutovg_gradient_get_reference_count(const plutovg_gradient_t* gradient) +{ + return gradient->ref; +} + +void plutovg_gradient_set_type(plutovg_gradient_t* gradient, plutovg_gradient_type_t type) +{ + gradient->type = type; +} + +plutovg_gradient_type_t plutovg_gradient_get_type(const plutovg_gradient_t* gradient) +{ + return gradient->type; +} + +void plutovg_gradient_set_spread(plutovg_gradient_t* gradient, plutovg_spread_method_t spread) +{ + gradient->spread = spread; +} + +plutovg_spread_method_t plutovg_gradient_get_spread(const plutovg_gradient_t* gradient) +{ + return gradient->spread; +} + +void plutovg_gradient_set_matrix(plutovg_gradient_t* gradient, const plutovg_matrix_t* matrix) +{ + gradient->matrix = *matrix; +} + +void plutovg_gradient_get_matrix(const plutovg_gradient_t* gradient, plutovg_matrix_t* matrix) +{ + *matrix = gradient->matrix; +} + +void plutovg_gradient_add_stop_rgb(plutovg_gradient_t* gradient, double offset, double r, double g, double b) +{ + plutovg_gradient_add_stop_rgba(gradient, offset, r, g, b, 1.0); +} + +void plutovg_gradient_add_stop_rgba(plutovg_gradient_t* gradient, double offset, double r, double g, double b, double a) +{ + plutovg_array_ensure(gradient->stops, 1); + plutovg_gradient_stop_t* stops = gradient->stops.data; + int nstops = gradient->stops.size; + int i; + for(i = 0;i < nstops;i++) + { + if(offset < stops[i].offset) + { + memmove(&stops[i+1], &stops[i], (size_t)(nstops - i) * sizeof(plutovg_gradient_stop_t)); + break; + } + } + + stops[i].offset = offset; + stops[i].color.r = r; + stops[i].color.g = g; + stops[i].color.b = b; + stops[i].color.a = a; + + gradient->stops.size++; +} + +void plutovg_gradient_add_stop(plutovg_gradient_t* gradient, const plutovg_gradient_stop_t* stop) +{ + plutovg_gradient_add_stop_rgba(gradient, stop->offset, stop->color.r, stop->color.g, stop->color.b, stop->color.a); +} + +void plutovg_gradient_clear_stops(plutovg_gradient_t* gradient) +{ + gradient->stops.size = 0; +} + +int plutovg_gradient_get_stop_count(const plutovg_gradient_t* gradient) +{ + return gradient->stops.size; +} + +plutovg_gradient_stop_t* plutovg_gradient_get_stops(const plutovg_gradient_t* gradient) +{ + return gradient->stops.data; +} + +void plutovg_gradient_get_values_linear(const plutovg_gradient_t* gradient, double* x1, double* y1, double* x2, double* y2) +{ + *x1 = gradient->values[0]; + *y1 = gradient->values[1]; + *x2 = gradient->values[2]; + *y2 = gradient->values[3]; +} + +void plutovg_gradient_get_values_radial(const plutovg_gradient_t* gradient, double* cx, double* cy, double* cr, double* fx, double* fy, double* fr) +{ + *cx = gradient->values[0]; + *cy = gradient->values[1]; + *cr = gradient->values[2]; + *fx = gradient->values[3]; + *fy = gradient->values[4]; + *fr = gradient->values[5]; +} + +void plutovg_gradient_set_values_linear(plutovg_gradient_t* gradient, double x1, double y1, double x2, double y2) +{ + gradient->values[0] = x1; + gradient->values[1] = y1; + gradient->values[2] = x2; + gradient->values[3] = y2; +} + +void plutovg_gradient_set_values_radial(plutovg_gradient_t* gradient, double cx, double cy, double cr, double fx, double fy, double fr) +{ + gradient->values[0] = cx; + gradient->values[1] = cy; + gradient->values[2] = cr; + gradient->values[3] = fx; + gradient->values[4] = fy; + gradient->values[5] = fr; +} + +void plutovg_gradient_set_opacity(plutovg_gradient_t* gradient, double opacity) +{ + gradient->opacity = opacity; +} + +double plutovg_gradient_get_opacity(const plutovg_gradient_t* gradient) +{ + return gradient->opacity; +} + +plutovg_texture_t* plutovg_texture_create(plutovg_surface_t* surface) +{ + plutovg_texture_t* texture = malloc(sizeof(plutovg_texture_t)); + texture->ref = 1; + texture->type = plutovg_texture_type_plain; + texture->surface = plutovg_surface_reference(surface); + texture->opacity = 1.0; + plutovg_matrix_init_identity(&texture->matrix); + return texture; +} + +plutovg_texture_t* plutovg_texture_reference(plutovg_texture_t* texture) +{ + ++texture->ref; + return texture; +} + +void plutovg_texture_destroy(plutovg_texture_t* texture) +{ + if(texture==NULL) + return; + + if(--texture->ref==0) + { + plutovg_surface_destroy(texture->surface); + free(texture); + } +} + +int plutovg_texture_get_reference_count(const plutovg_texture_t* texture) +{ + return texture->ref; +} + +void plutovg_texture_set_type(plutovg_texture_t* texture, plutovg_texture_type_t type) +{ + texture->type = type; +} + +plutovg_texture_type_t plutovg_texture_get_type(const plutovg_texture_t* texture) +{ + return texture->type; +} + +void plutovg_texture_set_matrix(plutovg_texture_t* texture, const plutovg_matrix_t* matrix) +{ + memcpy(&texture->matrix, matrix, sizeof(plutovg_matrix_t)); +} + +void plutovg_texture_get_matrix(const plutovg_texture_t* texture, plutovg_matrix_t* matrix) +{ + memcpy(matrix, &texture->matrix, sizeof(plutovg_matrix_t)); +} + +void plutovg_texture_set_surface(plutovg_texture_t* texture, plutovg_surface_t* surface) +{ + surface = plutovg_surface_reference(surface); + plutovg_surface_destroy(texture->surface); + texture->surface = surface; +} + +plutovg_surface_t* plutovg_texture_get_surface(const plutovg_texture_t* texture) +{ + return texture->surface; +} + +void plutovg_texture_set_opacity(plutovg_texture_t* texture, double opacity) +{ + texture->opacity = opacity; +} + +double plutovg_texture_get_opacity(const plutovg_texture_t* texture) +{ + return texture->opacity; +} + +plutovg_paint_t* plutovg_paint_create_rgb(double r, double g, double b) +{ + return plutovg_paint_create_rgba(r, g, b, 1.0); +} + +plutovg_paint_t* plutovg_paint_create_rgba(double r, double g, double b, double a) +{ + plutovg_paint_t* paint = malloc(sizeof(plutovg_paint_t)); + paint->ref = 1; + paint->type = plutovg_paint_type_color; + paint->color = malloc(sizeof(plutovg_color_t)); + plutovg_color_init_rgba(paint->color, r, g, b, a); + return paint; +} + +plutovg_paint_t* plutovg_paint_create_linear(double x1, double y1, double x2, double y2) +{ + plutovg_gradient_t* gradient = plutovg_gradient_create_linear(x1, y1, x2, y2); + plutovg_paint_t* paint = plutovg_paint_create_gradient(gradient); + plutovg_gradient_destroy(gradient); + return paint; +} + +plutovg_paint_t* plutovg_paint_create_radial(double cx, double cy, double cr, double fx, double fy, double fr) +{ + plutovg_gradient_t* gradient = plutovg_gradient_create_radial(cx, cy, cr, fx, fy, fr); + plutovg_paint_t* paint = plutovg_paint_create_gradient(gradient); + plutovg_gradient_destroy(gradient); + return paint; +} + +plutovg_paint_t* plutovg_paint_create_for_surface(plutovg_surface_t* surface) +{ + plutovg_texture_t* texture = plutovg_texture_create(surface); + plutovg_paint_t* paint = plutovg_paint_create_texture(texture); + plutovg_texture_destroy(texture); + return paint; +} + +plutovg_paint_t* plutovg_paint_create_color(const plutovg_color_t* color) +{ + return plutovg_paint_create_rgba(color->r, color->g, color->b, color->a); +} + +plutovg_paint_t* plutovg_paint_create_gradient(plutovg_gradient_t* gradient) +{ + plutovg_paint_t* paint = malloc(sizeof(plutovg_paint_t)); + paint->ref = 1; + paint->type = plutovg_paint_type_gradient; + paint->gradient = plutovg_gradient_reference(gradient); + return paint; +} + +plutovg_paint_t* plutovg_paint_create_texture(plutovg_texture_t* texture) +{ + plutovg_paint_t* paint = malloc(sizeof(plutovg_paint_t)); + paint->ref = 1; + paint->type = plutovg_paint_type_texture; + paint->texture = plutovg_texture_reference(texture); + return paint; +} + +plutovg_paint_t* plutovg_paint_reference(plutovg_paint_t* paint) +{ + ++paint->ref; + return paint; +} + +void plutovg_paint_destroy(plutovg_paint_t* paint) +{ + if(paint==NULL) + return; + + if(--paint->ref==0) + { + if(paint->type==plutovg_paint_type_color) + free(paint->color); + if(paint->type==plutovg_paint_type_gradient) + plutovg_gradient_destroy(paint->gradient); + if(paint->type==plutovg_paint_type_texture) + plutovg_texture_destroy(paint->texture); + free(paint); + } +} + +int plutovg_paint_get_reference_count(const plutovg_paint_t* paint) +{ + return paint->ref; +} + +plutovg_paint_type_t plutovg_paint_get_type(const plutovg_paint_t* paint) +{ + return paint->type; +} + +plutovg_color_t* plutovg_paint_get_color(const plutovg_paint_t* paint) +{ + return paint->type==plutovg_paint_type_color?paint->color:NULL; +} + +plutovg_gradient_t* plutovg_paint_get_gradient(const plutovg_paint_t* paint) +{ + return paint->type==plutovg_paint_type_gradient?paint->gradient:NULL; +} + +plutovg_texture_t* plutovg_paint_get_texture(const plutovg_paint_t* paint) +{ + return paint->type==plutovg_paint_type_texture?paint->texture:NULL; +} diff --git a/3rdparty/plutovg/plutovg-private.h b/3rdparty/plutovg/plutovg-private.h new file mode 100644 index 000000000..b1025383c --- /dev/null +++ b/3rdparty/plutovg/plutovg-private.h @@ -0,0 +1,168 @@ +#ifndef PLUTOVG_PRIVATE_H +#define PLUTOVG_PRIVATE_H + +#include +#include +#include +#include + +#include "plutovg.h" + +struct plutovg_surface { + int ref; + unsigned char* data; + int owndata; + int width; + int height; + int stride; +}; + +struct plutovg_path { + int ref; + int contours; + plutovg_point_t start; + struct { + plutovg_path_element_t* data; + int size; + int capacity; + } elements; + struct { + plutovg_point_t* data; + int size; + int capacity; + } points; +}; + +struct plutovg_gradient { + int ref; + plutovg_gradient_type_t type; + plutovg_spread_method_t spread; + plutovg_matrix_t matrix; + double values[6]; + double opacity; + struct { + plutovg_gradient_stop_t* data; + int size; + int capacity; + } stops; +}; + +struct plutovg_texture { + int ref; + plutovg_texture_type_t type; + plutovg_surface_t* surface; + plutovg_matrix_t matrix; + double opacity; +}; + +struct plutovg_paint { + int ref; + plutovg_paint_type_t type; + union { + plutovg_color_t* color; + plutovg_gradient_t* gradient; + plutovg_texture_t* texture; + }; +}; + +typedef struct { + short x; + short y; + unsigned short len; + unsigned char coverage; +} plutovg_span_t; + +typedef struct { + struct { + plutovg_span_t* data; + int size; + int capacity; + } spans; + + int x; + int y; + int w; + int h; +} plutovg_rle_t; + +typedef struct { + double offset; + double* data; + int size; +} plutovg_dash_t; + +typedef struct { + double width; + double miterlimit; + plutovg_line_cap_t cap; + plutovg_line_join_t join; + plutovg_dash_t* dash; +} plutovg_stroke_data_t; + +typedef struct plutovg_state { + plutovg_rle_t* clippath; + plutovg_paint_t* source; + plutovg_matrix_t matrix; + plutovg_fill_rule_t winding; + plutovg_stroke_data_t stroke; + plutovg_operator_t op; + double opacity; + struct plutovg_state* next; +} plutovg_state_t; + +struct plutovg { + int ref; + plutovg_surface_t* surface; + plutovg_state_t* state; + plutovg_path_t* path; + plutovg_rle_t* rle; + plutovg_rle_t* clippath; + plutovg_rect_t clip; +}; + +plutovg_rle_t* plutovg_rle_create(void); +void plutovg_rle_destroy(plutovg_rle_t* rle); +void plutovg_rle_rasterize(plutovg_rle_t* rle, const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_rect_t* clip, const plutovg_stroke_data_t* stroke, plutovg_fill_rule_t winding); +plutovg_rle_t* plutovg_rle_intersection(const plutovg_rle_t* a, const plutovg_rle_t* b); +void plutovg_rle_clip_path(plutovg_rle_t* rle, const plutovg_rle_t* clip); +plutovg_rle_t* plutovg_rle_clone(const plutovg_rle_t* rle); +void plutovg_rle_clear(plutovg_rle_t* rle); + +plutovg_dash_t* plutovg_dash_create(double offset, const double* data, int size); +plutovg_dash_t* plutovg_dash_clone(const plutovg_dash_t* dash); +void plutovg_dash_destroy(plutovg_dash_t* dash); +plutovg_path_t* plutovg_dash_path(const plutovg_dash_t* dash, const plutovg_path_t* path); + +plutovg_state_t* plutovg_state_create(void); +plutovg_state_t* plutovg_state_clone(const plutovg_state_t* state); +void plutovg_state_destroy(plutovg_state_t* state); + +void plutovg_blend(plutovg_t* pluto, const plutovg_rle_t* rle); +void plutovg_blend_color(plutovg_t* pluto, const plutovg_rle_t* rle, const plutovg_color_t* color); +void plutovg_blend_gradient(plutovg_t* pluto, const plutovg_rle_t* rle, const plutovg_gradient_t* gradient); +void plutovg_blend_texture(plutovg_t* pluto, const plutovg_rle_t* rle, const plutovg_texture_t* texture); + +#define plutovg_alpha(c) ((c) >> 24) +#define plutovg_red(c) (((c) >> 16) & 0xff) +#define plutovg_green(c) (((c) >> 8) & 0xff) +#define plutovg_blue(c) (((c) >> 0) & 0xff) + +#define plutovg_array_init(array) \ + do { \ + array.data = NULL; \ + array.size = 0; \ + array.capacity = 0; \ + } while(0) + +#define plutovg_array_ensure(array, count) \ + do { \ + if(array.size + count > array.capacity) { \ + int capacity = array.size + count; \ + int newcapacity = array.capacity == 0 ? 8 : array.capacity; \ + while(newcapacity < capacity) { newcapacity *= 2; } \ + array.data = realloc(array.data, (size_t)newcapacity * sizeof(array.data[0])); \ + array.capacity = newcapacity; \ + } \ + } while(0) + +#endif // PLUTOVG_PRIVATE_H diff --git a/3rdparty/plutovg/plutovg-rle.c b/3rdparty/plutovg/plutovg-rle.c new file mode 100644 index 000000000..0472e460f --- /dev/null +++ b/3rdparty/plutovg/plutovg-rle.c @@ -0,0 +1,413 @@ +#include "plutovg-private.h" + +#include "sw_ft_raster.h" +#include "sw_ft_stroker.h" +#include "sw_ft_types.h" +#include "sw_ft_math.h" + +#include +#include + +static SW_FT_Outline* sw_ft_outline_create(int points, int contours) +{ + SW_FT_Outline* ft = malloc(sizeof(SW_FT_Outline)); + ft->points = malloc((size_t)(points + contours) * sizeof(SW_FT_Vector)); + ft->tags = malloc((size_t)(points + contours) * sizeof(char)); + ft->contours = malloc((size_t)contours * sizeof(short)); + ft->contours_flag = malloc((size_t)contours * sizeof(char)); + ft->n_points = ft->n_contours = 0; + ft->flags = 0x0; + return ft; +} + +static void sw_ft_outline_destroy(SW_FT_Outline* ft) +{ + free(ft->points); + free(ft->tags); + free(ft->contours); + free(ft->contours_flag); + free(ft); +} + +#define FT_COORD(x) (SW_FT_Pos)((x) * 64) +static void sw_ft_outline_move_to(SW_FT_Outline* ft, double x, double y) +{ + ft->points[ft->n_points].x = FT_COORD(x); + ft->points[ft->n_points].y = FT_COORD(y); + ft->tags[ft->n_points] = SW_FT_CURVE_TAG_ON; + if(ft->n_points) + { + ft->contours[ft->n_contours] = ft->n_points - 1; + ft->n_contours++; + } + + ft->contours_flag[ft->n_contours] = 1; + ft->n_points++; +} + +static void sw_ft_outline_line_to(SW_FT_Outline* ft, double x, double y) +{ + ft->points[ft->n_points].x = FT_COORD(x); + ft->points[ft->n_points].y = FT_COORD(y); + ft->tags[ft->n_points] = SW_FT_CURVE_TAG_ON; + ft->n_points++; +} + +static void sw_ft_outline_cubic_to(SW_FT_Outline* ft, double x1, double y1, double x2, double y2, double x3, double y3) +{ + ft->points[ft->n_points].x = FT_COORD(x1); + ft->points[ft->n_points].y = FT_COORD(y1); + ft->tags[ft->n_points] = SW_FT_CURVE_TAG_CUBIC; + ft->n_points++; + + ft->points[ft->n_points].x = FT_COORD(x2); + ft->points[ft->n_points].y = FT_COORD(y2); + ft->tags[ft->n_points] = SW_FT_CURVE_TAG_CUBIC; + ft->n_points++; + + ft->points[ft->n_points].x = FT_COORD(x3); + ft->points[ft->n_points].y = FT_COORD(y3); + ft->tags[ft->n_points] = SW_FT_CURVE_TAG_ON; + ft->n_points++; +} + +static void sw_ft_outline_close(SW_FT_Outline* ft) +{ + ft->contours_flag[ft->n_contours] = 0; + int index = ft->n_contours ? ft->contours[ft->n_contours - 1] + 1 : 0; + if(index == ft->n_points) + return; + + ft->points[ft->n_points].x = ft->points[index].x; + ft->points[ft->n_points].y = ft->points[index].y; + ft->tags[ft->n_points] = SW_FT_CURVE_TAG_ON; + ft->n_points++; +} + +static void sw_ft_outline_end(SW_FT_Outline* ft) +{ + if(ft->n_points) + { + ft->contours[ft->n_contours] = ft->n_points - 1; + ft->n_contours++; + } +} + +static SW_FT_Outline* sw_ft_outline_convert(const plutovg_path_t* path, const plutovg_matrix_t* matrix) +{ + SW_FT_Outline* outline = sw_ft_outline_create(path->points.size, path->contours); + plutovg_path_element_t* elements = path->elements.data; + plutovg_point_t* points = path->points.data; + plutovg_point_t p[3]; + for(int i = 0;i < path->elements.size;i++) + { + switch(elements[i]) + { + case plutovg_path_element_move_to: + plutovg_matrix_map_point(matrix, &points[0], &p[0]); + sw_ft_outline_move_to(outline, p[0].x, p[0].y); + points += 1; + break; + case plutovg_path_element_line_to: + plutovg_matrix_map_point(matrix, &points[0], &p[0]); + sw_ft_outline_line_to(outline, p[0].x, p[0].y); + points += 1; + break; + case plutovg_path_element_cubic_to: + plutovg_matrix_map_point(matrix, &points[0], &p[0]); + plutovg_matrix_map_point(matrix, &points[1], &p[1]); + plutovg_matrix_map_point(matrix, &points[2], &p[2]); + sw_ft_outline_cubic_to(outline, p[0].x, p[0].y, p[1].x, p[1].y, p[2].x, p[2].y); + points += 3; + break; + case plutovg_path_element_close: + sw_ft_outline_close(outline); + points += 1; + break; + } + } + + sw_ft_outline_end(outline); + return outline; +} + +static SW_FT_Outline* sw_ft_outline_convert_dash(const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_dash_t* dash) +{ + plutovg_path_t* dashed = plutovg_dash_path(dash, path); + SW_FT_Outline* outline = sw_ft_outline_convert(dashed, matrix); + plutovg_path_destroy(dashed); + return outline; +} + +static void generation_callback(int count, const SW_FT_Span* spans, void* user) +{ + plutovg_rle_t* rle = user; + plutovg_array_ensure(rle->spans, count); + plutovg_span_t* data = rle->spans.data + rle->spans.size; + memcpy(data, spans, (size_t)count * sizeof(plutovg_span_t)); + rle->spans.size += count; +} + +static void bbox_callback(int x, int y, int w, int h, void* user) +{ + plutovg_rle_t* rle = user; + rle->x = x; + rle->y = y; + rle->w = w; + rle->h = h; +} + +plutovg_rle_t* plutovg_rle_create(void) +{ + plutovg_rle_t* rle = malloc(sizeof(plutovg_rle_t)); + plutovg_array_init(rle->spans); + rle->x = 0; + rle->y = 0; + rle->w = 0; + rle->h = 0; + return rle; +} + +void plutovg_rle_destroy(plutovg_rle_t* rle) +{ + if(rle==NULL) + return; + + free(rle->spans.data); + free(rle); +} + +#define SQRT2 1.41421356237309504880 +void plutovg_rle_rasterize(plutovg_rle_t* rle, const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_rect_t* clip, const plutovg_stroke_data_t* stroke, plutovg_fill_rule_t winding) +{ + SW_FT_Raster_Params params; + params.flags = SW_FT_RASTER_FLAG_DIRECT | SW_FT_RASTER_FLAG_AA; + params.gray_spans = generation_callback; + params.bbox_cb = bbox_callback; + params.user = rle; + + if(clip) + { + params.flags |= SW_FT_RASTER_FLAG_CLIP; + params.clip_box.xMin = (SW_FT_Pos)clip->x; + params.clip_box.yMin = (SW_FT_Pos)clip->y; + params.clip_box.xMax = (SW_FT_Pos)(clip->x + clip->w); + params.clip_box.yMax = (SW_FT_Pos)(clip->y + clip->h); + } + + if(stroke) + { + SW_FT_Outline* outline = stroke->dash ? sw_ft_outline_convert_dash(path, matrix, stroke->dash) : sw_ft_outline_convert(path, matrix); + SW_FT_Stroker_LineCap ftCap; + SW_FT_Stroker_LineJoin ftJoin; + SW_FT_Fixed ftWidth; + SW_FT_Fixed ftMiterLimit; + + plutovg_point_t p1 = {0, 0}; + plutovg_point_t p2 = {SQRT2, SQRT2}; + plutovg_point_t p3; + + plutovg_matrix_map_point(matrix, &p1, &p1); + plutovg_matrix_map_point(matrix, &p2, &p2); + + p3.x = p2.x - p1.x; + p3.y = p2.y - p1.y; + + double scale = sqrt(p3.x*p3.x + p3.y*p3.y) / 2.0; + + ftWidth = (SW_FT_Fixed)(stroke->width * scale * 0.5 * (1 << 6)); + ftMiterLimit = (SW_FT_Fixed)(stroke->miterlimit * (1 << 16)); + + switch(stroke->cap) + { + case plutovg_line_cap_square: + ftCap = SW_FT_STROKER_LINECAP_SQUARE; + break; + case plutovg_line_cap_round: + ftCap = SW_FT_STROKER_LINECAP_ROUND; + break; + default: + ftCap = SW_FT_STROKER_LINECAP_BUTT; + break; + } + + switch(stroke->join) + { + case plutovg_line_join_bevel: + ftJoin = SW_FT_STROKER_LINEJOIN_BEVEL; + break; + case plutovg_line_join_round: + ftJoin = SW_FT_STROKER_LINEJOIN_ROUND; + break; + default: + ftJoin = SW_FT_STROKER_LINEJOIN_MITER_FIXED; + break; + } + + SW_FT_Stroker stroker; + SW_FT_Stroker_New(&stroker); + SW_FT_Stroker_Set(stroker, ftWidth, ftCap, ftJoin, ftMiterLimit); + SW_FT_Stroker_ParseOutline(stroker, outline); + + SW_FT_UInt points; + SW_FT_UInt contours; + SW_FT_Stroker_GetCounts(stroker, &points, &contours); + + SW_FT_Outline* strokeOutline = sw_ft_outline_create((int)points, (int)contours); + SW_FT_Stroker_Export(stroker, strokeOutline); + SW_FT_Stroker_Done(stroker); + + strokeOutline->flags = SW_FT_OUTLINE_NONE; + params.source = strokeOutline; + sw_ft_grays_raster.raster_render(NULL, ¶ms); + sw_ft_outline_destroy(outline); + sw_ft_outline_destroy(strokeOutline); + } + else + { + SW_FT_Outline* outline = sw_ft_outline_convert(path, matrix); + outline->flags = winding == plutovg_fill_rule_even_odd ? SW_FT_OUTLINE_EVEN_ODD_FILL : SW_FT_OUTLINE_NONE; + params.source = outline; + sw_ft_grays_raster.raster_render(NULL, ¶ms); + sw_ft_outline_destroy(outline); + } +} + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define DIV255(x) (((x) + ((x) >> 8) + 0x80) >> 8) +plutovg_rle_t* plutovg_rle_intersection(const plutovg_rle_t* a, const plutovg_rle_t* b) +{ + int count = MAX(a->spans.size, b->spans.size); + plutovg_rle_t* result = malloc(sizeof(plutovg_rle_t)); + plutovg_array_init(result->spans); + plutovg_array_ensure(result->spans, count); + + plutovg_span_t* a_spans = a->spans.data; + plutovg_span_t* a_end = a_spans + a->spans.size; + + plutovg_span_t* b_spans = b->spans.data; + plutovg_span_t* b_end = b_spans + b->spans.size; + + while(count && a_spans < a_end && b_spans < b_end) + { + if(b_spans->y > a_spans->y) + { + ++a_spans; + continue; + } + + if(a_spans->y != b_spans->y) + { + ++b_spans; + continue; + } + + int ax1 = a_spans->x; + int ax2 = ax1 + a_spans->len; + int bx1 = b_spans->x; + int bx2 = bx1 + b_spans->len; + + if(bx1 < ax1 && bx2 < ax1) + { + ++b_spans; + continue; + } + else if(ax1 < bx1 && ax2 < bx1) + { + ++a_spans; + continue; + } + + int x = MAX(ax1, bx1); + int len = MIN(ax2, bx2) - x; + if(len) + { + plutovg_span_t* span = result->spans.data + result->spans.size; + span->x = (short)x; + span->len = (unsigned short)len; + span->y = a_spans->y; + span->coverage = DIV255(a_spans->coverage * b_spans->coverage); + ++result->spans.size; + --count; + } + + if(ax2 < bx2) + { + ++a_spans; + } + else + { + ++b_spans; + } + } + + if(result->spans.size==0) + { + result->x = 0; + result->y = 0; + result->w = 0; + result->h = 0; + return result; + } + + plutovg_span_t* spans = result->spans.data; + int x1 = INT_MAX; + int y1 = spans[0].y; + int x2 = 0; + int y2 = spans[result->spans.size - 1].y; + for(int i = 0;i < result->spans.size;i++) + { + if(spans[i].x < x1) x1 = spans[i].x; + if(spans[i].x + spans[i].len > x2) x2 = spans[i].x + spans[i].len; + } + + result->x = x1; + result->y = y1; + result->w = x2 - x1; + result->h = y2 - y1 + 1; + return result; +} + +void plutovg_rle_clip_path(plutovg_rle_t* rle, const plutovg_rle_t* clip) +{ + if(rle==NULL || clip==NULL) + return; + + plutovg_rle_t* result = plutovg_rle_intersection(rle, clip); + plutovg_array_ensure(rle->spans, result->spans.size); + memcpy(rle->spans.data, result->spans.data, (size_t)result->spans.size * sizeof(plutovg_span_t)); + rle->spans.size = result->spans.size; + rle->x = result->x; + rle->y = result->y; + rle->w = result->w; + rle->h = result->h; + plutovg_rle_destroy(result); +} + +plutovg_rle_t* plutovg_rle_clone(const plutovg_rle_t* rle) +{ + if(rle==NULL) + return NULL; + + plutovg_rle_t* result = malloc(sizeof(plutovg_rle_t)); + plutovg_array_init(result->spans); + plutovg_array_ensure(result->spans, rle->spans.size); + + memcpy(result->spans.data, rle->spans.data, (size_t)rle->spans.size * sizeof(plutovg_span_t)); + result->spans.size = rle->spans.size; + result->x = rle->x; + result->y = rle->y; + result->w = rle->w; + result->h = rle->h; + return result; +} + +void plutovg_rle_clear(plutovg_rle_t* rle) +{ + rle->spans.size = 0; + rle->x = 0; + rle->y = 0; + rle->w = 0; + rle->h = 0; +} diff --git a/3rdparty/plutovg/plutovg.c b/3rdparty/plutovg/plutovg.c new file mode 100644 index 000000000..28e751952 --- /dev/null +++ b/3rdparty/plutovg/plutovg.c @@ -0,0 +1,497 @@ +#include "plutovg-private.h" + +plutovg_surface_t* plutovg_surface_create(int width, int height) +{ + plutovg_surface_t* surface = malloc(sizeof(plutovg_surface_t)); + surface->ref = 1; + surface->owndata = 1; + surface->data = calloc(1, (size_t)(width * height * 4)); + surface->width = width; + surface->height = height; + surface->stride = width * 4; + return surface; +} + +plutovg_surface_t* plutovg_surface_create_for_data(unsigned char* data, int width, int height, int stride) +{ + plutovg_surface_t* surface = malloc(sizeof(plutovg_surface_t)); + surface->ref = 1; + surface->owndata = 0; + surface->data = data; + surface->width = width; + surface->height = height; + surface->stride = stride; + return surface; +} + +plutovg_surface_t* plutovg_surface_reference(plutovg_surface_t* surface) +{ + ++surface->ref; + return surface; +} + +void plutovg_surface_destroy(plutovg_surface_t* surface) +{ + if(surface==NULL) + return; + + if(--surface->ref==0) + { + if(surface->owndata) + free(surface->data); + free(surface); + } +} + +int plutovg_surface_get_reference_count(const plutovg_surface_t* surface) +{ + return surface->ref; +} + +unsigned char* plutovg_surface_get_data(const plutovg_surface_t* surface) +{ + return surface->data; +} + +int plutovg_surface_get_width(const plutovg_surface_t* surface) +{ + return surface->width; +} + +int plutovg_surface_get_height(const plutovg_surface_t* surface) +{ + return surface->height; +} + +int plutovg_surface_get_stride(const plutovg_surface_t* surface) +{ + return surface->stride; +} + +plutovg_state_t* plutovg_state_create(void) +{ + plutovg_state_t* state = malloc(sizeof(plutovg_state_t)); + state->clippath = NULL; + state->source = plutovg_paint_create_rgb(0, 0, 0); + plutovg_matrix_init_identity(&state->matrix); + state->winding = plutovg_fill_rule_non_zero; + state->stroke.width = 1.0; + state->stroke.miterlimit = 4.0; + state->stroke.cap = plutovg_line_cap_butt; + state->stroke.join = plutovg_line_join_miter; + state->stroke.dash = NULL; + state->op = plutovg_operator_src_over; + state->opacity = 1.0; + state->next = NULL; + return state; +} + +plutovg_state_t* plutovg_state_clone(const plutovg_state_t* state) +{ + plutovg_state_t* newstate = malloc(sizeof(plutovg_state_t)); + newstate->clippath = plutovg_rle_clone(state->clippath); + newstate->source = plutovg_paint_reference(state->source); /** FIXME: **/ + newstate->matrix = state->matrix; + newstate->winding = state->winding; + newstate->stroke.width = state->stroke.width; + newstate->stroke.miterlimit = state->stroke.miterlimit; + newstate->stroke.cap = state->stroke.cap; + newstate->stroke.join = state->stroke.join; + newstate->stroke.dash = plutovg_dash_clone(state->stroke.dash); + newstate->op = state->op; + newstate->opacity = state->opacity; + newstate->next = NULL; + return newstate; +} + +void plutovg_state_destroy(plutovg_state_t* state) +{ + plutovg_rle_destroy(state->clippath); + plutovg_paint_destroy(state->source); + plutovg_dash_destroy(state->stroke.dash); + free(state); +} + +plutovg_t* plutovg_create(plutovg_surface_t* surface) +{ + plutovg_t* pluto = malloc(sizeof(plutovg_t)); + pluto->ref = 1; + pluto->surface = plutovg_surface_reference(surface); + pluto->state = plutovg_state_create(); + pluto->path = plutovg_path_create(); + pluto->rle = plutovg_rle_create(); + pluto->clippath = NULL; + pluto->clip.x = 0.0; + pluto->clip.y = 0.0; + pluto->clip.w = surface->width; + pluto->clip.h = surface->height; + return pluto; +} + +plutovg_t* plutovg_reference(plutovg_t* pluto) +{ + ++pluto->ref; + return pluto; +} + +void plutovg_destroy(plutovg_t* pluto) +{ + if(pluto==NULL) + return; + + if(--pluto->ref==0) + { + while(pluto->state) + { + plutovg_state_t* state = pluto->state; + pluto->state = state->next; + plutovg_state_destroy(state); + } + + plutovg_surface_destroy(pluto->surface); + plutovg_path_destroy(pluto->path); + plutovg_rle_destroy(pluto->rle); + plutovg_rle_destroy(pluto->clippath); + free(pluto); + } +} + +int plutovg_get_reference_count(const plutovg_t* pluto) +{ + return pluto->ref; +} + +void plutovg_save(plutovg_t* pluto) +{ + plutovg_state_t* newstate = plutovg_state_clone(pluto->state); + newstate->next = pluto->state; + pluto->state = newstate; +} + +void plutovg_restore(plutovg_t* pluto) +{ + plutovg_state_t* oldstate = pluto->state; + pluto->state = oldstate->next; + plutovg_state_destroy(oldstate); +} + +void plutovg_set_source_rgb(plutovg_t* pluto, double r, double g, double b) +{ + plutovg_set_source_rgba(pluto, r, g, b, 1.0); +} + +void plutovg_set_source_rgba(plutovg_t* pluto, double r, double g, double b, double a) +{ + plutovg_paint_t* source = plutovg_paint_create_rgba(r, g, b, a); + plutovg_set_source(pluto, source); + plutovg_paint_destroy(source); +} + +void plutovg_set_source_surface(plutovg_t* pluto, plutovg_surface_t* surface, double x, double y) +{ + plutovg_paint_t* source = plutovg_paint_create_for_surface(surface); + plutovg_texture_t* texture = plutovg_paint_get_texture(source); + plutovg_matrix_t matrix; + plutovg_matrix_init_translate(&matrix, x, y); + plutovg_texture_set_matrix(texture, &matrix); + plutovg_set_source(pluto, source); + plutovg_paint_destroy(source); +} + +void plutovg_set_source_color(plutovg_t* pluto, const plutovg_color_t* color) +{ + plutovg_set_source_rgba(pluto, color->r, color->g, color->b, color->a); +} + +void plutovg_set_source_gradient(plutovg_t* pluto, plutovg_gradient_t* gradient) +{ + plutovg_paint_t* source = plutovg_paint_create_gradient(gradient); + plutovg_set_source(pluto, source); + plutovg_paint_destroy(source); +} + +void plutovg_set_source_texture(plutovg_t* pluto, plutovg_texture_t* texture) +{ + plutovg_paint_t* source = plutovg_paint_create_texture(texture); + plutovg_set_source(pluto, source); + plutovg_paint_destroy(source); +} + +void plutovg_set_source(plutovg_t* pluto, plutovg_paint_t* source) +{ + source = plutovg_paint_reference(source); + plutovg_paint_destroy(pluto->state->source); + pluto->state->source = source; +} + +plutovg_paint_t* plutovg_get_source(const plutovg_t* pluto) +{ + return pluto->state->source; +} + +void plutovg_set_operator(plutovg_t* pluto, plutovg_operator_t op) +{ + pluto->state->op = op; +} + +void plutovg_set_opacity(plutovg_t* pluto, double opacity) +{ + pluto->state->opacity = opacity; +} + +void plutovg_set_fill_rule(plutovg_t* pluto, plutovg_fill_rule_t fill_rule) +{ + pluto->state->winding = fill_rule; +} + +plutovg_operator_t plutovg_get_operator(const plutovg_t* pluto) +{ + return pluto->state->op; +} + +double plutovg_get_opacity(const plutovg_t* pluto) +{ + return pluto->state->opacity; +} + +plutovg_fill_rule_t plutovg_get_fill_rule(const plutovg_t* pluto) +{ + return pluto->state->winding; +} + +void plutovg_set_line_width(plutovg_t* pluto, double width) +{ + pluto->state->stroke.width = width; +} + +void plutovg_set_line_cap(plutovg_t* pluto, plutovg_line_cap_t cap) +{ + pluto->state->stroke.cap = cap; +} + +void plutovg_set_line_join(plutovg_t* pluto, plutovg_line_join_t join) +{ + pluto->state->stroke.join = join; +} + +void plutovg_set_miter_limit(plutovg_t* pluto, double limit) +{ + pluto->state->stroke.miterlimit = limit; +} + +void plutovg_set_dash(plutovg_t* pluto, double offset, const double* data, int size) +{ + plutovg_dash_destroy(pluto->state->stroke.dash); + pluto->state->stroke.dash = plutovg_dash_create(offset, data, size); +} + +double plutovg_get_line_width(const plutovg_t* pluto) +{ + return pluto->state->stroke.width; +} + +plutovg_line_cap_t plutovg_get_line_cap(const plutovg_t* pluto) +{ + return pluto->state->stroke.cap; +} + +plutovg_line_join_t plutovg_get_line_join(const plutovg_t* pluto) +{ + return pluto->state->stroke.join; +} + +double plutovg_get_miter_limit(const plutovg_t* pluto) +{ + return pluto->state->stroke.miterlimit; +} + +void plutovg_translate(plutovg_t* pluto, double x, double y) +{ + plutovg_matrix_translate(&pluto->state->matrix, x, y); +} + +void plutovg_scale(plutovg_t* pluto, double x, double y) +{ + plutovg_matrix_scale(&pluto->state->matrix, x, y); +} + +void plutovg_rotate(plutovg_t* pluto, double radians, double x, double y) +{ + plutovg_matrix_rotate(&pluto->state->matrix, radians, x, y); +} + +void plutovg_transform(plutovg_t* pluto, const plutovg_matrix_t* matrix) +{ + plutovg_matrix_multiply(&pluto->state->matrix, matrix, &pluto->state->matrix); +} + +void plutovg_set_matrix(plutovg_t* pluto, const plutovg_matrix_t* matrix) +{ + pluto->state->matrix = *matrix; +} + +void plutovg_identity_matrix(plutovg_t* pluto) +{ + plutovg_matrix_init_identity(&pluto->state->matrix); +} + +void plutovg_get_matrix(const plutovg_t* pluto, plutovg_matrix_t* matrix) +{ + *matrix = pluto->state->matrix; +} + +void plutovg_move_to(plutovg_t* pluto, double x, double y) +{ + plutovg_path_move_to(pluto->path, x, y); +} + +void plutovg_line_to(plutovg_t* pluto, double x, double y) +{ + plutovg_path_line_to(pluto->path, x, y); +} + +void plutovg_quad_to(plutovg_t* pluto, double x1, double y1, double x2, double y2) +{ + plutovg_path_quad_to(pluto->path, x1, y1, x2, y2); +} + +void plutovg_cubic_to(plutovg_t* pluto, double x1, double y1, double x2, double y2, double x3, double y3) +{ + plutovg_path_cubic_to(pluto->path, x1, y1, x2, y2, x3, y3); +} + +void plutovg_rel_move_to(plutovg_t* pluto, double x, double y) +{ + plutovg_path_rel_move_to(pluto->path, x, y); +} + +void plutovg_rel_line_to(plutovg_t* pluto, double x, double y) +{ + plutovg_path_rel_line_to(pluto->path, x, y); +} + +void plutovg_rel_quad_to(plutovg_t* pluto, double x1, double y1, double x2, double y2) +{ + plutovg_path_rel_quad_to(pluto->path, x1, y1, x2, y2); +} + +void plutovg_rel_cubic_to(plutovg_t* pluto, double x1, double y1, double x2, double y2, double x3, double y3) +{ + plutovg_path_rel_cubic_to(pluto->path, x1, y1, x2, y2, x3, y3); +} + +void plutovg_rect(plutovg_t* pluto, double x, double y, double w, double h) +{ + plutovg_path_add_rect(pluto->path, x, y, w, h); +} + +void plutovg_round_rect(plutovg_t* pluto, double x, double y, double w, double h, double rx, double ry) +{ + plutovg_path_add_round_rect(pluto->path, x, y, w, h, rx, ry); +} + +void plutovg_ellipse(plutovg_t* pluto, double cx, double cy, double rx, double ry) +{ + plutovg_path_add_ellipse(pluto->path, cx, cy, rx, ry); +} + +void plutovg_circle(plutovg_t* pluto, double cx, double cy, double r) +{ + plutovg_ellipse(pluto, cx, cy, r, r); +} + +void plutovg_add_path(plutovg_t* pluto, const plutovg_path_t* path) +{ + plutovg_path_add_path(pluto->path, path, NULL); +} + +void plutovg_new_path(plutovg_t* pluto) +{ + plutovg_path_clear(pluto->path); +} + +void plutovg_close_path(plutovg_t* pluto) +{ + plutovg_path_close(pluto->path); +} + +plutovg_path_t* plutovg_get_path(const plutovg_t* pluto) +{ + return pluto->path; +} + +void plutovg_fill(plutovg_t* pluto) +{ + plutovg_fill_preserve(pluto); + plutovg_new_path(pluto); +} + +void plutovg_stroke(plutovg_t* pluto) +{ + plutovg_stroke_preserve(pluto); + plutovg_new_path(pluto); +} + +void plutovg_clip(plutovg_t* pluto) +{ + plutovg_clip_preserve(pluto); + plutovg_new_path(pluto); +} + +void plutovg_paint(plutovg_t* pluto) +{ + plutovg_state_t* state = pluto->state; + if(state->clippath==NULL && pluto->clippath==NULL) + { + plutovg_path_t* path = plutovg_path_create(); + plutovg_path_add_rect(path, pluto->clip.x, pluto->clip.y, pluto->clip.w, pluto->clip.h); + plutovg_matrix_t matrix; + plutovg_matrix_init_identity(&matrix); + pluto->clippath = plutovg_rle_create(); + plutovg_rle_rasterize(pluto->clippath, path, &matrix, &pluto->clip, NULL, plutovg_fill_rule_non_zero); + plutovg_path_destroy(path); + } + + plutovg_rle_t* rle = state->clippath ? state->clippath : pluto->clippath; + plutovg_blend(pluto, rle); +} + +void plutovg_fill_preserve(plutovg_t* pluto) +{ + plutovg_state_t* state = pluto->state; + plutovg_rle_clear(pluto->rle); + plutovg_rle_rasterize(pluto->rle, pluto->path, &state->matrix, &pluto->clip, NULL, state->winding); + plutovg_rle_clip_path(pluto->rle, state->clippath); + plutovg_blend(pluto, pluto->rle); +} + +void plutovg_stroke_preserve(plutovg_t* pluto) +{ + plutovg_state_t* state = pluto->state; + plutovg_rle_clear(pluto->rle); + plutovg_rle_rasterize(pluto->rle, pluto->path, &state->matrix, &pluto->clip, &state->stroke, plutovg_fill_rule_non_zero); + plutovg_rle_clip_path(pluto->rle, state->clippath); + plutovg_blend(pluto, pluto->rle); +} + +void plutovg_clip_preserve(plutovg_t* pluto) +{ + plutovg_state_t* state = pluto->state; + if(state->clippath) + { + plutovg_rle_clear(pluto->rle); + plutovg_rle_rasterize(pluto->rle, pluto->path, &state->matrix, &pluto->clip, NULL, state->winding); + plutovg_rle_clip_path(state->clippath, pluto->rle); + } + else + { + state->clippath = plutovg_rle_create(); + plutovg_rle_rasterize(state->clippath, pluto->path, &state->matrix, &pluto->clip, NULL, state->winding); + } +} + +void plutovg_reset_clip(plutovg_t* pluto) +{ + plutovg_rle_destroy(pluto->state->clippath); + pluto->state->clippath = NULL; +} diff --git a/3rdparty/plutovg/plutovg.h b/3rdparty/plutovg/plutovg.h new file mode 100644 index 000000000..3daf75b17 --- /dev/null +++ b/3rdparty/plutovg/plutovg.h @@ -0,0 +1,288 @@ +#ifndef PLUTOVG_H +#define PLUTOVG_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct plutovg_surface plutovg_surface_t; + +plutovg_surface_t* plutovg_surface_create(int width, int height); +plutovg_surface_t* plutovg_surface_create_for_data(unsigned char* data, int width, int height, int stride); +plutovg_surface_t* plutovg_surface_reference(plutovg_surface_t* surface); +void plutovg_surface_destroy(plutovg_surface_t* surface); +int plutovg_surface_get_reference_count(const plutovg_surface_t* surface); +unsigned char* plutovg_surface_get_data(const plutovg_surface_t* surface); +int plutovg_surface_get_width(const plutovg_surface_t* surface); +int plutovg_surface_get_height(const plutovg_surface_t* surface); +int plutovg_surface_get_stride(const plutovg_surface_t* surface); + +typedef struct { + double x; + double y; +} plutovg_point_t; + +typedef struct { + double x; + double y; + double w; + double h; +} plutovg_rect_t; + +void plutovg_rect_init(plutovg_rect_t* rect, double x, double y, double w, double h); +void plutovg_rect_init_zero(plutovg_rect_t* rect); + +typedef struct { + double m00; double m10; + double m01; double m11; + double m02; double m12; +} plutovg_matrix_t; + +void plutovg_matrix_init(plutovg_matrix_t* matrix, double m00, double m10, double m01, double m11, double m02, double m12); +void plutovg_matrix_init_identity(plutovg_matrix_t* matrix); +void plutovg_matrix_init_translate(plutovg_matrix_t* matrix, double x, double y); +void plutovg_matrix_init_scale(plutovg_matrix_t* matrix, double x, double y); +void plutovg_matrix_init_shear(plutovg_matrix_t* matrix, double x, double y); +void plutovg_matrix_init_rotate(plutovg_matrix_t* matrix, double radians, double x, double y); +void plutovg_matrix_translate(plutovg_matrix_t* matrix, double x, double y); +void plutovg_matrix_scale(plutovg_matrix_t* matrix, double x, double y); +void plutovg_matrix_shear(plutovg_matrix_t* matrix, double x, double y); +void plutovg_matrix_rotate(plutovg_matrix_t* matrix, double radians, double x, double y); +void plutovg_matrix_multiply(plutovg_matrix_t* matrix, const plutovg_matrix_t* a, const plutovg_matrix_t* b); +int plutovg_matrix_invert(plutovg_matrix_t* matrix); +void plutovg_matrix_map(const plutovg_matrix_t* matrix, double x, double y, double* _x, double* _y); +void plutovg_matrix_map_point(const plutovg_matrix_t* matrix, const plutovg_point_t* src, plutovg_point_t* dst); +void plutovg_matrix_map_rect(const plutovg_matrix_t* matrix, const plutovg_rect_t* src, plutovg_rect_t* dst); + +typedef struct plutovg_path plutovg_path_t; + +typedef enum { + plutovg_path_element_move_to, + plutovg_path_element_line_to, + plutovg_path_element_cubic_to, + plutovg_path_element_close +} plutovg_path_element_t; + +plutovg_path_t* plutovg_path_create(void); +plutovg_path_t* plutovg_path_reference(plutovg_path_t* path); +void plutovg_path_destroy(plutovg_path_t* path); +int plutovg_path_get_reference_count(const plutovg_path_t* path); +void plutovg_path_move_to(plutovg_path_t* path, double x, double y); +void plutovg_path_line_to(plutovg_path_t* path, double x, double y); +void plutovg_path_quad_to(plutovg_path_t* path, double x1, double y1, double x2, double y2); +void plutovg_path_cubic_to(plutovg_path_t* path, double x1, double y1, double x2, double y2, double x3, double y3); +void plutovg_path_close(plutovg_path_t* path); +void plutovg_path_rel_move_to(plutovg_path_t* path, double x, double y); +void plutovg_path_rel_line_to(plutovg_path_t* path, double x, double y); +void plutovg_path_rel_quad_to(plutovg_path_t* path, double x1, double y1, double x2, double y2); +void plutovg_path_rel_cubic_to(plutovg_path_t* path, double x1, double y1, double x2, double y2, double x3, double y3); +void plutovg_path_add_rect(plutovg_path_t* path, double x, double y, double w, double h); +void plutovg_path_add_round_rect(plutovg_path_t* path, double x, double y, double w, double h, double rx, double ry); +void plutovg_path_add_ellipse(plutovg_path_t* path, double cx, double cy, double rx, double ry); +void plutovg_path_add_circle(plutovg_path_t* path, double cx, double cy, double r); +void plutovg_path_add_path(plutovg_path_t* path, const plutovg_path_t* source, const plutovg_matrix_t* matrix); +void plutovg_path_transform(plutovg_path_t* path, const plutovg_matrix_t* matrix); +void plutovg_path_get_current_point(const plutovg_path_t* path, double* x, double* y); +int plutovg_path_get_element_count(const plutovg_path_t* path); +plutovg_path_element_t* plutovg_path_get_elements(const plutovg_path_t* path); +int plutovg_path_get_point_count(const plutovg_path_t* path); +plutovg_point_t* plutovg_path_get_points(const plutovg_path_t* path); +void plutovg_path_clear(plutovg_path_t* path); +int plutovg_path_empty(const plutovg_path_t* path); +plutovg_path_t* plutovg_path_clone(const plutovg_path_t* path); +plutovg_path_t* plutovg_path_clone_flat(const plutovg_path_t* path); + +typedef struct { + double r; + double g; + double b; + double a; +} plutovg_color_t; + +void plutovg_color_init_rgb(plutovg_color_t* color, double r, double g, double b); +void plutovg_color_init_rgba(plutovg_color_t* color, double r, double g, double b, double a); + +typedef enum { + plutovg_spread_method_pad, + plutovg_spread_method_reflect, + plutovg_spread_method_repeat +} plutovg_spread_method_t; + +typedef struct plutovg_gradient plutovg_gradient_t; + +typedef enum { + plutovg_gradient_type_linear, + plutovg_gradient_type_radial +} plutovg_gradient_type_t; + +typedef struct { + double offset; + plutovg_color_t color; +} plutovg_gradient_stop_t; + +plutovg_gradient_t* plutovg_gradient_create_linear(double x1, double y1, double x2, double y2); +plutovg_gradient_t* plutovg_gradient_create_radial(double cx, double cy, double cr, double fx, double fy, double fr); +plutovg_gradient_t* plutovg_gradient_reference(plutovg_gradient_t* gradient); +void plutovg_gradient_destroy(plutovg_gradient_t* gradient); +int plutovg_gradient_get_reference_count(const plutovg_gradient_t* gradient); +void plutovg_gradient_set_type(plutovg_gradient_t* gradient, plutovg_gradient_type_t type); +plutovg_gradient_type_t plutovg_gradient_get_type(const plutovg_gradient_t* gradient); +void plutovg_gradient_set_spread(plutovg_gradient_t* gradient, plutovg_spread_method_t spread); +plutovg_spread_method_t plutovg_gradient_get_spread(const plutovg_gradient_t* gradient); +void plutovg_gradient_set_matrix(plutovg_gradient_t* gradient, const plutovg_matrix_t* matrix); +void plutovg_gradient_get_matrix(const plutovg_gradient_t* gradient, plutovg_matrix_t* matrix); +void plutovg_gradient_add_stop_rgb(plutovg_gradient_t* gradient, double offset, double r, double g, double b); +void plutovg_gradient_add_stop_rgba(plutovg_gradient_t* gradient, double offset, double r, double g, double b, double a); +void plutovg_gradient_add_stop(plutovg_gradient_t* gradient, const plutovg_gradient_stop_t* stop); +void plutovg_gradient_clear_stops(plutovg_gradient_t* gradient); +int plutovg_gradient_get_stop_count(const plutovg_gradient_t* gradient); +plutovg_gradient_stop_t* plutovg_gradient_get_stops(const plutovg_gradient_t* gradient); +void plutovg_gradient_get_values_linear(const plutovg_gradient_t* gradient, double* x1, double* y1, double* x2, double* y2); +void plutovg_gradient_get_values_radial(const plutovg_gradient_t* gradient, double* cx, double* cy, double* cr, double* fx, double* fy, double* fr); +void plutovg_gradient_set_values_linear(plutovg_gradient_t* gradient, double x1, double y1, double x2, double y2); +void plutovg_gradient_set_values_radial(plutovg_gradient_t* gradient, double cx, double cy, double cr, double fx, double fy, double fr); +void plutovg_gradient_set_opacity(plutovg_gradient_t* paint, double opacity); +double plutovg_gradient_get_opacity(const plutovg_gradient_t* paint); + +typedef struct plutovg_texture plutovg_texture_t; + +typedef enum { + plutovg_texture_type_plain, + plutovg_texture_type_tiled +} plutovg_texture_type_t; + +plutovg_texture_t* plutovg_texture_create(plutovg_surface_t* surface); +plutovg_texture_t* plutovg_texture_reference(plutovg_texture_t* texture); +void plutovg_texture_destroy(plutovg_texture_t* texture); +int plutovg_texture_get_reference_count(const plutovg_texture_t* texture); +void plutovg_texture_set_type(plutovg_texture_t* texture, plutovg_texture_type_t type); +plutovg_texture_type_t plutovg_texture_get_type(const plutovg_texture_t* texture); +void plutovg_texture_set_matrix(plutovg_texture_t* texture, const plutovg_matrix_t* matrix); +void plutovg_texture_get_matrix(const plutovg_texture_t* texture, plutovg_matrix_t* matrix); +void plutovg_texture_set_surface(plutovg_texture_t* texture, plutovg_surface_t* surface); +plutovg_surface_t* plutovg_texture_get_surface(const plutovg_texture_t* texture); +void plutovg_texture_set_opacity(plutovg_texture_t* texture, double opacity); +double plutovg_texture_get_opacity(const plutovg_texture_t* texture); + +typedef struct plutovg_paint plutovg_paint_t; + +typedef enum { + plutovg_paint_type_color, + plutovg_paint_type_gradient, + plutovg_paint_type_texture +} plutovg_paint_type_t; + +plutovg_paint_t* plutovg_paint_create_rgb(double r, double g, double b); +plutovg_paint_t* plutovg_paint_create_rgba(double r, double g, double b, double a); +plutovg_paint_t* plutovg_paint_create_linear(double x1, double y1, double x2, double y2); +plutovg_paint_t* plutovg_paint_create_radial(double cx, double cy, double cr, double fx, double fy, double fr); +plutovg_paint_t* plutovg_paint_create_for_surface(plutovg_surface_t* surface); +plutovg_paint_t* plutovg_paint_create_color(const plutovg_color_t* color); +plutovg_paint_t* plutovg_paint_create_gradient(plutovg_gradient_t* gradient); +plutovg_paint_t* plutovg_paint_create_texture(plutovg_texture_t* texture); +plutovg_paint_t* plutovg_paint_reference(plutovg_paint_t* paint); +void plutovg_paint_destroy(plutovg_paint_t* paint); +int plutovg_paint_get_reference_count(const plutovg_paint_t* paint); +plutovg_paint_type_t plutovg_paint_get_type(const plutovg_paint_t* paint); +plutovg_color_t* plutovg_paint_get_color(const plutovg_paint_t* paint); +plutovg_gradient_t* plutovg_paint_get_gradient(const plutovg_paint_t* paint); +plutovg_texture_t* plutovg_paint_get_texture(const plutovg_paint_t* paint); + +typedef enum { + plutovg_line_cap_butt, + plutovg_line_cap_round, + plutovg_line_cap_square +} plutovg_line_cap_t; + +typedef enum { + plutovg_line_join_miter, + plutovg_line_join_round, + plutovg_line_join_bevel +} plutovg_line_join_t; + +typedef enum { + plutovg_fill_rule_non_zero, + plutovg_fill_rule_even_odd +} plutovg_fill_rule_t; + +typedef enum { + plutovg_operator_src, + plutovg_operator_src_over, + plutovg_operator_dst_in, + plutovg_operator_dst_out +} plutovg_operator_t; + +typedef struct plutovg plutovg_t; + +plutovg_t* plutovg_create(plutovg_surface_t* surface); +plutovg_t* plutovg_reference(plutovg_t* pluto); +void plutovg_destroy(plutovg_t* pluto); +int plutovg_get_reference_count(const plutovg_t* pluto); +void plutovg_save(plutovg_t* pluto); +void plutovg_restore(plutovg_t* pluto); +void plutovg_set_source_rgb(plutovg_t* pluto, double r, double g, double b); +void plutovg_set_source_rgba(plutovg_t* pluto, double r, double g, double b, double a); +void plutovg_set_source_surface(plutovg_t* pluto, plutovg_surface_t* surface, double x, double y); +void plutovg_set_source_color(plutovg_t* pluto, const plutovg_color_t* color); +void plutovg_set_source_gradient(plutovg_t* pluto, plutovg_gradient_t* gradient); +void plutovg_set_source_texture(plutovg_t* pluto, plutovg_texture_t* texture); +void plutovg_set_source(plutovg_t* pluto, plutovg_paint_t* source); +plutovg_paint_t* plutovg_get_source(const plutovg_t* pluto); + +void plutovg_set_operator(plutovg_t* pluto, plutovg_operator_t op); +void plutovg_set_opacity(plutovg_t* pluto, double opacity); +void plutovg_set_fill_rule(plutovg_t* pluto, plutovg_fill_rule_t fill_rule); +plutovg_operator_t plutovg_get_operator(const plutovg_t* pluto); +double plutovg_get_opacity(const plutovg_t* pluto); +plutovg_fill_rule_t plutovg_get_fill_rule(const plutovg_t* pluto); + +void plutovg_set_line_width(plutovg_t* pluto, double width); +void plutovg_set_line_cap(plutovg_t* pluto, plutovg_line_cap_t cap); +void plutovg_set_line_join(plutovg_t* pluto, plutovg_line_join_t join); +void plutovg_set_miter_limit(plutovg_t* pluto, double limit); +void plutovg_set_dash(plutovg_t* pluto, double offset, const double* data, int size); +double plutovg_get_line_width(const plutovg_t* pluto); +plutovg_line_cap_t plutovg_get_line_cap(const plutovg_t* pluto); +plutovg_line_join_t plutovg_get_line_join(const plutovg_t* pluto); +double plutovg_get_miter_limit(const plutovg_t* pluto); + +void plutovg_translate(plutovg_t* pluto, double x, double y); +void plutovg_scale(plutovg_t* pluto, double x, double y); +void plutovg_rotate(plutovg_t* pluto, double radians, double x, double y); +void plutovg_transform(plutovg_t* pluto, const plutovg_matrix_t* matrix); +void plutovg_set_matrix(plutovg_t* pluto, const plutovg_matrix_t* matrix); +void plutovg_identity_matrix(plutovg_t* pluto); +void plutovg_get_matrix(const plutovg_t* pluto, plutovg_matrix_t* matrix); + +void plutovg_move_to(plutovg_t* pluto, double x, double y); +void plutovg_line_to(plutovg_t* pluto, double x, double y); +void plutovg_quad_to(plutovg_t* pluto, double x1, double y1, double x2, double y2); +void plutovg_cubic_to(plutovg_t* pluto, double x1, double y1, double x2, double y2, double x3, double y3); +void plutovg_rel_move_to(plutovg_t* pluto, double x, double y); +void plutovg_rel_line_to(plutovg_t* pluto, double x, double y); +void plutovg_rel_quad_to(plutovg_t* pluto, double x1, double y1, double x2, double y2); +void plutovg_rel_cubic_to(plutovg_t* pluto, double x1, double y1, double x2, double y2, double x3, double y3); +void plutovg_rect(plutovg_t* pluto, double x, double y, double w, double h); +void plutovg_round_rect(plutovg_t* pluto, double x, double y, double w, double h, double rx, double ry); +void plutovg_ellipse(plutovg_t* pluto, double cx, double cy, double rx, double ry); +void plutovg_circle(plutovg_t* pluto, double cx, double cy, double r); +void plutovg_add_path(plutovg_t* pluto, const plutovg_path_t* path); +void plutovg_new_path(plutovg_t* pluto); +void plutovg_close_path(plutovg_t* pluto); +plutovg_path_t* plutovg_get_path(const plutovg_t* pluto); + +void plutovg_fill(plutovg_t* pluto); +void plutovg_stroke(plutovg_t* pluto); +void plutovg_clip(plutovg_t* pluto); +void plutovg_paint(plutovg_t* pluto); + +void plutovg_fill_preserve(plutovg_t* pluto); +void plutovg_stroke_preserve(plutovg_t* pluto); +void plutovg_clip_preserve(plutovg_t* pluto); +void plutovg_reset_clip(plutovg_t* pluto); + +#ifdef __cplusplus +} +#endif + +#endif // PLUTOVG_H diff --git a/3rdparty/software/CMakeLists.txt b/3rdparty/software/CMakeLists.txt new file mode 100755 index 000000000..feb06bfc7 --- /dev/null +++ b/3rdparty/software/CMakeLists.txt @@ -0,0 +1,11 @@ +target_sources(lunasvg +PRIVATE + "${CMAKE_CURRENT_LIST_DIR}/sw_ft_math.c" + "${CMAKE_CURRENT_LIST_DIR}/sw_ft_raster.c" + "${CMAKE_CURRENT_LIST_DIR}/sw_ft_stroker.c" +) + +target_include_directories(lunasvg +PRIVATE + "${CMAKE_CURRENT_LIST_DIR}" +) diff --git a/3rdparty/software/FTL.TXT b/3rdparty/software/FTL.TXT new file mode 100644 index 000000000..5f673ffad --- /dev/null +++ b/3rdparty/software/FTL.TXT @@ -0,0 +1,166 @@ + The FreeType Project LICENSE + ---------------------------- + + 2006-Jan-27 + + Copyright 1996-2002, 2006 by + David Turner, Robert Wilhelm, and Werner Lemberg + + + +Introduction +============ + + The FreeType Project is distributed in several archive packages; + some of them may contain, in addition to the FreeType font engine, + various tools and contributions which rely on, or relate to, the + FreeType Project. + + This license applies to all files found in such packages, and + which do not fall under their own explicit license. The license + affects thus the FreeType font engine, the test programs, + documentation and makefiles, at the very least. + + This license was inspired by the BSD, Artistic, and IJG + (Independent JPEG Group) licenses, which all encourage inclusion + and use of free software in commercial and freeware products + alike. As a consequence, its main points are that: + + o We don't promise that this software works. However, we will be + interested in any kind of bug reports. (`as is' distribution) + + o You can use this software for whatever you want, in parts or + full form, without having to pay us. (`royalty-free' usage) + + o You may not pretend that you wrote this software. If you use + it, or only parts of it, in a program, you must acknowledge + somewhere in your documentation that you have used the + FreeType code. (`credits') + + We specifically permit and encourage the inclusion of this + software, with or without modifications, in commercial products. + We disclaim all warranties covering The FreeType Project and + assume no liability related to The FreeType Project. + + + Finally, many people asked us for a preferred form for a + credit/disclaimer to use in compliance with this license. We thus + encourage you to use the following text: + + """ + Portions of this software are copyright � The FreeType + Project (www.freetype.org). All rights reserved. + """ + + Please replace with the value from the FreeType version you + actually use. + + +Legal Terms +=========== + +0. Definitions +-------------- + + Throughout this license, the terms `package', `FreeType Project', + and `FreeType archive' refer to the set of files originally + distributed by the authors (David Turner, Robert Wilhelm, and + Werner Lemberg) as the `FreeType Project', be they named as alpha, + beta or final release. + + `You' refers to the licensee, or person using the project, where + `using' is a generic term including compiling the project's source + code as well as linking it to form a `program' or `executable'. + This program is referred to as `a program using the FreeType + engine'. + + This license applies to all files distributed in the original + FreeType Project, including all source code, binaries and + documentation, unless otherwise stated in the file in its + original, unmodified form as distributed in the original archive. + If you are unsure whether or not a particular file is covered by + this license, you must contact us to verify this. + + The FreeType Project is copyright (C) 1996-2000 by David Turner, + Robert Wilhelm, and Werner Lemberg. All rights reserved except as + specified below. + +1. No Warranty +-------------- + + THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO + USE, OF THE FREETYPE PROJECT. + +2. Redistribution +----------------- + + This license grants a worldwide, royalty-free, perpetual and + irrevocable right and license to use, execute, perform, compile, + display, copy, create derivative works of, distribute and + sublicense the FreeType Project (in both source and object code + forms) and derivative works thereof for any purpose; and to + authorize others to exercise some or all of the rights granted + herein, subject to the following conditions: + + o Redistribution of source code must retain this license file + (`FTL.TXT') unaltered; any additions, deletions or changes to + the original files must be clearly indicated in accompanying + documentation. The copyright notices of the unaltered, + original files must be preserved in all copies of source + files. + + o Redistribution in binary form must provide a disclaimer that + states that the software is based in part of the work of the + FreeType Team, in the distribution documentation. We also + encourage you to put an URL to the FreeType web page in your + documentation, though this isn't mandatory. + + These conditions apply to any software derived from or based on + the FreeType Project, not just the unmodified files. If you use + our work, you must acknowledge us. However, no fee need be paid + to us. + +3. Advertising +-------------- + + Neither the FreeType authors and contributors nor you shall use + the name of the other for commercial, advertising, or promotional + purposes without specific prior written permission. + + We suggest, but do not require, that you use one or more of the + following phrases to refer to this software in your documentation + or advertising materials: `FreeType Project', `FreeType Engine', + `FreeType library', or `FreeType Distribution'. + + As you have not signed this license, you are not required to + accept it. However, as the FreeType Project is copyrighted + material, only this license, or another one contracted with the + authors, grants you the right to use, distribute, and modify it. + Therefore, by using, distributing, or modifying the FreeType + Project, you indicate that you understand and accept all the terms + of this license. + +4. Contacts +----------- + + There are two mailing lists related to FreeType: + + o freetype@nongnu.org + + Discusses general use and applications of FreeType, as well as + future and wanted additions to the library and distribution. + If you are looking for support, start in this list if you + haven't found anything to help you in the documentation. + + o freetype-devel@nongnu.org + + Discusses bugs, as well as engine internals, design issues, + specific licenses, porting, etc. + + Our home page can be found at + + http://www.freetype.org diff --git a/3rdparty/software/sw_ft_math.c b/3rdparty/software/sw_ft_math.c new file mode 100644 index 000000000..79c9501b7 --- /dev/null +++ b/3rdparty/software/sw_ft_math.c @@ -0,0 +1,461 @@ +/***************************************************************************/ +/* */ +/* fttrigon.c */ +/* */ +/* FreeType trigonometric functions (body). */ +/* */ +/* Copyright 2001-2005, 2012-2013 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#include "sw_ft_math.h" +#include + +//form https://github.com/chromium/chromium/blob/59afd8336009c9d97c22854c52e0382b62b3aa5e/third_party/abseil-cpp/absl/base/internal/bits.h + +#if defined(_MSC_VER) +#include +static unsigned int __inline clz(unsigned int x) { + unsigned long r = 0; + if (x != 0) + { + _BitScanReverse(&r, x); + } + return r; +} +#define SW_FT_MSB(x) (clz(x)) +#elif defined(__GNUC__) +#define SW_FT_MSB(x) (31 - __builtin_clz(x)) +#else +static unsigned int __inline clz(unsigned int x) { + int c = 31; + x &= ~x + 1; + if (n & 0x0000FFFF) c -= 16; + if (n & 0x00FF00FF) c -= 8; + if (n & 0x0F0F0F0F) c -= 4; + if (n & 0x33333333) c -= 2; + if (n & 0x55555555) c -= 1; + return c; +} +#define SW_FT_MSB(x) (clz(x)) +#endif + + + + + +#define SW_FT_PAD_FLOOR(x, n) ((x) & ~((n)-1)) +#define SW_FT_PAD_ROUND(x, n) SW_FT_PAD_FLOOR((x) + ((n) / 2), n) +#define SW_FT_PAD_CEIL(x, n) SW_FT_PAD_FLOOR((x) + ((n)-1), n) + +#define SW_FT_BEGIN_STMNT do { +#define SW_FT_END_STMNT \ + } \ + while (0) +/* transfer sign leaving a positive number */ +#define SW_FT_MOVE_SIGN(x, s) \ + SW_FT_BEGIN_STMNT \ + if (x < 0) { \ + x = -x; \ + s = -s; \ + } \ + SW_FT_END_STMNT + +SW_FT_Long SW_FT_MulFix(SW_FT_Long a, SW_FT_Long b) +{ + SW_FT_Int s = 1; + SW_FT_Long c; + + SW_FT_MOVE_SIGN(a, s); + SW_FT_MOVE_SIGN(b, s); + + c = (SW_FT_Long)(((SW_FT_Int64)a * b + 0x8000L) >> 16); + + return (s > 0) ? c : -c; +} + +SW_FT_Long SW_FT_MulDiv(SW_FT_Long a, SW_FT_Long b, SW_FT_Long c) +{ + SW_FT_Int s = 1; + SW_FT_Long d; + + SW_FT_MOVE_SIGN(a, s); + SW_FT_MOVE_SIGN(b, s); + SW_FT_MOVE_SIGN(c, s); + + d = (SW_FT_Long)(c > 0 ? ((SW_FT_Int64)a * b + (c >> 1)) / c : 0x7FFFFFFFL); + + return (s > 0) ? d : -d; +} + +SW_FT_Long SW_FT_DivFix(SW_FT_Long a, SW_FT_Long b) +{ + SW_FT_Int s = 1; + SW_FT_Long q; + + SW_FT_MOVE_SIGN(a, s); + SW_FT_MOVE_SIGN(b, s); + + q = (SW_FT_Long)(b > 0 ? (((SW_FT_UInt64)a << 16) + (b >> 1)) / b + : 0x7FFFFFFFL); + + return (s < 0 ? -q : q); +} + +/*************************************************************************/ +/* */ +/* This is a fixed-point CORDIC implementation of trigonometric */ +/* functions as well as transformations between Cartesian and polar */ +/* coordinates. The angles are represented as 16.16 fixed-point values */ +/* in degrees, i.e., the angular resolution is 2^-16 degrees. Note that */ +/* only vectors longer than 2^16*180/pi (or at least 22 bits) on a */ +/* discrete Cartesian grid can have the same or better angular */ +/* resolution. Therefore, to maintain this precision, some functions */ +/* require an interim upscaling of the vectors, whereas others operate */ +/* with 24-bit long vectors directly. */ +/* */ +/*************************************************************************/ + +/* the Cordic shrink factor 0.858785336480436 * 2^32 */ +#define SW_FT_TRIG_SCALE 0xDBD95B16UL + +/* the highest bit in overflow-safe vector components, */ +/* MSB of 0.858785336480436 * sqrt(0.5) * 2^30 */ +#define SW_FT_TRIG_SAFE_MSB 29 + +/* this table was generated for SW_FT_PI = 180L << 16, i.e. degrees */ +#define SW_FT_TRIG_MAX_ITERS 23 + +static const SW_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}; + +/* multiply a given value by the CORDIC shrink factor */ +static SW_FT_Fixed ft_trig_downscale(SW_FT_Fixed val) +{ + SW_FT_Fixed s; + SW_FT_Int64 v; + + s = val; + val = SW_FT_ABS(val); + + v = (val * (SW_FT_Int64)SW_FT_TRIG_SCALE) + 0x100000000UL; + val = (SW_FT_Fixed)(v >> 32); + + return (s >= 0) ? val : -val; +} + +/* undefined and never called for zero vector */ +static SW_FT_Int ft_trig_prenorm(SW_FT_Vector* vec) +{ + SW_FT_Pos x, y; + SW_FT_Int shift; + + x = vec->x; + y = vec->y; + + shift = SW_FT_MSB(SW_FT_ABS(x) | SW_FT_ABS(y)); + + if (shift <= SW_FT_TRIG_SAFE_MSB) { + shift = SW_FT_TRIG_SAFE_MSB - shift; + vec->x = (SW_FT_Pos)((SW_FT_ULong)x << shift); + vec->y = (SW_FT_Pos)((SW_FT_ULong)y << shift); + } else { + shift -= SW_FT_TRIG_SAFE_MSB; + vec->x = x >> shift; + vec->y = y >> shift; + shift = -shift; + } + + return shift; +} + +static void ft_trig_pseudo_rotate(SW_FT_Vector* vec, SW_FT_Angle theta) +{ + SW_FT_Int i; + SW_FT_Fixed x, y, xtemp, b; + const SW_FT_Fixed* arctanptr; + + x = vec->x; + y = vec->y; + + /* Rotate inside [-PI/4,PI/4] sector */ + while (theta < -SW_FT_ANGLE_PI4) { + xtemp = y; + y = -x; + x = xtemp; + theta += SW_FT_ANGLE_PI2; + } + + while (theta > SW_FT_ANGLE_PI4) { + xtemp = -y; + y = x; + x = xtemp; + theta -= SW_FT_ANGLE_PI2; + } + + arctanptr = ft_trig_arctan_table; + + /* Pseudorotations, with right shifts */ + for (i = 1, b = 1; i < SW_FT_TRIG_MAX_ITERS; b <<= 1, i++) { + SW_FT_Fixed v1 = ((y + b) >> i); + SW_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(SW_FT_Vector* vec) +{ + SW_FT_Angle theta; + SW_FT_Int i; + SW_FT_Fixed x, y, xtemp, b; + const SW_FT_Fixed* arctanptr; + + x = vec->x; + y = vec->y; + + /* Get the vector into [-PI/4,PI/4] sector */ + if (y > x) { + if (y > -x) { + theta = SW_FT_ANGLE_PI2; + xtemp = y; + y = -x; + x = xtemp; + } else { + theta = y > 0 ? SW_FT_ANGLE_PI : -SW_FT_ANGLE_PI; + x = -x; + y = -y; + } + } else { + if (y < -x) { + theta = -SW_FT_ANGLE_PI2; + xtemp = -y; + y = x; + x = xtemp; + } else { + theta = 0; + } + } + + arctanptr = ft_trig_arctan_table; + + /* Pseudorotations, with right shifts */ + for (i = 1, b = 1; i < SW_FT_TRIG_MAX_ITERS; b <<= 1, i++) { + SW_FT_Fixed v1 = ((y + b) >> i); + SW_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++; + } + } + + /* round theta */ + if (theta >= 0) + theta = SW_FT_PAD_ROUND(theta, 32); + else + theta = -SW_FT_PAD_ROUND(-theta, 32); + + vec->x = x; + vec->y = theta; +} + +/* documentation is in fttrigon.h */ + +SW_FT_Fixed SW_FT_Cos(SW_FT_Angle angle) +{ + SW_FT_Vector v; + + v.x = SW_FT_TRIG_SCALE >> 8; + v.y = 0; + ft_trig_pseudo_rotate(&v, angle); + + return (v.x + 0x80L) >> 8; +} + +/* documentation is in fttrigon.h */ + +SW_FT_Fixed SW_FT_Sin(SW_FT_Angle angle) +{ + return SW_FT_Cos(SW_FT_ANGLE_PI2 - angle); +} + +/* documentation is in fttrigon.h */ + +SW_FT_Fixed SW_FT_Tan(SW_FT_Angle angle) +{ + SW_FT_Vector v; + + v.x = SW_FT_TRIG_SCALE >> 8; + v.y = 0; + ft_trig_pseudo_rotate(&v, angle); + + return SW_FT_DivFix(v.y, v.x); +} + +/* documentation is in fttrigon.h */ + +SW_FT_Angle SW_FT_Atan2(SW_FT_Fixed dx, SW_FT_Fixed dy) +{ + SW_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; +} + +/* documentation is in fttrigon.h */ + +void SW_FT_Vector_Unit(SW_FT_Vector* vec, SW_FT_Angle angle) +{ + vec->x = SW_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; +} + +/* these macros return 0 for positive numbers, + and -1 for negative ones */ +#define SW_FT_SIGN_LONG(x) ((x) >> (SW_FT_SIZEOF_LONG * 8 - 1)) +#define SW_FT_SIGN_INT(x) ((x) >> (SW_FT_SIZEOF_INT * 8 - 1)) +#define SW_FT_SIGN_INT32(x) ((x) >> 31) +#define SW_FT_SIGN_INT16(x) ((x) >> 15) + +/* documentation is in fttrigon.h */ + +void SW_FT_Vector_Rotate(SW_FT_Vector* vec, SW_FT_Angle angle) +{ + SW_FT_Int shift; + SW_FT_Vector v; + + v.x = vec->x; + v.y = vec->y; + + if (angle && (v.x != 0 || v.y != 0)) { + 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) { + SW_FT_Int32 half = (SW_FT_Int32)1L << (shift - 1); + + vec->x = (v.x + half + SW_FT_SIGN_LONG(v.x)) >> shift; + vec->y = (v.y + half + SW_FT_SIGN_LONG(v.y)) >> shift; + } else { + shift = -shift; + vec->x = (SW_FT_Pos)((SW_FT_ULong)v.x << shift); + vec->y = (SW_FT_Pos)((SW_FT_ULong)v.y << shift); + } + } +} + +/* documentation is in fttrigon.h */ + +SW_FT_Fixed SW_FT_Vector_Length(SW_FT_Vector* vec) +{ + SW_FT_Int shift; + SW_FT_Vector v; + + v = *vec; + + /* handle trivial cases */ + if (v.x == 0) { + return SW_FT_ABS(v.y); + } else if (v.y == 0) { + return SW_FT_ABS(v.x); + } + + /* general case */ + 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 (SW_FT_Fixed)((SW_FT_UInt32)v.x << -shift); +} + +/* documentation is in fttrigon.h */ + +void SW_FT_Vector_Polarize(SW_FT_Vector* vec, SW_FT_Fixed* length, + SW_FT_Angle* angle) +{ + SW_FT_Int shift; + SW_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) + : (SW_FT_Fixed)((SW_FT_UInt32)v.x << -shift); + *angle = v.y; +} + +/* documentation is in fttrigon.h */ + +void SW_FT_Vector_From_Polar(SW_FT_Vector* vec, SW_FT_Fixed length, + SW_FT_Angle angle) +{ + vec->x = length; + vec->y = 0; + + SW_FT_Vector_Rotate(vec, angle); +} + +/* documentation is in fttrigon.h */ + +SW_FT_Angle SW_FT_Angle_Diff( SW_FT_Angle angle1, SW_FT_Angle angle2 ) +{ + SW_FT_Angle delta = angle2 - angle1; + + while ( delta <= -SW_FT_ANGLE_PI ) + delta += SW_FT_ANGLE_2PI; + + while ( delta > SW_FT_ANGLE_PI ) + delta -= SW_FT_ANGLE_2PI; + + return delta; +} + +/* END */ diff --git a/3rdparty/software/sw_ft_math.h b/3rdparty/software/sw_ft_math.h new file mode 100644 index 000000000..95a5c4257 --- /dev/null +++ b/3rdparty/software/sw_ft_math.h @@ -0,0 +1,438 @@ +#ifndef SW_FT_MATH_H +#define SW_FT_MATH_H + +/***************************************************************************/ +/* */ +/* fttrigon.h */ +/* */ +/* FreeType trigonometric functions (specification). */ +/* */ +/* Copyright 2001, 2003, 2005, 2007, 2013 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#include "sw_ft_types.h" + + +/*************************************************************************/ +/* */ +/* The min and max functions missing in C. As usual, be careful not to */ +/* write things like SW_FT_MIN( a++, b++ ) to avoid side effects. */ +/* */ +#define SW_FT_MIN( a, b ) ( (a) < (b) ? (a) : (b) ) +#define SW_FT_MAX( a, b ) ( (a) > (b) ? (a) : (b) ) + +#define SW_FT_ABS( a ) ( (a) < 0 ? -(a) : (a) ) + +/* + * Approximate sqrt(x*x+y*y) using the `alpha max plus beta min' + * algorithm. We use alpha = 1, beta = 3/8, giving us results with a + * largest error less than 7% compared to the exact value. + */ +#define SW_FT_HYPOT( x, y ) \ + ( x = SW_FT_ABS( x ), \ + y = SW_FT_ABS( y ), \ + x > y ? x + ( 3 * y >> 3 ) \ + : y + ( 3 * x >> 3 ) ) + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_MulFix */ +/* */ +/* */ +/* A very simple function used to perform the computation */ +/* `(a*b)/0x10000' with maximum accuracy. Most of the time this is */ +/* used to multiply a given value by a 16.16 fixed-point factor. */ +/* */ +/* */ +/* a :: The first multiplier. */ +/* b :: The second multiplier. Use a 16.16 factor here whenever */ +/* possible (see note below). */ +/* */ +/* */ +/* The result of `(a*b)/0x10000'. */ +/* */ +/* */ +/* This function has been optimized for the case where the absolute */ +/* value of `a' is less than 2048, and `b' is a 16.16 scaling factor. */ +/* As this happens mainly when scaling from notional units to */ +/* fractional pixels in FreeType, it resulted in noticeable speed */ +/* improvements between versions 2.x and 1.x. */ +/* */ +/* As a conclusion, always try to place a 16.16 factor as the */ +/* _second_ argument of this function; this can make a great */ +/* difference. */ +/* */ +SW_FT_Long +SW_FT_MulFix( SW_FT_Long a, + SW_FT_Long b ); + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_MulDiv */ +/* */ +/* */ +/* A very simple function used to perform the computation `(a*b)/c' */ +/* with maximum accuracy (it uses a 64-bit intermediate integer */ +/* whenever necessary). */ +/* */ +/* This function isn't necessarily as fast as some processor specific */ +/* operations, but is at least completely portable. */ +/* */ +/* */ +/* a :: The first multiplier. */ +/* b :: The second multiplier. */ +/* c :: The divisor. */ +/* */ +/* */ +/* The result of `(a*b)/c'. This function never traps when trying to */ +/* divide by zero; it simply returns `MaxInt' or `MinInt' depending */ +/* on the signs of `a' and `b'. */ +/* */ +SW_FT_Long +SW_FT_MulDiv( SW_FT_Long a, + SW_FT_Long b, + SW_FT_Long c ); + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_DivFix */ +/* */ +/* */ +/* A very simple function used to perform the computation */ +/* `(a*0x10000)/b' with maximum accuracy. Most of the time, this is */ +/* used to divide a given value by a 16.16 fixed-point factor. */ +/* */ +/* */ +/* a :: The numerator. */ +/* b :: The denominator. Use a 16.16 factor here. */ +/* */ +/* */ +/* The result of `(a*0x10000)/b'. */ +/* */ +SW_FT_Long +SW_FT_DivFix( SW_FT_Long a, + SW_FT_Long b ); + + + + /*************************************************************************/ + /* */ + /*
*/ + /* computations */ + /* */ + /*************************************************************************/ + + + /************************************************************************* + * + * @type: + * SW_FT_Angle + * + * @description: + * This type is used to model angle values in FreeType. Note that the + * angle is a 16.16 fixed-point value expressed in degrees. + * + */ + typedef SW_FT_Fixed SW_FT_Angle; + + + /************************************************************************* + * + * @macro: + * SW_FT_ANGLE_PI + * + * @description: + * The angle pi expressed in @SW_FT_Angle units. + * + */ +#define SW_FT_ANGLE_PI ( 180L << 16 ) + + + /************************************************************************* + * + * @macro: + * SW_FT_ANGLE_2PI + * + * @description: + * The angle 2*pi expressed in @SW_FT_Angle units. + * + */ +#define SW_FT_ANGLE_2PI ( SW_FT_ANGLE_PI * 2 ) + + + /************************************************************************* + * + * @macro: + * SW_FT_ANGLE_PI2 + * + * @description: + * The angle pi/2 expressed in @SW_FT_Angle units. + * + */ +#define SW_FT_ANGLE_PI2 ( SW_FT_ANGLE_PI / 2 ) + + + /************************************************************************* + * + * @macro: + * SW_FT_ANGLE_PI4 + * + * @description: + * The angle pi/4 expressed in @SW_FT_Angle units. + * + */ +#define SW_FT_ANGLE_PI4 ( SW_FT_ANGLE_PI / 4 ) + + + /************************************************************************* + * + * @function: + * SW_FT_Sin + * + * @description: + * Return the sinus of a given angle in fixed-point format. + * + * @input: + * angle :: + * The input angle. + * + * @return: + * The sinus value. + * + * @note: + * If you need both the sinus and cosinus for a given angle, use the + * function @SW_FT_Vector_Unit. + * + */ + SW_FT_Fixed + SW_FT_Sin( SW_FT_Angle angle ); + + + /************************************************************************* + * + * @function: + * SW_FT_Cos + * + * @description: + * Return the cosinus of a given angle in fixed-point format. + * + * @input: + * angle :: + * The input angle. + * + * @return: + * The cosinus value. + * + * @note: + * If you need both the sinus and cosinus for a given angle, use the + * function @SW_FT_Vector_Unit. + * + */ + SW_FT_Fixed + SW_FT_Cos( SW_FT_Angle angle ); + + + /************************************************************************* + * + * @function: + * SW_FT_Tan + * + * @description: + * Return the tangent of a given angle in fixed-point format. + * + * @input: + * angle :: + * The input angle. + * + * @return: + * The tangent value. + * + */ + SW_FT_Fixed + SW_FT_Tan( SW_FT_Angle angle ); + + + /************************************************************************* + * + * @function: + * SW_FT_Atan2 + * + * @description: + * Return the arc-tangent corresponding to a given vector (x,y) in + * the 2d plane. + * + * @input: + * x :: + * The horizontal vector coordinate. + * + * y :: + * The vertical vector coordinate. + * + * @return: + * The arc-tangent value (i.e. angle). + * + */ + SW_FT_Angle + SW_FT_Atan2( SW_FT_Fixed x, + SW_FT_Fixed y ); + + + /************************************************************************* + * + * @function: + * SW_FT_Angle_Diff + * + * @description: + * Return the difference between two angles. The result is always + * constrained to the ]-PI..PI] interval. + * + * @input: + * angle1 :: + * First angle. + * + * angle2 :: + * Second angle. + * + * @return: + * Constrained value of `value2-value1'. + * + */ + SW_FT_Angle + SW_FT_Angle_Diff( SW_FT_Angle angle1, + SW_FT_Angle angle2 ); + + + /************************************************************************* + * + * @function: + * SW_FT_Vector_Unit + * + * @description: + * Return the unit vector corresponding to a given angle. After the + * call, the value of `vec.x' will be `sin(angle)', and the value of + * `vec.y' will be `cos(angle)'. + * + * This function is useful to retrieve both the sinus and cosinus of a + * given angle quickly. + * + * @output: + * vec :: + * The address of target vector. + * + * @input: + * angle :: + * The input angle. + * + */ + void + SW_FT_Vector_Unit( SW_FT_Vector* vec, + SW_FT_Angle angle ); + + + /************************************************************************* + * + * @function: + * SW_FT_Vector_Rotate + * + * @description: + * Rotate a vector by a given angle. + * + * @inout: + * vec :: + * The address of target vector. + * + * @input: + * angle :: + * The input angle. + * + */ + void + SW_FT_Vector_Rotate( SW_FT_Vector* vec, + SW_FT_Angle angle ); + + + /************************************************************************* + * + * @function: + * SW_FT_Vector_Length + * + * @description: + * Return the length of a given vector. + * + * @input: + * vec :: + * The address of target vector. + * + * @return: + * The vector length, expressed in the same units that the original + * vector coordinates. + * + */ + SW_FT_Fixed + SW_FT_Vector_Length( SW_FT_Vector* vec ); + + + /************************************************************************* + * + * @function: + * SW_FT_Vector_Polarize + * + * @description: + * Compute both the length and angle of a given vector. + * + * @input: + * vec :: + * The address of source vector. + * + * @output: + * length :: + * The vector length. + * + * angle :: + * The vector angle. + * + */ + void + SW_FT_Vector_Polarize( SW_FT_Vector* vec, + SW_FT_Fixed *length, + SW_FT_Angle *angle ); + + + /************************************************************************* + * + * @function: + * SW_FT_Vector_From_Polar + * + * @description: + * Compute vector coordinates from a length and angle. + * + * @output: + * vec :: + * The address of source vector. + * + * @input: + * length :: + * The vector length. + * + * angle :: + * The vector angle. + * + */ + void + SW_FT_Vector_From_Polar( SW_FT_Vector* vec, + SW_FT_Fixed length, + SW_FT_Angle angle ); + + +#endif // SW_FT_MATH_H diff --git a/3rdparty/software/sw_ft_raster.c b/3rdparty/software/sw_ft_raster.c new file mode 100644 index 000000000..b38d799d6 --- /dev/null +++ b/3rdparty/software/sw_ft_raster.c @@ -0,0 +1,1423 @@ +/***************************************************************************/ +/* */ +/* ftgrays.c */ +/* */ +/* A new `perfect' anti-aliasing renderer (body). */ +/* */ +/* Copyright 2000-2003, 2005-2014 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +/*************************************************************************/ +/* */ +/* This is a new anti-aliasing scan-converter for FreeType 2. The */ +/* algorithm used here is _very_ different from the one in the standard */ +/* `ftraster' module. Actually, `ftgrays' computes the _exact_ */ +/* coverage of the outline on each pixel cell. */ +/* */ +/* It is based on ideas that I initially found in Raph Levien's */ +/* excellent LibArt graphics library (see http://www.levien.com/libart */ +/* for more information, though the web pages do not tell anything */ +/* about the renderer; you'll have to dive into the source code to */ +/* understand how it works). */ +/* */ +/* Note, however, that this is a _very_ different implementation */ +/* compared to Raph's. Coverage information is stored in a very */ +/* different way, and I don't use sorted vector paths. Also, it doesn't */ +/* use floating point values. */ +/* */ +/* This renderer has the following advantages: */ +/* */ +/* - It doesn't need an intermediate bitmap. Instead, one can supply a */ +/* callback function that will be called by the renderer to draw gray */ +/* spans on any target surface. You can thus do direct composition on */ +/* any kind of bitmap, provided that you give the renderer the right */ +/* callback. */ +/* */ +/* - A perfect anti-aliaser, i.e., it computes the _exact_ coverage on */ +/* each pixel cell. */ +/* */ +/* - It performs a single pass on the outline (the `standard' FT2 */ +/* renderer makes two passes). */ +/* */ +/* - It can easily be modified to render to _any_ number of gray levels */ +/* cheaply. */ +/* */ +/* - For small (< 20) pixel sizes, it is faster than the standard */ +/* renderer. */ +/* */ +/*************************************************************************/ + +#include "sw_ft_raster.h" +#include "sw_ft_math.h" + +/* Auxiliary macros for token concatenation. */ +#define SW_FT_ERR_XCAT(x, y) x##y +#define SW_FT_ERR_CAT(x, y) SW_FT_ERR_XCAT(x, y) + +#define SW_FT_BEGIN_STMNT do { +#define SW_FT_END_STMNT \ + } \ + while (0) + +#include +#include +#include +#include +#define SW_FT_UINT_MAX UINT_MAX +#define SW_FT_INT_MAX INT_MAX +#define SW_FT_ULONG_MAX ULONG_MAX +#define SW_FT_CHAR_BIT CHAR_BIT + +#define ft_memset memset + +#define ft_setjmp setjmp +#define ft_longjmp longjmp +#define ft_jmp_buf jmp_buf + +typedef ptrdiff_t SW_FT_PtrDist; + +#define ErrRaster_Invalid_Mode -2 +#define ErrRaster_Invalid_Outline -1 +#define ErrRaster_Invalid_Argument -3 +#define ErrRaster_Memory_Overflow -4 + +#define SW_FT_BEGIN_HEADER +#define SW_FT_END_HEADER + +/* This macro is used to indicate that a function parameter is unused. */ +/* Its purpose is simply to reduce compiler warnings. Note also that */ +/* simply defining it as `(void)x' doesn't avoid warnings with certain */ +/* ANSI compilers (e.g. LCC). */ +#define SW_FT_UNUSED(x) (x) = (x) + +#define SW_FT_THROW(e) SW_FT_ERR_CAT(ErrRaster_, e) + +/* The size in bytes of the render pool used by the scan-line converter */ +/* to do all of its work. */ +#define SW_FT_RENDER_POOL_SIZE 16384L + +typedef int (*SW_FT_Outline_MoveToFunc)(const SW_FT_Vector* to, void* user); + +#define SW_FT_Outline_MoveTo_Func SW_FT_Outline_MoveToFunc + +typedef int (*SW_FT_Outline_LineToFunc)(const SW_FT_Vector* to, void* user); + +#define SW_FT_Outline_LineTo_Func SW_FT_Outline_LineToFunc + +typedef int (*SW_FT_Outline_ConicToFunc)(const SW_FT_Vector* control, + const SW_FT_Vector* to, void* user); + +#define SW_FT_Outline_ConicTo_Func SW_FT_Outline_ConicToFunc + +typedef int (*SW_FT_Outline_CubicToFunc)(const SW_FT_Vector* control1, + const SW_FT_Vector* control2, + const SW_FT_Vector* to, void* user); + +#define SW_FT_Outline_CubicTo_Func SW_FT_Outline_CubicToFunc + +typedef struct SW_FT_Outline_Funcs_ { + SW_FT_Outline_MoveToFunc move_to; + SW_FT_Outline_LineToFunc line_to; + SW_FT_Outline_ConicToFunc conic_to; + SW_FT_Outline_CubicToFunc cubic_to; + + int shift; + SW_FT_Pos delta; + +} SW_FT_Outline_Funcs; + +#define SW_FT_DEFINE_OUTLINE_FUNCS(class_, move_to_, line_to_, conic_to_, \ + cubic_to_, shift_, delta_) \ + static const SW_FT_Outline_Funcs class_ = {move_to_, line_to_, conic_to_, \ + cubic_to_, shift_, delta_}; + +#define SW_FT_DEFINE_RASTER_FUNCS(class_, raster_new_, raster_reset_, \ + raster_render_, raster_done_) \ + const SW_FT_Raster_Funcs class_ = {raster_new_, raster_reset_, \ + raster_render_, raster_done_}; + +#ifndef SW_FT_MEM_SET +#define SW_FT_MEM_SET(d, s, c) ft_memset(d, s, c) +#endif + +#ifndef SW_FT_MEM_ZERO +#define SW_FT_MEM_ZERO(dest, count) SW_FT_MEM_SET(dest, 0, count) +#endif + +/* as usual, for the speed hungry :-) */ + +#undef RAS_ARG +#undef RAS_ARG_ +#undef RAS_VAR +#undef RAS_VAR_ + +#ifndef SW_FT_STATIC_RASTER + +#define RAS_ARG gray_PWorker worker +#define RAS_ARG_ gray_PWorker worker, + +#define RAS_VAR worker +#define RAS_VAR_ worker, + +#else /* SW_FT_STATIC_RASTER */ + +#define RAS_ARG /* empty */ +#define RAS_ARG_ /* empty */ +#define RAS_VAR /* empty */ +#define RAS_VAR_ /* empty */ + +#endif /* SW_FT_STATIC_RASTER */ + +/* must be at least 6 bits! */ +#define PIXEL_BITS 8 + +#undef FLOOR +#undef CEILING +#undef TRUNC +#undef SCALED + +#define ONE_PIXEL (1L << PIXEL_BITS) +#define PIXEL_MASK (-1L << PIXEL_BITS) +#define TRUNC(x) ((TCoord)((x) >> PIXEL_BITS)) +#define SUBPIXELS(x) ((TPos)(x) << PIXEL_BITS) +#define FLOOR(x) ((x) & -ONE_PIXEL) +#define CEILING(x) (((x) + ONE_PIXEL - 1) & -ONE_PIXEL) +#define ROUND(x) (((x) + ONE_PIXEL / 2) & -ONE_PIXEL) + +#if PIXEL_BITS >= 6 +#define UPSCALE(x) ((x) << (PIXEL_BITS - 6)) +#define DOWNSCALE(x) ((x) >> (PIXEL_BITS - 6)) +#else +#define UPSCALE(x) ((x) >> (6 - PIXEL_BITS)) +#define DOWNSCALE(x) ((x) << (6 - PIXEL_BITS)) +#endif + +/* Compute `dividend / divisor' and return both its quotient and */ +/* remainder, cast to a specific type. This macro also ensures that */ +/* the remainder is always positive. */ +#define SW_FT_DIV_MOD(type, dividend, divisor, quotient, remainder) \ + SW_FT_BEGIN_STMNT(quotient) = (type)((dividend) / (divisor)); \ + (remainder) = (type)((dividend) % (divisor)); \ + if ((remainder) < 0) { \ + (quotient)--; \ + (remainder) += (type)(divisor); \ + } \ + SW_FT_END_STMNT + +#ifdef __arm__ +/* Work around a bug specific to GCC which make the compiler fail to */ +/* optimize a division and modulo operation on the same parameters */ +/* into a single call to `__aeabi_idivmod'. See */ +/* */ +/* http://gcc.gnu.org/bugzilla/show_bug.cgi?id=43721 */ +#undef SW_FT_DIV_MOD +#define SW_FT_DIV_MOD(type, dividend, divisor, quotient, remainder) \ + SW_FT_BEGIN_STMNT(quotient) = (type)((dividend) / (divisor)); \ + (remainder) = (type)((dividend) - (quotient) * (divisor)); \ + if ((remainder) < 0) { \ + (quotient)--; \ + (remainder) += (type)(divisor); \ + } \ + SW_FT_END_STMNT +#endif /* __arm__ */ + +/* These macros speed up repetitive divisions by replacing them */ +/* with multiplications and right shifts. */ +#define SW_FT_UDIVPREP(b) \ + long b##_r = (long)(SW_FT_ULONG_MAX >> PIXEL_BITS) / (b) +#define SW_FT_UDIV(a, b) \ + (((unsigned long)(a) * (unsigned long)(b##_r)) >> \ + (sizeof(long) * SW_FT_CHAR_BIT - PIXEL_BITS)) + +/*************************************************************************/ +/* */ +/* TYPE DEFINITIONS */ +/* */ + +/* don't change the following types to SW_FT_Int or SW_FT_Pos, since we might */ +/* need to define them to "float" or "double" when experimenting with */ +/* new algorithms */ + +typedef long TCoord; /* integer scanline/pixel coordinate */ +typedef long TPos; /* sub-pixel coordinate */ + +/* determine the type used to store cell areas. This normally takes at */ +/* least PIXEL_BITS*2 + 1 bits. On 16-bit systems, we need to use */ +/* `long' instead of `int', otherwise bad things happen */ + +#if PIXEL_BITS <= 7 + +typedef int TArea; + +#else /* PIXEL_BITS >= 8 */ + +/* approximately determine the size of integers using an ANSI-C header */ +#if SW_FT_UINT_MAX == 0xFFFFU +typedef long TArea; +#else +typedef int TArea; +#endif + +#endif /* PIXEL_BITS >= 8 */ + +/* maximum number of gray spans in a call to the span callback */ +#define SW_FT_MAX_GRAY_SPANS 256 + +typedef struct TCell_* PCell; + +typedef struct TCell_ { + TPos x; /* same with gray_TWorker.ex */ + TCoord cover; /* same with gray_TWorker.cover */ + TArea area; + PCell next; + +} TCell; + +#if defined(_MSC_VER) /* Visual C++ (and Intel C++) */ +/* We disable the warning `structure was padded due to */ +/* __declspec(align())' in order to compile cleanly with */ +/* the maximum level of warnings. */ +#pragma warning(push) +#pragma warning(disable : 4324) +#endif /* _MSC_VER */ + +typedef struct gray_TWorker_ { + TCoord ex, ey; + TPos min_ex, max_ex; + TPos min_ey, max_ey; + TPos count_ex, count_ey; + + TArea area; + TCoord cover; + int invalid; + + PCell cells; + SW_FT_PtrDist max_cells; + SW_FT_PtrDist num_cells; + + TPos x, y; + + SW_FT_Vector bez_stack[32 * 3 + 1]; + int lev_stack[32]; + + SW_FT_Outline outline; + SW_FT_BBox clip_box; + + int bound_left; + int bound_top; + int bound_right; + int bound_bottom; + + SW_FT_Span gray_spans[SW_FT_MAX_GRAY_SPANS]; + int num_gray_spans; + + SW_FT_Raster_Span_Func render_span; + void* render_span_data; + + int band_size; + int band_shoot; + + ft_jmp_buf jump_buffer; + + void* buffer; + long buffer_size; + + PCell* ycells; + TPos ycount; + +} gray_TWorker, *gray_PWorker; + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#ifndef SW_FT_STATIC_RASTER +#define ras (*worker) +#else +static gray_TWorker ras; +#endif + +typedef struct gray_TRaster_ { + void* memory; + +} gray_TRaster, *gray_PRaster; + +/*************************************************************************/ +/* */ +/* Initialize the cells table. */ +/* */ +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; + + ras.bound_left = INT_MAX; + ras.bound_top = INT_MAX; + ras.bound_right = INT_MIN; + ras.bound_bottom = INT_MIN; +} + +/*************************************************************************/ +/* */ +/* Compute the outline bounding box. */ +/* */ +static void gray_compute_cbox(RAS_ARG) +{ + SW_FT_Outline* outline = &ras.outline; + SW_FT_Vector* vec = outline->points; + SW_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; + } + + /* truncate the bounding box to integer pixels */ + 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; +} + +/*************************************************************************/ +/* */ +/* Record the current cell in the table. */ +/* */ +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) 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; + } +} + +/*************************************************************************/ +/* */ +/* Set the current cell to a new position. */ +/* */ +static void gray_set_cell(RAS_ARG_ TCoord ex, TCoord ey) +{ + /* Move the cell pointer to a new position. We set the `invalid' */ + /* flag to indicate that the cell isn't part of those we're interested */ + /* in during the render phase. This means that: */ + /* */ + /* . the new vertical position must be within min_ey..max_ey-1. */ + /* . the new horizontal position must be strictly less than max_ex */ + /* */ + /* Note that if a cell is to the left of the clipping region, it is */ + /* actually set to the (min_ex-1) horizontal position. */ + + /* All cells that are on the left of the clipping region go to the */ + /* min_ex - 1 horizontal position. */ + ey -= ras.min_ey; + + if (ex > ras.max_ex) ex = ras.max_ex; + + ex -= ras.min_ex; + if (ex < 0) ex = -1; + + /* are we moving to a different cell ? */ + if (ex != ras.ex || ey != ras.ey) { + /* record the current one if it is valid */ + if (!ras.invalid) gray_record_cell(RAS_VAR); + + ras.area = 0; + ras.cover = 0; + ras.ex = ex; + ras.ey = ey; + } + + ras.invalid = + ((unsigned)ey >= (unsigned)ras.count_ey || ex >= ras.count_ex); +} + +/*************************************************************************/ +/* */ +/* Start a new contour at a given cell. */ +/* */ +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); +} + +/*************************************************************************/ +/* */ +/* Render a straight line across multiple cells in any direction. */ +/* */ +static void gray_render_line(RAS_ARG_ TPos to_x, TPos to_y) +{ + TPos dx, dy, fx1, fy1, fx2, fy2; + TCoord ex1, ex2, ey1, ey2; + + ex1 = TRUNC(ras.x); + ex2 = TRUNC(to_x); + ey1 = TRUNC(ras.y); + ey2 = TRUNC(to_y); + + /* perform vertical clipping */ + if ((ey1 >= ras.max_ey && ey2 >= ras.max_ey) || + (ey1 < ras.min_ey && ey2 < ras.min_ey)) + goto End; + + dx = to_x - ras.x; + dy = to_y - ras.y; + + fx1 = ras.x - SUBPIXELS(ex1); + fy1 = ras.y - SUBPIXELS(ey1); + + if (ex1 == ex2 && ey1 == ey2) /* inside one cell */ + ; + else if (dy == 0) /* ex1 != ex2 */ /* any horizontal line */ + { + ex1 = ex2; + gray_set_cell(RAS_VAR_ ex1, ey1); + } else if (dx == 0) { + if (dy > 0) /* vertical line up */ + do { + fy2 = ONE_PIXEL; + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * fx1 * 2; + fy1 = 0; + ey1++; + gray_set_cell(RAS_VAR_ ex1, ey1); + } while (ey1 != ey2); + else /* vertical line down */ + do { + fy2 = 0; + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * fx1 * 2; + fy1 = ONE_PIXEL; + ey1--; + gray_set_cell(RAS_VAR_ ex1, ey1); + } while (ey1 != ey2); + } else /* any other line */ + { + TArea prod = dx * fy1 - dy * fx1; + SW_FT_UDIVPREP(dx); + SW_FT_UDIVPREP(dy); + + /* The fundamental value `prod' determines which side and the */ + /* exact coordinate where the line exits current cell. It is */ + /* also easily updated when moving from one cell to the next. */ + do { + if (prod <= 0 && prod - dx * ONE_PIXEL > 0) /* left */ + { + fx2 = 0; + fy2 = (TPos)SW_FT_UDIV(-prod, -dx); + prod -= dy * ONE_PIXEL; + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * (fx1 + fx2); + fx1 = ONE_PIXEL; + fy1 = fy2; + ex1--; + } else if (prod - dx * ONE_PIXEL <= 0 && + prod - dx * ONE_PIXEL + dy * ONE_PIXEL > 0) /* up */ + { + prod -= dx * ONE_PIXEL; + fx2 = (TPos)SW_FT_UDIV(-prod, dy); + fy2 = ONE_PIXEL; + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * (fx1 + fx2); + fx1 = fx2; + fy1 = 0; + ey1++; + } else if (prod - dx * ONE_PIXEL + dy * ONE_PIXEL <= 0 && + prod + dy * ONE_PIXEL >= 0) /* right */ + { + prod += dy * ONE_PIXEL; + fx2 = ONE_PIXEL; + fy2 = (TPos)SW_FT_UDIV(prod, dx); + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * (fx1 + fx2); + fx1 = 0; + fy1 = fy2; + ex1++; + } else /* ( prod + dy * ONE_PIXEL < 0 && + prod > 0 ) down */ + { + fx2 = (TPos)SW_FT_UDIV(prod, -dy); + fy2 = 0; + prod += dx * ONE_PIXEL; + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * (fx1 + fx2); + fx1 = fx2; + fy1 = ONE_PIXEL; + ey1--; + } + + gray_set_cell(RAS_VAR_ ex1, ey1); + } while (ex1 != ex2 || ey1 != ey2); + } + + fx2 = to_x - SUBPIXELS(ex2); + fy2 = to_y - SUBPIXELS(ey2); + + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * (fx1 + fx2); + +End: + ras.x = to_x; + ras.y = to_y; +} + +static void gray_split_conic(SW_FT_Vector* base) +{ + TPos 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 void gray_render_conic(RAS_ARG_ const SW_FT_Vector* control, + const SW_FT_Vector* to) +{ + TPos dx, dy; + TPos min, max, y; + int top, level; + int* levels; + SW_FT_Vector* arc; + + levels = ras.lev_stack; + + arc = ras.bez_stack; + 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; + top = 0; + + dx = SW_FT_ABS(arc[2].x + arc[0].x - 2 * arc[1].x); + dy = SW_FT_ABS(arc[2].y + arc[0].y - 2 * arc[1].y); + if (dx < dy) dx = dy; + + if (dx < ONE_PIXEL / 4) goto Draw; + + /* short-cut the arc that crosses the current band */ + min = max = arc[0].y; + + y = arc[1].y; + if (y < min) min = y; + if (y > max) max = y; + + y = arc[2].y; + if (y < min) min = y; + if (y > max) max = y; + + if (TRUNC(min) >= ras.max_ey || TRUNC(max) < ras.min_ey) goto Draw; + + level = 0; + do { + dx >>= 2; + level++; + } while (dx > ONE_PIXEL / 4); + + levels[0] = level; + + do { + level = levels[top]; + if (level > 0) { + gray_split_conic(arc); + arc += 2; + top++; + levels[top] = levels[top - 1] = level - 1; + continue; + } + + Draw: + gray_render_line(RAS_VAR_ arc[0].x, arc[0].y); + top--; + arc -= 2; + + } while (top >= 0); +} + +static void gray_split_cubic(SW_FT_Vector* base) +{ + TPos 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 void +gray_render_cubic(RAS_ARG_ const SW_FT_Vector* control1, + const SW_FT_Vector* control2, + const SW_FT_Vector* to) +{ + SW_FT_Vector* arc = ras.bez_stack; + + 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; + + /* short-cut the arc that crosses the current band */ + 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 (;;) + { + /* with each split, control points quickly converge towards */ + /* chord trisection points and the vanishing distances below */ + /* indicate when the segment is flat enough to draw */ + if ( SW_FT_ABS( 2 * arc[0].x - 3 * arc[1].x + arc[3].x ) > ONE_PIXEL / 2 || + SW_FT_ABS( 2 * arc[0].y - 3 * arc[1].y + arc[3].y ) > ONE_PIXEL / 2 || + SW_FT_ABS( arc[0].x - 3 * arc[2].x + 2 * arc[3].x ) > ONE_PIXEL / 2 || + SW_FT_ABS( arc[0].y - 3 * arc[2].y + 2 * arc[3].y ) > ONE_PIXEL / 2 ) + goto Split; + + gray_render_line( RAS_VAR_ arc[0].x, arc[0].y ); + + if ( arc == ras.bez_stack ) + return; + + arc -= 3; + continue; + + Split: + gray_split_cubic( arc ); + arc += 3; + } +} + +static int gray_move_to(const SW_FT_Vector* to, gray_PWorker worker) +{ + TPos x, y; + + /* record current cell, if any */ + if (!ras.invalid) gray_record_cell(RAS_VAR); + + /* start to a new position */ + x = UPSCALE(to->x); + y = UPSCALE(to->y); + + gray_start_cell(RAS_VAR_ TRUNC(x), TRUNC(y)); + + worker->x = x; + worker->y = y; + return 0; +} + +static int gray_line_to(const SW_FT_Vector* to, gray_PWorker worker) +{ + gray_render_line(RAS_VAR_ UPSCALE(to->x), UPSCALE(to->y)); + return 0; +} + +static int gray_conic_to(const SW_FT_Vector* control, const SW_FT_Vector* to, + gray_PWorker worker) +{ + gray_render_conic(RAS_VAR_ control, to); + return 0; +} + +static int gray_cubic_to(const SW_FT_Vector* control1, + const SW_FT_Vector* control2, const SW_FT_Vector* to, + gray_PWorker worker) +{ + gray_render_cubic(RAS_VAR_ control1, control2, to); + return 0; +} + +static void gray_hline(RAS_ARG_ TCoord x, TCoord y, TPos area, TCoord acount) +{ + int coverage; + + /* compute the coverage line's coverage, depending on the */ + /* outline fill rule */ + /* */ + /* the coverage percentage is area/(PIXEL_BITS*PIXEL_BITS*2) */ + /* */ + coverage = (int)(area >> (PIXEL_BITS * 2 + 1 - 8)); + /* use range 0..256 */ + if (coverage < 0) coverage = -coverage; + + if (ras.outline.flags & SW_FT_OUTLINE_EVEN_ODD_FILL) { + coverage &= 511; + + if (coverage > 256) + coverage = 512 - coverage; + else if (coverage == 256) + coverage = 255; + } else { + /* normal non-zero winding rule */ + if (coverage >= 256) coverage = 255; + } + + y += (TCoord)ras.min_ey; + x += (TCoord)ras.min_ex; + + /* SW_FT_Span.x is a 16-bit short, so limit our coordinates appropriately */ + if (x >= 32767) x = 32767; + + /* SW_FT_Span.y is an integer, so limit our coordinates appropriately */ + if (y >= SW_FT_INT_MAX) y = SW_FT_INT_MAX; + + if (coverage) { + SW_FT_Span* span; + int count; + + // update bounding box. + if (x < ras.bound_left) ras.bound_left = x; + if (y < ras.bound_top) ras.bound_top = y; + if (y > ras.bound_bottom) ras.bound_bottom = y; + if (x + acount > ras.bound_right) ras.bound_right = x + acount; + + /* see whether we can add this span to the current list */ + count = ras.num_gray_spans; + span = ras.gray_spans + count - 1; + if (count > 0 && span->y == y && (int)span->x + span->len == (int)x && + span->coverage == coverage) { + span->len = (unsigned short)(span->len + acount); + return; + } + + if (count >= SW_FT_MAX_GRAY_SPANS) { + if (ras.render_span && count > 0) + ras.render_span(count, ras.gray_spans, ras.render_span_data); + +#ifdef DEBUG_GRAYS + + if (1) { + int n; + + fprintf(stderr, "count = %3d ", count); + span = ras.gray_spans; + for (n = 0; n < count; n++, span++) + fprintf(stderr, "[%d , %d..%d] : %d ", span->y, span->x, + span->x + span->len - 1, span->coverage); + fprintf(stderr, "\n"); + } + +#endif /* DEBUG_GRAYS */ + + ras.num_gray_spans = 0; + + span = ras.gray_spans; + } else + span++; + + /* add a gray span to the current list */ + span->x = (short)x; + span->y = (short)y; + span->len = (unsigned short)acount; + span->coverage = (unsigned char)coverage; + + ras.num_gray_spans++; + } +} + +static void gray_sweep(RAS_ARG) +{ + int yindex; + + if (ras.num_cells == 0) return; + + ras.num_gray_spans = 0; + + for (yindex = 0; yindex < ras.ycount; yindex++) { + PCell cell = ras.ycells[yindex]; + TCoord cover = 0; + TCoord x = 0; + + for (; cell != NULL; cell = cell->next) { + TPos 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 (cover != 0) + gray_hline(RAS_VAR_ x, yindex, cover * (ONE_PIXEL * 2), + ras.count_ex - x); + } + + if (ras.render_span && ras.num_gray_spans > 0) + ras.render_span(ras.num_gray_spans, ras.gray_spans, + ras.render_span_data); +} + +/*************************************************************************/ +/* */ +/* The following function should only compile in stand-alone mode, */ +/* i.e., when building this component without the rest of FreeType. */ +/* */ +/*************************************************************************/ + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Outline_Decompose */ +/* */ +/* */ +/* Walk over an outline's structure to decompose it into individual */ +/* segments and Bézier arcs. This function is also able to emit */ +/* `move to' and `close to' operations to indicate the start and end */ +/* of new contours in the outline. */ +/* */ +/* */ +/* outline :: A pointer to the source target. */ +/* */ +/* func_interface :: A table of `emitters', i.e., function pointers */ +/* called during decomposition to indicate path */ +/* operations. */ +/* */ +/* */ +/* user :: A typeless pointer which is passed to each */ +/* emitter during the decomposition. It can be */ +/* used to store the state during the */ +/* decomposition. */ +/* */ +/* */ +/* Error code. 0 means success. */ +/* */ +static int SW_FT_Outline_Decompose(const SW_FT_Outline* outline, + const SW_FT_Outline_Funcs* func_interface, + void* user) +{ +#undef SCALED +#define SCALED(x) (((x) << shift) - delta) + + SW_FT_Vector v_last; + SW_FT_Vector v_control; + SW_FT_Vector v_start; + + SW_FT_Vector* point; + SW_FT_Vector* limit; + char* tags; + + int error; + + int n; /* index of contour in outline */ + int first; /* index of first point in contour */ + char tag; /* current point's state */ + + int shift; + TPos delta; + + if (!outline || !func_interface) return SW_FT_THROW(Invalid_Argument); + + shift = func_interface->shift; + delta = func_interface->delta; + first = 0; + + for (n = 0; n < outline->n_contours; n++) { + int last; /* index of last point in contour */ + + 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 = SW_FT_CURVE_TAG(tags[0]); + + /* A contour cannot start with a cubic control point! */ + if (tag == SW_FT_CURVE_TAG_CUBIC) goto Invalid_Outline; + + /* check first point to determine origin */ + if (tag == SW_FT_CURVE_TAG_CONIC) { + /* first point is conic control. Yes, this happens. */ + if (SW_FT_CURVE_TAG(outline->tags[last]) == SW_FT_CURVE_TAG_ON) { + /* start at last point if it is on the curve */ + v_start = v_last; + limit--; + } else { + /* if both first and last points are conic, */ + /* start at their middle and record its position */ + /* for closure */ + v_start.x = (v_start.x + v_last.x) / 2; + v_start.y = (v_start.y + v_last.y) / 2; + } + point--; + tags--; + } + + error = func_interface->move_to(&v_start, user); + if (error) goto Exit; + + while (point < limit) { + point++; + tags++; + + tag = SW_FT_CURVE_TAG(tags[0]); + switch (tag) { + case SW_FT_CURVE_TAG_ON: /* emit a single line_to */ + { + SW_FT_Vector vec; + + vec.x = SCALED(point->x); + vec.y = SCALED(point->y); + + error = func_interface->line_to(&vec, user); + if (error) goto Exit; + continue; + } + + case SW_FT_CURVE_TAG_CONIC: /* consume conic arcs */ + v_control.x = SCALED(point->x); + v_control.y = SCALED(point->y); + + Do_Conic: + if (point < limit) { + SW_FT_Vector vec; + SW_FT_Vector v_middle; + + point++; + tags++; + tag = SW_FT_CURVE_TAG(tags[0]); + + vec.x = SCALED(point->x); + vec.y = SCALED(point->y); + + if (tag == SW_FT_CURVE_TAG_ON) { + error = + func_interface->conic_to(&v_control, &vec, user); + if (error) goto Exit; + continue; + } + + if (tag != SW_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 = + func_interface->conic_to(&v_control, &v_middle, user); + if (error) goto Exit; + + v_control = vec; + goto Do_Conic; + } + + error = func_interface->conic_to(&v_control, &v_start, user); + goto Close; + + default: /* SW_FT_CURVE_TAG_CUBIC */ + { + SW_FT_Vector vec1, vec2; + + if (point + 1 > limit || + SW_FT_CURVE_TAG(tags[1]) != SW_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) { + SW_FT_Vector vec; + + vec.x = SCALED(point->x); + vec.y = SCALED(point->y); + + error = func_interface->cubic_to(&vec1, &vec2, &vec, user); + if (error) goto Exit; + continue; + } + + error = func_interface->cubic_to(&vec1, &vec2, &v_start, user); + goto Close; + } + } + } + + /* close the contour with a line segment */ + error = func_interface->line_to(&v_start, user); + + Close: + if (error) goto Exit; + + first = last + 1; + } + + return 0; + +Exit: + return error; + +Invalid_Outline: + return SW_FT_THROW(Invalid_Outline); +} + +typedef struct gray_TBand_ { + TPos min, max; + +} gray_TBand; + +SW_FT_DEFINE_OUTLINE_FUNCS(func_interface, + (SW_FT_Outline_MoveTo_Func)gray_move_to, + (SW_FT_Outline_LineTo_Func)gray_line_to, + (SW_FT_Outline_ConicTo_Func)gray_conic_to, + (SW_FT_Outline_CubicTo_Func)gray_cubic_to, 0, 0) + +static int gray_convert_glyph_inner(RAS_ARG) +{ + volatile int error = 0; + + if (ft_setjmp(ras.jump_buffer) == 0) { + error = SW_FT_Outline_Decompose(&ras.outline, &func_interface, &ras); + if (!ras.invalid) gray_record_cell(RAS_VAR); + } else + error = SW_FT_THROW(Memory_Overflow); + + return error; +} + +static int gray_convert_glyph(RAS_ARG) +{ + gray_TBand bands[40]; + gray_TBand* volatile band; + int volatile n, num_bands; + TPos volatile min, max, max_y; + SW_FT_BBox* clip; + + /* Set up state in the raster object */ + gray_compute_cbox(RAS_VAR); + + /* clip to target bitmap, exit if nothing to do */ + 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; + + /* set up vertical bands */ + 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; + long 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 = 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: + /* render pool overflow; we will reduce the render band by half */ + bottom = band->min; + top = band->max; + middle = bottom + ((top - bottom) >> 1); + + /* This is too complex for a single scanline; there must */ + /* be some problems. */ + if (middle == bottom) { + return 1; + } + + 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.band_shoot > 8 && ras.band_size > 16) + ras.band_size = ras.band_size / 2; + + return 0; +} + +static int gray_raster_render(gray_PRaster raster, + const SW_FT_Raster_Params* params) +{ + SW_FT_UNUSED(raster); + const SW_FT_Outline* outline = (const SW_FT_Outline*)params->source; + + gray_TWorker worker[1]; + + TCell buffer[SW_FT_RENDER_POOL_SIZE / sizeof(TCell)]; + long buffer_size = sizeof(buffer); + int band_size = (int)(buffer_size / (long)(sizeof(TCell) * 8)); + + if (!outline) return SW_FT_THROW(Invalid_Outline); + + /* return immediately if the outline is empty */ + if (outline->n_points == 0 || outline->n_contours <= 0) return 0; + + if (!outline->contours || !outline->points) + return SW_FT_THROW(Invalid_Outline); + + if (outline->n_points != outline->contours[outline->n_contours - 1] + 1) + return SW_FT_THROW(Invalid_Outline); + + /* this version does not support monochrome rendering */ + if (!(params->flags & SW_FT_RASTER_FLAG_AA)) + return SW_FT_THROW(Invalid_Mode); + + if (params->flags & SW_FT_RASTER_FLAG_CLIP) + ras.clip_box = params->clip_box; + else { + ras.clip_box.xMin = -32768L; + ras.clip_box.yMin = -32768L; + ras.clip_box.xMax = 32767L; + ras.clip_box.yMax = 32767L; + } + + gray_init_cells(RAS_VAR_ buffer, buffer_size); + + ras.outline = *outline; + ras.num_cells = 0; + ras.invalid = 1; + ras.band_size = band_size; + ras.num_gray_spans = 0; + + ras.render_span = (SW_FT_Raster_Span_Func)params->gray_spans; + ras.render_span_data = params->user; + + gray_convert_glyph(RAS_VAR); + params->bbox_cb(ras.bound_left, ras.bound_top, + ras.bound_right - ras.bound_left, + ras.bound_bottom - ras.bound_top + 1, params->user); + return 1; +} + +/**** RASTER OBJECT CREATION: In stand-alone mode, we simply use *****/ +/**** a static object. *****/ + +static int gray_raster_new(SW_FT_Raster* araster) +{ + static gray_TRaster the_raster; + + *araster = (SW_FT_Raster)&the_raster; + SW_FT_MEM_ZERO(&the_raster, sizeof(the_raster)); + + return 0; +} + +static void gray_raster_done(SW_FT_Raster raster) +{ + /* nothing */ + SW_FT_UNUSED(raster); +} + +static void gray_raster_reset(SW_FT_Raster raster, char* pool_base, + long pool_size) +{ + SW_FT_UNUSED(raster); + SW_FT_UNUSED(pool_base); + SW_FT_UNUSED(pool_size); +} + +SW_FT_DEFINE_RASTER_FUNCS(sw_ft_grays_raster, + + (SW_FT_Raster_New_Func)gray_raster_new, + (SW_FT_Raster_Reset_Func)gray_raster_reset, + (SW_FT_Raster_Render_Func)gray_raster_render, + (SW_FT_Raster_Done_Func)gray_raster_done) + +/* END */ diff --git a/3rdparty/software/sw_ft_raster.h b/3rdparty/software/sw_ft_raster.h new file mode 100644 index 000000000..f4499dce9 --- /dev/null +++ b/3rdparty/software/sw_ft_raster.h @@ -0,0 +1,607 @@ +#ifndef SW_FT_IMG_H +#define SW_FT_IMG_H +/***************************************************************************/ +/* */ +/* ftimage.h */ +/* */ +/* FreeType glyph image formats and default raster interface */ +/* (specification). */ +/* */ +/* Copyright 1996-2010, 2013 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + + /*************************************************************************/ + /* */ + /* Note: A `raster' is simply a scan-line converter, used to render */ + /* SW_FT_Outlines into SW_FT_Bitmaps. */ + /* */ + /*************************************************************************/ + +#include "sw_ft_types.h" + + /*************************************************************************/ + /* */ + /* */ + /* FT_BBox */ + /* */ + /* */ + /* A structure used to hold an outline's bounding box, i.e., the */ + /* coordinates of its extrema in the horizontal and vertical */ + /* directions. */ + /* */ + /* */ + /* xMin :: The horizontal minimum (left-most). */ + /* */ + /* yMin :: The vertical minimum (bottom-most). */ + /* */ + /* xMax :: The horizontal maximum (right-most). */ + /* */ + /* yMax :: The vertical maximum (top-most). */ + /* */ + /* */ + /* The bounding box is specified with the coordinates of the lower */ + /* left and the upper right corner. In PostScript, those values are */ + /* often called (llx,lly) and (urx,ury), respectively. */ + /* */ + /* If `yMin' is negative, this value gives the glyph's descender. */ + /* Otherwise, the glyph doesn't descend below the baseline. */ + /* Similarly, if `ymax' is positive, this value gives the glyph's */ + /* ascender. */ + /* */ + /* `xMin' gives the horizontal distance from the glyph's origin to */ + /* the left edge of the glyph's bounding box. If `xMin' is negative, */ + /* the glyph extends to the left of the origin. */ + /* */ + typedef struct SW_FT_BBox_ + { + SW_FT_Pos xMin, yMin; + SW_FT_Pos xMax, yMax; + + } SW_FT_BBox; + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Outline */ +/* */ +/* */ +/* This structure is used to describe an outline to the scan-line */ +/* converter. */ +/* */ +/* */ +/* n_contours :: The number of contours in the outline. */ +/* */ +/* n_points :: The number of points in the outline. */ +/* */ +/* points :: A pointer to an array of `n_points' @SW_FT_Vector */ +/* elements, giving the outline's point coordinates. */ +/* */ +/* tags :: A pointer to an array of `n_points' chars, giving */ +/* each outline point's type. */ +/* */ +/* If bit~0 is unset, the point is `off' the curve, */ +/* i.e., a Bézier control point, while it is `on' if */ +/* set. */ +/* */ +/* Bit~1 is meaningful for `off' points only. If set, */ +/* it indicates a third-order Bézier arc control point; */ +/* and a second-order control point if unset. */ +/* */ +/* If bit~2 is set, bits 5-7 contain the drop-out mode */ +/* (as defined in the OpenType specification; the value */ +/* is the same as the argument to the SCANMODE */ +/* instruction). */ +/* */ +/* Bits 3 and~4 are reserved for internal purposes. */ +/* */ +/* contours :: An array of `n_contours' shorts, giving the end */ +/* point of each contour within the outline. For */ +/* example, the first contour is defined by the points */ +/* `0' to `contours[0]', the second one is defined by */ +/* the points `contours[0]+1' to `contours[1]', etc. */ +/* */ +/* flags :: A set of bit flags used to characterize the outline */ +/* and give hints to the scan-converter and hinter on */ +/* how to convert/grid-fit it. See @SW_FT_OUTLINE_FLAGS.*/ +/* */ +typedef struct SW_FT_Outline_ +{ + short n_contours; /* number of contours in glyph */ + short n_points; /* number of points in the glyph */ + + SW_FT_Vector* points; /* the outline's points */ + char* tags; /* the points flags */ + short* contours; /* the contour end points */ + char* contours_flag; /* the contour open flags */ + + int flags; /* outline masks */ + +} SW_FT_Outline; + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_OUTLINE_FLAGS */ + /* */ + /* */ + /* A list of bit-field constants use for the flags in an outline's */ + /* `flags' field. */ + /* */ + /* */ + /* SW_FT_OUTLINE_NONE :: */ + /* Value~0 is reserved. */ + /* */ + /* SW_FT_OUTLINE_OWNER :: */ + /* If set, this flag indicates that the outline's field arrays */ + /* (i.e., `points', `flags', and `contours') are `owned' by the */ + /* outline object, and should thus be freed when it is destroyed. */ + /* */ + /* SW_FT_OUTLINE_EVEN_ODD_FILL :: */ + /* By default, outlines are filled using the non-zero winding rule. */ + /* If set to 1, the outline will be filled using the even-odd fill */ + /* rule (only works with the smooth rasterizer). */ + /* */ + /* SW_FT_OUTLINE_REVERSE_FILL :: */ + /* By default, outside contours of an outline are oriented in */ + /* clock-wise direction, as defined in the TrueType specification. */ + /* This flag is set if the outline uses the opposite direction */ + /* (typically for Type~1 fonts). This flag is ignored by the scan */ + /* converter. */ + /* */ + /* */ + /* */ + /* There exists a second mechanism to pass the drop-out mode to the */ + /* B/W rasterizer; see the `tags' field in @SW_FT_Outline. */ + /* */ + /* Please refer to the description of the `SCANTYPE' instruction in */ + /* the OpenType specification (in file `ttinst1.doc') how simple */ + /* drop-outs, smart drop-outs, and stubs are defined. */ + /* */ +#define SW_FT_OUTLINE_NONE 0x0 +#define SW_FT_OUTLINE_OWNER 0x1 +#define SW_FT_OUTLINE_EVEN_ODD_FILL 0x2 +#define SW_FT_OUTLINE_REVERSE_FILL 0x4 + + /* */ + +#define SW_FT_CURVE_TAG( flag ) ( flag & 3 ) + +#define SW_FT_CURVE_TAG_ON 1 +#define SW_FT_CURVE_TAG_CONIC 0 +#define SW_FT_CURVE_TAG_CUBIC 2 + + +#define SW_FT_Curve_Tag_On SW_FT_CURVE_TAG_ON +#define SW_FT_Curve_Tag_Conic SW_FT_CURVE_TAG_CONIC +#define SW_FT_Curve_Tag_Cubic SW_FT_CURVE_TAG_CUBIC + + /*************************************************************************/ + /* */ + /* A raster is a scan converter, in charge of rendering an outline into */ + /* a a bitmap. This section contains the public API for rasters. */ + /* */ + /* Note that in FreeType 2, all rasters are now encapsulated within */ + /* specific modules called `renderers'. See `ftrender.h' for more */ + /* details on renderers. */ + /* */ + /*************************************************************************/ + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Raster */ + /* */ + /* */ + /* A handle (pointer) to a raster object. Each object can be used */ + /* independently to convert an outline into a bitmap or pixmap. */ + /* */ + typedef struct SW_FT_RasterRec_* SW_FT_Raster; + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Span */ + /* */ + /* */ + /* A structure used to model a single span of gray (or black) pixels */ + /* when rendering a monochrome or anti-aliased bitmap. */ + /* */ + /* */ + /* x :: The span's horizontal start position. */ + /* */ + /* len :: The span's length in pixels. */ + /* */ + /* coverage :: The span color/coverage, ranging from 0 (background) */ + /* to 255 (foreground). Only used for anti-aliased */ + /* rendering. */ + /* */ + /* */ + /* This structure is used by the span drawing callback type named */ + /* @SW_FT_SpanFunc that takes the y~coordinate of the span as a */ + /* parameter. */ + /* */ + /* The coverage value is always between 0 and 255. If you want less */ + /* gray values, the callback function has to reduce them. */ + /* */ + typedef struct SW_FT_Span_ + { + short x; + short y; + unsigned short len; + unsigned char coverage; + + } SW_FT_Span; + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_SpanFunc */ + /* */ + /* */ + /* A function used as a call-back by the anti-aliased renderer in */ + /* order to let client applications draw themselves the gray pixel */ + /* spans on each scan line. */ + /* */ + /* */ + /* y :: The scanline's y~coordinate. */ + /* */ + /* count :: The number of spans to draw on this scanline. */ + /* */ + /* spans :: A table of `count' spans to draw on the scanline. */ + /* */ + /* user :: User-supplied data that is passed to the callback. */ + /* */ + /* */ + /* This callback allows client applications to directly render the */ + /* gray spans of the anti-aliased bitmap to any kind of surfaces. */ + /* */ + /* This can be used to write anti-aliased outlines directly to a */ + /* given background bitmap, and even perform translucency. */ + /* */ + /* Note that the `count' field cannot be greater than a fixed value */ + /* defined by the `SW_FT_MAX_GRAY_SPANS' configuration macro in */ + /* `ftoption.h'. By default, this value is set to~32, which means */ + /* that if there are more than 32~spans on a given scanline, the */ + /* callback is called several times with the same `y' parameter in */ + /* order to draw all callbacks. */ + /* */ + /* Otherwise, the callback is only called once per scan-line, and */ + /* only for those scanlines that do have `gray' pixels on them. */ + /* */ + typedef void + (*SW_FT_SpanFunc)( int count, + const SW_FT_Span* spans, + void* user ); + + typedef void + (*SW_FT_BboxFunc)( int x, int y, int w, int h, + void* user); + +#define SW_FT_Raster_Span_Func SW_FT_SpanFunc + + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_RASTER_FLAG_XXX */ + /* */ + /* */ + /* A list of bit flag constants as used in the `flags' field of a */ + /* @SW_FT_Raster_Params structure. */ + /* */ + /* */ + /* SW_FT_RASTER_FLAG_DEFAULT :: This value is 0. */ + /* */ + /* SW_FT_RASTER_FLAG_AA :: This flag is set to indicate that an */ + /* anti-aliased glyph image should be */ + /* generated. Otherwise, it will be */ + /* monochrome (1-bit). */ + /* */ + /* SW_FT_RASTER_FLAG_DIRECT :: This flag is set to indicate direct */ + /* rendering. In this mode, client */ + /* applications must provide their own span */ + /* callback. This lets them directly */ + /* draw or compose over an existing bitmap. */ + /* If this bit is not set, the target */ + /* pixmap's buffer _must_ be zeroed before */ + /* rendering. */ + /* */ + /* Note that for now, direct rendering is */ + /* only possible with anti-aliased glyphs. */ + /* */ + /* SW_FT_RASTER_FLAG_CLIP :: This flag is only used in direct */ + /* rendering mode. If set, the output will */ + /* be clipped to a box specified in the */ + /* `clip_box' field of the */ + /* @SW_FT_Raster_Params structure. */ + /* */ + /* Note that by default, the glyph bitmap */ + /* is clipped to the target pixmap, except */ + /* in direct rendering mode where all spans */ + /* are generated if no clipping box is set. */ + /* */ +#define SW_FT_RASTER_FLAG_DEFAULT 0x0 +#define SW_FT_RASTER_FLAG_AA 0x1 +#define SW_FT_RASTER_FLAG_DIRECT 0x2 +#define SW_FT_RASTER_FLAG_CLIP 0x4 + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Raster_Params */ + /* */ + /* */ + /* A structure to hold the arguments used by a raster's render */ + /* function. */ + /* */ + /* */ + /* target :: The target bitmap. */ + /* */ + /* source :: A pointer to the source glyph image (e.g., an */ + /* @SW_FT_Outline). */ + /* */ + /* flags :: The rendering flags. */ + /* */ + /* gray_spans :: The gray span drawing callback. */ + /* */ + /* black_spans :: The black span drawing callback. UNIMPLEMENTED! */ + /* */ + /* bit_test :: The bit test callback. UNIMPLEMENTED! */ + /* */ + /* bit_set :: The bit set callback. UNIMPLEMENTED! */ + /* */ + /* user :: User-supplied data that is passed to each drawing */ + /* callback. */ + /* */ + /* clip_box :: An optional clipping box. It is only used in */ + /* direct rendering mode. Note that coordinates here */ + /* should be expressed in _integer_ pixels (and not in */ + /* 26.6 fixed-point units). */ + /* */ + /* */ + /* An anti-aliased glyph bitmap is drawn if the @SW_FT_RASTER_FLAG_AA */ + /* bit flag is set in the `flags' field, otherwise a monochrome */ + /* bitmap is generated. */ + /* */ + /* If the @SW_FT_RASTER_FLAG_DIRECT bit flag is set in `flags', the */ + /* raster will call the `gray_spans' callback to draw gray pixel */ + /* spans, in the case of an aa glyph bitmap, it will call */ + /* `black_spans', and `bit_test' and `bit_set' in the case of a */ + /* monochrome bitmap. This allows direct composition over a */ + /* pre-existing bitmap through user-provided callbacks to perform the */ + /* span drawing/composition. */ + /* */ + /* Note that the `bit_test' and `bit_set' callbacks are required when */ + /* rendering a monochrome bitmap, as they are crucial to implement */ + /* correct drop-out control as defined in the TrueType specification. */ + /* */ + typedef struct SW_FT_Raster_Params_ + { + const void* source; + int flags; + SW_FT_SpanFunc gray_spans; + SW_FT_BboxFunc bbox_cb; + void* user; + SW_FT_BBox clip_box; + + } SW_FT_Raster_Params; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Outline_Check */ +/* */ +/* */ +/* Check the contents of an outline descriptor. */ +/* */ +/* */ +/* outline :: A handle to a source outline. */ +/* */ +/* */ +/* FreeType error code. 0~means success. */ +/* */ +SW_FT_Error +SW_FT_Outline_Check( SW_FT_Outline* outline ); + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Outline_Get_CBox */ +/* */ +/* */ +/* Return an outline's `control box'. The control box encloses all */ +/* the outline's points, including Bézier control points. Though it */ +/* coincides with the exact bounding box for most glyphs, it can be */ +/* slightly larger in some situations (like when rotating an outline */ +/* that contains Bézier outside arcs). */ +/* */ +/* Computing the control box is very fast, while getting the bounding */ +/* box can take much more time as it needs to walk over all segments */ +/* and arcs in the outline. To get the latter, you can use the */ +/* `ftbbox' component, which is dedicated to this single task. */ +/* */ +/* */ +/* outline :: A pointer to the source outline descriptor. */ +/* */ +/* */ +/* acbox :: The outline's control box. */ +/* */ +/* */ +/* See @SW_FT_Glyph_Get_CBox for a discussion of tricky fonts. */ +/* */ +void +SW_FT_Outline_Get_CBox( const SW_FT_Outline* outline, + SW_FT_BBox *acbox ); + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Raster_NewFunc */ + /* */ + /* */ + /* A function used to create a new raster object. */ + /* */ + /* */ + /* memory :: A handle to the memory allocator. */ + /* */ + /* */ + /* raster :: A handle to the new raster object. */ + /* */ + /* */ + /* Error code. 0~means success. */ + /* */ + /* */ + /* The `memory' parameter is a typeless pointer in order to avoid */ + /* un-wanted dependencies on the rest of the FreeType code. In */ + /* practice, it is an @SW_FT_Memory object, i.e., a handle to the */ + /* standard FreeType memory allocator. However, this field can be */ + /* completely ignored by a given raster implementation. */ + /* */ + typedef int + (*SW_FT_Raster_NewFunc)( SW_FT_Raster* raster ); + +#define SW_FT_Raster_New_Func SW_FT_Raster_NewFunc + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Raster_DoneFunc */ + /* */ + /* */ + /* A function used to destroy a given raster object. */ + /* */ + /* */ + /* raster :: A handle to the raster object. */ + /* */ + typedef void + (*SW_FT_Raster_DoneFunc)( SW_FT_Raster raster ); + +#define SW_FT_Raster_Done_Func SW_FT_Raster_DoneFunc + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Raster_ResetFunc */ + /* */ + /* */ + /* FreeType provides an area of memory called the `render pool', */ + /* available to all registered rasters. This pool can be freely used */ + /* during a given scan-conversion but is shared by all rasters. Its */ + /* content is thus transient. */ + /* */ + /* This function is called each time the render pool changes, or just */ + /* after a new raster object is created. */ + /* */ + /* */ + /* raster :: A handle to the new raster object. */ + /* */ + /* pool_base :: The address in memory of the render pool. */ + /* */ + /* pool_size :: The size in bytes of the render pool. */ + /* */ + /* */ + /* Rasters can ignore the render pool and rely on dynamic memory */ + /* allocation if they want to (a handle to the memory allocator is */ + /* passed to the raster constructor). However, this is not */ + /* recommended for efficiency purposes. */ + /* */ + typedef void + (*SW_FT_Raster_ResetFunc)( SW_FT_Raster raster, + unsigned char* pool_base, + unsigned long pool_size ); + +#define SW_FT_Raster_Reset_Func SW_FT_Raster_ResetFunc + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Raster_RenderFunc */ + /* */ + /* */ + /* Invoke a given raster to scan-convert a given glyph image into a */ + /* target bitmap. */ + /* */ + /* */ + /* raster :: A handle to the raster object. */ + /* */ + /* params :: A pointer to an @SW_FT_Raster_Params structure used to */ + /* store the rendering parameters. */ + /* */ + /* */ + /* Error code. 0~means success. */ + /* */ + /* */ + /* The exact format of the source image depends on the raster's glyph */ + /* format defined in its @SW_FT_Raster_Funcs structure. It can be an */ + /* @SW_FT_Outline or anything else in order to support a large array of */ + /* glyph formats. */ + /* */ + /* Note also that the render function can fail and return a */ + /* `SW_FT_Err_Unimplemented_Feature' error code if the raster used does */ + /* not support direct composition. */ + /* */ + /* XXX: For now, the standard raster doesn't support direct */ + /* composition but this should change for the final release (see */ + /* the files `demos/src/ftgrays.c' and `demos/src/ftgrays2.c' */ + /* for examples of distinct implementations that support direct */ + /* composition). */ + /* */ + typedef int + (*SW_FT_Raster_RenderFunc)( SW_FT_Raster raster, + const SW_FT_Raster_Params* params ); + +#define SW_FT_Raster_Render_Func SW_FT_Raster_RenderFunc + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Raster_Funcs */ + /* */ + /* */ + /* A structure used to describe a given raster class to the library. */ + /* */ + /* */ + /* glyph_format :: The supported glyph format for this raster. */ + /* */ + /* raster_new :: The raster constructor. */ + /* */ + /* raster_reset :: Used to reset the render pool within the raster. */ + /* */ + /* raster_render :: A function to render a glyph into a given bitmap. */ + /* */ + /* raster_done :: The raster destructor. */ + /* */ + typedef struct SW_FT_Raster_Funcs_ + { + SW_FT_Raster_NewFunc raster_new; + SW_FT_Raster_ResetFunc raster_reset; + SW_FT_Raster_RenderFunc raster_render; + SW_FT_Raster_DoneFunc raster_done; + + } SW_FT_Raster_Funcs; + + +extern const SW_FT_Raster_Funcs sw_ft_grays_raster; + +#endif // SW_FT_IMG_H diff --git a/3rdparty/software/sw_ft_stroker.c b/3rdparty/software/sw_ft_stroker.c new file mode 100644 index 000000000..259fe279f --- /dev/null +++ b/3rdparty/software/sw_ft_stroker.c @@ -0,0 +1,1939 @@ + +/***************************************************************************/ +/* */ +/* ftstroke.c */ +/* */ +/* FreeType path stroker (body). */ +/* */ +/* Copyright 2002-2006, 2008-2011, 2013 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#include "sw_ft_stroker.h" +#include +#include +#include +#include "sw_ft_math.h" + +/*************************************************************************/ +/*************************************************************************/ +/***** *****/ +/***** BEZIER COMPUTATIONS *****/ +/***** *****/ +/*************************************************************************/ +/*************************************************************************/ + +#define SW_FT_SMALL_CONIC_THRESHOLD (SW_FT_ANGLE_PI / 6) +#define SW_FT_SMALL_CUBIC_THRESHOLD (SW_FT_ANGLE_PI / 8) + +#define SW_FT_EPSILON 2 + +#define SW_FT_IS_SMALL(x) ((x) > -SW_FT_EPSILON && (x) < SW_FT_EPSILON) + +static SW_FT_Pos ft_pos_abs(SW_FT_Pos x) +{ + return x >= 0 ? x : -x; +} + +static void ft_conic_split(SW_FT_Vector* base) +{ + SW_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 SW_FT_Bool ft_conic_is_small_enough(SW_FT_Vector* base, + SW_FT_Angle* angle_in, + SW_FT_Angle* angle_out) +{ + SW_FT_Vector d1, d2; + SW_FT_Angle theta; + SW_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 = SW_FT_IS_SMALL(d1.x) && SW_FT_IS_SMALL(d1.y); + close2 = SW_FT_IS_SMALL(d2.x) && SW_FT_IS_SMALL(d2.y); + + if (close1) { + if (close2) { + /* basically a point; */ + /* do nothing to retain original direction */ + } else { + *angle_in = *angle_out = SW_FT_Atan2(d2.x, d2.y); + } + } else /* !close1 */ + { + if (close2) { + *angle_in = *angle_out = SW_FT_Atan2(d1.x, d1.y); + } else { + *angle_in = SW_FT_Atan2(d1.x, d1.y); + *angle_out = SW_FT_Atan2(d2.x, d2.y); + } + } + + theta = ft_pos_abs(SW_FT_Angle_Diff(*angle_in, *angle_out)); + + return SW_FT_BOOL(theta < SW_FT_SMALL_CONIC_THRESHOLD); +} + +static void ft_cubic_split(SW_FT_Vector* base) +{ + SW_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; +} + +/* Return the average of `angle1' and `angle2'. */ +/* This gives correct result even if `angle1' and `angle2' */ +/* have opposite signs. */ +static SW_FT_Angle ft_angle_mean(SW_FT_Angle angle1, SW_FT_Angle angle2) +{ + return angle1 + SW_FT_Angle_Diff(angle1, angle2) / 2; +} + +static SW_FT_Bool ft_cubic_is_small_enough(SW_FT_Vector* base, + SW_FT_Angle* angle_in, + SW_FT_Angle* angle_mid, + SW_FT_Angle* angle_out) +{ + SW_FT_Vector d1, d2, d3; + SW_FT_Angle theta1, theta2; + SW_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 = SW_FT_IS_SMALL(d1.x) && SW_FT_IS_SMALL(d1.y); + close2 = SW_FT_IS_SMALL(d2.x) && SW_FT_IS_SMALL(d2.y); + close3 = SW_FT_IS_SMALL(d3.x) && SW_FT_IS_SMALL(d3.y); + + if (close1) { + if (close2) { + if (close3) { + /* basically a point; */ + /* do nothing to retain original direction */ + } else /* !close3 */ + { + *angle_in = *angle_mid = *angle_out = SW_FT_Atan2(d3.x, d3.y); + } + } else /* !close2 */ + { + if (close3) { + *angle_in = *angle_mid = *angle_out = SW_FT_Atan2(d2.x, d2.y); + } else /* !close3 */ + { + *angle_in = *angle_mid = SW_FT_Atan2(d2.x, d2.y); + *angle_out = SW_FT_Atan2(d3.x, d3.y); + } + } + } else /* !close1 */ + { + if (close2) { + if (close3) { + *angle_in = *angle_mid = *angle_out = SW_FT_Atan2(d1.x, d1.y); + } else /* !close3 */ + { + *angle_in = SW_FT_Atan2(d1.x, d1.y); + *angle_out = SW_FT_Atan2(d3.x, d3.y); + *angle_mid = ft_angle_mean(*angle_in, *angle_out); + } + } else /* !close2 */ + { + if (close3) { + *angle_in = SW_FT_Atan2(d1.x, d1.y); + *angle_mid = *angle_out = SW_FT_Atan2(d2.x, d2.y); + } else /* !close3 */ + { + *angle_in = SW_FT_Atan2(d1.x, d1.y); + *angle_mid = SW_FT_Atan2(d2.x, d2.y); + *angle_out = SW_FT_Atan2(d3.x, d3.y); + } + } + } + + theta1 = ft_pos_abs(SW_FT_Angle_Diff(*angle_in, *angle_mid)); + theta2 = ft_pos_abs(SW_FT_Angle_Diff(*angle_mid, *angle_out)); + + return SW_FT_BOOL(theta1 < SW_FT_SMALL_CUBIC_THRESHOLD && + theta2 < SW_FT_SMALL_CUBIC_THRESHOLD); +} + +/*************************************************************************/ +/*************************************************************************/ +/***** *****/ +/***** STROKE BORDERS *****/ +/***** *****/ +/*************************************************************************/ +/*************************************************************************/ + +typedef enum SW_FT_StrokeTags_ { + SW_FT_STROKE_TAG_ON = 1, /* on-curve point */ + SW_FT_STROKE_TAG_CUBIC = 2, /* cubic off-point */ + SW_FT_STROKE_TAG_BEGIN = 4, /* sub-path start */ + SW_FT_STROKE_TAG_END = 8 /* sub-path end */ + +} SW_FT_StrokeTags; + +#define SW_FT_STROKE_TAG_BEGIN_END \ + (SW_FT_STROKE_TAG_BEGIN | SW_FT_STROKE_TAG_END) + +typedef struct SW_FT_StrokeBorderRec_ { + SW_FT_UInt num_points; + SW_FT_UInt max_points; + SW_FT_Vector* points; + SW_FT_Byte* tags; + SW_FT_Bool movable; /* TRUE for ends of lineto borders */ + SW_FT_Int start; /* index of current sub-path start point */ + SW_FT_Bool valid; + +} SW_FT_StrokeBorderRec, *SW_FT_StrokeBorder; + +SW_FT_Error SW_FT_Outline_Check(SW_FT_Outline* outline) +{ + if (outline) { + SW_FT_Int n_points = outline->n_points; + SW_FT_Int n_contours = outline->n_contours; + SW_FT_Int end0, end; + SW_FT_Int n; + + /* empty glyph? */ + if (n_points == 0 && n_contours == 0) return 0; + + /* check point and contour counts */ + if (n_points <= 0 || n_contours <= 0) goto Bad; + + end0 = end = -1; + for (n = 0; n < n_contours; n++) { + end = outline->contours[n]; + + /* note that we don't accept empty contours */ + if (end <= end0 || end >= n_points) goto Bad; + + end0 = end; + } + + if (end != n_points - 1) goto Bad; + + /* XXX: check the tags array */ + return 0; + } + +Bad: + return -1; // SW_FT_THROW( Invalid_Argument ); +} + +void SW_FT_Outline_Get_CBox(const SW_FT_Outline* outline, SW_FT_BBox* acbox) +{ + SW_FT_Pos xMin, yMin, xMax, yMax; + + if (outline && acbox) { + if (outline->n_points == 0) { + xMin = 0; + yMin = 0; + xMax = 0; + yMax = 0; + } else { + SW_FT_Vector* vec = outline->points; + SW_FT_Vector* limit = vec + outline->n_points; + + xMin = xMax = vec->x; + yMin = yMax = vec->y; + vec++; + + for (; vec < limit; vec++) { + SW_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 SW_FT_Error ft_stroke_border_grow(SW_FT_StrokeBorder border, + SW_FT_UInt new_points) +{ + SW_FT_UInt old_max = border->max_points; + SW_FT_UInt new_max = border->num_points + new_points; + SW_FT_Error error = 0; + + if (new_max > old_max) { + SW_FT_UInt cur_max = old_max; + + while (cur_max < new_max) cur_max += (cur_max >> 1) + 16; + + border->points = (SW_FT_Vector*)realloc(border->points, + cur_max * sizeof(SW_FT_Vector)); + border->tags = + (SW_FT_Byte*)realloc(border->tags, cur_max * sizeof(SW_FT_Byte)); + + if (!border->points || !border->tags) goto Exit; + + border->max_points = cur_max; + } + +Exit: + return error; +} + +static void ft_stroke_border_close(SW_FT_StrokeBorder border, + SW_FT_Bool reverse) +{ + SW_FT_UInt start = border->start; + SW_FT_UInt count = border->num_points; + + assert(border->start >= 0); + + /* don't record empty paths! */ + if (count <= start + 1U) + border->num_points = start; + else { + /* copy the last point to the start of this sub-path, since */ + /* it contains the `adjusted' starting coordinates */ + border->num_points = --count; + border->points[start] = border->points[count]; + + if (reverse) { + /* reverse the points */ + { + SW_FT_Vector* vec1 = border->points + start + 1; + SW_FT_Vector* vec2 = border->points + count - 1; + + for (; vec1 < vec2; vec1++, vec2--) { + SW_FT_Vector tmp; + + tmp = *vec1; + *vec1 = *vec2; + *vec2 = tmp; + } + } + + /* then the tags */ + { + SW_FT_Byte* tag1 = border->tags + start + 1; + SW_FT_Byte* tag2 = border->tags + count - 1; + + for (; tag1 < tag2; tag1++, tag2--) { + SW_FT_Byte tmp; + + tmp = *tag1; + *tag1 = *tag2; + *tag2 = tmp; + } + } + } + + border->tags[start] |= SW_FT_STROKE_TAG_BEGIN; + border->tags[count - 1] |= SW_FT_STROKE_TAG_END; + } + + border->start = -1; + border->movable = FALSE; +} + +static SW_FT_Error ft_stroke_border_lineto(SW_FT_StrokeBorder border, + SW_FT_Vector* to, SW_FT_Bool movable) +{ + SW_FT_Error error = 0; + + assert(border->start >= 0); + + if (border->movable) { + /* move last point */ + border->points[border->num_points - 1] = *to; + } else { + /* don't add zero-length lineto */ + if (border->num_points > 0 && + SW_FT_IS_SMALL(border->points[border->num_points - 1].x - to->x) && + SW_FT_IS_SMALL(border->points[border->num_points - 1].y - to->y)) + return error; + + /* add one point */ + error = ft_stroke_border_grow(border, 1); + if (!error) { + SW_FT_Vector* vec = border->points + border->num_points; + SW_FT_Byte* tag = border->tags + border->num_points; + + vec[0] = *to; + tag[0] = SW_FT_STROKE_TAG_ON; + + border->num_points += 1; + } + } + border->movable = movable; + return error; +} + +static SW_FT_Error ft_stroke_border_conicto(SW_FT_StrokeBorder border, + SW_FT_Vector* control, + SW_FT_Vector* to) +{ + SW_FT_Error error; + + assert(border->start >= 0); + + error = ft_stroke_border_grow(border, 2); + if (!error) { + SW_FT_Vector* vec = border->points + border->num_points; + SW_FT_Byte* tag = border->tags + border->num_points; + + vec[0] = *control; + vec[1] = *to; + + tag[0] = 0; + tag[1] = SW_FT_STROKE_TAG_ON; + + border->num_points += 2; + } + + border->movable = FALSE; + + return error; +} + +static SW_FT_Error ft_stroke_border_cubicto(SW_FT_StrokeBorder border, + SW_FT_Vector* control1, + SW_FT_Vector* control2, + SW_FT_Vector* to) +{ + SW_FT_Error error; + + assert(border->start >= 0); + + error = ft_stroke_border_grow(border, 3); + if (!error) { + SW_FT_Vector* vec = border->points + border->num_points; + SW_FT_Byte* tag = border->tags + border->num_points; + + vec[0] = *control1; + vec[1] = *control2; + vec[2] = *to; + + tag[0] = SW_FT_STROKE_TAG_CUBIC; + tag[1] = SW_FT_STROKE_TAG_CUBIC; + tag[2] = SW_FT_STROKE_TAG_ON; + + border->num_points += 3; + } + + border->movable = FALSE; + + return error; +} + +#define SW_FT_ARC_CUBIC_ANGLE (SW_FT_ANGLE_PI / 2) + + +static SW_FT_Error +ft_stroke_border_arcto( SW_FT_StrokeBorder border, + SW_FT_Vector* center, + SW_FT_Fixed radius, + SW_FT_Angle angle_start, + SW_FT_Angle angle_diff ) +{ + SW_FT_Fixed coef; + SW_FT_Vector a0, a1, a2, a3; + SW_FT_Int i, arcs = 1; + SW_FT_Error error = 0; + + + /* number of cubic arcs to draw */ + while ( angle_diff > SW_FT_ARC_CUBIC_ANGLE * arcs || + -angle_diff > SW_FT_ARC_CUBIC_ANGLE * arcs ) + arcs++; + + /* control tangents */ + coef = SW_FT_Tan( angle_diff / ( 4 * arcs ) ); + coef += coef / 3; + + /* compute start and first control point */ + SW_FT_Vector_From_Polar( &a0, radius, angle_start ); + a1.x = SW_FT_MulFix( -a0.y, coef ); + a1.y = SW_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++ ) + { + /* compute end and second control point */ + SW_FT_Vector_From_Polar( &a3, radius, + angle_start + i * angle_diff / arcs ); + a2.x = SW_FT_MulFix( a3.y, coef ); + a2.y = SW_FT_MulFix( -a3.x, coef ); + + a3.x += center->x; + a3.y += center->y; + a2.x += a3.x; + a2.y += a3.y; + + /* add cubic arc */ + error = ft_stroke_border_cubicto( border, &a1, &a2, &a3 ); + if ( error ) + break; + + /* a0 = a3; */ + a1.x = a3.x - a2.x + a3.x; + a1.y = a3.y - a2.y + a3.y; + } + + return error; +} + +static SW_FT_Error ft_stroke_border_moveto(SW_FT_StrokeBorder border, + SW_FT_Vector* to) +{ + /* close current open path if any ? */ + 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(SW_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(SW_FT_StrokeBorder border) +{ + border->num_points = 0; + border->start = -1; + border->valid = FALSE; +} + +static void ft_stroke_border_done(SW_FT_StrokeBorder border) +{ + free(border->points); + free(border->tags); + + border->num_points = 0; + border->max_points = 0; + border->start = -1; + border->valid = FALSE; +} + +static SW_FT_Error ft_stroke_border_get_counts(SW_FT_StrokeBorder border, + SW_FT_UInt* anum_points, + SW_FT_UInt* anum_contours) +{ + SW_FT_Error error = 0; + SW_FT_UInt num_points = 0; + SW_FT_UInt num_contours = 0; + + SW_FT_UInt count = border->num_points; + SW_FT_Vector* point = border->points; + SW_FT_Byte* tags = border->tags; + SW_FT_Int in_contour = 0; + + for (; count > 0; count--, num_points++, point++, tags++) { + if (tags[0] & SW_FT_STROKE_TAG_BEGIN) { + if (in_contour != 0) goto Fail; + + in_contour = 1; + } else if (in_contour == 0) + goto Fail; + + if (tags[0] & SW_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(SW_FT_StrokeBorder border, + SW_FT_Outline* outline) +{ + /* copy point locations */ + memcpy(outline->points + outline->n_points, border->points, + border->num_points * sizeof(SW_FT_Vector)); + + /* copy tags */ + { + SW_FT_UInt count = border->num_points; + SW_FT_Byte* read = border->tags; + SW_FT_Byte* write = (SW_FT_Byte*)outline->tags + outline->n_points; + + for (; count > 0; count--, read++, write++) { + if (*read & SW_FT_STROKE_TAG_ON) + *write = SW_FT_CURVE_TAG_ON; + else if (*read & SW_FT_STROKE_TAG_CUBIC) + *write = SW_FT_CURVE_TAG_CUBIC; + else + *write = SW_FT_CURVE_TAG_CONIC; + } + } + + /* copy contours */ + { + SW_FT_UInt count = border->num_points; + SW_FT_Byte* tags = border->tags; + SW_FT_Short* write = outline->contours + outline->n_contours; + SW_FT_Short idx = (SW_FT_Short)outline->n_points; + + for (; count > 0; count--, tags++, idx++) { + if (*tags & SW_FT_STROKE_TAG_END) { + *write++ = idx; + outline->n_contours++; + } + } + } + + outline->n_points = (short)(outline->n_points + border->num_points); + + assert(SW_FT_Outline_Check(outline) == 0); +} + +/*************************************************************************/ +/*************************************************************************/ +/***** *****/ +/***** STROKER *****/ +/***** *****/ +/*************************************************************************/ +/*************************************************************************/ + +#define SW_FT_SIDE_TO_ROTATE(s) (SW_FT_ANGLE_PI2 - (s)*SW_FT_ANGLE_PI) + +typedef struct SW_FT_StrokerRec_ { + SW_FT_Angle angle_in; /* direction into curr join */ + SW_FT_Angle angle_out; /* direction out of join */ + SW_FT_Vector center; /* current position */ + SW_FT_Fixed line_length; /* length of last lineto */ + SW_FT_Bool first_point; /* is this the start? */ + SW_FT_Bool subpath_open; /* is the subpath open? */ + SW_FT_Angle subpath_angle; /* subpath start direction */ + SW_FT_Vector subpath_start; /* subpath start position */ + SW_FT_Fixed subpath_line_length; /* subpath start lineto len */ + SW_FT_Bool handle_wide_strokes; /* use wide strokes logic? */ + + SW_FT_Stroker_LineCap line_cap; + SW_FT_Stroker_LineJoin line_join; + SW_FT_Stroker_LineJoin line_join_saved; + SW_FT_Fixed miter_limit; + SW_FT_Fixed radius; + + SW_FT_StrokeBorderRec borders[2]; +} SW_FT_StrokerRec; + +/* documentation is in ftstroke.h */ + +SW_FT_Error SW_FT_Stroker_New(SW_FT_Stroker* astroker) +{ + SW_FT_Error error = 0; /* assigned in SW_FT_NEW */ + SW_FT_Stroker stroker = NULL; + + stroker = (SW_FT_StrokerRec*)calloc(1, sizeof(SW_FT_StrokerRec)); + if (stroker) { + ft_stroke_border_init(&stroker->borders[0]); + ft_stroke_border_init(&stroker->borders[1]); + } + + *astroker = stroker; + + return error; +} + +void SW_FT_Stroker_Rewind(SW_FT_Stroker stroker) +{ + if (stroker) { + ft_stroke_border_reset(&stroker->borders[0]); + ft_stroke_border_reset(&stroker->borders[1]); + } +} + +/* documentation is in ftstroke.h */ + +void SW_FT_Stroker_Set(SW_FT_Stroker stroker, SW_FT_Fixed radius, + SW_FT_Stroker_LineCap line_cap, + SW_FT_Stroker_LineJoin line_join, + SW_FT_Fixed miter_limit) +{ + stroker->radius = radius; + stroker->line_cap = line_cap; + stroker->line_join = line_join; + stroker->miter_limit = miter_limit; + + /* ensure miter limit has sensible value */ + if (stroker->miter_limit < 0x10000) stroker->miter_limit = 0x10000; + + /* save line join style: */ + /* line join style can be temporarily changed when stroking curves */ + stroker->line_join_saved = line_join; + + SW_FT_Stroker_Rewind(stroker); +} + +/* documentation is in ftstroke.h */ + +void SW_FT_Stroker_Done(SW_FT_Stroker stroker) +{ + if (stroker) { + ft_stroke_border_done(&stroker->borders[0]); + ft_stroke_border_done(&stroker->borders[1]); + + free(stroker); + } +} + +/* create a circular arc at a corner or cap */ +static SW_FT_Error ft_stroker_arcto(SW_FT_Stroker stroker, SW_FT_Int side) +{ + SW_FT_Angle total, rotate; + SW_FT_Fixed radius = stroker->radius; + SW_FT_Error error = 0; + SW_FT_StrokeBorder border = stroker->borders + side; + + rotate = SW_FT_SIDE_TO_ROTATE(side); + + total = SW_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); + if (total == SW_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; +} + +/* add a cap at the end of an opened path */ +static SW_FT_Error +ft_stroker_cap(SW_FT_Stroker stroker, + SW_FT_Angle angle, + SW_FT_Int side) +{ + SW_FT_Error error = 0; + + if (stroker->line_cap == SW_FT_STROKER_LINECAP_ROUND) + { + /* add a round cap */ + stroker->angle_in = angle; + stroker->angle_out = angle + SW_FT_ANGLE_PI; + + error = ft_stroker_arcto(stroker, side); + } + else + { + /* add a square or butt cap */ + SW_FT_Vector middle, delta; + SW_FT_Fixed radius = stroker->radius; + SW_FT_StrokeBorder border = stroker->borders + side; + + /* compute middle point and first angle point */ + SW_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 == SW_FT_STROKER_LINECAP_SQUARE ) + { + middle.x += stroker->center.x; + middle.y += stroker->center.y; + } + else /* SW_FT_STROKER_LINECAP_BUTT */ + { + 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; + + /* compute second angle point */ + 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; +} + +/* process an inside corner, i.e. compute intersection */ +static SW_FT_Error ft_stroker_inside(SW_FT_Stroker stroker, SW_FT_Int side, + SW_FT_Fixed line_length) +{ + SW_FT_StrokeBorder border = stroker->borders + side; + SW_FT_Angle phi, theta, rotate; + SW_FT_Fixed length; + SW_FT_Vector sigma, delta; + SW_FT_Error error = 0; + SW_FT_Bool intersect; /* use intersection of lines? */ + + rotate = SW_FT_SIDE_TO_ROTATE(side); + + theta = SW_FT_Angle_Diff(stroker->angle_in, stroker->angle_out) / 2; + + /* Only intersect borders if between two lineto's and both */ + /* lines are long enough (line_length is zero for curves). */ + if (!border->movable || line_length == 0 || + theta > 0x59C000 || theta < -0x59C000 ) + intersect = FALSE; + else { + /* compute minimum required length of lines */ + SW_FT_Fixed min_length; + + + SW_FT_Vector_Unit( &sigma, theta ); + min_length = + ft_pos_abs( SW_FT_MulDiv( stroker->radius, sigma.y, sigma.x ) ); + + intersect = SW_FT_BOOL( min_length && + stroker->line_length >= min_length && + line_length >= min_length ); + } + + if (!intersect) { + SW_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 { + /* compute median angle */ + phi = stroker->angle_in + theta + rotate; + + length = SW_FT_DivFix( stroker->radius, sigma.x ); + + SW_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; +} + + /* process an outside corner, i.e. compute bevel/miter/round */ +static SW_FT_Error +ft_stroker_outside( SW_FT_Stroker stroker, + SW_FT_Int side, + SW_FT_Fixed line_length ) +{ + SW_FT_StrokeBorder border = stroker->borders + side; + SW_FT_Error error; + SW_FT_Angle rotate; + + + if ( stroker->line_join == SW_FT_STROKER_LINEJOIN_ROUND ) + error = ft_stroker_arcto( stroker, side ); + else + { + /* this is a mitered (pointed) or beveled (truncated) corner */ + SW_FT_Fixed radius = stroker->radius; + SW_FT_Vector sigma; + SW_FT_Angle theta = 0, phi = 0; + SW_FT_Bool bevel, fixed_bevel; + + + rotate = SW_FT_SIDE_TO_ROTATE( side ); + + bevel = + SW_FT_BOOL( stroker->line_join == SW_FT_STROKER_LINEJOIN_BEVEL ); + + fixed_bevel = + SW_FT_BOOL( stroker->line_join != SW_FT_STROKER_LINEJOIN_MITER_VARIABLE ); + + /* check miter limit first */ + if ( !bevel ) + { + theta = SW_FT_Angle_Diff( stroker->angle_in, stroker->angle_out ) / 2; + + if ( theta == SW_FT_ANGLE_PI2 ) + theta = -rotate; + + phi = stroker->angle_in + theta + rotate; + + SW_FT_Vector_From_Polar( &sigma, stroker->miter_limit, theta ); + + /* is miter limit exceeded? */ + if ( sigma.x < 0x10000L ) + { + /* don't create variable bevels for very small deviations; */ + /* FT_Sin(x) = 0 for x <= 57 */ + if ( fixed_bevel || ft_pos_abs( theta ) > 57 ) + bevel = TRUE; + } + } + + if ( bevel ) /* this is a bevel (broken angle) */ + { + if ( fixed_bevel ) + { + /* the outer corners are simply joined together */ + SW_FT_Vector delta; + + + /* add bevel */ + SW_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 /* variable bevel or clipped miter */ + { + /* the miter is truncated */ + SW_FT_Vector middle, delta; + SW_FT_Fixed coef; + + + /* compute middle point and first angle point */ + SW_FT_Vector_From_Polar( &middle, + SW_FT_MulFix( radius, stroker->miter_limit ), + phi ); + + coef = SW_FT_DivFix( 0x10000L - sigma.x, sigma.y ); + delta.x = SW_FT_MulFix( middle.y, coef ); + delta.y = SW_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; + + /* compute second angle point */ + 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; + + /* finally, add an end point; only needed if not lineto */ + /* (line_length is zero for curves) */ + if ( line_length == 0 ) + { + SW_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 /* this is a miter (intersection) */ + { + SW_FT_Fixed length; + SW_FT_Vector delta; + + + length = SW_FT_MulDiv( stroker->radius, stroker->miter_limit, sigma.x ); + + SW_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; + + /* now add an end point; only needed if not lineto */ + /* (line_length is zero for curves) */ + if ( line_length == 0 ) + { + SW_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 SW_FT_Error ft_stroker_process_corner(SW_FT_Stroker stroker, + SW_FT_Fixed line_length) +{ + SW_FT_Error error = 0; + SW_FT_Angle turn; + SW_FT_Int inside_side; + + turn = SW_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); + + /* no specific corner processing is required if the turn is 0 */ + if (turn == 0) goto Exit; + + /* when we turn to the right, the inside side is 0 */ + inside_side = 0; + + /* otherwise, the inside side is 1 */ + if (turn < 0) inside_side = 1; + + /* process the inside side */ + error = ft_stroker_inside(stroker, inside_side, line_length); + if (error) goto Exit; + + /* process the outside side */ + error = ft_stroker_outside(stroker, 1 - inside_side, line_length); + +Exit: + return error; +} + +/* add two points to the left and right borders corresponding to the */ +/* start of the subpath */ +static SW_FT_Error ft_stroker_subpath_start(SW_FT_Stroker stroker, + SW_FT_Angle start_angle, + SW_FT_Fixed line_length) +{ + SW_FT_Vector delta; + SW_FT_Vector point; + SW_FT_Error error; + SW_FT_StrokeBorder border; + + SW_FT_Vector_From_Polar(&delta, stroker->radius, + start_angle + SW_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); + + /* save angle, position, and line length for last join */ + /* (line_length is zero for curves) */ + stroker->subpath_angle = start_angle; + stroker->first_point = FALSE; + stroker->subpath_line_length = line_length; + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +SW_FT_Error SW_FT_Stroker_LineTo(SW_FT_Stroker stroker, SW_FT_Vector* to) +{ + SW_FT_Error error = 0; + SW_FT_StrokeBorder border; + SW_FT_Vector delta; + SW_FT_Angle angle; + SW_FT_Int side; + SW_FT_Fixed line_length; + + delta.x = to->x - stroker->center.x; + delta.y = to->y - stroker->center.y; + + /* a zero-length lineto is a no-op; avoid creating a spurious corner */ + if (delta.x == 0 && delta.y == 0) goto Exit; + + /* compute length of line */ + line_length = SW_FT_Vector_Length(&delta); + + angle = SW_FT_Atan2(delta.x, delta.y); + SW_FT_Vector_From_Polar(&delta, stroker->radius, angle + SW_FT_ANGLE_PI2); + + /* process corner if necessary */ + if (stroker->first_point) { + /* This is the first segment of a subpath. We need to */ + /* add a point to each border at their respective starting */ + /* point locations. */ + error = ft_stroker_subpath_start(stroker, angle, line_length); + if (error) goto Exit; + } else { + /* process the current corner */ + stroker->angle_out = angle; + error = ft_stroker_process_corner(stroker, line_length); + if (error) goto Exit; + } + + /* now add a line segment to both the `inside' and `outside' paths */ + for (border = stroker->borders, side = 1; side >= 0; side--, border++) { + SW_FT_Vector point; + + point.x = to->x + delta.x; + point.y = to->y + delta.y; + + /* the ends of lineto borders are movable */ + 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; +} + +/* documentation is in ftstroke.h */ + +SW_FT_Error SW_FT_Stroker_ConicTo(SW_FT_Stroker stroker, SW_FT_Vector* control, + SW_FT_Vector* to) +{ + SW_FT_Error error = 0; + SW_FT_Vector bez_stack[34]; + SW_FT_Vector* arc; + SW_FT_Vector* limit = bez_stack + 30; + SW_FT_Bool first_arc = TRUE; + + /* if all control points are coincident, this is a no-op; */ + /* avoid creating a spurious corner */ + if (SW_FT_IS_SMALL(stroker->center.x - control->x) && + SW_FT_IS_SMALL(stroker->center.y - control->y) && + SW_FT_IS_SMALL(control->x - to->x) && + SW_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) { + SW_FT_Angle angle_in, angle_out; + + /* initialize with current direction */ + 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; + + /* process corner if necessary */ + 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(SW_FT_Angle_Diff(stroker->angle_in, angle_in)) > + SW_FT_SMALL_CONIC_THRESHOLD / 4) { + /* if the deviation from one arc to the next is too great, */ + /* add a round corner */ + stroker->center = arc[2]; + stroker->angle_out = angle_in; + stroker->line_join = SW_FT_STROKER_LINEJOIN_ROUND; + + error = ft_stroker_process_corner(stroker, 0); + + /* reinstate line join style */ + stroker->line_join = stroker->line_join_saved; + } + + if (error) goto Exit; + + /* the arc's angle is small enough; we can add it directly to each */ + /* border */ + { + SW_FT_Vector ctrl, end; + SW_FT_Angle theta, phi, rotate, alpha0 = 0; + SW_FT_Fixed length; + SW_FT_StrokeBorder border; + SW_FT_Int side; + + theta = SW_FT_Angle_Diff(angle_in, angle_out) / 2; + phi = angle_in + theta; + length = SW_FT_DivFix(stroker->radius, SW_FT_Cos(theta)); + + /* compute direction of original arc */ + if (stroker->handle_wide_strokes) + alpha0 = SW_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 = SW_FT_SIDE_TO_ROTATE(side); + + /* compute control point */ + SW_FT_Vector_From_Polar(&ctrl, length, phi + rotate); + ctrl.x += arc[1].x; + ctrl.y += arc[1].y; + + /* compute end point */ + SW_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) { + SW_FT_Vector start; + SW_FT_Angle alpha1; + + /* determine whether the border radius is greater than the + */ + /* radius of curvature of the original arc */ + start = border->points[border->num_points - 1]; + + alpha1 = SW_FT_Atan2(end.x - start.x, end.y - start.y); + + /* is the direction of the border arc opposite to */ + /* that of the original arc? */ + if (ft_pos_abs(SW_FT_Angle_Diff(alpha0, alpha1)) > + SW_FT_ANGLE_PI / 2) { + SW_FT_Angle beta, gamma; + SW_FT_Vector bvec, delta; + SW_FT_Fixed blen, sinA, sinB, alen; + + /* use the sine rule to find the intersection point */ + beta = + SW_FT_Atan2(arc[2].x - start.x, arc[2].y - start.y); + gamma = SW_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 = SW_FT_Vector_Length(&bvec); + + sinA = ft_pos_abs(SW_FT_Sin(alpha1 - gamma)); + sinB = ft_pos_abs(SW_FT_Sin(beta - gamma)); + + alen = SW_FT_MulDiv(blen, sinA, sinB); + + SW_FT_Vector_From_Polar(&delta, alen, beta); + delta.x += start.x; + delta.y += start.y; + + /* circumnavigate the negative sector backwards */ + 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; + /* and then move to the endpoint */ + error = ft_stroke_border_lineto(border, &end, FALSE); + if (error) goto Exit; + + continue; + } + + /* else fall through */ + } + + /* simply add an arc */ + error = ft_stroke_border_conicto(border, &ctrl, &end); + if (error) goto Exit; + } + } + + arc -= 2; + + stroker->angle_in = angle_out; + } + + stroker->center = *to; + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +SW_FT_Error SW_FT_Stroker_CubicTo(SW_FT_Stroker stroker, SW_FT_Vector* control1, + SW_FT_Vector* control2, SW_FT_Vector* to) +{ + SW_FT_Error error = 0; + SW_FT_Vector bez_stack[37]; + SW_FT_Vector* arc; + SW_FT_Vector* limit = bez_stack + 32; + SW_FT_Bool first_arc = TRUE; + + /* if all control points are coincident, this is a no-op; */ + /* avoid creating a spurious corner */ + if (SW_FT_IS_SMALL(stroker->center.x - control1->x) && + SW_FT_IS_SMALL(stroker->center.y - control1->y) && + SW_FT_IS_SMALL(control1->x - control2->x) && + SW_FT_IS_SMALL(control1->y - control2->y) && + SW_FT_IS_SMALL(control2->x - to->x) && + SW_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) { + SW_FT_Angle angle_in, angle_mid, angle_out; + + /* initialize with current direction */ + 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; + + /* process corner if necessary */ + 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(SW_FT_Angle_Diff(stroker->angle_in, angle_in)) > + SW_FT_SMALL_CUBIC_THRESHOLD / 4) { + /* if the deviation from one arc to the next is too great, */ + /* add a round corner */ + stroker->center = arc[3]; + stroker->angle_out = angle_in; + stroker->line_join = SW_FT_STROKER_LINEJOIN_ROUND; + + error = ft_stroker_process_corner(stroker, 0); + + /* reinstate line join style */ + stroker->line_join = stroker->line_join_saved; + } + + if (error) goto Exit; + + /* the arc's angle is small enough; we can add it directly to each */ + /* border */ + { + SW_FT_Vector ctrl1, ctrl2, end; + SW_FT_Angle theta1, phi1, theta2, phi2, rotate, alpha0 = 0; + SW_FT_Fixed length1, length2; + SW_FT_StrokeBorder border; + SW_FT_Int side; + + theta1 = SW_FT_Angle_Diff(angle_in, angle_mid) / 2; + theta2 = SW_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 = SW_FT_DivFix(stroker->radius, SW_FT_Cos(theta1)); + length2 = SW_FT_DivFix(stroker->radius, SW_FT_Cos(theta2)); + + /* compute direction of original arc */ + if (stroker->handle_wide_strokes) + alpha0 = SW_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 = SW_FT_SIDE_TO_ROTATE(side); + + /* compute control points */ + SW_FT_Vector_From_Polar(&ctrl1, length1, phi1 + rotate); + ctrl1.x += arc[2].x; + ctrl1.y += arc[2].y; + + SW_FT_Vector_From_Polar(&ctrl2, length2, phi2 + rotate); + ctrl2.x += arc[1].x; + ctrl2.y += arc[1].y; + + /* compute end point */ + SW_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) { + SW_FT_Vector start; + SW_FT_Angle alpha1; + + /* determine whether the border radius is greater than the + */ + /* radius of curvature of the original arc */ + start = border->points[border->num_points - 1]; + + alpha1 = SW_FT_Atan2(end.x - start.x, end.y - start.y); + + /* is the direction of the border arc opposite to */ + /* that of the original arc? */ + if (ft_pos_abs(SW_FT_Angle_Diff(alpha0, alpha1)) > + SW_FT_ANGLE_PI / 2) { + SW_FT_Angle beta, gamma; + SW_FT_Vector bvec, delta; + SW_FT_Fixed blen, sinA, sinB, alen; + + /* use the sine rule to find the intersection point */ + beta = + SW_FT_Atan2(arc[3].x - start.x, arc[3].y - start.y); + gamma = SW_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 = SW_FT_Vector_Length(&bvec); + + sinA = ft_pos_abs(SW_FT_Sin(alpha1 - gamma)); + sinB = ft_pos_abs(SW_FT_Sin(beta - gamma)); + + alen = SW_FT_MulDiv(blen, sinA, sinB); + + SW_FT_Vector_From_Polar(&delta, alen, beta); + delta.x += start.x; + delta.y += start.y; + + /* circumnavigate the negative sector backwards */ + 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; + /* and then move to the endpoint */ + error = ft_stroke_border_lineto(border, &end, FALSE); + if (error) goto Exit; + + continue; + } + + /* else fall through */ + } + + /* simply add an arc */ + error = ft_stroke_border_cubicto(border, &ctrl1, &ctrl2, &end); + if (error) goto Exit; + } + } + + arc -= 3; + + stroker->angle_in = angle_out; + } + + stroker->center = *to; + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +SW_FT_Error SW_FT_Stroker_BeginSubPath(SW_FT_Stroker stroker, SW_FT_Vector* to, + SW_FT_Bool open) +{ + /* We cannot process the first point, because there is not enough */ + /* information regarding its corner/cap. The latter will be processed */ + /* in the `SW_FT_Stroker_EndSubPath' routine. */ + /* */ + stroker->first_point = TRUE; + stroker->center = *to; + stroker->subpath_open = open; + + /* Determine if we need to check whether the border radius is greater */ + /* than the radius of curvature of a curve, to handle this case */ + /* specially. This is only required if bevel joins or butt caps may */ + /* be created, because round & miter joins and round & square caps */ + /* cover the negative sector created with wide strokes. */ + stroker->handle_wide_strokes = + SW_FT_BOOL(stroker->line_join != SW_FT_STROKER_LINEJOIN_ROUND || + (stroker->subpath_open && + stroker->line_cap == SW_FT_STROKER_LINECAP_BUTT)); + + /* record the subpath start point for each border */ + stroker->subpath_start = *to; + + stroker->angle_in = 0; + + return 0; +} + +static SW_FT_Error ft_stroker_add_reverse_left(SW_FT_Stroker stroker, + SW_FT_Bool open) +{ + SW_FT_StrokeBorder right = stroker->borders + 0; + SW_FT_StrokeBorder left = stroker->borders + 1; + SW_FT_Int new_points; + SW_FT_Error error = 0; + + assert(left->start >= 0); + + new_points = left->num_points - left->start; + if (new_points > 0) { + error = ft_stroke_border_grow(right, (SW_FT_UInt)new_points); + if (error) goto Exit; + + { + SW_FT_Vector* dst_point = right->points + right->num_points; + SW_FT_Byte* dst_tag = right->tags + right->num_points; + SW_FT_Vector* src_point = left->points + left->num_points - 1; + SW_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] &= ~SW_FT_STROKE_TAG_BEGIN_END; + else { + SW_FT_Byte ttag = + (SW_FT_Byte)(dst_tag[0] & SW_FT_STROKE_TAG_BEGIN_END); + + /* switch begin/end tags if necessary */ + if (ttag == SW_FT_STROKE_TAG_BEGIN || + ttag == SW_FT_STROKE_TAG_END) + dst_tag[0] ^= SW_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; +} + +/* documentation is in ftstroke.h */ + +/* there's a lot of magic in this function! */ +SW_FT_Error SW_FT_Stroker_EndSubPath(SW_FT_Stroker stroker) +{ + SW_FT_Error error = 0; + + if (stroker->subpath_open) { + SW_FT_StrokeBorder right = stroker->borders; + + /* All right, this is an opened path, we need to add a cap between */ + /* right & left, add the reverse of left, then add a final cap */ + /* between left & right. */ + error = ft_stroker_cap(stroker, stroker->angle_in, 0); + if (error) goto Exit; + + /* add reversed points from `left' to `right' */ + error = ft_stroker_add_reverse_left(stroker, TRUE); + if (error) goto Exit; + + /* now add the final cap */ + stroker->center = stroker->subpath_start; + error = + ft_stroker_cap(stroker, stroker->subpath_angle + SW_FT_ANGLE_PI, 0); + if (error) goto Exit; + + /* Now end the right subpath accordingly. The left one is */ + /* rewind and doesn't need further processing. */ + ft_stroke_border_close(right, FALSE); + } else { + SW_FT_Angle turn; + SW_FT_Int inside_side; + + /* close the path if needed */ + if (stroker->center.x != stroker->subpath_start.x || + stroker->center.y != stroker->subpath_start.y) { + error = SW_FT_Stroker_LineTo(stroker, &stroker->subpath_start); + if (error) goto Exit; + } + + /* process the corner */ + stroker->angle_out = stroker->subpath_angle; + turn = SW_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); + + /* no specific corner processing is required if the turn is 0 */ + if (turn != 0) { + /* when we turn to the right, the inside side is 0 */ + inside_side = 0; + + /* otherwise, the inside side is 1 */ + if (turn < 0) inside_side = 1; + + error = ft_stroker_inside(stroker, inside_side, + stroker->subpath_line_length); + if (error) goto Exit; + + /* process the outside side */ + error = ft_stroker_outside(stroker, 1 - inside_side, + stroker->subpath_line_length); + if (error) goto Exit; + } + + /* then end our two subpaths */ + ft_stroke_border_close(stroker->borders + 0, FALSE); + ft_stroke_border_close(stroker->borders + 1, TRUE); + } + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +SW_FT_Error SW_FT_Stroker_GetBorderCounts(SW_FT_Stroker stroker, + SW_FT_StrokerBorder border, + SW_FT_UInt* anum_points, + SW_FT_UInt* anum_contours) +{ + SW_FT_UInt num_points = 0, num_contours = 0; + SW_FT_Error error; + + if (!stroker || border > 1) { + error = -1; // SW_FT_THROW( Invalid_Argument ); + 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; +} + +/* documentation is in ftstroke.h */ + +SW_FT_Error SW_FT_Stroker_GetCounts(SW_FT_Stroker stroker, + SW_FT_UInt* anum_points, + SW_FT_UInt* anum_contours) +{ + SW_FT_UInt count1, count2, num_points = 0; + SW_FT_UInt count3, count4, num_contours = 0; + SW_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; +} + +/* documentation is in ftstroke.h */ + +void SW_FT_Stroker_ExportBorder(SW_FT_Stroker stroker, + SW_FT_StrokerBorder border, + SW_FT_Outline* outline) +{ + if (border == SW_FT_STROKER_BORDER_LEFT || + border == SW_FT_STROKER_BORDER_RIGHT) { + SW_FT_StrokeBorder sborder = &stroker->borders[border]; + + if (sborder->valid) ft_stroke_border_export(sborder, outline); + } +} + +/* documentation is in ftstroke.h */ + +void SW_FT_Stroker_Export(SW_FT_Stroker stroker, SW_FT_Outline* outline) +{ + SW_FT_Stroker_ExportBorder(stroker, SW_FT_STROKER_BORDER_LEFT, outline); + SW_FT_Stroker_ExportBorder(stroker, SW_FT_STROKER_BORDER_RIGHT, outline); +} + +/* documentation is in ftstroke.h */ + +/* + * The following is very similar to SW_FT_Outline_Decompose, except + * that we do support opened paths, and do not scale the outline. + */ +SW_FT_Error SW_FT_Stroker_ParseOutline(SW_FT_Stroker stroker, + const SW_FT_Outline* outline) +{ + SW_FT_Vector v_last; + SW_FT_Vector v_control; + SW_FT_Vector v_start; + + SW_FT_Vector* point; + SW_FT_Vector* limit; + char* tags; + + SW_FT_Error error; + + SW_FT_Int n; /* index of contour in outline */ + SW_FT_UInt first; /* index of first point in contour */ + SW_FT_Int tag; /* current point's state */ + + if (!outline || !stroker) return -1; // SW_FT_THROW( Invalid_Argument ); + + SW_FT_Stroker_Rewind(stroker); + + first = 0; + + for (n = 0; n < outline->n_contours; n++) { + SW_FT_UInt last; /* index of last point in contour */ + + last = outline->contours[n]; + limit = outline->points + last; + + /* skip empty points; we don't stroke these */ + 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 = SW_FT_CURVE_TAG(tags[0]); + + /* A contour cannot start with a cubic control point! */ + if (tag == SW_FT_CURVE_TAG_CUBIC) goto Invalid_Outline; + + /* check first point to determine origin */ + if (tag == SW_FT_CURVE_TAG_CONIC) { + /* First point is conic control. Yes, this happens. */ + if (SW_FT_CURVE_TAG(outline->tags[last]) == SW_FT_CURVE_TAG_ON) { + /* start at last point if it is on the curve */ + v_start = v_last; + limit--; + } else { + /* if both first and last points are conic, */ + /* start at their middle */ + v_start.x = (v_start.x + v_last.x) / 2; + v_start.y = (v_start.y + v_last.y) / 2; + } + point--; + tags--; + } + + error = SW_FT_Stroker_BeginSubPath(stroker, &v_start, outline->contours_flag[n]); + if (error) goto Exit; + + while (point < limit) { + point++; + tags++; + + tag = SW_FT_CURVE_TAG(tags[0]); + switch (tag) { + case SW_FT_CURVE_TAG_ON: /* emit a single line_to */ + { + SW_FT_Vector vec; + + vec.x = point->x; + vec.y = point->y; + + error = SW_FT_Stroker_LineTo(stroker, &vec); + if (error) goto Exit; + continue; + } + + case SW_FT_CURVE_TAG_CONIC: /* consume conic arcs */ + v_control.x = point->x; + v_control.y = point->y; + + Do_Conic: + if (point < limit) { + SW_FT_Vector vec; + SW_FT_Vector v_middle; + + point++; + tags++; + tag = SW_FT_CURVE_TAG(tags[0]); + + vec = point[0]; + + if (tag == SW_FT_CURVE_TAG_ON) { + error = + SW_FT_Stroker_ConicTo(stroker, &v_control, &vec); + if (error) goto Exit; + continue; + } + + if (tag != SW_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 = + SW_FT_Stroker_ConicTo(stroker, &v_control, &v_middle); + if (error) goto Exit; + + v_control = vec; + goto Do_Conic; + } + + error = SW_FT_Stroker_ConicTo(stroker, &v_control, &v_start); + goto Close; + + default: /* SW_FT_CURVE_TAG_CUBIC */ + { + SW_FT_Vector vec1, vec2; + + if (point + 1 > limit || + SW_FT_CURVE_TAG(tags[1]) != SW_FT_CURVE_TAG_CUBIC) + goto Invalid_Outline; + + point += 2; + tags += 2; + + vec1 = point[-2]; + vec2 = point[-1]; + + if (point <= limit) { + SW_FT_Vector vec; + + vec = point[0]; + + error = SW_FT_Stroker_CubicTo(stroker, &vec1, &vec2, &vec); + if (error) goto Exit; + continue; + } + + error = SW_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 = SW_FT_Stroker_EndSubPath(stroker); + if (error) goto Exit; + + first = last + 1; + } + + return 0; + +Exit: + return error; + +Invalid_Outline: + return -2; // SW_FT_THROW( Invalid_Outline ); +} + +/* END */ diff --git a/3rdparty/software/sw_ft_stroker.h b/3rdparty/software/sw_ft_stroker.h new file mode 100644 index 000000000..95036c5ee --- /dev/null +++ b/3rdparty/software/sw_ft_stroker.h @@ -0,0 +1,319 @@ +#ifndef SW_FT_STROKER_H +#define SW_FT_STROKER_H +/***************************************************************************/ +/* */ +/* ftstroke.h */ +/* */ +/* FreeType path stroker (specification). */ +/* */ +/* Copyright 2002-2006, 2008, 2009, 2011-2012 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#include "sw_ft_raster.h" + + /************************************************************** + * + * @type: + * SW_FT_Stroker + * + * @description: + * Opaque handler to a path stroker object. + */ + typedef struct SW_FT_StrokerRec_* SW_FT_Stroker; + + + /************************************************************** + * + * @enum: + * SW_FT_Stroker_LineJoin + * + * @description: + * These values determine how two joining lines are rendered + * in a stroker. + * + * @values: + * SW_FT_STROKER_LINEJOIN_ROUND :: + * Used to render rounded line joins. Circular arcs are used + * to join two lines smoothly. + * + * SW_FT_STROKER_LINEJOIN_BEVEL :: + * Used to render beveled line joins. The outer corner of + * the joined lines is filled by enclosing the triangular + * region of the corner with a straight line between the + * outer corners of each stroke. + * + * SW_FT_STROKER_LINEJOIN_MITER_FIXED :: + * Used to render mitered line joins, with fixed bevels if the + * miter limit is exceeded. The outer edges of the strokes + * for the two segments are extended until they meet at an + * angle. If the segments meet at too sharp an angle (such + * that the miter would extend from the intersection of the + * segments a distance greater than the product of the miter + * limit value and the border radius), then a bevel join (see + * above) is used instead. This prevents long spikes being + * created. SW_FT_STROKER_LINEJOIN_MITER_FIXED generates a miter + * line join as used in PostScript and PDF. + * + * SW_FT_STROKER_LINEJOIN_MITER_VARIABLE :: + * SW_FT_STROKER_LINEJOIN_MITER :: + * Used to render mitered line joins, with variable bevels if + * the miter limit is exceeded. The intersection of the + * strokes is clipped at a line perpendicular to the bisector + * of the angle between the strokes, at the distance from the + * intersection of the segments equal to the product of the + * miter limit value and the border radius. This prevents + * long spikes being created. + * SW_FT_STROKER_LINEJOIN_MITER_VARIABLE generates a mitered line + * join as used in XPS. SW_FT_STROKER_LINEJOIN_MITER is an alias + * for SW_FT_STROKER_LINEJOIN_MITER_VARIABLE, retained for + * backwards compatibility. + */ + typedef enum SW_FT_Stroker_LineJoin_ + { + SW_FT_STROKER_LINEJOIN_ROUND = 0, + SW_FT_STROKER_LINEJOIN_BEVEL = 1, + SW_FT_STROKER_LINEJOIN_MITER_VARIABLE = 2, + SW_FT_STROKER_LINEJOIN_MITER = SW_FT_STROKER_LINEJOIN_MITER_VARIABLE, + SW_FT_STROKER_LINEJOIN_MITER_FIXED = 3 + + } SW_FT_Stroker_LineJoin; + + + /************************************************************** + * + * @enum: + * SW_FT_Stroker_LineCap + * + * @description: + * These values determine how the end of opened sub-paths are + * rendered in a stroke. + * + * @values: + * SW_FT_STROKER_LINECAP_BUTT :: + * The end of lines is rendered as a full stop on the last + * point itself. + * + * SW_FT_STROKER_LINECAP_ROUND :: + * The end of lines is rendered as a half-circle around the + * last point. + * + * SW_FT_STROKER_LINECAP_SQUARE :: + * The end of lines is rendered as a square around the + * last point. + */ + typedef enum SW_FT_Stroker_LineCap_ + { + SW_FT_STROKER_LINECAP_BUTT = 0, + SW_FT_STROKER_LINECAP_ROUND, + SW_FT_STROKER_LINECAP_SQUARE + + } SW_FT_Stroker_LineCap; + + + /************************************************************** + * + * @enum: + * SW_FT_StrokerBorder + * + * @description: + * These values are used to select a given stroke border + * in @SW_FT_Stroker_GetBorderCounts and @SW_FT_Stroker_ExportBorder. + * + * @values: + * SW_FT_STROKER_BORDER_LEFT :: + * Select the left border, relative to the drawing direction. + * + * SW_FT_STROKER_BORDER_RIGHT :: + * Select the right border, relative to the drawing direction. + * + * @note: + * Applications are generally interested in the `inside' and `outside' + * borders. However, there is no direct mapping between these and the + * `left' and `right' ones, since this really depends on the glyph's + * drawing orientation, which varies between font formats. + * + * You can however use @SW_FT_Outline_GetInsideBorder and + * @SW_FT_Outline_GetOutsideBorder to get these. + */ + typedef enum SW_FT_StrokerBorder_ + { + SW_FT_STROKER_BORDER_LEFT = 0, + SW_FT_STROKER_BORDER_RIGHT + + } SW_FT_StrokerBorder; + + + /************************************************************** + * + * @function: + * SW_FT_Stroker_New + * + * @description: + * Create a new stroker object. + * + * @input: + * library :: + * FreeType library handle. + * + * @output: + * astroker :: + * A new stroker object handle. NULL in case of error. + * + * @return: + * FreeType error code. 0~means success. + */ + SW_FT_Error + SW_FT_Stroker_New( SW_FT_Stroker *astroker ); + + + /************************************************************** + * + * @function: + * SW_FT_Stroker_Set + * + * @description: + * Reset a stroker object's attributes. + * + * @input: + * stroker :: + * The target stroker handle. + * + * radius :: + * The border radius. + * + * line_cap :: + * The line cap style. + * + * line_join :: + * The line join style. + * + * miter_limit :: + * The miter limit for the SW_FT_STROKER_LINEJOIN_MITER_FIXED and + * SW_FT_STROKER_LINEJOIN_MITER_VARIABLE line join styles, + * expressed as 16.16 fixed-point value. + * + * @note: + * The radius is expressed in the same units as the outline + * coordinates. + */ + void + SW_FT_Stroker_Set( SW_FT_Stroker stroker, + SW_FT_Fixed radius, + SW_FT_Stroker_LineCap line_cap, + SW_FT_Stroker_LineJoin line_join, + SW_FT_Fixed miter_limit ); + + /************************************************************** + * + * @function: + * SW_FT_Stroker_ParseOutline + * + * @description: + * A convenience function used to parse a whole outline with + * the stroker. The resulting outline(s) can be retrieved + * later by functions like @SW_FT_Stroker_GetCounts and @SW_FT_Stroker_Export. + * + * @input: + * stroker :: + * The target stroker handle. + * + * outline :: + * The source outline. + * + * + * @return: + * FreeType error code. 0~means success. + * + * @note: + * If `opened' is~0 (the default), the outline is treated as a closed + * path, and the stroker generates two distinct `border' outlines. + * + * + * This function calls @SW_FT_Stroker_Rewind automatically. + */ + SW_FT_Error + SW_FT_Stroker_ParseOutline( SW_FT_Stroker stroker, + const SW_FT_Outline* outline); + + + /************************************************************** + * + * @function: + * SW_FT_Stroker_GetCounts + * + * @description: + * Call this function once you have finished parsing your paths + * with the stroker. It returns the number of points and + * contours necessary to export all points/borders from the stroked + * outline/path. + * + * @input: + * stroker :: + * The target stroker handle. + * + * @output: + * anum_points :: + * The number of points. + * + * anum_contours :: + * The number of contours. + * + * @return: + * FreeType error code. 0~means success. + */ + SW_FT_Error + SW_FT_Stroker_GetCounts( SW_FT_Stroker stroker, + SW_FT_UInt *anum_points, + SW_FT_UInt *anum_contours ); + + + /************************************************************** + * + * @function: + * SW_FT_Stroker_Export + * + * @description: + * Call this function after @SW_FT_Stroker_GetBorderCounts to + * export all borders to your own @SW_FT_Outline structure. + * + * Note that this function appends the border points and + * contours to your outline, but does not try to resize its + * arrays. + * + * @input: + * stroker :: + * The target stroker handle. + * + * outline :: + * The target outline handle. + */ + void + SW_FT_Stroker_Export( SW_FT_Stroker stroker, + SW_FT_Outline* outline ); + + + /************************************************************** + * + * @function: + * SW_FT_Stroker_Done + * + * @description: + * Destroy a stroker object. + * + * @input: + * stroker :: + * A stroker handle. Can be NULL. + */ + void + SW_FT_Stroker_Done( SW_FT_Stroker stroker ); + + +#endif // SW_FT_STROKER_H diff --git a/3rdparty/software/sw_ft_types.h b/3rdparty/software/sw_ft_types.h new file mode 100644 index 000000000..828103aea --- /dev/null +++ b/3rdparty/software/sw_ft_types.h @@ -0,0 +1,160 @@ +#ifndef SW_FT_TYPES_H +#define SW_FT_TYPES_H + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Fixed */ +/* */ +/* */ +/* This type is used to store 16.16 fixed-point values, like scaling */ +/* values or matrix coefficients. */ +/* */ +typedef signed long SW_FT_Fixed; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Int */ +/* */ +/* */ +/* A typedef for the int type. */ +/* */ +typedef signed int SW_FT_Int; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_UInt */ +/* */ +/* */ +/* A typedef for the unsigned int type. */ +/* */ +typedef unsigned int SW_FT_UInt; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Long */ +/* */ +/* */ +/* A typedef for signed long. */ +/* */ +typedef signed long SW_FT_Long; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_ULong */ +/* */ +/* */ +/* A typedef for unsigned long. */ +/* */ +typedef unsigned long SW_FT_ULong; + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Short */ +/* */ +/* */ +/* A typedef for signed short. */ +/* */ +typedef signed short SW_FT_Short; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Byte */ +/* */ +/* */ +/* A simple typedef for the _unsigned_ char type. */ +/* */ +typedef unsigned char SW_FT_Byte; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Bool */ +/* */ +/* */ +/* A typedef of unsigned char, used for simple booleans. As usual, */ +/* values 1 and~0 represent true and false, respectively. */ +/* */ +typedef unsigned char SW_FT_Bool; + + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Error */ +/* */ +/* */ +/* The FreeType error code type. A value of~0 is always interpreted */ +/* as a successful operation. */ +/* */ +typedef int SW_FT_Error; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Pos */ +/* */ +/* */ +/* The type SW_FT_Pos is used to store vectorial coordinates. Depending */ +/* on the context, these can represent distances in integer font */ +/* units, or 16.16, or 26.6 fixed-point pixel coordinates. */ +/* */ +typedef signed long SW_FT_Pos; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Vector */ +/* */ +/* */ +/* A simple structure used to store a 2D vector; coordinates are of */ +/* the SW_FT_Pos type. */ +/* */ +/* */ +/* x :: The horizontal coordinate. */ +/* y :: The vertical coordinate. */ +/* */ +typedef struct SW_FT_Vector_ +{ + SW_FT_Pos x; + SW_FT_Pos y; + +} SW_FT_Vector; + + +typedef long long int SW_FT_Int64; +typedef unsigned long long int SW_FT_UInt64; + +typedef signed int SW_FT_Int32; +typedef unsigned int SW_FT_UInt32; + + +#define SW_FT_BOOL( x ) ( (SW_FT_Bool)( x ) ) + +#define SW_FT_SIZEOF_LONG 4 + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + + +#endif // SW_FT_TYPES_H diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 000000000..b9bf46a88 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.3) + +project(lunasvg VERSION 2.3.2 LANGUAGES CXX C) + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_C_STANDARD 11) + +option(BUILD_SHARED_LIBS "Builds as shared library" OFF) +option(LUNASVG_BUILD_EXAMPLES "Builds examples" OFF) + +add_library(lunasvg) + +add_subdirectory(include) +add_subdirectory(source) +add_subdirectory(3rdparty/software) +add_subdirectory(3rdparty/plutovg) + +if(BUILD_SHARED_LIBS) + target_compile_definitions(lunasvg PUBLIC LUNASVG_SHARED) + target_compile_definitions(lunasvg PRIVATE LUNASVG_EXPORT) +endif() + +if(LUNASVG_BUILD_EXAMPLES) + add_subdirectory(example) +endif() + +set(LUNASVG_LIBDIR ${CMAKE_INSTALL_PREFIX}/lib) +set(LUNASVG_INCDIR ${CMAKE_INSTALL_PREFIX}/include) + +install(FILES + include/lunasvg.h + DESTINATION ${LUNASVG_INCDIR} +) + +install(TARGETS lunasvg + LIBRARY DESTINATION ${LUNASVG_LIBDIR} + ARCHIVE DESTINATION ${LUNASVG_LIBDIR} + INCLUDES DESTINATION ${LUNASVG_INCDIR} +) diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..ce476ed0d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Nwutobo Samuel Ugochukwu + +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. diff --git a/README.md b/README.md new file mode 100644 index 000000000..54ae0432f --- /dev/null +++ b/README.md @@ -0,0 +1,89 @@ +[![Releases](https://img.shields.io/badge/Version-2.3.2-orange.svg)](https://github.com/sammycage/lunasvg/releases) +[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/sammycage/lunasvg/blob/master/LICENSE) +[![Build Status](https://github.com/sammycage/lunasvg/actions/workflows/ci.yml/badge.svg)](https://github.com/sammycage/lunasvg/actions) + +# LunaSVG - SVG rendering library in C++ + +![LunaSVG](https://github.com/sammycage/lunasvg/blob/master/luna.png) + +## Example + +```cpp +#include + +using namespace lunasvg; + +int main() +{ + auto document = Document::loadFromFile("tiger.svg"); + auto bitmap = document->renderToBitmap(); + + // do something useful with the bitmap here. + + return 0; +} + +``` + +## Features + +- Basic Shapes +- Document Structures +- Coordinate Systems, Transformations and Units +- SolidColors +- Gradients +- Patterns +- Masks +- ClipPaths +- Markers +- StyleSheet + +## TODO + +- Texts +- Filters +- Images + +## Build + +``` +git clone https://github.com/sammycage/lunasvg.git +cd lunasvg +mkdir build +cd build +cmake .. +make -j 2 +``` + +To install lunasvg library. + +``` +make install +``` + +## Demo + +While building lunasvg example it generates a simple SVG to PNG converter which can be used to convert SVG file to PNG file. + +Run Demo. +``` +svg2png [filename] [resolution] [bgColor] +``` + +## Projects Using LunaSVG + +- [OpenSiv3D](https://github.com/Siv3D/OpenSiv3D) +- [PICsimLab](https://github.com/lcgamboa/picsimlab) +- [MoneyManagerEx](https://github.com/moneymanagerex/moneymanagerex) +- [RmlUi](https://github.com/mikke89/RmlUi) +- [EKA2L1](https://github.com/EKA2L/EKA2L1) +- [ObEngine](https://github.com/ObEngine/ObEngine) +- [OTTO](https://github.com/bitfieldaudio/OTTO) + +## Support Me + +If you like the work lunasvg is doing please consider a small donation : + +[![Donate](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://www.paypal.me/sammycage) +[![Donate](https://img.shields.io/badge/Donate-BuyMeACoffee-yellow.svg)](https://www.buymeacoffee.com/sammycage) +[![Sponsor](https://img.shields.io/badge/Sponsor-Patreon-orange.svg)](https://patreon.com/sammycage) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 000000000..53ec5ef46 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.3) + +set(CMAKE_CXX_STANDARD 14) + +project(svg2png CXX) + +add_executable(svg2png svg2png.cpp) +target_link_libraries(svg2png lunasvg) diff --git a/example/stb_image_write.h b/example/stb_image_write.h new file mode 100644 index 000000000..5de315924 --- /dev/null +++ b/example/stb_image_write.h @@ -0,0 +1,511 @@ +/* stbiw-0.92 - public domain - http://nothings.org/stb/stb_image_write.h + writes out PNG/BMP/TGA images to C stdio - Sean Barrett 2010 + no warranty implied; use at your own risk + + +Before including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + +in the file that you want to have the implementation. + + +ABOUT: + + This header file is a library for writing images to C stdio. It could be + adapted to write to memory or a general streaming interface; let me know. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation. This library is designed + for source code compactness and simplicitly, not optimal image file size + or run-time performance. + +USAGE: + + There are three functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP and TGA formats expand Y to RGB in the file format. BMP does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#ifdef __cplusplus +extern "C" { +#endif + +extern int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +extern int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +extern int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + +#ifdef __cplusplus +} +#endif + +#endif//INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#include +#include +#include +#include +#include + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +static void writefv(FILE *f, const char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = (unsigned char) va_arg(v, int); fputc(x,f); break; } + case '2': { int x = va_arg(v,int); unsigned char b[2]; + b[0] = (unsigned char) x; b[1] = (unsigned char) (x>>8); + fwrite(b,2,1,f); break; } + case '4': { stbiw_uint32 x = va_arg(v,int); unsigned char b[4]; + b[0]=(unsigned char)x; b[1]=(unsigned char)(x>>8); + b[2]=(unsigned char)(x>>16); b[3]=(unsigned char)(x>>24); + fwrite(b,4,1,f); break; } + default: + assert(0); + return; + } + } +} + +static void write3(FILE *f, unsigned char a, unsigned char b, unsigned char c) +{ + unsigned char arr[3]; + arr[0] = a, arr[1] = b, arr[2] = c; + fwrite(arr, 3, 1, f); +} + +static void write_pixels(FILE *f, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad) +{ + unsigned char bg[3] = { 255, 0, 255}, px[3]; + stbiw_uint32 zero = 0; + int i,j,k, j_end; + + if (y <= 0) + return; + + if (vdir < 0) + j_end = -1, j = y-1; + else + j_end = y, j = 0; + + for (; j != j_end; j += vdir) { + for (i=0; i < x; ++i) { + unsigned char *d = (unsigned char *) data + (j*x+i)*comp; + if (write_alpha < 0) + fwrite(&d[comp-1], 1, 1, f); + switch (comp) { + case 1: + case 2: write3(f, d[0],d[0],d[0]); + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k=0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3])/255; + write3(f, px[1-rgb_dir],px[1],px[1+rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + write3(f, d[1-rgb_dir],d[1],d[1+rgb_dir]); + break; + } + if (write_alpha > 0) + fwrite(&d[comp-1], 1, 1, f); + } + fwrite(&zero,scanline_pad,1,f); + } +} + +static int outfile(char const *filename, int rgb_dir, int vdir, int x, int y, int comp, void *data, int alpha, int pad, const char *fmt, ...) +{ + FILE *f; + if (y < 0 || x < 0) return 0; + f = fopen(filename, "wb"); + if (f) { + va_list v; + va_start(v, fmt); + writefv(f, fmt, v); + va_end(v); + write_pixels(f,rgb_dir,vdir,x,y,comp,data,alpha,pad); + fclose(f); + } + return f != NULL; +} + +int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) +{ + int pad = (-x*3) & 3; + return outfile(filename,-1,-1,x,y,comp,(void *) data,0,pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header + 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header +} + +int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) +{ + int has_alpha = !(comp & 1); + return outfile(filename, -1,-1, x, y, comp, (void *) data, has_alpha, 0, + "111 221 2222 11", 0,0,2, 0,0,0, 0,0,x,y, 24+8*has_alpha, 8*has_alpha); +} + +// stretchy buffer; stbi__sbpush() == vector<>::push_back() -- stbi__sbcount() == vector<>::size() +#define stbi__sbraw(a) ((int *) (a) - 2) +#define stbi__sbm(a) stbi__sbraw(a)[0] +#define stbi__sbn(a) stbi__sbraw(a)[1] + +#define stbi__sbneedgrow(a,n) ((a)==0 || stbi__sbn(a)+n >= stbi__sbm(a)) +#define stbi__sbmaybegrow(a,n) (stbi__sbneedgrow(a,(n)) ? stbi__sbgrow(a,n) : 0) +#define stbi__sbgrow(a,n) stbi__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define stbi__sbpush(a, v) (stbi__sbmaybegrow(a,1), (a)[stbi__sbn(a)++] = (v)) +#define stbi__sbcount(a) ((a) ? stbi__sbn(a) : 0) +#define stbi__sbfree(a) ((a) ? free(stbi__sbraw(a)),0 : 0) + +static void *stbi__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stbi__sbm(*arr)+increment : increment+1; + void *p = realloc(*arr ? stbi__sbraw(*arr) : 0, itemsize * m + sizeof(int)*2); + assert(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stbi__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbi__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + stbi__sbpush(data, (unsigned char) *bitbuffer); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbi__zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbi__zlib_countm(unsigned char *a, unsigned char *b, int limit) +{ + int i; + for (i=0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int stbi__zhash(unsigned char *data) +{ + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define stbi__zlib_flush() (out = stbi__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbi__zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbi__zlib_flush()) +#define stbi__zlib_huffa(b,c) stbi__zlib_add(stbi__zlib_bitrev(b,c),c) +// default huffman tables +#define stbi__zlib_huff1(n) stbi__zlib_huffa(0x30 + (n), 8) +#define stbi__zlib_huff2(n) stbi__zlib_huffa(0x190 + (n)-144, 9) +#define stbi__zlib_huff3(n) stbi__zlib_huffa(0 + (n)-256,7) +#define stbi__zlib_huff4(n) stbi__zlib_huffa(0xc0 + (n)-280,8) +#define stbi__zlib_huff(n) ((n) <= 143 ? stbi__zlib_huff1(n) : (n) <= 255 ? stbi__zlib_huff2(n) : (n) <= 279 ? stbi__zlib_huff3(n) : stbi__zlib_huff4(n)) +#define stbi__zlib_huffb(n) ((n) <= 143 ? stbi__zlib_huff1(n) : stbi__zlib_huff2(n)) + +#define stbi__ZHASH 16384 + +unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ + static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; + static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; + static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; + unsigned int bitbuf=0; + int i,j, bitcount=0; + unsigned char *out = NULL; + unsigned char **hash_table[stbi__ZHASH]; // 64KB on the stack! + if (quality < 5) quality = 5; + + stbi__sbpush(out, 0x78); // DEFLATE 32K window + stbi__sbpush(out, 0x5e); // FLEVEL = 1 + stbi__zlib_add(1,1); // BFINAL = 1 + stbi__zlib_add(1,2); // BTYPE = 1 -- fixed huffman + + for (i=0; i < stbi__ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + // hash next 3 bytes of data to be compressed + int h = stbi__zhash(data+i)&(stbi__ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbi__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { // if entry lies within window + int d = stbi__zlib_countm(hlist[j], data+i, data_len-i); + if (d >= best) best=d,bestloc=hlist[j]; + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbi__sbn(hash_table[h]) == 2*quality) { + memcpy(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + stbi__sbn(hash_table[h]) = quality; + } + stbi__sbpush(hash_table[h],data+i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal + h = stbi__zhash(data+i+1)&(stbi__ZHASH-1); + hlist = hash_table[h]; + n = stbi__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = stbi__zlib_countm(hlist[j], data+i+1, data_len-i-1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = data+i - bestloc; // distance back + assert(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + stbi__zlib_huff(j+257); + if (lengtheb[j]) stbi__zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + stbi__zlib_add(stbi__zlib_bitrev(j,5),5); + if (disteb[j]) stbi__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbi__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (;i < data_len; ++i) + stbi__zlib_huffb(data[i]); + stbi__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbi__zlib_add(0,1); + + for (i=0; i < stbi__ZHASH; ++i) + (void) stbi__sbfree(hash_table[i]); + + { + // compute adler32 on input + unsigned int i=0, s1=1, s2=0, blocklen = data_len % 5552; + int j=0; + while (j < data_len) { + for (i=0; i < blocklen; ++i) s1 += data[j+i], s2 += s1; + s1 %= 65521, s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbi__sbpush(out, (unsigned char) (s2 >> 8)); + stbi__sbpush(out, (unsigned char) s2); + stbi__sbpush(out, (unsigned char) (s1 >> 8)); + stbi__sbpush(out, (unsigned char) s1); + } + *out_len = stbi__sbn(out); + // make returned pointer freeable + memmove(stbi__sbraw(out), out, *out_len); + return (unsigned char *) stbi__sbraw(out); +} + +unsigned int stbi__crc32(unsigned char *buffer, int len) +{ + static unsigned int crc_table[256]; + unsigned int crc = ~0u; + int i,j; + if (crc_table[1] == 0) + for(i=0; i < 256; i++) + for (crc_table[i]=i, j=0; j < 8; ++j) + crc_table[i] = (crc_table[i] >> 1) ^ (crc_table[i] & 1 ? 0xedb88320 : 0); + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +} + +#define stbi__wpng4(o,a,b,c,d) ((o)[0]=(unsigned char)(a),(o)[1]=(unsigned char)(b),(o)[2]=(unsigned char)(c),(o)[3]=(unsigned char)(d),(o)+=4) +#define stbi__wp32(data,v) stbi__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +#define stbi__wptag(data,s) stbi__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbi__wpcrc(unsigned char **data, int len) +{ + unsigned int crc = stbi__crc32(*data - len - 4, len+4); + stbi__wp32(*data, crc); +} + +static unsigned char stbi__paeth(int a, int b, int c) +{ + int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); + if (pa <= pb && pa <= pc) return (unsigned char) a; + if (pb <= pc) return (unsigned char) b; + return (unsigned char) c; +} + +unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int ctype[5] = { -1, 0, 4, 2, 6 }; + unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; + unsigned char *out,*o, *filt, *zlib; + signed char *line_buffer; + int i,j,k,p,zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + filt = (unsigned char *) malloc((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) malloc(x * n); if (!line_buffer) { free(filt); return 0; } + for (j=0; j < y; ++j) { + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = j ? mapping : firstmap; + int best = 0, bestval = 0x7fffffff; + for (p=0; p < 2; ++p) { + for (k= p?best:0; k < 5; ++k) { + int type = mymap[k],est=0; + unsigned char *z = pixels + stride_bytes*j; + for (i=0; i < n; ++i) + switch (type) { + case 0: line_buffer[i] = z[i]; break; + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; + case 3: line_buffer[i] = z[i] - (z[i-stride_bytes]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbi__paeth(0,z[i-stride_bytes],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + for (i=n; i < x*n; ++i) { + switch (type) { + case 0: line_buffer[i] = z[i]; break; + case 1: line_buffer[i] = z[i] - z[i-n]; break; + case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; + case 3: line_buffer[i] = z[i] - ((z[i-n] + z[i-stride_bytes])>>1); break; + case 4: line_buffer[i] = z[i] - stbi__paeth(z[i-n], z[i-stride_bytes], z[i-stride_bytes-n]); break; + case 5: line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: line_buffer[i] = z[i] - stbi__paeth(z[i-n], 0,0); break; + } + } + if (p) break; + for (i=0; i < x*n; ++i) + est += abs((signed char) line_buffer[i]); + if (est < bestval) { bestval = est; best = k; } + } + } + // when we get here, best contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) best; + memcpy(filt+j*(x*n+1)+1, line_buffer, x*n); + } + free(line_buffer); + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, 8); // increase 8 to get smaller but use more memory + free(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *) malloc(8 + 12+13 + 12+zlen + 12); + if (!out) return 0; + *out_len = 8 + 12+13 + 12+zlen + 12; + + o=out; + memcpy(o,sig,8); o+= 8; + stbi__wp32(o, 13); // header length + stbi__wptag(o, "IHDR"); + stbi__wp32(o, x); + stbi__wp32(o, y); + *o++ = 8; + *o++ = (unsigned char) ctype[n]; + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbi__wpcrc(&o,13); + + stbi__wp32(o, zlen); + stbi__wptag(o, "IDAT"); + memcpy(o, zlib, zlen); o += zlen; free(zlib); + stbi__wpcrc(&o, zlen); + + stbi__wp32(o,0); + stbi__wptag(o, "IEND"); + stbi__wpcrc(&o,0); + + assert(o == out + *out_len); + + return out; +} + +int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) +{ + FILE *f; + int len; + unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); + if (!png) return 0; + f = fopen(filename, "wb"); + if (!f) { free(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + free(png); + return 1; +} +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ diff --git a/example/svg2png.cpp b/example/svg2png.cpp new file mode 100644 index 000000000..f9c33d733 --- /dev/null +++ b/example/svg2png.cpp @@ -0,0 +1,66 @@ +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + +#include +#include + +#include + +using namespace lunasvg; + +int help() +{ + std::cout << "Usage: \n svg2png [filename] [resolution] [bgColor]\n\nExamples: \n $ svg2png input.svg\n $ svg2png input.svg 512x512\n $ svg2png input.svg 512x512 0xff00ffff\n\n"; + return 1; +} + +bool setup(int argc, char** argv, std::string& filename, std::uint32_t& width, std::uint32_t& height, std::uint32_t& bgColor) +{ + if(argc > 1) filename.assign(argv[1]); + if(argc > 2) + { + std::stringstream ss; + + ss << argv[2]; + ss >> width; + + if(ss.fail() || ss.get() != 'x') + return false; + + ss >> height; + } + + if(argc > 3) + { + std::stringstream ss; + + ss << std::hex << argv[3]; + ss >> std::hex >> bgColor; + } + + return true; +} + +int main(int argc, char** argv) +{ + std::string filename; + std::uint32_t width = 0, height = 0; + std::uint32_t bgColor = 0x00000000; + if(!setup(argc, argv, filename, width, height, bgColor)) return help(); + + auto document = Document::loadFromFile(filename); + if(!document) return help(); + + auto bitmap = document->renderToBitmap(width, height, bgColor); + if(!bitmap.valid()) return help(); + + auto basename = filename.substr(filename.find_last_of("/\\") + 1); + basename.append(".png"); + + bitmap.convertToRGBA(); + stbi_write_png(basename.c_str(), int(bitmap.width()), int(bitmap.height()), 4, bitmap.data(), 0); + + std::cout << "Generated PNG file : " << basename << std::endl; + + return 0; +} diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt new file mode 100755 index 000000000..de589546c --- /dev/null +++ b/include/CMakeLists.txt @@ -0,0 +1,4 @@ +target_include_directories(lunasvg +PUBLIC + "${CMAKE_CURRENT_LIST_DIR}" +) diff --git a/include/lunasvg.h b/include/lunasvg.h new file mode 100644 index 000000000..c83c27c91 --- /dev/null +++ b/include/lunasvg.h @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2020 Nwutobo Samuel Ugochukwu + * + * 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. +*/ + +#ifndef LUNASVG_H +#define LUNASVG_H + +#include +#include + +#if defined(_MSC_VER) && defined(LUNASVG_SHARED) +#ifdef LUNASVG_EXPORT +#define LUNASVG_API __declspec(dllexport) +#else +#define LUNASVG_API __declspec(dllimport) +#endif +#else +#define LUNASVG_API +#endif + +namespace lunasvg { + +class Rect; + +class LUNASVG_API Box +{ +public: + Box() = default; + Box(double x, double y, double w, double h); + Box(const Rect& rect); + +public: + double x{0}; + double y{0}; + double w{0}; + double h{0}; +}; + +class Transform; + +class LUNASVG_API Matrix +{ +public: + Matrix() = default; + Matrix(double a, double b, double c, double d, double e, double f); + Matrix(const Transform& transform); + + Matrix& rotate(double angle); + Matrix& rotate(double angle, double cx, double cy); + Matrix& scale(double sx, double sy); + Matrix& shear(double shx, double shy); + Matrix& translate(double tx, double ty); + Matrix& transform(double a, double b, double c, double d, double e, double f); + Matrix& identity(); + Matrix& invert(); + + Matrix& operator*=(const Matrix& matrix); + Matrix& premultiply(const Matrix& matrix); + Matrix& postmultiply(const Matrix& matrix); + + Matrix inverted() const; + Matrix operator*(const Matrix& matrix) const; + Box map(const Box& box) const; + + static Matrix rotated(double angle); + static Matrix rotated(double angle, double cx, double cy); + static Matrix scaled(double sx, double sy); + static Matrix sheared(double shx, double shy); + static Matrix translated(double tx, double ty); + +public: + double a{1}; + double b{0}; + double c{0}; + double d{1}; + double e{0}; + double f{0}; +}; + +class LUNASVG_API Bitmap +{ +public: + /** + * @note Bitmap format is ARGB Premultiplied. + */ + Bitmap(); + Bitmap(std::uint8_t* data, std::uint32_t width, std::uint32_t height, std::uint32_t stride); + Bitmap(std::uint32_t width, std::uint32_t height); + + void reset(std::uint8_t* data, std::uint32_t width, std::uint32_t height, std::uint32_t stride); + void reset(std::uint32_t width, std::uint32_t height); + + std::uint8_t* data() const; + std::uint32_t width() const; + std::uint32_t height() const; + std::uint32_t stride() const; + + void clear(std::uint32_t color); + void convert(int ri, int gi, int bi, int ai, bool unpremultiply); + void convertToRGBA() { convert(0, 1, 2, 3, true); } + + bool valid() const { return !!m_impl; } + +private: + struct Impl; + std::shared_ptr m_impl; +}; + +class LayoutSymbol; + +class LUNASVG_API Document +{ +public: + /** + * @brief Creates a document from a file + * @param filename - file to load + * @return pointer to document on success, otherwise nullptr + */ + static std::unique_ptr loadFromFile(const std::string& filename); + + /** + * @brief Creates a document from a string + * @param string - string to load + * @return pointer to document on success, otherwise nullptr + */ + static std::unique_ptr loadFromData(const std::string& string); + + /** + * @brief Creates a document from a string data and size + * @param data - string data to load + * @param size - size of the data to load, in bytes + * @return pointer to document on success, otherwise nullptr + */ + static std::unique_ptr loadFromData(const char* data, std::size_t size); + + /** + * @brief Creates a document from a null terminated string data + * @param data - null terminated string data to load + * @return pointer to document on success, otherwise nullptr + */ + static std::unique_ptr loadFromData(const char* data); + + /** + * @brief Pre-Rotates the document matrix clockwise around the current origin + * @param angle - rotation angle, in degrees + * @return this + */ + Document* rotate(double angle); + + /** + * @brief Pre-Rotates the document matrix clockwise around the given point + * @param angle - rotation angle, in degrees + * @param cx - horizontal translation + * @param cy - vertical translation + * @return this + */ + Document* rotate(double angle, double cx, double cy); + + /** + * @brief Pre-Scales the document matrix by sx horizontally and sy vertically + * @param sx - horizontal scale factor + * @param sy - vertical scale factor + * @return this + */ + Document* scale(double sx, double sy); + + /** + * @brief Pre-Shears the document matrix by shx horizontally and shy vertically + * @param shx - horizontal skew factor, in degree + * @param shy - vertical skew factor, in degree + * @return this + */ + Document* shear(double shx, double shy); + + /** + * @brief Pre-Translates the document matrix by tx horizontally and ty vertically + * @param tx - horizontal translation + * @param ty - vertical translation + * @return this + */ + Document* translate(double tx, double ty); + + /** + * @brief Pre-Multiplies the document matrix by Matrix(a, b, c, d, e, f) + * @param a - horizontal scale factor + * @param b - horizontal skew factor + * @param c - vertical skew factor + * @param d - vertical scale factor + * @param e - horizontal translation + * @param f - vertical translation + * @return this + */ + Document* transform(double a, double b, double c, double d, double e, double f); + + /** + * @brief Resets the document matrix to identity + * @return this + */ + Document* identity(); + + void setMatrix(const Matrix& matrix); + + /** + * @brief Returns the current transformation matrix of the document + * @return the current transformation matrix + */ + Matrix matrix() const; + + /** + * @brief Returns the smallest rectangle in which the document fits + * @return the smallest rectangle in which the document fits + */ + Box box() const; + + /** + * @brief Returns width of the document + * @return the width of the document in pixels + */ + double width() const; + + /** + * @brief Returns the height of the document + * @return the height of the document in pixels + */ + double height() const; + + /** + * @brief Renders the document to a bitmap + * @param matrix - the current transformation matrix + * @param bitmap - target image on which the content will be drawn + */ + void render(Bitmap bitmap, const Matrix& matrix = Matrix{}) const; + + /** + * @brief Renders the document to a bitmap + * @param width - maximum width, in pixels + * @param height - maximum height, in pixels + * @param backgroundColor - background color in 0xRRGGBBAA format + * @return the raster representation of the document + */ + Bitmap renderToBitmap(std::uint32_t width = 0, std::uint32_t height = 0, std::uint32_t backgroundColor = 0x00000000) const; + + ~Document(); +private: + Document(); + + std::unique_ptr root; +}; + +} //namespace lunasvg + +#endif // LUNASVG_H diff --git a/luna.png b/luna.png new file mode 100644 index 0000000000000000000000000000000000000000..5db24e50d6efab6b7e3fe16b9ded09fc727c08cd GIT binary patch literal 16536 zcmcJ%`#;nF|3CiN$;ReTPID$jP8oAPZKzSHR4VCUavF&xMUrh~Qck6uI#`L+v!YVT zq0J$Lu&8v9iHe9J=WUPgL$CMuPx$;`moE13xZm!l+wFS2-fp+jytjME%Baf#03f^B zbK@=mfRR^zFjCNO-e#BF2LQ7F=8djCafbu(sA4^WI{g3Lhr10D?=ci_(f`Bb8c+?CArc4|MWG9Z9ScP}nXEyQMz`E^5QiI2$E8Q$(sAP;NUX4ckM5sQ zfuFP#!3~=Hakz96>o@Z+SVVrt#e;~~YHBWkaxLoj>6u>xMK8Gc#M=GuzAVd@O@OqH z3^q`rNALqOOZ9HM37Q@rNQfZqR{|}?djORqGN788GAqpk=3Dg!fnC3oqql|w&*&9g zw`Qz*V*!{hl2Jc2C~UACBobVZDp%wsL;dJEPl$?)`q@NO=91t(h4_v-F`IM1o~Fu5y0#5O8F6lEEJ1JreO5xO9&KHBkG;r~YVkS!7K5apZ6Y zWCh-1dTMLz>;3yBXMmPBPAt?-#R^9ghrU|J^)lp2@?w_|RBmK6*LmS^j*<$=XZsg* z;vY2Z@i;b*2D3~5eiD?3O>1vQtAGWPGw@JC)My^ zRIq5Cp}br!Ut_T2rszZ^L9_bbU)NXFME{Wa5=)U&r3R?V#Uuh1B`vl-IRbAJU}wW3 zsnLTPP$eN%5Q0Ra=&lF*pC)Fhu-#2omP~K8KWB6LQ`-qUpr!N(vv`J8>6hy^DZsx& z>4t|b$k~0UzR4OK`2MN?Z7OqDan5>o-KY!|X0p>|vZ9))DX7S-S-XpQsl@WDAHMM} zu(1hOaVxaPGBo#d`_5>Q(d*shRP##J=DZ!f=NqLxr9E&rDY(onKu8stb)0Tdlsjw{VB8 zk!yo;2M+?bUk|to{2K4!yhx#Me~*jo{1oIeSB@vz)#*An(SNWDWT41**CO=Kg{}?G zT;&5K6)X1{i{K9gA>bky`n~qshty)!An)G>G6xw;ZHI_brQmDcrZzp^qt!j09XS1m zz{U$hT%O8cxWK0ArhVPpP!DVaOM2zl0uADhtB%`we`vG3TxP%Ljs^WH;GBmu{nr~9 zHJ}f;?(71Y^v)JqYqA4-zQ82*81v{{A*(OI>Xy43wG!BP2PqhzI!s|Ro;{5(%*ra2 zUpfb!CC^m4RP0GL&=ywna-vvT!+PZf4~&j1a~WW`M@@5Omsv4NSGIAXW45N6cL@HF zm(&$oDQMJ&mRk4bl_?NpGrYj?pE1u4YHcG`uGpF_H5fnf!HmyfYE6YuGtj85*O zYMBAj>|TFCeI;@K{>1m&{*1&q`YG)CXRtBbFfMC@VyKPq{9L@iP6wnNS*N)&z~g(v zAX@OPTB(3Ua4AAc1PQ(yofPJ!F zR6$N{{@z`a7U~VXkVPuGDT8s}n-QCG8H~H|j491+SMH?pXxQuypBWI$C4u12^bMt@ zrXMq&18;Do?!^9{j}t1;)jkniDbz4q8r6oUr@NSXneOYkFgD}n?q1*#$gEx2{5O~v z(2@RabclOK^J{-Fby$5{0_5|_dc3#1Cc;i{*D)I9cwHyWG#gs4L)6s`5ukWkvvvUd z0A!UKqr(H$w$ZOZrl{tJ-^0Yu69rAUO=EB)(sn zBcFtEXDW}Bel8k!`_;2$EQ1nwzG}r1>2r#-2l94}2Nprn&~btF<=@59N3D=jCou#| z8oWPt&BMw^m9JhUS}_#QKi~rmiez40P4tD2lM@KzFvNHMhDq8dTOT)|Pd#WnUh)T= zxbh^vfKO}GTG@uo3j4Evwy&i-50vl~4^W=Y=Q^6r@#=xV>bB&WU{yEHw$#^MSrW~At`z$S1g>*U{EL| zHe=k7GF>G&Zwl-<#65=6oJ9Qju}8~H#E!%O?ap-9`9EW^Qr@@F;o?<)G&mvN{|Q@O z6yZoLl-}m})IGNYdZ|H2TrM5n8)dV4rs2E^&~>m*wQV6QOoy(yF$g-!4JkSmz_PS) zZK4RSo$pmZk^nU=1FytXfUj?{4L~EBm>I(eqjk?%`fow~QUIyr7eBk*2r&fi;aQO8 z_zh7bY=EXYiLT(mPaeJZ6YI^AiI7h6yR83IAxIE(B*+hYT~F#zH|fX+bk_OJ3f+m{ zbUsdQLrwg6sA;w!wGWf+APukeybkL&E5r2{8%!4TjXFglo1 z#48x)f|4vjP$T{|&+xIat!>`fNXx(LyAdNgW&j!Qvm*ei&Zv<8Cu5dFxT8qN%>a8oU}J zF&Y!O<-c;B=XY<)x7T+PV<)GHB5I%1_0ebQ-AJ5iYoZus)+CnQ&_{0HaNg{5K9gu| zYHEsmz&tBNW6_+}%tsw09p)B4R)UNhdVX)2i?J3ZXI zqw%2|{*=X;>Ce`KAPGMw7c@AJ?z^x&J*(09C94SUa#jM5_#sA`Kh`Zdgbqg~`Cahm zg46}!D6w%ltEm3rrf%Gb=h3T7KL%pw0;^$hzH31Lm9?D1>Iw8R*k7QmoU{^TL#nj1>a*2P7@#;9P-aiEQU-0QFBU<}Z-eCL z&t-j{Ae3K$V20dgAoX&GL=^Dll8;}T<8_((fF3w9W06%|w=%L{u>Y&Gh!?%&3)?e-_Wg`j#X0j7FI|Ji)e# z$f^!G`9-)wWoOo}F9Yv+pEN_4RLCG_=nP4!Qdz2SkL2$U6GYhoCrxa9efN71jDkYz z<^$=t36lM}Ttrrn`t#l(boQBCs-3VpohgFHl z?L6Gwg72<7ImuFMJbt+nwz^W-G*{HySGT^-;GdubpN__EWx|SFyLceV$#+JPo*PSe z@q|a@^W20W$dA-$T+eaG5Pp{Ae)u8(o}+ykr90wjBB+--^cNT7T|cd6_rXR<9nSeM z+G0MyevPxB#|0`3n+Y&8BjPl>A()-TyQ0Ip#z;!odcA!>k~}eZft!DmCHt8nV=&PVXqBEE}#6LujA{u8zVGB!NMGH~kJCb?9vf^HgSX0>=v^7RT=VI-ST_ zGdc6I9LGo%cE5DSbQoV^+r9sFLl~o3w$j z)Y4R$@y*aa*>5Ko&5tm@mo_#wKZ$eyB<}Fa#->YI?#}3b3fn9nO_7=c8EVf<@E9FYwm@2za1+*7#dWJPLm&0e!ZH`%^BoDU{gm!7)5hSx=t}SSvIz~T8yy`^ z7d#H*8IgP**Ts@FLajQA>uC1US?r7dXGifl_Yep?d&RYpO+ln zE4R1W16?-O-sI$fa0}rf&{3ed&Unt6WSdj2cWZ#ji9_<&|8ka@#$`X|HRASsr~kLa zl>j%iiB^uYoHq)4_b&aZhR-?R`}+2K9AGG=0L9%P7yPKAq7p6lfvH)E66k!0>S3l7 zb&`=^v-H01YEV7Q>DP0Ad7!Jglk%dc;FEoy8s#InQ*YkFQvGbe&l?cOxU2xbTi&0B zR7pIJSViM(>f)G1H9q^T^X}z%K2Ijj#D3otVc7 zKidx33xMB$S;kDr)mlHOadsEKTi2a#YBo7aMQ2(+smR!#;O(dgOjZ@-`1qMy_gQ}A z7z3^?2gIT9$PiH^&ZqlZ1A=Jq$U;p=B-eh z9;9?6V1YF90WEhQt`JwdM{S|$6U<8*iu}R&3Bmm2Ee2nHBm(YzGy~yF+iA5MYjs|^ z@B7eq*f)vI=RYOK--X7Up8ErG9PC57m6)OB0~RoLVh>rxg>&MsB!d}vhiv!-m*=K! zwYNn&LN=Q2X5e|=xe{en58JR%Wyg71SKLz9t-p;R2_5k?FX!u+jz)PyXVcM{`9qFY zH^7v8#XRLjNCya-VrjppXmyR@;lUutmqzgRYGLW0a3kCO6+&SB||U9gz=8=Ex>z6Pn2yZxfAuNsf&AHeXJHV&+21N*s~u zRTrUZ@sckubZ}Nx`L7IKKA@b~sLB-Vha&4Q01@C_xzS_SEyCpudqeRkydGfu^%X;$ z_DtZu4_BT4dj;q)K<}wS6m?(zC~0P#<`!hMrrZr3BZl|_j1vtYvau0 z;E*G&&SWYLxHJ7r{`cV~`SCpmNZ=CbtgikMo(xaXR(rjrr6@|x=N`S%8~^Ykg^Xc7 zcfWk1e?9HU@JI7-^IJ#8y^Q|LQ!AtDWJ7+eWL-O#yPNfp^vLz-rks_I`0Ci@;$z$TAsUdb~eT4Y`i;d>g7do0hMSJ1sXc2PUA z(w7W$oK|Eiu8Aqam7IYs+(;v8RQ=p>j9}1q>B%XaPj{B~td-@(P2r|=Nf$Y}9`byD zLI?2gMzl;~%z&=`>UA<-WpIa6RmEL1A|&`{gPW%{S06w zmp|k&1Jxk{ZG$8~13lLddmOk{<<7jCgz(@0iI%uShS)&j2NzEA96 z6`1No@%*856-KuL6{SMsC_XEgx}Jvd+wfY>>EyMEedjDKmr7JVz7t&sK4-CsmR9o5 zl+fX@CxD^5M6^hJV2M>VL?a&N64G;x-HAwYj(UfwCU5AGgvgX@Lz!KX$ZM{b+LK$+ z7=-Q!^-Aa5)ZZ4H&acEmK2Xb$nt1|$VRLe7&I#WiVcm6V#r*^PBkKDw1Ph{D^=qEu zQ`e74N!2ZX@so|J`nkKgS-y|G-(TUxVsgSnYUh<-!^Jx1a$dx%Ym=vT9%r-G|Pne zHb+BpU_mjq$fNz0o)#QbyG=VuGu|e{gNU{=F6EF;lYxv&WTccqNU2^P8(MBP_Sof{ z{58jh&jA&t0Rc-ODNn_vH2#qbL-GpfzV)WjD4x6(zVPxip8ZZv#`{0{-gqsu^%Qri z-8n!AJWBuiHQptBIpsGLuI9L-yf>7agSq3-U&Ot9iDx%#G%kDAhK+cZxqT^9C9!cG zlDYNBLLyzXLmP^%BZXTz?oZiApCHUG4h{`!_nNtSx9?!)r3U=v^+0^5Dg~&BcvCuO zUFdTRW(-M6GPszRgV+E1vX;-G;!dmOqX+YKM|9DNRG4%8S20BXyxhr)qpDK(BOh?W zVGED|b#m=mXYWC@F%sS578bcdPRjd5Y|OJ6=+W!$%(PL`o_73wWoCE5>hkAh@d9VW(#-aKDCXDD9Pe1+p}ZLU|5$fPv^eFiiD`X55?8ZJrs% z-LM>|g9mYNUQ%{ObIaT0?I&1D)d{%kbJ_xVDAPilpz>#0^`{T$;faP~v9Iqw3_b!pQihqsJVZ`Vq{ z!Mmux{Xd1D3@T7>SB>8)16K4^nz|*`>bHkG)e1>*XF|J1vNJQwxNV|oWDJl*M~$I7^Gy zSifa$yz6DGRD&c@9OvRU=eOvQe_ltvIYbqHVt{JbDf&=i1=6ELXl?H)@z@S_5h>B4 zyZS#FOHj32+uIf6fDg2?-l?*J)DJQZvYkv^qhWEsmz?(qbjwF*=Q7wlx(uv)XYc?Q z(pI)bcK7&m+~(2UhqJ}SMi-J4s6kh@7(X&tbhEjPCFe50haURj(15!!%<9{uS6~pn zY*9M9PIuWg$E4WkqDX-n5Ty4qVoe>kqv>DT|4I*W&=V9{ z7xsDLV9lUc?#eSJk7hjrgP@V4A@EfuhKRUl9IVnRB<3_OZ0G9*kn8eRhAf!Vs^gE7 zpf}kg6XhQ7|4?tUnK&|Z^U}^d1C*d-t2~N@)@@A>0U@*Q>jB}5VvQp?*lc%YNv46r zcXzV=f3I$)`u^CsBS{n@`Oi@u3tiYN?$om9rHS%SN*t1%WkrkAJFY>ogVYSJ#M$Yn zR!HOG{^4@L)H}=dc_ZL5sc`8$j>Sk%kM}VLAjc1ZApTmrAm}Y#2?7}bi9xBUZ!&P9 ztZs%CoVUFa04YdjKxzaAHwzD{1lGEY!gjE4C1Mr&fPEToLC63lci@!UjCW=oL!emZ z(WA2y+BR~`{!Knu;Yv1*ry)RkBr~9uIf4;s8GLCY&tFO}CEs=w~lb#^#L1ll^yy_YQ8R+IAktQKyh9LNku3H(dBGR2=(-V%<%6NY0A9m|WE|R}Ei{e3uT3_7@XOg!;O=rj)YWSD8Kn(PAQFSiSr=1m8-6((sivk> z)&~#5AA@D8Fx_pD_|(nPt#*Z&G!1>d|e_uiqm>7CQ5AxHZL#E0#uJvyaCqX?mqY z!tT^I^7l5=q%pw!a6&x_7d!XAV_=QU&(CLX8n%#AWOT2y#H+=*kBxo8eA+|^76pr4 zJ}+sQEwf`Fs3e~S&klo@-$+X)QDo+I9P$NI zf8aTn?$&@_js^y)Zm7k(82M0IEr(5wS_# zYlW@{u1G}UM&R>MYiny04;S(MX+=8aFKx@+L=01g++8x{ZXJO>^9nF~Cv|9OQ77)W z!g~MY&-XcdO$Y~w1lD;NtyU5!@~A+0RscbwuJt@}a0|F3zBUL+aX2n0s`t#|9GBaE z>Xg*>Q-XybBM&~(_Is8nP^B^vNtVOjUscd;Qpv|P6@3I^Dec}ZBaf85r(pM)`M&vh zGRj{6#Af>nj z3P;}hNZ(Js_(a*q2L{QKkl^58yjL-Uqw+0^YPb4~rwL7L<7-w?A$q-Vjg@gVg>_ZS z=Wq9ieNftWeGy8E0%$yYGVrV1e@Z2>bxs7kIXE{pqY6b7CKOo1QvegfkcHgi!>HCe z)A8g`UyKhc0bELEjVs<~l?;}zc=)r6Z-l?*b<_ZIvLSf`;bx5sS;yA|}`0ckmwvr*UXf`LyOw(X5q1R}Q^nvK>g9|WI z5$*Wh=_slRWKI#Tay@hjB)h-Ck$YW~UI4B++E+BUEDn7v=dXrhZP{u#XQ$yQx#xPT z3M0mSv=R-wx_FmIf4%U>RXX|1LD)u+tNTnp#n_BY9o%s|yx!)^)(fGGti#fFexjWg zNHXz$gl2M$3GVlO;!~VRE*Dam`N5jnR@$E8;4MCQ#GPgr>XxHfATNgw64P&@to&ql znIp-Ew&uE4SK)^@boULATjq-f7-}P@i!5nLKS;{vOtkFcY8m&299__(>z0;T96J0M;Su1uS_> z=#xHQU&@RG0yt}t`1%xGjYe{;z3b_7H!fxbRG97-&5TVlOR5IA_k!mcZCJ9P`^%0k z?nxObS6P?$>MkoBsIm6jD_f@kY;&kcHlRpx7Ew=Bt&E7iOLm;x0dgdHVL8>|s4Q!6 z@#1$CUHUW&`lMMqs-n^RU&4KePap!CYgDX9o>8E}CX@z;zXPYzMv2QZ>XV2sEe=)j zb_{gfdlc=%;G}<>xvQj$=UFjDSs);#*hRgbr!{#9IdXe|=O|8{ikcZw~3j^D;$~hl^nL9&;wsv`uo;rs+k}b0B>E`VdJDqAC-e)2*wI3- z{ZDoHCF~nW&d?C%OOo3*h>R|tAAQ7)qCEZe4-B_Tv+C&}!MgJp@;O&J_Ye|GZU#G| zJQTtI`x@5l+I_!-tsxPMw?Sk20+!%UO(-b+lt2c%>Jji0Isv5(%ACo?6<0nxyY3Y% z6_J?vlL>t-%eu4*j;OO$kIo{dT;zkqgv0%Po{QTRsQ#LjOJSADcK@L3xpP3ore^Z# zJmXk9qNT!~kX3s+EG;Dr=fb7`A^Po@3Y=ihDZEeiV4J&7!KNU`oT)+D_x~aG%_v=B zBl+tM_~+#vvS(NieQ&p!-j*R#br$OX|3~)gktdV)yJ~Fky?1K52V8DyQaOG|05+-F z6uSNc?(7{H=Bc`hC;X^ua7RNE*kiB`b55)5 zhsT~X5~35w535kMVMIdv{XbI*^nhxLQp`cp!vuMUjB6Pg)uC%FEhHs}u|nIP0ltcW zHUT}jN*yjZieR?P_I$s5N*LB>Wj7^uR3*+DxSIC%y1?W&#@QAk{+UGy!7w|Y!L+_~ zbxK#iI5)jJl1!|-evpT~M|=A9=VC*1vrbc!62JW+tHw1l!(e4cpe5IWa}KRJ_*D}Z zA=s-iiqczXIwp8lbe&A3J^wL5Lhyr?rM04(xAMwf;NJrO*pxn$(*2=L`tZTl>$x_2-V^ZexBU0LD&<6_E;er9t3X`Xeb(6a)oITg3Top4j!5n31oBsEBVn+8yH2)3h}j z)p|S(N%jmN7ETTX-+uw@2QB?2uct$;j(wMgd5=vv3LoW6k~TX!p?!`@M6(xR=Uj=$ zzFxnME8d4lH+TjyQ4^%(sgQ7J-Cjd}j&6MX^dyVzqMe~M4#C+!b>Ko8tG7YEzjA;J z5)u~vswIT=(tNX4h-0=Ue7@A$Ch=B03}YiCO(kE{H~RhxT&Ne0w(pY)KWs1~OZ)lD zd;l-ih_Ws&phE!58*^~Y>xXM^IAAmpwRVL{-tK}iNRajCf}G72B9R#DSCPqHTDfe1 zg&uIdgmVZwcMk^_XjZyz0yO>W1eEZ!lD#~@kPs5YbH}Xu{#}Xl7?{B6kHs$dg*z&O za>j!eN+4`CoWVr(i1R&$39NNW|zoc zNQG0-mGEXm*mg(ANgJ0n?~kp8Fu^<$(E^*#>0AFr$K&K;G+z&Ji&J-{uQ)I=mZcSA0)V1`&-Sa6bt=t{5hAuN+kS>? zz7<2|Yw|GaP0{?k{7_`IGBY>6&79S4*_l~shc`uaaT&#CJiZ9xNfipfO)&OIBxA@C zdQA5sUJcBuiWGwU=uoSV5HY3VU{BiD7(}0)DM|}i_S}l~O7}``X!k$PoHEOtVm$8FgixW z(0}u`B8n$NwzhspDP>KoaR^HeNksj62pjzRS6-+D>InxF9|CW~MufU63^(2a7? zHa{mhki1LRu|lX(XBKHS1BLXpNG4i;=fOGe1ZlILdHFN=DkSrwf?5h?OB21~adH%6 z*`sTB0CTH!{K_vm+sCla1w(NbEUA2hTy%t4n9DCp5 z@lk%)Z(RrlDJtd+K&e02$>bc5EigJ~pErRV{sgA&Pd zJK*qcd_@KWSy%Co$G*5ubvAV%GtnU`AyTFMPYbZEnLNb)4-XF4Et!|daoS?OlcY*t z>zf588R(C^SC-0foCeIdLS+)IkVJn}3>C8B23{qGE=>JVZG>Knmt68*U`0q-<6KjI znse+2pj=?#EP~b<#RiKHD57=4B>rx)Tkq`x3d4=H;U8|l@bHuR@qL)enZ1K7dtKlD z+SzaJ5~Qx2?SVv!BMXv0hK)rKVlR7nW0j9k3@==ijkEq;uESNMyx8*2s6Y~0VGu7|>L(VQ{iJr;Bbm0`hc`a+r%rV%GpT3`i?`ZuEaZ$u)OQ}*X%s`uO%UxZyg}TgyuBiNH>inJgX%unsKID_!}&&6=xXt<(@tE?g?vF zzfjY4kh`3HiM2Cv?JxiCCJZKdVqyWMsR7TB7b9$TkXuHR2_rsg-HC_0r~Eic@>az- z2*Bl@9){i>GWRR1C^>pH+)9EeWDW{~0;!sZY~1&+h8&X|eR>G6o{`>zB~$bM3>5qq zTWU$LjL8TIk)k59rTYp45kq_-XB)(0xsZe>_gjG46VKWD$DZ|{6b0)#UHqG*->Afi zyq_8A%{X@>`M64Via&)Yg5bUblDzdG*^GNj;T`AbK}t=dQj?Of`zjYV@N1{zqkD&e z!u2MMAQ8xzqs{5a!u%k;r2)}9t+D0kB-yOKh{VV+z(u<;r@+6b`}>Q~XJMFVU42B) zMgg;9#DU4bvpq)vm}T(-6W!@(DO#8ut)P?kPS*&@)E`aH30oCnwLd82!Nb1_cJSx% zR+l~8nJQ4UCzAsi?$$A`?J<;((wA4C0rNV?A`@(aWXNPraq!M$jeD#<91wifam*6xD&^1;Vd_{zxRc1pvQ+xvO_ z){Y@3pC8nYM(MWJgLpL!%2NopdYd504@vP+QN_XIVu^D+6MjpG+FoYEx0It*^LxFv zj_T5R@sqSwP;~0Wu>2b%q?9YIes-1f|3p{4lcH}Ov=KD?8ghRQAnmWG4f_B2XXzG5 zLSl~6v75U$+~NmTRp;?Hd3FnduzqjnpRii12pMv=heQJIBUU@E=w9W3 zrsPl)_it?gUBS?ux=EN?Fcsxocir>F5c}=3J>~GwbD^0kFo>c-5|#!mrP2G_C%3ms zvQAr~rqd^KuQfgbkf%p3wTsAs}fiAYvlU~G@}uP1zWdA*G2NR`DgDKVyTDrJ*6KRN)3SOYn6>w1{$rh(R%R$_1+ zJFBY(?S4T=>E70D+x{5pH-dO7GyPPGN4x741*)bIN`RR%gvwNSf6YTaJlM~LifisD z5cs>C(N1Qi@m3&on;;1|K1Kgla8r|$n_o4EZ=;=lv+CZf7|1Ysi=5@>tX_`6Q+$emszPduYX^ddK6a?Pw!S??v0Kf011zg8JTEEROLWI z$7)>5am&3nD(rwS%c#ud$D(Bo%hS`tMUc^IdemFD=sIzysJ$TH6LnTvLZw&vg6J2+1fESRsY9~@dbqM|+mJ0?^!4-7`1 zI-5E=ELXATfQ5Ic=+fog?53|ZX}QrFbSHYxzjGWB!BgoWthdVNrP#OAkBJ`^yzlQN zDmmG%g;dQYvec<&JHhr#8qkWg%E7uHDSQa%J{A-h8jR1(APMrtSGnl}Im~;1L8!p| zwE|)1Udcjg!D}r3emMnO4^hGh-lHM!!wzs`5qtDq-Y=d^+nK|$^S^Fgc1!J@KGA&# z(@2u?r*0K4)Aa0~xHE4gXTW+hG{bKJeqETtMJ@JYboIA}(7mBU#kYoE=)C}&eLNPG zDkc@29f(d1H4Qw8zN6uCwx1i^xWW}RPrqm{OrPX32A@`IbjezCW#7o2H}EqaV?FgM z-gh)p7(%Eb4+d$h4h-WwOVs4S&-luTEG&O^{yz52K`Cb_yPtLJ!9Ax{ctMUD1@_)e z@Ue)5gK854p_)m>#xL?ZYTwR^nuJsBs_aP;gCJVAwQH#?U>UxyI3YWctE))VM;O|o z8!MbXjFH?Y$0wS@+^sd_KEW02h$8T2=`KB?ojABSST|Tt2qoLt5LF^1z`yH|7#1Sf zw_dp48Oh`TiT#iMiRY3Boh!UWl0f=~5%izckg7$+r{DERHgL4FXQd241Z}D);MQ7O z_fOuP4EH6Rc6Z4Z+`xb#pigb2>3JT;h!$t^1`!p4W!VVvAQ9%H3l&=37$jDt*x?)1 zd#k@ApCk86@gMJs;2n$9px%6$~CK zPSFj6cK*$&KHYDBU;K;0EjwuJh@cDoIVLK-ngb&c1IibVzsz58Kj`)hj!YRzI8J4m4)w zBjjwfYl^vQg`-PCR@D+WYq{%g_xp?tF5CUEhD}R%VZJA0xqZ4zwJLa`7n-acl8ox> z@*f`9Iy2PL<*L;IRZaG>%n07Do?{J|-oA56)z@(X>%_(whB;k*+zzW+%wRCUiv6jM z4>|{*d+%OunoE5jSgEk~ppAHG+ozI)+(h{dDd2&lOyOFVrt_w{chXj)@ZUe9r|AS- z%!_Luo~MrXwNN282HUY*f9hu=-C4u6;LfdwQpL^dnd}}pmGdW zLu2h?vdlEdgKG`z2%Z?X-cj(@tMWxG$@>UdDu?9f&p6VDa~^(WxTMb6&dIm`x$D*V z4Du|}(@!Nc=%4&q$OA#I)+Z7zYiHKKoIF*zv2;E1 zm8Arnw7nv4quVm?rEkX15C1PxG@r%SuG%a7;n_t<=WWI%9@dtktTrIpefp$kM>)V~ zG#hn7$G1k7X@_mv97M=ya^rdJTeKqTKSj}_hnGCfk*+P9rKhI|3Q%>zGo{lntMFAX z?bImsLTOmHmNw1{r{X?E*d5*k@A2H$!7jWYkN>1*#*z%!y(3!DJPDmL1Y8>Czl6Q$+?PMJ@7X2lBf2MQtB{gD0HVy-vcHuutX3|Z9- zrs&%JX>7iY6*J~pq!|EHNzirr+@SFd%Ib~-{a+kw&f0u=SHk*dl%}ecRAh>St-1lE z;%K@9ebbJwMUz=m4<>$!i!E>~O+pGr>zK_b#?+I~z>BW8>f3aoC$B^8C%xC&re^B; z|GxUrQfzS%1Iy*}$g(gKd;9Lv$-l&p6XQTzM*x+oq%Kr@t!L zO2xvRT8TQZzlZ$jThM#dBh|0OLOLYWtY-uCw01cv)+4G?*r;^|>aB4y`r^A=qDKGP zB<`3!vq%os;?;o>#hdD(+07{cr(zkFlEC%X`oAEnZRS8)`k>rO9}%k;R=CdQ{jP~n zNjpvIy6#KM!hGocbV%RWn(oEe7CLK_BnRTnM3{{qpwJf-Zu4$Wb?X#ZV^Oj7AlhpW zmvV8XtiucPn6t3{-EW0cDQ_Nh=C&6&mvAB=7G4Kmv38M zGxdp9mZP)M^^O`DwYBZ%~6%7#<<`bmS*@ z8}Cny9u#%vR=S>!-^B@T{=M3f&KUDBw4>#np|k*DzLu&6UG5Iygw|^;`KV~ep;t%ST06Rt5vR_i8;nDW z9HhC$#%d^hd^>u~#Zlei@4x=GOe5JCRq1Yrt{&S7U!o+q44o-oooVow4{<0zK+`<3 zu)ilE!OCjnUq2#n$8NXm)nf9|{?0?M?t)pPj8$}x%}UuMbyaIRK+ExZT3qp8zL0IQ_=7uBM3s+pA zp3d9vey&svbTIOBSeM`jx3+SeR?9T3+6_oR#;+BUE-;Ij`FS!nE3!y)ZQ2<~9y?cp zqw8KD9e-aV$Fpegp_WRkcm7h4`vq4>*r3x0KI1)_&=h?Cf#BV-iFbOh+a6Usv^}{3 zI5$E3hK~9Cif9{oUIezNV>>q9GsuXNTHesYpFTD%L`?4?czJTyt@y)%B%{t}xe{Y7#}P z5F9Xqq{Q;akEgn5w`^k|_W@{9?x{kG^HQ60putb@Ce%s_kG&?cML*X>&!&-*>i{I% zNVs>ily}}KG<{ci&05t)d#LFpsam4NCn!d04yU8lN7gAsI-LGfNYF=IGwESa zb_noSvEGNnt%g>wv}}`L&+oW?yfv{fpLcxztE8Df^p+jgpa$i#vRA{b(xN9mpM42> zfe%^ON5_~6-DeG|d2YwaJv?0+44#}cxEf+v#k{FreXszZ)%iOj!OCxEdiu)5GqbPA z-Pm{<^LGQr9C3QNqrXOk?>e z+r4gnT?nh>X%zfx?iO5$y8S@3YD9d)8z+|r)h7!@)Zuqhr(mft{~K^nk#jEPk1mEX?hn<)y1`yYaScpK$aZ+_t{? zKDsYHXt};gUz9bNMbIB&Tmo@k4e1~r$vd!!2cIHT=}%!bpT~I(7i9ZTsCT#so|{tK zYNeqax?;t$iH{5^3PJWK7`iZ + +namespace lunasvg { + +static plutovg_matrix_t to_plutovg_matrix(const Transform& transform); +static plutovg_fill_rule_t to_plutovg_fill_rule(WindRule winding); +static plutovg_operator_t to_plutovg_operator(BlendMode mode); +static plutovg_line_cap_t to_plutovg_line_cap(LineCap cap); +static plutovg_line_join_t to_plutovg_line_join(LineJoin join); +static plutovg_spread_method_t to_plutovg_spread_methood(SpreadMethod spread); +static void to_plutovg_stops(plutovg_gradient_t* gradient, const GradientStops& stops); +static void to_plutovg_path(plutovg_t* pluto, const Path& path); + +std::shared_ptr Canvas::create(unsigned char* data, unsigned int width, unsigned int height, unsigned int stride) +{ + return std::shared_ptr(new Canvas(data, static_cast(width), static_cast(height), static_cast(stride))); +} + +std::shared_ptr Canvas::create(double x, double y, double width, double height) +{ + if(width <= 0.0 || height <= 0.0) + return std::shared_ptr(new Canvas(0, 0, 1, 1)); + + auto l = static_cast(floor(x)); + auto t = static_cast(floor(y)); + auto r = static_cast(ceil(x + width)); + auto b = static_cast(ceil(y + height)); + return std::shared_ptr(new Canvas(l, t, r - l, b - t)); +} + +std::shared_ptr Canvas::create(const Rect& box) +{ + return create(box.x, box.y, box.w, box.h); +} + +Canvas::Canvas(unsigned char* data, int width, int height, int stride) +{ + surface = plutovg_surface_create_for_data(data, width, height, stride); + pluto = plutovg_create(surface); + plutovg_matrix_init_identity(&translation); + plutovg_rect_init(&rect, 0, 0, width, height); +} + +Canvas::Canvas(int x, int y, int width, int height) +{ + surface = plutovg_surface_create(width, height); + pluto = plutovg_create(surface); + plutovg_matrix_init_translate(&translation, -x, -y); + plutovg_rect_init(&rect, x, y, width, height); +} + +Canvas::~Canvas() +{ + plutovg_surface_destroy(surface); + plutovg_destroy(pluto); +} + +void Canvas::setColor(const Color& color) +{ + plutovg_set_source_rgba(pluto, color.r, color.g, color.b, color.a); +} + +void Canvas::setLinearGradient(double x1, double y1, double x2, double y2, const GradientStops& stops, SpreadMethod spread, const Transform& transform) +{ + auto gradient = plutovg_gradient_create_linear(x1, y1, x2, y2); + auto matrix = to_plutovg_matrix(transform); + to_plutovg_stops(gradient, stops); + plutovg_gradient_set_spread(gradient, to_plutovg_spread_methood(spread)); + plutovg_gradient_set_matrix(gradient, &matrix); + plutovg_set_source_gradient(pluto, gradient); + plutovg_gradient_destroy(gradient); +} + +void Canvas::setRadialGradient(double cx, double cy, double r, double fx, double fy, const GradientStops& stops, SpreadMethod spread, const Transform& transform) +{ + auto gradient = plutovg_gradient_create_radial(cx, cy, r, fx, fy, 0); + auto matrix = to_plutovg_matrix(transform); + to_plutovg_stops(gradient, stops); + plutovg_gradient_set_spread(gradient, to_plutovg_spread_methood(spread)); + plutovg_gradient_set_matrix(gradient, &matrix); + plutovg_set_source_gradient(pluto, gradient); + plutovg_gradient_destroy(gradient); +} + +void Canvas::setTexture(const Canvas* source, TextureType type, const Transform& transform) +{ + auto texture = plutovg_texture_create(source->surface); + auto matrix = to_plutovg_matrix(transform); + if(type == TextureType::Plain) + plutovg_texture_set_type(texture, plutovg_texture_type_plain); + else + plutovg_texture_set_type(texture, plutovg_texture_type_tiled); + + plutovg_texture_set_matrix(texture, &matrix); + plutovg_set_source_texture(pluto, texture); + plutovg_texture_destroy(texture); +} + +void Canvas::fill(const Path& path, const Transform& transform, WindRule winding, BlendMode mode, double opacity) +{ + auto matrix = to_plutovg_matrix(transform); + plutovg_matrix_multiply(&matrix, &matrix, &translation); + to_plutovg_path(pluto, path); + plutovg_set_matrix(pluto, &matrix); + plutovg_set_fill_rule(pluto, to_plutovg_fill_rule(winding)); + plutovg_set_opacity(pluto, opacity); + plutovg_set_operator(pluto, to_plutovg_operator(mode)); + plutovg_fill(pluto); +} + +void Canvas::stroke(const Path& path, const Transform& transform, double width, LineCap cap, LineJoin join, double miterlimit, const DashData& dash, BlendMode mode, double opacity) +{ + auto matrix = to_plutovg_matrix(transform); + plutovg_matrix_multiply(&matrix, &matrix, &translation); + to_plutovg_path(pluto, path); + plutovg_set_matrix(pluto, &matrix); + plutovg_set_line_width(pluto, width); + plutovg_set_line_cap(pluto, to_plutovg_line_cap(cap)); + plutovg_set_line_join(pluto, to_plutovg_line_join(join)); + plutovg_set_miter_limit(pluto, miterlimit); + plutovg_set_dash(pluto, dash.offset, dash.array.data(), static_cast(dash.array.size())); + plutovg_set_operator(pluto, to_plutovg_operator(mode)); + plutovg_set_opacity(pluto, opacity); + plutovg_stroke(pluto); +} + +void Canvas::blend(const Canvas* source, BlendMode mode, double opacity) +{ + plutovg_set_source_surface(pluto, source->surface, source->rect.x, source->rect.y); + plutovg_set_operator(pluto, to_plutovg_operator(mode)); + plutovg_set_opacity(pluto, opacity); + plutovg_set_matrix(pluto, &translation); + plutovg_paint(pluto); +} + +void Canvas::mask(const Rect& clip, const Transform& transform) +{ + auto matrix = to_plutovg_matrix(transform); + auto path = plutovg_path_create(); + plutovg_path_add_rect(path, clip.x, clip.y, clip.w, clip.h); + plutovg_path_transform(path, &matrix); + plutovg_rect(pluto, rect.x, rect.y, rect.w, rect.h); + plutovg_add_path(pluto, path); + plutovg_path_destroy(path); + + plutovg_set_source_rgba(pluto, 0, 0, 0, 0); + plutovg_set_fill_rule(pluto, plutovg_fill_rule_even_odd); + plutovg_set_operator(pluto, plutovg_operator_src); + plutovg_set_opacity(pluto, 0.0); + plutovg_set_matrix(pluto, &translation); + plutovg_fill(pluto); +} + +void Canvas::luminance() +{ + auto width = plutovg_surface_get_width(surface); + auto height = plutovg_surface_get_height(surface); + auto stride = plutovg_surface_get_stride(surface); + auto data = plutovg_surface_get_data(surface); + for(int y = 0;y < height;y++) + { + auto pixels = reinterpret_cast(data + stride * y); + for(int x = 0;x < width;x++) + { + auto pixel = pixels[x]; + auto r = (pixel >> 16) & 0xFF; + auto g = (pixel >> 8) & 0xFF; + auto b = (pixel >> 0) & 0xFF; + auto l = (2*r + 3*g + b) / 6; + + pixels[x] = l << 24; + } + } +} + +unsigned int Canvas::width() const +{ + return plutovg_surface_get_width(surface); +} + +unsigned int Canvas::height() const +{ + return plutovg_surface_get_height(surface); +} + +unsigned int Canvas::stride() const +{ + return plutovg_surface_get_stride(surface); +} + +unsigned char* Canvas::data() const +{ + return plutovg_surface_get_data(surface); +} + +Rect Canvas::box() const +{ + return Rect(rect.x, rect.y, rect.w, rect.h); +} + +plutovg_matrix_t to_plutovg_matrix(const Transform& transform) +{ + plutovg_matrix_t matrix; + plutovg_matrix_init(&matrix, transform.m00, transform.m10, transform.m01, transform.m11, transform.m02, transform.m12); + return matrix; +} + +plutovg_fill_rule_t to_plutovg_fill_rule(WindRule winding) +{ + return winding == WindRule::EvenOdd ? plutovg_fill_rule_even_odd : plutovg_fill_rule_non_zero; +} + +plutovg_operator_t to_plutovg_operator(BlendMode mode) +{ + return mode == BlendMode::Src ? plutovg_operator_src : mode == BlendMode::Src_Over ? plutovg_operator_src_over : mode == BlendMode::Dst_In ? plutovg_operator_dst_in : plutovg_operator_dst_out; +} + +plutovg_line_cap_t to_plutovg_line_cap(LineCap cap) +{ + return cap == LineCap::Butt ? plutovg_line_cap_butt : cap == LineCap::Round ? plutovg_line_cap_round : plutovg_line_cap_square; +} + +plutovg_line_join_t to_plutovg_line_join(LineJoin join) +{ + return join == LineJoin::Miter ? plutovg_line_join_miter : join == LineJoin::Round ? plutovg_line_join_round : plutovg_line_join_bevel; +} + +static plutovg_spread_method_t to_plutovg_spread_methood(SpreadMethod spread) +{ + return spread == SpreadMethod::Pad ? plutovg_spread_method_pad : spread == SpreadMethod::Reflect ? plutovg_spread_method_reflect : plutovg_spread_method_repeat; +} + +static void to_plutovg_stops(plutovg_gradient_t* gradient, const GradientStops& stops) +{ + for(const auto& stop : stops) + { + auto offset = std::get<0>(stop); + auto& color = std::get<1>(stop); + plutovg_gradient_add_stop_rgba(gradient, offset, color.r, color.g, color.b, color.a); + } +} + +void to_plutovg_path(plutovg_t* pluto, const Path& path) +{ + PathIterator it(path); + std::array p; + while(!it.isDone()) + { + switch(it.currentSegment(p)) { + case PathCommand::MoveTo: + plutovg_move_to(pluto, p[0].x, p[0].y); + break; + case PathCommand::LineTo: + plutovg_line_to(pluto, p[0].x, p[0].y); + break; + case PathCommand::CubicTo: + plutovg_cubic_to(pluto, p[0].x, p[0].y, p[1].x, p[1].y, p[2].x, p[2].y); + break; + case PathCommand::Close: + plutovg_close_path(pluto); + break; + } + + it.next(); + } +} + +} // namespace lunasvg diff --git a/source/canvas.h b/source/canvas.h new file mode 100644 index 000000000..a7d743300 --- /dev/null +++ b/source/canvas.h @@ -0,0 +1,76 @@ +#ifndef CANVAS_H +#define CANVAS_H + +#include "property.h" +#include "plutovg.h" + +#include + +namespace lunasvg { + +using GradientStop = std::pair; +using GradientStops = std::vector; + +using DashArray = std::vector; + +struct DashData +{ + DashArray array; + double offset{0.0}; +}; + +enum class TextureType +{ + Plain, + Tiled +}; + +enum class BlendMode +{ + Src, + Src_Over, + Dst_In, + Dst_Out +}; + +class CanvasImpl; + +class Canvas +{ +public: + static std::shared_ptr create(unsigned char* data, unsigned int width, unsigned int height, unsigned int stride); + static std::shared_ptr create(double x, double y, double width, double height); + static std::shared_ptr create(const Rect& box); + + void setColor(const Color& color); + void setLinearGradient(double x1, double y1, double x2, double y2, const GradientStops& stops, SpreadMethod spread, const Transform& transform); + void setRadialGradient(double cx, double cy, double r, double fx, double fy, const GradientStops& stops, SpreadMethod spread, const Transform& transform); + void setTexture(const Canvas* source, TextureType type, const Transform& transform); + + void fill(const Path& path, const Transform& transform, WindRule winding, BlendMode mode, double opacity); + void stroke(const Path& path, const Transform& transform, double width, LineCap cap, LineJoin join, double miterlimit, const DashData& dash, BlendMode mode, double opacity); + void blend(const Canvas* source, BlendMode mode, double opacity); + void mask(const Rect& clip, const Transform& transform); + + void luminance(); + + unsigned int width() const; + unsigned int height() const; + unsigned int stride() const; + unsigned char* data() const; + Rect box() const; + + ~Canvas(); +private: + Canvas(unsigned char* data, int width, int height, int stride); + Canvas(int x, int y, int width, int height); + + plutovg_surface_t* surface; + plutovg_t* pluto; + plutovg_matrix_t translation; + plutovg_rect_t rect; +}; + +} // namespace lunasvg + +#endif // CANVAS_H diff --git a/source/clippathelement.cpp b/source/clippathelement.cpp new file mode 100644 index 000000000..9472b5ce5 --- /dev/null +++ b/source/clippathelement.cpp @@ -0,0 +1,37 @@ +#include "clippathelement.h" +#include "parser.h" +#include "layoutcontext.h" + +namespace lunasvg { + +ClipPathElement::ClipPathElement() + : GraphicsElement(ElementId::ClipPath) +{ +} + +Units ClipPathElement::clipPathUnits() const +{ + auto& value = get(PropertyId::ClipPathUnits); + return Parser::parseUnits(value, Units::UserSpaceOnUse); +} + +std::unique_ptr ClipPathElement::getClipper(LayoutContext* context) const +{ + if(context->hasReference(this)) + return nullptr; + + LayoutBreaker layoutBreaker(context, this); + auto clipper = std::make_unique(); + clipper->units = clipPathUnits(); + clipper->transform = transform(); + clipper->clipper = context->getClipper(clip_path()); + layoutChildren(context, clipper.get()); + return clipper; +} + +std::unique_ptr ClipPathElement::clone() const +{ + return cloneElement(); +} + +} // namespace lunasvg diff --git a/source/clippathelement.h b/source/clippathelement.h new file mode 100644 index 000000000..5a7c393ee --- /dev/null +++ b/source/clippathelement.h @@ -0,0 +1,23 @@ +#ifndef CLIPPATHELEMENT_H +#define CLIPPATHELEMENT_H + +#include "graphicselement.h" + +namespace lunasvg { + +class LayoutClipPath; + +class ClipPathElement : public GraphicsElement +{ +public: + ClipPathElement(); + + Units clipPathUnits() const; + std::unique_ptr getClipper(LayoutContext* context) const; + + std::unique_ptr clone() const; +}; + +} // namespace lunasvg + +#endif // CLIPPATHELEMENT_H diff --git a/source/defselement.cpp b/source/defselement.cpp new file mode 100644 index 000000000..b39ddeac9 --- /dev/null +++ b/source/defselement.cpp @@ -0,0 +1,15 @@ +#include "defselement.h" + +namespace lunasvg { + +DefsElement::DefsElement() + : GraphicsElement(ElementId::Defs) +{ +} + +std::unique_ptr DefsElement::clone() const +{ + return cloneElement(); +} + +} // namespace lunasvg diff --git a/source/defselement.h b/source/defselement.h new file mode 100644 index 000000000..4f986539e --- /dev/null +++ b/source/defselement.h @@ -0,0 +1,18 @@ +#ifndef DEFSELEMENT_H +#define DEFSELEMENT_H + +#include "graphicselement.h" + +namespace lunasvg { + +class DefsElement : public GraphicsElement +{ +public: + DefsElement(); + + std::unique_ptr clone() const; +}; + +} // namespace lunasvg + +#endif // DEFSELEMENT_H diff --git a/source/element.cpp b/source/element.cpp new file mode 100644 index 000000000..a3d3a319a --- /dev/null +++ b/source/element.cpp @@ -0,0 +1,187 @@ +#include "element.h" +#include "parser.h" +#include "svgelement.h" + +namespace lunasvg { + +void PropertyList::set(PropertyId id, const std::string& value, int specificity) +{ + auto property = get(id); + if(property == nullptr) + { + Property property{id, value, specificity}; + m_properties.push_back(std::move(property)); + return; + } + + if(property->specificity > specificity) + return; + + property->specificity = specificity; + property->value = value; +} + +Property* PropertyList::get(PropertyId id) const +{ + auto data = m_properties.data(); + auto end = data + m_properties.size(); + while(data < end) + { + if(data->id == id) + return const_cast(data); + ++data; + } + + return nullptr; +} + +void PropertyList::add(const Property& property) +{ + set(property.id, property.value, property.specificity); +} + +void PropertyList::add(const PropertyList& properties) +{ + auto it = properties.m_properties.begin(); + auto end = properties.m_properties.end(); + for(;it != end;++it) + add(*it); +} + +void Node::layout(LayoutContext*, LayoutContainer*) const +{ +} + +std::unique_ptr TextNode::clone() const +{ + auto node = std::make_unique(); + node->text = text; + return std::move(node); +} + +Element::Element(ElementId id) + : id(id) +{ +} + +void Element::set(PropertyId id, const std::string& value, int specificity) +{ + properties.set(id, value, specificity); +} + +static const std::string EmptyString; + +const std::string& Element::get(PropertyId id) const +{ + auto property = properties.get(id); + if(property == nullptr) + return EmptyString; + + return property->value; +} + +static const std::string InheritString{"inherit"}; + +const std::string& Element::find(PropertyId id) const +{ + auto element = this; + do { + auto& value = element->get(id); + if(!value.empty() && value != InheritString) + return value; + element = element->parent; + } while(element); + + return EmptyString; +} + +bool Element::has(PropertyId id) const +{ + return properties.get(id); +} + +Element* Element::previousSibling() const +{ + if(parent == nullptr) + return nullptr; + + Element* element = nullptr; + auto it = parent->children.begin(); + auto end = parent->children.end(); + for(;it != end;++it) + { + auto node = it->get(); + if(node->isText()) + continue; + + if(node == this) + return element; + element = static_cast(node); + } + + return nullptr; +} + +Element* Element::nextSibling() const +{ + if(parent == nullptr) + return nullptr; + + Element* element = nullptr; + auto it = parent->children.rbegin(); + auto end = parent->children.rend(); + for(;it != end;++it) + { + auto node = it->get(); + if(node->isText()) + continue; + + if(node == this) + return element; + element = static_cast(node); + } + + return nullptr; +} + +Node* Element::addChild(std::unique_ptr child) +{ + child->parent = this; + children.push_back(std::move(child)); + return &*children.back(); +} + +void Element::layoutChildren(LayoutContext* context, LayoutContainer* current) const +{ + for(auto& child : children) + child->layout(context, current); +} + +Rect Element::currentViewport() const +{ + if(parent == nullptr) + { + auto element = static_cast(this); + if(element->has(PropertyId::ViewBox)) + return element->viewBox(); + return Rect{0, 0, 300, 150}; + } + + if(parent->id == ElementId::Svg) + { + auto element = static_cast(parent); + if(element->has(PropertyId::ViewBox)) + return element->viewBox(); + + LengthContext lengthContext(element); + auto _x = lengthContext.valueForLength(element->x(), LengthMode::Width); + auto _y = lengthContext.valueForLength(element->y(), LengthMode::Height); + auto _w = lengthContext.valueForLength(element->width(), LengthMode::Width); + auto _h = lengthContext.valueForLength(element->height(), LengthMode::Height); + return Rect{_x, _y, _w, _h}; + } + + return parent->currentViewport(); +} + +} // namespace lunasvg diff --git a/source/element.h b/source/element.h new file mode 100644 index 000000000..f94f92774 --- /dev/null +++ b/source/element.h @@ -0,0 +1,218 @@ +#ifndef ELEMENT_H +#define ELEMENT_H + +#include +#include + +#include "property.h" + +namespace lunasvg { + +enum class ElementId +{ + Unknown = 0, + Star, + Circle, + ClipPath, + Defs, + Ellipse, + G, + Line, + LinearGradient, + Marker, + Mask, + Path, + Pattern, + Polygon, + Polyline, + RadialGradient, + Rect, + SolidColor, + Stop, + Style, + Svg, + Symbol, + Use +}; + +enum class PropertyId +{ + Unknown = 0, + Class, + Clip_Path, + Clip_Rule, + ClipPathUnits, + Color, + Cx, + Cy, + D, + Display, + Fill, + Fill_Opacity, + Fill_Rule, + Fx, + Fy, + GradientTransform, + GradientUnits, + Height, + Href, + Id, + Marker_End, + Marker_Mid, + Marker_Start, + MarkerHeight, + MarkerUnits, + MarkerWidth, + Mask, + MaskContentUnits, + MaskUnits, + Offset, + Opacity, + Orient, + Overflow, + PatternContentUnits, + PatternTransform, + PatternUnits, + Points, + PreserveAspectRatio, + R, + RefX, + RefY, + Rx, + Ry, + Solid_Color, + Solid_Opacity, + SpreadMethod, + Stop_Color, + Stop_Opacity, + Stroke, + Stroke_Dasharray, + Stroke_Dashoffset, + Stroke_Linecap, + Stroke_Linejoin, + Stroke_Miterlimit, + Stroke_Opacity, + Stroke_Width, + Style, + Transform, + ViewBox, + Visibility, + Width, + X, + X1, + X2, + Y, + Y1, + Y2 +}; + +struct Property +{ + PropertyId id; + std::string value; + int specificity; +}; + +class PropertyList +{ +public: + PropertyList() = default; + + void set(PropertyId id, const std::string& value, int specificity); + Property* get(PropertyId id) const; + void add(const Property& property); + void add(const PropertyList& properties); + +private: + std::vector m_properties; +}; + +class LayoutContext; +class LayoutContainer; +class Element; + +class Node +{ +public: + Node() = default; + virtual ~Node() = default; + + virtual bool isText() const { return false; } + virtual bool isPaint() const { return false; } + virtual bool isGeometry() const { return false; } + virtual void layout(LayoutContext*, LayoutContainer*) const; + virtual std::unique_ptr clone() const = 0; + +public: + Element* parent = nullptr; +}; + +class TextNode : public Node +{ +public: + TextNode() = default; + + bool isText() const { return true; } + std::unique_ptr clone() const; + +public: + std::string text; +}; + +using NodeList = std::list>; + +class Element : public Node +{ +public: + Element(ElementId id); + + void set(PropertyId id, const std::string& value, int specificity); + const std::string& get(PropertyId id) const; + const std::string& find(PropertyId id) const; + bool has(PropertyId id) const; + + Element* previousSibling() const; + Element* nextSibling() const; + Node* addChild(std::unique_ptr child); + void layoutChildren(LayoutContext* context, LayoutContainer* current) const; + Rect currentViewport() const; + + template + void transverse(T callback) + { + if(callback(this)) + return; + + for(auto& child : children) + { + if(child->isText()) + { + if(callback(child.get())) + return; + continue; + } + + auto element = static_cast(child.get()); + element->transverse(callback); + } + } + + template + std::unique_ptr cloneElement() const + { + auto element = std::make_unique(); + element->properties = properties; + for(auto& child : children) + element->addChild(child->clone()); + return element; + } + +public: + ElementId id; + NodeList children; + PropertyList properties; +}; + +} // namespace lunasvg + +#endif // ELEMENT_H diff --git a/source/gelement.cpp b/source/gelement.cpp new file mode 100644 index 000000000..b0f5c84a7 --- /dev/null +++ b/source/gelement.cpp @@ -0,0 +1,30 @@ +#include "gelement.h" +#include "layoutcontext.h" + +namespace lunasvg { + +GElement::GElement() + : GraphicsElement(ElementId::G) +{ +} + +void GElement::layout(LayoutContext* context, LayoutContainer* current) const +{ + if(isDisplayNone()) + return; + + auto group = std::make_unique(); + group->transform = transform(); + group->opacity = opacity(); + group->masker = context->getMasker(mask()); + group->clipper = context->getClipper(clip_path()); + layoutChildren(context, group.get()); + current->addChildIfNotEmpty(std::move(group)); +} + +std::unique_ptr GElement::clone() const +{ + return cloneElement(); +} + +} // namespace lunasvg diff --git a/source/gelement.h b/source/gelement.h new file mode 100644 index 000000000..be6a20b54 --- /dev/null +++ b/source/gelement.h @@ -0,0 +1,19 @@ +#ifndef GELEMENT_H +#define GELEMENT_H + +#include "graphicselement.h" + +namespace lunasvg { + +class GElement : public GraphicsElement +{ +public: + GElement(); + + void layout(LayoutContext* context, LayoutContainer* current) const; + std::unique_ptr clone() const; +}; + +} // namespace lunasvg + +#endif // GELEMENT_H diff --git a/source/geometryelement.cpp b/source/geometryelement.cpp new file mode 100644 index 000000000..c93b6d52b --- /dev/null +++ b/source/geometryelement.cpp @@ -0,0 +1,335 @@ +#include "geometryelement.h" +#include "parser.h" +#include "layoutcontext.h" + +#include + +namespace lunasvg { + +GeometryElement::GeometryElement(ElementId id) + : GraphicsElement(id) +{ +} + +void GeometryElement::layout(LayoutContext* context, LayoutContainer* current) const +{ + if(isDisplayNone()) + return; + + auto path = this->path(); + if(path.empty()) + return; + + auto shape = std::make_unique(); + shape->path = std::move(path); + shape->transform = transform(); + shape->fillData = context->fillData(this); + shape->strokeData = context->strokeData(this); + shape->markerData = context->markerData(this, shape->path); + shape->visibility = visibility(); + shape->clipRule = clip_rule(); + shape->opacity = opacity(); + shape->masker = context->getMasker(mask()); + shape->clipper = context->getClipper(clip_path()); + current->addChild(std::move(shape)); +} + +PathElement::PathElement() + : GeometryElement(ElementId::Path) +{ +} + +Path PathElement::d() const +{ + auto& value = get(PropertyId::D); + return Parser::parsePath(value); +} + +Path PathElement::path() const +{ + return d(); +} + +std::unique_ptr PathElement::clone() const +{ + return cloneElement(); +} + +PolyElement::PolyElement(ElementId id) + : GeometryElement(id) +{ +} + +PointList PolyElement::points() const +{ + auto& value = get(PropertyId::Points); + return Parser::parsePointList(value); +} + +PolygonElement::PolygonElement() + : PolyElement(ElementId::Polygon) +{ +} + +Path PolygonElement::path() const +{ + auto points = this->points(); + if(points.empty()) + return Path{}; + + Path path; + path.moveTo(points[0].x, points[0].y); + for(std::size_t i = 1;i < points.size();i++) + path.lineTo(points[i].x, points[i].y); + + path.close(); + return path; +} + +std::unique_ptr PolygonElement::clone() const +{ + return cloneElement(); +} + +PolylineElement::PolylineElement() + : PolyElement(ElementId::Polyline) +{ +} + +Path PolylineElement::path() const +{ + auto points = this->points(); + if(points.empty()) + return Path{}; + + Path path; + path.moveTo(points[0].x, points[0].y); + for(std::size_t i = 1;i < points.size();i++) + path.lineTo(points[i].x, points[i].y); + + return path; +} + +std::unique_ptr PolylineElement::clone() const +{ + return cloneElement(); +} + +CircleElement::CircleElement() + : GeometryElement(ElementId::Circle) +{ +} + +Length CircleElement::cx() const +{ + auto& value = get(PropertyId::Cx); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length CircleElement::cy() const +{ + auto& value = get(PropertyId::Cy); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length CircleElement::r() const +{ + auto& value = get(PropertyId::R); + return Parser::parseLength(value, ForbidNegativeLengths, Length::Zero); +} + +Path CircleElement::path() const +{ + auto r = this->r(); + if(r.isZero()) + return Path{}; + + LengthContext lengthContext(this); + auto _cx = lengthContext.valueForLength(cx(), LengthMode::Width); + auto _cy = lengthContext.valueForLength(cy(), LengthMode::Height); + auto _r = lengthContext.valueForLength(r, LengthMode::Both); + + Path path; + path.ellipse(_cx, _cy, _r, _r); + return path; +} + +std::unique_ptr CircleElement::clone() const +{ + return cloneElement(); +} + +EllipseElement::EllipseElement() + : GeometryElement(ElementId::Ellipse) +{ +} + +Length EllipseElement::cx() const +{ + auto& value = get(PropertyId::Cx); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length EllipseElement::cy() const +{ + auto& value = get(PropertyId::Cy); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length EllipseElement::rx() const +{ + auto& value = get(PropertyId::Rx); + return Parser::parseLength(value, ForbidNegativeLengths, Length::Zero); +} + +Length EllipseElement::ry() const +{ + auto& value = get(PropertyId::Ry); + return Parser::parseLength(value, ForbidNegativeLengths, Length::Zero); +} + +Path EllipseElement::path() const +{ + auto rx = this->rx(); + auto ry = this->ry(); + if(rx.isZero() || ry.isZero()) + return Path{}; + + LengthContext lengthContext(this); + auto _cx = lengthContext.valueForLength(cx(), LengthMode::Width); + auto _cy = lengthContext.valueForLength(cy(), LengthMode::Height); + auto _rx = lengthContext.valueForLength(rx, LengthMode::Width); + auto _ry = lengthContext.valueForLength(ry, LengthMode::Height); + + Path path; + path.ellipse(_cx, _cy, _rx, _ry); + return path; +} + +std::unique_ptr EllipseElement::clone() const +{ + return cloneElement(); +} + +LineElement::LineElement() + : GeometryElement(ElementId::Line) +{ +} + +Length LineElement::x1() const +{ + auto& value = get(PropertyId::X1); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length LineElement::y1() const +{ + auto& value = get(PropertyId::Y1); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length LineElement::x2() const +{ + auto& value = get(PropertyId::X2); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length LineElement::y2() const +{ + auto& value = get(PropertyId::Y2); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Path LineElement::path() const +{ + LengthContext lengthContext(this); + auto _x1 = lengthContext.valueForLength(x1(), LengthMode::Width); + auto _y1 = lengthContext.valueForLength(y1(), LengthMode::Height); + auto _x2 = lengthContext.valueForLength(x2(), LengthMode::Width); + auto _y2 = lengthContext.valueForLength(y2(), LengthMode::Height); + + Path path; + path.moveTo(_x1, _y1); + path.lineTo(_x2, _y2); + return path; +} + +std::unique_ptr LineElement::clone() const +{ + return cloneElement(); +} + +RectElement::RectElement() + : GeometryElement(ElementId::Rect) +{ +} + +Length RectElement::x() const +{ + auto& value = get(PropertyId::X); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length RectElement::y() const +{ + auto& value = get(PropertyId::Y); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length RectElement::rx() const +{ + auto& value = get(PropertyId::Rx); + return Parser::parseLength(value, ForbidNegativeLengths, Length::Unknown); +} + +Length RectElement::ry() const +{ + auto& value = get(PropertyId::Ry); + return Parser::parseLength(value, ForbidNegativeLengths, Length::Unknown); +} + +Length RectElement::width() const +{ + auto& value = get(PropertyId::Width); + return Parser::parseLength(value, ForbidNegativeLengths, Length::Zero); +} + +Length RectElement::height() const +{ + auto& value = get(PropertyId::Height); + return Parser::parseLength(value, ForbidNegativeLengths, Length::Zero); +} + +Path RectElement::path() const +{ + auto w = this->width(); + auto h = this->height(); + if(w.isZero() || h.isZero()) + return Path{}; + + LengthContext lengthContext(this); + auto _x = lengthContext.valueForLength(x(), LengthMode::Width); + auto _y = lengthContext.valueForLength(y(), LengthMode::Height); + auto _w = lengthContext.valueForLength(w, LengthMode::Width); + auto _h = lengthContext.valueForLength(h, LengthMode::Height); + + auto rx = this->rx(); + auto ry = this->ry(); + + auto _rx = lengthContext.valueForLength(rx, LengthMode::Width); + auto _ry = lengthContext.valueForLength(ry, LengthMode::Height); + + if(!rx.isValid()) _rx = _ry; + if(!ry.isValid()) _ry = _rx; + + Path path; + path.rect(_x, _y, _w, _h, _rx, _ry); + return path; +} + +std::unique_ptr RectElement::clone() const +{ + return cloneElement(); +} + +} // namespace lunasvg diff --git a/source/geometryelement.h b/source/geometryelement.h new file mode 100644 index 000000000..8d302ee39 --- /dev/null +++ b/source/geometryelement.h @@ -0,0 +1,122 @@ +#ifndef GEOMETRYELEMENT_H +#define GEOMETRYELEMENT_H + +#include "graphicselement.h" + +namespace lunasvg { + +class LayoutShape; + +class GeometryElement : public GraphicsElement +{ +public: + GeometryElement(ElementId id); + + bool isGeometry() const { return true; } + virtual void layout(LayoutContext* context, LayoutContainer* current) const; + virtual Path path() const = 0; +}; + +class PathElement : public GeometryElement +{ +public: + PathElement(); + + Path d() const; + Path path() const; + + std::unique_ptr clone() const; +}; + +class PolyElement : public GeometryElement +{ +public: + PolyElement(ElementId id); + + PointList points() const; +}; + +class PolygonElement : public PolyElement +{ +public: + PolygonElement(); + + Path path() const; + + std::unique_ptr clone() const; +}; + +class PolylineElement : public PolyElement +{ +public: + PolylineElement(); + + Path path() const; + + std::unique_ptr clone() const; +}; + +class CircleElement : public GeometryElement +{ +public: + CircleElement(); + + Length cx() const; + Length cy() const; + Length r() const; + + Path path() const; + + std::unique_ptr clone() const; +}; + +class EllipseElement : public GeometryElement +{ +public: + EllipseElement(); + + Length cx() const; + Length cy() const; + Length rx() const; + Length ry() const; + + Path path() const; + + std::unique_ptr clone() const; +}; + +class LineElement : public GeometryElement +{ +public: + LineElement(); + + Length x1() const; + Length y1() const; + Length x2() const; + Length y2() const; + + Path path() const; + + std::unique_ptr clone() const; +}; + +class RectElement : public GeometryElement +{ +public: + RectElement(); + + Length x() const; + Length y() const; + Length rx() const; + Length ry() const; + Length width() const; + Length height() const; + + Path path() const; + + std::unique_ptr clone() const; +}; + +} // namespace lunasvg + +#endif // GEOMETRYELEMENT_H diff --git a/source/graphicselement.cpp b/source/graphicselement.cpp new file mode 100644 index 000000000..60c28f955 --- /dev/null +++ b/source/graphicselement.cpp @@ -0,0 +1,17 @@ +#include "graphicselement.h" +#include "parser.h" + +namespace lunasvg { + +GraphicsElement::GraphicsElement(ElementId id) + : StyledElement(id) +{ +} + +Transform GraphicsElement::transform() const +{ + auto& value = get(PropertyId::Transform); + return Parser::parseTransform(value); +} + +} // namespace lunasvg diff --git a/source/graphicselement.h b/source/graphicselement.h new file mode 100644 index 000000000..4da8ce8ab --- /dev/null +++ b/source/graphicselement.h @@ -0,0 +1,18 @@ +#ifndef GRAPHICSELEMENT_H +#define GRAPHICSELEMENT_H + +#include "styledelement.h" + +namespace lunasvg { + +class GraphicsElement : public StyledElement +{ +public: + GraphicsElement(ElementId id); + + Transform transform() const; +}; + +} // namespace lunasvg + +#endif // GRAPHICSELEMENT_H diff --git a/source/layoutcontext.cpp b/source/layoutcontext.cpp new file mode 100644 index 000000000..5b92c8779 --- /dev/null +++ b/source/layoutcontext.cpp @@ -0,0 +1,766 @@ +#include "layoutcontext.h" +#include "parser.h" + +#include "maskelement.h" +#include "clippathelement.h" +#include "paintelement.h" +#include "markerelement.h" +#include "geometryelement.h" + +#include + +namespace lunasvg { + +LayoutObject::LayoutObject(LayoutId id) + : id(id) +{ +} + +LayoutObject::~LayoutObject() +{ +} + +void LayoutObject::render(RenderState&) const +{ +} + +void LayoutObject::apply(RenderState&) const +{ +} + +Rect LayoutObject::map(const Rect&) const +{ + return Rect::Invalid; +} + +LayoutContainer::LayoutContainer(LayoutId id) + : LayoutObject(id) +{ +} + +const Rect& LayoutContainer::fillBoundingBox() const +{ + if(m_fillBoundingBox.valid()) + return m_fillBoundingBox; + + for(const auto& child : children) + { + if(child->isHidden()) + continue; + m_fillBoundingBox.unite(child->map(child->fillBoundingBox())); + } + + return m_fillBoundingBox; +} + +const Rect& LayoutContainer::strokeBoundingBox() const +{ + if(m_strokeBoundingBox.valid()) + return m_strokeBoundingBox; + + for(const auto& child : children) + { + if(child->isHidden()) + continue; + m_strokeBoundingBox.unite(child->map(child->strokeBoundingBox())); + } + + return m_strokeBoundingBox; +} + +LayoutObject* LayoutContainer::addChild(std::unique_ptr child) +{ + children.push_back(std::move(child)); + return &*children.back(); +} + +LayoutObject* LayoutContainer::addChildIfNotEmpty(std::unique_ptr child) +{ + if(child->children.empty()) + return nullptr; + + return addChild(std::move(child)); +} + +void LayoutContainer::renderChildren(RenderState& state) const +{ + for(const auto& child : children) + child->render(state); +} + +LayoutClipPath::LayoutClipPath() + : LayoutContainer(LayoutId::ClipPath) +{ +} + +void LayoutClipPath::apply(RenderState& state) const +{ + RenderState newState(this, RenderMode::Clipping); + newState.canvas = Canvas::create(state.canvas->box()); + newState.transform = transform * state.transform; + if(units == Units::ObjectBoundingBox) + { + const auto& box = state.objectBoundingBox(); + newState.transform.translate(box.x, box.y); + newState.transform.scale(box.w, box.h); + } + + renderChildren(newState); + if(clipper) clipper->apply(newState); + state.canvas->blend(newState.canvas.get(), BlendMode::Dst_In, 1.0); +} + +LayoutMask::LayoutMask() + : LayoutContainer(LayoutId::Mask) +{ +} + +void LayoutMask::apply(RenderState& state) const +{ + Rect rect{x, y, width, height}; + if(units == Units::ObjectBoundingBox) + { + const auto& box = state.objectBoundingBox(); + rect.x = rect.x * box.w + box.x; + rect.y = rect.y * box.h + box.y; + rect.w = rect.w * box.w; + rect.h = rect.h * box.h; + } + + RenderState newState(this, state.mode()); + newState.canvas = Canvas::create(state.canvas->box()); + newState.transform = state.transform; + if(contentUnits == Units::ObjectBoundingBox) + { + const auto& box = state.objectBoundingBox(); + newState.transform.translate(box.x, box.y); + newState.transform.scale(box.w, box.h); + } + + renderChildren(newState); + if(clipper) clipper->apply(newState); + if(masker) masker->apply(newState); + newState.canvas->mask(rect, state.transform); + newState.canvas->luminance(); + state.canvas->blend(newState.canvas.get(), BlendMode::Dst_In, opacity); +} + +LayoutSymbol::LayoutSymbol() + : LayoutContainer(LayoutId::Symbol) +{ +} + +void LayoutSymbol::render(RenderState& state) const +{ + BlendInfo info{clipper, masker, opacity, clip}; + RenderState newState(this, state.mode()); + newState.transform = transform * state.transform; + newState.beginGroup(state, info); + renderChildren(newState); + newState.endGroup(state, info); +} + +Rect LayoutSymbol::map(const Rect& rect) const +{ + return transform.map(rect); +} + +LayoutGroup::LayoutGroup() + : LayoutContainer(LayoutId::Group) +{ +} + +void LayoutGroup::render(RenderState& state) const +{ + BlendInfo info{clipper, masker, opacity, Rect::Invalid}; + RenderState newState(this, state.mode()); + newState.transform = transform * state.transform; + newState.beginGroup(state, info); + renderChildren(newState); + newState.endGroup(state, info); +} + +Rect LayoutGroup::map(const Rect& rect) const +{ + return transform.map(rect); +} + +LayoutMarker::LayoutMarker() + : LayoutContainer(LayoutId::Marker) +{ +} + +Transform LayoutMarker::markerTransform(const Point& origin, double angle, double strokeWidth) const +{ + auto transform = Transform::translated(origin.x, origin.y); + if(orient.type() == MarkerOrient::Auto) + transform.rotate(angle); + else + transform.rotate(orient.value()); + + if(units == MarkerUnits::StrokeWidth) + transform.scale(strokeWidth, strokeWidth); + + transform.translate(-refX, -refY); + return transform; +} + +Rect LayoutMarker::markerBoundingBox(const Point& origin, double angle, double strokeWidth) const +{ + auto box = transform.map(strokeBoundingBox()); + auto transform = markerTransform(origin, angle, strokeWidth); + return transform.map(box); +} + +void LayoutMarker::renderMarker(RenderState& state, const Point& origin, double angle, double strokeWidth) const +{ + BlendInfo info{clipper, masker, opacity, clip}; + RenderState newState(this, state.mode()); + newState.transform = transform * markerTransform(origin, angle, strokeWidth) * state.transform; + newState.beginGroup(state, info); + renderChildren(newState); + newState.endGroup(state, info); +} + +LayoutPattern::LayoutPattern() + : LayoutContainer(LayoutId::Pattern) +{ +} + +void LayoutPattern::apply(RenderState& state) const +{ + Rect rect{x, y, width, height}; + if(units == Units::ObjectBoundingBox) + { + const auto& box = state.objectBoundingBox(); + rect.x = rect.x * box.w + box.x; + rect.y = rect.y * box.h + box.y; + rect.w = rect.w * box.w; + rect.h = rect.h * box.h; + } + + auto ctm = state.transform * transform; + auto scalex = std::sqrt(ctm.m00 * ctm.m00 + ctm.m01 * ctm.m01); + auto scaley = std::sqrt(ctm.m10 * ctm.m10 + ctm.m11 * ctm.m11); + + auto width = rect.w * scalex; + auto height = rect.h * scaley; + + RenderState newState(this, RenderMode::Display); + newState.canvas = Canvas::create(0, 0, width, height); + newState.transform = Transform::scaled(scalex, scaley); + + if(viewBox.valid()) + { + auto viewTransform = preserveAspectRatio.getMatrix(rect.w, rect.h, viewBox); + newState.transform.premultiply(viewTransform); + } + else if(contentUnits == Units::ObjectBoundingBox) + { + const auto& box = state.objectBoundingBox(); + newState.transform.scale(box.w, box.h); + } + + auto transform = this->transform; + transform.translate(rect.x, rect.y); + transform.scale(1.0/scalex, 1.0/scaley); + + renderChildren(newState); + state.canvas->setTexture(newState.canvas.get(), TextureType::Tiled, transform); +} + +LayoutGradient::LayoutGradient(LayoutId id) + : LayoutObject(id) +{ +} + +LayoutLinearGradient::LayoutLinearGradient() + : LayoutGradient(LayoutId::LinearGradient) +{ +} + +void LayoutLinearGradient::apply(RenderState& state) const +{ + auto transform = this->transform; + if(units == Units::ObjectBoundingBox) + { + const auto& box = state.objectBoundingBox(); + transform *= Transform(box.w, 0, 0, box.h, box.x, box.y); + } + + state.canvas->setLinearGradient(x1, y1, x2, y2, stops, spreadMethod, transform); +} + +LayoutRadialGradient::LayoutRadialGradient() + : LayoutGradient(LayoutId::RadialGradient) +{ +} + +void LayoutRadialGradient::apply(RenderState& state) const +{ + auto transform = this->transform; + if(units == Units::ObjectBoundingBox) + { + const auto& box = state.objectBoundingBox(); + transform *= Transform(box.w, 0, 0, box.h, box.x, box.y); + } + + state.canvas->setRadialGradient(cx, cy, r, fx, fy, stops, spreadMethod, transform); +} + +LayoutSolidColor::LayoutSolidColor() + : LayoutObject(LayoutId::SolidColor) +{ +} + +void LayoutSolidColor::apply(RenderState& state) const +{ + state.canvas->setColor(color); +} + +void FillData::fill(RenderState& state, const Path& path) const +{ + if(opacity == 0.0 || (painter == nullptr && color.isNone())) + return; + + if(painter == nullptr) + state.canvas->setColor(color); + else + painter->apply(state); + + state.canvas->fill(path, state.transform, fillRule, BlendMode::Src_Over, opacity); +} + +void StrokeData::stroke(RenderState& state, const Path& path) const +{ + if(opacity == 0.0 || (painter == nullptr && color.isNone())) + return; + + if(painter == nullptr) + state.canvas->setColor(color); + else + painter->apply(state); + + state.canvas->stroke(path, state.transform, width, cap, join, miterlimit, dash, BlendMode::Src_Over, opacity); +} + +static const double sqrt2 = 1.41421356237309504880; + +void StrokeData::inflate(Rect& box) const +{ + if(opacity == 0.0 || (painter == nullptr && color.isNone())) + return; + + double caplimit = width / 2.0; + if(cap == LineCap::Square) + caplimit *= sqrt2; + + double joinlimit = width / 2.0; + if(join == LineJoin::Miter) + joinlimit *= miterlimit; + + double delta = std::max(caplimit, joinlimit); + box.x -= delta; + box.y -= delta; + box.w += delta * 2.0; + box.h += delta * 2.0; +} + +MarkerPosition::MarkerPosition(const LayoutMarker* marker, const Point& origin, double angle) + : marker(marker), origin(origin), angle(angle) +{ +} + +void MarkerData::add(const LayoutMarker* marker, const Point& origin, double angle) +{ + positions.emplace_back(marker, origin, angle); +} + +void MarkerData::render(RenderState& state) const +{ + for(const auto& position : positions) + position.marker->renderMarker(state, position.origin, position.angle, strokeWidth); +} + +void MarkerData::inflate(Rect& box) const +{ + for(const auto& position : positions) + box.unite(position.marker->markerBoundingBox(position.origin, position.angle, strokeWidth)); +} + +LayoutShape::LayoutShape() + : LayoutObject(LayoutId::Shape) +{ +} + +void LayoutShape::render(RenderState& state) const +{ + if(visibility == Visibility::Hidden) + return; + + BlendInfo info{clipper, masker, opacity, Rect::Invalid}; + RenderState newState(this, state.mode()); + newState.transform = transform * state.transform; + newState.beginGroup(state, info); + + if(newState.mode() == RenderMode::Display) + { + fillData.fill(newState, path); + strokeData.stroke(newState, path); + markerData.render(newState); + } + else + { + newState.canvas->setColor(Color::Black); + newState.canvas->fill(path, newState.transform, clipRule, BlendMode::Src, 1.0); + } + + newState.endGroup(state, info); +} + +Rect LayoutShape::map(const Rect& rect) const +{ + return transform.map(rect); +} + +const Rect& LayoutShape::fillBoundingBox() const +{ + if(m_fillBoundingBox.valid()) + return m_fillBoundingBox; + + m_fillBoundingBox = path.box(); + return m_fillBoundingBox; +} + +const Rect& LayoutShape::strokeBoundingBox() const +{ + if(m_strokeBoundingBox.valid()) + return m_strokeBoundingBox; + + m_strokeBoundingBox = fillBoundingBox(); + strokeData.inflate(m_strokeBoundingBox); + markerData.inflate(m_strokeBoundingBox); + return m_strokeBoundingBox; +} + +RenderState::RenderState(const LayoutObject* object, RenderMode mode) + : m_object(object), m_mode(mode) +{ +} + +void RenderState::beginGroup(RenderState& state, const BlendInfo& info) +{ + if(!info.clipper && !info.clip.valid() && (m_mode == RenderMode::Display && !(info.masker || info.opacity < 1.0))) + { + canvas = state.canvas; + return; + } + + auto box = transform.map(m_object->strokeBoundingBox()); + box.intersect(transform.map(info.clip)); + box.intersect(state.canvas->box()); + canvas = Canvas::create(box); +} + +void RenderState::endGroup(RenderState& state, const BlendInfo& info) +{ + if(state.canvas == canvas) + return; + + if(info.clipper) + info.clipper->apply(*this); + + if(info.masker && m_mode == RenderMode::Display) + info.masker->apply(*this); + + if(info.clip.valid()) + canvas->mask(info.clip, transform); + + state.canvas->blend(canvas.get(), BlendMode::Src_Over, m_mode == RenderMode::Display ? info.opacity : 1.0); +} + +LayoutContext::LayoutContext(const ParseDocument* document, LayoutSymbol* root) + : m_document(document), m_root(root) +{ +} + +Element* LayoutContext::getElementById(const std::string& id) const +{ + return m_document->getElementById(id); +} + +LayoutObject* LayoutContext::getResourcesById(const std::string& id) const +{ + auto it = m_resourcesCache.find(id); + if(it == m_resourcesCache.end()) + return nullptr; + + return it->second; +} + +LayoutObject* LayoutContext::addToResourcesCache(const std::string& id, std::unique_ptr resources) +{ + if(resources == nullptr) + return nullptr; + + m_resourcesCache.emplace(id, resources.get()); + return m_root->addChild(std::move(resources)); +} + +LayoutMask* LayoutContext::getMasker(const std::string& id) +{ + if(id.empty()) + return nullptr; + + auto ref = getResourcesById(id); + if(ref && ref->id == LayoutId::Mask) + return static_cast(ref); + + auto element = getElementById(id); + if(element == nullptr || element->id != ElementId::Mask) + return nullptr; + + auto masker = static_cast(element)->getMasker(this); + return static_cast(addToResourcesCache(id, std::move(masker))); +} + +LayoutClipPath* LayoutContext::getClipper(const std::string& id) +{ + if(id.empty()) + return nullptr; + + auto ref = getResourcesById(id); + if(ref && ref->id == LayoutId::ClipPath) + return static_cast(ref); + + auto element = getElementById(id); + if(element == nullptr || element->id != ElementId::ClipPath) + return nullptr; + + auto clipper = static_cast(element)->getClipper(this); + return static_cast(addToResourcesCache(id, std::move(clipper))); +} + +LayoutMarker* LayoutContext::getMarker(const std::string& id) +{ + if(id.empty()) + return nullptr; + + auto ref = getResourcesById(id); + if(ref && ref->id == LayoutId::Marker) + return static_cast(ref); + + auto element = getElementById(id); + if(element == nullptr || element->id != ElementId::Marker) + return nullptr; + + auto marker = static_cast(element)->getMarker(this); + return static_cast(addToResourcesCache(id, std::move(marker))); +} + +LayoutObject* LayoutContext::getPainter(const std::string& id) +{ + if(id.empty()) + return nullptr; + + auto ref = getResourcesById(id); + if(ref && ref->isPaint()) + return ref; + + auto element = getElementById(id); + if(element == nullptr || !element->isPaint()) + return nullptr; + + auto painter = static_cast(element)->getPainter(this); + return addToResourcesCache(id, std::move(painter)); +} + +FillData LayoutContext::fillData(const StyledElement* element) +{ + auto fill = element->fill(); + if(fill.isNone()) + return FillData{}; + + FillData fillData; + fillData.painter = getPainter(fill.ref()); + fillData.color = fill.color(); + fillData.opacity = element->fill_opacity(); + fillData.fillRule = element->fill_rule(); + return fillData; +} + +DashData LayoutContext::dashData(const StyledElement* element) +{ + auto dasharray = element->stroke_dasharray(); + if(dasharray.empty()) + return DashData{}; + + LengthContext lengthContex(element); + DashArray dashes; + for(auto& dash : dasharray) + { + auto value = lengthContex.valueForLength(dash, LengthMode::Both); + dashes.push_back(value); + } + + auto num_dash = dashes.size(); + if(num_dash % 2) + num_dash *= 2; + + DashData dashData; + dashData.array.resize(num_dash); + double sum = 0.0; + for(std::size_t i = 0;i < num_dash;i++) + { + dashData.array[i] = dashes[i % dashes.size()]; + sum += dashData.array[i]; + } + + if(sum == 0.0) + return DashData{}; + + auto offset = lengthContex.valueForLength(element->stroke_dashoffset(), LengthMode::Both); + dashData.offset = std::fmod(offset, sum); + if(dashData.offset < 0.0) + dashData.offset += sum; + + return dashData; +} + +StrokeData LayoutContext::strokeData(const StyledElement* element) +{ + auto stroke = element->stroke(); + if(stroke.isNone()) + return StrokeData{}; + + LengthContext lengthContex(element); + StrokeData strokeData; + strokeData.painter = getPainter(stroke.ref()); + strokeData.color = stroke.color(); + strokeData.opacity = element->stroke_opacity(); + strokeData.width = lengthContex.valueForLength(element->stroke_width(), LengthMode::Both); + strokeData.miterlimit = element->stroke_miterlimit(); + strokeData.cap = element->stroke_linecap(); + strokeData.join = element->stroke_linejoin(); + strokeData.dash = dashData(element); + return strokeData; +} + +static const double pi = 3.14159265358979323846; + +MarkerData LayoutContext::markerData(const GeometryElement* element, const Path& path) +{ + auto markerStart = getMarker(element->marker_start()); + auto markerMid = getMarker(element->marker_mid()); + auto markerEnd = getMarker(element->marker_end()); + + if(markerStart == nullptr && markerMid == nullptr && markerEnd == nullptr) + return MarkerData{}; + + LengthContext lengthContex(element); + MarkerData markerData; + markerData.strokeWidth = lengthContex.valueForLength(element->stroke_width(), LengthMode::Both); + + PathIterator it(path); + Point origin; + Point startPoint; + Point inslopePoints[2]; + Point outslopePoints[2]; + + int index = 0; + std::array points; + while(!it.isDone()) + { + switch(it.currentSegment(points)) { + case PathCommand::MoveTo: + startPoint = points[0]; + inslopePoints[0] = origin; + inslopePoints[1] = points[0]; + origin = points[0]; + break; + case PathCommand::LineTo: + inslopePoints[0] = origin; + inslopePoints[1] = points[0]; + origin = points[0]; + break; + case PathCommand::CubicTo: + inslopePoints[0] = points[1]; + inslopePoints[1] = points[2]; + origin = points[2]; + break; + case PathCommand::Close: + inslopePoints[0] = origin; + inslopePoints[1] = points[0]; + origin = startPoint; + startPoint = Point{}; + break; + } + + index += 1; + it.next(); + + if(!it.isDone() && (markerStart || markerMid)) + { + it.currentSegment(points); + outslopePoints[0] = origin; + outslopePoints[1] = points[0]; + + if(index == 1 && markerStart) + { + Point slope{outslopePoints[1].x - outslopePoints[0].x, outslopePoints[1].y - outslopePoints[0].y}; + auto angle = 180.0 * std::atan2(slope.y, slope.x) / pi; + + markerData.add(markerStart, origin, angle); + } + + if(index > 1 && markerMid) + { + Point inslope{inslopePoints[1].x - inslopePoints[0].x, inslopePoints[1].y - inslopePoints[0].y}; + Point outslope{outslopePoints[1].x - outslopePoints[0].x, outslopePoints[1].y - outslopePoints[0].y}; + auto inangle = 180.0 * std::atan2(inslope.y, inslope.x) / pi; + auto outangle = 180.0 * std::atan2(outslope.y, outslope.x) / pi; + auto angle = (inangle + outangle) * 0.5; + + markerData.add(markerMid, origin, angle); + } + } + + if(it.isDone() && markerEnd) + { + Point slope{inslopePoints[1].x - inslopePoints[0].x, inslopePoints[1].y - inslopePoints[0].y}; + auto angle = 180.0 * std::atan2(slope.y, slope.x) / pi; + + markerData.add(markerEnd, origin, angle); + } + } + + return markerData; +} + +void LayoutContext::addReference(const Element* element) +{ + m_references.insert(element); +} + +void LayoutContext::removeReference(const Element* element) +{ + m_references.erase(element); +} + +bool LayoutContext::hasReference(const Element* element) const +{ + return m_references.count(element); +} + +LayoutBreaker::LayoutBreaker(LayoutContext* context, const Element* element) + : m_context(context), m_element(element) +{ + context->addReference(element); +} + +LayoutBreaker::~LayoutBreaker() +{ + m_context->removeReference(m_element); +} + +} // namespace lunasvg diff --git a/source/layoutcontext.h b/source/layoutcontext.h new file mode 100644 index 000000000..13c3d3c5a --- /dev/null +++ b/source/layoutcontext.h @@ -0,0 +1,395 @@ +#ifndef LAYOUTCONTEXT_H +#define LAYOUTCONTEXT_H + +#include "property.h" +#include "canvas.h" + +#include +#include +#include + +namespace lunasvg { + +enum class LayoutId +{ + Symbol, + Group, + Shape, + Mask, + ClipPath, + Marker, + LinearGradient, + RadialGradient, + Pattern, + SolidColor +}; + +class RenderState; + +class LayoutObject +{ +public: + LayoutObject(LayoutId id); + virtual ~LayoutObject(); + virtual void render(RenderState&) const; + virtual void apply(RenderState&) const; + virtual Rect map(const Rect&) const; + + virtual const Rect& fillBoundingBox() const { return Rect::Invalid;} + virtual const Rect& strokeBoundingBox() const { return Rect::Invalid;} + + bool isPaint() const { return id == LayoutId::LinearGradient || id == LayoutId::RadialGradient || id == LayoutId::Pattern || id == LayoutId::SolidColor; } + bool isHidden() const { return isPaint() || id == LayoutId::ClipPath || id == LayoutId::Mask || id == LayoutId::Marker; } + +public: + LayoutId id; +}; + +using LayoutList = std::list>; + +class LayoutContainer : public LayoutObject +{ +public: + LayoutContainer(LayoutId id); + + const Rect& fillBoundingBox() const; + const Rect& strokeBoundingBox() const; + + LayoutObject* addChild(std::unique_ptr child); + LayoutObject* addChildIfNotEmpty(std::unique_ptr child); + void renderChildren(RenderState& state) const; + +public: + LayoutList children; + +protected: + mutable Rect m_fillBoundingBox{Rect::Invalid}; + mutable Rect m_strokeBoundingBox{Rect::Invalid}; +}; + +class LayoutClipPath : public LayoutContainer +{ +public: + LayoutClipPath(); + + void apply(RenderState& state) const; + +public: + Units units; + Transform transform; + const LayoutClipPath* clipper; +}; + +class LayoutMask : public LayoutContainer +{ +public: + LayoutMask(); + + void apply(RenderState& state) const; + +public: + double x; + double y; + double width; + double height; + Units units; + Units contentUnits; + double opacity; + const LayoutMask* masker; + const LayoutClipPath* clipper; +}; + +class LayoutSymbol : public LayoutContainer +{ +public: + LayoutSymbol(); + + void render(RenderState& state) const; + Rect map(const Rect& rect) const; + +public: + double width; + double height; + Transform transform; + Rect clip; + double opacity; + const LayoutMask* masker; + const LayoutClipPath* clipper; +}; + +class LayoutGroup : public LayoutContainer +{ +public: + LayoutGroup(); + + void render(RenderState& state) const; + Rect map(const Rect& rect) const; + +public: + Transform transform; + double opacity; + const LayoutMask* masker; + const LayoutClipPath* clipper; +}; + +class LayoutMarker : public LayoutContainer +{ +public: + LayoutMarker(); + + Transform markerTransform(const Point& origin, double angle, double strokeWidth) const; + Rect markerBoundingBox(const Point& origin, double angle, double strokeWidth) const; + void renderMarker(RenderState& state, const Point& origin, double angle, double strokeWidth) const; + +public: + double refX; + double refY; + Transform transform; + Angle orient; + MarkerUnits units; + Rect clip; + double opacity; + const LayoutMask* masker; + const LayoutClipPath* clipper; +}; + +class LayoutPattern : public LayoutContainer +{ +public: + LayoutPattern(); + + void apply(RenderState& state) const; + +public: + double x; + double y; + double width; + double height; + Transform transform; + Units units; + Units contentUnits; + Rect viewBox; + PreserveAspectRatio preserveAspectRatio; +}; + +class LayoutGradient : public LayoutObject +{ +public: + LayoutGradient(LayoutId id); + +public: + Transform transform; + SpreadMethod spreadMethod; + Units units; + GradientStops stops; +}; + +class LayoutLinearGradient : public LayoutGradient +{ +public: + LayoutLinearGradient(); + + void apply(RenderState& state) const; + +public: + double x1; + double y1; + double x2; + double y2; +}; + +class LayoutRadialGradient : public LayoutGradient +{ +public: + LayoutRadialGradient(); + + void apply(RenderState& state) const; + +public: + double cx; + double cy; + double r; + double fx; + double fy; +}; + +class LayoutSolidColor : public LayoutObject +{ +public: + LayoutSolidColor(); + + void apply(RenderState& state) const; + +public: + Color color; +}; + +class FillData +{ +public: + FillData() = default; + + void fill(RenderState& state, const Path& path) const; + +public: + const LayoutObject* painter{nullptr}; + Color color{Color::Transparent}; + double opacity{0}; + WindRule fillRule{WindRule::NonZero}; +}; + +class StrokeData +{ +public: + StrokeData() = default; + + void stroke(RenderState& state, const Path& path) const; + void inflate(Rect& box) const; + +public: + const LayoutObject* painter{nullptr}; + Color color{Color::Transparent}; + double opacity{0}; + double width{1}; + double miterlimit{4}; + LineCap cap{LineCap::Butt}; + LineJoin join{LineJoin::Miter}; + DashData dash; +}; + +class MarkerPosition +{ +public: + MarkerPosition(const LayoutMarker* marker, const Point& origin, double angle); + +public: + const LayoutMarker* marker; + Point origin; + double angle; +}; + +using MarkerPositionList = std::vector; + +class MarkerData +{ +public: + MarkerData() = default; + + void add(const LayoutMarker* marker, const Point& origin, double angle); + void render(RenderState& state) const; + void inflate(Rect& box) const; + +public: + MarkerPositionList positions; + double strokeWidth{1}; +}; + +class LayoutShape : public LayoutObject +{ +public: + LayoutShape(); + + void render(RenderState& state) const; + Rect map(const Rect& rect) const; + const Rect& fillBoundingBox() const; + const Rect& strokeBoundingBox() const; + +public: + Path path; + Transform transform; + FillData fillData; + StrokeData strokeData; + MarkerData markerData; + Visibility visibility; + WindRule clipRule; + double opacity; + const LayoutMask* masker; + const LayoutClipPath* clipper; + +private: + mutable Rect m_fillBoundingBox{Rect::Invalid}; + mutable Rect m_strokeBoundingBox{Rect::Invalid}; +}; + +enum class RenderMode +{ + Display, + Clipping +}; + +struct BlendInfo +{ + const LayoutClipPath* clipper; + const LayoutMask* masker; + double opacity; + Rect clip; +}; + +class RenderState +{ +public: + RenderState(const LayoutObject* object, RenderMode mode); + + void beginGroup(RenderState& state, const BlendInfo& info); + void endGroup(RenderState& state, const BlendInfo& info); + + const LayoutObject* object() const { return m_object;} + RenderMode mode() const { return m_mode; } + const Rect& objectBoundingBox() const { return m_object->fillBoundingBox(); } + +public: + std::shared_ptr canvas; + Transform transform; + +private: + const LayoutObject* m_object; + RenderMode m_mode; +}; + +class ParseDocument; +class StyledElement; +class GeometryElement; + +class LayoutContext +{ +public: + LayoutContext(const ParseDocument* document, LayoutSymbol* root); + + Element* getElementById(const std::string& id) const; + LayoutObject* getResourcesById(const std::string& id) const; + LayoutObject* addToResourcesCache(const std::string& id, std::unique_ptr resources); + LayoutMask* getMasker(const std::string& id); + LayoutClipPath* getClipper(const std::string& id); + LayoutMarker* getMarker(const std::string& id); + LayoutObject* getPainter(const std::string& id); + + FillData fillData(const StyledElement* element); + DashData dashData(const StyledElement* element); + StrokeData strokeData(const StyledElement* element); + MarkerData markerData(const GeometryElement* element, const Path& path); + + void addReference(const Element* element); + void removeReference(const Element* element); + bool hasReference(const Element* element) const; + +private: + const ParseDocument* m_document; + LayoutSymbol* m_root; + std::map m_resourcesCache; + std::set m_references; +}; + +class LayoutBreaker +{ +public: + LayoutBreaker(LayoutContext* context, const Element* element); + ~LayoutBreaker(); + +private: + LayoutContext* m_context; + const Element* m_element; +}; + +} // namespace lunasvg + +#endif // LAYOUTCONTEXT_H diff --git a/source/lunasvg.cpp b/source/lunasvg.cpp new file mode 100644 index 000000000..0457067e1 --- /dev/null +++ b/source/lunasvg.cpp @@ -0,0 +1,413 @@ +#include "lunasvg.h" +#include "layoutcontext.h" +#include "parser.h" + +#include +#include +#include + +namespace lunasvg { + +struct Bitmap::Impl +{ + Impl(std::uint8_t* data, std::uint32_t width, std::uint32_t height, std::uint32_t stride); + Impl(std::uint32_t width, std::uint32_t height); + + std::unique_ptr ownData; + std::uint8_t* data; + std::uint32_t width; + std::uint32_t height; + std::uint32_t stride; +}; + +Bitmap::Impl::Impl(std::uint8_t* data, std::uint32_t width, std::uint32_t height, std::uint32_t stride) + : data(data), width(width), height(height), stride(stride) +{ +} + +Bitmap::Impl::Impl(std::uint32_t width, std::uint32_t height) + : ownData(new std::uint8_t[width*height*4]), data(nullptr), width(width), height(height), stride(width * 4) +{ +} + +Bitmap::Bitmap() +{ +} + +Bitmap::Bitmap(std::uint8_t* data, std::uint32_t width, std::uint32_t height, std::uint32_t stride) + : m_impl(new Impl(data, width, height, stride)) +{ +} + +Bitmap::Bitmap(std::uint32_t width, std::uint32_t height) + : m_impl(new Impl(width, height)) +{ +} + +void Bitmap::reset(std::uint8_t* data, std::uint32_t width, std::uint32_t height, std::uint32_t stride) +{ + m_impl.reset(new Impl(data, width, height, stride)); +} + +void Bitmap::reset(std::uint32_t width, std::uint32_t height) +{ + m_impl.reset(new Impl(width, height)); +} + +std::uint8_t* Bitmap::data() const +{ + return m_impl ? m_impl->data ? m_impl->data : m_impl->ownData.get() : nullptr; +} + +std::uint32_t Bitmap::width() const +{ + return m_impl ? m_impl->width : 0; +} + +std::uint32_t Bitmap::height() const +{ + return m_impl ? m_impl->height : 0; +} + +std::uint32_t Bitmap::stride() const +{ + return m_impl ? m_impl->stride : 0; +} + +void Bitmap::clear(std::uint32_t color) +{ + auto r = (color >> 24) & 0xFF; + auto g = (color >> 16) & 0xFF; + auto b = (color >> 8) & 0xFF; + auto a = (color >> 0) & 0xFF; + + auto pr = (r * a) / 255; + auto pg = (g * a) / 255; + auto pb = (b * a) / 255; + + auto width = this->width(); + auto height = this->height(); + auto stride = this->stride(); + auto rowData = this->data(); + + for(std::uint32_t y = 0;y < height;y++) + { + auto data = rowData; + for(std::uint32_t x = 0;x < width;x++) + { + data[0] = pb; + data[1] = pg; + data[2] = pr; + data[3] = a; + data += 4; + } + rowData += stride; + } +} + +void Bitmap::convert(int ri, int gi, int bi, int ai, bool unpremultiply) +{ + auto width = this->width(); + auto height = this->height(); + auto stride = this->stride(); + auto rowData = this->data(); + + for(std::uint32_t y = 0;y < height;y++) + { + auto data = rowData; + for(std::uint32_t x = 0;x < width;x++) + { + auto b = data[0]; + auto g = data[1]; + auto r = data[2]; + auto a = data[3]; + + if(unpremultiply && a != 0) + { + r = (r * 255) / a; + g = (g * 255) / a; + b = (b * 255) / a; + } + + data[ri] = r; + data[gi] = g; + data[bi] = b; + data[ai] = a; + data += 4; + } + rowData += stride; + } +} + +Box::Box(double x, double y, double w, double h) + : x(x), y(y), w(w), h(h) +{} + +Box::Box(const Rect& rect) + : x(rect.x), y(rect.y), w(rect.w), h(rect.h) +{} + +Matrix::Matrix(double a, double b, double c, double d, double e, double f) + : a(a), b(b), c(c), d(d), e(e), f(f) +{} + +Matrix::Matrix(const Transform& transform) + : a(transform.m00), b(transform.m10), c(transform.m01), d(transform.m11), e(transform.m02), f(transform.m12) +{ +} + +Matrix& Matrix::rotate(double angle) +{ + *this = rotated(angle) * *this; + return *this; +} + +Matrix& Matrix::rotate(double angle, double cx, double cy) +{ + *this = rotated(angle, cx, cy) * *this; + return *this; +} + +Matrix& Matrix::scale(double sx, double sy) +{ + *this = scaled(sx, sy) * *this; + return *this; +} + +Matrix& Matrix::shear(double shx, double shy) +{ + *this = sheared(shx, shy) * *this; + return *this; +} + +Matrix& Matrix::translate(double tx, double ty) +{ + *this = translated(tx, ty) * *this; + return *this; +} + +Matrix& Matrix::transform(double a, double b, double c, double d, double e, double f) +{ + *this = Matrix{a, b, c, d, e, f} * *this; + return *this; +} + +Matrix& Matrix::identity() +{ + *this = Matrix{1, 0, 0, 1, 0, 0}; + return *this; +} + +Matrix& Matrix::invert() +{ + *this = inverted(); + return *this; +} + +Matrix& Matrix::operator*=(const Matrix& matrix) +{ + *this = *this * matrix; + return *this; +} + +Matrix& Matrix::premultiply(const Matrix& matrix) +{ + *this = matrix * *this; + return *this; +} + +Matrix& Matrix::postmultiply(const Matrix& matrix) +{ + *this = *this * matrix; + return *this; +} + +Matrix Matrix::inverted() const +{ + return Transform(*this).inverted(); +} + +Matrix Matrix::operator*(const Matrix& matrix) const +{ + return Transform(*this) * Transform(matrix); +} + +Box Matrix::map(const Box& box) const +{ + return Transform(*this).map(box); +} + +Matrix Matrix::rotated(double angle) +{ + return Transform::rotated(angle); +} + +Matrix Matrix::rotated(double angle, double cx, double cy) +{ + return Transform::rotated(angle, cx, cy); +} + +Matrix Matrix::scaled(double sx, double sy) +{ + return Transform::scaled(sx, sy);; +} + +Matrix Matrix::sheared(double shx, double shy) +{ + return Transform::sheared(shx, shy); +} + +Matrix Matrix::translated(double tx, double ty) +{ + return Transform::translated(tx, ty); +} + +std::unique_ptr Document::loadFromFile(const std::string& filename) +{ + std::ifstream fs; + fs.open(filename); + if(!fs.is_open()) + return nullptr; + + std::string content; + std::getline(fs, content, '\0'); + fs.close(); + + return loadFromData(content); +} + +std::unique_ptr Document::loadFromData(const std::string& string) +{ + return loadFromData(string.data(), string.size()); +} + +std::unique_ptr Document::loadFromData(const char* data, std::size_t size) +{ + ParseDocument parser; + if(!parser.parse(data, size)) + return nullptr; + + auto root = parser.layout(); + if(!root || root->children.empty()) + return nullptr; + + std::unique_ptr document(new Document); + document->root = std::move(root); + return document; +} + +std::unique_ptr Document::loadFromData(const char* data) +{ + return loadFromData(data, std::strlen(data)); +} + +Document* Document::rotate(double angle) +{ + root->transform.rotate(angle); + return this; +} + +Document* Document::rotate(double angle, double cx, double cy) +{ + root->transform.rotate(angle, cx, cy); + return this; +} + +Document* Document::scale(double sx, double sy) +{ + root->transform.scale(sx, sy); + return this; +} + +Document* Document::shear(double shx, double shy) +{ + root->transform.shear(shx, shy); + return this; +} + +Document* Document::translate(double tx, double ty) +{ + root->transform.translate(tx, ty); + return this; +} + +Document* Document::transform(double a, double b, double c, double d, double e, double f) +{ + root->transform.transform(a, b, c, d, e, f); + return this; +} + +Document* Document::identity() +{ + root->transform.identity(); + return this; +} + +void Document::setMatrix(const Matrix& matrix) +{ + root->transform = Transform(matrix); +} + +Matrix Document::matrix() const +{ + return root->transform; +} + +Box Document::box() const +{ + return root->map(root->strokeBoundingBox()); +} + +double Document::width() const +{ + return root->width; +} + +double Document::height() const +{ + return root->height; +} + +void Document::render(Bitmap bitmap, const Matrix& matrix) const +{ + RenderState state(nullptr, RenderMode::Display); + state.canvas = Canvas::create(bitmap.data(), bitmap.width(), bitmap.height(), bitmap.stride()); + state.transform = Transform(matrix); + root->render(state); +} + +Bitmap Document::renderToBitmap(std::uint32_t width, std::uint32_t height, std::uint32_t backgroundColor) const +{ + if(root->width == 0.0 || root->height == 0.0) + return Bitmap{}; + + if(width == 0 && height == 0) + { + width = static_cast(std::ceil(root->width)); + height = static_cast(std::ceil(root->height)); + } + else if(width != 0 && height == 0) + { + height = static_cast(std::ceil(width * root->height / root->width)); + } + else if(height != 0 && width == 0) + { + width = static_cast(std::ceil(height * root->width / root->height)); + } + + Matrix matrix(width / root->width, 0, 0, height / root->height, 0, 0); + Bitmap bitmap(width, height); + bitmap.clear(backgroundColor); + render(bitmap, matrix); + return bitmap; +} + +Document::Document() +{ +} + +Document::~Document() +{ +} + +} // namespace lunasvg diff --git a/source/markerelement.cpp b/source/markerelement.cpp new file mode 100644 index 000000000..262d7b201 --- /dev/null +++ b/source/markerelement.cpp @@ -0,0 +1,98 @@ +#include "markerelement.h" +#include "parser.h" +#include "layoutcontext.h" + +namespace lunasvg { + +MarkerElement::MarkerElement() + : StyledElement(ElementId::Marker) +{ +} + +Length MarkerElement::refX() const +{ + auto& value = get(PropertyId::RefX); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length MarkerElement::refY() const +{ + auto& value = get(PropertyId::RefY); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length MarkerElement::markerWidth() const +{ + auto& value = get(PropertyId::MarkerWidth); + return Parser::parseLength(value, ForbidNegativeLengths, Length::ThreePercent); +} + +Length MarkerElement::markerHeight() const +{ + auto& value = get(PropertyId::MarkerHeight); + return Parser::parseLength(value, ForbidNegativeLengths, Length::ThreePercent); +} + +Angle MarkerElement::orient() const +{ + auto& value = get(PropertyId::Orient); + return Parser::parseAngle(value); +} + +MarkerUnits MarkerElement::markerUnits() const +{ + auto& value = get(PropertyId::MarkerUnits); + return Parser::parseMarkerUnits(value); +} + +Rect MarkerElement::viewBox() const +{ + auto& value = get(PropertyId::ViewBox); + return Parser::parseViewBox(value); +} + +PreserveAspectRatio MarkerElement::preserveAspectRatio() const +{ + auto& value = get(PropertyId::PreserveAspectRatio); + return Parser::parsePreserveAspectRatio(value); +} + +std::unique_ptr MarkerElement::getMarker(LayoutContext* context) const +{ + auto markerWidth = this->markerWidth(); + auto markerHeight = this->markerHeight(); + if(markerWidth.isZero() || markerHeight.isZero() || context->hasReference(this)) + return nullptr; + + LengthContext lengthContext(this); + auto _refX = lengthContext.valueForLength(refX(), LengthMode::Width); + auto _refY = lengthContext.valueForLength(refY(), LengthMode::Height); + auto _markerWidth = lengthContext.valueForLength(markerWidth, LengthMode::Width); + auto _markerHeight = lengthContext.valueForLength(markerHeight, LengthMode::Height); + + auto viewBox = this->viewBox(); + auto preserveAspectRatio = this->preserveAspectRatio(); + auto viewTransform = preserveAspectRatio.getMatrix(_markerWidth, _markerHeight, viewBox); + viewTransform.map(_refX, _refY, &_refX, &_refY); + + LayoutBreaker layoutBreaker(context, this); + auto marker = std::make_unique(); + marker->refX = _refX; + marker->refY = _refY; + marker->transform = viewTransform; + marker->orient = orient(); + marker->units = markerUnits(); + marker->clip = isOverflowHidden() ? preserveAspectRatio.getClip(_markerWidth, _markerHeight, viewBox) : Rect::Invalid; + marker->opacity = opacity(); + marker->masker = context->getMasker(mask()); + marker->clipper = context->getClipper(clip_path()); + layoutChildren(context, marker.get()); + return marker; +} + +std::unique_ptr MarkerElement::clone() const +{ + return cloneElement(); +} + +} // namespace lunasvg diff --git a/source/markerelement.h b/source/markerelement.h new file mode 100644 index 000000000..1b519f190 --- /dev/null +++ b/source/markerelement.h @@ -0,0 +1,31 @@ +#ifndef MARKERELEMENT_H +#define MARKERELEMENT_H + +#include "graphicselement.h" + +namespace lunasvg { + +class LayoutMarker; + +class MarkerElement : public StyledElement +{ +public: + MarkerElement(); + + Length refX() const; + Length refY() const; + Length markerWidth() const; + Length markerHeight() const; + Angle orient() const; + MarkerUnits markerUnits() const; + + Rect viewBox() const; + PreserveAspectRatio preserveAspectRatio() const; + std::unique_ptr getMarker(LayoutContext* context) const; + + std::unique_ptr clone() const; +}; + +} // namespace lunasvg + +#endif // MARKERELEMENT_H diff --git a/source/maskelement.cpp b/source/maskelement.cpp new file mode 100644 index 000000000..da0a7fbe6 --- /dev/null +++ b/source/maskelement.cpp @@ -0,0 +1,77 @@ +#include "maskelement.h" +#include "parser.h" +#include "layoutcontext.h" + +namespace lunasvg { + +MaskElement::MaskElement() + : StyledElement(ElementId::Mask) +{ +} + +Length MaskElement::x() const +{ + auto& value = get(PropertyId::X); + return Parser::parseLength(value, AllowNegativeLengths, Length::MinusTenPercent); +} + +Length MaskElement::y() const +{ + auto& value = get(PropertyId::Y); + return Parser::parseLength(value, AllowNegativeLengths, Length::MinusTenPercent); +} + +Length MaskElement::width() const +{ + auto& value = get(PropertyId::Width); + return Parser::parseLength(value, ForbidNegativeLengths, Length::OneTwentyPercent); +} + +Length MaskElement::height() const +{ + auto& value = get(PropertyId::Height); + return Parser::parseLength(value, ForbidNegativeLengths, Length::OneTwentyPercent); +} + +Units MaskElement::maskUnits() const +{ + auto& value = get(PropertyId::MaskUnits); + return Parser::parseUnits(value, Units::ObjectBoundingBox); +} + +Units MaskElement::maskContentUnits() const +{ + auto& value = get(PropertyId::MaskContentUnits); + return Parser::parseUnits(value, Units::UserSpaceOnUse); +} + +std::unique_ptr MaskElement::getMasker(LayoutContext* context) const +{ + auto w = this->width(); + auto h = this->height(); + if(w.isZero() || h.isZero() || context->hasReference(this)) + return nullptr; + + LayoutBreaker layoutBreaker(context, this); + auto masker = std::make_unique(); + masker->units = maskUnits(); + masker->contentUnits = maskContentUnits(); + masker->opacity = opacity(); + masker->clipper = context->getClipper(clip_path()); + masker->masker = context->getMasker(mask()); + + LengthContext lengthContext(this, maskUnits()); + masker->x = lengthContext.valueForLength(x(), LengthMode::Width); + masker->y = lengthContext.valueForLength(y(), LengthMode::Height); + masker->width = lengthContext.valueForLength(w, LengthMode::Width); + masker->height = lengthContext.valueForLength(h, LengthMode::Height); + layoutChildren(context, masker.get()); + return masker; +} + +std::unique_ptr MaskElement::clone() const +{ + return cloneElement(); +} + +} // namespace lunasvg diff --git a/source/maskelement.h b/source/maskelement.h new file mode 100644 index 000000000..44df229fb --- /dev/null +++ b/source/maskelement.h @@ -0,0 +1,28 @@ +#ifndef MASKELEMENT_H +#define MASKELEMENT_H + +#include "styledelement.h" + +namespace lunasvg { + +class LayoutMask; + +class MaskElement : public StyledElement +{ +public: + MaskElement(); + + Length x() const; + Length y() const; + Length width() const; + Length height() const; + Units maskUnits() const; + Units maskContentUnits() const; + std::unique_ptr getMasker(LayoutContext* context) const; + + std::unique_ptr clone() const; +}; + +} // namespace lunasvg + +#endif // MASKELEMENT_H diff --git a/source/paintelement.cpp b/source/paintelement.cpp new file mode 100644 index 000000000..0e0776e72 --- /dev/null +++ b/source/paintelement.cpp @@ -0,0 +1,433 @@ +#include "paintelement.h" +#include "stopelement.h" +#include "parser.h" +#include "layoutcontext.h" + +#include + +namespace lunasvg { + +PaintElement::PaintElement(ElementId id) + : StyledElement(id) +{ +} + +GradientElement::GradientElement(ElementId id) + : PaintElement(id) +{ +} + +Transform GradientElement::gradientTransform() const +{ + auto& value = get(PropertyId::GradientTransform); + return Parser::parseTransform(value); +} + +SpreadMethod GradientElement::spreadMethod() const +{ + auto& value = get(PropertyId::SpreadMethod); + return Parser::parseSpreadMethod(value); +} + +Units GradientElement::gradientUnits() const +{ + auto& value = get(PropertyId::GradientUnits); + return Parser::parseUnits(value, Units::ObjectBoundingBox); +} + +std::string GradientElement::href() const +{ + auto& value = get(PropertyId::Href); + return Parser::parseHref(value); +} + +GradientStops GradientElement::buildGradientStops() const +{ + GradientStops gradientStops; + double prevOffset = 0.0; + for(auto& child : children) + { + if(child->isText()) + continue; + auto element = static_cast(child.get()); + if(element->id != ElementId::Stop) + continue; + auto stop = static_cast(element); + auto offset = std::max(prevOffset, stop->offset()); + prevOffset = offset; + gradientStops.emplace_back(offset, stop->stopColorWithOpacity()); + } + + return gradientStops; +} + +LinearGradientElement::LinearGradientElement() + : GradientElement(ElementId::LinearGradient) +{ +} + +Length LinearGradientElement::x1() const +{ + auto& value = get(PropertyId::X1); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length LinearGradientElement::y1() const +{ + auto& value = get(PropertyId::Y1); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length LinearGradientElement::x2() const +{ + auto& value = get(PropertyId::X2); + return Parser::parseLength(value, AllowNegativeLengths, Length::HundredPercent); +} + +Length LinearGradientElement::y2() const +{ + auto& value = get(PropertyId::Y2); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +std::unique_ptr LinearGradientElement::getPainter(LayoutContext* context) const +{ + LinearGradientAttributes attributes; + std::set processedGradients; + const GradientElement* current = this; + + while(true) + { + if(!attributes.hasGradientTransform() && current->has(PropertyId::GradientTransform)) + attributes.setGradientTransform(current->gradientTransform()); + if(!attributes.hasSpreadMethod() && current->has(PropertyId::SpreadMethod)) + attributes.setSpreadMethod(current->spreadMethod()); + if(!attributes.hasGradientUnits() && current->has(PropertyId::GradientUnits)) + attributes.setGradientUnits(current->gradientUnits()); + if(!attributes.hasGradientStops()) + attributes.setGradientStops(current->buildGradientStops()); + + if(current->id == ElementId::LinearGradient) + { + auto element = static_cast(current); + if(!attributes.hasX1() && element->has(PropertyId::X1)) + attributes.setX1(element->x1()); + if(!attributes.hasY1() && element->has(PropertyId::Y1)) + attributes.setY1(element->y1()); + if(!attributes.hasX2() && element->has(PropertyId::X2)) + attributes.setX2(element->x2()); + if(!attributes.hasY2() && element->has(PropertyId::Y2)) + attributes.setY2(element->y2()); + } + + auto ref = context->getElementById(current->href()); + if(!ref || !(ref->id == ElementId::LinearGradient || ref->id == ElementId::RadialGradient)) + break; + + processedGradients.insert(current); + current = static_cast(ref); + if(processedGradients.find(current) != processedGradients.end()) + break; + } + + auto& stops = attributes.gradientStops(); + if(stops.empty()) + return nullptr; + + LengthContext lengthContext(this, attributes.gradientUnits()); + auto x1 = lengthContext.valueForLength(attributes.x1(), LengthMode::Width); + auto y1 = lengthContext.valueForLength(attributes.y1(), LengthMode::Height); + auto x2 = lengthContext.valueForLength(attributes.x2(), LengthMode::Width); + auto y2 = lengthContext.valueForLength(attributes.y2(), LengthMode::Height); + if((x1 == x2 && y1 == y2) || stops.size() == 1) + { + auto solid = std::make_unique(); + solid->color = std::get<1>(stops.back()); + return std::move(solid); + } + + auto gradient = std::make_unique(); + gradient->transform = attributes.gradientTransform(); + gradient->spreadMethod = attributes.spreadMethod(); + gradient->units = attributes.gradientUnits(); + gradient->stops = attributes.gradientStops(); + gradient->x1 = x1; + gradient->y1 = y1; + gradient->x2 = x2; + gradient->y2 = y2; + return std::move(gradient); +} + +std::unique_ptr LinearGradientElement::clone() const +{ + return cloneElement(); +} + +RadialGradientElement::RadialGradientElement() + : GradientElement(ElementId::RadialGradient) +{ +} + +Length RadialGradientElement::cx() const +{ + auto& value = get(PropertyId::Cx); + return Parser::parseLength(value, AllowNegativeLengths, Length::FiftyPercent); +} + +Length RadialGradientElement::cy() const +{ + auto& value = get(PropertyId::Cy); + return Parser::parseLength(value, AllowNegativeLengths, Length::FiftyPercent); +} + +Length RadialGradientElement::r() const +{ + auto& value = get(PropertyId::R); + return Parser::parseLength(value, ForbidNegativeLengths, Length::FiftyPercent); +} + +Length RadialGradientElement::fx() const +{ + auto& value = get(PropertyId::Fx); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length RadialGradientElement::fy() const +{ + auto& value = get(PropertyId::Fy); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +std::unique_ptr RadialGradientElement::getPainter(LayoutContext* context) const +{ + RadialGradientAttributes attributes; + std::set processedGradients; + const GradientElement* current = this; + + while(true) + { + if(!attributes.hasGradientTransform() && current->has(PropertyId::GradientTransform)) + attributes.setGradientTransform(current->gradientTransform()); + if(!attributes.hasSpreadMethod() && current->has(PropertyId::SpreadMethod)) + attributes.setSpreadMethod(current->spreadMethod()); + if(!attributes.hasGradientUnits() && current->has(PropertyId::GradientUnits)) + attributes.setGradientUnits(current->gradientUnits()); + if(!attributes.hasGradientStops()) + attributes.setGradientStops(current->buildGradientStops()); + + if(current->id == ElementId::RadialGradient) + { + auto element = static_cast(current); + if(!attributes.hasCx() && element->has(PropertyId::Cx)) + attributes.setCx(element->cx()); + if(!attributes.hasCy() && element->has(PropertyId::Cy)) + attributes.setCy(element->cy()); + if(!attributes.hasR() && element->has(PropertyId::R)) + attributes.setR(element->r()); + if(!attributes.hasFx() && element->has(PropertyId::Fx)) + attributes.setFx(element->fx()); + if(!attributes.hasFy() && element->has(PropertyId::Fy)) + attributes.setFy(element->fy()); + } + + auto ref = context->getElementById(current->href()); + if(!ref || !(ref->id == ElementId::LinearGradient || ref->id == ElementId::RadialGradient)) + break; + + processedGradients.insert(current); + current = static_cast(ref); + if(processedGradients.find(current) != processedGradients.end()) + break; + } + + if(!attributes.hasFx()) + attributes.setFx(attributes.cx()); + if(!attributes.hasFy()) + attributes.setFy(attributes.cy()); + + auto& stops = attributes.gradientStops(); + if(stops.empty()) + return nullptr; + + auto& r = attributes.r(); + if(r.isZero() || stops.size() == 1) + { + auto solid = std::make_unique(); + solid->color = std::get<1>(stops.back()); + return std::move(solid); + } + + auto gradient = std::make_unique(); + gradient->transform = attributes.gradientTransform(); + gradient->spreadMethod = attributes.spreadMethod(); + gradient->units = attributes.gradientUnits(); + gradient->stops = attributes.gradientStops(); + + LengthContext lengthContext(this, attributes.gradientUnits()); + gradient->cx = lengthContext.valueForLength(attributes.cx(), LengthMode::Width); + gradient->cy = lengthContext.valueForLength(attributes.cy(), LengthMode::Height); + gradient->r = lengthContext.valueForLength(attributes.r(), LengthMode::Both); + gradient->fx = lengthContext.valueForLength(attributes.fx(), LengthMode::Width); + gradient->fy = lengthContext.valueForLength(attributes.fy(), LengthMode::Height); + return std::move(gradient); +} + +std::unique_ptr RadialGradientElement::clone() const +{ + return cloneElement(); +} + +PatternElement::PatternElement() + : PaintElement(ElementId::Pattern) +{ +} + +Length PatternElement::x() const +{ + auto& value = get(PropertyId::X); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length PatternElement::y() const +{ + auto& value = get(PropertyId::Y); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length PatternElement::width() const +{ + auto& value = get(PropertyId::Width); + return Parser::parseLength(value, ForbidNegativeLengths, Length::Zero); +} + +Length PatternElement::height() const +{ + auto& value = get(PropertyId::Height); + return Parser::parseLength(value, ForbidNegativeLengths, Length::Zero); +} + +Transform PatternElement::patternTransform() const +{ + auto& value = get(PropertyId::PatternTransform); + return Parser::parseTransform(value); +} + +Units PatternElement::patternUnits() const +{ + auto& value = get(PropertyId::PatternUnits); + return Parser::parseUnits(value, Units::ObjectBoundingBox); +} + +Units PatternElement::patternContentUnits() const +{ + auto& value = get(PropertyId::PatternContentUnits); + return Parser::parseUnits(value, Units::UserSpaceOnUse); +} + +Rect PatternElement::viewBox() const +{ + auto& value = get(PropertyId::ViewBox); + return Parser::parseViewBox(value); +} + +PreserveAspectRatio PatternElement::preserveAspectRatio() const +{ + auto& value = get(PropertyId::PreserveAspectRatio); + return Parser::parsePreserveAspectRatio(value); +} + +std::string PatternElement::href() const +{ + auto& value = get(PropertyId::Href); + return Parser::parseHref(value); +} + +std::unique_ptr PatternElement::getPainter(LayoutContext* context) const +{ + if(context->hasReference(this)) + return nullptr; + + PatternAttributes attributes; + std::set processedPatterns; + const PatternElement* current = this; + + while(true) + { + if(!attributes.hasX() && current->has(PropertyId::X)) + attributes.setX(current->x()); + if(!attributes.hasY() && current->has(PropertyId::Y)) + attributes.setY(current->y()); + if(!attributes.hasWidth() && current->has(PropertyId::Width)) + attributes.setWidth(current->width()); + if(!attributes.hasHeight() && current->has(PropertyId::Height)) + attributes.setHeight(current->height()); + if(!attributes.hasPatternTransform() && current->has(PropertyId::PatternTransform)) + attributes.setPatternTransform(current->patternTransform()); + if(!attributes.hasPatternUnits() && current->has(PropertyId::PatternUnits)) + attributes.setPatternUnits(current->patternUnits()); + if(!attributes.hasPatternContentUnits() && current->has(PropertyId::PatternContentUnits)) + attributes.setPatternContentUnits(current->patternContentUnits()); + if(!attributes.hasViewBox() && current->has(PropertyId::ViewBox)) + attributes.setViewBox(current->viewBox()); + if(!attributes.hasPreserveAspectRatio() && current->has(PropertyId::PreserveAspectRatio)) + attributes.setPreserveAspectRatio(current->preserveAspectRatio()); + if(!attributes.hasPatternContentElement() && current->children.size()) + attributes.setPatternContentElement(current); + + auto ref = context->getElementById(current->href()); + if(!ref || ref->id != ElementId::Pattern) + break; + + processedPatterns.insert(current); + current = static_cast(ref); + if(processedPatterns.find(current) != processedPatterns.end()) + break; + } + + auto& width = attributes.width(); + auto& height = attributes.height(); + auto element = attributes.patternContentElement(); + if(element == nullptr || width.isZero() || height.isZero()) + return nullptr; + + LayoutBreaker layoutBreaker(context, this); + auto pattern = std::make_unique(); + pattern->transform = attributes.patternTransform(); + pattern->units = attributes.patternUnits(); + pattern->contentUnits = attributes.patternContentUnits(); + pattern->viewBox = attributes.viewBox(); + pattern->preserveAspectRatio = attributes.preserveAspectRatio(); + + LengthContext lengthContext(this, attributes.patternUnits()); + pattern->x = lengthContext.valueForLength(attributes.x(), LengthMode::Width); + pattern->y = lengthContext.valueForLength(attributes.y(), LengthMode::Height); + pattern->width = lengthContext.valueForLength(attributes.width(), LengthMode::Width); + pattern->height = lengthContext.valueForLength(attributes.height(), LengthMode::Height); + element->layoutChildren(context, pattern.get()); + return std::move(pattern); +} + +std::unique_ptr PatternElement::clone() const +{ + return cloneElement(); +} + +SolidColorElement::SolidColorElement() + : PaintElement(ElementId::SolidColor) +{ +} + +std::unique_ptr SolidColorElement::getPainter(LayoutContext*) const +{ + auto solid = std::make_unique(); + solid->color = solid_color(); + solid->color.a = solid_opacity(); + return std::move(solid); +} + +std::unique_ptr SolidColorElement::clone() const +{ + return cloneElement(); +} + +} // namespace lunasvg diff --git a/source/paintelement.h b/source/paintelement.h new file mode 100644 index 000000000..70c86cd9f --- /dev/null +++ b/source/paintelement.h @@ -0,0 +1,368 @@ +#ifndef PAINTELEMENT_H +#define PAINTELEMENT_H + +#include "styledelement.h" +#include "canvas.h" + +namespace lunasvg { + +class LayoutObject; + +class PaintElement : public StyledElement +{ +public: + PaintElement(ElementId id); + + bool isPaint() const { return true; } + virtual std::unique_ptr getPainter(LayoutContext* context) const = 0; +}; + +class GradientElement : public PaintElement +{ +public: + GradientElement(ElementId id); + + Transform gradientTransform() const; + SpreadMethod spreadMethod() const; + Units gradientUnits() const; + std::string href() const; + GradientStops buildGradientStops() const; +}; + +class LinearGradientElement : public GradientElement +{ +public: + LinearGradientElement(); + + Length x1() const; + Length y1() const; + Length x2() const; + Length y2() const; + + std::unique_ptr getPainter(LayoutContext* context) const; + std::unique_ptr clone() const; +}; + +class RadialGradientElement : public GradientElement +{ +public: + RadialGradientElement(); + + Length cx() const; + Length cy() const; + Length r() const; + Length fx() const; + Length fy() const; + + std::unique_ptr getPainter(LayoutContext* context) const; + std::unique_ptr clone() const; +}; + +class PatternElement : public PaintElement +{ +public: + PatternElement(); + + Length x() const; + Length y() const; + Length width() const; + Length height() const; + Transform patternTransform() const; + Units patternUnits() const; + Units patternContentUnits() const; + + Rect viewBox() const; + PreserveAspectRatio preserveAspectRatio() const; + std::string href() const; + + std::unique_ptr getPainter(LayoutContext* context) const; + std::unique_ptr clone() const; +}; + +class SolidColorElement : public PaintElement +{ +public: + SolidColorElement(); + + std::unique_ptr getPainter(LayoutContext*) const; + std::unique_ptr clone() const; +}; + +class GradientAttributes +{ +public: + GradientAttributes() = default; + + const Transform& gradientTransform() const { return m_gradientTransform; } + SpreadMethod spreadMethod() const { return m_spreadMethod; } + Units gradientUnits() const { return m_gradientUnits; } + const GradientStops& gradientStops() const { return m_gradientStops; } + + bool hasGradientTransform() const { return m_hasGradientTransform; } + bool hasSpreadMethod() const { return m_hasSpreadMethod; } + bool hasGradientUnits() const { return m_hasGradientUnits; } + bool hasGradientStops() const { return m_hasGradientStops; } + + void setGradientTransform(const Transform& gradientTransform) + { + m_gradientTransform = gradientTransform; + m_hasGradientTransform = true; + } + + void setSpreadMethod(SpreadMethod spreadMethod) + { + m_spreadMethod = spreadMethod; + m_hasSpreadMethod = true; + } + + void setGradientUnits(Units gradientUnits) + { + m_gradientUnits = gradientUnits; + m_hasGradientUnits = true; + } + + void setGradientStops(const GradientStops& gradientStops) + { + m_gradientStops = gradientStops; + m_hasGradientStops = gradientStops.size(); + } + +private: + Transform m_gradientTransform; + SpreadMethod m_spreadMethod{SpreadMethod::Pad}; + Units m_gradientUnits{Units::ObjectBoundingBox}; + GradientStops m_gradientStops; + + bool m_hasGradientTransform{false}; + bool m_hasSpreadMethod{false}; + bool m_hasGradientUnits{false}; + bool m_hasGradientStops{false}; +}; + +class LinearGradientAttributes : public GradientAttributes +{ +public: + LinearGradientAttributes() = default; + + const Length& x1() const { return m_x1; } + const Length& y1() const { return m_y1; } + const Length& x2() const { return m_x2; } + const Length& y2() const { return m_y2; } + + bool hasX1() const { return m_hasX1; } + bool hasY1() const { return m_hasY1; } + bool hasX2() const { return m_hasX2; } + bool hasY2() const { return m_hasY2; } + + void setX1(const Length& x1) + { + m_x1 = x1; + m_hasX1 = true; + } + + void setY1(const Length& y1) + { + m_y1 = y1; + m_hasY1 = true; + } + + void setX2(const Length& x2) + { + m_x2 = x2; + m_hasX2 = true; + } + + void setY2(const Length& y2) + { + m_y2 = y2; + m_hasY2 = true; + } + +private: + Length m_x1; + Length m_y1; + Length m_x2{100, LengthUnits::Percent}; + Length m_y2; + + bool m_hasX1{false}; + bool m_hasY1{false}; + bool m_hasX2{false}; + bool m_hasY2{false}; +}; + +class RadialGradientAttributes : public GradientAttributes +{ +public: + RadialGradientAttributes() = default; + + const Length& cx() const { return m_cx; } + const Length& cy() const { return m_cy; } + const Length& r() const { return m_r; } + const Length& fx() const { return m_fx; } + const Length& fy() const { return m_fy; } + + bool hasCx() const { return m_hasCx; } + bool hasCy() const { return m_hasCy; } + bool hasR() const { return m_hasR; } + bool hasFx() const { return m_hasFx; } + bool hasFy() const { return m_hasFy; } + + void setCx(const Length& cx) + { + m_cx = cx; + m_hasCx = true; + } + + void setCy(const Length& cy) + { + m_cy = cy; + m_hasCy = true; + } + + void setR(const Length& r) + { + m_r = r; + m_hasR = true; + } + + void setFx(const Length& fx) + { + m_fx = fx; + m_hasFx = true; + } + + void setFy(const Length& fy) + { + m_fy = fy; + m_hasFy = true; + } + + +private: + Length m_cx{50, LengthUnits::Percent}; + Length m_cy{50, LengthUnits::Percent}; + Length m_r{50, LengthUnits::Percent}; + Length m_fx; + Length m_fy; + + bool m_hasCx{false}; + bool m_hasCy{false}; + bool m_hasR{false}; + bool m_hasFx{false}; + bool m_hasFy{false}; +}; + +class PatternAttributes +{ +public: + PatternAttributes() = default; + + const Length& x() const { return m_x; } + const Length& y() const { return m_y; } + const Length& width() const { return m_width; } + const Length& height() const { return m_height; } + const Transform& patternTransform() const { return m_patternTransform; } + Units patternUnits() const { return m_patternUnits; } + Units patternContentUnits() const { return m_patternContentUnits; } + const Rect& viewBox() const { return m_viewBox; } + const PreserveAspectRatio& preserveAspectRatio() const { return m_preserveAspectRatio; } + const PatternElement* patternContentElement() const { return m_patternContentElement; } + + bool hasX() const { return m_hasX; } + bool hasY() const { return m_hasY; } + bool hasWidth() const { return m_hasWidth; } + bool hasHeight() const { return m_hasHeight; } + bool hasPatternTransform() const { return m_hasPatternTransform; } + bool hasPatternUnits() const { return m_hasPatternUnits; } + bool hasPatternContentUnits() const { return m_hasPatternContentUnits; } + bool hasViewBox() const { return m_hasViewBox; } + bool hasPreserveAspectRatio() const { return m_hasPreserveAspectRatio; } + bool hasPatternContentElement() const { return m_hasPatternContentElement; } + + void setX(const Length& x) + { + m_x = x; + m_hasX = true; + } + + void setY(const Length& y) + { + m_y = y; + m_hasY = true; + } + + void setWidth(const Length& width) + { + m_width = width; + m_hasWidth = true; + } + + void setHeight(const Length& height) + { + m_height = height; + m_hasHeight = true; + } + + void setPatternTransform(const Transform& patternTransform) + { + m_patternTransform = patternTransform; + m_hasPatternTransform = true; + } + + void setPatternUnits(Units patternUnits) + { + m_patternUnits = patternUnits; + m_hasPatternUnits = true; + } + + void setPatternContentUnits(Units patternContentUnits) + { + m_patternContentUnits = patternContentUnits; + m_hasPatternContentUnits = true; + } + + void setViewBox(const Rect& viewBox) + { + m_viewBox = viewBox; + m_hasViewBox = true; + } + + void setPreserveAspectRatio(const PreserveAspectRatio& preserveAspectRatio) + { + m_preserveAspectRatio = preserveAspectRatio; + m_hasPreserveAspectRatio = true; + } + + void setPatternContentElement(const PatternElement* patternContentElement) + { + m_patternContentElement = patternContentElement; + m_hasPatternContentElement = true; + } + +private: + Length m_x; + Length m_y; + Length m_width; + Length m_height; + Transform m_patternTransform; + Units m_patternUnits{Units::ObjectBoundingBox}; + Units m_patternContentUnits{Units::UserSpaceOnUse}; + Rect m_viewBox{Rect::Invalid}; + PreserveAspectRatio m_preserveAspectRatio; + const PatternElement* m_patternContentElement{nullptr}; + + bool m_hasX{false}; + bool m_hasY{false}; + bool m_hasWidth{false}; + bool m_hasHeight{false}; + bool m_hasPatternTransform{false}; + bool m_hasPatternUnits{false}; + bool m_hasPatternContentUnits{false}; + bool m_hasViewBox{false}; + bool m_hasPreserveAspectRatio{false}; + bool m_hasPatternContentElement{false}; +}; + +} // namespace lunasvg + +#endif // PAINTELEMENT_H diff --git a/source/parser.cpp b/source/parser.cpp new file mode 100644 index 000000000..cda095a4b --- /dev/null +++ b/source/parser.cpp @@ -0,0 +1,2073 @@ +#include "parser.h" +#include "parserutils.h" +#include "layoutcontext.h" + +#include "clippathelement.h" +#include "defselement.h" +#include "gelement.h" +#include "geometryelement.h" +#include "markerelement.h" +#include "maskelement.h" +#include "paintelement.h" +#include "stopelement.h" +#include "svgelement.h" +#include "symbolelement.h" +#include "useelement.h" +#include "styleelement.h" + +namespace lunasvg { + +Length Parser::parseLength(const std::string& string, LengthNegativeValuesMode mode, const Length& defaultValue) +{ + if(string.empty()) + return defaultValue; + + auto ptr = string.data(); + auto end = ptr + string.size(); + + double value; + LengthUnits units; + if(!parseLength(ptr, end, value, units, mode)) + return defaultValue; + + return Length{value, units}; +} + +LengthList Parser::parseLengthList(const std::string& string, LengthNegativeValuesMode mode) +{ + if(string.empty()) + return LengthList{}; + + auto ptr = string.data(); + auto end = ptr + string.size(); + + double value; + LengthUnits units; + + LengthList values; + while(ptr < end) + { + if(!parseLength(ptr, end, value, units, mode)) + break; + values.emplace_back(value, units); + Utils::skipWsComma(ptr, end); + } + + return values; +} + +double Parser::parseNumber(const std::string& string, double defaultValue) +{ + if(string.empty()) + return defaultValue; + + auto ptr = string.data(); + auto end = ptr + string.size(); + + double value; + if(!Utils::parseNumber(ptr, end, value)) + return defaultValue; + + return value; +} + +double Parser::parseNumberPercentage(const std::string& string, double defaultValue) +{ + if(string.empty()) + return defaultValue; + + auto ptr = string.data(); + auto end = ptr + string.size(); + + double value; + if(!Utils::parseNumber(ptr, end, value)) + return defaultValue; + + if(Utils::skipDesc(ptr, end, '%')) + value /= 100.0; + return value < 0.0 ? 0.0 : value > 1.0 ? 1.0 : value; +} + +PointList Parser::parsePointList(const std::string& string) +{ + if(string.empty()) + return PointList{}; + + auto ptr = string.data(); + auto end = ptr + string.size(); + + double x; + double y; + + PointList values; + while(ptr < end) + { + if(!Utils::parseNumber(ptr, end, x) + || !Utils::skipWsComma(ptr, end) + || !Utils::parseNumber(ptr, end, y)) + break; + values.emplace_back(x, y); + Utils::skipWsComma(ptr, end); + } + + return values; +} + +Transform Parser::parseTransform(const std::string& string) +{ + if(string.empty()) + return Transform{}; + + auto ptr = string.data(); + auto end = ptr + string.size(); + + TransformType type; + double values[6]; + int count; + + Transform transform; + while(ptr < end) + { + if(!parseTransform(ptr, end, type, values, count)) + break; + Utils::skipWsComma(ptr, end); + switch(type) { + case TransformType::Matrix: + transform.transform(values[0], values[1], values[2], values[3], values[4], values[5]); + break; + case TransformType::Rotate: + if(count == 1) + transform.rotate(values[0], 0, 0); + else + transform.rotate(values[0], values[1], values[2]); + break; + case TransformType::Scale: + if(count == 1) + transform.scale(values[0], values[0]); + else + transform.scale(values[0], values[1]); + break; + case TransformType::SkewX: + transform.shear(values[0], 0); + break; + case TransformType::SkewY: + transform.shear(0, values[0]); + break; + case TransformType::Translate: + if(count == 1) + transform.translate(values[0], 0); + else + transform.translate(values[0], values[1]); + break; + } + } + + return transform; +} + +Path Parser::parsePath(const std::string& string) +{ + auto ptr = string.data(); + auto end = ptr + string.size(); + if(ptr >= end || !(*ptr == 'M' || *ptr == 'm')) + return Path{}; + + auto command = *ptr++; + double c[6]; + bool f[2]; + + int lastCommand = 0; + Point startPoint; + Point currentPoint; + Point controlPoint; + + Path path; + while(true) + { + Utils::skipWs(ptr, end); + switch(command) { + case 'M': + case 'm': + if(!parseNumberList(ptr, end, c, 2)) + return path; + + if(command == 'm') + { + c[0] += currentPoint.x; + c[1] += currentPoint.y; + } + + path.moveTo(c[0], c[1]); + startPoint.x = currentPoint.x = controlPoint.x = c[0]; + startPoint.y = currentPoint.y = controlPoint.y = c[1]; + command = command == 'm' ? 'l' : 'L'; + break; + case 'L': + case 'l': + if(!parseNumberList(ptr, end, c, 2)) + return path; + + if(command == 'l') + { + c[0] += currentPoint.x; + c[1] += currentPoint.y; + } + + path.lineTo(c[0], c[1]); + currentPoint.x = controlPoint.x = c[0]; + currentPoint.y = controlPoint.y = c[1]; + break; + case 'Q': + case 'q': + if(!parseNumberList(ptr, end, c, 4)) + return path; + + if(command == 'q') + { + c[0] += currentPoint.x; + c[1] += currentPoint.y; + c[2] += currentPoint.x; + c[3] += currentPoint.y; + } + + path.quadTo(currentPoint.x, currentPoint.y, c[0], c[1], c[2], c[3]); + controlPoint.x = c[0]; + controlPoint.y = c[1]; + currentPoint.x = c[2]; + currentPoint.y = c[3]; + break; + case 'C': + case 'c': + if(!parseNumberList(ptr, end, c, 6)) + return path; + + if(command == 'c') + { + c[0] += currentPoint.x; + c[1] += currentPoint.y; + c[2] += currentPoint.x; + c[3] += currentPoint.y; + c[4] += currentPoint.x; + c[5] += currentPoint.y; + } + + path.cubicTo(c[0], c[1], c[2], c[3], c[4], c[5]); + controlPoint.x = c[2]; + controlPoint.y = c[3]; + currentPoint.x = c[4]; + currentPoint.y = c[5]; + break; + case 'T': + case 't': + if(lastCommand != 'Q' && lastCommand != 'q' && lastCommand != 'T' && lastCommand != 't') + controlPoint = currentPoint; + c[0] = 2 * currentPoint.x - controlPoint.x; + c[1] = 2 * currentPoint.y - controlPoint.y; + if(!parseNumberList(ptr, end, c + 2, 2)) + return path; + + if(command == 't') + { + c[2] += currentPoint.x; + c[3] += currentPoint.y; + } + + path.quadTo(currentPoint.x, currentPoint.y, c[0], c[1], c[2], c[3]); + controlPoint.x = c[0]; + controlPoint.y = c[1]; + currentPoint.x = c[2]; + currentPoint.y = c[3]; + break; + case 'S': + case 's': + if(lastCommand != 'C' && lastCommand != 'c' && lastCommand != 'S' && lastCommand != 's') + controlPoint = currentPoint; + c[0] = 2 * currentPoint.x - controlPoint.x; + c[1] = 2 * currentPoint.y - controlPoint.y; + if(!parseNumberList(ptr, end, c + 2, 4)) + return path; + + if(command == 's') + { + c[2] += currentPoint.x; + c[3] += currentPoint.y; + c[4] += currentPoint.x; + c[5] += currentPoint.y; + } + + path.cubicTo(c[0], c[1], c[2], c[3], c[4], c[5]); + controlPoint.x = c[2]; + controlPoint.y = c[3]; + currentPoint.x = c[4]; + currentPoint.y = c[5]; + break; + case 'H': + case 'h': + if(!parseNumberList(ptr, end, c, 1)) + return path; + + if(command == 'h') + c[0] += currentPoint.x; + + path.lineTo(c[0], currentPoint.y); + currentPoint.x = controlPoint.x = c[0]; + break; + case 'V': + case 'v': + if(!parseNumberList(ptr, end, c + 1, 1)) + return path; + + if(command == 'v') + c[1] += currentPoint.y; + + path.lineTo(currentPoint.x, c[1]); + currentPoint.y = controlPoint.y = c[1]; + break; + case 'A': + case 'a': + if(!parseNumberList(ptr, end, c, 3) + || !parseArcFlag(ptr, end, f[0]) + || !parseArcFlag(ptr, end, f[1]) + || !parseNumberList(ptr, end, c + 3, 2)) + return path; + + if(command == 'a') + { + c[3] += currentPoint.x; + c[4] += currentPoint.y; + } + + path.arcTo(currentPoint.x, currentPoint.y, c[0], c[1], c[2], f[0], f[1], c[3], c[4]); + currentPoint.x = controlPoint.x = c[3]; + currentPoint.y = controlPoint.y = c[4]; + break; + case 'Z': + case 'z': + path.close(); + currentPoint.x = controlPoint.x = startPoint.x; + currentPoint.y = controlPoint.y = startPoint.y; + break; + default: + return path; + } + + Utils::skipWsComma(ptr, end); + if(ptr >= end) + break; + + if(IS_ALPHA(*ptr)) + { + lastCommand = command; + command = *ptr++; + } + } + + return path; +} + +std::string Parser::parseUrl(const std::string& string) +{ + if(string.empty()) + return std::string{}; + + auto ptr = string.data(); + auto end = ptr + string.size(); + + std::string value; + parseUrlFragment(ptr, end, value); + return value; +} + +std::string Parser::parseHref(const std::string& string) +{ + if(string.size() > 1 && string.front() == '#') + return string.substr(1); + + return std::string{}; +} + +Rect Parser::parseViewBox(const std::string& string) +{ + if(string.empty()) + return Rect::Invalid; + + auto ptr = string.data(); + auto end = ptr + string.size(); + + double x; + double y; + double w; + double h; + if(!Utils::parseNumber(ptr, end, x) + || !Utils::skipWsComma(ptr, end) + || !Utils::parseNumber(ptr, end, y) + || !Utils::skipWsComma(ptr, end) + || !Utils::parseNumber(ptr, end, w) + || !Utils::skipWsComma(ptr, end) + || !Utils::parseNumber(ptr, end, h)) + return Rect::Invalid; + + if(w < 0.0 || h < 0.0) + return Rect::Invalid; + + return Rect{x, y, w, h}; +} + +PreserveAspectRatio Parser::parsePreserveAspectRatio(const std::string& string) +{ + if(string.empty()) + return PreserveAspectRatio{}; + + auto ptr = string.data(); + auto end = ptr + string.size(); + + Align align{Align::xMidYMid}; + MeetOrSlice scale{MeetOrSlice::Meet}; + if(Utils::skipDesc(ptr, end, "none")) + align = Align::None; + else if(Utils::skipDesc(ptr, end, "xMinYMin")) + align = Align::xMinYMin; + else if(Utils::skipDesc(ptr, end, "xMidYMin")) + align = Align::xMidYMin; + else if(Utils::skipDesc(ptr, end, "xMaxYMin")) + align = Align::xMaxYMin; + else if(Utils::skipDesc(ptr, end, "xMinYMid")) + align = Align::xMinYMid; + else if(Utils::skipDesc(ptr, end, "xMidYMid")) + align = Align::xMidYMid; + else if(Utils::skipDesc(ptr, end, "xMaxYMid")) + align = Align::xMaxYMid; + else if(Utils::skipDesc(ptr, end, "xMinYMax")) + align = Align::xMinYMax; + else if(Utils::skipDesc(ptr, end, "xMidYMax")) + align = Align::xMidYMax; + else if(Utils::skipDesc(ptr, end, "xMaxYMax")) + align = Align::xMaxYMax; + else + return PreserveAspectRatio{}; + + Utils::skipWs(ptr, end); + if(Utils::skipDesc(ptr, end, "slice")) + scale = MeetOrSlice::Slice; + else + scale = MeetOrSlice::Meet; + + return PreserveAspectRatio{align, scale}; +} + +static const double pi = 3.14159265358979323846; + +Angle Parser::parseAngle(const std::string& string) +{ + if(string.empty()) + return Angle{}; + + auto ptr = string.data(); + auto end = ptr + string.size(); + + if(Utils::skipDesc(ptr, end, "auto")) + return MarkerOrient::Auto; + + double value; + if(!Utils::parseNumber(ptr, end, value)) + return Angle{}; + + if(Utils::skipDesc(ptr, end, "rad")) + value *= 180.0 / pi; + else if(Utils::skipDesc(ptr, end, "grad")) + value *= 360.0 / 400.0; + + return Angle{value, MarkerOrient::Angle}; +} + +MarkerUnits Parser::parseMarkerUnits(const std::string& string) +{ + if(string.empty()) + return MarkerUnits::StrokeWidth; + + if(string.compare("userSpaceOnUse") == 0) + return MarkerUnits::UserSpaceOnUse; + return MarkerUnits::StrokeWidth; +} + +SpreadMethod Parser::parseSpreadMethod(const std::string& string) +{ + if(string.empty()) + return SpreadMethod::Pad; + + if(string.compare("repeat") == 0) + return SpreadMethod::Repeat; + if(string.compare("reflect") == 0) + return SpreadMethod::Reflect; + return SpreadMethod::Pad; +} + +Units Parser::parseUnits(const std::string& string, Units defaultValue) +{ + if(string.empty()) + return defaultValue; + + if(string.compare("userSpaceOnUse") == 0) + return Units::UserSpaceOnUse; + if(string.compare("objectBoundingBox") == 0) + return Units::ObjectBoundingBox; + return defaultValue; +} + +static const std::map colormap = { + {"aliceblue", 0xF0F8FF}, + {"antiquewhite", 0xFAEBD7}, + {"aqua", 0x00FFFF}, + {"aquamarine", 0x7FFFD4}, + {"azure", 0xF0FFFF}, + {"beige", 0xF5F5DC}, + {"bisque", 0xFFE4C4}, + {"black", 0x000000}, + {"blanchedalmond", 0xFFEBCD}, + {"blue", 0x0000FF}, + {"blueviolet", 0x8A2BE2}, + {"brown", 0xA52A2A}, + {"burlywood", 0xDEB887}, + {"cadetblue", 0x5F9EA0}, + {"chartreuse", 0x7FFF00}, + {"chocolate", 0xD2691E}, + {"coral", 0xFF7F50}, + {"cornflowerblue", 0x6495ED}, + {"cornsilk", 0xFFF8DC}, + {"crimson", 0xDC143C}, + {"cyan", 0x00FFFF}, + {"darkblue", 0x00008B}, + {"darkcyan", 0x008B8B}, + {"darkgoldenrod", 0xB8860B}, + {"darkgray", 0xA9A9A9}, + {"darkgreen", 0x006400}, + {"darkgrey", 0xA9A9A9}, + {"darkkhaki", 0xBDB76B}, + {"darkmagenta", 0x8B008B}, + {"darkolivegreen", 0x556B2F}, + {"darkorange", 0xFF8C00}, + {"darkorchid", 0x9932CC}, + {"darkred", 0x8B0000}, + {"darksalmon", 0xE9967A}, + {"darkseagreen", 0x8FBC8F}, + {"darkslateblue", 0x483D8B}, + {"darkslategray", 0x2F4F4F}, + {"darkslategrey", 0x2F4F4F}, + {"darkturquoise", 0x00CED1}, + {"darkviolet", 0x9400D3}, + {"deeppink", 0xFF1493}, + {"deepskyblue", 0x00BFFF}, + {"dimgray", 0x696969}, + {"dimgrey", 0x696969}, + {"dodgerblue", 0x1E90FF}, + {"firebrick", 0xB22222}, + {"floralwhite", 0xFFFAF0}, + {"forestgreen", 0x228B22}, + {"fuchsia", 0xFF00FF}, + {"gainsboro", 0xDCDCDC}, + {"ghostwhite", 0xF8F8FF}, + {"gold", 0xFFD700}, + {"goldenrod", 0xDAA520}, + {"gray", 0x808080}, + {"green", 0x008000}, + {"greenyellow", 0xADFF2F}, + {"grey", 0x808080}, + {"honeydew", 0xF0FFF0}, + {"hotpink", 0xFF69B4}, + {"indianred", 0xCD5C5C}, + {"indigo", 0x4B0082}, + {"ivory", 0xFFFFF0}, + {"khaki", 0xF0E68C}, + {"lavender", 0xE6E6FA}, + {"lavenderblush", 0xFFF0F5}, + {"lawngreen", 0x7CFC00}, + {"lemonchiffon", 0xFFFACD}, + {"lightblue", 0xADD8E6}, + {"lightcoral", 0xF08080}, + {"lightcyan", 0xE0FFFF}, + {"lightgoldenrodyellow", 0xFAFAD2}, + {"lightgray", 0xD3D3D3}, + {"lightgreen", 0x90EE90}, + {"lightgrey", 0xD3D3D3}, + {"lightpink", 0xFFB6C1}, + {"lightsalmon", 0xFFA07A}, + {"lightseagreen", 0x20B2AA}, + {"lightskyblue", 0x87CEFA}, + {"lightslategray", 0x778899}, + {"lightslategrey", 0x778899}, + {"lightsteelblue", 0xB0C4DE}, + {"lightyellow", 0xFFFFE0}, + {"lime", 0x00FF00}, + {"limegreen", 0x32CD32}, + {"linen", 0xFAF0E6}, + {"magenta", 0xFF00FF}, + {"maroon", 0x800000}, + {"mediumaquamarine", 0x66CDAA}, + {"mediumblue", 0x0000CD}, + {"mediumorchid", 0xBA55D3}, + {"mediumpurple", 0x9370DB}, + {"mediumseagreen", 0x3CB371}, + {"mediumslateblue", 0x7B68EE}, + {"mediumspringgreen", 0x00FA9A}, + {"mediumturquoise", 0x48D1CC}, + {"mediumvioletred", 0xC71585}, + {"midnightblue", 0x191970}, + {"mintcream", 0xF5FFFA}, + {"mistyrose", 0xFFE4E1}, + {"moccasin", 0xFFE4B5}, + {"navajowhite", 0xFFDEAD}, + {"navy", 0x000080}, + {"oldlace", 0xFDF5E6}, + {"olive", 0x808000}, + {"olivedrab", 0x6B8E23}, + {"orange", 0xFFA500}, + {"orangered", 0xFF4500}, + {"orchid", 0xDA70D6}, + {"palegoldenrod", 0xEEE8AA}, + {"palegreen", 0x98FB98}, + {"paleturquoise", 0xAFEEEE}, + {"palevioletred", 0xDB7093}, + {"papayawhip", 0xFFEFD5}, + {"peachpuff", 0xFFDAB9}, + {"peru", 0xCD853F}, + {"pink", 0xFFC0CB}, + {"plum", 0xDDA0DD}, + {"powderblue", 0xB0E0E6}, + {"purple", 0x800080}, + {"rebeccapurple", 0x663399}, + {"red", 0xFF0000}, + {"rosybrown", 0xBC8F8F}, + {"royalblue", 0x4169E1}, + {"saddlebrown", 0x8B4513}, + {"salmon", 0xFA8072}, + {"sandybrown", 0xF4A460}, + {"seagreen", 0x2E8B57}, + {"seashell", 0xFFF5EE}, + {"sienna", 0xA0522D}, + {"silver", 0xC0C0C0}, + {"skyblue", 0x87CEEB}, + {"slateblue", 0x6A5ACD}, + {"slategray", 0x708090}, + {"slategrey", 0x708090}, + {"snow", 0xFFFAFA}, + {"springgreen", 0x00FF7F}, + {"steelblue", 0x4682B4}, + {"tan", 0xD2B48C}, + {"teal", 0x008080}, + {"thistle", 0xD8BFD8}, + {"tomato", 0xFF6347}, + {"turquoise", 0x40E0D0}, + {"violet", 0xEE82EE}, + {"wheat", 0xF5DEB3}, + {"white", 0xFFFFFF}, + {"whitesmoke", 0xF5F5F5}, + {"yellow", 0xFFFF00}, + {"yellowgreen", 0x9ACD32} +}; + +Color Parser::parseColor(const std::string& string, const StyledElement* element, const Color& defaultValue) +{ + if(string.empty()) + return defaultValue; + + auto ptr = string.data(); + auto end = ptr + string.size(); + + if(Utils::skipDesc(ptr, end, '#')) + { + auto start = ptr; + unsigned int value; + if(!Utils::parseInteger(ptr, end, value, 16)) + return defaultValue; + + auto n = ptr - start; + if(n != 3 && n != 6) + return defaultValue; + + if(n == 3) + { + value = ((value&0xf00) << 8) | ((value&0x0f0) << 4) | (value&0x00f); + value |= value << 4; + } + + auto r = (value&0xff0000)>>16; + auto g = (value&0x00ff00)>>8; + auto b = (value&0x0000ff)>>0; + + return Color{r / 255.0, g / 255.0, b / 255.0}; + } + + if(Utils::skipDesc(ptr, end, "rgb(")) + { + double r, g, b; + if(!Utils::skipWs(ptr, end) + || !parseColorComponent(ptr, end, r) + || !Utils::skipWsComma(ptr, end) + || !parseColorComponent(ptr, end, g) + || !Utils::skipWsComma(ptr, end) + || !parseColorComponent(ptr, end, b) + || !Utils::skipWs(ptr, end) + || !Utils::skipDesc(ptr, end, ')')) + return defaultValue; + + return Color{r / 255.0, g / 255.0, b / 255.0}; + } + + if(Utils::skipDesc(ptr, end, "none")) + return Color::Transparent; + + if(Utils::skipDesc(ptr, end, "currentColor")) + return element->color(); + + auto it = colormap.find(string); + if(it == colormap.end()) + return defaultValue; + + auto value = it->second; + auto r = (value&0xff0000)>>16; + auto g = (value&0x00ff00)>>8; + auto b = (value&0x0000ff)>>0; + + return Color{r / 255.0, g / 255.0, b / 255.0}; +} + +Paint Parser::parsePaint(const std::string& string, const StyledElement* element, const Color& defaultValue) +{ + if(string.empty()) + return defaultValue; + + auto ptr = string.data(); + auto end = ptr + string.size(); + + std::string ref; + if(!parseUrlFragment(ptr, end, ref)) + return parseColor(string, element, defaultValue); + + std::string fallback{ptr, end}; + if(fallback.empty()) + return Paint{ref, Color::Transparent}; + return Paint{ref, parseColor(fallback, element, defaultValue)}; +} + +WindRule Parser::parseWindRule(const std::string& string) +{ + if(string.empty()) + return WindRule::NonZero; + + if(string.compare("evenodd") == 0) + return WindRule::EvenOdd; + return WindRule::NonZero; +} + +LineCap Parser::parseLineCap(const std::string& string) +{ + if(string.empty()) + return LineCap::Butt; + + if(string.compare("round") == 0) + return LineCap::Round; + if(string.compare("square") == 0) + return LineCap::Square; + return LineCap::Butt; +} + +LineJoin Parser::parseLineJoin(const std::string& string) +{ + if(string.empty()) + return LineJoin::Miter; + + if(string.compare("bevel") == 0) + return LineJoin::Bevel; + if(string.compare("round") == 0) + return LineJoin::Round; + return LineJoin::Miter; +} + +Display Parser::parseDisplay(const std::string& string) +{ + if(string.empty()) + return Display::Inline; + + if(string.compare("none") == 0) + return Display::None; + return Display::Inline; +} + +Visibility Parser::parseVisibility(const std::string& string) +{ + if(string.empty()) + return Visibility::Visible; + + if(string.compare("visible") == 0) + return Visibility::Visible; + return Visibility::Hidden; +} + +Overflow Parser::parseOverflow(const std::string& string, Overflow defaultValue) +{ + if(string.empty()) + return defaultValue; + + if(string.compare("visible") == 0) + return Overflow::Visible; + if(string.compare("hidden") == 0) + return Overflow::Hidden; + return defaultValue; +} + +bool Parser::parseLength(const char*& ptr, const char* end, double& value, LengthUnits& units, LengthNegativeValuesMode mode) +{ + if(!Utils::parseNumber(ptr, end, value)) + return false; + + if(mode == ForbidNegativeLengths && value < 0.0) + return false; + + char c[2] = {0, 0}; + if(ptr + 0 < end) c[0] = ptr[0]; + if(ptr + 1 < end) c[1] = ptr[1]; + + switch(c[0]) { + case '%': + units = LengthUnits::Percent; + ptr += 1; + break; + case 'p': + if(c[1] == 'x') + units = LengthUnits::Px; + else if(c[1] == 'c') + units = LengthUnits::Pc; + else if(ptr[1] == 't') + units = LengthUnits::Pt; + else + return false; + ptr += 2; + break; + case 'i': + if(c[1] == 'n') + units = LengthUnits::In; + else + return false; + ptr += 2; + break; + case 'c': + if(c[1] == 'm') + units = LengthUnits::Cm; + else + return false; + ptr += 2; + break; + case 'm': + if(c[1] == 'm') + units = LengthUnits::Mm; + else + return false; + ptr += 2; + break; + case 'e': + if(c[1] == 'm') + units = LengthUnits::Em; + else if(c[1] == 'x') + units = LengthUnits::Ex; + else + return false; + ptr += 2; + break; + default: + units = LengthUnits::Number; + break; + } + + return true; +} + +bool Parser::parseNumberList(const char*& ptr, const char* end, double* values, int count) +{ + for(int i = 0;i < count;i++) + { + if(!Utils::parseNumber(ptr, end, values[i])) + return false; + Utils::skipWsComma(ptr, end); + } + + return true; +} + +bool Parser::parseArcFlag(const char*& ptr, const char* end, bool& flag) +{ + if(ptr < end && *ptr == '0') + flag = false; + else if(ptr < end && *ptr == '1') + flag = true; + else + return false; + + ++ptr; + Utils::skipWsComma(ptr, end); + return true; +} + +bool Parser::parseColorComponent(const char*& ptr, const char* end, double& value) +{ + if(!Utils::parseNumber(ptr, end, value)) + return false; + + if(Utils::skipDesc(ptr, end, '%')) + value *= 2.55; + + value = (value < 0.0) ? 0.0 : (value > 255.0) ? 255.0 : std::round(value); + return true; +} + +bool Parser::parseUrlFragment(const char*& ptr, const char* end, std::string& ref) +{ + if(!Utils::skipDesc(ptr, end, "url(") + || !Utils::skipWs(ptr, end)) { + return false; + } + + switch(*ptr) { + case '\'': + case '"': { + auto delim = *ptr; + ++ptr; // delim + if(!Utils::skipWs(ptr, end) || *ptr != '#') + return false; + ++ptr; // # + if(!Utils::readUntil(ptr, end, delim, ref)) + return false; + ++ptr; // delim + break; + } + + case '#': + ++ptr; // # + Utils::readUntil(ptr, end, ')', ref); + break; + default: + return false; + } + + if(ptr >= end || *ptr != ')') + return false; + + ++ptr; // ) + Utils::skipWs(ptr, end); + return true; +} + +bool Parser::parseTransform(const char*& ptr, const char* end, TransformType& type, double* values, int& count) +{ + int required = 0; + int optional = 0; + if(Utils::skipDesc(ptr, end, "matrix")) + { + type = TransformType::Matrix; + required = 6; + optional = 0; + } + else if(Utils::skipDesc(ptr, end, "rotate")) + { + type = TransformType::Rotate; + required = 1; + optional = 2; + } + else if(Utils::skipDesc(ptr, end, "scale")) + { + type = TransformType::Scale; + required = 1; + optional = 1; + } + else if(Utils::skipDesc(ptr, end, "skewX")) + { + type = TransformType::SkewX; + required = 1; + optional = 0; + } + else if(Utils::skipDesc(ptr, end, "skewY")) + { + type = TransformType::SkewY; + required = 1; + optional = 0; + } + else if(Utils::skipDesc(ptr, end, "translate")) + { + type = TransformType::Translate; + required = 1; + optional = 1; + } + else + { + return false; + } + + Utils::skipWs(ptr, end); + if(ptr >= end || *ptr != '(') + return false; + ++ptr; + + int maxCount = required + optional; + count = 0; + Utils::skipWs(ptr, end); + while(count < maxCount) + { + if(!Utils::parseNumber(ptr, end, values[count])) + break; + ++count; + Utils::skipWsComma(ptr, end); + } + + if(ptr >= end || *ptr != ')' || !(count == required || count == maxCount)) + return false; + ++ptr; + + return true; +} + +static const std::map elementmap = { + {"circle", ElementId::Circle}, + {"clipPath", ElementId::ClipPath}, + {"defs", ElementId::Defs}, + {"ellipse", ElementId::Ellipse}, + {"g", ElementId::G}, + {"line", ElementId::Line}, + {"linearGradient", ElementId::LinearGradient}, + {"marker", ElementId::Marker}, + {"mask", ElementId::Mask}, + {"path", ElementId::Path}, + {"pattern", ElementId::Pattern}, + {"polygon", ElementId::Polygon}, + {"polyline", ElementId::Polyline}, + {"radialGradient", ElementId::RadialGradient}, + {"rect", ElementId::Rect}, + {"stop", ElementId::Stop}, + {"style", ElementId::Style}, + {"solidColor", ElementId::SolidColor}, + {"svg", ElementId::Svg}, + {"symbol", ElementId::Symbol}, + {"use", ElementId::Use} +}; + +static const std::map propertymap = { + {"class", PropertyId::Class}, + {"clipPathUnits", PropertyId::ClipPathUnits}, + {"cx", PropertyId::Cx}, + {"cy", PropertyId::Cy}, + {"d", PropertyId::D}, + {"fx", PropertyId::Fx}, + {"fy", PropertyId::Fy}, + {"gradientTransform", PropertyId::GradientTransform}, + {"gradientUnits", PropertyId::GradientUnits}, + {"height", PropertyId::Height}, + {"href", PropertyId::Href}, + {"id", PropertyId::Id}, + {"markerHeight", PropertyId::MarkerHeight}, + {"markerUnits", PropertyId::MarkerUnits}, + {"markerWidth", PropertyId::MarkerWidth}, + {"maskContentUnits", PropertyId::MaskContentUnits}, + {"maskUnits", PropertyId::MaskUnits}, + {"offset", PropertyId::Offset}, + {"orient", PropertyId::Orient}, + {"patternContentUnits", PropertyId::PatternContentUnits}, + {"patternTransform", PropertyId::PatternTransform}, + {"patternUnits", PropertyId::PatternUnits}, + {"points", PropertyId::Points}, + {"preserveAspectRatio", PropertyId::PreserveAspectRatio}, + {"r", PropertyId::R}, + {"refX", PropertyId::RefX}, + {"refY", PropertyId::RefY}, + {"rx", PropertyId::Rx}, + {"ry", PropertyId::Ry}, + {"spreadMethod", PropertyId::SpreadMethod}, + {"style", PropertyId::Style}, + {"transform", PropertyId::Transform}, + {"viewBox", PropertyId::ViewBox}, + {"width", PropertyId::Width}, + {"x", PropertyId::X}, + {"x1", PropertyId::X1}, + {"x2", PropertyId::X2}, + {"xlink:href", PropertyId::Href}, + {"y", PropertyId::Y}, + {"y1", PropertyId::Y1}, + {"y2", PropertyId::Y2} +}; + +static const std::map csspropertymap = { + {"clip-path", PropertyId::Clip_Path}, + {"clip-rule", PropertyId::Clip_Rule}, + {"color", PropertyId::Color}, + {"display", PropertyId::Display}, + {"fill", PropertyId::Fill}, + {"fill-opacity", PropertyId::Fill_Opacity}, + {"fill-rule", PropertyId::Fill_Rule}, + {"marker-end", PropertyId::Marker_End}, + {"marker-mid", PropertyId::Marker_Mid}, + {"marker-start", PropertyId::Marker_Start}, + {"mask", PropertyId::Mask}, + {"opacity", PropertyId::Opacity}, + {"overflow", PropertyId::Overflow}, + {"solid-color", PropertyId::Solid_Color}, + {"solid-opacity", PropertyId::Solid_Opacity}, + {"stop-color", PropertyId::Stop_Color}, + {"stop-opacity", PropertyId::Stop_Opacity}, + {"stroke", PropertyId::Stroke}, + {"stroke-dasharray", PropertyId::Stroke_Dasharray}, + {"stroke-dashoffset", PropertyId::Stroke_Dashoffset}, + {"stroke-linecap", PropertyId::Stroke_Linecap}, + {"stroke-linejoin", PropertyId::Stroke_Linejoin}, + {"stroke-miterlimit", PropertyId::Stroke_Miterlimit}, + {"stroke-opacity", PropertyId::Stroke_Opacity}, + {"stroke-width", PropertyId::Stroke_Width}, + {"visibility", PropertyId::Visibility} +}; + +static inline ElementId elementId(const std::string& name) +{ + auto it = elementmap.find(name); + if(it == elementmap.end()) + return ElementId::Unknown; + + return it->second; +} + +static inline PropertyId cssPropertyId(const std::string& name) +{ + auto it = csspropertymap.find(name); + if(it == csspropertymap.end()) + return PropertyId::Unknown; + + return it->second; +} + +static inline PropertyId propertyId(const std::string& name) +{ + auto it = propertymap.find(name); + if(it == propertymap.end()) + return cssPropertyId(name); + + return it->second; +} + +#define IS_STARTNAMECHAR(c) (IS_ALPHA(c) || (c) == '_' || (c) == ':') +#define IS_NAMECHAR(c) (IS_STARTNAMECHAR(c) || IS_NUM(c) || (c) == '-' || (c) == '.') +static inline bool readIdentifier(const char*& ptr, const char* end, std::string& value) +{ + if(ptr >= end || !IS_STARTNAMECHAR(*ptr)) + return false; + + auto start = ptr; + ++ptr; + while(ptr < end && IS_NAMECHAR(*ptr)) + ++ptr; + + value.assign(start, ptr); + return true; +} + +#define IS_CSS_STARTNAMECHAR(c) (IS_ALPHA(c) || (c) == '_') +#define IS_CSS_NAMECHAR(c) (IS_CSS_STARTNAMECHAR(c) || IS_NUM(c) || (c) == '-') +static inline bool readCSSIdentifier(const char*& ptr, const char* end, std::string& value) +{ + if(ptr >= end || !IS_CSS_STARTNAMECHAR(*ptr)) + return false; + + auto start = ptr; + ++ptr; + while(ptr < end && IS_CSS_NAMECHAR(*ptr)) + ++ptr; + + value.assign(start, ptr); + return true; +} + +bool CSSParser::parseMore(const std::string& value) +{ + auto ptr = value.data(); + auto end = ptr + value.size(); + + while(ptr < end) + { + Utils::skipWs(ptr, end); + if(Utils::skipDesc(ptr, end, '@')) + { + if(!parseAtRule(ptr, end)) + return false; + continue; + } + + Rule rule; + if(!parseRule(ptr, end, rule)) + return false; + m_rules.push_back(rule); + } + + return true; +} + +bool CSSParser::parseAtRule(const char*& ptr, const char* end) const +{ + int depth = 0; + while(ptr < end) + { + auto ch = *ptr; + ++ptr; + if(ch == ';' && depth == 0) + break; + if(ch == '{') ++depth; + else if(ch == '}' && depth > 0) + { + if(depth == 1) + break; + --depth; + } + } + + return true; +} + +bool CSSParser::parseRule(const char*& ptr, const char* end, Rule& rule) const +{ + if(!parseSelectors(ptr, end, rule.selectors)) + return false; + + if(!parseDeclarations(ptr, end, rule.declarations)) + return false; + + return true; +} + +bool CSSParser::parseSelectors(const char*& ptr, const char* end, SelectorList& selectors) const +{ + Selector selector; + if(!parseSelector(ptr, end, selector)) + return false; + selectors.push_back(selector); + + while(Utils::skipDesc(ptr, end, ',')) + { + Utils::skipWs(ptr, end); + Selector selector; + if(!parseSelector(ptr, end, selector)) + return false; + selectors.push_back(selector); + } + + return true; +} + +bool CSSParser::parseDeclarations(const char*& ptr, const char* end, PropertyList& declarations) const +{ + if(!Utils::skipDesc(ptr, end, '{')) + return false; + + std::string name; + std::string value; + Utils::skipWs(ptr, end); + do { + if(!readCSSIdentifier(ptr, end, name)) + return false; + Utils::skipWs(ptr, end); + if(!Utils::skipDesc(ptr, end, ':')) + return false; + Utils::skipWs(ptr, end); + auto start = ptr; + while(ptr < end && !(*ptr == '!' || *ptr == ';' || *ptr == '}')) + ++ptr; + value.assign(start, Utils::rtrim(start, ptr)); + int specificity = 0x10; + if(Utils::skipDesc(ptr, end, '!')) + { + if(!Utils::skipDesc(ptr, end, "important")) + return false; + specificity = 0x1000; + } + + auto id = cssPropertyId(name); + if(id != PropertyId::Unknown) + declarations.set(id, value, specificity); + Utils::skipWsDelimiter(ptr, end, ';'); + } while(ptr < end && *ptr != '}'); + + return Utils::skipDesc(ptr, end, '}'); +} + +#define IS_SELECTOR_STARTNAMECHAR(c) (IS_CSS_STARTNAMECHAR(c) || (c) == '*' || (c) == '#' || (c) == '.' || (c) == '[' || (c) == ':') +bool CSSParser::parseSelector(const char*& ptr, const char* end, Selector& selector) const +{ + do { + SimpleSelector simpleSelector; + if(!parseSimpleSelector(ptr, end, simpleSelector)) + return false; + + selector.specificity += (simpleSelector.id == ElementId::Star) ? 0x0 : 0x1; + for(auto& attributeSelector : simpleSelector.attributeSelectors) + selector.specificity += (attributeSelector.id == PropertyId::Id) ? 0x10000 : 0x100; + + selector.simpleSelectors.push_back(simpleSelector); + Utils::skipWs(ptr, end); + } while(ptr < end && IS_SELECTOR_STARTNAMECHAR(*ptr)); + + return true; +} + +bool CSSParser::parseSimpleSelector(const char*& ptr, const char* end, SimpleSelector& simpleSelector) const +{ + std::string name; + if(Utils::skipDesc(ptr, end, '*')) + simpleSelector.id = ElementId::Star; + else if(readCSSIdentifier(ptr, end, name)) + simpleSelector.id = elementId(name); + + while(ptr < end) + { + if(Utils::skipDesc(ptr, end, '#')) + { + AttributeSelector a; + a.id = PropertyId::Id; + a.matchType = AttributeSelector::MatchType::Equal; + if(!readCSSIdentifier(ptr, end, a.value)) + return false; + simpleSelector.attributeSelectors.push_back(a); + continue; + } + + if(Utils::skipDesc(ptr, end, '.')) + { + AttributeSelector a; + a.id = PropertyId::Class; + a.matchType = AttributeSelector::MatchType::Includes; + if(!readCSSIdentifier(ptr, end, a.value)) + return false; + simpleSelector.attributeSelectors.push_back(a); + continue; + } + + if(Utils::skipDesc(ptr, end, '[')) + { + Utils::skipWs(ptr, end); + if(!readCSSIdentifier(ptr, end, name)) + return false; + AttributeSelector a; + a.id = propertyId(name); + if(Utils::skipDesc(ptr, end, '=')) + a.matchType = AttributeSelector::MatchType::Equal; + else if(Utils::skipDesc(ptr, end, "~=")) + a.matchType = AttributeSelector::MatchType::Includes; + else if(Utils::skipDesc(ptr, end, "|=")) + a.matchType = AttributeSelector::MatchType::DashMatch; + else if(Utils::skipDesc(ptr, end, "^=")) + a.matchType = AttributeSelector::MatchType::StartsWith; + else if(Utils::skipDesc(ptr, end, "$=")) + a.matchType = AttributeSelector::MatchType::EndsWith; + else if(Utils::skipDesc(ptr, end, "*=")) + a.matchType = AttributeSelector::MatchType::Contains; + if(a.matchType != AttributeSelector::MatchType::None) + { + Utils::skipWs(ptr, end); + if(!readCSSIdentifier(ptr, end, a.value)) + { + if(ptr >= end || !(*ptr == '\"' || *ptr == '\'')) + return false; + + auto quote = *ptr; + ++ptr; + if(!Utils::readUntil(ptr, end, quote, a.value)) + return false; + ++ptr; + } + } + + Utils::skipWs(ptr, end); + if(!Utils::skipDesc(ptr, end, ']')) + return false; + simpleSelector.attributeSelectors.push_back(a); + continue; + } + + if(Utils::skipDesc(ptr, end, ':')) + { + if(!readCSSIdentifier(ptr, end, name)) + return false; + PseudoClass pseudo; + if(name.compare("empty") == 0) + pseudo.type = PseudoClass::Type::Empty; + else if(name.compare("root") == 0) + pseudo.type = PseudoClass::Type::Root; + else if(name.compare("not") == 0) + pseudo.type = PseudoClass::Type::Not; + else if(name.compare("first-child") == 0) + pseudo.type = PseudoClass::Type::FirstChild; + else if(name.compare("last-child") == 0) + pseudo.type = PseudoClass::Type::LastChild; + else if(name.compare("only-child") == 0) + pseudo.type = PseudoClass::Type::OnlyChild; + else if(name.compare("first-of-type") == 0) + pseudo.type = PseudoClass::Type::FirstOfType; + else if(name.compare("last-of-type") == 0) + pseudo.type = PseudoClass::Type::LastOfType; + else if(name.compare("only-of-type") == 0) + pseudo.type = PseudoClass::Type::OnlyOfType; + if(pseudo.type == PseudoClass::Type::Not) + { + if(!Utils::skipDesc(ptr, end, '(')) + return false; + + Utils::skipWs(ptr, end); + if(!parseSelectors(ptr, end, pseudo.notSelectors)) + return false; + + Utils::skipWs(ptr, end); + if(!Utils::skipDesc(ptr, end, ')')) + return false; + } + + simpleSelector.pseudoClasses.push_back(pseudo); + continue; + } + + break; + } + + Utils::skipWs(ptr, end); + if(Utils::skipDesc(ptr, end, '>')) + simpleSelector.combinator = SimpleSelector::Combinator::Child; + else if(Utils::skipDesc(ptr, end, '+')) + simpleSelector.combinator = SimpleSelector::Combinator::DirectAdjacent; + else if(Utils::skipDesc(ptr, end, '~')) + simpleSelector.combinator = SimpleSelector::Combinator::InDirectAdjacent; + + return true; +} + +RuleMatchContext::RuleMatchContext(const std::vector& rules) +{ + for(auto& rule : rules) + for(auto& selector : rule.selectors) + m_selectors.emplace(selector.specificity, std::make_pair(&selector, &rule.declarations)); +} + +std::vector RuleMatchContext::match(const Element* element) const +{ + std::vector declarations; + auto it = m_selectors.begin(); + auto end = m_selectors.end(); + for(;it != end;++it) + { + auto& value = it->second; + if(!selectorMatch(std::get<0>(value), element)) + continue; + declarations.push_back(std::get<1>(value)); + } + + return declarations; +} + +bool RuleMatchContext::selectorMatch(const Selector* selector, const Element* element) const +{ + if(selector->simpleSelectors.empty()) + return false; + + if(selector->simpleSelectors.size() == 1) + return simpleSelectorMatch(selector->simpleSelectors.front(), element); + + auto it = selector->simpleSelectors.rbegin(); + auto end = selector->simpleSelectors.rend(); + if(!simpleSelectorMatch(*it, element)) + return false; + ++it; + + while(it != end) + { + switch(it->combinator) { + case SimpleSelector::Combinator::Child: + case SimpleSelector::Combinator::Descendant: + element = element->parent; + break; + case SimpleSelector::Combinator::DirectAdjacent: + case SimpleSelector::Combinator::InDirectAdjacent: + element = element->previousSibling(); + break; + } + + if(element == nullptr) + return false; + + auto match = simpleSelectorMatch(*it, element); + if(!match && (it->combinator != SimpleSelector::Combinator::Descendant && it->combinator != SimpleSelector::Combinator::InDirectAdjacent)) + return false; + + if(match || (it->combinator != SimpleSelector::Combinator::Descendant && it->combinator != SimpleSelector::Combinator::InDirectAdjacent)) + ++it; + } + + return true; +} + +bool RuleMatchContext::simpleSelectorMatch(const SimpleSelector& selector, const Element* element) const +{ + if(selector.id != ElementId::Star && selector.id != element->id) + return false; + + for(auto& attributeSelector : selector.attributeSelectors) + if(!attributeSelectorMatch(attributeSelector, element)) + return false; + + for(auto& pseudoClass : selector.pseudoClasses) + if(!pseudoClassMatch(pseudoClass, element)) + return false; + + return true; +} + +bool RuleMatchContext::attributeSelectorMatch(const AttributeSelector& selector, const Element* element) const +{ + auto& value = element->get(selector.id); + if(value.empty()) + return false; + + if(selector.matchType == AttributeSelector::MatchType::None) + return true; + + if(selector.matchType == AttributeSelector::MatchType::Equal) + return selector.value == value; + + if(selector.matchType == AttributeSelector::MatchType::Includes) + { + auto ptr = value.data(); + auto end = ptr + value.size(); + while(ptr < end) + { + auto start = ptr; + while(ptr < end && !IS_WS(*ptr)) + ++ptr; + + if(selector.value == std::string(start, ptr)) + return true; + Utils::skipWs(ptr, end); + } + + return false; + } + + auto starts_with = [](const std::string& string, const std::string& prefix) { + if(prefix.empty() || prefix.size() > string.size()) + return false; + + return string.compare(0, prefix.size(), prefix) == 0; + }; + + auto ends_with = [](const std::string& string, const std::string& suffix) { + if(suffix.empty() || suffix.size() > string.size()) + return false; + + return string.compare(string.size() - suffix.size(), suffix.size(), suffix) == 0; + }; + + if(selector.matchType == AttributeSelector::MatchType::DashMatch) + { + if(selector.value == value) + return true; + + return starts_with(value, selector.value + '-'); + } + + if(selector.matchType == AttributeSelector::MatchType::StartsWith) + return starts_with(value, selector.value); + + if(selector.matchType == AttributeSelector::MatchType::EndsWith) + return ends_with(value, selector.value); + + if(selector.matchType == AttributeSelector::MatchType::Contains) + return value.find(selector.value) != std::string::npos; + + return false; +} + +bool RuleMatchContext::pseudoClassMatch(const PseudoClass& pseudo, const Element* element) const +{ + if(pseudo.type == PseudoClass::Type::Empty) + return element->children.empty(); + + if(pseudo.type == PseudoClass::Type::Root) + return element->parent == nullptr; + + if(pseudo.type == PseudoClass::Type::Not) + { + for(auto& selector : pseudo.notSelectors) + if(selectorMatch(&selector, element)) + return false; + return true; + } + + if(pseudo.type == PseudoClass::Type::FirstChild) + return !element->previousSibling(); + + if(pseudo.type == PseudoClass::Type::LastChild) + return !element->nextSibling(); + + if(pseudo.type == PseudoClass::Type::OnlyChild) + return !(element->previousSibling() || element->nextSibling()); + + if(pseudo.type == PseudoClass::Type::FirstOfType) + { + auto sibling = element->previousSibling(); + while(sibling) + { + if(sibling->id == element->id) + return false; + sibling = element->previousSibling(); + } + + return true; + } + + if(pseudo.type == PseudoClass::Type::LastOfType) + { + auto sibling = element->nextSibling(); + while(sibling) + { + if(sibling->id == element->id) + return false; + sibling = element->nextSibling(); + } + + return true; + } + + return false; +} + +static inline std::unique_ptr createElement(ElementId id) +{ + switch(id) { + case ElementId::Svg: + return std::make_unique(); + case ElementId::Path: + return std::make_unique(); + case ElementId::G: + return std::make_unique(); + case ElementId::Rect: + return std::make_unique(); + case ElementId::Circle: + return std::make_unique(); + case ElementId::Ellipse: + return std::make_unique(); + case ElementId::Line: + return std::make_unique(); + case ElementId::Defs: + return std::make_unique(); + case ElementId::Polygon: + return std::make_unique(); + case ElementId::Polyline: + return std::make_unique(); + case ElementId::Stop: + return std::make_unique(); + case ElementId::LinearGradient: + return std::make_unique(); + case ElementId::RadialGradient: + return std::make_unique(); + case ElementId::Symbol: + return std::make_unique(); + case ElementId::Use: + return std::make_unique(); + case ElementId::Pattern: + return std::make_unique(); + case ElementId::Mask: + return std::make_unique(); + case ElementId::ClipPath: + return std::make_unique(); + case ElementId::SolidColor: + return std::make_unique(); + case ElementId::Marker: + return std::make_unique(); + case ElementId::Style: + return std::make_unique(); + default: + break; + } + + return nullptr; +} + +static inline bool decodeText(const char* ptr, const char* end, std::string& value) +{ + value.clear(); + while(ptr < end) + { + auto ch = *ptr; + ++ptr; + if(ch != '&') + { + value.push_back(ch); + continue; + } + + if(Utils::skipDesc(ptr, end, '#')) + { + int base = 10; + if(Utils::skipDesc(ptr, end, 'x')) + base = 16; + + unsigned int cp; + if(!Utils::parseInteger(ptr, end, cp, base)) + return false; + + char c[5] = {0, 0, 0, 0, 0}; + if(cp < 0x80) + { + c[1] = 0; + c[0] = cp; + } + else if(cp < 0x800) + { + c[2] = 0; + c[1] = (cp & 0x3F) | 0x80; + cp >>= 6; + c[0] = cp | 0xC0; + } + else if(cp < 0x10000) + { + c[3] = 0; + c[2] = (cp & 0x3F) | 0x80; + cp >>= 6; + c[1] = (cp & 0x3F) | 0x80; + cp >>= 6; + c[0] = cp | 0xE0; + } + else if(cp < 0x200000) + { + c[4] = 0; + c[3] = (cp & 0x3F) | 0x80; + cp >>= 6; + c[2] = (cp & 0x3F) | 0x80; + cp >>= 6; + c[1] = (cp & 0x3F) | 0x80; + cp >>= 6; + c[0] = cp | 0xF0; + } + + value.append(c); + } + else + { + if(Utils::skipDesc(ptr, end, "amp")) + value.push_back('&'); + else if(Utils::skipDesc(ptr, end, "lt")) + value.push_back('<'); + else if(Utils::skipDesc(ptr, end, "gt")) + value.push_back('>'); + else if(Utils::skipDesc(ptr, end, "quot")) + value.push_back('\"'); + else if(Utils::skipDesc(ptr, end, "apos")) + value.push_back('\''); + else + return false; + } + + if(!Utils::skipDesc(ptr, end, ';')) + return false; + } + + return true; +} + +static inline void parseStyle(const std::string& string, Element* element) +{ + auto ptr = string.data(); + auto end = ptr + string.size(); + + std::string name; + std::string value; + Utils::skipWs(ptr, end); + while(ptr < end && readCSSIdentifier(ptr, end, name)) + { + Utils::skipWs(ptr, end); + if(!Utils::skipDesc(ptr, end, ':')) + return; + Utils::skipWs(ptr, end); + auto start = ptr; + while(ptr < end && *ptr != ';') + ++ptr; + value.assign(start, Utils::rtrim(start, ptr)); + auto id = cssPropertyId(name); + if(id != PropertyId::Unknown) + element->set(id, value, 0x100); + Utils::skipWsDelimiter(ptr, end, ';'); + } +} + +ParseDocument::ParseDocument() +{ +} + +ParseDocument::~ParseDocument() +{ +} + +bool ParseDocument::parse(const char* data, std::size_t size) +{ + auto ptr = data; + auto end = ptr + size; + + CSSParser cssparser; + Element* current = nullptr; + std::string name; + std::string value; + int ignoring = 0; + + auto remove_comments = [](std::string& value) { + auto start = value.find("/*"); + while(start != std::string::npos) { + auto end = value.find("*/", start + 2); + value.erase(start, end - start + 2); + start = value.find("/*"); + } + }; + + auto handle_text = [&](const char* start, const char* end, bool in_cdata) { + if(ignoring > 0 || current == nullptr || current->id != ElementId::Style) + return; + + if(in_cdata) + value.assign(start, end); + else + decodeText(start, end, value); + + remove_comments(value); + cssparser.parseMore(value); + }; + + while(ptr < end) + { + auto start = ptr; + if(!Utils::skipUntil(ptr, end, '<')) + break; + + handle_text(start, ptr, false); + ptr += 1; + + if(ptr < end && *ptr == '/') + { + if(current == nullptr && ignoring == 0) + return false; + + ++ptr; + if(!readIdentifier(ptr, end, name)) + return false; + + Utils::skipWs(ptr, end); + if(ptr >= end || *ptr != '>') + return false; + + if(ignoring > 0) + --ignoring; + else + current = current->parent; + + ++ptr; + continue; + } + + if(ptr < end && *ptr == '?') + { + ++ptr; + if(!readIdentifier(ptr, end, name)) + return false; + + if(!Utils::skipUntil(ptr, end, "?>")) + return false; + + ptr += 2; + continue; + } + + if(ptr < end && *ptr == '!') + { + ++ptr; + if(Utils::skipDesc(ptr, end, "--")) + { + start = ptr; + if(!Utils::skipUntil(ptr, end, "-->")) + return false; + + handle_text(start, ptr, false); + ptr += 3; + continue; + } + + if(Utils::skipDesc(ptr, end, "[CDATA[")) + { + start = ptr; + if(!Utils::skipUntil(ptr, end, "]]>")) + return false; + + handle_text(start, ptr, true); + ptr += 3; + continue; + } + + if(Utils::skipDesc(ptr, end, "DOCTYPE")) + { + while(ptr < end && *ptr != '>') + { + if(*ptr == '[') + { + ++ptr; + int depth = 1; + while(ptr < end && depth > 0) + { + if(*ptr == '[') ++depth; + else if(*ptr == ']') --depth; + ++ptr; + } + } + else + { + ++ptr; + } + } + + if(ptr >= end || *ptr != '>') + return false; + + ptr += 1; + continue; + } + + return false; + } + + if(!readIdentifier(ptr, end, name)) + return false; + + auto id = ignoring == 0 ? elementId(name) : ElementId::Unknown; + if(id == ElementId::Unknown) + ++ignoring; + + Element* element = nullptr; + if(ignoring == 0) + { + if(m_rootElement && current == nullptr) + return false; + + if(m_rootElement == nullptr) + { + if(id != ElementId::Svg) + return false; + + m_rootElement = std::make_unique(); + element = m_rootElement.get(); + } + else + { + auto child = createElement(id); + element = child.get(); + current->addChild(std::move(child)); + } + } + + Utils::skipWs(ptr, end); + while(ptr < end && readIdentifier(ptr, end, name)) + { + Utils::skipWs(ptr, end); + if(ptr >= end || *ptr != '=') + return false; + ++ptr; + + Utils::skipWs(ptr, end); + if(ptr >= end || !(*ptr == '\"' || *ptr == '\'')) + return false; + + auto quote = *ptr; + ++ptr; + Utils::skipWs(ptr, end); + start = ptr; + while(ptr < end && *ptr != quote) + ++ptr; + + if(ptr >= end || *ptr != quote) + return false; + + auto id = element ? propertyId(name) : PropertyId::Unknown; + if(id != PropertyId::Unknown) + { + decodeText(start, Utils::rtrim(start, ptr), value); + if(id == PropertyId::Style) + { + remove_comments(value); + parseStyle(value, element); + } + else + { + if(id == PropertyId::Id) + m_idCache.emplace(value, element); + element->set(id, value, 0x1); + } + } + + ++ptr; + Utils::skipWs(ptr, end); + } + + if(ptr < end && *ptr == '>') + { + if(element != nullptr) + current = element; + + ++ptr; + continue; + } + + if(ptr < end && *ptr == '/') + { + ++ptr; + if(ptr >= end || *ptr != '>') + return false; + + if(ignoring > 0) + --ignoring; + + ++ptr; + continue; + } + + return false; + } + + if(!m_rootElement || ptr < end || ignoring > 0) + return false; + + const auto& rules = cssparser.rules(); + if(!rules.empty()) + { + RuleMatchContext context(rules); + m_rootElement->transverse([&context](Node* node) { + if(node->isText()) + return false; + + auto element = static_cast(node); + auto declarations = context.match(element); + for(auto& declaration : declarations) + element->properties.add(*declaration); + return false; + }); + } + + return true; +} + +Element* ParseDocument::getElementById(const std::string& id) const +{ + auto it = m_idCache.find(id); + if(it == m_idCache.end()) + return nullptr; + + return it->second; +} + +std::unique_ptr ParseDocument::layout() const +{ + return m_rootElement->layoutDocument(this); +} + +} // namespace lunasvg diff --git a/source/parser.h b/source/parser.h new file mode 100644 index 000000000..6ced83e16 --- /dev/null +++ b/source/parser.h @@ -0,0 +1,195 @@ +#ifndef PARSER_H +#define PARSER_H + +#include + +#include "property.h" +#include "element.h" + +namespace lunasvg { + +class SVGElement; +class StyledElement; + +enum LengthNegativeValuesMode +{ + AllowNegativeLengths, + ForbidNegativeLengths +}; + +enum class TransformType +{ + Matrix, + Rotate, + Scale, + SkewX, + SkewY, + Translate +}; + +class Parser +{ +public: + static Length parseLength(const std::string& string, LengthNegativeValuesMode mode, const Length& defaultValue); + static LengthList parseLengthList(const std::string& string, LengthNegativeValuesMode mode); + static double parseNumber(const std::string& string, double defaultValue); + static double parseNumberPercentage(const std::string& string, double defaultValue); + static PointList parsePointList(const std::string& string); + static Transform parseTransform(const std::string& string); + static Path parsePath(const std::string& string); + static std::string parseUrl(const std::string& string); + static std::string parseHref(const std::string& string); + static Rect parseViewBox(const std::string& string); + static PreserveAspectRatio parsePreserveAspectRatio(const std::string& string); + static Angle parseAngle(const std::string& string); + static MarkerUnits parseMarkerUnits(const std::string& string); + static SpreadMethod parseSpreadMethod(const std::string& string); + static Units parseUnits(const std::string& string, Units defaultValue); + static Color parseColor(const std::string& string, const StyledElement* element, const Color& defaultValue); + static Paint parsePaint(const std::string& string, const StyledElement* element, const Color& defaultValue); + static WindRule parseWindRule(const std::string& string); + static LineCap parseLineCap(const std::string& string); + static LineJoin parseLineJoin(const std::string& string); + static Display parseDisplay(const std::string& string); + static Visibility parseVisibility(const std::string& string); + static Overflow parseOverflow(const std::string& string, Overflow defaultValue); + +private: + static bool parseLength(const char*& ptr, const char* end, double& value, LengthUnits& units, LengthNegativeValuesMode mode); + static bool parseNumberList(const char*& ptr, const char* end, double* values, int count); + static bool parseArcFlag(const char*& ptr, const char* end, bool& flag); + static bool parseColorComponent(const char*& ptr, const char* end, double& value); + static bool parseUrlFragment(const char*& ptr, const char* end, std::string& ref); + static bool parseTransform(const char*& ptr, const char* end, TransformType& type, double* values, int& count); +}; + +struct Selector; + +struct AttributeSelector +{ + enum class MatchType + { + None, + Equal, + Includes, + DashMatch, + StartsWith, + EndsWith, + Contains + }; + + PropertyId id{PropertyId::Unknown}; + std::string value; + MatchType matchType{MatchType::None}; +}; + +using SelectorList = std::vector; + +struct PseudoClass +{ + enum class Type + { + Unknown, + Empty, + Root, + Not, + FirstChild, + LastChild, + OnlyChild, + FirstOfType, + LastOfType, + OnlyOfType + }; + + Type type{Type::Unknown}; + SelectorList notSelectors; +}; + +struct SimpleSelector +{ + enum class Combinator + { + Descendant, + Child, + DirectAdjacent, + InDirectAdjacent + }; + + ElementId id{ElementId::Star}; + std::vector attributeSelectors; + std::vector pseudoClasses; + Combinator combinator{Combinator::Descendant}; +}; + +struct Selector +{ + std::vector simpleSelectors; + int specificity{0}; +}; + +struct Rule +{ + SelectorList selectors; + PropertyList declarations; +}; + +class RuleMatchContext +{ +public: + RuleMatchContext(const std::vector& rules); + + std::vector match(const Element* element) const; + +private: + bool selectorMatch(const Selector* selector, const Element* element) const; + bool simpleSelectorMatch(const SimpleSelector& selector, const Element* element) const; + bool attributeSelectorMatch(const AttributeSelector& selector, const Element* element) const; + bool pseudoClassMatch(const PseudoClass& pseudo, const Element* element) const; + +private: + std::multimap, std::less> m_selectors; +}; + +class CSSParser +{ +public: + CSSParser() = default; + + bool parseMore(const std::string& value); + + const std::vector& rules() const { return m_rules; } + +private: + bool parseAtRule(const char*& ptr, const char* end) const; + bool parseRule(const char*& ptr, const char* end, Rule& rule) const; + bool parseSelectors(const char*& ptr, const char* end, SelectorList& selectors) const; + bool parseDeclarations(const char*& ptr, const char* end, PropertyList& declarations) const; + bool parseSelector(const char*& ptr, const char* end, Selector& selector) const; + bool parseSimpleSelector(const char*& ptr, const char* end, SimpleSelector& simpleSelector) const; + +private: + std::vector m_rules; +}; + +class LayoutSymbol; + +class ParseDocument +{ +public: + ParseDocument(); + ~ParseDocument(); + + bool parse(const char* data, std::size_t size); + + SVGElement* rootElement() const { return m_rootElement.get(); } + Element* getElementById(const std::string& id) const; + std::unique_ptr layout() const; + +private: + std::unique_ptr m_rootElement; + std::map m_idCache; +}; + +} // namespace lunasvg + +#endif // PARSER_H diff --git a/source/parserutils.h b/source/parserutils.h new file mode 100644 index 000000000..e83746d6d --- /dev/null +++ b/source/parserutils.h @@ -0,0 +1,267 @@ +#ifndef PARSERUTILS_H +#define PARSERUTILS_H + +#include +#include +#include +#include +#include + +namespace lunasvg { + +#define IS_ALPHA(c) ((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z') +#define IS_NUM(c) ((c) >= '0' && (c) <= '9') +#define IS_WS(c) ((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\r') + +namespace Utils { + +inline const char* rtrim(const char* start, const char* end) +{ + while(end > start && IS_WS(end[-1])) + --end; + + return end; +} + +inline const char* ltrim(const char* start, const char* end) +{ + while(start < end && IS_WS(*start)) + ++start; + + return start; +} + +inline bool skipDesc(const char*& ptr, const char* end, const char ch) +{ + if(ptr >= end || *ptr != ch) + return false; + + ++ptr; + return true; +} + +inline bool skipDesc(const char*& ptr, const char* end, const char* data) +{ + int read = 0; + while(data[read]) + { + if(ptr >= end || *ptr != data[read]) + { + ptr -= read; + return false; + } + + ++read; + ++ptr; + } + + return true; +} + +inline bool skipUntil(const char*& ptr, const char* end, const char ch) +{ + while(ptr < end && *ptr != ch) + ++ptr; + + return ptr < end; +} + +inline bool skipUntil(const char*& ptr, const char* end, const char* data) +{ + while(ptr < end) + { + auto start = ptr; + if(skipDesc(start, end, data)) + break; + ++ptr; + } + + return ptr < end; +} + +inline bool readUntil(const char*& ptr, const char* end, const char ch, std::string& value) +{ + auto start = ptr; + if(!skipUntil(ptr, end, ch)) + return false; + + value.assign(start, ptr); + return true; +} + +inline bool readUntil(const char*& ptr, const char* end, const char* data, std::string& value) +{ + auto start = ptr; + if(!skipUntil(ptr, end, data)) + return false; + + value.assign(start, ptr); + return true; +} + +inline bool skipWs(const char*& ptr, const char* end) +{ + while(ptr < end && IS_WS(*ptr)) + ++ptr; + + return ptr < end; +} + +inline bool skipWsDelimiter(const char*& ptr, const char* end, const char delimiter) +{ + if(ptr < end && !IS_WS(*ptr) && *ptr != delimiter) + return false; + + if(skipWs(ptr, end)) + { + if(ptr < end && *ptr == delimiter) + { + ++ptr; + skipWs(ptr, end); + } + } + + return ptr < end; +} + +inline bool skipWsComma(const char*& ptr, const char* end) +{ + return skipWsDelimiter(ptr, end, ','); +} + +inline bool isIntegralDigit(char ch, int base) +{ + if(IS_NUM(ch)) + return ch - '0' < base; + + if(IS_ALPHA(ch)) + return (ch >= 'a' && ch < 'a' + std::min(base, 36) - 10) || (ch >= 'A' && ch < 'A' + std::min(base, 36) - 10); + + return false; +} + +template +inline bool parseInteger(const char*& ptr, const char* end, T& integer, int base = 10) +{ + bool isNegative = 0; + T value = 0; + + static const T intMax = std::numeric_limits::max(); + static const bool isSigned = std::numeric_limits::is_signed; + using signed_t = typename std::make_signed::type; + const T maxMultiplier = intMax / static_cast(base); + + if(ptr < end && *ptr == '+') + ++ptr; + else if(ptr < end && isSigned && *ptr == '-') + { + ++ptr; + isNegative = true; + } + + if(ptr >= end || !isIntegralDigit(*ptr, base)) + return false; + + do { + const char ch = *ptr++; + int digitValue; + if(IS_NUM(ch)) + digitValue = ch - '0'; + else if(ch >= 'a') + digitValue = ch - 'a' + 10; + else + digitValue = ch - 'A' + 10; + + if(value > maxMultiplier || (value == maxMultiplier && static_cast(digitValue) > (intMax % static_cast(base)) + isNegative)) + return false; + value = static_cast(base) * value + static_cast(digitValue); + } while(ptr < end && isIntegralDigit(*ptr, base)); + + if(isNegative) + integer = -static_cast(value); + else + integer = value; + + return true; +} + +template +inline bool parseNumber(const char*& ptr, const char* end, T& number) +{ + T integer, fraction; + int sign, expsign, exponent; + + static const T numberMax = std::numeric_limits::max(); + fraction = 0; + integer = 0; + exponent = 0; + sign = 1; + expsign = 1; + + if(ptr < end && *ptr == '+') + ++ptr; + else if(ptr < end && *ptr == '-') + { + ++ptr; + sign = -1; + } + + if(ptr >= end || !(IS_NUM(*ptr) || *ptr == '.')) + return false; + + if(*ptr != '.') + { + do { + integer = static_cast(10) * integer + (*ptr - '0'); + ++ptr; + } while(ptr < end && IS_NUM(*ptr)); + } + + if(ptr < end && *ptr == '.') + { + ++ptr; + if(ptr >= end || !IS_NUM(*ptr)) + return false; + + T divisor = 1; + do { + fraction = static_cast(10) * fraction + (*ptr - '0'); + divisor *= static_cast(10); + ++ptr; + } while(ptr < end && IS_NUM(*ptr)); + fraction /= divisor; + } + + if(ptr < end && (*ptr == 'e' || *ptr == 'E') + && (ptr[1] != 'x' && ptr[1] != 'm')) + { + ++ptr; + if(ptr < end && *ptr == '+') + ++ptr; + else if(ptr < end && *ptr == '-') + { + ++ptr; + expsign = -1; + } + + if(ptr >= end || !IS_NUM(*ptr)) + return false; + + do { + exponent = 10 * exponent + (*ptr - '0'); + ++ptr; + } while(ptr < end && IS_NUM(*ptr)); + } + + number = sign * (integer + fraction); + if(exponent) + number *= static_cast(pow(10.0, expsign*exponent)); + + return number >= -numberMax && number <= numberMax; +} + +} // namespace Utils + +} // namespace lunasvg + +#endif // PARSERUTILS_H diff --git a/source/property.cpp b/source/property.cpp new file mode 100644 index 000000000..97ddf3730 --- /dev/null +++ b/source/property.cpp @@ -0,0 +1,698 @@ +#include "property.h" +#include "styledelement.h" +#include "lunasvg.h" + +#include + +namespace lunasvg { + +const Color Color::Black{0, 0, 0, 1}; +const Color Color::White{1, 1, 1, 1}; +const Color Color::Red{1, 0, 0, 1}; +const Color Color::Green{0, 1, 0, 1}; +const Color Color::Blue{0, 0, 1, 1}; +const Color Color::Yellow{1, 1, 0, 1}; +const Color Color::Transparent{0, 0, 0, 0}; + +Color::Color(double r, double g, double b, double a) + : r(r), g(g), b(b), a(a) +{ +} + +Paint::Paint(const Color& color) + : m_color(color) +{ +} + +Paint::Paint(const std::string& ref, const Color& color) + : m_ref(ref), m_color(color) +{ +} + +Point::Point(double x, double y) + : x(x), y(y) +{ +} + +const Rect Rect::Empty{0, 0, 0, 0}; +const Rect Rect::Invalid{0, 0, -1, -1}; + +Rect::Rect(double x, double y, double w, double h) + : x(x), y(y), w(w), h(h) +{ +} + +Rect::Rect(const Box& box) + : x(box.x), y(box.y), w(box.w), h(box.h) +{ +} + +Rect Rect::operator&(const Rect& rect) const +{ + if(!rect.valid()) + return *this; + + if(!valid()) + return rect; + + auto l = std::max(x, rect.x); + auto t = std::max(y, rect.y); + auto r = std::min(x + w, rect.x + rect.w); + auto b = std::min(y + h, rect.y + rect.h); + + return Rect{l, t, r-l, b-t}; +} + +Rect Rect::operator|(const Rect& rect) const +{ + if(!rect.valid()) + return *this; + + if(!valid()) + return rect; + + auto l = std::min(x, rect.x); + auto t = std::min(y, rect.y); + auto r = std::max(x + w, rect.x + rect.w); + auto b = std::max(y + h, rect.y + rect.h); + + return Rect{l, t, r-l, b-t}; +} + +Rect& Rect::intersect(const Rect& rect) +{ + *this = *this & rect; + return *this; +} + +Rect& Rect::unite(const Rect& rect) +{ + *this = *this | rect; + return *this; +} + +Transform::Transform(double m00, double m10, double m01, double m11, double m02, double m12) + : m00(m00), m10(m10), m01(m01), m11(m11), m02(m02), m12(m12) +{ +} + +Transform::Transform(const Matrix& matrix) + : m00(matrix.a), m10(matrix.b), m01(matrix.c), m11(matrix.d), m02(matrix.e), m12(matrix.f) +{ +} + +Transform Transform::inverted() const +{ + double det = (this->m00 * this->m11 - this->m10 * this->m01); + if(det == 0.0) + return Transform{}; + + double inv_det = 1.0 / det; + double m00 = this->m00 * inv_det; + double m10 = this->m10 * inv_det; + double m01 = this->m01 * inv_det; + double m11 = this->m11 * inv_det; + double m02 = (this->m01 * this->m12 - this->m11 * this->m02) * inv_det; + double m12 = (this->m10 * this->m02 - this->m00 * this->m12) * inv_det; + + return Transform{m11, -m10, -m01, m00, m02, m12}; +} + +Transform Transform::operator*(const Transform& transform) const +{ + double m00 = this->m00 * transform.m00 + this->m10 * transform.m01; + double m10 = this->m00 * transform.m10 + this->m10 * transform.m11; + double m01 = this->m01 * transform.m00 + this->m11 * transform.m01; + double m11 = this->m01 * transform.m10 + this->m11 * transform.m11; + double m02 = this->m02 * transform.m00 + this->m12 * transform.m01 + transform.m02; + double m12 = this->m02 * transform.m10 + this->m12 * transform.m11 + transform.m12; + + return Transform{m00, m10, m01, m11, m02, m12}; +} + +Transform& Transform::operator*=(const Transform& transform) +{ + *this = *this * transform; + return *this; +} + +Transform& Transform::premultiply(const Transform& transform) +{ + *this = transform * *this; + return *this; +} + +Transform& Transform::postmultiply(const Transform& transform) +{ + *this = *this * transform; + return *this; +} + +Transform& Transform::rotate(double angle) +{ + *this = rotated(angle) * *this; + return *this; +} + +Transform& Transform::rotate(double angle, double cx, double cy) +{ + *this = rotated(angle, cx, cy) * *this; + return *this; +} + +Transform& Transform::scale(double sx, double sy) +{ + *this = scaled(sx, sy) * *this; + return *this; +} + +Transform& Transform::shear(double shx, double shy) +{ + *this = sheared(shx, shy) * *this; + return *this; +} + +Transform& Transform::translate(double tx, double ty) +{ + *this = translated(tx, ty) * *this; + return *this; +} + +Transform& Transform::transform(double m00, double m10, double m01, double m11, double m02, double m12) +{ + *this = Transform{m00, m10, m01, m11, m02, m12} * *this; + return *this; +} + +Transform& Transform::identity() +{ + *this = Transform{1, 0, 0, 1, 0, 0}; + return *this; +} + +Transform& Transform::invert() +{ + *this = inverted(); + return *this; +} + +void Transform::map(double x, double y, double* _x, double* _y) const +{ + *_x = x * m00 + y * m01 + m02; + *_y = x * m10 + y * m11 + m12; +} + +Point Transform::map(double x, double y) const +{ + map(x, y, &x, &y); + return Point{x, y}; +} + +Point Transform::map(const Point& point) const +{ + return map(point.x, point.y); +} + +Rect Transform::map(const Rect& rect) const +{ + if(!rect.valid()) + return Rect::Invalid; + + auto x1 = rect.x; + auto y1 = rect.y; + auto x2 = rect.x + rect.w; + auto y2 = rect.y + rect.h; + + const Point p[] = { + map(x1, y1), map(x2, y1), + map(x2, y2), map(x1, y2) + }; + + auto l = p[0].x; + auto t = p[0].y; + auto r = p[0].x; + auto b = p[0].y; + + for(int i = 1;i < 4;i++) + { + if(p[i].x < l) l = p[i].x; + if(p[i].x > r) r = p[i].x; + if(p[i].y < t) t = p[i].y; + if(p[i].y > b) b = p[i].y; + } + + return Rect{l, t, r-l, b-t}; +} + +static const double pi = 3.14159265358979323846; + +Transform Transform::rotated(double angle) +{ + auto c = std::cos(angle * pi / 180.0); + auto s = std::sin(angle * pi / 180.0); + + return Transform{c, s, -s, c, 0, 0}; +} + +Transform Transform::rotated(double angle, double cx, double cy) +{ + auto c = std::cos(angle * pi / 180.0); + auto s = std::sin(angle * pi / 180.0); + + auto x = cx * (1 - c) + cy * s; + auto y = cy * (1 - c) - cx * s; + + return Transform{c, s, -s, c, x, y}; +} + +Transform Transform::scaled(double sx, double sy) +{ + return Transform{sx, 0, 0, sy, 0, 0}; +} + +Transform Transform::sheared(double shx, double shy) +{ + auto x = std::tan(shx * pi / 180.0); + auto y = std::tan(shy * pi / 180.0); + + return Transform{1, y, x, 1, 0, 0}; +} + +Transform Transform::translated(double tx, double ty) +{ + return Transform{1, 0, 0, 1, tx, ty}; +} + +void Path::moveTo(double x, double y) +{ + m_commands.push_back(PathCommand::MoveTo); + m_points.emplace_back(x, y); +} + +void Path::lineTo(double x, double y) +{ + m_commands.push_back(PathCommand::LineTo); + m_points.emplace_back(x, y); +} + +void Path::cubicTo(double x1, double y1, double x2, double y2, double x3, double y3) +{ + m_commands.push_back(PathCommand::CubicTo); + m_points.emplace_back(x1, y1); + m_points.emplace_back(x2, y2); + m_points.emplace_back(x3, y3); +} + +void Path::close() +{ + if(m_commands.empty()) + return; + + if(m_commands.back() == PathCommand::Close) + return; + + m_commands.push_back(PathCommand::Close); +} + +void Path::reset() +{ + m_commands.clear(); + m_points.clear(); +} + +bool Path::empty() const +{ + return m_commands.empty(); +} + +void Path::quadTo(double cx, double cy, double x1, double y1, double x2, double y2) +{ + auto cx1 = 2.0 / 3.0 * x1 + 1.0 / 3.0 * cx; + auto cy1 = 2.0 / 3.0 * y1 + 1.0 / 3.0 * cy; + auto cx2 = 2.0 / 3.0 * x1 + 1.0 / 3.0 * x2; + auto cy2 = 2.0 / 3.0 * y1 + 1.0 / 3.0 * y2; + cubicTo(cx1, cy1, cx2, cy2, x2, y2); +} + +void Path::arcTo(double cx, double cy, double rx, double ry, double xAxisRotation, bool largeArcFlag, bool sweepFlag, double x, double y) +{ + rx = std::fabs(rx); + ry = std::fabs(ry); + + auto sin_th = std::sin(xAxisRotation * pi / 180.0); + auto cos_th = std::cos(xAxisRotation * pi / 180.0); + + auto dx = (cx - x) / 2.0; + auto dy = (cy - y) / 2.0; + auto dx1 = cos_th * dx + sin_th * dy; + auto dy1 = -sin_th * dx + cos_th * dy; + auto Pr1 = rx * rx; + auto Pr2 = ry * ry; + auto Px = dx1 * dx1; + auto Py = dy1 * dy1; + auto check = Px / Pr1 + Py / Pr2; + if(check > 1) + { + rx = rx * std::sqrt(check); + ry = ry * std::sqrt(check); + } + + auto a00 = cos_th / rx; + auto a01 = sin_th / rx; + auto a10 = -sin_th / ry; + auto a11 = cos_th / ry; + auto x0 = a00 * cx + a01 * cy; + auto y0 = a10 * cx + a11 * cy; + auto x1 = a00 * x + a01 * y; + auto y1 = a10 * x + a11 * y; + auto d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0); + auto sfactor_sq = 1.0 / d - 0.25; + if(sfactor_sq < 0) sfactor_sq = 0; + auto sfactor = std::sqrt(sfactor_sq); + if(sweepFlag == largeArcFlag) sfactor = -sfactor; + auto xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0); + auto yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0); + + auto th0 = std::atan2(y0 - yc, x0 - xc); + auto th1 = std::atan2(y1 - yc, x1 - xc); + + double th_arc = th1 - th0; + if(th_arc < 0.0 && sweepFlag) + th_arc += 2.0 * pi; + else if(th_arc > 0.0 && !sweepFlag) + th_arc -= 2.0 * pi; + + auto n_segs = static_cast(std::ceil(std::fabs(th_arc / (pi * 0.5 + 0.001)))); + for(int i = 0;i < n_segs;i++) + { + auto th2 = th0 + i * th_arc / n_segs; + auto th3 = th0 + (i + 1) * th_arc / n_segs; + + auto a00 = cos_th * rx; + auto a01 = -sin_th * ry; + auto a10 = sin_th * rx; + auto a11 = cos_th * ry; + + auto thHalf = 0.5 * (th3 - th2); + auto t = (8.0 / 3.0) * std::sin(thHalf * 0.5) * std::sin(thHalf * 0.5) / std::sin(thHalf); + auto x1 = xc + std::cos(th2) - t * std::sin(th2); + auto y1 = yc + std::sin(th2) + t * std::cos(th2); + auto x3 = xc + std::cos(th3); + auto y3 = yc + std::sin(th3); + auto x2 = x3 + t * std::sin(th3); + auto y2 = y3 - t * std::cos(th3); + + auto cx1 = a00 * x1 + a01 * y1; + auto cy1 = a10 * x1 + a11 * y1; + auto cx2 = a00 * x2 + a01 * y2; + auto cy2 = a10 * x2 + a11 * y2; + auto cx3 = a00 * x3 + a01 * y3; + auto cy3 = a10 * x3 + a11 * y3; + cubicTo(cx1, cy1, cx2, cy2, cx3, cy3); + } +} + +static const double kappa = 0.55228474983079339840; + +void Path::ellipse(double cx, double cy, double rx, double ry) +{ + auto left = cx - rx; + auto top = cy - ry; + auto right = cx + rx; + auto bottom = cy + ry; + + auto cpx = rx * kappa; + auto cpy = ry * kappa; + + moveTo(cx, top); + cubicTo(cx+cpx, top, right, cy-cpy, right, cy); + cubicTo(right, cy+cpy, cx+cpx, bottom, cx, bottom); + cubicTo(cx-cpx, bottom, left, cy+cpy, left, cy); + cubicTo(left, cy-cpy, cx-cpx, top, cx, top); + close(); +} + +void Path::rect(double x, double y, double w, double h, double rx, double ry) +{ + rx = std::min(rx, w * 0.5); + ry = std::min(ry, h * 0.5); + + auto right = x + w; + auto bottom = y + h; + + if(rx == 0.0 && ry == 0.0) + { + moveTo(x, y); + lineTo(right, y); + lineTo(right, bottom); + lineTo(x, bottom); + lineTo(x, y); + close(); + } + else + { + double cpx = rx * kappa; + double cpy = ry * kappa; + moveTo(x, y+ry); + cubicTo(x, y+ry-cpy, x+rx-cpx, y, x+rx, y); + lineTo(right-rx, y); + cubicTo(right-rx+cpx, y, right, y+ry-cpy, right, y+ry); + lineTo(right, bottom-ry); + cubicTo(right, bottom-ry+cpy, right-rx+cpx, bottom, right-rx, bottom); + lineTo(x+rx, bottom); + cubicTo(x+rx-cpx, bottom, x, bottom-ry+cpy, x, bottom-ry); + lineTo(x, y+ry); + close(); + } +} + +Rect Path::box() const +{ + if(m_points.empty()) + return Rect{}; + + auto l = m_points[0].x; + auto t = m_points[0].y; + auto r = m_points[0].x; + auto b = m_points[0].y; + + for(std::size_t i = 1;i < m_points.size();i++) + { + if(m_points[i].x < l) l = m_points[i].x; + if(m_points[i].x > r) r = m_points[i].x; + if(m_points[i].y < t) t = m_points[i].y; + if(m_points[i].y > b) b = m_points[i].y; + } + + return Rect{l, t, r-l, b-t}; +} + +PathIterator::PathIterator(const Path& path) + : m_commands(path.commands()), + m_points(path.points().data()) +{ +} + +PathCommand PathIterator::currentSegment(std::array& points) const +{ + auto command = m_commands[m_index]; + switch(command) { + case PathCommand::MoveTo: + points[0] = m_points[0]; + m_startPoint = points[0]; + break; + case PathCommand::LineTo: + points[0] = m_points[0]; + break; + case PathCommand::CubicTo: + points[0] = m_points[0]; + points[1] = m_points[1]; + points[2] = m_points[2]; + break; + case PathCommand::Close: + points[0] = m_startPoint; + break; + } + + return command; +} + +bool PathIterator::isDone() const +{ + return (m_index >= m_commands.size()); +} + +void PathIterator::next() +{ + switch(m_commands[m_index]) { + case PathCommand::MoveTo: + case PathCommand::LineTo: + m_points += 1; + break; + case PathCommand::CubicTo: + m_points += 3; + break; + default: + break; + } + + m_index += 1; +} + +const Length Length::Unknown{0, LengthUnits::Unknown}; +const Length Length::Zero{0, LengthUnits::Number}; +const Length Length::One{1, LengthUnits::Number}; +const Length Length::ThreePercent{3, LengthUnits::Percent}; +const Length Length::HundredPercent{100, LengthUnits::Percent}; +const Length Length::FiftyPercent{50, LengthUnits::Percent}; +const Length Length::OneTwentyPercent{120, LengthUnits::Percent}; +const Length Length::MinusTenPercent{-10, LengthUnits::Percent}; + +Length::Length(double value) + : m_value(value) +{ +} + +Length::Length(double value, LengthUnits units) + : m_value(value), m_units(units) +{ +} + +static const double dpi = 96.0; + +double Length::value(double max) const +{ + switch(m_units) { + case LengthUnits::Number: + case LengthUnits::Px: + return m_value; + case LengthUnits::In: + return m_value * dpi; + case LengthUnits::Cm: + return m_value * dpi / 2.54; + case LengthUnits::Mm: + return m_value * dpi / 25.4; + case LengthUnits::Pt: + return m_value * dpi / 72.0; + case LengthUnits::Pc: + return m_value * dpi / 6.0; + case LengthUnits::Percent: + return m_value * max / 100.0; + default: + break; + } + + return 0.0; +} + +static const double sqrt2 = 1.41421356237309504880; + +double Length::value(const Element* element, LengthMode mode) const +{ + if(m_units == LengthUnits::Percent) + { + auto viewport = element->currentViewport(); + auto w = viewport.w; + auto h = viewport.h; + auto max = (mode == LengthMode::Width) ? w : (mode == LengthMode::Height) ? h : std::sqrt(w*w+h*h) / sqrt2; + return m_value * max / 100.0; + } + + return value(1.0); +} + +LengthContext::LengthContext(const Element* element) + : m_element(element) +{ +} + +LengthContext::LengthContext(const Element* element, Units units) + : m_element(element), m_units(units) +{ +} + +double LengthContext::valueForLength(const Length& length, LengthMode mode) const +{ + if(m_units == Units::ObjectBoundingBox) + return length.value(1.0); + return length.value(m_element, mode); +} + +PreserveAspectRatio::PreserveAspectRatio(Align align, MeetOrSlice scale) + : m_align(align), m_scale(scale) +{ +} + +Transform PreserveAspectRatio::getMatrix(double width, double height, const Rect& viewBox) const +{ + if(viewBox.empty()) + return Transform{}; + + auto xscale = width / viewBox.w; + auto yscale = height / viewBox.h; + if(m_align == Align::None) + { + auto xoffset = -viewBox.x * xscale; + auto yoffset = -viewBox.y * yscale; + return Transform{xscale, 0, 0, yscale, xoffset, yoffset}; + } + + auto scale = (m_scale == MeetOrSlice::Meet) ? std::min(xscale, yscale) : std::max(xscale, yscale); + auto viewWidth = viewBox.w * scale; + auto viewHeight = viewBox.h * scale; + + auto xoffset = -viewBox.x * scale; + auto yoffset = -viewBox.y * scale; + + switch(m_align) { + case Align::xMidYMin: + case Align::xMidYMid: + case Align::xMidYMax: + xoffset += (width - viewWidth) * 0.5; + break; + case Align::xMaxYMin: + case Align::xMaxYMid: + case Align::xMaxYMax: + xoffset += (width - viewWidth); + break; + default: + break; + } + + switch(m_align) { + case Align::xMinYMid: + case Align::xMidYMid: + case Align::xMaxYMid: + yoffset += (height - viewHeight) * 0.5; + break; + case Align::xMinYMax: + case Align::xMidYMax: + case Align::xMaxYMax: + yoffset += (height - viewHeight); + break; + default: + break; + } + + return Transform{scale, 0, 0, scale, xoffset, yoffset}; +} + +Rect PreserveAspectRatio::getClip(double width, double height, const Rect& viewBox) const +{ + if(viewBox.empty()) + return Rect{0, 0, width, height}; + + return viewBox; +} + +Angle::Angle(MarkerOrient type) + : m_type(type) +{ +} + +Angle::Angle(double value, MarkerOrient type) + : m_value(value), m_type(type) +{ +} + +} // namespace lunasvg diff --git a/source/property.h b/source/property.h new file mode 100644 index 000000000..52a07e523 --- /dev/null +++ b/source/property.h @@ -0,0 +1,370 @@ +#ifndef PROPERTY_H +#define PROPERTY_H + +#include +#include +#include + +namespace lunasvg { + +enum class Display +{ + Inline, + None +}; + +enum class Visibility +{ + Visible, + Hidden +}; + +enum class Overflow +{ + Visible, + Hidden +}; + +enum class LineCap +{ + Butt, + Round, + Square +}; + +enum class LineJoin +{ + Miter, + Round, + Bevel +}; + +enum class WindRule +{ + NonZero, + EvenOdd +}; + +enum class Units +{ + UserSpaceOnUse, + ObjectBoundingBox +}; + +enum class SpreadMethod +{ + Pad, + Reflect, + Repeat +}; + +enum class MarkerUnits +{ + StrokeWidth, + UserSpaceOnUse +}; + +class Color +{ +public: + Color() = default; + Color(double r, double g, double b, double a = 1); + + bool isNone() const { return a == 0.0; } + + static const Color Black; + static const Color White; + static const Color Red; + static const Color Green; + static const Color Blue; + static const Color Yellow; + static const Color Transparent; + +public: + double r{0}; + double g{0}; + double b{0}; + double a{1}; +}; + +class Paint +{ +public: + Paint() = default; + Paint(const Color& color); + Paint(const std::string& ref, const Color& color); + + const Color& color() const { return m_color; } + const std::string& ref() const { return m_ref; } + bool isNone() const { return m_ref.empty() && m_color.isNone(); } + +public: + std::string m_ref; + Color m_color{Color::Transparent}; +}; + +class Point +{ +public: + Point() = default; + Point(double x, double y); + +public: + double x{0}; + double y{0}; +}; + +using PointList = std::vector; + +class Box; + +class Rect +{ +public: + Rect() = default; + Rect(double x, double y, double w, double h); + Rect(const Box& box); + + Rect operator&(const Rect& rect) const; + Rect operator|(const Rect& rect) const; + + Rect& intersect(const Rect& rect); + Rect& unite(const Rect& rect); + + bool empty() const { return w <= 0.0 || h <= 0.0; } + bool valid() const { return w >= 0.0 && h >= 0.0; } + + static const Rect Empty; + static const Rect Invalid; + +public: + double x{0}; + double y{0}; + double w{0}; + double h{0}; +}; + +class Matrix; + +class Transform +{ +public: + Transform() = default; + Transform(double m00, double m10, double m01, double m11, double m02, double m12); + Transform(const Matrix& matrix); + + Transform inverted() const; + Transform operator*(const Transform& transform) const; + Transform& operator*=(const Transform& transform); + + Transform& premultiply(const Transform& transform); + Transform& postmultiply(const Transform& transform); + Transform& rotate(double angle); + Transform& rotate(double angle, double cx, double cy); + Transform& scale(double sx, double sy); + Transform& shear(double shx, double shy); + Transform& translate(double tx, double ty); + Transform& transform(double m00, double m10, double m01, double m11, double m02, double m12); + Transform& identity(); + Transform& invert(); + + void map(double x, double y, double* _x, double* _y) const; + Point map(double x, double y) const; + Point map(const Point& point) const; + Rect map(const Rect& rect) const; + + static Transform rotated(double angle); + static Transform rotated(double angle, double cx, double cy); + static Transform scaled(double sx, double sy); + static Transform sheared(double shx, double shy); + static Transform translated(double tx, double ty); + +public: + double m00{1}; + double m10{0}; + double m01{0}; + double m11{1}; + double m02{0}; + double m12{0}; +}; + +enum class PathCommand +{ + MoveTo, + LineTo, + CubicTo, + Close +}; + +class Path +{ +public: + Path() = default; + + void moveTo(double x, double y); + void lineTo(double x, double y); + void cubicTo(double x1, double y1, double x2, double y2, double x3, double y3); + void close(); + void reset(); + bool empty() const; + + void quadTo(double cx, double cy, double x1, double y1, double x2, double y2); + void arcTo(double cx, double cy, double rx, double ry, double xAxisRotation, bool largeArcFlag, bool sweepFlag, double x, double y); + + void ellipse(double cx, double cy, double rx, double ry); + void rect(double x, double y, double w, double h, double rx, double ry); + + Rect box() const; + + const std::vector& commands() const { return m_commands; } + const std::vector& points() const { return m_points; } + +private: + std::vector m_commands; + std::vector m_points; +}; + +class PathIterator +{ +public: + PathIterator(const Path& path); + + PathCommand currentSegment(std::array& points) const; + bool isDone() const; + void next(); + +private: + mutable Point m_startPoint; + const std::vector& m_commands; + const Point* m_points{nullptr}; + unsigned int m_index{0}; +}; + +enum class LengthUnits +{ + Unknown, + Number, + Px, + Pt, + Pc, + In, + Cm, + Mm, + Ex, + Em, + Percent +}; + +enum LengthMode +{ + Width, + Height, + Both +}; + +class Element; + +class Length +{ +public: + Length() = default; + Length(double value); + Length(double value, LengthUnits units); + + double value(double max) const; + double value(const Element* element, LengthMode mode) const; + + bool isValid() const { return m_units != LengthUnits::Unknown; } + bool isZero() const { return m_value == 0.0; } + bool isRelative() const { return m_units == LengthUnits::Percent || m_units == LengthUnits::Em || m_units == LengthUnits::Ex; } + + static const Length Unknown; + static const Length Zero; + static const Length One; + static const Length ThreePercent; + static const Length HundredPercent; + static const Length FiftyPercent; + static const Length OneTwentyPercent; + static const Length MinusTenPercent; + +private: + double m_value{0}; + LengthUnits m_units{LengthUnits::Px}; +}; + +using LengthList = std::vector; + +class LengthContext +{ +public: + LengthContext(const Element* element); + LengthContext(const Element* element, Units units); + + double valueForLength(const Length& length, LengthMode mode) const; + +private: + const Element* m_element{nullptr}; + Units m_units{Units::UserSpaceOnUse}; +}; + +enum class Align +{ + None, + xMinYMin, + xMidYMin, + xMaxYMin, + xMinYMid, + xMidYMid, + xMaxYMid, + xMinYMax, + xMidYMax, + xMaxYMax +}; + +enum class MeetOrSlice +{ + Meet, + Slice +}; + +class PreserveAspectRatio +{ +public: + PreserveAspectRatio() = default; + PreserveAspectRatio(Align align, MeetOrSlice scale); + + Transform getMatrix(double width, double height, const Rect& viewBox) const; + Rect getClip(double width, double height, const Rect& viewBox) const; + + Align align() const { return m_align; } + MeetOrSlice scale() const { return m_scale; } + +private: + Align m_align{Align::xMidYMid}; + MeetOrSlice m_scale{MeetOrSlice::Meet}; +}; + +enum class MarkerOrient +{ + Auto, + Angle +}; + +class Angle +{ +public: + Angle() = default; + Angle(MarkerOrient type); + Angle(double value, MarkerOrient type); + + double value() const { return m_value; } + MarkerOrient type() const { return m_type; } + +private: + double m_value{0}; + MarkerOrient m_type{MarkerOrient::Angle}; +}; + +} // namespace lunasvg + +#endif // PROPERTY_H diff --git a/source/stopelement.cpp b/source/stopelement.cpp new file mode 100644 index 000000000..453138356 --- /dev/null +++ b/source/stopelement.cpp @@ -0,0 +1,29 @@ +#include "stopelement.h" +#include "parser.h" + +namespace lunasvg { + +StopElement::StopElement() + : StyledElement(ElementId::Stop) +{ +} + +double StopElement::offset() const +{ + auto& value = get(PropertyId::Offset); + return Parser::parseNumberPercentage(value, 0.0); +} + +Color StopElement::stopColorWithOpacity() const +{ + auto color = stop_color(); + color.a = stop_opacity(); + return color; +} + +std::unique_ptr StopElement::clone() const +{ + return cloneElement(); +} + +} // namespace lunasvg diff --git a/source/stopelement.h b/source/stopelement.h new file mode 100644 index 000000000..ee97f2830 --- /dev/null +++ b/source/stopelement.h @@ -0,0 +1,21 @@ +#ifndef STOPELEMENT_H +#define STOPELEMENT_H + +#include "styledelement.h" + +namespace lunasvg { + +class StopElement : public StyledElement +{ +public: + StopElement(); + + double offset() const; + Color stopColorWithOpacity() const; + + std::unique_ptr clone() const; +}; + +} // namespace lunasvg + +#endif // STOPELEMENT_H diff --git a/source/styledelement.cpp b/source/styledelement.cpp new file mode 100644 index 000000000..bfb5ad8f0 --- /dev/null +++ b/source/styledelement.cpp @@ -0,0 +1,177 @@ +#include "styledelement.h" +#include "parser.h" + +namespace lunasvg { + +StyledElement::StyledElement(ElementId id) + : Element(id) +{ +} + +Paint StyledElement::fill() const +{ + auto& value = find(PropertyId::Fill); + return Parser::parsePaint(value, this, Color::Black); +} + +Paint StyledElement::stroke() const +{ + auto& value = find(PropertyId::Stroke); + return Parser::parsePaint(value, this, Color::Transparent); +} + +Color StyledElement::color() const +{ + auto& value = find(PropertyId::Color); + return Parser::parseColor(value, this, Color::Black); +} + +Color StyledElement::stop_color() const +{ + auto& value = find(PropertyId::Stop_Color); + return Parser::parseColor(value, this, Color::Black); +} + +Color StyledElement::solid_color() const +{ + auto& value = find(PropertyId::Solid_Color); + return Parser::parseColor(value, this, Color::Black); +} + +double StyledElement::opacity() const +{ + auto& value = get(PropertyId::Opacity); + return Parser::parseNumberPercentage(value, 1.0); +} + +double StyledElement::fill_opacity() const +{ + auto& value = find(PropertyId::Fill_Opacity); + return Parser::parseNumberPercentage(value, 1.0); +} + +double StyledElement::stroke_opacity() const +{ + auto& value = find(PropertyId::Stroke_Opacity); + return Parser::parseNumberPercentage(value, 1.0); +} + +double StyledElement::stop_opacity() const +{ + auto& value = find(PropertyId::Stop_Opacity); + return Parser::parseNumberPercentage(value, 1.0); +} + +double StyledElement::solid_opacity() const +{ + auto& value = find(PropertyId::Solid_Opacity); + return Parser::parseNumberPercentage(value, 1.0); +} + +double StyledElement::stroke_miterlimit() const +{ + auto& value = find(PropertyId::Stroke_Miterlimit); + return Parser::parseNumber(value, 4.0); +} + +Length StyledElement::stroke_width() const +{ + auto& value = find(PropertyId::Stroke_Width); + return Parser::parseLength(value, ForbidNegativeLengths, Length::One); +} + +Length StyledElement::stroke_dashoffset() const +{ + auto& value = find(PropertyId::Stroke_Dashoffset); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +LengthList StyledElement::stroke_dasharray() const +{ + auto& value = find(PropertyId::Stroke_Dasharray); + return Parser::parseLengthList(value, ForbidNegativeLengths); +} + +WindRule StyledElement::fill_rule() const +{ + auto& value = find(PropertyId::Fill_Rule); + return Parser::parseWindRule(value); +} + +WindRule StyledElement::clip_rule() const +{ + auto& value = find(PropertyId::Clip_Rule); + return Parser::parseWindRule(value); +} + +LineCap StyledElement::stroke_linecap() const +{ + auto& value = find(PropertyId::Stroke_Linecap); + return Parser::parseLineCap(value); +} + +LineJoin StyledElement::stroke_linejoin() const +{ + auto& value = find(PropertyId::Stroke_Linejoin); + return Parser::parseLineJoin(value); +} + +Display StyledElement::display() const +{ + auto& value = get(PropertyId::Display); + return Parser::parseDisplay(value); +} + +Visibility StyledElement::visibility() const +{ + auto& value = find(PropertyId::Visibility); + return Parser::parseVisibility(value); +} + +Overflow StyledElement::overflow() const +{ + auto& value = get(PropertyId::Overflow); + return Parser::parseOverflow(value, parent == nullptr ? Overflow::Visible : Overflow::Hidden); +} + +std::string StyledElement::clip_path() const +{ + auto& value = get(PropertyId::Clip_Path); + return Parser::parseUrl(value); +} + +std::string StyledElement::mask() const +{ + auto& value = get(PropertyId::Mask); + return Parser::parseUrl(value); +} + +std::string StyledElement::marker_start() const +{ + auto& value = find(PropertyId::Marker_Start); + return Parser::parseUrl(value); +} + +std::string StyledElement::marker_mid() const +{ + auto& value = find(PropertyId::Marker_Mid); + return Parser::parseUrl(value); +} + +std::string StyledElement::marker_end() const +{ + auto& value = find(PropertyId::Marker_End); + return Parser::parseUrl(value); +} + +bool StyledElement::isDisplayNone() const +{ + return display() == Display::None; +} + +bool StyledElement::isOverflowHidden() const +{ + return overflow() == Overflow::Hidden; +} + +} // namespace lunasvg diff --git a/source/styledelement.h b/source/styledelement.h new file mode 100644 index 000000000..1257548ad --- /dev/null +++ b/source/styledelement.h @@ -0,0 +1,53 @@ +#ifndef STYLEDELEMENT_H +#define STYLEDELEMENT_H + +#include "element.h" + +namespace lunasvg { + +class StyledElement : public Element +{ +public: + StyledElement(ElementId id); + + Paint fill() const; + Paint stroke() const; + + Color color() const; + Color stop_color() const; + Color solid_color() const; + + double opacity() const; + double fill_opacity() const; + double stroke_opacity() const; + double stop_opacity() const; + double solid_opacity() const; + double stroke_miterlimit() const; + + Length stroke_width() const; + Length stroke_dashoffset() const; + LengthList stroke_dasharray() const; + + WindRule fill_rule() const; + WindRule clip_rule() const; + + LineCap stroke_linecap() const; + LineJoin stroke_linejoin() const; + + Display display() const; + Visibility visibility() const; + Overflow overflow() const; + + std::string clip_path() const; + std::string mask() const; + std::string marker_start() const; + std::string marker_mid() const; + std::string marker_end() const; + + bool isDisplayNone() const; + bool isOverflowHidden() const; +}; + +} // namespace lunasvg + +#endif // STYLEDELEMENT_H diff --git a/source/styleelement.cpp b/source/styleelement.cpp new file mode 100644 index 000000000..800146cf5 --- /dev/null +++ b/source/styleelement.cpp @@ -0,0 +1,15 @@ +#include "styleelement.h" + +namespace lunasvg { + +StyleElement::StyleElement() + : Element(ElementId::Style) +{ +} + +std::unique_ptr StyleElement::clone() const +{ + return cloneElement(); +} + +} // namespace lunasvg diff --git a/source/styleelement.h b/source/styleelement.h new file mode 100644 index 000000000..7c46af155 --- /dev/null +++ b/source/styleelement.h @@ -0,0 +1,18 @@ +#ifndef STYLEELEMENT_H +#define STYLEELEMENT_H + +#include "element.h" + +namespace lunasvg { + +class StyleElement : public Element +{ +public: + StyleElement(); + + std::unique_ptr clone() const; +}; + +} // namespace lunasvg + +#endif // STYLEELEMENT_H diff --git a/source/svgelement.cpp b/source/svgelement.cpp new file mode 100644 index 000000000..1468045c8 --- /dev/null +++ b/source/svgelement.cpp @@ -0,0 +1,121 @@ +#include "svgelement.h" +#include "parser.h" +#include "layoutcontext.h" + +namespace lunasvg { + +SVGElement::SVGElement() + : GraphicsElement(ElementId::Svg) +{ +} + +Length SVGElement::x() const +{ + auto& value = get(PropertyId::X); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length SVGElement::y() const +{ + auto& value = get(PropertyId::Y); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length SVGElement::width() const +{ + auto& value = get(PropertyId::Width); + return Parser::parseLength(value, ForbidNegativeLengths, Length::HundredPercent); +} + +Length SVGElement::height() const +{ + auto& value = get(PropertyId::Height); + return Parser::parseLength(value, ForbidNegativeLengths, Length::HundredPercent); +} + +Rect SVGElement::viewBox() const +{ + auto& value = get(PropertyId::ViewBox); + return Parser::parseViewBox(value); +} + +PreserveAspectRatio SVGElement::preserveAspectRatio() const +{ + auto& value = get(PropertyId::PreserveAspectRatio); + return Parser::parsePreserveAspectRatio(value); +} + +std::unique_ptr SVGElement::layoutDocument(const ParseDocument* document) const +{ + if(isDisplayNone()) + return nullptr; + + auto w = this->width(); + auto h = this->height(); + if(w.isZero() || h.isZero()) + return nullptr; + + LengthContext lengthContext(this); + auto _x = lengthContext.valueForLength(x(), LengthMode::Width); + auto _y = lengthContext.valueForLength(y(), LengthMode::Height); + auto _w = lengthContext.valueForLength(w, LengthMode::Width); + auto _h = lengthContext.valueForLength(h, LengthMode::Height); + + auto viewBox = this->viewBox(); + auto preserveAspectRatio = this->preserveAspectRatio(); + auto viewTranslation = Transform::translated(_x, _y); + auto viewTransform = preserveAspectRatio.getMatrix(_w, _h, viewBox); + + auto root = std::make_unique(); + root->width = _w; + root->height = _h; + root->transform = (viewTransform * viewTranslation) * transform(); + root->clip = isOverflowHidden() ? preserveAspectRatio.getClip(_w, _h, viewBox) : Rect::Invalid; + root->opacity = opacity(); + + LayoutContext context(document, root.get()); + root->masker = context.getMasker(mask()); + root->clipper = context.getClipper(clip_path()); + layoutChildren(&context, root.get()); + return root; +} + +void SVGElement::layout(LayoutContext* context, LayoutContainer* current) const +{ + if(isDisplayNone()) + return; + + auto w = this->width(); + auto h = this->height(); + if(w.isZero() || h.isZero()) + return; + + LengthContext lengthContext(this); + auto _x = lengthContext.valueForLength(x(), LengthMode::Width); + auto _y = lengthContext.valueForLength(y(), LengthMode::Height); + auto _w = lengthContext.valueForLength(w, LengthMode::Width); + auto _h = lengthContext.valueForLength(h, LengthMode::Height); + + auto viewBox = this->viewBox(); + auto preserveAspectRatio = this->preserveAspectRatio(); + auto viewTranslation = Transform::translated(_x, _y); + auto viewTransform = preserveAspectRatio.getMatrix(_w, _h, viewBox); + + auto symbol = std::make_unique(); + symbol->width = _w; + symbol->height = _h; + symbol->transform = (viewTransform * viewTranslation) * transform(); + symbol->clip = isOverflowHidden() ? preserveAspectRatio.getClip(_w, _h, viewBox) : Rect::Invalid; + symbol->opacity = opacity(); + symbol->masker = context->getMasker(mask()); + symbol->clipper = context->getClipper(clip_path()); + layoutChildren(context, symbol.get()); + current->addChildIfNotEmpty(std::move(symbol)); +} + +std::unique_ptr SVGElement::clone() const +{ + return cloneElement(); +} + +} // namespace lunasvg diff --git a/source/svgelement.h b/source/svgelement.h new file mode 100644 index 000000000..b6e62343e --- /dev/null +++ b/source/svgelement.h @@ -0,0 +1,31 @@ +#ifndef SVGELEMENT_H +#define SVGELEMENT_H + +#include "graphicselement.h" + +namespace lunasvg { + +class ParseDocument; +class LayoutSymbol; + +class SVGElement : public GraphicsElement +{ +public: + SVGElement(); + + Length x() const; + Length y() const; + Length width() const; + Length height() const; + + Rect viewBox() const; + PreserveAspectRatio preserveAspectRatio() const; + std::unique_ptr layoutDocument(const ParseDocument* document) const; + + void layout(LayoutContext* context, LayoutContainer* current) const; + std::unique_ptr clone() const; +}; + +} // namespace lunasvg + +#endif // SVGELEMENT_H diff --git a/source/symbolelement.cpp b/source/symbolelement.cpp new file mode 100644 index 000000000..10d451cb7 --- /dev/null +++ b/source/symbolelement.cpp @@ -0,0 +1,52 @@ +#include "symbolelement.h" +#include "parser.h" + +namespace lunasvg { + +SymbolElement::SymbolElement() + : StyledElement(ElementId::Symbol) +{ +} + +Length SymbolElement::x() const +{ + auto& value = get(PropertyId::X); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length SymbolElement::y() const +{ + auto& value = get(PropertyId::Y); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length SymbolElement::width() const +{ + auto& value = get(PropertyId::Width); + return Parser::parseLength(value, ForbidNegativeLengths, Length::HundredPercent); +} + +Length SymbolElement::height() const +{ + auto& value = get(PropertyId::Height); + return Parser::parseLength(value, ForbidNegativeLengths, Length::HundredPercent); +} + +Rect SymbolElement::viewBox() const +{ + auto& value = get(PropertyId::ViewBox); + return Parser::parseViewBox(value); +} + +PreserveAspectRatio SymbolElement::preserveAspectRatio() const +{ + auto& value = get(PropertyId::PreserveAspectRatio); + return Parser::parsePreserveAspectRatio(value); +} + +std::unique_ptr SymbolElement::clone() const +{ + return cloneElement(); +} + +} // namespace lunasvg diff --git a/source/symbolelement.h b/source/symbolelement.h new file mode 100644 index 000000000..e2345f7ad --- /dev/null +++ b/source/symbolelement.h @@ -0,0 +1,25 @@ +#ifndef SYMBOLELEMENT_H +#define SYMBOLELEMENT_H + +#include "styledelement.h" + +namespace lunasvg { + +class SymbolElement : public StyledElement +{ +public: + SymbolElement(); + + Length x() const; + Length y() const; + Length width() const; + Length height() const; + Rect viewBox() const; + PreserveAspectRatio preserveAspectRatio() const; + + std::unique_ptr clone() const; +}; + +} // namespace lunasvg + +#endif // SYMBOLELEMENT_H diff --git a/source/useelement.cpp b/source/useelement.cpp new file mode 100644 index 000000000..f67383303 --- /dev/null +++ b/source/useelement.cpp @@ -0,0 +1,99 @@ +#include "useelement.h" +#include "parser.h" +#include "layoutcontext.h" + +#include "gelement.h" +#include "svgelement.h" + +namespace lunasvg { + +UseElement::UseElement() + : GraphicsElement(ElementId::Use) +{ +} + +Length UseElement::x() const +{ + auto& value = get(PropertyId::X); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length UseElement::y() const +{ + auto& value = get(PropertyId::Y); + return Parser::parseLength(value, AllowNegativeLengths, Length::Zero); +} + +Length UseElement::width() const +{ + auto& value = get(PropertyId::Width); + return Parser::parseLength(value, ForbidNegativeLengths, Length::HundredPercent); +} + +Length UseElement::height() const +{ + auto& value = get(PropertyId::Height); + return Parser::parseLength(value, ForbidNegativeLengths, Length::HundredPercent); +} + +std::string UseElement::href() const +{ + auto& value = get(PropertyId::Href); + return Parser::parseHref(value); +} + +void UseElement::transferWidthAndHeight(Element* element) const +{ + auto& width = get(PropertyId::Width); + auto& height = get(PropertyId::Height); + + element->set(PropertyId::Width, width, 0x0); + element->set(PropertyId::Height, height, 0x0); +} + +void UseElement::layout(LayoutContext* context, LayoutContainer* current) const +{ + if(isDisplayNone()) + return; + + auto ref = context->getElementById(href()); + if(ref == nullptr || context->hasReference(ref) || (current->id == LayoutId::ClipPath && !ref->isGeometry())) + return; + + LayoutBreaker layoutBreaker(context, ref); + auto group = std::make_unique(); + group->parent = parent; + group->properties = properties; + + LengthContext lengthContext(this); + auto _x = lengthContext.valueForLength(x(), LengthMode::Width); + auto _y = lengthContext.valueForLength(y(), LengthMode::Height); + + auto transform = get(PropertyId::Transform); + transform += "translate("; + transform += std::to_string(_x); + transform += ' '; + transform += std::to_string(_y); + transform += ')'; + group->set(PropertyId::Transform, transform, 0x10); + + if(ref->id == ElementId::Svg || ref->id == ElementId::Symbol) + { + auto element = ref->cloneElement(); + transferWidthAndHeight(element.get()); + group->addChild(std::move(element)); + } + else + { + group->addChild(ref->clone()); + } + + group->layout(context, current); +} + +std::unique_ptr UseElement::clone() const +{ + return cloneElement(); +} + +} // namespace lunasvg diff --git a/source/useelement.h b/source/useelement.h new file mode 100644 index 000000000..ea898f232 --- /dev/null +++ b/source/useelement.h @@ -0,0 +1,26 @@ +#ifndef USEELEMENT_H +#define USEELEMENT_H + +#include "graphicselement.h" + +namespace lunasvg { + +class UseElement : public GraphicsElement +{ +public: + UseElement(); + + Length x() const; + Length y() const; + Length width() const; + Length height() const; + std::string href() const; + void transferWidthAndHeight(Element* element) const; + + void layout(LayoutContext* context, LayoutContainer* current) const; + std::unique_ptr clone() const; +}; + +} // namespace lunasvg + +#endif // USEELEMENT_H