From 3c1bd90f8c2c63e9bd60af258127d5322c287783 Mon Sep 17 00:00:00 2001 From: Ilya Zaytsev Date: Mon, 2 Sep 2024 14:45:08 +0400 Subject: [PATCH 01/17] Link lunasvg. --- core/render2d/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/render2d/CMakeLists.txt b/core/render2d/CMakeLists.txt index 2df436856c..77ab3e5332 100644 --- a/core/render2d/CMakeLists.txt +++ b/core/render2d/CMakeLists.txt @@ -41,7 +41,8 @@ endif() if (BUILD_STANDALONE) target_link_libraries(${PROJECT_NAME} PUBLIC rapidjson - PUBLIC cairo) + PUBLIC cairo + PUBLIC lunasvg) else () find_package(Cairo REQUIRED) find_package(RapidJSON REQUIRED) From da67445a6a6693161ec7d936987e210afde46ce1 Mon Sep 17 00:00:00 2001 From: Ilya Zaytsev Date: Mon, 2 Sep 2024 14:46:16 +0400 Subject: [PATCH 02/17] Draft implementation of svg to png rendering. --- core/render2d/src/render_item_aux.cpp | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/core/render2d/src/render_item_aux.cpp b/core/render2d/src/render_item_aux.cpp index 0d026a3ee1..0c9b2d2f28 100644 --- a/core/render2d/src/render_item_aux.cpp +++ b/core/render2d/src/render_item_aux.cpp @@ -24,6 +24,8 @@ #include "render_context.h" #include "render_internal.h" #include +#include +#include #ifdef _MSC_VER #pragma warning(push) @@ -395,9 +397,26 @@ 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) + puts("document null"); + + auto bitmap = document->renderToBitmap(); + if (bitmap.isNull()) + puts("bitmap null"); + + bitmap.writeToPng("_drawImage.tmp"); + + std::ifstream pngFile("_drawImage.tmp", std::ios::binary | std::ios::in); + if (!pngFile) + puts("file error"); + std::string pngData((std::istreambuf_iterator(pngFile)), std::istreambuf_iterator()); + std::remove("_drawImage.tmp"); + if (pngData.empty()) + puts("data error"); + _rc.drawPng(pngData, Rect2f(v1, v2)); } } From 931eae9ecb3f9b9d495afc387fa2dac357f58e19 Mon Sep 17 00:00:00 2001 From: Ilya Zaytsev Date: Mon, 2 Sep 2024 14:47:55 +0400 Subject: [PATCH 03/17] Add lunasvg source as-is (unnecessary files removed). --- .../lunasvg/3rdparty/plutovg/CMakeLists.txt | 17 + third_party/lunasvg/3rdparty/plutovg/FTL.TXT | 166 ++ .../lunasvg/3rdparty/plutovg/plutovg-blend.c | 831 +++++++ .../lunasvg/3rdparty/plutovg/plutovg-dash.c | 114 + .../3rdparty/plutovg/plutovg-ft-math.c | 446 ++++ .../3rdparty/plutovg/plutovg-ft-math.h | 436 ++++ .../3rdparty/plutovg/plutovg-ft-raster.c | 1632 ++++++++++++++ .../3rdparty/plutovg/plutovg-ft-raster.h | 420 ++++ .../3rdparty/plutovg/plutovg-ft-stroker.c | 1947 +++++++++++++++++ .../3rdparty/plutovg/plutovg-ft-stroker.h | 320 +++ .../3rdparty/plutovg/plutovg-ft-types.h | 173 ++ .../3rdparty/plutovg/plutovg-geometry.c | 581 +++++ .../lunasvg/3rdparty/plutovg/plutovg-paint.c | 265 +++ .../3rdparty/plutovg/plutovg-private.h | 198 ++ .../lunasvg/3rdparty/plutovg/plutovg-rle.c | 424 ++++ .../lunasvg/3rdparty/plutovg/plutovg.c | 495 +++++ .../lunasvg/3rdparty/plutovg/plutovg.h | 260 +++ .../lunasvg/3rdparty/stb/CMakeLists.txt | 4 + .../lunasvg/3rdparty/stb/stb_image_write.h | 1724 +++++++++++++++ third_party/lunasvg/CMakeLists.txt | 40 + third_party/lunasvg/LICENSE | 21 + third_party/lunasvg/README.md | 86 + third_party/lunasvg/include/CMakeLists.txt | 4 + third_party/lunasvg/include/lunasvg.h | 340 +++ third_party/lunasvg/source/CMakeLists.txt | 28 + third_party/lunasvg/source/canvas.cpp | 256 +++ third_party/lunasvg/source/canvas.h | 77 + .../lunasvg/source/clippathelement.cpp | 31 + third_party/lunasvg/source/clippathelement.h | 20 + third_party/lunasvg/source/defselement.cpp | 10 + third_party/lunasvg/source/defselement.h | 15 + third_party/lunasvg/source/element.cpp | 233 ++ third_party/lunasvg/source/element.h | 222 ++ third_party/lunasvg/source/gelement.cpp | 24 + third_party/lunasvg/source/gelement.h | 17 + .../lunasvg/source/geometryelement.cpp | 297 +++ third_party/lunasvg/source/geometryelement.h | 99 + .../lunasvg/source/graphicselement.cpp | 17 + third_party/lunasvg/source/graphicselement.h | 17 + third_party/lunasvg/source/layoutcontext.cpp | 710 ++++++ third_party/lunasvg/source/layoutcontext.h | 379 ++++ third_party/lunasvg/source/lunasvg.cpp | 507 +++++ third_party/lunasvg/source/markerelement.cpp | 93 + third_party/lunasvg/source/markerelement.h | 28 + third_party/lunasvg/source/maskelement.cpp | 72 + third_party/lunasvg/source/maskelement.h | 25 + third_party/lunasvg/source/paintelement.cpp | 405 ++++ third_party/lunasvg/source/paintelement.h | 331 +++ third_party/lunasvg/source/parser.cpp | 1892 ++++++++++++++++ third_party/lunasvg/source/parser.h | 182 ++ third_party/lunasvg/source/parserutils.h | 257 +++ third_party/lunasvg/source/property.cpp | 732 +++++++ third_party/lunasvg/source/property.h | 357 +++ third_party/lunasvg/source/stopelement.cpp | 24 + third_party/lunasvg/source/stopelement.h | 18 + third_party/lunasvg/source/styledelement.cpp | 177 ++ third_party/lunasvg/source/styledelement.h | 52 + third_party/lunasvg/source/styleelement.cpp | 10 + third_party/lunasvg/source/styleelement.h | 15 + third_party/lunasvg/source/svgelement.cpp | 124 ++ third_party/lunasvg/source/svgelement.h | 28 + third_party/lunasvg/source/symbolelement.cpp | 47 + third_party/lunasvg/source/symbolelement.h | 22 + third_party/lunasvg/source/useelement.cpp | 127 ++ third_party/lunasvg/source/useelement.h | 26 + 65 files changed, 18947 insertions(+) create mode 100644 third_party/lunasvg/3rdparty/plutovg/CMakeLists.txt create mode 100644 third_party/lunasvg/3rdparty/plutovg/FTL.TXT create mode 100644 third_party/lunasvg/3rdparty/plutovg/plutovg-blend.c create mode 100644 third_party/lunasvg/3rdparty/plutovg/plutovg-dash.c create mode 100644 third_party/lunasvg/3rdparty/plutovg/plutovg-ft-math.c create mode 100644 third_party/lunasvg/3rdparty/plutovg/plutovg-ft-math.h create mode 100644 third_party/lunasvg/3rdparty/plutovg/plutovg-ft-raster.c create mode 100644 third_party/lunasvg/3rdparty/plutovg/plutovg-ft-raster.h create mode 100644 third_party/lunasvg/3rdparty/plutovg/plutovg-ft-stroker.c create mode 100644 third_party/lunasvg/3rdparty/plutovg/plutovg-ft-stroker.h create mode 100644 third_party/lunasvg/3rdparty/plutovg/plutovg-ft-types.h create mode 100644 third_party/lunasvg/3rdparty/plutovg/plutovg-geometry.c create mode 100644 third_party/lunasvg/3rdparty/plutovg/plutovg-paint.c create mode 100644 third_party/lunasvg/3rdparty/plutovg/plutovg-private.h create mode 100644 third_party/lunasvg/3rdparty/plutovg/plutovg-rle.c create mode 100644 third_party/lunasvg/3rdparty/plutovg/plutovg.c create mode 100644 third_party/lunasvg/3rdparty/plutovg/plutovg.h create mode 100644 third_party/lunasvg/3rdparty/stb/CMakeLists.txt create mode 100644 third_party/lunasvg/3rdparty/stb/stb_image_write.h create mode 100755 third_party/lunasvg/CMakeLists.txt create mode 100644 third_party/lunasvg/LICENSE create mode 100644 third_party/lunasvg/README.md create mode 100755 third_party/lunasvg/include/CMakeLists.txt create mode 100644 third_party/lunasvg/include/lunasvg.h create mode 100755 third_party/lunasvg/source/CMakeLists.txt create mode 100644 third_party/lunasvg/source/canvas.cpp create mode 100644 third_party/lunasvg/source/canvas.h create mode 100644 third_party/lunasvg/source/clippathelement.cpp create mode 100644 third_party/lunasvg/source/clippathelement.h create mode 100644 third_party/lunasvg/source/defselement.cpp create mode 100644 third_party/lunasvg/source/defselement.h create mode 100644 third_party/lunasvg/source/element.cpp create mode 100644 third_party/lunasvg/source/element.h create mode 100644 third_party/lunasvg/source/gelement.cpp create mode 100644 third_party/lunasvg/source/gelement.h create mode 100644 third_party/lunasvg/source/geometryelement.cpp create mode 100644 third_party/lunasvg/source/geometryelement.h create mode 100644 third_party/lunasvg/source/graphicselement.cpp create mode 100644 third_party/lunasvg/source/graphicselement.h create mode 100644 third_party/lunasvg/source/layoutcontext.cpp create mode 100644 third_party/lunasvg/source/layoutcontext.h create mode 100644 third_party/lunasvg/source/lunasvg.cpp create mode 100644 third_party/lunasvg/source/markerelement.cpp create mode 100644 third_party/lunasvg/source/markerelement.h create mode 100644 third_party/lunasvg/source/maskelement.cpp create mode 100644 third_party/lunasvg/source/maskelement.h create mode 100644 third_party/lunasvg/source/paintelement.cpp create mode 100644 third_party/lunasvg/source/paintelement.h create mode 100644 third_party/lunasvg/source/parser.cpp create mode 100644 third_party/lunasvg/source/parser.h create mode 100644 third_party/lunasvg/source/parserutils.h create mode 100644 third_party/lunasvg/source/property.cpp create mode 100644 third_party/lunasvg/source/property.h create mode 100644 third_party/lunasvg/source/stopelement.cpp create mode 100644 third_party/lunasvg/source/stopelement.h create mode 100644 third_party/lunasvg/source/styledelement.cpp create mode 100644 third_party/lunasvg/source/styledelement.h create mode 100644 third_party/lunasvg/source/styleelement.cpp create mode 100644 third_party/lunasvg/source/styleelement.h create mode 100644 third_party/lunasvg/source/svgelement.cpp create mode 100644 third_party/lunasvg/source/svgelement.h create mode 100644 third_party/lunasvg/source/symbolelement.cpp create mode 100644 third_party/lunasvg/source/symbolelement.h create mode 100644 third_party/lunasvg/source/useelement.cpp create mode 100644 third_party/lunasvg/source/useelement.h 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..3565911b08 --- /dev/null +++ b/third_party/lunasvg/CMakeLists.txt @@ -0,0 +1,40 @@ +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) + target_compile_definitions(lunasvg PRIVATE 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 From a4e0dbbff0b5f250ccf5ffafc0dd8d907a0a98d8 Mon Sep 17 00:00:00 2001 From: Ilya Zaytsev Date: Tue, 3 Sep 2024 15:32:00 +0400 Subject: [PATCH 04/17] Link lunasvg. --- core/render2d/CMakeLists.txt | 5 +++-- third_party/CMakeLists.txt | 3 ++- third_party/lunasvg/CMakeLists.txt | 27 +-------------------------- 3 files changed, 6 insertions(+), 29 deletions(-) diff --git a/core/render2d/CMakeLists.txt b/core/render2d/CMakeLists.txt index 77ab3e5332..9fb3f7554c 100644 --- a/core/render2d/CMakeLists.txt +++ b/core/render2d/CMakeLists.txt @@ -38,11 +38,12 @@ 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 - PUBLIC cairo - PUBLIC lunasvg) + PUBLIC cairo) else () find_package(Cairo REQUIRED) find_package(RapidJSON REQUIRED) diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index c7f0e4eba5..63d879b6b0 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(libpng) +add_subdirectory(lunasvg) if (BUILD_STANDALONE) # InChI don't have a Conan package yet add_subdirectory(cppcodec) @@ -20,4 +21,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/CMakeLists.txt b/third_party/lunasvg/CMakeLists.txt index 3565911b08..cd1c5b950c 100755 --- a/third_party/lunasvg/CMakeLists.txt +++ b/third_party/lunasvg/CMakeLists.txt @@ -5,9 +5,6 @@ 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) @@ -15,26 +12,4 @@ add_subdirectory(source) add_subdirectory(3rdparty/plutovg) target_compile_definitions(lunasvg PRIVATE LUNASVG_BUILD) -if(NOT BUILD_SHARED_LIBS) - target_compile_definitions(lunasvg PRIVATE 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} -) +target_compile_definitions(lunasvg PUBLIC LUNASVG_BUILD_STATIC) From 6cc459dd72ac87317b7dffe24764a3726c596529 Mon Sep 17 00:00:00 2001 From: Ilya Zaytsev Date: Tue, 3 Sep 2024 15:53:41 +0400 Subject: [PATCH 05/17] Set Debug config flags. --- cmake/setup.cmake | 3 +++ 1 file changed, 3 insertions(+) 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++") From 700c1b3d9cdbff59f85b69cea4af92792dff17c8 Mon Sep 17 00:00:00 2001 From: Ilya Zaytsev Date: Tue, 3 Sep 2024 22:07:21 +0400 Subject: [PATCH 06/17] Set stb include directory. --- third_party/lunasvg/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/third_party/lunasvg/CMakeLists.txt b/third_party/lunasvg/CMakeLists.txt index cd1c5b950c..8929201957 100755 --- a/third_party/lunasvg/CMakeLists.txt +++ b/third_party/lunasvg/CMakeLists.txt @@ -11,5 +11,9 @@ add_subdirectory(include) add_subdirectory(source) add_subdirectory(3rdparty/plutovg) +target_include_directories(lunasvg + INTERFACE 3rdparty/stb +) + target_compile_definitions(lunasvg PRIVATE LUNASVG_BUILD) target_compile_definitions(lunasvg PUBLIC LUNASVG_BUILD_STATIC) From 555fb7f3a9f5e05978e93dde8ac52b3ccd76716e Mon Sep 17 00:00:00 2001 From: Ilya Zaytsev Date: Tue, 3 Sep 2024 22:25:52 +0400 Subject: [PATCH 07/17] Use lunasvg cmake as-is. --- third_party/CMakeLists.txt | 5 +++++ third_party/lunasvg/CMakeLists.txt | 29 +++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index 63d879b6b0..9aa46bb936 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -1,5 +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) diff --git a/third_party/lunasvg/CMakeLists.txt b/third_party/lunasvg/CMakeLists.txt index 8929201957..3565911b08 100755 --- a/third_party/lunasvg/CMakeLists.txt +++ b/third_party/lunasvg/CMakeLists.txt @@ -5,15 +5,36 @@ 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_include_directories(lunasvg - INTERFACE 3rdparty/stb +target_compile_definitions(lunasvg PRIVATE LUNASVG_BUILD) +if(NOT BUILD_SHARED_LIBS) + target_compile_definitions(lunasvg PRIVATE 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} ) -target_compile_definitions(lunasvg PRIVATE LUNASVG_BUILD) -target_compile_definitions(lunasvg PUBLIC LUNASVG_BUILD_STATIC) +install(TARGETS lunasvg + LIBRARY DESTINATION ${LUNASVG_LIBDIR} + ARCHIVE DESTINATION ${LUNASVG_LIBDIR} + INCLUDES DESTINATION ${LUNASVG_INCDIR} +) From ff80c7eb638ecf32ddc5b88a28a5fd899952100f Mon Sep 17 00:00:00 2001 From: Ilya Zaytsev Date: Tue, 3 Sep 2024 22:26:38 +0400 Subject: [PATCH 08/17] Update rendering draft. --- core/render2d/src/render_item_aux.cpp | 29 ++++++++++++++++----------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/core/render2d/src/render_item_aux.cpp b/core/render2d/src/render_item_aux.cpp index 0c9b2d2f28..bf0815b6b9 100644 --- a/core/render2d/src/render_item_aux.cpp +++ b/core/render2d/src/render_item_aux.cpp @@ -25,7 +25,8 @@ #include "render_internal.h" #include #include -#include +#include +#include #ifdef _MSC_VER #pragma warning(push) @@ -388,6 +389,16 @@ void RenderItemAuxiliary::_drawMeta(bool idle) } } +struct stbi_context +{ + std::string data; +}; + +void my_stbi_write_func(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(); @@ -404,19 +415,13 @@ void RenderItemAuxiliary::_drawImage(const KETImage& img) puts("document null"); auto bitmap = document->renderToBitmap(); - if (bitmap.isNull()) + if (!bitmap.valid()) puts("bitmap null"); - bitmap.writeToPng("_drawImage.tmp"); - - std::ifstream pngFile("_drawImage.tmp", std::ios::binary | std::ios::in); - if (!pngFile) - puts("file error"); - std::string pngData((std::istreambuf_iterator(pngFile)), std::istreambuf_iterator()); - std::remove("_drawImage.tmp"); - if (pngData.empty()) - puts("data error"); - _rc.drawPng(pngData, Rect2f(v1, v2)); + stbi_context context; + if (!stbi_write_png_to_func(my_stbi_write_func, &context, bitmap.width(), bitmap.height(), 4, bitmap.data(), 0)) + puts("stbi error"); + _rc.drawPng(context.data, Rect2f(v1, v2)); } } From 4d67ee08b3ea4e496a4a016368e823c697720969 Mon Sep 17 00:00:00 2001 From: Ilya Zaytsev Date: Tue, 3 Sep 2024 22:27:32 +0400 Subject: [PATCH 09/17] Disable fsanitize. --- api/c/tests/unit/CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/api/c/tests/unit/CMakeLists.txt b/api/c/tests/unit/CMakeLists.txt index d08749ba76..76efd558a2 100644 --- a/api/c/tests/unit/CMakeLists.txt +++ b/api/c/tests/unit/CMakeLists.txt @@ -3,12 +3,6 @@ cmake_minimum_required(VERSION 3.6) project(indigo-api-unit-tests LANGUAGES CXX) if (ENABLE_TESTS) - if(UNIX AND NOT APPLE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fsanitize=leak -g") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fsanitize=leak -g") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address -fsanitize=leak") - set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -fsanitize=address -fsanitize=leak") - endif() set(DATA_PATH ${CMAKE_SOURCE_DIR}/data) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/common.h.in ${CMAKE_CURRENT_BINARY_DIR}/common.h) From eab8f67494f6a3481b81735e2e54301e6478dae0 Mon Sep 17 00:00:00 2001 From: Ilya Zaytsev Date: Tue, 3 Sep 2024 22:27:48 +0400 Subject: [PATCH 10/17] Revert "Disable fsanitize." This reverts commit 4d67ee08b3ea4e496a4a016368e823c697720969. --- api/c/tests/unit/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/api/c/tests/unit/CMakeLists.txt b/api/c/tests/unit/CMakeLists.txt index 76efd558a2..d08749ba76 100644 --- a/api/c/tests/unit/CMakeLists.txt +++ b/api/c/tests/unit/CMakeLists.txt @@ -3,6 +3,12 @@ cmake_minimum_required(VERSION 3.6) project(indigo-api-unit-tests LANGUAGES CXX) if (ENABLE_TESTS) + if(UNIX AND NOT APPLE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fsanitize=leak -g") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fsanitize=leak -g") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address -fsanitize=leak") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -fsanitize=address -fsanitize=leak") + endif() set(DATA_PATH ${CMAKE_SOURCE_DIR}/data) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/common.h.in ${CMAKE_CURRENT_BINARY_DIR}/common.h) From cf478448be15d219b564bc71ca6b0dd2f5f7e61d Mon Sep 17 00:00:00 2001 From: Ilya Zaytsev Date: Wed, 4 Sep 2024 16:27:31 +0400 Subject: [PATCH 11/17] Refactoring. --- core/render2d/src/render_item_aux.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/core/render2d/src/render_item_aux.cpp b/core/render2d/src/render_item_aux.cpp index bf0815b6b9..7bab149e02 100644 --- a/core/render2d/src/render_item_aux.cpp +++ b/core/render2d/src/render_item_aux.cpp @@ -389,14 +389,14 @@ void RenderItemAuxiliary::_drawMeta(bool idle) } } -struct stbi_context +struct StbiContext { std::string data; }; -void my_stbi_write_func(void *context, void *data, int size) +void ketImageStbiWriteFunc(void* context, void* data, int size) { - static_cast(context)->data.assign(static_cast(data), size); + static_cast(context)->data.assign(static_cast(data), size); } void RenderItemAuxiliary::_drawImage(const KETImage& img) @@ -412,16 +412,18 @@ void RenderItemAuxiliary::_drawImage(const KETImage& img) { auto document = lunasvg::Document::loadFromData(img.getData()); if (!document) - puts("document null"); + throw Error("RenderItemAuxiliary::_drawImage: loadFromData error"); auto bitmap = document->renderToBitmap(); if (!bitmap.valid()) - puts("bitmap null"); + throw Error("RenderItemAuxiliary::_drawImage: renderToBitmap error"); - stbi_context context; - if (!stbi_write_png_to_func(my_stbi_write_func, &context, bitmap.width(), bitmap.height(), 4, bitmap.data(), 0)) - puts("stbi error"); - _rc.drawPng(context.data, Rect2f(v1, v2)); + 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)); } } From f37fbc8cfd65dcec774f77c316d28a889600aa44 Mon Sep 17 00:00:00 2001 From: Ilya Zaytsev Date: Wed, 4 Sep 2024 19:18:05 +0400 Subject: [PATCH 12/17] Link indigo-ketcher with lunasvg. --- api/wasm/indigo-ketcher/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 () From 004b0ba7f6a3d50e71950439ead8245c81bd4cae Mon Sep 17 00:00:00 2001 From: Ilya Zaytsev Date: Wed, 4 Sep 2024 20:37:08 +0400 Subject: [PATCH 13/17] Disable MSVC warning 4251. --- core/render2d/src/render_item_aux.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/render2d/src/render_item_aux.cpp b/core/render2d/src/render_item_aux.cpp index 7bab149e02..4b784d3f49 100644 --- a/core/render2d/src/render_item_aux.cpp +++ b/core/render2d/src/render_item_aux.cpp @@ -25,14 +25,16 @@ #include "render_internal.h" #include #include -#include -#include #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4996) +#pragma warning(disable : 4251) #endif +#include +#include + using namespace indigo; IMPL_ERROR(RenderItemAuxiliary, "RenderItemAuxiliary"); From a8869a60e9c13695851cf92b1567707e9e8fe2a1 Mon Sep 17 00:00:00 2001 From: Ilya Zaytsev Date: Thu, 5 Sep 2024 14:52:28 +0400 Subject: [PATCH 14/17] Define LUNASVG_BUILD_STATIC for all builds. --- third_party/lunasvg/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/lunasvg/CMakeLists.txt b/third_party/lunasvg/CMakeLists.txt index 3565911b08..b13a606bb7 100755 --- a/third_party/lunasvg/CMakeLists.txt +++ b/third_party/lunasvg/CMakeLists.txt @@ -16,7 +16,7 @@ add_subdirectory(3rdparty/plutovg) target_compile_definitions(lunasvg PRIVATE LUNASVG_BUILD) if(NOT BUILD_SHARED_LIBS) - target_compile_definitions(lunasvg PRIVATE LUNASVG_BUILD_STATIC) + target_compile_definitions(lunasvg PUBLIC LUNASVG_BUILD_STATIC) endif() if(LUNASVG_BUILD_EXAMPLES) From de28b2aac1dc03c32049d922edd4d2f719021c23 Mon Sep 17 00:00:00 2001 From: Ilya Zaytsev Date: Fri, 6 Sep 2024 13:19:35 +0400 Subject: [PATCH 15/17] Add explanation of scope change. --- third_party/lunasvg/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/third_party/lunasvg/CMakeLists.txt b/third_party/lunasvg/CMakeLists.txt index b13a606bb7..5c8dba5064 100755 --- a/third_party/lunasvg/CMakeLists.txt +++ b/third_party/lunasvg/CMakeLists.txt @@ -16,6 +16,7 @@ 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() From d0fd1beae9ce5b75e84e8f170b532557ea0a2f38 Mon Sep 17 00:00:00 2001 From: Ilya Zaytsev Date: Mon, 9 Sep 2024 17:12:04 +0400 Subject: [PATCH 16/17] Update images.png ref. --- .../tests/rendering/ref/linux/images.png | Bin 63874 -> 88407 bytes .../tests/rendering/ref/mac/images.png | Bin 63874 -> 88407 bytes .../tests/rendering/ref/win/images.png | Bin 63874 -> 88407 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/api/tests/integration/tests/rendering/ref/linux/images.png b/api/tests/integration/tests/rendering/ref/linux/images.png index 199f0df18b18730deabc1e82e6e3a0bbc7d35514..fc83e7b0aba5c92b29124be7a19523af19d9fd82 100644 GIT binary patch delta 73809 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 delta 48364 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%0DWMiOHM{$mP+A*M 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 Z;yqqn!|Jp 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 delta 48364 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%0DWMiOHM{$mP+A*M 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 Z;yqqn!|Jp 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 delta 48364 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%0DWMiOHM{$mP+A*M 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 Z;yqq Date: Mon, 9 Sep 2024 17:40:00 +0400 Subject: [PATCH 17/17] Remove no longer necessary header. --- core/render2d/src/render_item_aux.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/core/render2d/src/render_item_aux.cpp b/core/render2d/src/render_item_aux.cpp index 4b784d3f49..41caafde31 100644 --- a/core/render2d/src/render_item_aux.cpp +++ b/core/render2d/src/render_item_aux.cpp @@ -24,7 +24,6 @@ #include "render_context.h" #include "render_internal.h" #include -#include #ifdef _MSC_VER #pragma warning(push)