diff --git a/api/tests/integration/tests/rendering/ref/linux/images.png b/api/tests/integration/tests/rendering/ref/linux/images.png index 199f0df18b..fc83e7b0ab 100644 Binary files a/api/tests/integration/tests/rendering/ref/linux/images.png and b/api/tests/integration/tests/rendering/ref/linux/images.png differ diff --git a/api/tests/integration/tests/rendering/ref/mac/images.png b/api/tests/integration/tests/rendering/ref/mac/images.png index 199f0df18b..fc83e7b0ab 100644 Binary files a/api/tests/integration/tests/rendering/ref/mac/images.png and b/api/tests/integration/tests/rendering/ref/mac/images.png differ diff --git a/api/tests/integration/tests/rendering/ref/win/images.png b/api/tests/integration/tests/rendering/ref/win/images.png index 199f0df18b..fc83e7b0ab 100644 Binary files a/api/tests/integration/tests/rendering/ref/win/images.png and b/api/tests/integration/tests/rendering/ref/win/images.png differ diff --git a/api/wasm/indigo-ketcher/CMakeLists.txt b/api/wasm/indigo-ketcher/CMakeLists.txt index 0393f036c6..f78553321a 100644 --- a/api/wasm/indigo-ketcher/CMakeLists.txt +++ b/api/wasm/indigo-ketcher/CMakeLists.txt @@ -47,7 +47,7 @@ set(EMCC_FLAGS set(TARGET_FILES $ - $ $ $ $ $ $ $ $ $ $ $ $ + $ $ $ $ $ $ $ $ $ $ $ $ $ ) set(TARGET_FILES_NORENDER @@ -122,4 +122,4 @@ else () COMMAND ${COPY_COMMAND} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}${SEP}${PROJECT_NAME}*.tgz ${NATIVE_DIST_DIRECTORY}${SEP} DEPENDS ${PROJECT_NAME} ${PROJECT_NAME}-separate ${PROJECT_NAME}-separate-norender ${PROJECT_NAME}-norender ) -endif () \ No newline at end of file +endif () diff --git a/cmake/setup.cmake b/cmake/setup.cmake index c617a4be3a..ef944d40ac 100644 --- a/cmake/setup.cmake +++ b/cmake/setup.cmake @@ -57,6 +57,9 @@ if (UNIX OR MINGW) string(APPEND CMAKE_C_FLAGS " -fvisibility=hidden $ENV{CFLAGS}") string(APPEND CMAKE_CXX_FLAGS " -fvisibility=hidden -fvisibility-inlines-hidden $ENV{CXXFLAGS}") + set(CMAKE_C_FLAGS_DEBUG "-g3") + set(CMAKE_CXX_FLAGS_DEBUG "-g3") + if(BUILD_STANDALONE AND NOT EMSCRIPTEN) if (CMAKE_CXX_COMPILER_ID STREQUAL GNU) string(APPEND CMAKE_CXX_FLAGS " -static-libstdc++") diff --git a/core/render2d/CMakeLists.txt b/core/render2d/CMakeLists.txt index 2df436856c..9fb3f7554c 100644 --- a/core/render2d/CMakeLists.txt +++ b/core/render2d/CMakeLists.txt @@ -38,6 +38,8 @@ if (RENDER_ENABLE_CJK) target_compile_definitions(${PROJECT_NAME} PRIVATE RENDER_ENABLE_CJK) endif() +target_link_libraries(${PROJECT_NAME} PUBLIC lunasvg) + if (BUILD_STANDALONE) target_link_libraries(${PROJECT_NAME} PUBLIC rapidjson diff --git a/core/render2d/src/render_item_aux.cpp b/core/render2d/src/render_item_aux.cpp index 0d026a3ee1..41caafde31 100644 --- a/core/render2d/src/render_item_aux.cpp +++ b/core/render2d/src/render_item_aux.cpp @@ -28,8 +28,12 @@ #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4996) +#pragma warning(disable : 4251) #endif +#include +#include + using namespace indigo; IMPL_ERROR(RenderItemAuxiliary, "RenderItemAuxiliary"); @@ -386,6 +390,16 @@ void RenderItemAuxiliary::_drawMeta(bool idle) } } +struct StbiContext +{ + std::string data; +}; + +void ketImageStbiWriteFunc(void* context, void* data, int size) +{ + static_cast(context)->data.assign(static_cast(data), size); +} + void RenderItemAuxiliary::_drawImage(const KETImage& img) { auto& bb = img.getBoundingBox(); @@ -395,9 +409,22 @@ void RenderItemAuxiliary::_drawImage(const KETImage& img) scale(v2); if (img.getFormat() == KETImage::EKETPNG) _rc.drawPng(img.getData(), Rect2f(v1, v2)); - else if (img.getFormat() == KETImage::EKETPNG) + else if (img.getFormat() == KETImage::EKETSVG) { - // TODO: implement SVG-rendering + auto document = lunasvg::Document::loadFromData(img.getData()); + if (!document) + throw Error("RenderItemAuxiliary::_drawImage: loadFromData error"); + + auto bitmap = document->renderToBitmap(); + if (!bitmap.valid()) + throw Error("RenderItemAuxiliary::_drawImage: renderToBitmap error"); + + StbiContext stbiContext; + int rgbaChannels = 4, stride = 0; + if (!stbi_write_png_to_func(ketImageStbiWriteFunc, &stbiContext, bitmap.width(), bitmap.height(), rgbaChannels, bitmap.data(), stride)) + throw Error("RenderItemAuxiliary::_drawImage: stbi_write_png_to_func error"); + + _rc.drawPng(stbiContext.data, Rect2f(v1, v2)); } } diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index c7f0e4eba5..9aa46bb936 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -1,4 +1,10 @@ add_subdirectory(libpng) + +option(BUILD_SHARED_LIBS OFF) +add_subdirectory(lunasvg) +target_include_directories(lunasvg INTERFACE lunasvg/3rdparty/stb) +target_compile_definitions(lunasvg INTERFACE STB_IMAGE_WRITE_IMPLEMENTATION) + if (BUILD_STANDALONE) # InChI don't have a Conan package yet add_subdirectory(cppcodec) @@ -20,4 +26,4 @@ if (ENABLE_TESTS) add_subdirectory(googletest) endif() -add_subdirectory(object_threadsafe) \ No newline at end of file +add_subdirectory(object_threadsafe) diff --git a/third_party/lunasvg/3rdparty/plutovg/CMakeLists.txt b/third_party/lunasvg/3rdparty/plutovg/CMakeLists.txt new file mode 100644 index 0000000000..62a92756ce --- /dev/null +++ b/third_party/lunasvg/3rdparty/plutovg/CMakeLists.txt @@ -0,0 +1,17 @@ +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" + "${CMAKE_CURRENT_LIST_DIR}/plutovg-ft-raster.c" + "${CMAKE_CURRENT_LIST_DIR}/plutovg-ft-stroker.c" + "${CMAKE_CURRENT_LIST_DIR}/plutovg-ft-math.c" +) + +target_include_directories(lunasvg +PRIVATE + "${CMAKE_CURRENT_LIST_DIR}" +) diff --git a/third_party/lunasvg/3rdparty/plutovg/FTL.TXT b/third_party/lunasvg/3rdparty/plutovg/FTL.TXT new file mode 100644 index 0000000000..5f673ffad1 --- /dev/null +++ b/third_party/lunasvg/3rdparty/plutovg/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/third_party/lunasvg/3rdparty/plutovg/plutovg-blend.c b/third_party/lunasvg/3rdparty/plutovg/plutovg-blend.c new file mode 100644 index 0000000000..a8a0c2a9ce --- /dev/null +++ b/third_party/lunasvg/3rdparty/plutovg/plutovg-blend.c @@ -0,0 +1,831 @@ +#include "plutovg-private.h" + +#include +#include +#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; + det = fabs(det) < DBL_EPSILON ? 0.0 : det; + 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) + { + det = fabs(det) < DBL_EPSILON ? 0.0 : det; + uint32_t result = 0; + if (det >= 0) + result = gradient_pixel(gradient, sqrt(det) - b); + *buffer++ = result; + 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 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 = plutovg_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 = plutovg_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 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 = plutovg_min(length, BUFFER_SIZE); + const uint32_t* end = buffer + l; + uint32_t* b = buffer; + while(b < end) + { + int px = plutovg_clamp(x >> 16, 0, image_width - 1); + int py = plutovg_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 = plutovg_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 = plutovg_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->paint; + 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/third_party/lunasvg/3rdparty/plutovg/plutovg-dash.c b/third_party/lunasvg/3rdparty/plutovg/plutovg-dash.c new file mode 100644 index 0000000000..40a8027027 --- /dev/null +++ b/third_party/lunasvg/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/third_party/lunasvg/3rdparty/plutovg/plutovg-ft-math.c b/third_party/lunasvg/3rdparty/plutovg/plutovg-ft-math.c new file mode 100644 index 0000000000..417242b239 --- /dev/null +++ b/third_party/lunasvg/3rdparty/plutovg/plutovg-ft-math.c @@ -0,0 +1,446 @@ +/***************************************************************************/ +/* */ +/* 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 "plutovg-ft-math.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 PVG_FT_MSB(x) (clz(x)) +#elif defined(__GNUC__) +#define PVG_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 PVG_FT_MSB(x) (clz(x)) +#endif + +#define PVG_FT_PAD_FLOOR(x, n) ((x) & ~((n)-1)) +#define PVG_FT_PAD_ROUND(x, n) PVG_FT_PAD_FLOOR((x) + ((n) / 2), n) +#define PVG_FT_PAD_CEIL(x, n) PVG_FT_PAD_FLOOR((x) + ((n)-1), n) + +#define PVG_FT_BEGIN_STMNT do { +#define PVG_FT_END_STMNT } while (0) + +/* transfer sign leaving a positive number */ +#define PVG_FT_MOVE_SIGN(x, s) \ + PVG_FT_BEGIN_STMNT \ + if (x < 0) { \ + x = -x; \ + s = -s; \ + } \ + PVG_FT_END_STMNT + +PVG_FT_Long PVG_FT_MulFix(PVG_FT_Long a, PVG_FT_Long b) +{ + PVG_FT_Int s = 1; + PVG_FT_Long c; + + PVG_FT_MOVE_SIGN(a, s); + PVG_FT_MOVE_SIGN(b, s); + + c = (PVG_FT_Long)(((PVG_FT_Int64)a * b + 0x8000L) >> 16); + + return (s > 0) ? c : -c; +} + +PVG_FT_Long PVG_FT_MulDiv(PVG_FT_Long a, PVG_FT_Long b, PVG_FT_Long c) +{ + PVG_FT_Int s = 1; + PVG_FT_Long d; + + PVG_FT_MOVE_SIGN(a, s); + PVG_FT_MOVE_SIGN(b, s); + PVG_FT_MOVE_SIGN(c, s); + + d = (PVG_FT_Long)(c > 0 ? ((PVG_FT_Int64)a * b + (c >> 1)) / c : 0x7FFFFFFFL); + + return (s > 0) ? d : -d; +} + +PVG_FT_Long PVG_FT_DivFix(PVG_FT_Long a, PVG_FT_Long b) +{ + PVG_FT_Int s = 1; + PVG_FT_Long q; + + PVG_FT_MOVE_SIGN(a, s); + PVG_FT_MOVE_SIGN(b, s); + + q = (PVG_FT_Long)(b > 0 ? (((PVG_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 PVG_FT_TRIG_SCALE 0xDBD95B16UL + +/* the highest bit in overflow-safe vector components, */ +/* MSB of 0.858785336480436 * sqrt(0.5) * 2^30 */ +#define PVG_FT_TRIG_SAFE_MSB 29 + +/* this table was generated for PVG_FT_PI = 180L << 16, i.e. degrees */ +#define PVG_FT_TRIG_MAX_ITERS 23 + +static const PVG_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 PVG_FT_Fixed ft_trig_downscale(PVG_FT_Fixed val) +{ + PVG_FT_Fixed s; + PVG_FT_Int64 v; + + s = val; + val = PVG_FT_ABS(val); + + v = (val * (PVG_FT_Int64)PVG_FT_TRIG_SCALE) + 0x100000000UL; + val = (PVG_FT_Fixed)(v >> 32); + + return (s >= 0) ? val : -val; +} + +/* undefined and never called for zero vector */ +static PVG_FT_Int ft_trig_prenorm(PVG_FT_Vector* vec) +{ + PVG_FT_Pos x, y; + PVG_FT_Int shift; + + x = vec->x; + y = vec->y; + + shift = PVG_FT_MSB(PVG_FT_ABS(x) | PVG_FT_ABS(y)); + + if (shift <= PVG_FT_TRIG_SAFE_MSB) { + shift = PVG_FT_TRIG_SAFE_MSB - shift; + vec->x = (PVG_FT_Pos)((PVG_FT_ULong)x << shift); + vec->y = (PVG_FT_Pos)((PVG_FT_ULong)y << shift); + } else { + shift -= PVG_FT_TRIG_SAFE_MSB; + vec->x = x >> shift; + vec->y = y >> shift; + shift = -shift; + } + + return shift; +} + +static void ft_trig_pseudo_rotate(PVG_FT_Vector* vec, PVG_FT_Angle theta) +{ + PVG_FT_Int i; + PVG_FT_Fixed x, y, xtemp, b; + const PVG_FT_Fixed* arctanptr; + + x = vec->x; + y = vec->y; + + /* Rotate inside [-PI/4,PI/4] sector */ + while (theta < -PVG_FT_ANGLE_PI4) { + xtemp = y; + y = -x; + x = xtemp; + theta += PVG_FT_ANGLE_PI2; + } + + while (theta > PVG_FT_ANGLE_PI4) { + xtemp = -y; + y = x; + x = xtemp; + theta -= PVG_FT_ANGLE_PI2; + } + + arctanptr = ft_trig_arctan_table; + + /* Pseudorotations, with right shifts */ + for (i = 1, b = 1; i < PVG_FT_TRIG_MAX_ITERS; b <<= 1, i++) { + PVG_FT_Fixed v1 = ((y + b) >> i); + PVG_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(PVG_FT_Vector* vec) +{ + PVG_FT_Angle theta; + PVG_FT_Int i; + PVG_FT_Fixed x, y, xtemp, b; + const PVG_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 = PVG_FT_ANGLE_PI2; + xtemp = y; + y = -x; + x = xtemp; + } else { + theta = y > 0 ? PVG_FT_ANGLE_PI : -PVG_FT_ANGLE_PI; + x = -x; + y = -y; + } + } else { + if (y < -x) { + theta = -PVG_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 < PVG_FT_TRIG_MAX_ITERS; b <<= 1, i++) { + PVG_FT_Fixed v1 = ((y + b) >> i); + PVG_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 = PVG_FT_PAD_ROUND(theta, 32); + else + theta = -PVG_FT_PAD_ROUND(-theta, 32); + + vec->x = x; + vec->y = theta; +} + +/* documentation is in fttrigon.h */ + +PVG_FT_Fixed PVG_FT_Cos(PVG_FT_Angle angle) +{ + PVG_FT_Vector v; + + v.x = PVG_FT_TRIG_SCALE >> 8; + v.y = 0; + ft_trig_pseudo_rotate(&v, angle); + + return (v.x + 0x80L) >> 8; +} + +/* documentation is in fttrigon.h */ + +PVG_FT_Fixed PVG_FT_Sin(PVG_FT_Angle angle) +{ + return PVG_FT_Cos(PVG_FT_ANGLE_PI2 - angle); +} + +/* documentation is in fttrigon.h */ + +PVG_FT_Fixed PVG_FT_Tan(PVG_FT_Angle angle) +{ + PVG_FT_Vector v; + + v.x = PVG_FT_TRIG_SCALE >> 8; + v.y = 0; + ft_trig_pseudo_rotate(&v, angle); + + return PVG_FT_DivFix(v.y, v.x); +} + +/* documentation is in fttrigon.h */ + +PVG_FT_Angle PVG_FT_Atan2(PVG_FT_Fixed dx, PVG_FT_Fixed dy) +{ + PVG_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 PVG_FT_Vector_Unit(PVG_FT_Vector* vec, PVG_FT_Angle angle) +{ + vec->x = PVG_FT_TRIG_SCALE >> 8; + vec->y = 0; + ft_trig_pseudo_rotate(vec, angle); + vec->x = (vec->x + 0x80L) >> 8; + vec->y = (vec->y + 0x80L) >> 8; +} + +void PVG_FT_Vector_Rotate(PVG_FT_Vector* vec, PVG_FT_Angle angle) +{ + PVG_FT_Int shift; + PVG_FT_Vector v = *vec; + + if ( v.x == 0 && v.y == 0 ) + return; + + shift = ft_trig_prenorm( &v ); + ft_trig_pseudo_rotate( &v, angle ); + v.x = ft_trig_downscale( v.x ); + v.y = ft_trig_downscale( v.y ); + + if ( shift > 0 ) + { + PVG_FT_Int32 half = (PVG_FT_Int32)1L << ( shift - 1 ); + + + vec->x = ( v.x + half - ( v.x < 0 ) ) >> shift; + vec->y = ( v.y + half - ( v.y < 0 ) ) >> shift; + } + else + { + shift = -shift; + vec->x = (PVG_FT_Pos)( (PVG_FT_ULong)v.x << shift ); + vec->y = (PVG_FT_Pos)( (PVG_FT_ULong)v.y << shift ); + } +} + +/* documentation is in fttrigon.h */ + +PVG_FT_Fixed PVG_FT_Vector_Length(PVG_FT_Vector* vec) +{ + PVG_FT_Int shift; + PVG_FT_Vector v; + + v = *vec; + + /* handle trivial cases */ + if (v.x == 0) { + return PVG_FT_ABS(v.y); + } else if (v.y == 0) { + return PVG_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 (PVG_FT_Fixed)((PVG_FT_UInt32)v.x << -shift); +} + +/* documentation is in fttrigon.h */ + +void PVG_FT_Vector_Polarize(PVG_FT_Vector* vec, PVG_FT_Fixed* length, + PVG_FT_Angle* angle) +{ + PVG_FT_Int shift; + PVG_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) + : (PVG_FT_Fixed)((PVG_FT_UInt32)v.x << -shift); + *angle = v.y; +} + +/* documentation is in fttrigon.h */ + +void PVG_FT_Vector_From_Polar(PVG_FT_Vector* vec, PVG_FT_Fixed length, + PVG_FT_Angle angle) +{ + vec->x = length; + vec->y = 0; + + PVG_FT_Vector_Rotate(vec, angle); +} + +/* documentation is in fttrigon.h */ + +PVG_FT_Angle PVG_FT_Angle_Diff( PVG_FT_Angle angle1, PVG_FT_Angle angle2 ) +{ + PVG_FT_Angle delta = angle2 - angle1; + + while ( delta <= -PVG_FT_ANGLE_PI ) + delta += PVG_FT_ANGLE_2PI; + + while ( delta > PVG_FT_ANGLE_PI ) + delta -= PVG_FT_ANGLE_2PI; + + return delta; +} + +/* END */ diff --git a/third_party/lunasvg/3rdparty/plutovg/plutovg-ft-math.h b/third_party/lunasvg/3rdparty/plutovg/plutovg-ft-math.h new file mode 100644 index 0000000000..81c66b81b1 --- /dev/null +++ b/third_party/lunasvg/3rdparty/plutovg/plutovg-ft-math.h @@ -0,0 +1,436 @@ +/***************************************************************************/ +/* */ +/* 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. */ +/* */ +/***************************************************************************/ + +#ifndef PLUTOVG_FT_MATH_H +#define PLUTOVG_FT_MATH_H + +#include "plutovg-ft-types.h" + +/*************************************************************************/ +/* */ +/* The min and max functions missing in C. As usual, be careful not to */ +/* write things like PVG_FT_MIN( a++, b++ ) to avoid side effects. */ +/* */ +#define PVG_FT_MIN( a, b ) ( (a) < (b) ? (a) : (b) ) +#define PVG_FT_MAX( a, b ) ( (a) > (b) ? (a) : (b) ) + +#define PVG_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 PVG_FT_HYPOT( x, y ) \ + ( x = PVG_FT_ABS( x ), \ + y = PVG_FT_ABS( y ), \ + x > y ? x + ( 3 * y >> 3 ) \ + : y + ( 3 * x >> 3 ) ) + +/*************************************************************************/ +/* */ +/* */ +/* PVG_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. */ +/* */ +PVG_FT_Long +PVG_FT_MulFix( PVG_FT_Long a, + PVG_FT_Long b ); + +/*************************************************************************/ +/* */ +/* */ +/* PVG_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'. */ +/* */ +PVG_FT_Long +PVG_FT_MulDiv( PVG_FT_Long a, + PVG_FT_Long b, + PVG_FT_Long c ); + +/*************************************************************************/ +/* */ +/* */ +/* PVG_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'. */ +/* */ +PVG_FT_Long +PVG_FT_DivFix( PVG_FT_Long a, + PVG_FT_Long b ); + + + +/*************************************************************************/ +/* */ +/*
*/ +/* computations */ +/* */ +/*************************************************************************/ + + +/************************************************************************* + * + * @type: + * PVG_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 PVG_FT_Fixed PVG_FT_Angle; + + +/************************************************************************* + * + * @macro: + * PVG_FT_ANGLE_PI + * + * @description: + * The angle pi expressed in @PVG_FT_Angle units. + * + */ +#define PVG_FT_ANGLE_PI ( 180L << 16 ) + + +/************************************************************************* + * + * @macro: + * PVG_FT_ANGLE_2PI + * + * @description: + * The angle 2*pi expressed in @PVG_FT_Angle units. + * + */ +#define PVG_FT_ANGLE_2PI ( PVG_FT_ANGLE_PI * 2 ) + + +/************************************************************************* + * + * @macro: + * PVG_FT_ANGLE_PI2 + * + * @description: + * The angle pi/2 expressed in @PVG_FT_Angle units. + * + */ +#define PVG_FT_ANGLE_PI2 ( PVG_FT_ANGLE_PI / 2 ) + + +/************************************************************************* + * + * @macro: + * PVG_FT_ANGLE_PI4 + * + * @description: + * The angle pi/4 expressed in @PVG_FT_Angle units. + * + */ +#define PVG_FT_ANGLE_PI4 ( PVG_FT_ANGLE_PI / 4 ) + + +/************************************************************************* + * + * @function: + * PVG_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 @PVG_FT_Vector_Unit. + * + */ +PVG_FT_Fixed +PVG_FT_Sin( PVG_FT_Angle angle ); + + +/************************************************************************* + * + * @function: + * PVG_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 @PVG_FT_Vector_Unit. + * + */ +PVG_FT_Fixed +PVG_FT_Cos( PVG_FT_Angle angle ); + + +/************************************************************************* + * + * @function: + * PVG_FT_Tan + * + * @description: + * Return the tangent of a given angle in fixed-point format. + * + * @input: + * angle :: + * The input angle. + * + * @return: + * The tangent value. + * + */ +PVG_FT_Fixed +PVG_FT_Tan( PVG_FT_Angle angle ); + + +/************************************************************************* + * + * @function: + * PVG_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). + * + */ +PVG_FT_Angle +PVG_FT_Atan2( PVG_FT_Fixed x, + PVG_FT_Fixed y ); + + +/************************************************************************* + * + * @function: + * PVG_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'. + * + */ +PVG_FT_Angle +PVG_FT_Angle_Diff( PVG_FT_Angle angle1, + PVG_FT_Angle angle2 ); + + +/************************************************************************* + * + * @function: + * PVG_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 +PVG_FT_Vector_Unit( PVG_FT_Vector* vec, + PVG_FT_Angle angle ); + + +/************************************************************************* + * + * @function: + * PVG_FT_Vector_Rotate + * + * @description: + * Rotate a vector by a given angle. + * + * @inout: + * vec :: + * The address of target vector. + * + * @input: + * angle :: + * The input angle. + * + */ +void +PVG_FT_Vector_Rotate( PVG_FT_Vector* vec, + PVG_FT_Angle angle ); + + +/************************************************************************* + * + * @function: + * PVG_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. + * + */ +PVG_FT_Fixed +PVG_FT_Vector_Length( PVG_FT_Vector* vec ); + + +/************************************************************************* + * + * @function: + * PVG_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 +PVG_FT_Vector_Polarize( PVG_FT_Vector* vec, + PVG_FT_Fixed *length, + PVG_FT_Angle *angle ); + + +/************************************************************************* + * + * @function: + * PVG_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 +PVG_FT_Vector_From_Polar( PVG_FT_Vector* vec, + PVG_FT_Fixed length, + PVG_FT_Angle angle ); + +#endif /* PLUTOVG_FT_MATH_H */ diff --git a/third_party/lunasvg/3rdparty/plutovg/plutovg-ft-raster.c b/third_party/lunasvg/3rdparty/plutovg/plutovg-ft-raster.c new file mode 100644 index 0000000000..dd99c86e0f --- /dev/null +++ b/third_party/lunasvg/3rdparty/plutovg/plutovg-ft-raster.c @@ -0,0 +1,1632 @@ +/***************************************************************************/ +/* */ +/* 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 "plutovg-ft-raster.h" +#include "plutovg-ft-math.h" + +#define PVG_FT_BEGIN_STMNT do { +#define PVG_FT_END_STMNT } while ( 0 ) + +#include + +#define pvg_ft_setjmp setjmp +#define pvg_ft_longjmp longjmp +#define pvg_ft_jmp_buf jmp_buf + +#include + +typedef ptrdiff_t PVG_FT_PtrDist; + +#define ErrRaster_Invalid_Mode -2 +#define ErrRaster_Invalid_Outline -1 +#define ErrRaster_Invalid_Argument -3 +#define ErrRaster_Memory_Overflow -4 +#define ErrRaster_OutOfMemory -6 + +#include +#include + +#define PVG_FT_MINIMUM_POOL_SIZE 8192 + +#define RAS_ARG PWorker worker +#define RAS_ARG_ PWorker worker, + +#define RAS_VAR worker +#define RAS_VAR_ worker, + +#define ras (*worker) + + /* must be at least 6 bits! */ +#define PIXEL_BITS 8 + +#define ONE_PIXEL ( 1L << PIXEL_BITS ) +#define TRUNC( x ) (TCoord)( (x) >> PIXEL_BITS ) +#define FRACT( x ) (TCoord)( (x) & ( ONE_PIXEL - 1 ) ) + +#if PIXEL_BITS >= 6 +#define UPSCALE( x ) ( (x) * ( ONE_PIXEL >> 6 ) ) +#define DOWNSCALE( x ) ( (x) >> ( PIXEL_BITS - 6 ) ) +#else +#define UPSCALE( x ) ( (x) >> ( 6 - PIXEL_BITS ) ) +#define DOWNSCALE( x ) ( (x) * ( 64 >> PIXEL_BITS ) ) +#endif + +/* 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 PVG_FT_DIV_MOD( type, dividend, divisor, quotient, remainder ) \ +PVG_FT_BEGIN_STMNT \ + (quotient) = (type)( (dividend) / (divisor) ); \ + (remainder) = (type)( (dividend) % (divisor) ); \ + if ( (remainder) < 0 ) \ + { \ + (quotient)--; \ + (remainder) += (type)(divisor); \ + } \ +PVG_FT_END_STMNT + + /* These macros speed up repetitive divisions by replacing them */ + /* with multiplications and right shifts. */ +#define PVG_FT_UDIVPREP( b ) \ + long b ## _r = (long)( ULONG_MAX >> PIXEL_BITS ) / ( b ) +#define PVG_FT_UDIV( a, b ) \ + ( ( (unsigned long)( a ) * (unsigned long)( b ## _r ) ) >> \ + ( sizeof( long ) * CHAR_BIT - PIXEL_BITS ) ) + + + /*************************************************************************/ + /* */ + /* TYPE DEFINITIONS */ + /* */ + + /* don't change the following types to PVG_FT_Int or PVG_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 */ + typedef long TArea ; /* cell areas, coordinate products */ + + /* maximal number of gray spans in a call to the span callback */ +#define PVG_FT_MAX_GRAY_SPANS 256 + + + typedef struct TCell_* PCell; + + typedef struct TCell_ + { + int x; + int cover; + TArea area; + PCell next; + + } TCell; + + + typedef struct TWorker_ + { + TCoord ex, ey; + TPos min_ex, max_ex; + TPos min_ey, max_ey; + TPos count_ex, count_ey; + + TArea area; + int cover; + int invalid; + + PCell cells; + PVG_FT_PtrDist max_cells; + PVG_FT_PtrDist num_cells; + + TPos x, y; + + PVG_FT_Outline outline; + PVG_FT_BBox clip_box; + + PVG_FT_Span gray_spans[PVG_FT_MAX_GRAY_SPANS]; + int num_gray_spans; + int skip_spans; + + PVG_FT_Raster_Span_Func render_span; + void* render_span_data; + + int band_size; + int band_shoot; + + pvg_ft_jmp_buf jump_buffer; + + void* buffer; + long buffer_size; + + PCell* ycells; + TPos ycount; + } TWorker, *PWorker; + + + /*************************************************************************/ + /* */ + /* 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; + } + + + /*************************************************************************/ + /* */ + /* Compute the outline bounding box. */ + /* */ + static void + gray_compute_cbox( RAS_ARG ) + { + PVG_FT_Outline* outline = &ras.outline; + PVG_FT_Vector* vec = outline->points; + PVG_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 ) + pvg_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 int)ey >= (unsigned int)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 ); + } + +// The new render-line implementation is not yet used +#if 1 + + /*************************************************************************/ + /* */ + /* Render a scanline as one or more cells. */ + /* */ + static void + gray_render_scanline( RAS_ARG_ TCoord ey, + TPos x1, + TCoord y1, + TPos x2, + TCoord y2 ) + { + TCoord ex1, ex2, fx1, fx2, first, dy, delta, mod; + TPos p, dx; + int incr; + + + ex1 = TRUNC( x1 ); + ex2 = TRUNC( x2 ); + + /* trivial case. Happens often */ + if ( y1 == y2 ) + { + gray_set_cell( RAS_VAR_ ex2, ey ); + return; + } + + fx1 = FRACT( x1 ); + fx2 = FRACT( x2 ); + + /* everything is located in a single cell. That is easy! */ + /* */ + if ( ex1 == ex2 ) + goto End; + + /* ok, we'll have to render a run of adjacent cells on the same */ + /* scanline... */ + /* */ + dx = x2 - x1; + dy = y2 - y1; + + if ( dx > 0 ) + { + p = ( ONE_PIXEL - fx1 ) * dy; + first = ONE_PIXEL; + incr = 1; + } else { + p = fx1 * dy; + first = 0; + incr = -1; + dx = -dx; + } + + PVG_FT_DIV_MOD( TCoord, p, dx, delta, mod ); + + ras.area += (TArea)( fx1 + first ) * delta; + ras.cover += delta; + y1 += delta; + ex1 += incr; + gray_set_cell( RAS_VAR_ ex1, ey ); + + if ( ex1 != ex2 ) + { + TCoord lift, rem; + + + p = ONE_PIXEL * dy; + PVG_FT_DIV_MOD( TCoord, p, dx, lift, rem ); + + do + { + delta = lift; + mod += rem; + if ( mod >= (TCoord)dx ) + { + mod -= (TCoord)dx; + delta++; + } + + ras.area += (TArea)( ONE_PIXEL * delta ); + ras.cover += delta; + y1 += delta; + ex1 += incr; + gray_set_cell( RAS_VAR_ ex1, ey ); + } while ( ex1 != ex2 ); + } + fx1 = ONE_PIXEL - first; + + End: + dy = y2 - y1; + + ras.area += (TArea)( ( fx1 + fx2 ) * dy ); + ras.cover += dy; + } + + + /*************************************************************************/ + /* */ + /* Render a given line as a series of scanlines. */ + /* */ + static void + gray_render_line( RAS_ARG_ TPos to_x, + TPos to_y ) + { + TCoord ey1, ey2, fy1, fy2, first, delta, mod; + TPos p, dx, dy, x, x2; + int incr; + + ey1 = TRUNC( ras.y ); + ey2 = TRUNC( to_y ); /* if (ey2 >= ras.max_ey) ey2 = ras.max_ey-1; */ + + /* perform vertical clipping */ + if ( ( ey1 >= ras.max_ey && ey2 >= ras.max_ey ) || + ( ey1 < ras.min_ey && ey2 < ras.min_ey ) ) + goto End; + + fy1 = FRACT( ras.y ); + fy2 = FRACT( to_y ); + + /* everything is on a single scanline */ + if ( ey1 == ey2 ) + { + gray_render_scanline( RAS_VAR_ ey1, ras.x, fy1, to_x, fy2 ); + goto End; + } + + dx = to_x - ras.x; + dy = to_y - ras.y; + + /* vertical line - avoid calling gray_render_scanline */ + if ( dx == 0 ) + { + TCoord ex = TRUNC( ras.x ); + TCoord two_fx = FRACT( ras.x ) << 1; + TPos area, max_ey1; + + + if ( dy > 0) + { + first = ONE_PIXEL; + } + else + { + first = 0; + } + + delta = first - fy1; + ras.area += (TArea)two_fx * delta; + ras.cover += delta; + + delta = first + first - ONE_PIXEL; + area = (TArea)two_fx * delta; + max_ey1 = ras.count_ey + ras.min_ey; + if (dy < 0) { + if (ey1 > max_ey1) { + ey1 = (max_ey1 > ey2) ? max_ey1 : ey2; + gray_set_cell( &ras, ex, ey1 ); + } else { + ey1--; + gray_set_cell( &ras, ex, ey1 ); + } + while ( ey1 > ey2 && ey1 >= ras.min_ey) + { + ras.area += area; + ras.cover += delta; + ey1--; + + gray_set_cell( &ras, ex, ey1 ); + } + if (ey1 != ey2) { + ey1 = ey2; + gray_set_cell( &ras, ex, ey1 ); + } + } else { + if (ey1 < ras.min_ey) { + ey1 = (ras.min_ey < ey2) ? ras.min_ey : ey2; + gray_set_cell( &ras, ex, ey1 ); + } else { + ey1++; + gray_set_cell( &ras, ex, ey1 ); + } + while ( ey1 < ey2 && ey1 < max_ey1) + { + ras.area += area; + ras.cover += delta; + ey1++; + + gray_set_cell( &ras, ex, ey1 ); + } + if (ey1 != ey2) { + ey1 = ey2; + gray_set_cell( &ras, ex, ey1 ); + } + } + + delta = (int)( fy2 - ONE_PIXEL + first ); + ras.area += (TArea)two_fx * delta; + ras.cover += delta; + + goto End; + } + + /* ok, we have to render several scanlines */ + if ( dy > 0) + { + p = ( ONE_PIXEL - fy1 ) * dx; + first = ONE_PIXEL; + incr = 1; + } + else + { + p = fy1 * dx; + first = 0; + incr = -1; + dy = -dy; + } + + /* the fractional part of x-delta is mod/dy. It is essential to */ + /* keep track of its accumulation for accurate rendering. */ + PVG_FT_DIV_MOD( TCoord, p, dy, delta, mod ); + + x = ras.x + delta; + gray_render_scanline( RAS_VAR_ ey1, ras.x, fy1, x, (TCoord)first ); + + ey1 += incr; + gray_set_cell( RAS_VAR_ TRUNC( x ), ey1 ); + + if ( ey1 != ey2 ) + { + TCoord lift, rem; + + + p = ONE_PIXEL * dx; + PVG_FT_DIV_MOD( TCoord, p, dy, lift, rem ); + + do + { + delta = lift; + mod += rem; + if ( mod >= (TCoord)dy ) + { + mod -= (TCoord)dy; + delta++; + } + + x2 = x + delta; + gray_render_scanline( RAS_VAR_ ey1, + x, ONE_PIXEL - first, + x2, first ); + x = x2; + + ey1 += incr; + gray_set_cell( RAS_VAR_ TRUNC( x ), ey1 ); + } while ( ey1 != ey2 ); + } + + gray_render_scanline( RAS_VAR_ ey1, + x, ONE_PIXEL - first, + to_x, fy2 ); + + End: + ras.x = to_x; + ras.y = to_y; + } + + +#else + + /*************************************************************************/ + /* */ + /* 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 = FRACT( ras.x ); + fy1 = FRACT( ras.y ); + + 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; + PVG_FT_UDIVPREP( dx ); + PVG_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)PVG_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)PVG_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)PVG_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)PVG_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 = FRACT( to_x ); + fy2 = FRACT( to_y ); + + ras.cover += ( fy2 - fy1 ); + ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 ); + + End: + ras.x = to_x; + ras.y = to_y; + } + +#endif + + static void + gray_split_conic( PVG_FT_Vector* base ) + { + TPos a, b; + + + base[4].x = base[2].x; + b = base[1].x; + a = base[3].x = ( base[2].x + b ) / 2; + b = base[1].x = ( base[0].x + b ) / 2; + base[2].x = ( a + b ) / 2; + + base[4].y = base[2].y; + b = base[1].y; + a = base[3].y = ( base[2].y + b ) / 2; + b = base[1].y = ( base[0].y + b ) / 2; + base[2].y = ( a + b ) / 2; + } + + + static void + gray_render_conic( RAS_ARG_ const PVG_FT_Vector* control, + const PVG_FT_Vector* to ) + { + PVG_FT_Vector bez_stack[16 * 2 + 1]; /* enough to accommodate bisections */ + PVG_FT_Vector* arc = bez_stack; + TPos dx, dy; + int draw, split; + + + arc[0].x = UPSCALE( to->x ); + arc[0].y = UPSCALE( to->y ); + arc[1].x = UPSCALE( control->x ); + arc[1].y = UPSCALE( control->y ); + arc[2].x = ras.x; + arc[2].y = ras.y; + + /* 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[0].y ) < ras.min_ey && + TRUNC( arc[1].y ) < ras.min_ey && + TRUNC( arc[2].y ) < ras.min_ey ) ) + { + ras.x = arc[0].x; + ras.y = arc[0].y; + return; + } + + dx = PVG_FT_ABS( arc[2].x + arc[0].x - 2 * arc[1].x ); + dy = PVG_FT_ABS( arc[2].y + arc[0].y - 2 * arc[1].y ); + if ( dx < dy ) + dx = dy; + + /* We can calculate the number of necessary bisections because */ + /* each bisection predictably reduces deviation exactly 4-fold. */ + /* Even 32-bit deviation would vanish after 16 bisections. */ + draw = 1; + while ( dx > ONE_PIXEL / 4 ) + { + dx >>= 2; + draw <<= 1; + } + + /* We use decrement counter to count the total number of segments */ + /* to draw starting from 2^level. Before each draw we split as */ + /* many times as there are trailing zeros in the counter. */ + do + { + split = 1; + while ( ( draw & split ) == 0 ) + { + gray_split_conic( arc ); + arc += 2; + split <<= 1; + } + + gray_render_line( RAS_VAR_ arc[0].x, arc[0].y ); + arc -= 2; + + } while ( --draw ); + } + + + static void + gray_split_cubic( PVG_FT_Vector* base ) + { + TPos a, b, c, d; + + + base[6].x = base[3].x; + c = base[1].x; + d = base[2].x; + base[1].x = a = ( base[0].x + c ) / 2; + base[5].x = b = ( base[3].x + d ) / 2; + c = ( c + d ) / 2; + base[2].x = a = ( a + c ) / 2; + base[4].x = b = ( b + c ) / 2; + base[3].x = ( a + b ) / 2; + + base[6].y = base[3].y; + c = base[1].y; + d = base[2].y; + base[1].y = a = ( base[0].y + c ) / 2; + base[5].y = b = ( base[3].y + d ) / 2; + c = ( c + d ) / 2; + base[2].y = a = ( a + c ) / 2; + base[4].y = b = ( b + c ) / 2; + base[3].y = ( a + b ) / 2; + } + + + static void + gray_render_cubic( RAS_ARG_ const PVG_FT_Vector* control1, + const PVG_FT_Vector* control2, + const PVG_FT_Vector* to ) + { + PVG_FT_Vector bez_stack[16 * 3 + 1]; /* enough to accommodate bisections */ + PVG_FT_Vector* arc = bez_stack; + TPos dx, dy, dx_, dy_; + TPos dx1, dy1, dx2, dy2; + TPos L, s, s_limit; + + + arc[0].x = UPSCALE( to->x ); + arc[0].y = UPSCALE( to->y ); + arc[1].x = UPSCALE( control2->x ); + arc[1].y = UPSCALE( control2->y ); + arc[2].x = UPSCALE( control1->x ); + arc[2].y = UPSCALE( control1->y ); + arc[3].x = ras.x; + arc[3].y = ras.y; + + /* 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 (;;) + { + /* Decide whether to split or draw. See `Rapid Termination */ + /* Evaluation for Recursive Subdivision of Bezier Curves' by Thomas */ + /* F. Hain, at */ + /* http://www.cis.southalabama.edu/~hain/general/Publications/Bezier/Camera-ready%20CISST02%202.pdf */ + + + /* dx and dy are x and y components of the P0-P3 chord vector. */ + dx = dx_ = arc[3].x - arc[0].x; + dy = dy_ = arc[3].y - arc[0].y; + + L = PVG_FT_HYPOT( dx_, dy_ ); + + /* Avoid possible arithmetic overflow below by splitting. */ + if ( L >= (1 << 23) ) + goto Split; + + /* Max deviation may be as much as (s/L) * 3/4 (if Hain's v = 1). */ + s_limit = L * (TPos)( ONE_PIXEL / 6 ); + + /* s is L * the perpendicular distance from P1 to the line P0-P3. */ + dx1 = arc[1].x - arc[0].x; + dy1 = arc[1].y - arc[0].y; + s = PVG_FT_ABS( dy * dx1 - dx * dy1 ); + + if ( s > s_limit ) + goto Split; + + /* s is L * the perpendicular distance from P2 to the line P0-P3. */ + dx2 = arc[2].x - arc[0].x; + dy2 = arc[2].y - arc[0].y; + s = PVG_FT_ABS( dy * dx2 - dx * dy2 ); + + if ( s > s_limit ) + goto Split; + + /* Split super curvy segments where the off points are so far + from the chord that the angles P0-P1-P3 or P0-P2-P3 become + acute as detected by appropriate dot products. */ + if ( dx1 * ( dx1 - dx ) + dy1 * ( dy1 - dy ) > 0 || + dx2 * ( dx2 - dx ) + dy2 * ( dy2 - dy ) > 0 ) + goto Split; + + gray_render_line( RAS_VAR_ arc[0].x, arc[0].y ); + + if ( arc == bez_stack ) + return; + + arc -= 3; + continue; + + Split: + gray_split_cubic( arc ); + arc += 3; + } + } + + + + static int + gray_move_to( const PVG_FT_Vector* to, + PWorker worker ) + { + TPos x, y; + + + /* record current cell, if any */ + if ( !ras.invalid ) + gray_record_cell( worker ); + + /* start to a new position */ + x = UPSCALE( to->x ); + y = UPSCALE( to->y ); + + gray_start_cell( worker, TRUNC( x ), TRUNC( y ) ); + + ras.x = x; + ras.y = y; + return 0; + } + + + static void + gray_hline( RAS_ARG_ TCoord x, + TCoord y, + TPos area, + int acount ) + { + int coverage; + + + /* 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 & PVG_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; + + /* PVG_FT_Span.x is an int, so limit our coordinates appropriately */ + if ( x >= (1 << 23) ) + x = (1 << 23) - 1; + + /* PVG_FT_Span.y is an int, so limit our coordinates appropriately */ + if ( y >= (1 << 23) ) + y = (1 << 23) - 1; + + if ( coverage ) + { + PVG_FT_Span* span; + int count; + int skip; + + /* 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 && + span->x + span->len == x && + span->coverage == coverage ) + { + span->len = span->len + acount; + return; + } + + if ( count >= PVG_FT_MAX_GRAY_SPANS ) + { + if ( ras.render_span && count > ras.skip_spans ) + { + skip = ras.skip_spans > 0 ? ras.skip_spans : 0; + ras.render_span( ras.num_gray_spans - skip, + ras.gray_spans + skip, + ras.render_span_data ); + } + + ras.skip_spans -= ras.num_gray_spans; + /* ras.render_span( span->y, ras.gray_spans, count ); */ + ras.num_gray_spans = 0; + + span = ras.gray_spans; + } + else + span++; + + /* add a gray span to the current list */ + span->x = x; + span->len = acount; + span->y = y; + span->coverage = (unsigned char)coverage; + + ras.num_gray_spans++; + } + } + + + + static void + gray_sweep( RAS_ARG) + { + int yindex; + + if ( ras.num_cells == 0 ) + return; + + for ( yindex = 0; yindex < ras.ycount; yindex++ ) + { + PCell cell = ras.ycells[yindex]; + TCoord cover = 0; + TCoord x = 0; + + + for ( ; cell != NULL; cell = cell->next ) + { + TArea area; + + + if ( cell->x > x && cover != 0 ) + gray_hline( RAS_VAR_ x, yindex, cover * ( ONE_PIXEL * 2 ), + cell->x - x ); + + cover += cell->cover; + area = cover * ( ONE_PIXEL * 2 ) - cell->area; + + if ( area != 0 && cell->x >= 0 ) + gray_hline( RAS_VAR_ cell->x, yindex, area, 1 ); + + x = cell->x + 1; + } + + if ( ras.count_ex > x && cover != 0 ) + gray_hline( RAS_VAR_ x, yindex, cover * ( ONE_PIXEL * 2 ), + ras.count_ex - x ); + } + } + + /*************************************************************************/ + /* */ + /* The following function should only compile in stand_alone mode, */ + /* i.e., when building this component without the rest of FreeType. */ + /* */ + /*************************************************************************/ + + /*************************************************************************/ + /* */ + /* */ + /* PVG_FT_Outline_Decompose */ + /* */ + /* */ + /* Walks over an outline's structure to decompose it into individual */ + /* segments and Bezier 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. */ + /* */ + /* 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 PVG_FT_Outline_Decompose( const PVG_FT_Outline* outline, + void* user ) + { +#undef SCALED +#define SCALED( x ) (x) + + PVG_FT_Vector v_last; + PVG_FT_Vector v_control; + PVG_FT_Vector v_start; + + PVG_FT_Vector* point; + PVG_FT_Vector* limit; + char* tags; + + int n; /* index of contour in outline */ + int first; /* index of first point in contour */ + int error; + char tag; /* current point's state */ + + if ( !outline ) + return ErrRaster_Invalid_Outline; + + 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 = PVG_FT_CURVE_TAG( tags[0] ); + + /* A contour cannot start with a cubic control point! */ + if ( tag == PVG_FT_CURVE_TAG_CUBIC ) + goto Invalid_Outline; + + /* check first point to determine origin */ + if ( tag == PVG_FT_CURVE_TAG_CONIC ) + { + /* first point is conic control. Yes, this happens. */ + if ( PVG_FT_CURVE_TAG( outline->tags[last] ) == PVG_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; + + v_last = v_start; + } + point--; + tags--; + } + + error = gray_move_to( &v_start, user ); + if ( error ) + goto Exit; + + while ( point < limit ) + { + point++; + tags++; + + tag = PVG_FT_CURVE_TAG( tags[0] ); + switch ( tag ) + { + case PVG_FT_CURVE_TAG_ON: /* emit a single line_to */ + { + PVG_FT_Vector vec; + + + vec.x = SCALED( point->x ); + vec.y = SCALED( point->y ); + + gray_render_line(user, UPSCALE(vec.x), UPSCALE(vec.y)); + continue; + } + + case PVG_FT_CURVE_TAG_CONIC: /* consume conic arcs */ + { + v_control.x = SCALED( point->x ); + v_control.y = SCALED( point->y ); + + Do_Conic: + if ( point < limit ) + { + PVG_FT_Vector vec; + PVG_FT_Vector v_middle; + + + point++; + tags++; + tag = PVG_FT_CURVE_TAG( tags[0] ); + + vec.x = SCALED( point->x ); + vec.y = SCALED( point->y ); + + if ( tag == PVG_FT_CURVE_TAG_ON ) + { + gray_render_conic(user, &v_control, &vec); + continue; + } + + if ( tag != PVG_FT_CURVE_TAG_CONIC ) + goto Invalid_Outline; + + v_middle.x = ( v_control.x + vec.x ) / 2; + v_middle.y = ( v_control.y + vec.y ) / 2; + + gray_render_conic(user, &v_control, &v_middle); + + v_control = vec; + goto Do_Conic; + } + + gray_render_conic(user, &v_control, &v_start); + goto Close; + } + + default: /* PVG_FT_CURVE_TAG_CUBIC */ + { + PVG_FT_Vector vec1, vec2; + + + if ( point + 1 > limit || + PVG_FT_CURVE_TAG( tags[1] ) != PVG_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 ) + { + PVG_FT_Vector vec; + + + vec.x = SCALED( point->x ); + vec.y = SCALED( point->y ); + + gray_render_cubic(user, &vec1, &vec2, &vec); + continue; + } + + gray_render_cubic(user, &vec1, &vec2, &v_start); + goto Close; + } + } + } + + /* close the contour with a line segment */ + gray_render_line(user, UPSCALE(v_start.x), UPSCALE(v_start.y)); + + Close: + first = last + 1; + } + + return 0; + + Exit: + return error; + + Invalid_Outline: + return ErrRaster_Invalid_Outline; + } + + typedef struct TBand_ + { + TPos min, max; + + } TBand; + + static int + gray_convert_glyph_inner( RAS_ARG ) + { + volatile int error = 0; + + if ( pvg_ft_setjmp( ras.jump_buffer ) == 0 ) + { + error = PVG_FT_Outline_Decompose( &ras.outline, &ras ); + if ( !ras.invalid ) + gray_record_cell( RAS_VAR ); + } + else + { + error = ErrRaster_Memory_Overflow; + } + + return error; + } + + + static int + gray_convert_glyph( RAS_ARG ) + { + TBand bands[40]; + TBand* volatile band; + int volatile n, num_bands; + TPos volatile min, max, max_y; + PVG_FT_BBox* clip; + int skip; + + ras.num_gray_spans = 0; + + /* 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; + int cell_start, cell_end, cell_mod; + + + ras.ycells = (PCell*)ras.buffer; + ras.ycount = band->max - band->min; + + cell_start = sizeof ( PCell ) * ras.ycount; + cell_mod = cell_start % sizeof ( TCell ); + if ( cell_mod > 0 ) + cell_start += sizeof ( TCell ) - cell_mod; + + cell_end = ras.buffer_size; + cell_end -= cell_end % sizeof( TCell ); + + cells_max = (PCell)( (char*)ras.buffer + cell_end ); + ras.cells = (PCell)( (char*)ras.buffer + cell_start ); + if ( ras.cells >= cells_max ) + goto ReduceBands; + + ras.max_cells = (int)(cells_max - ras.cells); + if ( ras.max_cells < 2 ) + goto ReduceBands; + + for ( yindex = 0; yindex < ras.ycount; yindex++ ) + ras.ycells[yindex] = NULL; + } + + ras.num_cells = 0; + ras.invalid = 1; + ras.min_ey = band->min; + ras.max_ey = band->max; + ras.count_ey = band->max - band->min; + + error = gray_convert_glyph_inner( RAS_VAR ); + + if ( !error ) + { + gray_sweep( RAS_VAR); + band--; + continue; + } + else if ( error != ErrRaster_Memory_Overflow ) + return 1; + + ReduceBands: + /* 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 ErrRaster_OutOfMemory; + } + + if ( bottom-top >= ras.band_size ) + ras.band_shoot++; + + band[1].min = bottom; + band[1].max = middle; + band[0].min = middle; + band[0].max = top; + band++; + } + } + + if ( ras.render_span && ras.num_gray_spans > ras.skip_spans ) + { + skip = ras.skip_spans > 0 ? ras.skip_spans : 0; + ras.render_span( ras.num_gray_spans - skip, + ras.gray_spans + skip, + ras.render_span_data ); + } + + ras.skip_spans -= ras.num_gray_spans; + + if ( ras.band_shoot > 8 && ras.band_size > 16 ) + ras.band_size = ras.band_size / 2; + + return 0; + } + + + static int + gray_raster_render( RAS_ARG_ void* buffer, long buffer_size, + const PVG_FT_Raster_Params* params ) + { + const PVG_FT_Outline* outline = (const PVG_FT_Outline*)params->source; + if ( outline == NULL ) + return ErrRaster_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 ErrRaster_Invalid_Outline; + + if ( outline->n_points != + outline->contours[outline->n_contours - 1] + 1 ) + return ErrRaster_Invalid_Outline; + + /* this version does not support monochrome rendering */ + if ( !( params->flags & PVG_FT_RASTER_FLAG_AA ) ) + return ErrRaster_Invalid_Mode; + + if ( !( params->flags & PVG_FT_RASTER_FLAG_DIRECT ) ) + return ErrRaster_Invalid_Mode; + + /* compute clipping box */ + if ( params->flags & PVG_FT_RASTER_FLAG_CLIP ) + { + ras.clip_box = params->clip_box; + } + else + { + ras.clip_box.xMin = -(1 << 23); + ras.clip_box.yMin = -(1 << 23); + ras.clip_box.xMax = (1 << 23) - 1; + ras.clip_box.yMax = (1 << 23) - 1; + } + + gray_init_cells( RAS_VAR_ buffer, buffer_size ); + + ras.outline = *outline; + ras.num_cells = 0; + ras.invalid = 1; + ras.band_size = (int)(buffer_size / (long)(sizeof(TCell) * 8)); + + ras.render_span = (PVG_FT_Raster_Span_Func)params->gray_spans; + ras.render_span_data = params->user; + + return gray_convert_glyph( RAS_VAR ); + } + + void + PVG_FT_Raster_Render(const PVG_FT_Raster_Params *params) + { + char stack[PVG_FT_MINIMUM_POOL_SIZE]; + long length = PVG_FT_MINIMUM_POOL_SIZE; + + TWorker worker; + worker.skip_spans = 0; + int rendered_spans = 0; + int error = gray_raster_render(&worker, stack, length, params); + while(error == ErrRaster_OutOfMemory) { + if(worker.skip_spans < 0) + rendered_spans += -worker.skip_spans; + worker.skip_spans = rendered_spans; + length *= 2; + void* heap = malloc((size_t)(length)); + error = gray_raster_render(&worker, heap, length, params); + free(heap); + } + } + +/* END */ diff --git a/third_party/lunasvg/3rdparty/plutovg/plutovg-ft-raster.h b/third_party/lunasvg/3rdparty/plutovg/plutovg-ft-raster.h new file mode 100644 index 0000000000..5f5d934c61 --- /dev/null +++ b/third_party/lunasvg/3rdparty/plutovg/plutovg-ft-raster.h @@ -0,0 +1,420 @@ +/***************************************************************************/ +/* */ +/* 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. */ +/* */ +/***************************************************************************/ + +#ifndef PLUTOVG_FT_RASTER_H +#define PLUTOVG_FT_RASTER_H + +#include "plutovg-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 PVG_FT_BBox_ +{ + PVG_FT_Pos xMin, yMin; + PVG_FT_Pos xMax, yMax; + +} PVG_FT_BBox; + +/*************************************************************************/ +/* */ +/* */ +/* PVG_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' @PVG_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 @PVG_FT_OUTLINE_FLAGS.*/ +/* */ +typedef struct PVG_FT_Outline_ +{ + int n_contours; /* number of contours in glyph */ + int n_points; /* number of points in the glyph */ + + PVG_FT_Vector* points; /* the outline's points */ + char* tags; /* the points flags */ + int* contours; /* the contour end points */ + char* contours_flag; /* the contour open flags */ + + int flags; /* outline masks */ + +} PVG_FT_Outline; + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_OUTLINE_FLAGS */ +/* */ +/* */ +/* A list of bit-field constants use for the flags in an outline's */ +/* `flags' field. */ +/* */ +/* */ +/* PVG_FT_OUTLINE_NONE :: */ +/* Value~0 is reserved. */ +/* */ +/* PVG_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. */ +/* */ +/* PVG_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). */ +/* */ +/* PVG_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 @PVG_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 PVG_FT_OUTLINE_NONE 0x0 +#define PVG_FT_OUTLINE_OWNER 0x1 +#define PVG_FT_OUTLINE_EVEN_ODD_FILL 0x2 +#define PVG_FT_OUTLINE_REVERSE_FILL 0x4 + +/* */ + +#define PVG_FT_CURVE_TAG( flag ) ( flag & 3 ) + +#define PVG_FT_CURVE_TAG_ON 1 +#define PVG_FT_CURVE_TAG_CONIC 0 +#define PVG_FT_CURVE_TAG_CUBIC 2 + + +#define PVG_FT_Curve_Tag_On PVG_FT_CURVE_TAG_ON +#define PVG_FT_Curve_Tag_Conic PVG_FT_CURVE_TAG_CONIC +#define PVG_FT_Curve_Tag_Cubic PVG_FT_CURVE_TAG_CUBIC + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Outline_Check */ +/* */ +/* */ +/* Check the contents of an outline descriptor. */ +/* */ +/* */ +/* outline :: A handle to a source outline. */ +/* */ +/* */ +/* FreeType error code. 0~means success. */ +/* */ +PVG_FT_Error +PVG_FT_Outline_Check( PVG_FT_Outline* outline ); + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_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 @PVG_FT_Glyph_Get_CBox for a discussion of tricky fonts. */ +/* */ +void +PVG_FT_Outline_Get_CBox( const PVG_FT_Outline* outline, + PVG_FT_BBox *acbox ); + +/*************************************************************************/ +/* */ +/* */ +/* PVG_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 */ +/* @PVG_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 PVG_FT_Span_ +{ + int x; + int len; + int y; + unsigned char coverage; + +} PVG_FT_Span; + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_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 `PVG_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 + (*PVG_FT_SpanFunc)( int count, + const PVG_FT_Span* spans, + void* user ); + +#define PVG_FT_Raster_Span_Func PVG_FT_SpanFunc + + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_RASTER_FLAG_XXX */ +/* */ +/* */ +/* A list of bit flag constants as used in the `flags' field of a */ +/* @PVG_FT_Raster_Params structure. */ +/* */ +/* */ +/* PVG_FT_RASTER_FLAG_DEFAULT :: This value is 0. */ +/* */ +/* PVG_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). */ +/* */ +/* PVG_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. */ +/* */ +/* PVG_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 */ +/* @PVG_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 PVG_FT_RASTER_FLAG_DEFAULT 0x0 +#define PVG_FT_RASTER_FLAG_AA 0x1 +#define PVG_FT_RASTER_FLAG_DIRECT 0x2 +#define PVG_FT_RASTER_FLAG_CLIP 0x4 + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_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 */ +/* @PVG_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 @PVG_FT_RASTER_FLAG_AA */ +/* bit flag is set in the `flags' field, otherwise a monochrome */ +/* bitmap is generated. */ +/* */ +/* If the @PVG_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 PVG_FT_Raster_Params_ +{ + const void* source; + int flags; + PVG_FT_SpanFunc gray_spans; + void* user; + PVG_FT_BBox clip_box; + +} PVG_FT_Raster_Params; + + +void +PVG_FT_Raster_Render(const PVG_FT_Raster_Params *params); + +#endif // PLUTOVG_FT_RASTER_H diff --git a/third_party/lunasvg/3rdparty/plutovg/plutovg-ft-stroker.c b/third_party/lunasvg/3rdparty/plutovg/plutovg-ft-stroker.c new file mode 100644 index 0000000000..2d2f446066 --- /dev/null +++ b/third_party/lunasvg/3rdparty/plutovg/plutovg-ft-stroker.c @@ -0,0 +1,1947 @@ + +/***************************************************************************/ +/* */ +/* 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 "plutovg-ft-stroker.h" +#include "plutovg-ft-math.h" + +#include +#include +#include + +/*************************************************************************/ +/*************************************************************************/ +/***** *****/ +/***** BEZIER COMPUTATIONS *****/ +/***** *****/ +/*************************************************************************/ +/*************************************************************************/ + +#define PVG_FT_SMALL_CONIC_THRESHOLD (PVG_FT_ANGLE_PI / 6) +#define PVG_FT_SMALL_CUBIC_THRESHOLD (PVG_FT_ANGLE_PI / 8) + +#define PVG_FT_EPSILON 2 + +#define PVG_FT_IS_SMALL(x) ((x) > -PVG_FT_EPSILON && (x) < PVG_FT_EPSILON) + +static PVG_FT_Pos ft_pos_abs(PVG_FT_Pos x) +{ + return x >= 0 ? x : -x; +} + +static void ft_conic_split(PVG_FT_Vector* base) +{ + PVG_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 PVG_FT_Bool ft_conic_is_small_enough(PVG_FT_Vector* base, + PVG_FT_Angle* angle_in, + PVG_FT_Angle* angle_out) +{ + PVG_FT_Vector d1, d2; + PVG_FT_Angle theta; + PVG_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 = PVG_FT_IS_SMALL(d1.x) && PVG_FT_IS_SMALL(d1.y); + close2 = PVG_FT_IS_SMALL(d2.x) && PVG_FT_IS_SMALL(d2.y); + + if (close1) { + if (close2) { + /* basically a point; */ + /* do nothing to retain original direction */ + } else { + *angle_in = *angle_out = PVG_FT_Atan2(d2.x, d2.y); + } + } else /* !close1 */ + { + if (close2) { + *angle_in = *angle_out = PVG_FT_Atan2(d1.x, d1.y); + } else { + *angle_in = PVG_FT_Atan2(d1.x, d1.y); + *angle_out = PVG_FT_Atan2(d2.x, d2.y); + } + } + + theta = ft_pos_abs(PVG_FT_Angle_Diff(*angle_in, *angle_out)); + + return PVG_FT_BOOL(theta < PVG_FT_SMALL_CONIC_THRESHOLD); +} + +static void ft_cubic_split(PVG_FT_Vector* base) +{ + PVG_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 PVG_FT_Angle ft_angle_mean(PVG_FT_Angle angle1, PVG_FT_Angle angle2) +{ + return angle1 + PVG_FT_Angle_Diff(angle1, angle2) / 2; +} + +static PVG_FT_Bool ft_cubic_is_small_enough(PVG_FT_Vector* base, + PVG_FT_Angle* angle_in, + PVG_FT_Angle* angle_mid, + PVG_FT_Angle* angle_out) +{ + PVG_FT_Vector d1, d2, d3; + PVG_FT_Angle theta1, theta2; + PVG_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 = PVG_FT_IS_SMALL(d1.x) && PVG_FT_IS_SMALL(d1.y); + close2 = PVG_FT_IS_SMALL(d2.x) && PVG_FT_IS_SMALL(d2.y); + close3 = PVG_FT_IS_SMALL(d3.x) && PVG_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 = PVG_FT_Atan2(d3.x, d3.y); + } + } else /* !close2 */ + { + if (close3) { + *angle_in = *angle_mid = *angle_out = PVG_FT_Atan2(d2.x, d2.y); + } else /* !close3 */ + { + *angle_in = *angle_mid = PVG_FT_Atan2(d2.x, d2.y); + *angle_out = PVG_FT_Atan2(d3.x, d3.y); + } + } + } else /* !close1 */ + { + if (close2) { + if (close3) { + *angle_in = *angle_mid = *angle_out = PVG_FT_Atan2(d1.x, d1.y); + } else /* !close3 */ + { + *angle_in = PVG_FT_Atan2(d1.x, d1.y); + *angle_out = PVG_FT_Atan2(d3.x, d3.y); + *angle_mid = ft_angle_mean(*angle_in, *angle_out); + } + } else /* !close2 */ + { + if (close3) { + *angle_in = PVG_FT_Atan2(d1.x, d1.y); + *angle_mid = *angle_out = PVG_FT_Atan2(d2.x, d2.y); + } else /* !close3 */ + { + *angle_in = PVG_FT_Atan2(d1.x, d1.y); + *angle_mid = PVG_FT_Atan2(d2.x, d2.y); + *angle_out = PVG_FT_Atan2(d3.x, d3.y); + } + } + } + + theta1 = ft_pos_abs(PVG_FT_Angle_Diff(*angle_in, *angle_mid)); + theta2 = ft_pos_abs(PVG_FT_Angle_Diff(*angle_mid, *angle_out)); + + return PVG_FT_BOOL(theta1 < PVG_FT_SMALL_CUBIC_THRESHOLD && + theta2 < PVG_FT_SMALL_CUBIC_THRESHOLD); +} + +/*************************************************************************/ +/*************************************************************************/ +/***** *****/ +/***** STROKE BORDERS *****/ +/***** *****/ +/*************************************************************************/ +/*************************************************************************/ + +typedef enum PVG_FT_StrokeTags_ { + PVG_FT_STROKE_TAG_ON = 1, /* on-curve point */ + PVG_FT_STROKE_TAG_CUBIC = 2, /* cubic off-point */ + PVG_FT_STROKE_TAG_BEGIN = 4, /* sub-path start */ + PVG_FT_STROKE_TAG_END = 8 /* sub-path end */ + +} PVG_FT_StrokeTags; + +#define PVG_FT_STROKE_TAG_BEGIN_END \ + (PVG_FT_STROKE_TAG_BEGIN | PVG_FT_STROKE_TAG_END) + +typedef struct PVG_FT_StrokeBorderRec_ { + PVG_FT_UInt num_points; + PVG_FT_UInt max_points; + PVG_FT_Vector* points; + PVG_FT_Byte* tags; + PVG_FT_Bool movable; /* TRUE for ends of lineto borders */ + PVG_FT_Int start; /* index of current sub-path start point */ + PVG_FT_Bool valid; + +} PVG_FT_StrokeBorderRec, *PVG_FT_StrokeBorder; + +PVG_FT_Error PVG_FT_Outline_Check(PVG_FT_Outline* outline) +{ + if (outline) { + PVG_FT_Int n_points = outline->n_points; + PVG_FT_Int n_contours = outline->n_contours; + PVG_FT_Int end0, end; + PVG_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; // PVG_FT_THROW( Invalid_Argument ); +} + +void PVG_FT_Outline_Get_CBox(const PVG_FT_Outline* outline, PVG_FT_BBox* acbox) +{ + PVG_FT_Pos xMin, yMin, xMax, yMax; + + if (outline && acbox) { + if (outline->n_points == 0) { + xMin = 0; + yMin = 0; + xMax = 0; + yMax = 0; + } else { + PVG_FT_Vector* vec = outline->points; + PVG_FT_Vector* limit = vec + outline->n_points; + + xMin = xMax = vec->x; + yMin = yMax = vec->y; + vec++; + + for (; vec < limit; vec++) { + PVG_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 PVG_FT_Error ft_stroke_border_grow(PVG_FT_StrokeBorder border, + PVG_FT_UInt new_points) +{ + PVG_FT_UInt old_max = border->max_points; + PVG_FT_UInt new_max = border->num_points + new_points; + PVG_FT_Error error = 0; + + if (new_max > old_max) { + PVG_FT_UInt cur_max = old_max; + + while (cur_max < new_max) cur_max += (cur_max >> 1) + 16; + + border->points = (PVG_FT_Vector*)realloc(border->points, + cur_max * sizeof(PVG_FT_Vector)); + border->tags = + (PVG_FT_Byte*)realloc(border->tags, cur_max * sizeof(PVG_FT_Byte)); + + if (!border->points || !border->tags) goto Exit; + + border->max_points = cur_max; + } + +Exit: + return error; +} + +static void ft_stroke_border_close(PVG_FT_StrokeBorder border, + PVG_FT_Bool reverse) +{ + PVG_FT_UInt start = border->start; + PVG_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]; + border->tags[start] = border->tags[count]; + + if (reverse) { + /* reverse the points */ + { + PVG_FT_Vector* vec1 = border->points + start + 1; + PVG_FT_Vector* vec2 = border->points + count - 1; + + for (; vec1 < vec2; vec1++, vec2--) { + PVG_FT_Vector tmp; + + tmp = *vec1; + *vec1 = *vec2; + *vec2 = tmp; + } + } + + /* then the tags */ + { + PVG_FT_Byte* tag1 = border->tags + start + 1; + PVG_FT_Byte* tag2 = border->tags + count - 1; + + for (; tag1 < tag2; tag1++, tag2--) { + PVG_FT_Byte tmp; + + tmp = *tag1; + *tag1 = *tag2; + *tag2 = tmp; + } + } + } + + border->tags[start] |= PVG_FT_STROKE_TAG_BEGIN; + border->tags[count - 1] |= PVG_FT_STROKE_TAG_END; + } + + border->start = -1; + border->movable = FALSE; +} + +static PVG_FT_Error ft_stroke_border_lineto(PVG_FT_StrokeBorder border, + PVG_FT_Vector* to, PVG_FT_Bool movable) +{ + PVG_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 && + PVG_FT_IS_SMALL(border->points[border->num_points - 1].x - to->x) && + PVG_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) { + PVG_FT_Vector* vec = border->points + border->num_points; + PVG_FT_Byte* tag = border->tags + border->num_points; + + vec[0] = *to; + tag[0] = PVG_FT_STROKE_TAG_ON; + + border->num_points += 1; + } + } + border->movable = movable; + return error; +} + +static PVG_FT_Error ft_stroke_border_conicto(PVG_FT_StrokeBorder border, + PVG_FT_Vector* control, + PVG_FT_Vector* to) +{ + PVG_FT_Error error; + + assert(border->start >= 0); + + error = ft_stroke_border_grow(border, 2); + if (!error) { + PVG_FT_Vector* vec = border->points + border->num_points; + PVG_FT_Byte* tag = border->tags + border->num_points; + + vec[0] = *control; + vec[1] = *to; + + tag[0] = 0; + tag[1] = PVG_FT_STROKE_TAG_ON; + + border->num_points += 2; + } + + border->movable = FALSE; + + return error; +} + +static PVG_FT_Error ft_stroke_border_cubicto(PVG_FT_StrokeBorder border, + PVG_FT_Vector* control1, + PVG_FT_Vector* control2, + PVG_FT_Vector* to) +{ + PVG_FT_Error error; + + assert(border->start >= 0); + + error = ft_stroke_border_grow(border, 3); + if (!error) { + PVG_FT_Vector* vec = border->points + border->num_points; + PVG_FT_Byte* tag = border->tags + border->num_points; + + vec[0] = *control1; + vec[1] = *control2; + vec[2] = *to; + + tag[0] = PVG_FT_STROKE_TAG_CUBIC; + tag[1] = PVG_FT_STROKE_TAG_CUBIC; + tag[2] = PVG_FT_STROKE_TAG_ON; + + border->num_points += 3; + } + + border->movable = FALSE; + + return error; +} + +#define PVG_FT_ARC_CUBIC_ANGLE (PVG_FT_ANGLE_PI / 2) + + +static PVG_FT_Error +ft_stroke_border_arcto( PVG_FT_StrokeBorder border, + PVG_FT_Vector* center, + PVG_FT_Fixed radius, + PVG_FT_Angle angle_start, + PVG_FT_Angle angle_diff ) +{ + PVG_FT_Fixed coef; + PVG_FT_Vector a0, a1, a2, a3; + PVG_FT_Int i, arcs = 1; + PVG_FT_Error error = 0; + + + /* number of cubic arcs to draw */ + while ( angle_diff > PVG_FT_ARC_CUBIC_ANGLE * arcs || + -angle_diff > PVG_FT_ARC_CUBIC_ANGLE * arcs ) + arcs++; + + /* control tangents */ + coef = PVG_FT_Tan( angle_diff / ( 4 * arcs ) ); + coef += coef / 3; + + /* compute start and first control point */ + PVG_FT_Vector_From_Polar( &a0, radius, angle_start ); + a1.x = PVG_FT_MulFix( -a0.y, coef ); + a1.y = PVG_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 */ + PVG_FT_Vector_From_Polar( &a3, radius, + angle_start + i * angle_diff / arcs ); + a2.x = PVG_FT_MulFix( a3.y, coef ); + a2.y = PVG_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 PVG_FT_Error ft_stroke_border_moveto(PVG_FT_StrokeBorder border, + PVG_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(PVG_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(PVG_FT_StrokeBorder border) +{ + border->num_points = 0; + border->start = -1; + border->valid = FALSE; +} + +static void ft_stroke_border_done(PVG_FT_StrokeBorder border) +{ + free(border->points); + free(border->tags); + + border->num_points = 0; + border->max_points = 0; + border->start = -1; + border->valid = FALSE; +} + +static PVG_FT_Error ft_stroke_border_get_counts(PVG_FT_StrokeBorder border, + PVG_FT_UInt* anum_points, + PVG_FT_UInt* anum_contours) +{ + PVG_FT_Error error = 0; + PVG_FT_UInt num_points = 0; + PVG_FT_UInt num_contours = 0; + + PVG_FT_UInt count = border->num_points; + PVG_FT_Vector* point = border->points; + PVG_FT_Byte* tags = border->tags; + PVG_FT_Int in_contour = 0; + + for (; count > 0; count--, num_points++, point++, tags++) { + if (tags[0] & PVG_FT_STROKE_TAG_BEGIN) { + if (in_contour != 0) goto Fail; + + in_contour = 1; + } else if (in_contour == 0) + goto Fail; + + if (tags[0] & PVG_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(PVG_FT_StrokeBorder border, + PVG_FT_Outline* outline) +{ + /* copy point locations */ + if (outline->points != NULL && border->points != NULL) + memcpy(outline->points + outline->n_points, border->points, + border->num_points * sizeof(PVG_FT_Vector)); + + /* copy tags */ + if (outline->tags) + { + PVG_FT_UInt count = border->num_points; + PVG_FT_Byte* read = border->tags; + PVG_FT_Byte* write = (PVG_FT_Byte*)outline->tags + outline->n_points; + + for (; count > 0; count--, read++, write++) { + if (*read & PVG_FT_STROKE_TAG_ON) + *write = PVG_FT_CURVE_TAG_ON; + else if (*read & PVG_FT_STROKE_TAG_CUBIC) + *write = PVG_FT_CURVE_TAG_CUBIC; + else + *write = PVG_FT_CURVE_TAG_CONIC; + } + } + + /* copy contours */ + if (outline->contours) + { + PVG_FT_UInt count = border->num_points; + PVG_FT_Byte* tags = border->tags; + PVG_FT_Int* write = outline->contours + outline->n_contours; + PVG_FT_Int idx = (PVG_FT_Int)outline->n_points; + + for (; count > 0; count--, tags++, idx++) { + if (*tags & PVG_FT_STROKE_TAG_END) { + *write++ = idx; + outline->n_contours++; + } + } + } + + outline->n_points = (int)(outline->n_points + border->num_points); + + assert(PVG_FT_Outline_Check(outline) == 0); +} + +/*************************************************************************/ +/*************************************************************************/ +/***** *****/ +/***** STROKER *****/ +/***** *****/ +/*************************************************************************/ +/*************************************************************************/ + +#define PVG_FT_SIDE_TO_ROTATE(s) (PVG_FT_ANGLE_PI2 - (s)*PVG_FT_ANGLE_PI) + +typedef struct PVG_FT_StrokerRec_ { + PVG_FT_Angle angle_in; /* direction into curr join */ + PVG_FT_Angle angle_out; /* direction out of join */ + PVG_FT_Vector center; /* current position */ + PVG_FT_Fixed line_length; /* length of last lineto */ + PVG_FT_Bool first_point; /* is this the start? */ + PVG_FT_Bool subpath_open; /* is the subpath open? */ + PVG_FT_Angle subpath_angle; /* subpath start direction */ + PVG_FT_Vector subpath_start; /* subpath start position */ + PVG_FT_Fixed subpath_line_length; /* subpath start lineto len */ + PVG_FT_Bool handle_wide_strokes; /* use wide strokes logic? */ + + PVG_FT_Stroker_LineCap line_cap; + PVG_FT_Stroker_LineJoin line_join; + PVG_FT_Stroker_LineJoin line_join_saved; + PVG_FT_Fixed miter_limit; + PVG_FT_Fixed radius; + + PVG_FT_StrokeBorderRec borders[2]; +} PVG_FT_StrokerRec; + +/* documentation is in ftstroke.h */ + +PVG_FT_Error PVG_FT_Stroker_New(PVG_FT_Stroker* astroker) +{ + PVG_FT_Error error = 0; /* assigned in PVG_FT_NEW */ + PVG_FT_Stroker stroker = NULL; + + stroker = (PVG_FT_StrokerRec*)calloc(1, sizeof(PVG_FT_StrokerRec)); + if (stroker) { + ft_stroke_border_init(&stroker->borders[0]); + ft_stroke_border_init(&stroker->borders[1]); + } + + *astroker = stroker; + + return error; +} + +void PVG_FT_Stroker_Rewind(PVG_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 PVG_FT_Stroker_Set(PVG_FT_Stroker stroker, PVG_FT_Fixed radius, + PVG_FT_Stroker_LineCap line_cap, + PVG_FT_Stroker_LineJoin line_join, + PVG_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; + + PVG_FT_Stroker_Rewind(stroker); +} + +/* documentation is in ftstroke.h */ + +void PVG_FT_Stroker_Done(PVG_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 PVG_FT_Error ft_stroker_arcto(PVG_FT_Stroker stroker, PVG_FT_Int side) +{ + PVG_FT_Angle total, rotate; + PVG_FT_Fixed radius = stroker->radius; + PVG_FT_Error error = 0; + PVG_FT_StrokeBorder border = stroker->borders + side; + + rotate = PVG_FT_SIDE_TO_ROTATE(side); + + total = PVG_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); + if (total == PVG_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 PVG_FT_Error +ft_stroker_cap(PVG_FT_Stroker stroker, + PVG_FT_Angle angle, + PVG_FT_Int side) +{ + PVG_FT_Error error = 0; + + if (stroker->line_cap == PVG_FT_STROKER_LINECAP_ROUND) + { + /* add a round cap */ + stroker->angle_in = angle; + stroker->angle_out = angle + PVG_FT_ANGLE_PI; + + error = ft_stroker_arcto(stroker, side); + } + else + { + /* add a square or butt cap */ + PVG_FT_Vector middle, delta; + PVG_FT_Fixed radius = stroker->radius; + PVG_FT_StrokeBorder border = stroker->borders + side; + + /* compute middle point and first angle point */ + PVG_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 == PVG_FT_STROKER_LINECAP_SQUARE ) + { + middle.x += stroker->center.x; + middle.y += stroker->center.y; + } + else /* PVG_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 PVG_FT_Error ft_stroker_inside(PVG_FT_Stroker stroker, PVG_FT_Int side, + PVG_FT_Fixed line_length) +{ + PVG_FT_StrokeBorder border = stroker->borders + side; + PVG_FT_Angle phi, theta, rotate; + PVG_FT_Fixed length; + PVG_FT_Vector sigma = {0, 0}; + PVG_FT_Vector delta; + PVG_FT_Error error = 0; + PVG_FT_Bool intersect; /* use intersection of lines? */ + + rotate = PVG_FT_SIDE_TO_ROTATE(side); + + theta = PVG_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 */ + PVG_FT_Fixed min_length; + + + PVG_FT_Vector_Unit( &sigma, theta ); + min_length = + ft_pos_abs( PVG_FT_MulDiv( stroker->radius, sigma.y, sigma.x ) ); + + intersect = PVG_FT_BOOL( min_length && + stroker->line_length >= min_length && + line_length >= min_length ); + } + + if (!intersect) { + PVG_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 = PVG_FT_DivFix( stroker->radius, sigma.x ); + + PVG_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 PVG_FT_Error +ft_stroker_outside( PVG_FT_Stroker stroker, + PVG_FT_Int side, + PVG_FT_Fixed line_length ) +{ + PVG_FT_StrokeBorder border = stroker->borders + side; + PVG_FT_Error error; + PVG_FT_Angle rotate; + + + if ( stroker->line_join == PVG_FT_STROKER_LINEJOIN_ROUND ) + error = ft_stroker_arcto( stroker, side ); + else + { + /* this is a mitered (pointed) or beveled (truncated) corner */ + PVG_FT_Fixed radius = stroker->radius; + PVG_FT_Vector sigma = {0, 0}; + PVG_FT_Angle theta = 0, phi = 0; + PVG_FT_Bool bevel, fixed_bevel; + + + rotate = PVG_FT_SIDE_TO_ROTATE( side ); + + bevel = + PVG_FT_BOOL( stroker->line_join == PVG_FT_STROKER_LINEJOIN_BEVEL ); + + fixed_bevel = + PVG_FT_BOOL( stroker->line_join != PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE ); + + /* check miter limit first */ + if ( !bevel ) + { + theta = PVG_FT_Angle_Diff( stroker->angle_in, stroker->angle_out ) / 2; + + if ( theta == PVG_FT_ANGLE_PI2 ) + theta = -rotate; + + phi = stroker->angle_in + theta + rotate; + + PVG_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 */ + PVG_FT_Vector delta; + + + /* add bevel */ + PVG_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 */ + PVG_FT_Vector middle, delta; + PVG_FT_Fixed coef; + + + /* compute middle point and first angle point */ + PVG_FT_Vector_From_Polar( &middle, + PVG_FT_MulFix( radius, stroker->miter_limit ), + phi ); + + coef = PVG_FT_DivFix( 0x10000L - sigma.x, sigma.y ); + delta.x = PVG_FT_MulFix( middle.y, coef ); + delta.y = PVG_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 ) + { + PVG_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) */ + { + PVG_FT_Fixed length; + PVG_FT_Vector delta; + + + length = PVG_FT_MulDiv( stroker->radius, stroker->miter_limit, sigma.x ); + + PVG_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 ) + { + PVG_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 PVG_FT_Error ft_stroker_process_corner(PVG_FT_Stroker stroker, + PVG_FT_Fixed line_length) +{ + PVG_FT_Error error = 0; + PVG_FT_Angle turn; + PVG_FT_Int inside_side; + + turn = PVG_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 PVG_FT_Error ft_stroker_subpath_start(PVG_FT_Stroker stroker, + PVG_FT_Angle start_angle, + PVG_FT_Fixed line_length) +{ + PVG_FT_Vector delta; + PVG_FT_Vector point; + PVG_FT_Error error; + PVG_FT_StrokeBorder border; + + PVG_FT_Vector_From_Polar(&delta, stroker->radius, + start_angle + PVG_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 */ + +PVG_FT_Error PVG_FT_Stroker_LineTo(PVG_FT_Stroker stroker, PVG_FT_Vector* to) +{ + PVG_FT_Error error = 0; + PVG_FT_StrokeBorder border; + PVG_FT_Vector delta; + PVG_FT_Angle angle; + PVG_FT_Int side; + PVG_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 = PVG_FT_Vector_Length(&delta); + + angle = PVG_FT_Atan2(delta.x, delta.y); + PVG_FT_Vector_From_Polar(&delta, stroker->radius, angle + PVG_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++) { + PVG_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 */ + +PVG_FT_Error PVG_FT_Stroker_ConicTo(PVG_FT_Stroker stroker, PVG_FT_Vector* control, + PVG_FT_Vector* to) +{ + PVG_FT_Error error = 0; + PVG_FT_Vector bez_stack[34]; + PVG_FT_Vector* arc; + PVG_FT_Vector* limit = bez_stack + 30; + PVG_FT_Bool first_arc = TRUE; + + /* if all control points are coincident, this is a no-op; */ + /* avoid creating a spurious corner */ + if (PVG_FT_IS_SMALL(stroker->center.x - control->x) && + PVG_FT_IS_SMALL(stroker->center.y - control->y) && + PVG_FT_IS_SMALL(control->x - to->x) && + PVG_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) { + PVG_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(PVG_FT_Angle_Diff(stroker->angle_in, angle_in)) > + PVG_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 = PVG_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 */ + { + PVG_FT_Vector ctrl, end; + PVG_FT_Angle theta, phi, rotate, alpha0 = 0; + PVG_FT_Fixed length; + PVG_FT_StrokeBorder border; + PVG_FT_Int side; + + theta = PVG_FT_Angle_Diff(angle_in, angle_out) / 2; + phi = angle_in + theta; + length = PVG_FT_DivFix(stroker->radius, PVG_FT_Cos(theta)); + + /* compute direction of original arc */ + if (stroker->handle_wide_strokes) + alpha0 = PVG_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 = PVG_FT_SIDE_TO_ROTATE(side); + + /* compute control point */ + PVG_FT_Vector_From_Polar(&ctrl, length, phi + rotate); + ctrl.x += arc[1].x; + ctrl.y += arc[1].y; + + /* compute end point */ + PVG_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) { + PVG_FT_Vector start; + PVG_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 = PVG_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(PVG_FT_Angle_Diff(alpha0, alpha1)) > + PVG_FT_ANGLE_PI / 2) { + PVG_FT_Angle beta, gamma; + PVG_FT_Vector bvec, delta; + PVG_FT_Fixed blen, sinA, sinB, alen; + + /* use the sine rule to find the intersection point */ + beta = + PVG_FT_Atan2(arc[2].x - start.x, arc[2].y - start.y); + gamma = PVG_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 = PVG_FT_Vector_Length(&bvec); + + sinA = ft_pos_abs(PVG_FT_Sin(alpha1 - gamma)); + sinB = ft_pos_abs(PVG_FT_Sin(beta - gamma)); + + alen = PVG_FT_MulDiv(blen, sinA, sinB); + + PVG_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; + stroker->line_length = 0; + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +PVG_FT_Error PVG_FT_Stroker_CubicTo(PVG_FT_Stroker stroker, PVG_FT_Vector* control1, + PVG_FT_Vector* control2, PVG_FT_Vector* to) +{ + PVG_FT_Error error = 0; + PVG_FT_Vector bez_stack[37]; + PVG_FT_Vector* arc; + PVG_FT_Vector* limit = bez_stack + 32; + PVG_FT_Bool first_arc = TRUE; + + /* if all control points are coincident, this is a no-op; */ + /* avoid creating a spurious corner */ + if (PVG_FT_IS_SMALL(stroker->center.x - control1->x) && + PVG_FT_IS_SMALL(stroker->center.y - control1->y) && + PVG_FT_IS_SMALL(control1->x - control2->x) && + PVG_FT_IS_SMALL(control1->y - control2->y) && + PVG_FT_IS_SMALL(control2->x - to->x) && + PVG_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) { + PVG_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(PVG_FT_Angle_Diff(stroker->angle_in, angle_in)) > + PVG_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 = PVG_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 */ + { + PVG_FT_Vector ctrl1, ctrl2, end; + PVG_FT_Angle theta1, phi1, theta2, phi2, rotate, alpha0 = 0; + PVG_FT_Fixed length1, length2; + PVG_FT_StrokeBorder border; + PVG_FT_Int side; + + theta1 = PVG_FT_Angle_Diff(angle_in, angle_mid) / 2; + theta2 = PVG_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 = PVG_FT_DivFix(stroker->radius, PVG_FT_Cos(theta1)); + length2 = PVG_FT_DivFix(stroker->radius, PVG_FT_Cos(theta2)); + + /* compute direction of original arc */ + if (stroker->handle_wide_strokes) + alpha0 = PVG_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 = PVG_FT_SIDE_TO_ROTATE(side); + + /* compute control points */ + PVG_FT_Vector_From_Polar(&ctrl1, length1, phi1 + rotate); + ctrl1.x += arc[2].x; + ctrl1.y += arc[2].y; + + PVG_FT_Vector_From_Polar(&ctrl2, length2, phi2 + rotate); + ctrl2.x += arc[1].x; + ctrl2.y += arc[1].y; + + /* compute end point */ + PVG_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) { + PVG_FT_Vector start; + PVG_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 = PVG_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(PVG_FT_Angle_Diff(alpha0, alpha1)) > + PVG_FT_ANGLE_PI / 2) { + PVG_FT_Angle beta, gamma; + PVG_FT_Vector bvec, delta; + PVG_FT_Fixed blen, sinA, sinB, alen; + + /* use the sine rule to find the intersection point */ + beta = + PVG_FT_Atan2(arc[3].x - start.x, arc[3].y - start.y); + gamma = PVG_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 = PVG_FT_Vector_Length(&bvec); + + sinA = ft_pos_abs(PVG_FT_Sin(alpha1 - gamma)); + sinB = ft_pos_abs(PVG_FT_Sin(beta - gamma)); + + alen = PVG_FT_MulDiv(blen, sinA, sinB); + + PVG_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; + stroker->line_length = 0; + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +PVG_FT_Error PVG_FT_Stroker_BeginSubPath(PVG_FT_Stroker stroker, PVG_FT_Vector* to, + PVG_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 `PVG_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 = + PVG_FT_BOOL(stroker->line_join != PVG_FT_STROKER_LINEJOIN_ROUND || + (stroker->subpath_open && + stroker->line_cap == PVG_FT_STROKER_LINECAP_BUTT)); + + /* record the subpath start point for each border */ + stroker->subpath_start = *to; + + stroker->angle_in = 0; + + return 0; +} + +static PVG_FT_Error ft_stroker_add_reverse_left(PVG_FT_Stroker stroker, + PVG_FT_Bool open) +{ + PVG_FT_StrokeBorder right = stroker->borders + 0; + PVG_FT_StrokeBorder left = stroker->borders + 1; + PVG_FT_Int new_points; + PVG_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, (PVG_FT_UInt)new_points); + if (error) goto Exit; + + { + PVG_FT_Vector* dst_point = right->points + right->num_points; + PVG_FT_Byte* dst_tag = right->tags + right->num_points; + PVG_FT_Vector* src_point = left->points + left->num_points - 1; + PVG_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] &= ~PVG_FT_STROKE_TAG_BEGIN_END; + else { + PVG_FT_Byte ttag = + (PVG_FT_Byte)(dst_tag[0] & PVG_FT_STROKE_TAG_BEGIN_END); + + /* switch begin/end tags if necessary */ + if (ttag == PVG_FT_STROKE_TAG_BEGIN || + ttag == PVG_FT_STROKE_TAG_END) + dst_tag[0] ^= PVG_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! */ +PVG_FT_Error PVG_FT_Stroker_EndSubPath(PVG_FT_Stroker stroker) +{ + PVG_FT_Error error = 0; + + if (stroker->subpath_open) { + PVG_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 + PVG_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 { + PVG_FT_Angle turn; + PVG_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 = PVG_FT_Stroker_LineTo(stroker, &stroker->subpath_start); + if (error) goto Exit; + } + + /* process the corner */ + stroker->angle_out = stroker->subpath_angle; + turn = PVG_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 */ + +PVG_FT_Error PVG_FT_Stroker_GetBorderCounts(PVG_FT_Stroker stroker, + PVG_FT_StrokerBorder border, + PVG_FT_UInt* anum_points, + PVG_FT_UInt* anum_contours) +{ + PVG_FT_UInt num_points = 0, num_contours = 0; + PVG_FT_Error error; + + if (!stroker || border > 1) { + error = -1; // PVG_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 */ + +PVG_FT_Error PVG_FT_Stroker_GetCounts(PVG_FT_Stroker stroker, + PVG_FT_UInt* anum_points, + PVG_FT_UInt* anum_contours) +{ + PVG_FT_UInt count1, count2, num_points = 0; + PVG_FT_UInt count3, count4, num_contours = 0; + PVG_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 PVG_FT_Stroker_ExportBorder(PVG_FT_Stroker stroker, + PVG_FT_StrokerBorder border, + PVG_FT_Outline* outline) +{ + if (border == PVG_FT_STROKER_BORDER_LEFT || + border == PVG_FT_STROKER_BORDER_RIGHT) { + PVG_FT_StrokeBorder sborder = &stroker->borders[border]; + + if (sborder->valid) ft_stroke_border_export(sborder, outline); + } +} + +/* documentation is in ftstroke.h */ + +void PVG_FT_Stroker_Export(PVG_FT_Stroker stroker, PVG_FT_Outline* outline) +{ + PVG_FT_Stroker_ExportBorder(stroker, PVG_FT_STROKER_BORDER_LEFT, outline); + PVG_FT_Stroker_ExportBorder(stroker, PVG_FT_STROKER_BORDER_RIGHT, outline); +} + +/* documentation is in ftstroke.h */ + +/* + * The following is very similar to PVG_FT_Outline_Decompose, except + * that we do support opened paths, and do not scale the outline. + */ +PVG_FT_Error PVG_FT_Stroker_ParseOutline(PVG_FT_Stroker stroker, + const PVG_FT_Outline* outline) +{ + PVG_FT_Vector v_last; + PVG_FT_Vector v_control; + PVG_FT_Vector v_start; + + PVG_FT_Vector* point; + PVG_FT_Vector* limit; + char* tags; + + PVG_FT_Error error; + + PVG_FT_Int n; /* index of contour in outline */ + PVG_FT_UInt first; /* index of first point in contour */ + PVG_FT_Int tag; /* current point's state */ + + if (!outline || !stroker) return -1; // PVG_FT_THROW( Invalid_Argument ); + + PVG_FT_Stroker_Rewind(stroker); + + first = 0; + + for (n = 0; n < outline->n_contours; n++) { + PVG_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 = PVG_FT_CURVE_TAG(tags[0]); + + /* A contour cannot start with a cubic control point! */ + if (tag == PVG_FT_CURVE_TAG_CUBIC) goto Invalid_Outline; + + /* check first point to determine origin */ + if (tag == PVG_FT_CURVE_TAG_CONIC) { + /* First point is conic control. Yes, this happens. */ + if (PVG_FT_CURVE_TAG(outline->tags[last]) == PVG_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 = PVG_FT_Stroker_BeginSubPath(stroker, &v_start, outline->contours_flag[n]); + if (error) goto Exit; + + while (point < limit) { + point++; + tags++; + + tag = PVG_FT_CURVE_TAG(tags[0]); + switch (tag) { + case PVG_FT_CURVE_TAG_ON: /* emit a single line_to */ + { + PVG_FT_Vector vec; + + vec.x = point->x; + vec.y = point->y; + + error = PVG_FT_Stroker_LineTo(stroker, &vec); + if (error) goto Exit; + continue; + } + + case PVG_FT_CURVE_TAG_CONIC: /* consume conic arcs */ + v_control.x = point->x; + v_control.y = point->y; + + Do_Conic: + if (point < limit) { + PVG_FT_Vector vec; + PVG_FT_Vector v_middle; + + point++; + tags++; + tag = PVG_FT_CURVE_TAG(tags[0]); + + vec = point[0]; + + if (tag == PVG_FT_CURVE_TAG_ON) { + error = + PVG_FT_Stroker_ConicTo(stroker, &v_control, &vec); + if (error) goto Exit; + continue; + } + + if (tag != PVG_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 = + PVG_FT_Stroker_ConicTo(stroker, &v_control, &v_middle); + if (error) goto Exit; + + v_control = vec; + goto Do_Conic; + } + + error = PVG_FT_Stroker_ConicTo(stroker, &v_control, &v_start); + goto Close; + + default: /* PVG_FT_CURVE_TAG_CUBIC */ + { + PVG_FT_Vector vec1, vec2; + + if (point + 1 > limit || + PVG_FT_CURVE_TAG(tags[1]) != PVG_FT_CURVE_TAG_CUBIC) + goto Invalid_Outline; + + point += 2; + tags += 2; + + vec1 = point[-2]; + vec2 = point[-1]; + + if (point <= limit) { + PVG_FT_Vector vec; + + vec = point[0]; + + error = PVG_FT_Stroker_CubicTo(stroker, &vec1, &vec2, &vec); + if (error) goto Exit; + continue; + } + + error = PVG_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 = PVG_FT_Stroker_EndSubPath(stroker); + if (error) goto Exit; + + first = last + 1; + } + + return 0; + +Exit: + return error; + +Invalid_Outline: + return -2; // PVG_FT_THROW( Invalid_Outline ); +} + +/* END */ diff --git a/third_party/lunasvg/3rdparty/plutovg/plutovg-ft-stroker.h b/third_party/lunasvg/3rdparty/plutovg/plutovg-ft-stroker.h new file mode 100644 index 0000000000..09e1c50df9 --- /dev/null +++ b/third_party/lunasvg/3rdparty/plutovg/plutovg-ft-stroker.h @@ -0,0 +1,320 @@ +/***************************************************************************/ +/* */ +/* 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. */ +/* */ +/***************************************************************************/ + +#ifndef PLUTOVG_FT_STROKER_H +#define PLUTOVG_FT_STROKER_H + +#include "plutovg-ft-raster.h" + +/************************************************************** + * + * @type: + * PVG_FT_Stroker + * + * @description: + * Opaque handler to a path stroker object. + */ +typedef struct PVG_FT_StrokerRec_* PVG_FT_Stroker; + + +/************************************************************** + * + * @enum: + * PVG_FT_Stroker_LineJoin + * + * @description: + * These values determine how two joining lines are rendered + * in a stroker. + * + * @values: + * PVG_FT_STROKER_LINEJOIN_ROUND :: + * Used to render rounded line joins. Circular arcs are used + * to join two lines smoothly. + * + * PVG_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. + * + * PVG_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. PVG_FT_STROKER_LINEJOIN_MITER_FIXED generates a miter + * line join as used in PostScript and PDF. + * + * PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE :: + * PVG_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. + * PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE generates a mitered line + * join as used in XPS. PVG_FT_STROKER_LINEJOIN_MITER is an alias + * for PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE, retained for + * backwards compatibility. + */ +typedef enum PVG_FT_Stroker_LineJoin_ +{ + PVG_FT_STROKER_LINEJOIN_ROUND = 0, + PVG_FT_STROKER_LINEJOIN_BEVEL = 1, + PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE = 2, + PVG_FT_STROKER_LINEJOIN_MITER = PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE, + PVG_FT_STROKER_LINEJOIN_MITER_FIXED = 3 + +} PVG_FT_Stroker_LineJoin; + + +/************************************************************** + * + * @enum: + * PVG_FT_Stroker_LineCap + * + * @description: + * These values determine how the end of opened sub-paths are + * rendered in a stroke. + * + * @values: + * PVG_FT_STROKER_LINECAP_BUTT :: + * The end of lines is rendered as a full stop on the last + * point itself. + * + * PVG_FT_STROKER_LINECAP_ROUND :: + * The end of lines is rendered as a half-circle around the + * last point. + * + * PVG_FT_STROKER_LINECAP_SQUARE :: + * The end of lines is rendered as a square around the + * last point. + */ +typedef enum PVG_FT_Stroker_LineCap_ +{ + PVG_FT_STROKER_LINECAP_BUTT = 0, + PVG_FT_STROKER_LINECAP_ROUND, + PVG_FT_STROKER_LINECAP_SQUARE + +} PVG_FT_Stroker_LineCap; + + +/************************************************************** + * + * @enum: + * PVG_FT_StrokerBorder + * + * @description: + * These values are used to select a given stroke border + * in @PVG_FT_Stroker_GetBorderCounts and @PVG_FT_Stroker_ExportBorder. + * + * @values: + * PVG_FT_STROKER_BORDER_LEFT :: + * Select the left border, relative to the drawing direction. + * + * PVG_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 @PVG_FT_Outline_GetInsideBorder and + * @PVG_FT_Outline_GetOutsideBorder to get these. + */ +typedef enum PVG_FT_StrokerBorder_ +{ + PVG_FT_STROKER_BORDER_LEFT = 0, + PVG_FT_STROKER_BORDER_RIGHT + +} PVG_FT_StrokerBorder; + + +/************************************************************** + * + * @function: + * PVG_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. + */ +PVG_FT_Error +PVG_FT_Stroker_New( PVG_FT_Stroker *astroker ); + + +/************************************************************** + * + * @function: + * PVG_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 PVG_FT_STROKER_LINEJOIN_MITER_FIXED and + * PVG_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 +PVG_FT_Stroker_Set( PVG_FT_Stroker stroker, + PVG_FT_Fixed radius, + PVG_FT_Stroker_LineCap line_cap, + PVG_FT_Stroker_LineJoin line_join, + PVG_FT_Fixed miter_limit ); + +/************************************************************** + * + * @function: + * PVG_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 @PVG_FT_Stroker_GetCounts and @PVG_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 @PVG_FT_Stroker_Rewind automatically. + */ +PVG_FT_Error +PVG_FT_Stroker_ParseOutline( PVG_FT_Stroker stroker, + const PVG_FT_Outline* outline); + + +/************************************************************** + * + * @function: + * PVG_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. + */ +PVG_FT_Error +PVG_FT_Stroker_GetCounts( PVG_FT_Stroker stroker, + PVG_FT_UInt *anum_points, + PVG_FT_UInt *anum_contours ); + + +/************************************************************** + * + * @function: + * PVG_FT_Stroker_Export + * + * @description: + * Call this function after @PVG_FT_Stroker_GetBorderCounts to + * export all borders to your own @PVG_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 +PVG_FT_Stroker_Export( PVG_FT_Stroker stroker, + PVG_FT_Outline* outline ); + + +/************************************************************** + * + * @function: + * PVG_FT_Stroker_Done + * + * @description: + * Destroy a stroker object. + * + * @input: + * stroker :: + * A stroker handle. Can be NULL. + */ +void +PVG_FT_Stroker_Done( PVG_FT_Stroker stroker ); + + +#endif // PLUTOVG_FT_STROKER_H diff --git a/third_party/lunasvg/3rdparty/plutovg/plutovg-ft-types.h b/third_party/lunasvg/3rdparty/plutovg/plutovg-ft-types.h new file mode 100644 index 0000000000..61b77787a0 --- /dev/null +++ b/third_party/lunasvg/3rdparty/plutovg/plutovg-ft-types.h @@ -0,0 +1,173 @@ +/**************************************************************************** + * + * fttypes.h + * + * FreeType simple types definitions (specification only). + * + * Copyright (C) 1996-2020 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. + * + */ + +#ifndef PLUTOVG_FT_TYPES_H +#define PLUTOVG_FT_TYPES_H + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Fixed */ +/* */ +/* */ +/* This type is used to store 16.16 fixed-point values, like scaling */ +/* values or matrix coefficients. */ +/* */ +typedef signed long PVG_FT_Fixed; + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Int */ +/* */ +/* */ +/* A typedef for the int type. */ +/* */ +typedef signed int PVG_FT_Int; + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_UInt */ +/* */ +/* */ +/* A typedef for the unsigned int type. */ +/* */ +typedef unsigned int PVG_FT_UInt; + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Long */ +/* */ +/* */ +/* A typedef for signed long. */ +/* */ +typedef signed long PVG_FT_Long; + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_ULong */ +/* */ +/* */ +/* A typedef for unsigned long. */ +/* */ +typedef unsigned long PVG_FT_ULong; + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Short */ +/* */ +/* */ +/* A typedef for signed short. */ +/* */ +typedef signed short PVG_FT_Short; + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Byte */ +/* */ +/* */ +/* A simple typedef for the _unsigned_ char type. */ +/* */ +typedef unsigned char PVG_FT_Byte; + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_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 PVG_FT_Bool; + + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Error */ +/* */ +/* */ +/* The FreeType error code type. A value of~0 is always interpreted */ +/* as a successful operation. */ +/* */ +typedef int PVG_FT_Error; + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Pos */ +/* */ +/* */ +/* The type PVG_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 PVG_FT_Pos; + + +/*************************************************************************/ +/* */ +/* */ +/* PVG_FT_Vector */ +/* */ +/* */ +/* A simple structure used to store a 2D vector; coordinates are of */ +/* the PVG_FT_Pos type. */ +/* */ +/* */ +/* x :: The horizontal coordinate. */ +/* y :: The vertical coordinate. */ +/* */ +typedef struct PVG_FT_Vector_ +{ + PVG_FT_Pos x; + PVG_FT_Pos y; + +} PVG_FT_Vector; + + +typedef long long int PVG_FT_Int64; +typedef unsigned long long int PVG_FT_UInt64; + +typedef signed int PVG_FT_Int32; +typedef unsigned int PVG_FT_UInt32; + +#define PVG_FT_BOOL( x ) ( (PVG_FT_Bool)( x ) ) + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#endif // PLUTOVG_FT_TYPES_H diff --git a/third_party/lunasvg/3rdparty/plutovg/plutovg-geometry.c b/third_party/lunasvg/3rdparty/plutovg/plutovg-geometry.c new file mode 100644 index 0000000000..6bc7489825 --- /dev/null +++ b/third_party/lunasvg/3rdparty/plutovg/plutovg-geometry.c @@ -0,0 +1,581 @@ +#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); +} + +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 * plutovg_kappa; + double cpy = ry * plutovg_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 * plutovg_kappa; + double cpy = ry * plutovg_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/third_party/lunasvg/3rdparty/plutovg/plutovg-paint.c b/third_party/lunasvg/3rdparty/plutovg/plutovg-paint.c new file mode 100644 index 0000000000..247231312e --- /dev/null +++ b/third_party/lunasvg/3rdparty/plutovg/plutovg-paint.c @@ -0,0 +1,265 @@ +#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 = plutovg_clamp(r, 0.0, 1.0); + color->g = plutovg_clamp(g, 0.0, 1.0); + color->b = plutovg_clamp(b, 0.0, 1.0); + color->a = plutovg_clamp(a, 0.0, 1.0); +} + +void plutovg_gradient_init_linear(plutovg_gradient_t* gradient, double x1, double y1, double x2, double y2) +{ + gradient->type = plutovg_gradient_type_linear; + gradient->spread = plutovg_spread_method_pad; + gradient->opacity = 1.0; + plutovg_array_clear(gradient->stops); + plutovg_matrix_init_identity(&gradient->matrix); + plutovg_gradient_set_values_linear(gradient, x1, y1, x2, y2); +} + +void plutovg_gradient_init_radial(plutovg_gradient_t* gradient, double cx, double cy, double cr, double fx, double fy, double fr) +{ + gradient->type = plutovg_gradient_type_radial; + gradient->spread = plutovg_spread_method_pad; + gradient->opacity = 1.0; + plutovg_array_clear(gradient->stops); + plutovg_matrix_init_identity(&gradient->matrix); + plutovg_gradient_set_values_radial(gradient, cx, cy, cr, fx, fy, fr); +} + +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) +{ + if(offset < 0.0) offset = 0.0; + if(offset > 1.0) offset = 1.0; + + plutovg_array_ensure(gradient->stops, 1); + plutovg_gradient_stop_t* stops = gradient->stops.data; + int nstops = gradient->stops.size; + int i = 0; + for(; i < nstops; i++) { + if(offset < stops[i].offset) { + memmove(&stops[i+1], &stops[i], (size_t)(nstops - i) * sizeof(plutovg_gradient_stop_t)); + break; + } + } + + plutovg_gradient_stop_t* stop = &stops[i]; + stop->offset = offset; + plutovg_color_init_rgba(&stop->color, r, g, b, a); + gradient->stops.size += 1; +} + +void plutovg_gradient_add_stop_color(plutovg_gradient_t* gradient, double offset, const plutovg_color_t* color) +{ + plutovg_gradient_add_stop_rgba(gradient, offset, color->r, color->g, color->b, color->a); +} + +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; +} + +plutovg_gradient_type_t plutovg_gradient_get_type(const plutovg_gradient_t* gradient) +{ + return gradient->type; +} + +void plutovg_gradient_get_values_linear(const plutovg_gradient_t* gradient, double* x1, double* y1, double* x2, double* y2) +{ + if(x1) *x1 = gradient->values[0]; + if(y1) *y1 = gradient->values[1]; + if(x2) *x2 = gradient->values[2]; + if(y2) *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) +{ + if(cx) *cx = gradient->values[0]; + if(cy) *cy = gradient->values[1]; + if(cr) *cr = gradient->values[2]; + if(fx) *fx = gradient->values[3]; + if(fy) *fy = gradient->values[4]; + if(fr) *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 = plutovg_clamp(opacity, 0.0, 1.0); +} + +double plutovg_gradient_get_opacity(const plutovg_gradient_t* gradient) +{ + return gradient->opacity; +} + +void plutovg_gradient_copy(plutovg_gradient_t* gradient, const plutovg_gradient_t* source) +{ + gradient->type = source->type; + gradient->spread = source->spread; + gradient->matrix = source->matrix; + gradient->opacity = source->opacity; + plutovg_array_ensure(gradient->stops, source->stops.size); + memcpy(gradient->values, source->values, sizeof(source->values)); + memcpy(gradient->stops.data, source->stops.data, source->stops.size * sizeof(plutovg_gradient_stop_t)); +} + +void plutovg_gradient_destroy(plutovg_gradient_t* gradient) +{ + plutovg_array_destroy(gradient->stops); +} + +void plutovg_texture_init(plutovg_texture_t* texture, plutovg_surface_t* surface, plutovg_texture_type_t type) +{ + surface = plutovg_surface_reference(surface); + plutovg_surface_destroy(texture->surface); + texture->type = type; + texture->surface = surface; + texture->opacity = 1.0; + plutovg_matrix_init_identity(&texture->matrix); +} + +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) +{ + texture->matrix = *matrix; +} + +void plutovg_texture_get_matrix(const plutovg_texture_t* texture, plutovg_matrix_t* matrix) +{ + *matrix = texture->matrix; +} + +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 = plutovg_clamp(opacity, 0.0, 1.0); +} + +double plutovg_texture_get_opacity(const plutovg_texture_t* texture) +{ + return texture->opacity; +} + +void plutovg_texture_copy(plutovg_texture_t* texture, const plutovg_texture_t* source) +{ + plutovg_surface_t* surface = plutovg_surface_reference(source->surface); + plutovg_surface_destroy(texture->surface); + texture->type = source->type; + texture->surface = surface; + texture->opacity = source->opacity; + texture->matrix = source->matrix; +} + +void plutovg_texture_destroy(plutovg_texture_t* texture) +{ + plutovg_surface_destroy(texture->surface); +} + +void plutovg_paint_init(plutovg_paint_t* paint) +{ + paint->type = plutovg_paint_type_color; + paint->texture.surface = NULL; + plutovg_array_init(paint->gradient.stops); + plutovg_color_init_rgb(&paint->color, 0, 0, 0); +} + +void plutovg_paint_destroy(plutovg_paint_t* paint) +{ + plutovg_texture_destroy(&paint->texture); + plutovg_gradient_destroy(&paint->gradient); +} + +void plutovg_paint_copy(plutovg_paint_t* paint, const plutovg_paint_t* source) +{ + paint->type = source->type; + if(source->type == plutovg_paint_type_color) + paint->color = source->color; + else if(source->type == plutovg_paint_type_color) + plutovg_gradient_copy(&paint->gradient, &paint->gradient); + else + plutovg_texture_copy(&paint->texture, &paint->texture); +} diff --git a/third_party/lunasvg/3rdparty/plutovg/plutovg-private.h b/third_party/lunasvg/3rdparty/plutovg/plutovg-private.h new file mode 100644 index 0000000000..8524873751 --- /dev/null +++ b/third_party/lunasvg/3rdparty/plutovg/plutovg-private.h @@ -0,0 +1,198 @@ +#ifndef PLUTOVG_PRIVATE_H +#define PLUTOVG_PRIVATE_H + +#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; +}; + +typedef int plutovg_paint_type_t; + +enum { + plutovg_paint_type_color, + plutovg_paint_type_gradient, + plutovg_paint_type_texture +}; + +typedef struct { + plutovg_paint_type_t type; + plutovg_color_t color; + plutovg_gradient_t gradient; + plutovg_texture_t texture; +} plutovg_paint_t; + +typedef struct { + int x; + int len; + int y; + 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 paint; + 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; + void* outline_data; + size_t outline_size; +}; + +void plutovg_paint_init(plutovg_paint_t* paint); +void plutovg_paint_destroy(plutovg_paint_t* paint); +void plutovg_paint_copy(plutovg_paint_t* paint, const plutovg_paint_t* source); + +void plutovg_gradient_copy(plutovg_gradient_t* gradient, const plutovg_gradient_t* source); +void plutovg_gradient_destroy(plutovg_gradient_t* gradient); + +void plutovg_texture_copy(plutovg_texture_t* texture, const plutovg_texture_t* source); +void plutovg_texture_destroy(plutovg_texture_t* texture); + +plutovg_rle_t* plutovg_rle_create(void); +void plutovg_rle_destroy(plutovg_rle_t* rle); +void plutovg_rle_rasterize(plutovg_t* pluto, 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_sqrt2 1.41421356237309504880 +#define plutovg_pi 3.14159265358979323846 +#define plutovg_two_pi 6.28318530717958647693 +#define plutovg_half_pi 1.57079632679489661923 +#define plutovg_kappa 0.55228474983079339840 + +#define plutovg_min(a, b) ((a) < (b) ? (a) : (b)) +#define plutovg_max(a, b) ((a) > (b) ? (a) : (b)) +#define plutovg_clamp(v, lo, hi) ((v) < (lo) ? (lo) : (hi) < (v) ? (hi) : (v)) +#define plutovg_div255(x) (((x) + ((x) >> 8) + 0x80) >> 8) + +#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, newcapacity * sizeof(array.data[0])); \ + array.capacity = newcapacity; \ + } \ +} while(0) + +#define plutovg_array_clear(array) (array.size = 0) +#define plutovg_array_destroy(array) free(array.data) + +#endif // PLUTOVG_PRIVATE_H diff --git a/third_party/lunasvg/3rdparty/plutovg/plutovg-rle.c b/third_party/lunasvg/3rdparty/plutovg/plutovg-rle.c new file mode 100644 index 0000000000..31eb86501c --- /dev/null +++ b/third_party/lunasvg/3rdparty/plutovg/plutovg-rle.c @@ -0,0 +1,424 @@ +#include "plutovg-private.h" + +#include "plutovg-ft-raster.h" +#include "plutovg-ft-stroker.h" + +#include +#include + +#define ALIGN_SIZE(size) (((size) + 7ul) & ~7ul) +static void ft_outline_init(PVG_FT_Outline* outline, plutovg_t* pluto, int points, int contours) +{ + size_t size_a = ALIGN_SIZE((points + contours) * sizeof(PVG_FT_Vector)); + size_t size_b = ALIGN_SIZE((points + contours) * sizeof(char)); + size_t size_c = ALIGN_SIZE(contours * sizeof(int)); + size_t size_d = ALIGN_SIZE(contours * sizeof(char)); + size_t size_n = size_a + size_b + size_c + size_d; + if(size_n > pluto->outline_size) { + pluto->outline_data = realloc(pluto->outline_data, size_n); + pluto->outline_size = size_n; + } + + PVG_FT_Byte* data = pluto->outline_data; + outline->points = (PVG_FT_Vector*)(data); + outline->tags = outline->contours_flag = NULL; + outline->contours = NULL; + if(data){ + outline->tags = (char*)(data + size_a); + outline->contours = (int*)(data + size_a + size_b); + outline->contours_flag = (char*)(data + size_a + size_b + size_c); + } + outline->n_points = 0; + outline->n_contours = 0; + outline->flags = 0x0; +} + +#define FT_COORD(x) (PVG_FT_Pos)((x) * 64) +static void ft_outline_move_to(PVG_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] = PVG_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 ft_outline_line_to(PVG_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] = PVG_FT_CURVE_TAG_ON; + ft->n_points++; +} + +static void ft_outline_cubic_to(PVG_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] = PVG_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] = PVG_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] = PVG_FT_CURVE_TAG_ON; + ft->n_points++; +} + +static void ft_outline_close(PVG_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] = PVG_FT_CURVE_TAG_ON; + ft->n_points++; +} + +static void ft_outline_end(PVG_FT_Outline* ft) +{ + if(ft->n_points) { + ft->contours[ft->n_contours] = ft->n_points - 1; + ft->n_contours++; + } +} + +static void ft_outline_convert(PVG_FT_Outline* outline, plutovg_t* pluto, const plutovg_path_t* path, const plutovg_matrix_t* matrix) +{ + ft_outline_init(outline, pluto, 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]); + 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]); + 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]); + 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: + ft_outline_close(outline); + points += 1; + break; + } + } + + ft_outline_end(outline); +} + +static void ft_outline_convert_dash(PVG_FT_Outline* outline, plutovg_t* pluto, const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_dash_t* dash) +{ + plutovg_path_t* dashed = plutovg_dash_path(dash, path); + ft_outline_convert(outline, pluto, dashed, matrix); + plutovg_path_destroy(dashed); +} + +static void generation_callback(int count, const PVG_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; +} + +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); +} + +void plutovg_rle_rasterize(plutovg_t* pluto, 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) +{ + PVG_FT_Raster_Params params; + params.flags = PVG_FT_RASTER_FLAG_DIRECT | PVG_FT_RASTER_FLAG_AA; + params.gray_spans = generation_callback; + params.user = rle; + if(clip) { + params.flags |= PVG_FT_RASTER_FLAG_CLIP; + params.clip_box.xMin = (PVG_FT_Pos)(clip->x); + params.clip_box.yMin = (PVG_FT_Pos)(clip->y); + params.clip_box.xMax = (PVG_FT_Pos)(clip->x + clip->w); + params.clip_box.yMax = (PVG_FT_Pos)(clip->y + clip->h); + } + + if(stroke) { + PVG_FT_Outline outline; + if(stroke->dash == NULL) + ft_outline_convert(&outline, pluto, path, matrix); + else + ft_outline_convert_dash(&outline, pluto, path, matrix, stroke->dash); + PVG_FT_Stroker_LineCap ftCap; + PVG_FT_Stroker_LineJoin ftJoin; + PVG_FT_Fixed ftWidth; + PVG_FT_Fixed ftMiterLimit; + + plutovg_point_t p1 = {0, 0}; + plutovg_point_t p2 = {plutovg_sqrt2, plutovg_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 = (PVG_FT_Fixed)(stroke->width * scale * 0.5 * (1 << 6)); + ftMiterLimit = (PVG_FT_Fixed)(stroke->miterlimit * (1 << 16)); + + switch(stroke->cap) { + case plutovg_line_cap_square: + ftCap = PVG_FT_STROKER_LINECAP_SQUARE; + break; + case plutovg_line_cap_round: + ftCap = PVG_FT_STROKER_LINECAP_ROUND; + break; + default: + ftCap = PVG_FT_STROKER_LINECAP_BUTT; + break; + } + + switch(stroke->join) { + case plutovg_line_join_bevel: + ftJoin = PVG_FT_STROKER_LINEJOIN_BEVEL; + break; + case plutovg_line_join_round: + ftJoin = PVG_FT_STROKER_LINEJOIN_ROUND; + break; + default: + ftJoin = PVG_FT_STROKER_LINEJOIN_MITER_FIXED; + break; + } + + PVG_FT_Stroker stroker; + PVG_FT_Stroker_New(&stroker); + PVG_FT_Stroker_Set(stroker, ftWidth, ftCap, ftJoin, ftMiterLimit); + PVG_FT_Stroker_ParseOutline(stroker, &outline); + + PVG_FT_UInt points; + PVG_FT_UInt contours; + PVG_FT_Stroker_GetCounts(stroker, &points, &contours); + + ft_outline_init(&outline, pluto, points, contours); + PVG_FT_Stroker_Export(stroker, &outline); + PVG_FT_Stroker_Done(stroker); + + outline.flags = PVG_FT_OUTLINE_NONE; + params.source = &outline; + PVG_FT_Raster_Render(¶ms); + } else { + PVG_FT_Outline outline; + ft_outline_convert(&outline, pluto, path, matrix); + switch(winding) { + case plutovg_fill_rule_even_odd: + outline.flags = PVG_FT_OUTLINE_EVEN_ODD_FILL; + break; + default: + outline.flags = PVG_FT_OUTLINE_NONE; + break; + } + + params.source = &outline; + PVG_FT_Raster_Render(¶ms); + } + + if(rle->spans.size == 0) { + rle->x = 0; + rle->y = 0; + rle->w = 0; + rle->h = 0; + return; + } + + plutovg_span_t* spans = rle->spans.data; + int x1 = INT_MAX; + int y1 = spans[0].y; + int x2 = 0; + int y2 = spans[rle->spans.size - 1].y; + for(int i = 0;i < rle->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; + } + + rle->x = x1; + rle->y = y1; + rle->w = x2 - x1; + rle->h = y2 - y1 + 1; +} + +plutovg_rle_t* plutovg_rle_intersection(const plutovg_rle_t* a, const plutovg_rle_t* b) +{ + int count = plutovg_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 = plutovg_max(ax1, bx1); + int len = plutovg_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 = plutovg_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/third_party/lunasvg/3rdparty/plutovg/plutovg.c b/third_party/lunasvg/3rdparty/plutovg/plutovg.c new file mode 100644 index 0000000000..3b357bcbf0 --- /dev/null +++ b/third_party/lunasvg/3rdparty/plutovg/plutovg.c @@ -0,0 +1,495 @@ +#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; + plutovg_paint_init(&state->paint); + 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 = plutovg_state_create(); + newstate->clippath = plutovg_rle_clone(state->clippath); + plutovg_paint_copy(&newstate->paint, &state->paint); + 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->paint); + 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; + pluto->outline_data = NULL; + pluto->outline_size = 0; + 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->outline_data); + 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); +} + +plutovg_color_t* plutovg_set_rgb(plutovg_t* pluto, double r, double g, double b) +{ + return plutovg_set_rgba(pluto, r, g, b, 1.0); +} + +plutovg_color_t* plutovg_set_rgba(plutovg_t* pluto, double r, double g, double b, double a) +{ + plutovg_paint_t* paint = &pluto->state->paint; + paint->type = plutovg_paint_type_color; + plutovg_color_init_rgba(&paint->color, r, g, b, a); + return &paint->color; +} + +plutovg_color_t* plutovg_set_color(plutovg_t* pluto, const plutovg_color_t* color) +{ + return plutovg_set_rgba(pluto, color->r, color->g, color->b, color->a); +} + +plutovg_gradient_t* plutovg_set_linear_gradient(plutovg_t* pluto, double x1, double y1, double x2, double y2) +{ + plutovg_paint_t* paint = &pluto->state->paint; + paint->type = plutovg_paint_type_gradient; + plutovg_gradient_init_linear(&paint->gradient, x1, y1, x2, y2); + return &paint->gradient; +} + +plutovg_gradient_t* plutovg_set_radial_gradient(plutovg_t* pluto, double cx, double cy, double cr, double fx, double fy, double fr) +{ + plutovg_paint_t* paint = &pluto->state->paint; + paint->type = plutovg_paint_type_gradient; + plutovg_gradient_init_radial(&paint->gradient, cx, cy, cr, fx, fy, fr); + return &paint->gradient; +} + +plutovg_texture_t* plutovg_set_texture_surface(plutovg_t* pluto, plutovg_surface_t* surface, double x, double y) +{ + plutovg_texture_t* texture = plutovg_set_texture(pluto, surface, plutovg_texture_type_plain); + plutovg_matrix_init_translate(&texture->matrix, x, y); + return texture; +} + +plutovg_texture_t* plutovg_set_texture(plutovg_t* pluto, plutovg_surface_t* surface, plutovg_texture_type_t type) +{ + plutovg_paint_t* paint = &pluto->state->paint; + paint->type = plutovg_paint_type_texture; + plutovg_texture_init(&paint->texture, surface, type); + return &paint->texture; +} + +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, 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, 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, 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, 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(pluto, 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/third_party/lunasvg/3rdparty/plutovg/plutovg.h b/third_party/lunasvg/3rdparty/plutovg/plutovg.h new file mode 100644 index 0000000000..e9da860e7d --- /dev/null +++ b/third_party/lunasvg/3rdparty/plutovg/plutovg.h @@ -0,0 +1,260 @@ +#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; + +void plutovg_gradient_init_linear(plutovg_gradient_t* gradient, double x1, double y1, double x2, double y2); +void plutovg_gradient_init_radial(plutovg_gradient_t* gradient, double cx, double cy, double cr, double fx, double fy, double fr); +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; + +void plutovg_texture_init(plutovg_texture_t* texture, plutovg_surface_t* surface, plutovg_texture_type_t type); +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 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); + +plutovg_color_t* plutovg_set_rgb(plutovg_t* pluto, double r, double g, double b); +plutovg_color_t* plutovg_set_rgba(plutovg_t* pluto, double r, double g, double b, double a); +plutovg_color_t* plutovg_set_color(plutovg_t* pluto, const plutovg_color_t* color); + +plutovg_gradient_t* plutovg_set_linear_gradient(plutovg_t* pluto, double x1, double y1, double x2, double y2); +plutovg_gradient_t* plutovg_set_radial_gradient(plutovg_t* pluto, double cx, double cy, double cr, double fx, double fy, double fr); + +plutovg_texture_t* plutovg_set_texture_surface(plutovg_t* pluto, plutovg_surface_t* surface, double x, double y); +plutovg_texture_t* plutovg_set_texture(plutovg_t* pluto, plutovg_surface_t* surface, plutovg_texture_type_t type); + +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/third_party/lunasvg/3rdparty/stb/CMakeLists.txt b/third_party/lunasvg/3rdparty/stb/CMakeLists.txt new file mode 100644 index 0000000000..00a974fbb6 --- /dev/null +++ b/third_party/lunasvg/3rdparty/stb/CMakeLists.txt @@ -0,0 +1,4 @@ +target_include_directories(lunasvg +PRIVATE + "${CMAKE_CURRENT_LIST_DIR}" +) diff --git a/third_party/lunasvg/3rdparty/stb/stb_image_write.h b/third_party/lunasvg/3rdparty/stb/stb_image_write.h new file mode 100644 index 0000000000..e4b32ed1bc --- /dev/null +++ b/third_party/lunasvg/3rdparty/stb/stb_image_write.h @@ -0,0 +1,1724 @@ +/* stb_image_write - v1.16 - public domain - http://nothings.org/stb + writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 + 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. + + Will probably not work correctly with strict-aliasing optimizations. + +ABOUT: + + This header file is a library for writing images to C stdio or a callback. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation; though providing a custom + zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. + This library is designed for source code compactness and simplicity, + not optimal image file size or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can #define STBIW_MEMMOVE() to replace memmove() + You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function + for PNG compression (instead of the builtin one), it must have the following signature: + unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); + The returned data will be freed with STBIW_FREE() (free() by default), + so it must be heap allocated with STBIW_MALLOC() (malloc() by default), + +UNICODE: + + If compiling for Windows and you wish to use Unicode filenames, compile + with + #define STBIW_WINDOWS_UTF8 + and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert + Windows wchar_t filenames to utf8. + +USAGE: + + There are five 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); + int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); + + void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically + + There are also five equivalent functions that use an arbitrary write function. You are + expected to open/close your file-equivalent before and after calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can configure it with these global variables: + int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE + int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression + int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode + + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + 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 format expands Y to RGB in the file format and 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.) + + PNG allows you to set the deflate compression level by setting the global + variable 'stbi_write_png_compression_level' (it defaults to 8). + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + + JPEG does ignore alpha channels in input data; quality is between 1 and 100. + Higher quality looks better but results in a bigger image. + JPEG baseline (no JPEG progressive). + +CREDITS: + + + Sean Barrett - PNG/BMP/TGA + Baldur Karlsson - HDR + Jean-Sebastien Guay - TGA monochrome + Tim Kelsey - misc enhancements + Alan Hickman - TGA RLE + Emmanuel Julien - initial file IO callback implementation + Jon Olick - original jo_jpeg.cpp code + Daniel Gibson - integrate JPEG, allow external zlib + Aarni Koskela - allow choosing PNG filter + + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + Thatcher Ulrich + github:poppolopoppo + Patrick Boettcher + github:xeekworx + Cap Petschulat + Simon Rodriguez + Ivan Tikhonov + github:ignotion + Adam Schackart + Andrew Kensler + +LICENSE + + See end of file for license information. + +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#include + +// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' +#ifndef STBIWDEF +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#ifdef __cplusplus +#define STBIWDEF extern "C" +#else +#define STBIWDEF extern +#endif +#endif +#endif + +#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations +STBIWDEF int stbi_write_tga_with_rle; +STBIWDEF int stbi_write_png_compression_level; +STBIWDEF int stbi_write_force_png_filter; +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); + +#ifdef STBIW_WINDOWS_UTF8 +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif +#endif + +typedef void stbi_write_func(void *context, void *data, int size); + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + +STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); + +#endif//INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include +#endif // STBI_WRITE_NO_STDIO + +#include +#include +#include +#include + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,newsz) realloc(p,newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#endif + + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT +#include +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) + +#ifdef STB_IMAGE_WRITE_STATIC +static int stbi_write_png_compression_level = 8; +static int stbi_write_tga_with_rle = 1; +static int stbi_write_force_png_filter = -1; +#else +int stbi_write_png_compression_level = 8; +int stbi_write_tga_with_rle = 1; +int stbi_write_force_png_filter = -1; +#endif + +static int stbi__flip_vertically_on_write = 0; + +STBIWDEF void stbi_flip_vertically_on_write(int flag) +{ + stbi__flip_vertically_on_write = flag; +} + +typedef struct +{ + stbi_write_func *func; + void *context; + unsigned char buffer[64]; + int buf_used; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) +{ + s->func = c; + s->context = context; +} + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) +{ + fwrite(data,1,size,(FILE*) context); +} + +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) +#ifdef __cplusplus +#define STBIW_EXTERN extern "C" +#else +#define STBIW_EXTERN extern +#endif +STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); + +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbiw__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + +static int stbi__start_write_file(stbi__write_context *s, const char *filename) +{ + FILE *f = stbiw__fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) +{ + fclose((FILE *)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); + s->func(s->context,&x,1); + break; } + case '2': { int x = va_arg(v,int); + unsigned char b[2]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x>>8); + s->func(s->context,b,2); + break; } + case '4': { stbiw_uint32 x = va_arg(v,int); + unsigned char b[4]; + b[0]=STBIW_UCHAR(x); + b[1]=STBIW_UCHAR(x>>8); + b[2]=STBIW_UCHAR(x>>16); + b[3]=STBIW_UCHAR(x>>24); + s->func(s->context,b,4); + break; } + default: + STBIW_ASSERT(0); + return; + } + } +} + +static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) +{ + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); +} + +static void stbiw__write_flush(stbi__write_context *s) +{ + if (s->buf_used) { + s->func(s->context, &s->buffer, s->buf_used); + s->buf_used = 0; + } +} + +static void stbiw__putc(stbi__write_context *s, unsigned char c) +{ + s->func(s->context, &c, 1); +} + +static void stbiw__write1(stbi__write_context *s, unsigned char a) +{ + if ((size_t)s->buf_used + 1 > sizeof(s->buffer)) + stbiw__write_flush(s); + s->buffer[s->buf_used++] = a; +} + +static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) +{ + int n; + if ((size_t)s->buf_used + 3 > sizeof(s->buffer)) + stbiw__write_flush(s); + n = s->buf_used; + s->buf_used = n+3; + s->buffer[n+0] = a; + s->buffer[n+1] = b; + s->buffer[n+2] = c; +} + +static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) +{ + unsigned char bg[3] = { 255, 0, 255}, px[3]; + int k; + + if (write_alpha < 0) + stbiw__write1(s, d[comp - 1]); + + switch (comp) { + case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case + case 1: + if (expand_mono) + stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp + else + stbiw__write1(s, d[0]); // monochrome TGA + 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; + stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); + break; + } + if (write_alpha > 0) + stbiw__write1(s, d[comp - 1]); +} + +static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) +{ + stbiw_uint32 zero = 0; + int i,j, j_end; + + if (y <= 0) + return; + + if (stbi__flip_vertically_on_write) + vdir *= -1; + + 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; + stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); + } + stbiw__write_flush(s); + s->func(s->context, &zero, scanline_pad); + } +} + +static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) +{ + if (y < 0 || x < 0) { + return 0; + } else { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); + stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); + return 1; + } +} + +static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) +{ + if (comp != 4) { + // write RGB bitmap + int pad = (-x*3) & 3; + return stbiw__outfile(s,-1,-1,x,y,comp,1,(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 + } else { + // RGBA bitmaps need a v4 header + // use BI_BITFIELDS mode with 32bpp and alpha mask + // (straight BI_RGB with alpha mask doesn't work in most readers) + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *)data,1,0, + "11 4 22 4" "4 44 22 444444 4444 4 444 444 444 444", + 'B', 'M', 14+108+x*y*4, 0, 0, 14+108, // file header + 108, x,y, 1,32, 3,0,0,0,0,0, 0xff0000,0xff00,0xff,0xff000000u, 0, 0,0,0, 0,0,0, 0,0,0, 0,0,0); // bitmap V4 header + } +} + +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_bmp_core(&s, x, y, comp, data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_bmp_core(&s, x, y, comp, data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif //!STBI_WRITE_NO_STDIO + +static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) +{ + int has_alpha = (comp == 2 || comp == 4); + int colorbytes = has_alpha ? comp-1 : comp; + int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 + + if (y < 0 || x < 0) + return 0; + + if (!stbi_write_tga_with_rle) { + return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, + "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); + } else { + int i,j,k; + int jend, jdir; + + stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); + + if (stbi__flip_vertically_on_write) { + j = 0; + jend = y; + jdir = 1; + } else { + j = y-1; + jend = -1; + jdir = -1; + } + for (; j != jend; j += jdir) { + unsigned char *row = (unsigned char *) data + j * x * comp; + int len; + + for (i = 0; i < x; i += len) { + unsigned char *begin = row + i * comp; + int diff = 1; + len = 1; + + if (i < x - 1) { + ++len; + diff = memcmp(begin, row + (i + 1) * comp, comp); + if (diff) { + const unsigned char *prev = begin; + for (k = i + 2; k < x && len < 128; ++k) { + if (memcmp(prev, row + k * comp, comp)) { + prev += comp; + ++len; + } else { + --len; + break; + } + } + } else { + for (k = i + 2; k < x && len < 128; ++k) { + if (!memcmp(begin, row + k * comp, comp)) { + ++len; + } else { + break; + } + } + } + } + + if (diff) { + unsigned char header = STBIW_UCHAR(len - 1); + stbiw__write1(s, header); + for (k = 0; k < len; ++k) { + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); + } + } else { + unsigned char header = STBIW_UCHAR(len - 129); + stbiw__write1(s, header); + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); + } + } + } + stbiw__write_flush(s); + } + return 1; +} + +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_tga_core(&s, x, y, comp, (void *) data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson + +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +#ifndef STBI_WRITE_NO_STDIO + +static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +{ + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) +{ + unsigned char lengthbyte = STBIW_UCHAR(length+128); + STBIW_ASSERT(length+128 <= 255); + s->func(s->context, &lengthbyte, 1); + s->func(s->context, &databyte, 1); +} + +static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) +{ + unsigned char lengthbyte = STBIW_UCHAR(length); + STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code + s->func(s->context, &lengthbyte, 1); + s->func(s->context, data, length); +} + +static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) +{ + unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width&0xff00)>>8; + scanlineheader[3] = (width&0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x=0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + s->func(s->context, rgbe, 4); + } + } else { + int c,r; + /* encode into scratch buffer */ + for (x=0; x < width; x++) { + switch(ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width*0] = rgbe[0]; + scratch[x + width*1] = rgbe[1]; + scratch[x + width*2] = rgbe[2]; + scratch[x + width*3] = rgbe[3]; + } + + s->func(s->context, scanlineheader, 4); + + /* RLE each component separately */ + for (c=0; c < 4; c++) { + unsigned char *comp = &scratch[width*c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r+2 < width) { + if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) + break; + ++r; + } + if (r+2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r-x; + if (len > 128) len = 128; + stbiw__write_dump_data(s, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r-x; + if (len > 127) len = 127; + stbiw__write_run_data(s, len, comp[x]); + x += len; + } + } + } + } + } +} + +static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) +{ + if (y <= 0 || x <= 0 || data == NULL) + return 0; + else { + // Each component is stored separately. Allocate scratch space for full output scanline. + unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); + int i, len; + char buffer[128]; + char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; + s->func(s->context, header, sizeof(header)-1); + +#ifdef __STDC_LIB_EXT1__ + len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#else + len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#endif + s->func(s->context, buffer, len); + + for(i=0; i < y; i++) + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); + STBIW_FREE(scratch); + return 1; + } +} + +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_hdr_core(&s, x, y, comp, (float *) data); +} + +STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif // STBI_WRITE_NO_STDIO + + +////////////////////////////////////////////////////////////////////////////// +// +// PNG writer +// + +#ifndef STBIW_ZLIB_COMPRESS +// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() +#define stbiw__sbraw(a) ((int *) (void *) (a) - 2) +#define stbiw__sbm(a) stbiw__sbraw(a)[0] +#define stbiw__sbn(a) stbiw__sbraw(a)[1] + +#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) +#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) +#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) +#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) + +static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; + void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stbiw__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbiw__zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbiw__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 stbiw__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 stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbiw__zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) +#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) +// default huffman tables +#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) +#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) +#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) +#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) +#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) +#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) + +#define stbiw__ZHASH 16384 + +#endif // STBIW_ZLIB_COMPRESS + +STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ +#ifdef STBIW_ZLIB_COMPRESS + // user provided a zlib compress implementation, use that + return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); +#else // use builtin + 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 = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**)); + if (hash_table == NULL) + return NULL; + if (quality < 5) quality = 5; + + stbiw__sbpush(out, 0x78); // DEFLATE 32K window + stbiw__sbpush(out, 0x5e); // FLEVEL = 1 + stbiw__zlib_add(1,1); // BFINAL = 1 + stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman + + for (i=0; i < stbiw__ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + // hash next 3 bytes of data to be compressed + int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { // if entry lies within window + int d = stbiw__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] && stbiw__sbn(hash_table[h]) == 2*quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + stbiw__sbn(hash_table[h]) = quality; + } + stbiw__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 = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); + hlist = hash_table[h]; + n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = stbiw__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 = (int) (data+i - bestloc); // distance back + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + stbiw__zlib_huff(j+257); + if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); + if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbiw__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (;i < data_len; ++i) + stbiw__zlib_huffb(data[i]); + stbiw__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbiw__zlib_add(0,1); + + for (i=0; i < stbiw__ZHASH; ++i) + (void) stbiw__sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + // store uncompressed instead if compression was worse + if (stbiw__sbn(out) > data_len + 2 + ((data_len+32766)/32767)*5) { + stbiw__sbn(out) = 2; // truncate to DEFLATE 32K window and FLEVEL = 1 + for (j = 0; j < data_len;) { + int blocklen = data_len - j; + if (blocklen > 32767) blocklen = 32767; + stbiw__sbpush(out, data_len - j == blocklen); // BFINAL = ?, BTYPE = 0 -- no compression + stbiw__sbpush(out, STBIW_UCHAR(blocklen)); // LEN + stbiw__sbpush(out, STBIW_UCHAR(blocklen >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(~blocklen)); // NLEN + stbiw__sbpush(out, STBIW_UCHAR(~blocklen >> 8)); + memcpy(out+stbiw__sbn(out), data+j, blocklen); + stbiw__sbn(out) += blocklen; + j += blocklen; + } + } + + { + // compute adler32 on input + unsigned int s1=1, s2=0; + int blocklen = (int) (data_len % 5552); + 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; + } + stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s2)); + stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = stbiw__sbn(out); + // make returned pointer freeable + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); + return (unsigned char *) stbiw__sbraw(out); +#endif // STBIW_ZLIB_COMPRESS +} + +static unsigned int stbiw__crc32(unsigned char *buffer, int len) +{ +#ifdef STBIW_CRC32 + return STBIW_CRC32(buffer, len); +#else + static unsigned int crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + unsigned int crc = ~0u; + int i; + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +#endif +} + +#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) +#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbiw__wpcrc(unsigned char **data, int len) +{ + unsigned int crc = stbiw__crc32(*data - len - 4, len+4); + stbiw__wp32(*data, crc); +} + +static unsigned char stbiw__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 STBIW_UCHAR(a); + if (pb <= pc) return STBIW_UCHAR(b); + return STBIW_UCHAR(c); +} + +// @OPTIMIZE: provide an option that always forces left-predict or paeth predict +static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer) +{ + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = (y != 0) ? mapping : firstmap; + int i; + int type = mymap[filter_type]; + unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); + int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; + + if (type==0) { + memcpy(line_buffer, z, width*n); + return; + } + + // first loop isn't optimized since it's just one pixel + for (i = 0; i < n; ++i) { + switch (type) { + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + } + switch (type) { + case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; + case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; + case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; + case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; + } +} + +STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int force_filter = stbi_write_force_png_filter; + 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 j,zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + if (force_filter >= 5) { + force_filter = -1; + } + + filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } + for (j=0; j < y; ++j) { + int filter_type; + if (force_filter > -1) { + filter_type = force_filter; + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); + } else { // Estimate the best filter by running through all of them: + int best_filter = 0, best_filter_val = 0x7fffffff, est, i; + for (filter_type = 0; filter_type < 5; filter_type++) { + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); + + // Estimate the entropy of the line using this filter; the less, the better. + est = 0; + for (i = 0; i < x*n; ++i) { + est += abs((signed char) line_buffer[i]); + } + if (est < best_filter_val) { + best_filter_val = est; + best_filter = filter_type; + } + } + if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); + filter_type = best_filter; + } + } + // when we get here, filter_type contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) filter_type; + STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); + } + STBIW_FREE(line_buffer); + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level); + STBIW_FREE(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); + if (!out) return 0; + *out_len = 8 + 12+13 + 12+zlen + 12; + + o=out; + STBIW_MEMMOVE(o,sig,8); o+= 8; + stbiw__wp32(o, 13); // header length + stbiw__wptag(o, "IHDR"); + stbiw__wp32(o, x); + stbiw__wp32(o, y); + *o++ = 8; + *o++ = STBIW_UCHAR(ctype[n]); + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbiw__wpcrc(&o,13); + + stbiw__wp32(o, zlen); + stbiw__wptag(o, "IDAT"); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); + stbiw__wpcrc(&o, zlen); + + stbiw__wp32(o,0); + stbiw__wptag(o, "IEND"); + stbiw__wpcrc(&o,0); + + STBIW_ASSERT(o == out + *out_len); + + return out; +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF 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((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + + f = stbiw__fopen(filename, "wb"); + if (!f) { STBIW_FREE(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + STBIW_FREE(png); + return 1; +} +#endif + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) +{ + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + func(context, png, len); + STBIW_FREE(png); + return 1; +} + + +/* *************************************************************************** + * + * JPEG writer + * + * This is based on Jon Olick's jo_jpeg.cpp: + * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html + */ + +static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, + 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; + +static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { + int bitBuf = *bitBufP, bitCnt = *bitCntP; + bitCnt += bs[1]; + bitBuf |= bs[0] << (24 - bitCnt); + while(bitCnt >= 8) { + unsigned char c = (bitBuf >> 16) & 255; + stbiw__putc(s, c); + if(c == 255) { + stbiw__putc(s, 0); + } + bitBuf <<= 8; + bitCnt -= 8; + } + *bitBufP = bitBuf; + *bitCntP = bitCnt; +} + +static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { + float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; + float z1, z2, z3, z4, z5, z11, z13; + + float tmp0 = d0 + d7; + float tmp7 = d0 - d7; + float tmp1 = d1 + d6; + float tmp6 = d1 - d6; + float tmp2 = d2 + d5; + float tmp5 = d2 - d5; + float tmp3 = d3 + d4; + float tmp4 = d3 - d4; + + // Even part + float tmp10 = tmp0 + tmp3; // phase 2 + float tmp13 = tmp0 - tmp3; + float tmp11 = tmp1 + tmp2; + float tmp12 = tmp1 - tmp2; + + d0 = tmp10 + tmp11; // phase 3 + d4 = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * 0.707106781f; // c4 + d2 = tmp13 + z1; // phase 5 + d6 = tmp13 - z1; + + // Odd part + tmp10 = tmp4 + tmp5; // phase 2 + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + // The rotator is modified from fig 4-8 to avoid extra negations. + z5 = (tmp10 - tmp12) * 0.382683433f; // c6 + z2 = tmp10 * 0.541196100f + z5; // c2-c6 + z4 = tmp12 * 1.306562965f + z5; // c2+c6 + z3 = tmp11 * 0.707106781f; // c4 + + z11 = tmp7 + z3; // phase 5 + z13 = tmp7 - z3; + + *d5p = z13 + z2; // phase 6 + *d3p = z13 - z2; + *d1p = z11 + z4; + *d7p = z11 - z4; + + *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; +} + +static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { + int tmp1 = val < 0 ? -val : val; + val = val < 0 ? val-1 : val; + bits[1] = 1; + while(tmp1 >>= 1) { + ++bits[1]; + } + bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { + } + // end0pos = first element in reverse order !=0 + if(end0pos == 0) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + return DU[0]; + } + for(i = 1; i <= end0pos; ++i) { + int startpos = i; + int nrzeroes; + unsigned short bits[2]; + for (; DU[i]==0 && i<=end0pos; ++i) { + } + nrzeroes = i-startpos; + if ( nrzeroes >= 16 ) { + int lng = nrzeroes>>4; + int nrmarker; + for (nrmarker=1; nrmarker <= lng; ++nrmarker) + stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); + nrzeroes &= 15; + } + stbiw__jpg_calcBits(DU[i], bits); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); + } + if(end0pos != 63) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + } + return DU[0]; +} + +static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { + // Constants that don't pollute global namespace + static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; + static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; + static const unsigned char std_ac_luminance_values[] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; + static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; + static const unsigned char std_ac_chrominance_values[] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + // Huffman tables + static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; + static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; + static const unsigned short YAC_HT[256][2] = { + {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const unsigned short UVAC_HT[256][2] = { + {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, + 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; + static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; + static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, + 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; + + int row, col, i, k, subsample; + float fdtbl_Y[64], fdtbl_UV[64]; + unsigned char YTable[64], UVTable[64]; + + if(!data || !width || !height || comp > 4 || comp < 1) { + return 0; + } + + quality = quality ? quality : 90; + subsample = quality <= 90 ? 1 : 0; + quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; + quality = quality < 50 ? 5000 / quality : 200 - quality * 2; + + for(i = 0; i < 64; ++i) { + int uvti, yti = (YQT[i]*quality+50)/100; + YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); + uvti = (UVQT[i]*quality+50)/100; + UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); + } + + for(row = 0, k = 0; row < 8; ++row) { + for(col = 0; col < 8; ++col, ++k) { + fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + } + } + + // Write Headers + { + static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; + static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; + const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width), + 3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; + s->func(s->context, (void*)head0, sizeof(head0)); + s->func(s->context, (void*)YTable, sizeof(YTable)); + stbiw__putc(s, 1); + s->func(s->context, UVTable, sizeof(UVTable)); + s->func(s->context, (void*)head1, sizeof(head1)); + s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); + s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); + stbiw__putc(s, 0x10); // HTYACinfo + s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); + s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); + stbiw__putc(s, 1); // HTUDCinfo + s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); + stbiw__putc(s, 0x11); // HTUACinfo + s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); + s->func(s->context, (void*)head2, sizeof(head2)); + } + + // Encode 8x8 macroblocks + { + static const unsigned short fillBits[] = {0x7F, 7}; + int DCY=0, DCU=0, DCV=0; + int bitBuf=0, bitCnt=0; + // comp == 2 is grey+alpha (alpha is ignored) + int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; + const unsigned char *dataR = (const unsigned char *)data; + const unsigned char *dataG = dataR + ofsG; + const unsigned char *dataB = dataR + ofsB; + int x, y, pos; + if(subsample) { + for(y = 0; y < height; y += 16) { + for(x = 0; x < width; x += 16) { + float Y[256], U[256], V[256]; + for(row = y, pos = 0; row < y+16; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+16; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + + // subsample U,V + { + float subU[64], subV[64]; + int yy, xx; + for(yy = 0, pos = 0; yy < 8; ++yy) { + for(xx = 0; xx < 8; ++xx, ++pos) { + int j = yy*32+xx*2; + subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f; + subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f; + } + } + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + } else { + for(y = 0; y < height; y += 8) { + for(x = 0; x < width; x += 8) { + float Y[64], U[64], V[64]; + for(row = y, pos = 0; row < y+8; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+8; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + + // Do the bit alignment of the EOI marker + stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); + } + + // EOI + stbiw__putc(s, 0xFF); + stbiw__putc(s, 0xD9); + + return 1; +} + +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); +} + + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.16 (2021-07-11) + make Deflate code emit uncompressed blocks when it would otherwise expand + support writing BMPs with alpha channel + 1.15 (2020-07-13) unknown + 1.14 (2020-02-02) updated JPEG writer to downsample chroma channels + 1.13 + 1.12 + 1.11 (2019-08-11) + + 1.10 (2019-02-07) + support utf8 filenames in Windows; fix warnings and platform ifdefs + 1.09 (2018-02-11) + fix typo in zlib quality API, improve STB_I_W_STATIC in C++ + 1.08 (2018-01-29) + add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter + 1.07 (2017-07-24) + doc fix + 1.06 (2017-07-23) + writing JPEG (using Jon Olick's code) + 1.05 ??? + 1.04 (2017-03-03) + monochrome BMP expansion + 1.03 ??? + 1.02 (2016-04-02) + avoid allocating large structures on the stack + 1.01 (2016-01-16) + STBIW_REALLOC_SIZED: support allocators with no realloc support + avoid race-condition in crc initialization + minor compile issues + 1.00 (2015-09-14) + installable file IO function + 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 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 +*/ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +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. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +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 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/third_party/lunasvg/CMakeLists.txt b/third_party/lunasvg/CMakeLists.txt new file mode 100755 index 0000000000..5c8dba5064 --- /dev/null +++ b/third_party/lunasvg/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required(VERSION 3.5) + +project(lunasvg VERSION 2.4.1 LANGUAGES CXX C) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_C_STANDARD 11) + +option(BUILD_SHARED_LIBS "Build as a shared library" ON) +option(LUNASVG_BUILD_EXAMPLES "Build example(s)" OFF) + +add_library(lunasvg) + +add_subdirectory(include) +add_subdirectory(source) +add_subdirectory(3rdparty/plutovg) + +target_compile_definitions(lunasvg PRIVATE LUNASVG_BUILD) +if(NOT BUILD_SHARED_LIBS) +# Originally in lunasvg the scope is set to PRIVATE. But it seems to be a bug. + target_compile_definitions(lunasvg PUBLIC LUNASVG_BUILD_STATIC) +endif() + +if(LUNASVG_BUILD_EXAMPLES) + add_executable(svg2png svg2png.cpp) + target_link_libraries(svg2png PRIVATE lunasvg) + target_include_directories(svg2png PRIVATE 3rdparty/stb) +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/third_party/lunasvg/LICENSE b/third_party/lunasvg/LICENSE new file mode 100644 index 0000000000..ce476ed0dd --- /dev/null +++ b/third_party/lunasvg/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/third_party/lunasvg/README.md b/third_party/lunasvg/README.md new file mode 100644 index 0000000000..c757c361c7 --- /dev/null +++ b/third_party/lunasvg/README.md @@ -0,0 +1,86 @@ +[![Releases](https://img.shields.io/badge/Version-2.4.1-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 + +By enabling the `LUNASVG_BUILD_EXAMPLES` option during the CMake configuration, the lunasvg build includes a simple SVG to PNG converter for easy conversion of SVG files to PNG format. + +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) +- [ObEngine](https://github.com/ObEngine/ObEngine) +- [OTTO](https://github.com/bitfieldaudio/OTTO) +- [EmulationStation-DE](https://gitlab.com/es-de/emulationstation-de) +- [SvgBooga](https://github.com/etodanik/SvgBooga/tree/main) +- [Dear ImGui](https://github.com/ocornut/imgui) +- [Multi Theft Auto: San Andreas](https://github.com/multitheftauto/mtasa-blue) +- [eScada Solutions](https://www.escadasolutions.com) + diff --git a/third_party/lunasvg/include/CMakeLists.txt b/third_party/lunasvg/include/CMakeLists.txt new file mode 100755 index 0000000000..de589546c6 --- /dev/null +++ b/third_party/lunasvg/include/CMakeLists.txt @@ -0,0 +1,4 @@ +target_include_directories(lunasvg +PUBLIC + "${CMAKE_CURRENT_LIST_DIR}" +) diff --git a/third_party/lunasvg/include/lunasvg.h b/third_party/lunasvg/include/lunasvg.h new file mode 100644 index 0000000000..728030a7ba --- /dev/null +++ b/third_party/lunasvg/include/lunasvg.h @@ -0,0 +1,340 @@ +/* + * 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 +#include +#include + +#if !defined(LUNASVG_BUILD_STATIC) && (defined(_WIN32) || defined(__CYGWIN__)) +#define LUNASVG_EXPORT __declspec(dllexport) +#define LUNASVG_IMPORT __declspec(dllimport) +#elif defined(__GNUC__) && (__GNUC__ >= 4) +#define LUNASVG_EXPORT __attribute__((__visibility__("default"))) +#define LUNASVG_IMPORT +#else +#define LUNASVG_EXPORT +#define LUNASVG_IMPORT +#endif + +#ifdef LUNASVG_BUILD +#define LUNASVG_API LUNASVG_EXPORT +#else +#define LUNASVG_API LUNASVG_IMPORT +#endif + +#define LUNASVG_VERSION_MAJOR 2 +#define LUNASVG_VERSION_MINOR 4 +#define LUNASVG_VERSION_MICRO 1 + +#define LUNASVG_VERSION_ENCODE(major, minor, micro) (((major) * 10000) + ((minor) * 100) + ((micro) * 1)) +#define LUNASVG_VERSION LUNASVG_VERSION_ENCODE(LUNASVG_VERSION_MAJOR, LUNASVG_VERSION_MINOR, LUNASVG_VERSION_MICRO) + +#define LUNASVG_VERSION_XSTRINGIZE(major, minor, micro) #major"."#minor"."#micro +#define LUNASVG_VERSION_STRINGIZE(major, minor, micro) LUNASVG_VERSION_XSTRINGIZE(major, minor, micro) +#define LUNASVG_VERSION_STRING LUNASVG_VERSION_STRINGIZE(LUNASVG_VERSION_MAJOR, LUNASVG_VERSION_MINOR, LUNASVG_VERSION_MICRO) + +namespace lunasvg { + +class Rect; +class Matrix; + +class LUNASVG_API Box { +public: + Box() = default; + Box(double x, double y, double w, double h); + Box(const Rect& rect); + + Box& transform(const Matrix& matrix); + Box transformed(const Matrix& matrix) const; + +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; + + 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 ARGB32 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 Element; + +class LUNASVG_API DomElement { +public: + /** + * @brief DomElement + */ + DomElement() = default; + + /** + * @brief DomElement + * @param element + */ + DomElement(Element* element); + + /** + * @brief setAttribute + * @param name + * @param value + */ + void setAttribute(const std::string& name, const std::string& value); + + /** + * @brief getAttribute + * @param name + * @return + */ + std::string getAttribute(const std::string& name) const; + + /** + * @brief removeAttribute + * @param name + */ + void removeAttribute(const std::string& name); + + /** + * @brief hasAttribute + * @param name + * @return + */ + bool hasAttribute(const std::string& name) const; + + /** + * @brief getBBox + * @return + */ + Box getBBox() const; + + /** + * @brief getLocalTransform + * @return + */ + Matrix getLocalTransform() const; + + /** + * @brief getAbsoluteTransform + * @return + */ + Matrix getAbsoluteTransform() const; + + /** + * @brief isNull + * @return + */ + bool isNull() const { return m_element == nullptr; } + + /** + * @brief get + * @return + */ + Element* get() { return m_element; } + + /** + * @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 renderToBitmap + * @param width + * @param height + * @param backgroundColor + * @return + */ + Bitmap renderToBitmap(std::uint32_t width, std::uint32_t height, std::uint32_t backgroundColor = 0x00000000) const; + +private: + Element* m_element = nullptr; +}; + +class LayoutSymbol; +class SVGElement; + +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 Sets the current transformation matrix of the document + * @param matrix - current transformation matrix + */ + 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; + + /** + * @brief updateLayout + */ + void updateLayout(); + + Document(Document&&); + ~Document(); + + DomElement getElementById(const std::string& id) const; + DomElement rootElement() const; + +private: + Document(); + bool parse(const char* data, size_t size); + std::unique_ptr m_rootElement; + std::map m_idCache; + std::unique_ptr m_rootBox; +}; + +} //namespace lunasvg + +#endif // LUNASVG_H diff --git a/third_party/lunasvg/source/CMakeLists.txt b/third_party/lunasvg/source/CMakeLists.txt new file mode 100755 index 0000000000..1866748d2c --- /dev/null +++ b/third_party/lunasvg/source/CMakeLists.txt @@ -0,0 +1,28 @@ +target_sources(lunasvg +PRIVATE + "${CMAKE_CURRENT_LIST_DIR}/lunasvg.cpp" + "${CMAKE_CURRENT_LIST_DIR}/element.cpp" + "${CMAKE_CURRENT_LIST_DIR}/property.cpp" + "${CMAKE_CURRENT_LIST_DIR}/parser.cpp" + "${CMAKE_CURRENT_LIST_DIR}/layoutcontext.cpp" + "${CMAKE_CURRENT_LIST_DIR}/canvas.cpp" + "${CMAKE_CURRENT_LIST_DIR}/clippathelement.cpp" + "${CMAKE_CURRENT_LIST_DIR}/defselement.cpp" + "${CMAKE_CURRENT_LIST_DIR}/gelement.cpp" + "${CMAKE_CURRENT_LIST_DIR}/geometryelement.cpp" + "${CMAKE_CURRENT_LIST_DIR}/graphicselement.cpp" + "${CMAKE_CURRENT_LIST_DIR}/maskelement.cpp" + "${CMAKE_CURRENT_LIST_DIR}/markerelement.cpp" + "${CMAKE_CURRENT_LIST_DIR}/paintelement.cpp" + "${CMAKE_CURRENT_LIST_DIR}/stopelement.cpp" + "${CMAKE_CURRENT_LIST_DIR}/styledelement.cpp" + "${CMAKE_CURRENT_LIST_DIR}/styleelement.cpp" + "${CMAKE_CURRENT_LIST_DIR}/svgelement.cpp" + "${CMAKE_CURRENT_LIST_DIR}/symbolelement.cpp" + "${CMAKE_CURRENT_LIST_DIR}/useelement.cpp" +) + +target_include_directories(lunasvg +PRIVATE + "${CMAKE_CURRENT_LIST_DIR}" +) diff --git a/third_party/lunasvg/source/canvas.cpp b/third_party/lunasvg/source/canvas.cpp new file mode 100644 index 0000000000..8fb1cf7af0 --- /dev/null +++ b/third_party/lunasvg/source/canvas.cpp @@ -0,0 +1,256 @@ +#include "canvas.h" + +#include + +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_method(SpreadMethod spread); +static plutovg_texture_type_t to_plutovg_texture_type(TextureType type); +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) +{ + m_surface = plutovg_surface_create_for_data(data, width, height, stride); + m_pluto = plutovg_create(m_surface); + plutovg_matrix_init_identity(&m_translation); + plutovg_rect_init(&m_rect, 0, 0, width, height); +} + +Canvas::Canvas(int x, int y, int width, int height) +{ + m_surface = plutovg_surface_create(width, height); + m_pluto = plutovg_create(m_surface); + plutovg_matrix_init_translate(&m_translation, -x, -y); + plutovg_rect_init(&m_rect, x, y, width, height); +} + +Canvas::~Canvas() +{ + plutovg_surface_destroy(m_surface); + plutovg_destroy(m_pluto); +} + +void Canvas::setColor(const Color& color) +{ + plutovg_set_rgba(m_pluto, color.red() / 255.0, color.green() / 255.0, color.blue() / 255.0, color.alpha() / 255.0); +} + +void Canvas::setLinearGradient(double x1, double y1, double x2, double y2, const GradientStops& stops, SpreadMethod spread, const Transform& transform) +{ + auto gradient = plutovg_set_linear_gradient(m_pluto, x1, y1, x2, y2); + auto matrix = to_plutovg_matrix(transform); + to_plutovg_stops(gradient, stops); + plutovg_gradient_set_spread(gradient, to_plutovg_spread_method(spread)); + plutovg_gradient_set_matrix(gradient, &matrix); +} + +void Canvas::setRadialGradient(double cx, double cy, double r, double fx, double fy, const GradientStops& stops, SpreadMethod spread, const Transform& transform) +{ + auto gradient = plutovg_set_radial_gradient(m_pluto, 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_method(spread)); + plutovg_gradient_set_matrix(gradient, &matrix); +} + +void Canvas::setTexture(const Canvas* source, TextureType type, const Transform& transform) +{ + auto texture = plutovg_set_texture(m_pluto, source->surface(), to_plutovg_texture_type(type)); + auto matrix = to_plutovg_matrix(transform); + plutovg_texture_set_matrix(texture, &matrix); +} + +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, &m_translation); + to_plutovg_path(m_pluto, path); + plutovg_set_matrix(m_pluto, &matrix); + plutovg_set_fill_rule(m_pluto, to_plutovg_fill_rule(winding)); + plutovg_set_opacity(m_pluto, opacity); + plutovg_set_operator(m_pluto, to_plutovg_operator(mode)); + plutovg_fill(m_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, &m_translation); + to_plutovg_path(m_pluto, path); + plutovg_set_matrix(m_pluto, &matrix); + plutovg_set_line_width(m_pluto, width); + plutovg_set_line_cap(m_pluto, to_plutovg_line_cap(cap)); + plutovg_set_line_join(m_pluto, to_plutovg_line_join(join)); + plutovg_set_miter_limit(m_pluto, miterlimit); + plutovg_set_dash(m_pluto, dash.offset, dash.array.data(), static_cast(dash.array.size())); + plutovg_set_operator(m_pluto, to_plutovg_operator(mode)); + plutovg_set_opacity(m_pluto, opacity); + plutovg_stroke(m_pluto); +} + +void Canvas::blend(const Canvas* source, BlendMode mode, double opacity) +{ + plutovg_set_texture_surface(m_pluto, source->surface(), source->x(), source->y()); + plutovg_set_operator(m_pluto, to_plutovg_operator(mode)); + plutovg_set_opacity(m_pluto, opacity); + plutovg_set_matrix(m_pluto, &m_translation); + plutovg_paint(m_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(m_pluto, m_rect.x, m_rect.y, m_rect.w, m_rect.h); + plutovg_add_path(m_pluto, path); + plutovg_path_destroy(path); + + plutovg_set_rgba(m_pluto, 0, 0, 0, 0); + plutovg_set_fill_rule(m_pluto, plutovg_fill_rule_even_odd); + plutovg_set_operator(m_pluto, plutovg_operator_src); + plutovg_set_opacity(m_pluto, 0.0); + plutovg_set_matrix(m_pluto, &m_translation); + plutovg_fill(m_pluto); +} + +void Canvas::luminance() +{ + auto width = plutovg_surface_get_width(m_surface); + auto height = plutovg_surface_get_height(m_surface); + auto stride = plutovg_surface_get_stride(m_surface); + auto data = plutovg_surface_get_data(m_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(m_surface); +} + +unsigned int Canvas::height() const +{ + return plutovg_surface_get_height(m_surface); +} + +unsigned int Canvas::stride() const +{ + return plutovg_surface_get_stride(m_surface); +} + +unsigned char* Canvas::data() const +{ + return plutovg_surface_get_data(m_surface); +} + +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_method(SpreadMethod spread) +{ + return spread == SpreadMethod::Pad ? plutovg_spread_method_pad : spread == SpreadMethod::Reflect ? plutovg_spread_method_reflect : plutovg_spread_method_repeat; +} + +static plutovg_texture_type_t to_plutovg_texture_type(TextureType type) +{ + return type == TextureType::Plain ? plutovg_texture_type_plain : plutovg_texture_type_tiled; +} + +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.red() / 255.0, color.green() / 255.0, color.blue() / 255.0, color.alpha() / 255.0); + } +} + +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/third_party/lunasvg/source/canvas.h b/third_party/lunasvg/source/canvas.h new file mode 100644 index 0000000000..9e00fa25cc --- /dev/null +++ b/third_party/lunasvg/source/canvas.h @@ -0,0 +1,77 @@ +#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; + + double x() const { return m_rect.x; } + double y() const { return m_rect.y; } + Rect rect() const { return Rect(m_rect.x, m_rect.y, m_rect.w, m_rect.h); } + plutovg_surface_t* surface() const { return m_surface; } + + ~Canvas(); + +private: + Canvas(unsigned char* data, int width, int height, int stride); + Canvas(int x, int y, int width, int height); + + plutovg_surface_t* m_surface; + plutovg_t* m_pluto; + plutovg_matrix_t m_translation; + plutovg_rect_t m_rect; +}; + +} // namespace lunasvg + +#endif // CANVAS_H diff --git a/third_party/lunasvg/source/clippathelement.cpp b/third_party/lunasvg/source/clippathelement.cpp new file mode 100644 index 0000000000..08c9dbc4ff --- /dev/null +++ b/third_party/lunasvg/source/clippathelement.cpp @@ -0,0 +1,31 @@ +#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) +{ + if(context->hasReference(this)) + return nullptr; + LayoutBreaker layoutBreaker(context, this); + auto clipper = makeUnique(this); + clipper->units = clipPathUnits(); + clipper->transform = transform(); + clipper->clipper = context->getClipper(clip_path()); + layoutChildren(context, clipper.get()); + return clipper; +} + +} // namespace lunasvg diff --git a/third_party/lunasvg/source/clippathelement.h b/third_party/lunasvg/source/clippathelement.h new file mode 100644 index 0000000000..49a44223a8 --- /dev/null +++ b/third_party/lunasvg/source/clippathelement.h @@ -0,0 +1,20 @@ +#ifndef CLIPPATHELEMENT_H +#define CLIPPATHELEMENT_H + +#include "graphicselement.h" + +namespace lunasvg { + +class LayoutClipPath; + +class ClipPathElement final : public GraphicsElement { +public: + ClipPathElement(); + + Units clipPathUnits() const; + std::unique_ptr getClipper(LayoutContext* context); +}; + +} // namespace lunasvg + +#endif // CLIPPATHELEMENT_H diff --git a/third_party/lunasvg/source/defselement.cpp b/third_party/lunasvg/source/defselement.cpp new file mode 100644 index 0000000000..60ac760f30 --- /dev/null +++ b/third_party/lunasvg/source/defselement.cpp @@ -0,0 +1,10 @@ +#include "defselement.h" + +namespace lunasvg { + +DefsElement::DefsElement() + : GraphicsElement(ElementID::Defs) +{ +} + +} // namespace lunasvg diff --git a/third_party/lunasvg/source/defselement.h b/third_party/lunasvg/source/defselement.h new file mode 100644 index 0000000000..7322d80069 --- /dev/null +++ b/third_party/lunasvg/source/defselement.h @@ -0,0 +1,15 @@ +#ifndef DEFSELEMENT_H +#define DEFSELEMENT_H + +#include "graphicselement.h" + +namespace lunasvg { + +class DefsElement final : public GraphicsElement { +public: + DefsElement(); +}; + +} // namespace lunasvg + +#endif // DEFSELEMENT_H diff --git a/third_party/lunasvg/source/element.cpp b/third_party/lunasvg/source/element.cpp new file mode 100644 index 0000000000..ba905b5841 --- /dev/null +++ b/third_party/lunasvg/source/element.cpp @@ -0,0 +1,233 @@ +#include "element.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" +#include "parser.h" + +namespace lunasvg { + +std::unique_ptr TextNode::clone() const +{ + auto node = makeUnique(); + node->setText(m_text); + return std::move(node); +} + +Element::Element(ElementID id) + : m_id(id) +{ +} + +std::unique_ptr Element::create(ElementID id) +{ + switch(id) { + case ElementID::Svg: + return makeUnique(); + case ElementID::Path: + return makeUnique(); + case ElementID::G: + return makeUnique(); + case ElementID::Rect: + return makeUnique(); + case ElementID::Circle: + return makeUnique(); + case ElementID::Ellipse: + return makeUnique(); + case ElementID::Line: + return makeUnique(); + case ElementID::Defs: + return makeUnique(); + case ElementID::Polygon: + return makeUnique(); + case ElementID::Polyline: + return makeUnique(); + case ElementID::Stop: + return makeUnique(); + case ElementID::LinearGradient: + return makeUnique(); + case ElementID::RadialGradient: + return makeUnique(); + case ElementID::Symbol: + return makeUnique(); + case ElementID::Use: + return makeUnique(); + case ElementID::Pattern: + return makeUnique(); + case ElementID::Mask: + return makeUnique(); + case ElementID::ClipPath: + return makeUnique(); + case ElementID::SolidColor: + return makeUnique(); + case ElementID::Marker: + return makeUnique(); + case ElementID::Style: + return makeUnique(); + default: + break; + } + + return nullptr; +} + +void Element::set(PropertyID id, const std::string& value, int specificity) +{ + for(auto& property : m_properties) { + if(property.id == id) { + if(specificity >= property.specificity) { + property.specificity = specificity; + property.value = value; + } + + return; + } + } + + m_properties.push_back({specificity, id, value}); +} + +static const std::string EmptyString; + +const std::string& Element::get(PropertyID id) const +{ + for(auto& property : m_properties) { + if(property.id == id) { + return property.value; + } + } + + return EmptyString; +} + +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 +{ + for(auto& property : m_properties) { + if(property.id == id) { + return true; + } + } + + return false; +} + +Element* Element::previousElement() const +{ + if(parent() == nullptr) + return nullptr; + Element* element = nullptr; + const auto& children = parent()->children(); + auto it = children.begin(); + auto end = 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::nextElement() const +{ + if(parent() == nullptr) + return nullptr; + Element* element = nullptr; + const auto& children = parent()->children(); + auto it = children.begin(); + auto end = 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; +} + +Node* Element::addChild(std::unique_ptr child) +{ + child->setParent(this); + m_children.push_back(std::move(child)); + return &*m_children.back(); +} + +void Element::layoutChildren(LayoutContext* context, LayoutContainer* current) +{ + for(auto& child : m_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(); +} + +void Element::build(const Document* document) +{ + for(auto& child : m_children) { + if(child->isText()) + continue; + auto element = static_cast(child.get()); + element->build(document); + } +} + +std::unique_ptr Element::clone() const +{ + auto element = Element::create(m_id); + element->setPropertyList(m_properties); + for(auto& child : m_children) + element->addChild(child->clone()); + return element; +} + +} // namespace lunasvg diff --git a/third_party/lunasvg/source/element.h b/third_party/lunasvg/source/element.h new file mode 100644 index 0000000000..15a02d3760 --- /dev/null +++ b/third_party/lunasvg/source/element.h @@ -0,0 +1,222 @@ +#ifndef ELEMENT_H +#define ELEMENT_H + +#include +#include + +#include "property.h" +#include "lunasvg.h" + +namespace lunasvg { + +enum class ElementID { + Unknown = 0, + Star, + Circle, + ClipPath, + Defs, + Ellipse, + G, + Image, + Line, + LinearGradient, + Marker, + Mask, + Path, + Pattern, + Polygon, + Polyline, + RadialGradient, + Rect, + SolidColor, + Stop, + Style, + Svg, + Symbol, + Text, + TSpan, + 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 +}; + +ElementID elementid(const std::string& name); +PropertyID csspropertyid(const std::string& name); +PropertyID propertyid(const std::string& name); + +struct Property { + int specificity; + PropertyID id; + std::string value; +}; + +using PropertyList = std::vector; + +template +inline std::unique_ptr makeUnique(Args&&... args) +{ + return std::unique_ptr(new T(std::forward(args)...)); +} + +class LayoutObject; +class LayoutContainer; +class LayoutContext; +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*) {} + virtual std::unique_ptr clone() const = 0; + + void setParent(Element* parent) { m_parent = parent; } + Element* parent() const { return m_parent; } + + LayoutObject* box() const { return m_box; } + void setBox(LayoutObject* box) { m_box = box; } + +private: + Element* m_parent = nullptr; + LayoutObject* m_box = nullptr; +}; + +class TextNode final : public Node { +public: + TextNode() = default; + + bool isText() const final { return true; } + std::unique_ptr clone() const final; + + void setText(std::string text) { m_text = std::move(text); } + const std::string& text() const { return m_text; } + +private: + std::string m_text; +}; + +using NodeList = std::list>; + +class Element : public Node { +public: + static std::unique_ptr create(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; + + const PropertyList& properties() const { return m_properties; } + void setPropertyList(PropertyList properties) { m_properties = std::move(properties); } + + Element* previousElement() const; + Element* nextElement() const; + Node* addChild(std::unique_ptr child); + void layoutChildren(LayoutContext* context, LayoutContainer* current); + Rect currentViewport() const; + + ElementID id() const { return m_id; } + const NodeList& children() const { return m_children; } + + virtual void build(const Document* document); + + template + void transverse(T callback) { + if(!callback(this)) + return; + for(auto& child : m_children) { + if(child->isText()) { + if(!callback(child.get())) + return; + continue; + } + + auto element = static_cast(child.get()); + element->transverse(callback); + } + } + + std::unique_ptr clone() const final; + +protected: + Element(ElementID id); + ElementID m_id; + NodeList m_children; + PropertyList m_properties; +}; + +} // namespace lunasvg + +#endif // ELEMENT_H diff --git a/third_party/lunasvg/source/gelement.cpp b/third_party/lunasvg/source/gelement.cpp new file mode 100644 index 0000000000..c9a9bcac90 --- /dev/null +++ b/third_party/lunasvg/source/gelement.cpp @@ -0,0 +1,24 @@ +#include "gelement.h" +#include "layoutcontext.h" + +namespace lunasvg { + +GElement::GElement() + : GraphicsElement(ElementID::G) +{ +} + +void GElement::layout(LayoutContext* context, LayoutContainer* current) +{ + if(isDisplayNone()) + return; + auto group = makeUnique(this); + 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)); +} + +} // namespace lunasvg diff --git a/third_party/lunasvg/source/gelement.h b/third_party/lunasvg/source/gelement.h new file mode 100644 index 0000000000..ce2454c7ad --- /dev/null +++ b/third_party/lunasvg/source/gelement.h @@ -0,0 +1,17 @@ +#ifndef GELEMENT_H +#define GELEMENT_H + +#include "graphicselement.h" + +namespace lunasvg { + +class GElement final : public GraphicsElement { +public: + GElement(); + + void layout(LayoutContext* context, LayoutContainer* current) final; +}; + +} // namespace lunasvg + +#endif // GELEMENT_H diff --git a/third_party/lunasvg/source/geometryelement.cpp b/third_party/lunasvg/source/geometryelement.cpp new file mode 100644 index 0000000000..be87424c3e --- /dev/null +++ b/third_party/lunasvg/source/geometryelement.cpp @@ -0,0 +1,297 @@ +#include "geometryelement.h" +#include "parser.h" +#include "layoutcontext.h" + +namespace lunasvg { + +GeometryElement::GeometryElement(ElementID id) + : GraphicsElement(id) +{ +} + +void GeometryElement::layout(LayoutContext* context, LayoutContainer* current) +{ + if(isDisplayNone()) + return; + + auto path = this->path(); + if(path.empty()) + return; + auto shape = makeUnique(this); + 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(); +} + +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; +} + +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; +} + +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; +} + +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; +} + +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; +} + +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; +} + +} // namespace lunasvg diff --git a/third_party/lunasvg/source/geometryelement.h b/third_party/lunasvg/source/geometryelement.h new file mode 100644 index 0000000000..1b4c9f9795 --- /dev/null +++ b/third_party/lunasvg/source/geometryelement.h @@ -0,0 +1,99 @@ +#ifndef GEOMETRYELEMENT_H +#define GEOMETRYELEMENT_H + +#include "graphicselement.h" + +namespace lunasvg { + +class LayoutShape; + +class GeometryElement : public GraphicsElement { +public: + GeometryElement(ElementID id); + + bool isGeometry() const final { return true; } + void layout(LayoutContext* context, LayoutContainer* current) final; + virtual Path path() const = 0; +}; + +class PathElement final : public GeometryElement { +public: + PathElement(); + + Path d() const; + Path path() const final; +}; + +class PolyElement : public GeometryElement { +public: + PolyElement(ElementID id); + + PointList points() const; +}; + +class PolygonElement final : public PolyElement { +public: + PolygonElement(); + + Path path() const final; +}; + +class PolylineElement final : public PolyElement { +public: + PolylineElement(); + + Path path() const final; +}; + +class CircleElement final : public GeometryElement { +public: + CircleElement(); + + Length cx() const; + Length cy() const; + Length r() const; + + Path path() const final; +}; + +class EllipseElement final : public GeometryElement { +public: + EllipseElement(); + + Length cx() const; + Length cy() const; + Length rx() const; + Length ry() const; + + Path path() const final; +}; + +class LineElement final : public GeometryElement { +public: + LineElement(); + + Length x1() const; + Length y1() const; + Length x2() const; + Length y2() const; + + Path path() const final; +}; + +class RectElement final : 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 final; +}; + +} // namespace lunasvg + +#endif // GEOMETRYELEMENT_H diff --git a/third_party/lunasvg/source/graphicselement.cpp b/third_party/lunasvg/source/graphicselement.cpp new file mode 100644 index 0000000000..9df1a00c33 --- /dev/null +++ b/third_party/lunasvg/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/third_party/lunasvg/source/graphicselement.h b/third_party/lunasvg/source/graphicselement.h new file mode 100644 index 0000000000..29d1bdb996 --- /dev/null +++ b/third_party/lunasvg/source/graphicselement.h @@ -0,0 +1,17 @@ +#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/third_party/lunasvg/source/layoutcontext.cpp b/third_party/lunasvg/source/layoutcontext.cpp new file mode 100644 index 0000000000..4bca5f3bd1 --- /dev/null +++ b/third_party/lunasvg/source/layoutcontext.cpp @@ -0,0 +1,710 @@ +#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(Node* node, LayoutId id) + : m_node(node), m_id(id) +{ + node->setBox(this); +} + +LayoutContainer::LayoutContainer(Node* node, LayoutId id) + : LayoutObject(node, id) +{ +} + +const Rect& LayoutContainer::fillBoundingBox() const +{ + if(m_fillBoundingBox.valid()) + return m_fillBoundingBox; + for(const auto& child : m_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 : m_children) { + if(child->isHidden()) + continue; + m_strokeBoundingBox.unite(child->map(child->strokeBoundingBox())); + } + + return m_strokeBoundingBox; +} + +LayoutObject* LayoutContainer::addChild(std::unique_ptr child) +{ + m_children.push_back(std::move(child)); + return &*m_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 : m_children) { + child->render(state); + } +} + +LayoutClipPath::LayoutClipPath(Node* node) + : LayoutContainer(node, LayoutId::ClipPath) +{ +} + +void LayoutClipPath::apply(RenderState& state) const +{ + RenderState newState(this, RenderMode::Clipping); + newState.canvas = Canvas::create(state.canvas->rect()); + 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(Node* node) + : LayoutContainer(node, 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->rect()); + 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(Node* node) + : LayoutContainer(node, 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); +} + +LayoutGroup::LayoutGroup(Node* node) + : LayoutContainer(node, 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); +} + +LayoutMarker::LayoutMarker(Node* node) + : LayoutContainer(node, LayoutId::Marker) +{ +} + +Transform LayoutMarker::markerTransform(const Point& origin, double angle, double strokeWidth) const +{ + auto markerTransformation = Transform::translated(origin.x, origin.y); + if(orient.type() == MarkerOrient::Auto) + markerTransformation.rotate(angle); + else + markerTransformation.rotate(orient.value()); + + if(units == MarkerUnits::StrokeWidth) + markerTransformation.scale(strokeWidth, strokeWidth); + + markerTransformation.translate(-refX, -refY); + return markerTransformation; +} + +Rect LayoutMarker::markerBoundingBox(const Point& origin, double angle, double strokeWidth) const +{ + return markerTransform(origin, angle, strokeWidth).map(transform.map(strokeBoundingBox())); +} + +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(Node* node) + : LayoutContainer(node, 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); + + RenderState newState(this, RenderMode::Display); + newState.canvas = Canvas::create(0, 0, rect.w * scalex, rect.h * scaley); + 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 patternTransform = this->transform; + patternTransform.translate(rect.x, rect.y); + patternTransform.scale(1.0/scalex, 1.0/scaley); + + renderChildren(newState); + state.canvas->setTexture(newState.canvas.get(), TextureType::Tiled, patternTransform); +} + +LayoutGradient::LayoutGradient(Node* node, LayoutId id) + : LayoutObject(node, id) +{ +} + +LayoutLinearGradient::LayoutLinearGradient(Node* node) + : LayoutGradient(node, LayoutId::LinearGradient) +{ +} + +void LayoutLinearGradient::apply(RenderState& state) const +{ + auto gradientTransform = this->transform; + if(units == Units::ObjectBoundingBox) { + const auto& box = state.objectBoundingBox(); + gradientTransform *= Transform(box.w, 0, 0, box.h, box.x, box.y); + } + + state.canvas->setLinearGradient(x1, y1, x2, y2, stops, spreadMethod, gradientTransform); +} + +LayoutRadialGradient::LayoutRadialGradient(Node* node) + : LayoutGradient(node, LayoutId::RadialGradient) +{ +} + +void LayoutRadialGradient::apply(RenderState& state) const +{ + auto gradientTransform = this->transform; + if(units == Units::ObjectBoundingBox) { + const auto& box = state.objectBoundingBox(); + gradientTransform *= Transform(box.w, 0, 0, box.h, box.x, box.y); + } + + state.canvas->setRadialGradient(cx, cy, r, fx, fy, stops, spreadMethod, gradientTransform); +} + +LayoutSolidColor::LayoutSolidColor(Node* node) + : LayoutObject(node, 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(Node* node) + : LayoutObject(node, 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); +} + +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->rect()); + 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 Document* document, LayoutSymbol* root) + : m_document(document), m_root(root) +{ +} + +Element* LayoutContext::getElementById(const std::string& id) const +{ + auto element = m_document->getElementById(id); + if(element.isNull()) + return nullptr; + return element.get(); +} + +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/third_party/lunasvg/source/layoutcontext.h b/third_party/lunasvg/source/layoutcontext.h new file mode 100644 index 0000000000..4bc603dcfd --- /dev/null +++ b/third_party/lunasvg/source/layoutcontext.h @@ -0,0 +1,379 @@ +#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 Node; + +class LayoutObject { +public: + LayoutObject(Node* node, LayoutId id); + virtual ~LayoutObject() = default; + virtual void render(RenderState&) const {} + virtual void apply(RenderState&) const {} + + Rect map(const Rect& rect) const { return localTransform().map(rect); } + + virtual const Transform& localTransform() const { return Transform::Identity; } + virtual const Rect& fillBoundingBox() const { return Rect::Invalid; } + virtual const Rect& strokeBoundingBox() const { return Rect::Invalid; } + + bool isPaint() const { return m_id == LayoutId::LinearGradient || m_id == LayoutId::RadialGradient || m_id == LayoutId::Pattern || m_id == LayoutId::SolidColor; } + bool isHidden() const { return isPaint() || m_id == LayoutId::ClipPath || m_id == LayoutId::Mask || m_id == LayoutId::Marker; } + + Node* node() const { return m_node; } + LayoutId id() const { return m_id; } + +private: + Node* m_node; + LayoutId m_id; +}; + +using LayoutList = std::list>; + +class LayoutContainer : public LayoutObject { +public: + LayoutContainer(Node* node, LayoutId id); + + const Rect& fillBoundingBox() const; + const Rect& strokeBoundingBox() const; + const LayoutList& children() const { return m_children; } + + LayoutObject* addChild(std::unique_ptr child); + LayoutObject* addChildIfNotEmpty(std::unique_ptr child); + void renderChildren(RenderState& state) const; + +protected: + LayoutList m_children; + mutable Rect m_fillBoundingBox{Rect::Invalid}; + mutable Rect m_strokeBoundingBox{Rect::Invalid}; +}; + +class LayoutClipPath : public LayoutContainer { +public: + LayoutClipPath(Node* node); + + void apply(RenderState& state) const; + +public: + Units units; + Transform transform; + const LayoutClipPath* clipper; +}; + +class LayoutMask : public LayoutContainer { +public: + LayoutMask(Node* node); + + 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 final : public LayoutContainer { +public: + LayoutSymbol(Node* node); + + void render(RenderState& state) const final; + + const Transform& localTransform() const final { return transform; } + +public: + double width; + double height; + Transform transform; + Rect clip; + double opacity; + const LayoutMask* masker; + const LayoutClipPath* clipper; +}; + +class LayoutGroup final : public LayoutContainer { +public: + LayoutGroup(Node* node); + + void render(RenderState& state) const final; + const Transform& localTransform() const final { return transform; } + +public: + Transform transform; + double opacity; + const LayoutMask* masker; + const LayoutClipPath* clipper; +}; + +class LayoutMarker final : public LayoutContainer { +public: + LayoutMarker(Node* node); + + 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 final : public LayoutContainer { +public: + LayoutPattern(Node* node); + + void apply(RenderState& state) const final; + +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(Node* node, LayoutId id); + +public: + Transform transform; + SpreadMethod spreadMethod; + Units units; + GradientStops stops; +}; + +class LayoutLinearGradient final : public LayoutGradient { +public: + LayoutLinearGradient(Node* node); + + void apply(RenderState& state) const final; + +public: + double x1; + double y1; + double x2; + double y2; +}; + +class LayoutRadialGradient final : public LayoutGradient { +public: + LayoutRadialGradient(Node* node); + + void apply(RenderState& state) const final; + +public: + double cx; + double cy; + double r; + double fx; + double fy; +}; + +class LayoutSolidColor final : public LayoutObject { +public: + LayoutSolidColor(Node* node); + + void apply(RenderState& state) const final; + +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 final : public LayoutObject { +public: + LayoutShape(Node* node); + + void render(RenderState& state) const; + const Transform& localTransform() const final { return transform; } + 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 Document; +class StyledElement; +class GeometryElement; + +class LayoutContext { +public: + LayoutContext(const Document* 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 Document* 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/third_party/lunasvg/source/lunasvg.cpp b/third_party/lunasvg/source/lunasvg.cpp new file mode 100644 index 0000000000..3c91c3248f --- /dev/null +++ b/third_party/lunasvg/source/lunasvg.cpp @@ -0,0 +1,507 @@ +#include "lunasvg.h" +#include "layoutcontext.h" +#include "parser.h" +#include "svgelement.h" + +#include +#include +#include + +namespace lunasvg { + +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) +{ +} + +Box& Box::transform(const Matrix &matrix) +{ + *this = transformed(matrix); + return *this; +} + +Box Box::transformed(const Matrix& matrix) const +{ + return Transform(matrix).map(*this); +} + +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); +} + +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); +} + +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 +{ + if(m_impl == nullptr) + return nullptr; + if(m_impl->data == nullptr) + return m_impl->ownData.get(); + return m_impl->data; +} + +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] = static_cast(pb); + data[1] = static_cast(pg); + data[2] = static_cast(pr); + data[3] = static_cast(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; + } +} + +DomElement::DomElement(Element* element) + : m_element(element) +{ +} + +void DomElement::setAttribute(const std::string& name, const std::string& value) +{ + if(m_element) { + auto id = propertyid(name); + if(id != PropertyID::Unknown) { + m_element->set(id, value, 0x1000); + } + } +} + +std::string DomElement::getAttribute(const std::string& name) const +{ + if(m_element) { + auto id = propertyid(name); + if(id != PropertyID::Unknown) { + return m_element->get(id); + } + } + + return std::string(); +} + +void DomElement::removeAttribute(const std::string& name) +{ + setAttribute(name, std::string()); +} + +bool DomElement::hasAttribute(const std::string& name) const +{ + if(m_element) { + auto id = propertyid(name); + if(id != PropertyID::Unknown) { + return m_element->has(id); + } + } + + return false; +} + +Box DomElement::getBBox() const +{ + if(m_element && m_element->box()) + return m_element->box()->strokeBoundingBox(); + return Box(); +} + +Matrix DomElement::getLocalTransform() const +{ + if(m_element && m_element->box()) + return m_element->box()->localTransform(); + return Matrix(); +} + +Matrix DomElement::getAbsoluteTransform() const +{ + if(m_element == nullptr || !m_element->box()) + return Matrix(); + auto transform = m_element->box()->localTransform(); + for(auto currentElement = m_element->parent(); currentElement; currentElement = currentElement->parent()) { + if(auto box = currentElement->box()) { + transform.postmultiply(box->localTransform()); + } + } + + return transform; +} + +void DomElement::render(Bitmap bitmap, const Matrix& matrix) const +{ + if(m_element == nullptr || !m_element->box()) + return; + RenderState state(nullptr, RenderMode::Display); + state.canvas = Canvas::create(bitmap.data(), bitmap.width(), bitmap.height(), bitmap.stride()); + state.transform = Transform(matrix); + m_element->box()->render(state); +} + +Bitmap DomElement::renderToBitmap(std::uint32_t width, std::uint32_t height, std::uint32_t backgroundColor) const +{ + if(m_element == nullptr || !m_element->box()) + return Bitmap(); + auto elementBounds = m_element->box()->map(m_element->box()->strokeBoundingBox()); + if(elementBounds.empty()) + return Bitmap(); + if(width == 0 && height == 0) { + width = static_cast(std::ceil(elementBounds.w)); + height = static_cast(std::ceil(elementBounds.h)); + } else if(width != 0 && height == 0) { + height = static_cast(std::ceil(width * elementBounds.h / elementBounds.w)); + } else if(height != 0 && width == 0) { + width = static_cast(std::ceil(height * elementBounds.w / elementBounds.h)); + } + + const auto xScale = width / elementBounds.w; + const auto yScale = height / elementBounds.h; + + Matrix matrix(xScale, 0, 0, yScale, -elementBounds.x * xScale, -elementBounds.y * yScale); + Bitmap bitmap(width, height); + bitmap.clear(backgroundColor); + render(bitmap, matrix); + return bitmap; +} + +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) +{ + std::unique_ptr document(new Document); + if(!document->parse(data, size)) + return nullptr; + document->updateLayout(); + return document; +} + +std::unique_ptr Document::loadFromData(const char* data) +{ + return loadFromData(data, std::strlen(data)); +} + +void Document::setMatrix(const Matrix& matrix) +{ + if(m_rootBox) { + Box bbox(0, 0, m_rootBox->width, m_rootBox->height); + bbox.transform(matrix); + m_rootBox->width = bbox.w; + m_rootBox->height = bbox.h; + m_rootBox->transform = Transform(matrix); + } +} + +Matrix Document::matrix() const +{ + if(m_rootBox == nullptr) + return Matrix(); + return m_rootBox->transform; +} + +Box Document::box() const +{ + if(m_rootBox == nullptr) + return Box(); + return m_rootBox->map(m_rootBox->strokeBoundingBox()); +} + +double Document::width() const +{ + if(m_rootBox == nullptr) + return 0.0; + return m_rootBox->width; +} + +double Document::height() const +{ + if(m_rootBox == nullptr) + return 0.0; + return m_rootBox->height; +} + +void Document::render(Bitmap bitmap, const Matrix& matrix) const +{ + if(m_rootBox == nullptr) + return; + RenderState state(nullptr, RenderMode::Display); + state.canvas = Canvas::create(bitmap.data(), bitmap.width(), bitmap.height(), bitmap.stride()); + state.transform = Transform(matrix); + m_rootBox->render(state); +} + +Bitmap Document::renderToBitmap(std::uint32_t width, std::uint32_t height, std::uint32_t backgroundColor) const +{ + if(m_rootBox == nullptr || m_rootBox->width == 0.0 || m_rootBox->height == 0.0) + return Bitmap(); + if(width == 0 && height == 0) { + width = static_cast(std::ceil(m_rootBox->width)); + height = static_cast(std::ceil(m_rootBox->height)); + } else if(width != 0 && height == 0) { + height = static_cast(std::ceil(width * m_rootBox->height / m_rootBox->width)); + } else if(height != 0 && width == 0) { + width = static_cast(std::ceil(height * m_rootBox->width / m_rootBox->height)); + } + + Matrix matrix(width / m_rootBox->width, 0, 0, height / m_rootBox->height, 0, 0); + Bitmap bitmap(width, height); + bitmap.clear(backgroundColor); + render(bitmap, matrix); + return bitmap; +} + +void Document::updateLayout() +{ + m_rootBox = m_rootElement->layoutTree(this); +} + +DomElement Document::getElementById(const std::string& id) const +{ + auto it = m_idCache.find(id); + if(it == m_idCache.end()) + return nullptr; + return it->second; +} + +DomElement Document::rootElement() const +{ + return m_rootElement.get(); +} + +Document::Document(Document&&) = default; +Document::~Document() = default; +Document::Document() = default; + +} // namespace lunasvg diff --git a/third_party/lunasvg/source/markerelement.cpp b/third_party/lunasvg/source/markerelement.cpp new file mode 100644 index 0000000000..121ec18223 --- /dev/null +++ b/third_party/lunasvg/source/markerelement.cpp @@ -0,0 +1,93 @@ +#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::Three); +} + +Length MarkerElement::markerHeight() const +{ + auto& value = get(PropertyID::MarkerHeight); + return Parser::parseLength(value, ForbidNegativeLengths, Length::Three); +} + +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) +{ + 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 = makeUnique(this); + 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; +} + +} // namespace lunasvg diff --git a/third_party/lunasvg/source/markerelement.h b/third_party/lunasvg/source/markerelement.h new file mode 100644 index 0000000000..6fcea8c5b9 --- /dev/null +++ b/third_party/lunasvg/source/markerelement.h @@ -0,0 +1,28 @@ +#ifndef MARKERELEMENT_H +#define MARKERELEMENT_H + +#include "graphicselement.h" + +namespace lunasvg { + +class LayoutMarker; + +class MarkerElement final : 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); +}; + +} // namespace lunasvg + +#endif // MARKERELEMENT_H diff --git a/third_party/lunasvg/source/maskelement.cpp b/third_party/lunasvg/source/maskelement.cpp new file mode 100644 index 0000000000..53f24d93a2 --- /dev/null +++ b/third_party/lunasvg/source/maskelement.cpp @@ -0,0 +1,72 @@ +#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) +{ + auto w = this->width(); + auto h = this->height(); + if(w.isZero() || h.isZero() || context->hasReference(this)) + return nullptr; + + LayoutBreaker layoutBreaker(context, this); + auto masker = makeUnique(this); + 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; +} + +} // namespace lunasvg diff --git a/third_party/lunasvg/source/maskelement.h b/third_party/lunasvg/source/maskelement.h new file mode 100644 index 0000000000..5e65fe3ee2 --- /dev/null +++ b/third_party/lunasvg/source/maskelement.h @@ -0,0 +1,25 @@ +#ifndef MASKELEMENT_H +#define MASKELEMENT_H + +#include "styledelement.h" + +namespace lunasvg { + +class LayoutMask; + +class MaskElement final : 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); +}; + +} // namespace lunasvg + +#endif // MASKELEMENT_H diff --git a/third_party/lunasvg/source/paintelement.cpp b/third_party/lunasvg/source/paintelement.cpp new file mode 100644 index 0000000000..5d76c4580d --- /dev/null +++ b/third_party/lunasvg/source/paintelement.cpp @@ -0,0 +1,405 @@ +#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) +{ + 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 = makeUnique(this); + solid->color = std::get<1>(stops.back()); + return std::move(solid); + } + + auto gradient = makeUnique(this); + 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); +} + +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) +{ + 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 = makeUnique(this); + solid->color = std::get<1>(stops.back()); + return std::move(solid); + } + + auto gradient = makeUnique(this); + 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); +} + +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) +{ + if(context->hasReference(this)) + return nullptr; + + PatternAttributes attributes; + std::set processedPatterns; + 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().empty()) + 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 = makeUnique(this); + 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); +} + +SolidColorElement::SolidColorElement() + : PaintElement(ElementID::SolidColor) +{ +} + +std::unique_ptr SolidColorElement::getPainter(LayoutContext*) +{ + auto solid = makeUnique(this); + solid->color = solid_color(); + solid->color.combine(solid_opacity()); + return std::move(solid); +} + +} // namespace lunasvg diff --git a/third_party/lunasvg/source/paintelement.h b/third_party/lunasvg/source/paintelement.h new file mode 100644 index 0000000000..98b1452304 --- /dev/null +++ b/third_party/lunasvg/source/paintelement.h @@ -0,0 +1,331 @@ +#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 final { return true; } + virtual std::unique_ptr getPainter(LayoutContext* context) = 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 final : public GradientElement { +public: + LinearGradientElement(); + + Length x1() const; + Length y1() const; + Length x2() const; + Length y2() const; + + std::unique_ptr getPainter(LayoutContext* context) final; +}; + +class RadialGradientElement final : 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) final; +}; + +class PatternElement final : 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) final; +}; + +class SolidColorElement final : public PaintElement { +public: + SolidColorElement(); + + std::unique_ptr getPainter(LayoutContext*) final; +}; + +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; } + 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(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; + 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/third_party/lunasvg/source/parser.cpp b/third_party/lunasvg/source/parser.cpp new file mode 100644 index 0000000000..d5d1c2b5bd --- /dev/null +++ b/third_party/lunasvg/source/parser.cpp @@ -0,0 +1,1892 @@ +#include "parser.h" +#include "parserutils.h" +#include "layoutcontext.h" +#include "svgelement.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++; + auto lastCommand = command; + double c[6]; + bool f[2]; + + Point startPoint; + Point currentPoint; + Point controlPoint; + + Path path; + while(true) { + Utils::skipWs(ptr, end); + if(command == 'M' || command == '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 = c[0]; + startPoint.y = currentPoint.y = c[1]; + command = command == 'm' ? 'l' : 'L'; + } else if(command == 'L' || command == '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 = c[0]; + currentPoint.y = c[1]; + } else if(command == 'H' || command == 'h') { + if(!parseNumberList(ptr, end, c, 1)) + return path; + + if(command == 'h') + c[0] += currentPoint.x; + + path.lineTo(c[0], currentPoint.y); + currentPoint.x = c[0]; + } else if(command == 'V' || command == '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 = c[1]; + } else if(command == 'Q' || command == '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]; + } else if(command == 'C' || command == '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]; + } else if(command == 'T' || command == 't') { + if(lastCommand != 'Q' && lastCommand != 'q' && lastCommand != 'T' && lastCommand != 't') { + c[0] = currentPoint.x; + c[1] = currentPoint.y; + } else { + 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]; + } else if(command == 'S' || command == 's') { + if(lastCommand != 'C' && lastCommand != 'c' && lastCommand != 'S' && lastCommand != 's') { + c[0] = currentPoint.x; + c[1] = currentPoint.y; + } else { + 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]; + } else if(command == 'A' || command == '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 = c[3]; + currentPoint.y = c[4]; + } else if(command == 'Z' || command == 'z') { + if(lastCommand == 'Z' || lastCommand == 'z') + return path; + path.close(); + currentPoint.x = startPoint.x; + currentPoint.y = startPoint.y; + } else { + return path; + } + + Utils::skipWsComma(ptr, end); + if(ptr >= end) + break; + + lastCommand = command; + if(IS_ALPHA(*ptr)) { + 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; +} + +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; + } + + return Color(value | 0xFF000000); + } + + if(Utils::skipDesc(ptr, end, "rgb(")) { + uint8_t 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, g, b, 255); + } + + if(Utils::skipDesc(ptr, end, "none")) + return Color::Transparent; + + if(Utils::skipDesc(ptr, end, "currentColor")) + return element->color(); + + 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} + }; + + auto it = colormap.find(string); + if(it == colormap.end()) + return defaultValue; + return Color(it->second | 0xFF000000); +} + +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, uint8_t& component) +{ + double value = 0; + if(!Utils::parseNumber(ptr, end, value)) + return false; + + if(Utils::skipDesc(ptr, end, '%')) + value *= 2.55; + + value = clamp(value, 0.0, 255.0); + component = static_cast(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 + Utils::skipWs(ptr, end); + if(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; +} + +ElementID elementid(const std::string& name) +{ + static const std::map elementmap = { + {"a", ElementID::G}, + {"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} + }; + + auto it = elementmap.find(name); + if(it == elementmap.end()) + return ElementID::Unknown; + return it->second; +} + +PropertyID csspropertyid(const std::string& name) +{ + 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} + }; + + auto it = csspropertymap.find(name); + if(it == csspropertymap.end()) + return PropertyID::Unknown; + return it->second; +} + +PropertyID propertyid(const std::string& name) +{ + 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} + }; + + auto it = propertymap.find(name); + if(it == propertymap.end()) + return csspropertyid(name); + return it->second; +} + +bool RuleData::match(const Element* element) const +{ + if(m_selector.empty()) + return false; + + if(m_selector.size() == 1) + return matchSimpleSelector(m_selector.front(), element); + + auto it = m_selector.rbegin(); + auto end = m_selector.rend(); + if(!matchSimpleSelector(*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->previousElement(); + break; + } + + if(element == nullptr) + return false; + + if(matchSimpleSelector(*it, element)) { + ++it; + } else if(it->combinator != SimpleSelector::Combinator::Descendant + && it->combinator != SimpleSelector::Combinator::InDirectAdjacent) { + return false; + } + } + + return true; +} + +bool RuleData::matchSimpleSelector(const SimpleSelector& selector, const Element* element) +{ + if(selector.id != ElementID::Star && selector.id != element->id()) + return false; + + for(auto& sel : selector.attributeSelectors) { + if(!matchAttributeSelector(sel, element)) { + return false; + } + } + + for(auto& sel : selector.pseudoClassSelectors) { + if(!matchPseudoClassSelector(sel, element)) { + return false; + } + } + + return true; +} + +bool RuleData::matchAttributeSelector(const AttributeSelector& selector, const Element* element) +{ + 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 RuleData::matchPseudoClassSelector(const PseudoClassSelector& selector, const Element* element) +{ + if(selector.type == PseudoClassSelector::Type::Empty) + return element->children().empty(); + if(selector.type == PseudoClassSelector::Type::Root) + return element->parent() == nullptr; + if(selector.type == PseudoClassSelector::Type::Is) { + for(auto& subselector : selector.subSelectors) { + for(auto& sel : subselector) { + if(!matchSimpleSelector(sel, element)) { + return false; + } + } + } + + return true; + } + + if(selector.type == PseudoClassSelector::Type::Not) { + for(auto& subselector : selector.subSelectors) { + for(auto& sel : subselector) { + if(matchSimpleSelector(sel, element)) { + return false; + } + } + } + + return true; + } + + if(selector.type == PseudoClassSelector::Type::FirstChild) + return !element->previousElement(); + + if(selector.type == PseudoClassSelector::Type::LastChild) + return !element->nextElement(); + + if(selector.type == PseudoClassSelector::Type::OnlyChild) + return !(element->previousElement() || element->nextElement()); + + if(selector.type == PseudoClassSelector::Type::FirstOfType) { + auto sibling = element->previousElement(); + while(sibling) { + if(sibling->id() == element->id()) + return false; + sibling = element->previousElement(); + } + + return true; + } + + if(selector.type == PseudoClassSelector::Type::LastOfType) { + auto sibling = element->nextElement(); + while(sibling) { + if(sibling->id() == element->id()) + return false; + sibling = element->nextElement(); + } + + return true; + } + + return false; +} + +#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) == '_' || (c) == '-') +#define IS_CSS_NAMECHAR(c) (IS_CSS_STARTNAMECHAR(c) || IS_NUM(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 StyleSheet::parse(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; + add(rule); + } + + return true; +} + +void StyleSheet::add(const Rule& rule) +{ + for(auto& selector : rule.selectors) { + uint32_t specificity = 0; + for(auto& simpleSelector : selector) { + specificity += (simpleSelector.id == ElementID::Star) ? 0x0 : 0x1; + for(auto& attributeSelector : simpleSelector.attributeSelectors) { + specificity += (attributeSelector.id == PropertyID::Id) ? 0x10000 : 0x100; + } + } + + m_rules.emplace(selector, rule.declarations, specificity, m_position); + } + + m_position += 1; +} + +bool StyleSheet::parseAtRule(const char*& ptr, const char* end) +{ + 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 StyleSheet::parseRule(const char*& ptr, const char* end, Rule& rule) +{ + if(!parseSelectors(ptr, end, rule.selectors)) + return false; + + if(!parseDeclarations(ptr, end, rule.declarations)) + return false; + + return true; +} + +bool StyleSheet::parseSelectors(const char*& ptr, const char* end, SelectorList& selectors) +{ + Selector selector; + if(!parseSelector(ptr, end, selector)) + return false; + selectors.push_back(std::move(selector)); + + while(Utils::skipDesc(ptr, end, ',')) { + Utils::skipWs(ptr, end); + if(!parseSelector(ptr, end, selector)) + return false; + selectors.push_back(std::move(selector)); + } + + return true; +} + +bool StyleSheet::parseDeclarations(const char*& ptr, const char* end, DeclarationList& declarations) +{ + if(!Utils::skipDesc(ptr, end, '{')) + return false; + + Utils::skipWs(ptr, end); + do { + std::string name; + 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; + + Declaration declaration; + declaration.specificity = 0x10; + declaration.id = csspropertyid(name); + declaration.value.assign(start, Utils::rtrim(start, ptr)); + if(Utils::skipDesc(ptr, end, '!')) { + if(!Utils::skipDesc(ptr, end, "important")) + return false; + declaration.specificity = 0x1000; + } + + if(declaration.id != PropertyID::Unknown) + declarations.push_back(std::move(declaration)); + 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 StyleSheet::parseSelector(const char*& ptr, const char* end, Selector& selector) +{ + do { + SimpleSelector simpleSelector; + if(!parseSimpleSelector(ptr, end, simpleSelector)) + return false; + selector.push_back(std::move(simpleSelector)); + Utils::skipWs(ptr, end); + } while(ptr < end && IS_SELECTOR_STARTNAMECHAR(*ptr)); + + return true; +} + +bool StyleSheet::parseSimpleSelector(const char*& ptr, const char* end, SimpleSelector& simpleSelector) +{ + 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(std::move(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(std::move(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(std::move(a)); + continue; + } + + if(Utils::skipDesc(ptr, end, ':')) { + if(!readCSSIdentifier(ptr, end, name)) + return false; + PseudoClassSelector selector; + if(name.compare("empty") == 0) + selector.type = PseudoClassSelector::Type::Empty; + else if(name.compare("root") == 0) + selector.type = PseudoClassSelector::Type::Root; + else if(name.compare("not") == 0) + selector.type = PseudoClassSelector::Type::Not; + else if(name.compare("first-child") == 0) + selector.type = PseudoClassSelector::Type::FirstChild; + else if(name.compare("last-child") == 0) + selector.type = PseudoClassSelector::Type::LastChild; + else if(name.compare("only-child") == 0) + selector.type = PseudoClassSelector::Type::OnlyChild; + else if(name.compare("first-of-type") == 0) + selector.type = PseudoClassSelector::Type::FirstOfType; + else if(name.compare("last-of-type") == 0) + selector.type = PseudoClassSelector::Type::LastOfType; + else if(name.compare("only-of-type") == 0) + selector.type = PseudoClassSelector::Type::OnlyOfType; + if(selector.type == PseudoClassSelector::Type::Is || selector.type == PseudoClassSelector::Type::Not) { + if(!Utils::skipDesc(ptr, end, '(')) + return false; + + Utils::skipWs(ptr, end); + if(!parseSelectors(ptr, end, selector.subSelectors)) + return false; + + Utils::skipWs(ptr, end); + if(!Utils::skipDesc(ptr, end, ')')) { + return false; + } + } + + simpleSelector.pseudoClassSelectors.push_back(std::move(selector)); + 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; +} + +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] = static_cast(cp); + } else if(cp < 0x800) { + c[2] = 0; + c[1] = static_cast((cp & 0x3F) | 0x80); + cp >>= 6; + c[0] = static_cast(cp | 0xC0); + } else if(cp < 0x10000) { + c[3] = 0; + c[2] = static_cast((cp & 0x3F) | 0x80); + cp >>= 6; + c[1] = static_cast((cp & 0x3F) | 0x80); + cp >>= 6; + c[0] = static_cast(cp | 0xE0); + } else if(cp < 0x200000) { + c[4] = 0; + c[3] = static_cast((cp & 0x3F) | 0x80); + cp >>= 6; + c[2] = static_cast((cp & 0x3F) | 0x80); + cp >>= 6; + c[1] = static_cast((cp & 0x3F) | 0x80); + cp >>= 6; + c[0] = static_cast(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, ';'); + } +} + +static inline void removeComments(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("/*"); + } +} + +bool Document::parse(const char* data, std::size_t size) +{ + auto ptr = data; + auto end = ptr + size; + + StyleSheet styleSheet; + Element* current = nullptr; + std::string name; + std::string value; + int ignoring = 0; + auto handleText = [&](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); + + removeComments(value); + styleSheet.parse(value); + }; + + while(ptr < end) { + auto start = ptr; + if(!Utils::skipUntil(ptr, end, '<')) + break; + + handleText(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; + + handleText(start, ptr, false); + ptr += 3; + continue; + } + + if(Utils::skipDesc(ptr, end, "[CDATA[")) { + start = ptr; + if(!Utils::skipUntil(ptr, end, "]]>")) + return false; + + handleText(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 = ElementID::Unknown; + if(ignoring == 0) + id = elementid(name); + 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 = makeUnique(); + element = m_rootElement.get(); + } else { + auto child = Element::create(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 attrId = PropertyID::Unknown; + if(element != nullptr) + attrId = propertyid(name); + if(attrId != PropertyID::Unknown) { + decodeText(start, Utils::rtrim(start, ptr), value); + if(attrId == PropertyID::Style) { + removeComments(value); + parseStyle(value, element); + } else { + if(attrId == PropertyID::Id) + m_idCache.emplace(value, element); + element->set(attrId, 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; + if(!styleSheet.empty()) { + m_rootElement->transverse([&styleSheet](Node* node) { + if(node->isText()) + return true; + + auto element = static_cast(node); + for(auto& rule : styleSheet.rules()) { + if(rule.match(element)) { + for(auto& declaration : rule.declarations()) { + element->set(declaration.id, declaration.value, declaration.specificity); + } + } + } + + return true; + }); + } + + m_rootElement->build(this); + return true; +} + +} // namespace lunasvg diff --git a/third_party/lunasvg/source/parser.h b/third_party/lunasvg/source/parser.h new file mode 100644 index 0000000000..e20daadc6d --- /dev/null +++ b/third_party/lunasvg/source/parser.h @@ -0,0 +1,182 @@ +#ifndef PARSER_H +#define PARSER_H + +#include +#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, uint8_t& component); + 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 SimpleSelector; + +using Selector = std::vector; +using SelectorList = std::vector; + +struct AttributeSelector { + enum class MatchType { + None, + Equal, + Includes, + DashMatch, + StartsWith, + EndsWith, + Contains + }; + + MatchType matchType{MatchType::None}; + PropertyID id{PropertyID::Unknown}; + std::string value; +}; + +struct PseudoClassSelector { + enum class Type { + Unknown, + Empty, + Root, + Is, + Not, + FirstChild, + LastChild, + OnlyChild, + FirstOfType, + LastOfType, + OnlyOfType + }; + + Type type{Type::Unknown}; + SelectorList subSelectors; +}; + +struct SimpleSelector { + enum class Combinator { + Descendant, + Child, + DirectAdjacent, + InDirectAdjacent + }; + + Combinator combinator{Combinator::Descendant}; + ElementID id{ElementID::Star}; + std::vector attributeSelectors; + std::vector pseudoClassSelectors; +}; + +struct Declaration { + int specificity; + PropertyID id; + std::string value; +}; + +using DeclarationList = std::vector; + +struct Rule { + SelectorList selectors; + DeclarationList declarations; +}; + +class RuleData { +public: + RuleData(const Selector& selector, const DeclarationList& declarations, uint32_t specificity, uint32_t position) + : m_selector(selector), m_declarations(declarations), m_specificity(specificity), m_position(position) + {} + + const Selector& selector() const { return m_selector; } + const DeclarationList& declarations() const { return m_declarations; } + const uint32_t& specificity() const { return m_specificity; } + const uint32_t& position() const { return m_position; } + + bool match(const Element* element) const; + +private: + static bool matchSimpleSelector(const SimpleSelector& selector, const Element* element); + static bool matchAttributeSelector(const AttributeSelector& selector, const Element* element); + static bool matchPseudoClassSelector(const PseudoClassSelector& selector, const Element* element); + + Selector m_selector; + DeclarationList m_declarations; + uint32_t m_specificity; + uint32_t m_position; +}; + +inline bool operator<(const RuleData& a, const RuleData& b) { return std::tie(a.specificity(), a.position()) < std::tie(b.specificity(), b.position()); } +inline bool operator>(const RuleData& a, const RuleData& b) { return std::tie(a.specificity(), a.position()) > std::tie(b.specificity(), b.position()); } + +class StyleSheet { +public: + StyleSheet() = default; + + bool parse(const std::string& content); + void add(const Rule& rule); + bool empty() const { return m_rules.empty(); } + + const std::multiset& rules() const { return m_rules; } + +private: + static bool parseAtRule(const char*& ptr, const char* end); + static bool parseRule(const char*& ptr, const char* end, Rule& rule); + static bool parseSelectors(const char*& ptr, const char* end, SelectorList& selectors); + static bool parseDeclarations(const char*& ptr, const char* end, DeclarationList& declarations); + static bool parseSelector(const char*& ptr, const char* end, Selector& selector); + static bool parseSimpleSelector(const char*& ptr, const char* end, SimpleSelector& simpleSelector); + + std::multiset m_rules; + uint32_t m_position{0}; +}; + +} // namespace lunasvg + +#endif // PARSER_H diff --git a/third_party/lunasvg/source/parserutils.h b/third_party/lunasvg/source/parserutils.h new file mode 100644 index 0000000000..d7d00e59cf --- /dev/null +++ b/third_party/lunasvg/source/parserutils.h @@ -0,0 +1,257 @@ +#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/third_party/lunasvg/source/property.cpp b/third_party/lunasvg/source/property.cpp new file mode 100644 index 0000000000..2de3dee17a --- /dev/null +++ b/third_party/lunasvg/source/property.cpp @@ -0,0 +1,732 @@ +#include "property.h" +#include "element.h" +#include "lunasvg.h" + +#include +#include + +namespace lunasvg { + +const Color Color::Black(0xFF000000); +const Color Color::White(0xFFFFFFFF); +const Color Color::Transparent(0x00000000); + +Color& Color::combine(double opacity) +{ + *this = combined(opacity); + return *this; +} + +Color Color::combined(double opacity) const +{ + auto rgb = m_value & 0x00FFFFFF; + auto a = static_cast(clamp(opacity * alpha(), 0.0, 255.0)); + return Color(rgb | a << 24); +} + +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; +} + +const Transform Transform::Identity(1, 0, 0, 1, 0, 0); + +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 = (m00 * m11 - m10 * m01); + if(det == 0.0) + return Transform{}; + + double inv_det = 1.0 / det; + double _m00 = m00 * inv_det; + double _m10 = m10 * inv_det; + double _m01 = m01 * inv_det; + double _m11 = m11 * inv_det; + double _m02 = (m01 * m12 - m11 * m02) * inv_det; + double _m12 = (m10 * m02 - m00 * m12) * inv_det; + + return Transform{_m11, -_m10, -_m01, _m00, _m02, _m12}; +} + +Transform Transform::operator*(const Transform& transform) const +{ + double _m00 = m00 * transform.m00 + m10 * transform.m01; + double _m10 = m00 * transform.m10 + m10 * transform.m11; + double _m01 = m01 * transform.m00 + m11 * transform.m01; + double _m11 = m01 * transform.m10 + m11 * transform.m11; + double _m02 = m02 * transform.m00 + m12 * transform.m01 + transform.m02; + double _m12 = m02 * transform.m10 + 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); + + auto 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::Three{3, LengthUnits::Number}; +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}; + if(m_scale == MeetOrSlice::Meet) + return viewBox; + auto scale = std::max(width / viewBox.w, height / viewBox.h); + auto xOffset = -viewBox.x * scale; + auto yOffset = -viewBox.y * scale; + auto viewWidth = viewBox.w * scale; + auto viewHeight = viewBox.h * scale; + switch(m_align) { + case Align::xMidYMin: + case Align::xMidYMid: + case Align::xMidYMax: + xOffset += (width - viewWidth) * 0.5f; + 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.5f; + break; + case Align::xMinYMax: + case Align::xMidYMax: + case Align::xMaxYMax: + yOffset += (height - viewHeight); + break; + default: + break; + } + + return Rect(-xOffset / scale, -yOffset / scale, width / scale, height / scale); +} + +Angle::Angle(MarkerOrient type) + : m_type(type) +{ +} + +Angle::Angle(double value, MarkerOrient type) + : m_value(value), m_type(type) +{ +} + +} // namespace lunasvg diff --git a/third_party/lunasvg/source/property.h b/third_party/lunasvg/source/property.h new file mode 100644 index 0000000000..a6ffed9b04 --- /dev/null +++ b/third_party/lunasvg/source/property.h @@ -0,0 +1,357 @@ +#ifndef PROPERTY_H +#define PROPERTY_H + +#include +#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 +}; + +template +constexpr const T& clamp(const T& val, const T& lo, const T& hi) +{ + return (val < lo) ? lo : (hi < val) ? hi : val; +} + +class Color { +public: + Color() = default; + explicit Color(uint32_t value) : m_value(value) {} + Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) : m_value(a << 24 | r << 16 | g << 8 | b) {} + + uint8_t alpha() const { return (m_value >> 24) & 0xff; } + uint8_t red() const { return (m_value >> 16) & 0xff; } + uint8_t green() const { return (m_value >> 8) & 0xff; } + uint8_t blue() const { return (m_value >> 0) & 0xff; } + + uint32_t value() const { return m_value; } + + Color& combine(double opacity); + Color combined(double opacity) const; + + bool isNone() const { return m_value == 0; } + + static const Color Black; + static const Color White; + static const Color Transparent; + +private: + uint32_t m_value{0}; +}; + +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(); } + +private: + 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); + + static const Transform Identity; + +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 Three; + 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/third_party/lunasvg/source/stopelement.cpp b/third_party/lunasvg/source/stopelement.cpp new file mode 100644 index 0000000000..1ba5668f8c --- /dev/null +++ b/third_party/lunasvg/source/stopelement.cpp @@ -0,0 +1,24 @@ +#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.combine(stop_opacity()); + return color; +} + +} // namespace lunasvg diff --git a/third_party/lunasvg/source/stopelement.h b/third_party/lunasvg/source/stopelement.h new file mode 100644 index 0000000000..21273d4bb0 --- /dev/null +++ b/third_party/lunasvg/source/stopelement.h @@ -0,0 +1,18 @@ +#ifndef STOPELEMENT_H +#define STOPELEMENT_H + +#include "styledelement.h" + +namespace lunasvg { + +class StopElement final : public StyledElement { +public: + StopElement(); + + double offset() const; + Color stopColorWithOpacity() const; +}; + +} // namespace lunasvg + +#endif // STOPELEMENT_H diff --git a/third_party/lunasvg/source/styledelement.cpp b/third_party/lunasvg/source/styledelement.cpp new file mode 100644 index 0000000000..ccd4868072 --- /dev/null +++ b/third_party/lunasvg/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/third_party/lunasvg/source/styledelement.h b/third_party/lunasvg/source/styledelement.h new file mode 100644 index 0000000000..eb1888dc84 --- /dev/null +++ b/third_party/lunasvg/source/styledelement.h @@ -0,0 +1,52 @@ +#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/third_party/lunasvg/source/styleelement.cpp b/third_party/lunasvg/source/styleelement.cpp new file mode 100644 index 0000000000..a0281b9c31 --- /dev/null +++ b/third_party/lunasvg/source/styleelement.cpp @@ -0,0 +1,10 @@ +#include "styleelement.h" + +namespace lunasvg { + +StyleElement::StyleElement() + : Element(ElementID::Style) +{ +} + +} // namespace lunasvg diff --git a/third_party/lunasvg/source/styleelement.h b/third_party/lunasvg/source/styleelement.h new file mode 100644 index 0000000000..2ad60d809d --- /dev/null +++ b/third_party/lunasvg/source/styleelement.h @@ -0,0 +1,15 @@ +#ifndef STYLEELEMENT_H +#define STYLEELEMENT_H + +#include "element.h" + +namespace lunasvg { + +class StyleElement final : public Element { +public: + StyleElement(); +}; + +} // namespace lunasvg + +#endif // STYLEELEMENT_H diff --git a/third_party/lunasvg/source/svgelement.cpp b/third_party/lunasvg/source/svgelement.cpp new file mode 100644 index 0000000000..6b751cb826 --- /dev/null +++ b/third_party/lunasvg/source/svgelement.cpp @@ -0,0 +1,124 @@ +#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::layoutTree(const Document* document) +{ + 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); + + Point transformOrigin(_w * 0.5, _h * 0.5); // transform-origin: 50% 50% + auto transform = Transform::translated(transformOrigin.x, transformOrigin.y); + transform.premultiply(this->transform()); + transform.translate(-transformOrigin.x, -transformOrigin.y); + + auto viewBox = this->viewBox(); + auto preserveAspectRatio = this->preserveAspectRatio(); + auto viewTranslation = Transform::translated(_x, _y); + auto viewTransform = preserveAspectRatio.getMatrix(_w, _h, viewBox); + + auto root = makeUnique(this); + 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()); + if((w.isRelative() || h.isRelative()) && !has(PropertyID::ViewBox)) { + auto box = root->map(root->strokeBoundingBox()); + root->width = w.value(box.x + box.w); + root->height = h.value(box.y + box.h); + } + + return root; +} + +void SVGElement::layout(LayoutContext* context, LayoutContainer* current) +{ + 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 = makeUnique(this); + 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)); +} + +} // namespace lunasvg diff --git a/third_party/lunasvg/source/svgelement.h b/third_party/lunasvg/source/svgelement.h new file mode 100644 index 0000000000..2e09e9ac6c --- /dev/null +++ b/third_party/lunasvg/source/svgelement.h @@ -0,0 +1,28 @@ +#ifndef SVGELEMENT_H +#define SVGELEMENT_H + +#include "graphicselement.h" + +namespace lunasvg { + +class Document; +class LayoutSymbol; + +class SVGElement final : 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 layoutTree(const Document* document); + void layout(LayoutContext* context, LayoutContainer* current) final; +}; + +} // namespace lunasvg + +#endif // SVGELEMENT_H diff --git a/third_party/lunasvg/source/symbolelement.cpp b/third_party/lunasvg/source/symbolelement.cpp new file mode 100644 index 0000000000..85a9c20fce --- /dev/null +++ b/third_party/lunasvg/source/symbolelement.cpp @@ -0,0 +1,47 @@ +#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); +} + +} // namespace lunasvg diff --git a/third_party/lunasvg/source/symbolelement.h b/third_party/lunasvg/source/symbolelement.h new file mode 100644 index 0000000000..40618157ff --- /dev/null +++ b/third_party/lunasvg/source/symbolelement.h @@ -0,0 +1,22 @@ +#ifndef SYMBOLELEMENT_H +#define SYMBOLELEMENT_H + +#include "styledelement.h" + +namespace lunasvg { + +class SymbolElement final : public StyledElement { +public: + SymbolElement(); + + Length x() const; + Length y() const; + Length width() const; + Length height() const; + Rect viewBox() const; + PreserveAspectRatio preserveAspectRatio() const; +}; + +} // namespace lunasvg + +#endif // SYMBOLELEMENT_H diff --git a/third_party/lunasvg/source/useelement.cpp b/third_party/lunasvg/source/useelement.cpp new file mode 100644 index 0000000000..4e01cf4a79 --- /dev/null +++ b/third_party/lunasvg/source/useelement.cpp @@ -0,0 +1,127 @@ +#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::layout(LayoutContext* context, LayoutContainer* current) +{ + if(isDisplayNone()) + return; + LengthContext lengthContext(this); + auto _x = lengthContext.valueForLength(x(), LengthMode::Width); + auto _y = lengthContext.valueForLength(y(), LengthMode::Height); + + auto group = makeUnique(this); + group->transform = Transform::translated(_x, _y) * 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 UseElement::cloneTargetElement(const Element* targetElement) const +{ + if(targetElement == this) + return nullptr; + switch(targetElement->id()) { + case ElementID::Circle: + case ElementID::Ellipse: + case ElementID::G: + case ElementID::Image: + case ElementID::Line: + case ElementID::Path: + case ElementID::Polygon: + case ElementID::Polyline: + case ElementID::Rect: + case ElementID::Svg: + case ElementID::Symbol: + case ElementID::Text: + case ElementID::TSpan: + case ElementID::Use: + break; + default: + return nullptr; + } + + const auto& idAttr = targetElement->get(PropertyID::Id); + for(const auto* element = parent(); element; element = element->parent()) { + if(!idAttr.empty() && idAttr == element->get(PropertyID::Id)) { + return nullptr; + } + } + + auto tagId = targetElement->id(); + if(tagId == ElementID::Symbol) { + tagId = ElementID::Svg; + } + + auto newElement = Element::create(tagId); + newElement->setPropertyList(targetElement->properties()); + if(newElement->id() == ElementID::Svg) { + for(const auto& property : properties()) { + if(property.id == PropertyID::Width || property.id == PropertyID::Height) { + newElement->set(property.id, property.value, 0x0); + } + } + } + + if(newElement->id() == ElementID::Use) + return newElement; + for(auto& child : targetElement->children()) + newElement->addChild(child->clone()); + return newElement; +} + +void UseElement::build(const Document* document) +{ + auto targetElement = document->getElementById(href()); + if(!targetElement.isNull()) { + if(auto newElement = cloneTargetElement(targetElement.get())) { + addChild(std::move(newElement)); + } + } + + Element::build(document); +} + +} // namespace lunasvg diff --git a/third_party/lunasvg/source/useelement.h b/third_party/lunasvg/source/useelement.h new file mode 100644 index 0000000000..ebc18d0696 --- /dev/null +++ b/third_party/lunasvg/source/useelement.h @@ -0,0 +1,26 @@ +#ifndef USEELEMENT_H +#define USEELEMENT_H + +#include "graphicselement.h" + +namespace lunasvg { + +class UseElement final : 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) final; + std::unique_ptr cloneTargetElement(const Element* targetElement) const; + void build(const Document* document) final; +}; + +} // namespace lunasvg + +#endif // USEELEMENT_H