소스 검색

Merge pull request #33 from bmx-ng/task/svg-lunasvg

Changed to use lunasvg instead of nanosvg
Brucey 1 년 전
부모
커밋
3a740b3bdf
88개의 변경된 파일19282개의 추가작업 그리고 5960개의 파일을 삭제
  1. 25 28
      svg.mod/common.bmx
  2. 0 28
      svg.mod/glue.c
  3. 86 0
      svg.mod/glue.cpp
  4. 2 0
      svg.mod/lunasvg/.gitignore
  5. 17 0
      svg.mod/lunasvg/3rdparty/plutovg/CMakeLists.txt
  6. 166 0
      svg.mod/lunasvg/3rdparty/plutovg/FTL.TXT
  7. 831 0
      svg.mod/lunasvg/3rdparty/plutovg/plutovg-blend.c
  8. 114 0
      svg.mod/lunasvg/3rdparty/plutovg/plutovg-dash.c
  9. 446 0
      svg.mod/lunasvg/3rdparty/plutovg/plutovg-ft-math.c
  10. 436 0
      svg.mod/lunasvg/3rdparty/plutovg/plutovg-ft-math.h
  11. 1632 0
      svg.mod/lunasvg/3rdparty/plutovg/plutovg-ft-raster.c
  12. 420 0
      svg.mod/lunasvg/3rdparty/plutovg/plutovg-ft-raster.h
  13. 1947 0
      svg.mod/lunasvg/3rdparty/plutovg/plutovg-ft-stroker.c
  14. 320 0
      svg.mod/lunasvg/3rdparty/plutovg/plutovg-ft-stroker.h
  15. 173 0
      svg.mod/lunasvg/3rdparty/plutovg/plutovg-ft-types.h
  16. 581 0
      svg.mod/lunasvg/3rdparty/plutovg/plutovg-geometry.c
  17. 265 0
      svg.mod/lunasvg/3rdparty/plutovg/plutovg-paint.c
  18. 198 0
      svg.mod/lunasvg/3rdparty/plutovg/plutovg-private.h
  19. 424 0
      svg.mod/lunasvg/3rdparty/plutovg/plutovg-rle.c
  20. 495 0
      svg.mod/lunasvg/3rdparty/plutovg/plutovg.c
  21. 260 0
      svg.mod/lunasvg/3rdparty/plutovg/plutovg.h
  22. 4 0
      svg.mod/lunasvg/3rdparty/stb/CMakeLists.txt
  23. 1724 0
      svg.mod/lunasvg/3rdparty/stb/stb_image_write.h
  24. 40 0
      svg.mod/lunasvg/CMakeLists.txt
  25. 21 0
      svg.mod/lunasvg/LICENSE
  26. 86 0
      svg.mod/lunasvg/README.md
  27. 4 0
      svg.mod/lunasvg/include/CMakeLists.txt
  28. 340 0
      svg.mod/lunasvg/include/lunasvg.h
  29. BIN
      svg.mod/lunasvg/luna.png
  30. 28 0
      svg.mod/lunasvg/source/CMakeLists.txt
  31. 256 0
      svg.mod/lunasvg/source/canvas.cpp
  32. 77 0
      svg.mod/lunasvg/source/canvas.h
  33. 31 0
      svg.mod/lunasvg/source/clippathelement.cpp
  34. 20 0
      svg.mod/lunasvg/source/clippathelement.h
  35. 10 0
      svg.mod/lunasvg/source/defselement.cpp
  36. 15 0
      svg.mod/lunasvg/source/defselement.h
  37. 233 0
      svg.mod/lunasvg/source/element.cpp
  38. 222 0
      svg.mod/lunasvg/source/element.h
  39. 24 0
      svg.mod/lunasvg/source/gelement.cpp
  40. 17 0
      svg.mod/lunasvg/source/gelement.h
  41. 297 0
      svg.mod/lunasvg/source/geometryelement.cpp
  42. 99 0
      svg.mod/lunasvg/source/geometryelement.h
  43. 17 0
      svg.mod/lunasvg/source/graphicselement.cpp
  44. 17 0
      svg.mod/lunasvg/source/graphicselement.h
  45. 710 0
      svg.mod/lunasvg/source/layoutcontext.cpp
  46. 379 0
      svg.mod/lunasvg/source/layoutcontext.h
  47. 507 0
      svg.mod/lunasvg/source/lunasvg.cpp
  48. 93 0
      svg.mod/lunasvg/source/markerelement.cpp
  49. 28 0
      svg.mod/lunasvg/source/markerelement.h
  50. 72 0
      svg.mod/lunasvg/source/maskelement.cpp
  51. 25 0
      svg.mod/lunasvg/source/maskelement.h
  52. 405 0
      svg.mod/lunasvg/source/paintelement.cpp
  53. 331 0
      svg.mod/lunasvg/source/paintelement.h
  54. 1892 0
      svg.mod/lunasvg/source/parser.cpp
  55. 182 0
      svg.mod/lunasvg/source/parser.h
  56. 257 0
      svg.mod/lunasvg/source/parserutils.h
  57. 732 0
      svg.mod/lunasvg/source/property.cpp
  58. 357 0
      svg.mod/lunasvg/source/property.h
  59. 24 0
      svg.mod/lunasvg/source/stopelement.cpp
  60. 18 0
      svg.mod/lunasvg/source/stopelement.h
  61. 177 0
      svg.mod/lunasvg/source/styledelement.cpp
  62. 52 0
      svg.mod/lunasvg/source/styledelement.h
  63. 10 0
      svg.mod/lunasvg/source/styleelement.cpp
  64. 15 0
      svg.mod/lunasvg/source/styleelement.h
  65. 124 0
      svg.mod/lunasvg/source/svgelement.cpp
  66. 28 0
      svg.mod/lunasvg/source/svgelement.h
  67. 47 0
      svg.mod/lunasvg/source/symbolelement.cpp
  68. 22 0
      svg.mod/lunasvg/source/symbolelement.h
  69. 127 0
      svg.mod/lunasvg/source/useelement.cpp
  70. 26 0
      svg.mod/lunasvg/source/useelement.h
  71. 69 0
      svg.mod/lunasvg/svg2png.cpp
  72. 0 75
      svg.mod/nanosvg/CMakeLists.txt
  73. 0 5
      svg.mod/nanosvg/Config.cmake.in
  74. 0 18
      svg.mod/nanosvg/LICENSE.txt
  75. 0 112
      svg.mod/nanosvg/README.md
  76. 0 44
      svg.mod/nanosvg/example/23.svg
  77. 0 97
      svg.mod/nanosvg/example/drawing.svg
  78. 0 258
      svg.mod/nanosvg/example/example1.c
  79. 0 69
      svg.mod/nanosvg/example/example2.c
  80. 0 27
      svg.mod/nanosvg/example/nano.svg
  81. BIN
      svg.mod/nanosvg/example/screenshot-1.png
  82. BIN
      svg.mod/nanosvg/example/screenshot-2.png
  83. 0 511
      svg.mod/nanosvg/example/stb_image_write.h
  84. 0 56
      svg.mod/nanosvg/premake4.lua
  85. 0 3098
      svg.mod/nanosvg/src/nanosvg.h
  86. 0 1459
      svg.mod/nanosvg/src/nanosvgrast.h
  87. 57 0
      svg.mod/source.bmx
  88. 96 75
      svg.mod/svg.bmx

+ 25 - 28
svg.mod/common.bmx

@@ -1,37 +1,34 @@
-' Copyright (c) 2022-2024 Bruce A Henderson
+' Copyright (c) 2021 Bruce A Henderson
 ' 
-' This software is provided 'as-is', without any express or implied
-' warranty. In no event will the authors be held liable for any damages
-' arising from the use of this software.
+' 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:
 ' 
-' Permission is granted to anyone to use this software for any purpose,
-' including commercial applications, and to alter it and redistribute it
-' freely, subject to the following restrictions:
-' 
-' 1. The origin of this software must not be misrepresented; you must not
-'    claim that you wrote the original software. If you use this software
-'    in a product, an acknowledgment in the product documentation would be
-'    appreciated but is not required.
-' 2. Altered source versions must be plainly marked as such, and must not be
-'    misrepresented as being the original software.
-' 3. This notice may not be removed or altered from any source distribution.
+' 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.
+'
 SuperStrict
 
-Import "nanosvg/src/*.h"
-Import "glue.c"
+Import "source.bmx"
 
 Extern
-	Function nsvgParse:SNSVGimage Ptr(input:Byte ptr, units:Byte Ptr, dpi:Float)
-	Function nsvgDelete(handle:SNSVGimage Ptr)
 
-	Function nsvgCreateRasterizer:Byte Ptr()
-	Function nsvgRasterize(handle:Byte Ptr, image:SNSVGimage Ptr, tx:Float, ty:Float, scale:Float, dst:Byte Ptr, w:Int, h:Int, stride:Int)
-	Function nsvgDeleteRasterizer(handle:Byte Ptr)
-End Extern
+	Function bmx_svg_loadFromData:Byte Ptr(data:Byte Ptr, length:Int)
+	Function bmx_svg_free(handle:Byte Ptr)
+	Function bmx_svg_width:Double(handle:Byte Ptr)
+	Function bmx_svg_height:Double(handle:Byte Ptr)
 
-Struct SNSVGimage
-	Field width:Float
-	Field height:Float
-	Field shapes:Byte Ptr
-End Struct
+	Function bmx_svg_renderToPixmap(handle:Byte Ptr, pixels:Byte Ptr, width:Int, height:Int, pitch:Int)
+	Function bmx_svg_setDimensions(handle:Byte Ptr, width:Double, height:Double)
+End Extern

+ 0 - 28
svg.mod/glue.c

@@ -1,28 +0,0 @@
-/*
-  Copyright (c) 2022-2024 Bruce A Henderson
-  
-  This software is provided 'as-is', without any express or implied
-  warranty. In no event will the authors be held liable for any damages
-  arising from the use of this software.
-  
-  Permission is granted to anyone to use this software for any purpose,
-  including commercial applications, and to alter it and redistribute it
-  freely, subject to the following restrictions:
-  
-  1. The origin of this software must not be misrepresented; you must not
-     claim that you wrote the original software. If you use this software
-     in a product, an acknowledgment in the product documentation would be
-     appreciated but is not required.
-  2. Altered source versions must be plainly marked as such, and must not be
-     misrepresented as being the original software.
-  3. This notice may not be removed or altered from any source distribution.
-*/
-#include <stdio.h>
-#include <string.h>
-#include <math.h>
-
-#define NANOSVG_ALL_COLOR_KEYWORDS
-#define NANOSVG_IMPLEMENTATION
-#include "nanosvg.h"
-#define NANOSVGRAST_IMPLEMENTATION
-#include "nanosvgrast.h"

+ 86 - 0
svg.mod/glue.cpp

@@ -0,0 +1,86 @@
+/*
+ Copyright (c) 2024 Bruce A Henderson
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+#include "lunasvg.h"
+
+class MaxDocument
+{
+    public:
+    MaxDocument(std::unique_ptr<lunasvg::Document> document ) : document(std::move(document)) {}
+
+    double width() const { return document->width(); }
+    double height() const { return document->height(); }
+    void render(lunasvg::Bitmap & bitmap) const { document->render(bitmap); }
+    void render(lunasvg::Bitmap & bitmap, lunasvg::Matrix & matrix) const { document->render(bitmap, matrix); }
+
+    lunasvg::DomElement rootElement() const { return document->rootElement(); }
+    void updateLayout() { document->updateLayout(); }
+
+    private:
+    std::unique_ptr<lunasvg::Document> document;
+
+};
+
+extern "C" {
+
+    MaxDocument * bmx_svg_loadFromData(void * data, int length);
+    void bmx_svg_free(MaxDocument * doc);
+
+    double bmx_svg_width(MaxDocument * doc);
+	double bmx_svg_height(MaxDocument * doc);
+    void bmx_svg_renderToPixmap(MaxDocument * doc, std::uint8_t * pixels, int width, int height, int pitch);
+    void bmx_svg_setDimensions(MaxDocument * doc, double width, double height);
+}
+
+MaxDocument * bmx_svg_loadFromData(void * data, int length) {
+    auto document = lunasvg::Document::loadFromData(static_cast<const char*>(data), length);
+    if (document) {
+        return new MaxDocument(std::move(document));
+    }
+    return nullptr;
+}
+
+double bmx_svg_width(MaxDocument * doc) {
+    return doc->width();
+}
+
+double bmx_svg_height(MaxDocument * doc) {
+    return doc->height();
+}
+
+void bmx_svg_renderToPixmap(MaxDocument * doc, std::uint8_t * pixels, int width, int height, int pitch) {
+    lunasvg::Bitmap bitmap(pixels, width, height, pitch);
+    lunasvg::Matrix matrix(width / doc->width(), 0, 0, height / doc->height(), 0, 0);
+    doc->render(bitmap, matrix);
+    bitmap.convertToRGBA();
+}
+
+void bmx_svg_free(MaxDocument * doc) {
+    delete doc;
+}
+
+void bmx_svg_setDimensions(MaxDocument * doc, double width, double height) {
+    auto root = doc->rootElement();
+    root.setAttribute("width", std::to_string(width));
+    root.setAttribute("height", std::to_string(height));
+
+    doc->updateLayout();
+}

+ 2 - 0
svg.mod/lunasvg/.gitignore

@@ -0,0 +1,2 @@
+/build
+*.user

+ 17 - 0
svg.mod/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}"
+)

+ 166 - 0
svg.mod/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 � <year> The FreeType
+    Project (www.freetype.org).  All rights reserved.
+   """
+
+  Please replace <year> 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 [email protected]
+
+      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 [email protected]
+
+      Discusses bugs,  as well  as engine internals,  design issues,
+      specific licenses, porting, etc.
+
+  Our home page can be found at
+
+    http://www.freetype.org

+ 831 - 0
svg.mod/lunasvg/3rdparty/plutovg/plutovg-blend.c

@@ -0,0 +1,831 @@
+#include "plutovg-private.h"
+
+#include <limits.h>
+#include <math.h>
+#include <float.h>
+#include <stdint.h>
+
+#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);
+    }
+}

+ 114 - 0
svg.mod/lunasvg/3rdparty/plutovg/plutovg-dash.c

@@ -0,0 +1,114 @@
+#include "plutovg-private.h"
+
+#include <math.h>
+
+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;
+}

+ 446 - 0
svg.mod/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 <intrin.h>
+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 */

+ 436 - 0
svg.mod/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 ) )
+
+/*************************************************************************/
+/*                                                                       */
+/* <Function>                                                            */
+/*    PVG_FT_MulFix                                                      */
+/*                                                                       */
+/* <Description>                                                         */
+/*    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.      */
+/*                                                                       */
+/* <Input>                                                               */
+/*    a :: The first multiplier.                                         */
+/*    b :: The second multiplier.  Use a 16.16 factor here whenever      */
+/*         possible (see note below).                                    */
+/*                                                                       */
+/* <Return>                                                              */
+/*    The result of `(a*b)/0x10000'.                                     */
+/*                                                                       */
+/* <Note>                                                                */
+/*    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 );
+
+/*************************************************************************/
+/*                                                                       */
+/* <Function>                                                            */
+/*    PVG_FT_MulDiv                                                      */
+/*                                                                       */
+/* <Description>                                                         */
+/*    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.                   */
+/*                                                                       */
+/* <Input>                                                               */
+/*    a :: The first multiplier.                                         */
+/*    b :: The second multiplier.                                        */
+/*    c :: The divisor.                                                  */
+/*                                                                       */
+/* <Return>                                                              */
+/*    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 );
+
+/*************************************************************************/
+/*                                                                       */
+/* <Function>                                                            */
+/*    PVG_FT_DivFix                                                      */
+/*                                                                       */
+/* <Description>                                                         */
+/*    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.        */
+/*                                                                       */
+/* <Input>                                                               */
+/*    a :: The numerator.                                                */
+/*    b :: The denominator.  Use a 16.16 factor here.                    */
+/*                                                                       */
+/* <Return>                                                              */
+/*    The result of `(a*0x10000)/b'.                                     */
+/*                                                                       */
+PVG_FT_Long
+PVG_FT_DivFix( PVG_FT_Long  a,
+    PVG_FT_Long  b );
+
+
+
+/*************************************************************************/
+/*                                                                       */
+/* <Section>                                                             */
+/*   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 */

+ 1632 - 0
svg.mod/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 <setjmp.h>
+
+#define pvg_ft_setjmp   setjmp
+#define pvg_ft_longjmp  longjmp
+#define pvg_ft_jmp_buf  jmp_buf
+
+#include <stddef.h>
+
+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 <stdlib.h>
+#include <limits.h>
+
+#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.     */
+  /*                                                                       */
+  /*************************************************************************/
+
+  /*************************************************************************/
+  /*                                                                       */
+  /* <Function>                                                            */
+  /*    PVG_FT_Outline_Decompose                                               */
+  /*                                                                       */
+  /* <Description>                                                         */
+  /*    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.                                    */
+  /*                                                                       */
+  /* <Input>                                                               */
+  /*    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.                                   */
+  /*                                                                       */
+  /* <Return>                                                              */
+  /*    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 */

+ 420 - 0
svg.mod/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"
+
+/*************************************************************************/
+/*                                                                       */
+/* <Struct>                                                              */
+/*    FT_BBox                                                            */
+/*                                                                       */
+/* <Description>                                                         */
+/*    A structure used to hold an outline's bounding box, i.e., the      */
+/*    coordinates of its extrema in the horizontal and vertical          */
+/*    directions.                                                        */
+/*                                                                       */
+/* <Fields>                                                              */
+/*    xMin :: The horizontal minimum (left-most).                        */
+/*                                                                       */
+/*    yMin :: The vertical minimum (bottom-most).                        */
+/*                                                                       */
+/*    xMax :: The horizontal maximum (right-most).                       */
+/*                                                                       */
+/*    yMax :: The vertical maximum (top-most).                           */
+/*                                                                       */
+/* <Note>                                                                */
+/*    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;
+
+/*************************************************************************/
+/*                                                                       */
+/* <Struct>                                                              */
+/*    PVG_FT_Outline                                                      */
+/*                                                                       */
+/* <Description>                                                         */
+/*    This structure is used to describe an outline to the scan-line     */
+/*    converter.                                                         */
+/*                                                                       */
+/* <Fields>                                                              */
+/*    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;
+
+
+/*************************************************************************/
+/*                                                                       */
+/* <Enum>                                                                */
+/*    PVG_FT_OUTLINE_FLAGS                                                   */
+/*                                                                       */
+/* <Description>                                                         */
+/*    A list of bit-field constants use for the flags in an outline's    */
+/*    `flags' field.                                                     */
+/*                                                                       */
+/* <Values>                                                              */
+/*    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
+
+/*************************************************************************/
+/*                                                                       */
+/* <Function>                                                            */
+/*    PVG_FT_Outline_Check                                                   */
+/*                                                                       */
+/* <Description>                                                         */
+/*    Check the contents of an outline descriptor.                       */
+/*                                                                       */
+/* <Input>                                                               */
+/*    outline :: A handle to a source outline.                           */
+/*                                                                       */
+/* <Return>                                                              */
+/*    FreeType error code.  0~means success.                             */
+/*                                                                       */
+PVG_FT_Error
+PVG_FT_Outline_Check( PVG_FT_Outline*  outline );
+
+
+/*************************************************************************/
+/*                                                                       */
+/* <Function>                                                            */
+/*    PVG_FT_Outline_Get_CBox                                                */
+/*                                                                       */
+/* <Description>                                                         */
+/*    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.        */
+/*                                                                       */
+/* <Input>                                                               */
+/*    outline :: A pointer to the source outline descriptor.             */
+/*                                                                       */
+/* <Output>                                                              */
+/*    acbox   :: The outline's control box.                              */
+/*                                                                       */
+/* <Note>                                                                */
+/*    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 );
+
+/*************************************************************************/
+/*                                                                       */
+/* <Struct>                                                              */
+/*    PVG_FT_Span                                                            */
+/*                                                                       */
+/* <Description>                                                         */
+/*    A structure used to model a single span of gray (or black) pixels  */
+/*    when rendering a monochrome or anti-aliased bitmap.                */
+/*                                                                       */
+/* <Fields>                                                              */
+/*    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.                                             */
+/*                                                                       */
+/* <Note>                                                                */
+/*    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;
+
+
+/*************************************************************************/
+/*                                                                       */
+/* <FuncType>                                                            */
+/*    PVG_FT_SpanFunc                                                        */
+/*                                                                       */
+/* <Description>                                                         */
+/*    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.                                           */
+/*                                                                       */
+/* <Input>                                                               */
+/*    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.        */
+/*                                                                       */
+/* <Note>                                                                */
+/*    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
+
+
+
+/*************************************************************************/
+/*                                                                       */
+/* <Enum>                                                                */
+/*    PVG_FT_RASTER_FLAG_XXX                                                 */
+/*                                                                       */
+/* <Description>                                                         */
+/*    A list of bit flag constants as used in the `flags' field of a     */
+/*    @PVG_FT_Raster_Params structure.                                       */
+/*                                                                       */
+/* <Values>                                                              */
+/*    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
+
+
+/*************************************************************************/
+/*                                                                       */
+/* <Struct>                                                              */
+/*    PVG_FT_Raster_Params                                                   */
+/*                                                                       */
+/* <Description>                                                         */
+/*    A structure to hold the arguments used by a raster's render        */
+/*    function.                                                          */
+/*                                                                       */
+/* <Fields>                                                              */
+/*    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).                            */
+/*                                                                       */
+/* <Note>                                                                */
+/*    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

+ 1947 - 0
svg.mod/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 <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*************************************************************************/
+/*************************************************************************/
+/*****                                                               *****/
+/*****                      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 */

+ 320 - 0
svg.mod/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

+ 173 - 0
svg.mod/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
+
+/*************************************************************************/
+/*                                                                       */
+/* <Type>                                                                */
+/*    PVG_FT_Fixed                                                           */
+/*                                                                       */
+/* <Description>                                                         */
+/*    This type is used to store 16.16 fixed-point values, like scaling  */
+/*    values or matrix coefficients.                                     */
+/*                                                                       */
+typedef signed long  PVG_FT_Fixed;
+
+
+/*************************************************************************/
+/*                                                                       */
+/* <Type>                                                                */
+/*    PVG_FT_Int                                                             */
+/*                                                                       */
+/* <Description>                                                         */
+/*    A typedef for the int type.                                        */
+/*                                                                       */
+typedef signed int  PVG_FT_Int;
+
+
+/*************************************************************************/
+/*                                                                       */
+/* <Type>                                                                */
+/*    PVG_FT_UInt                                                            */
+/*                                                                       */
+/* <Description>                                                         */
+/*    A typedef for the unsigned int type.                               */
+/*                                                                       */
+typedef unsigned int  PVG_FT_UInt;
+
+
+/*************************************************************************/
+/*                                                                       */
+/* <Type>                                                                */
+/*    PVG_FT_Long                                                            */
+/*                                                                       */
+/* <Description>                                                         */
+/*    A typedef for signed long.                                         */
+/*                                                                       */
+typedef signed long  PVG_FT_Long;
+
+
+/*************************************************************************/
+/*                                                                       */
+/* <Type>                                                                */
+/*    PVG_FT_ULong                                                           */
+/*                                                                       */
+/* <Description>                                                         */
+/*    A typedef for unsigned long.                                       */
+/*                                                                       */
+typedef unsigned long PVG_FT_ULong;
+
+/*************************************************************************/
+/*                                                                       */
+/* <Type>                                                                */
+/*    PVG_FT_Short                                                           */
+/*                                                                       */
+/* <Description>                                                         */
+/*    A typedef for signed short.                                        */
+/*                                                                       */
+typedef signed short  PVG_FT_Short;
+
+
+/*************************************************************************/
+/*                                                                       */
+/* <Type>                                                                */
+/*    PVG_FT_Byte                                                            */
+/*                                                                       */
+/* <Description>                                                         */
+/*    A simple typedef for the _unsigned_ char type.                     */
+/*                                                                       */
+typedef unsigned char  PVG_FT_Byte;
+
+
+/*************************************************************************/
+/*                                                                       */
+/* <Type>                                                                */
+/*    PVG_FT_Bool                                                            */
+/*                                                                       */
+/* <Description>                                                         */
+/*    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;
+
+
+
+/*************************************************************************/
+/*                                                                       */
+/* <Type>                                                                */
+/*    PVG_FT_Error                                                           */
+/*                                                                       */
+/* <Description>                                                         */
+/*    The FreeType error code type.  A value of~0 is always interpreted  */
+/*    as a successful operation.                                         */
+/*                                                                       */
+typedef int  PVG_FT_Error;
+
+
+/*************************************************************************/
+/*                                                                       */
+/* <Type>                                                                */
+/*    PVG_FT_Pos                                                             */
+/*                                                                       */
+/* <Description>                                                         */
+/*    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;
+
+
+/*************************************************************************/
+/*                                                                       */
+/* <Struct>                                                              */
+/*    PVG_FT_Vector                                                          */
+/*                                                                       */
+/* <Description>                                                         */
+/*    A simple structure used to store a 2D vector; coordinates are of   */
+/*    the PVG_FT_Pos type.                                                   */
+/*                                                                       */
+/* <Fields>                                                              */
+/*    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

+ 581 - 0
svg.mod/lunasvg/3rdparty/plutovg/plutovg-geometry.c

@@ -0,0 +1,581 @@
+#include "plutovg-private.h"
+
+#include <math.h>
+
+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;
+}

+ 265 - 0
svg.mod/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);
+}

+ 198 - 0
svg.mod/lunasvg/3rdparty/plutovg/plutovg-private.h

@@ -0,0 +1,198 @@
+#ifndef PLUTOVG_PRIVATE_H
+#define PLUTOVG_PRIVATE_H
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+
+#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

+ 424 - 0
svg.mod/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 <math.h>
+#include <limits.h>
+
+#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(&params);
+    } 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(&params);
+    }
+
+    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;
+}

+ 495 - 0
svg.mod/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;
+}

+ 260 - 0
svg.mod/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

+ 4 - 0
svg.mod/lunasvg/3rdparty/stb/CMakeLists.txt

@@ -0,0 +1,4 @@
+target_include_directories(lunasvg
+PRIVATE
+    "${CMAKE_CURRENT_LIST_DIR}"
+)

+ 1724 - 0
svg.mod/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 <stdlib.h>
+
+// 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 <stdio.h>
+#endif // STBI_WRITE_NO_STDIO
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#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 <assert.h>
+#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<<bits[1])-1);
+}
+
+static int stbiw__jpg_processDU(stbi__write_context *s, int *bitBuf, int *bitCnt, float *CDU, int du_stride, float *fdtbl, int DC, const unsigned short HTDC[256][2], const unsigned short HTAC[256][2]) {
+   const unsigned short EOB[2] = { HTAC[0x00][0], HTAC[0x00][1] };
+   const unsigned short M16zeroes[2] = { HTAC[0xF0][0], HTAC[0xF0][1] };
+   int dataOff, i, j, n, diff, end0pos, x, y;
+   int DU[64];
+
+   // DCT rows
+   for(dataOff=0, n=du_stride*8; dataOff<n; dataOff+=du_stride) {
+      stbiw__jpg_DCT(&CDU[dataOff], &CDU[dataOff+1], &CDU[dataOff+2], &CDU[dataOff+3], &CDU[dataOff+4], &CDU[dataOff+5], &CDU[dataOff+6], &CDU[dataOff+7]);
+   }
+   // DCT columns
+   for(dataOff=0; dataOff<8; ++dataOff) {
+      stbiw__jpg_DCT(&CDU[dataOff], &CDU[dataOff+du_stride], &CDU[dataOff+du_stride*2], &CDU[dataOff+du_stride*3], &CDU[dataOff+du_stride*4],
+                     &CDU[dataOff+du_stride*5], &CDU[dataOff+du_stride*6], &CDU[dataOff+du_stride*7]);
+   }
+   // Quantize/descale/zigzag the coefficients
+   for(y = 0, j=0; y < 8; ++y) {
+      for(x = 0; x < 8; ++x,++j) {
+         float v;
+         i = y*du_stride+x;
+         v = CDU[i]*fdtbl[j];
+         // DU[stbiw__jpg_ZigZag[j]] = (int)(v < 0 ? ceilf(v - 0.5f) : floorf(v + 0.5f));
+         // ceilf() and floorf() are C99, not C89, but I /think/ they're not needed here anyway?
+         DU[stbiw__jpg_ZigZag[j]] = (int)(v < 0 ? v - 0.5f : v + 0.5f);
+      }
+   }
+
+   // Encode DC
+   diff = DU[0] - DC;
+   if (diff == 0) {
+      stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTDC[0]);
+   } else {
+      unsigned short bits[2];
+      stbiw__jpg_calcBits(diff, bits);
+      stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTDC[bits[1]]);
+      stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits);
+   }
+   // Encode ACs
+   end0pos = 63;
+   for(; (end0pos>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.
+------------------------------------------------------------------------------
+*/

+ 40 - 0
svg.mod/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}
+)

+ 21 - 0
svg.mod/lunasvg/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Nwutobo Samuel Ugochukwu <[email protected]>
+
+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.

+ 86 - 0
svg.mod/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 <lunasvg.h>
+
+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)
+

+ 4 - 0
svg.mod/lunasvg/include/CMakeLists.txt

@@ -0,0 +1,4 @@
+target_include_directories(lunasvg
+PUBLIC
+    "${CMAKE_CURRENT_LIST_DIR}"
+)

+ 340 - 0
svg.mod/lunasvg/include/lunasvg.h

@@ -0,0 +1,340 @@
+/*
+ * Copyright (c) 2020 Nwutobo Samuel Ugochukwu <[email protected]>
+ *
+ * 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 <cstdint>
+#include <memory>
+#include <string>
+#include <map>
+
+#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<Impl> 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<Document> 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<Document> 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<Document> 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<Document> 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<SVGElement> m_rootElement;
+    std::map<std::string, Element*> m_idCache;
+    std::unique_ptr<LayoutSymbol> m_rootBox;
+};
+
+} //namespace lunasvg
+
+#endif // LUNASVG_H

BIN
svg.mod/lunasvg/luna.png


+ 28 - 0
svg.mod/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}"
+)

+ 256 - 0
svg.mod/lunasvg/source/canvas.cpp

@@ -0,0 +1,256 @@
+#include "canvas.h"
+
+#include <cmath>
+
+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> Canvas::create(unsigned char* data, unsigned int width, unsigned int height, unsigned int stride)
+{
+    return std::shared_ptr<Canvas>(new Canvas(data, static_cast<int>(width), static_cast<int>(height), static_cast<int>(stride)));
+}
+
+std::shared_ptr<Canvas> Canvas::create(double x, double y, double width, double height)
+{
+    if(width <= 0.0 || height <= 0.0)
+        return std::shared_ptr<Canvas>(new Canvas(0, 0, 1, 1));
+
+    auto l = static_cast<int>(floor(x));
+    auto t = static_cast<int>(floor(y));
+    auto r = static_cast<int>(ceil(x + width));
+    auto b = static_cast<int>(ceil(y + height));
+    return std::shared_ptr<Canvas>(new Canvas(l, t, r - l, b - t));
+}
+
+std::shared_ptr<Canvas> 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<int>(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<uint32_t*>(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<Point, 3> 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

+ 77 - 0
svg.mod/lunasvg/source/canvas.h

@@ -0,0 +1,77 @@
+#ifndef CANVAS_H
+#define CANVAS_H
+
+#include "property.h"
+#include "plutovg.h"
+
+#include <memory>
+
+namespace lunasvg {
+
+using GradientStop = std::pair<double, Color>;
+using GradientStops = std::vector<GradientStop>;
+
+using DashArray = std::vector<double>;
+
+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<Canvas> create(unsigned char* data, unsigned int width, unsigned int height, unsigned int stride);
+    static std::shared_ptr<Canvas> create(double x, double y, double width, double height);
+    static std::shared_ptr<Canvas> 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

+ 31 - 0
svg.mod/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<LayoutClipPath> ClipPathElement::getClipper(LayoutContext* context)
+{
+    if(context->hasReference(this))
+        return nullptr;
+    LayoutBreaker layoutBreaker(context, this);
+    auto clipper = makeUnique<LayoutClipPath>(this);
+    clipper->units = clipPathUnits();
+    clipper->transform = transform();
+    clipper->clipper = context->getClipper(clip_path());
+    layoutChildren(context, clipper.get());
+    return clipper;
+}
+
+} // namespace lunasvg

+ 20 - 0
svg.mod/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<LayoutClipPath> getClipper(LayoutContext* context);
+};
+
+} // namespace lunasvg
+
+#endif // CLIPPATHELEMENT_H

+ 10 - 0
svg.mod/lunasvg/source/defselement.cpp

@@ -0,0 +1,10 @@
+#include "defselement.h"
+
+namespace lunasvg {
+
+DefsElement::DefsElement()
+    : GraphicsElement(ElementID::Defs)
+{
+}
+
+} // namespace lunasvg

+ 15 - 0
svg.mod/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

+ 233 - 0
svg.mod/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<Node> TextNode::clone() const
+{
+    auto node = makeUnique<TextNode>();
+    node->setText(m_text);
+    return std::move(node);
+}
+
+Element::Element(ElementID id)
+    : m_id(id)
+{
+}
+
+std::unique_ptr<Element> Element::create(ElementID id)
+{
+    switch(id) {
+    case ElementID::Svg:
+        return makeUnique<SVGElement>();
+    case ElementID::Path:
+        return makeUnique<PathElement>();
+    case ElementID::G:
+        return makeUnique<GElement>();
+    case ElementID::Rect:
+        return makeUnique<RectElement>();
+    case ElementID::Circle:
+        return makeUnique<CircleElement>();
+    case ElementID::Ellipse:
+        return makeUnique<EllipseElement>();
+    case ElementID::Line:
+        return makeUnique<LineElement>();
+    case ElementID::Defs:
+        return makeUnique<DefsElement>();
+    case ElementID::Polygon:
+        return makeUnique<PolygonElement>();
+    case ElementID::Polyline:
+        return makeUnique<PolylineElement>();
+    case ElementID::Stop:
+        return makeUnique<StopElement>();
+    case ElementID::LinearGradient:
+        return makeUnique<LinearGradientElement>();
+    case ElementID::RadialGradient:
+        return makeUnique<RadialGradientElement>();
+    case ElementID::Symbol:
+        return makeUnique<SymbolElement>();
+    case ElementID::Use:
+        return makeUnique<UseElement>();
+    case ElementID::Pattern:
+        return makeUnique<PatternElement>();
+    case ElementID::Mask:
+        return makeUnique<MaskElement>();
+    case ElementID::ClipPath:
+        return makeUnique<ClipPathElement>();
+    case ElementID::SolidColor:
+        return makeUnique<SolidColorElement>();
+    case ElementID::Marker:
+        return makeUnique<MarkerElement>();
+    case ElementID::Style:
+        return makeUnique<StyleElement>();
+    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<Element*>(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<Element*>(node);
+    }
+
+    return nullptr;
+}
+
+Node* Element::addChild(std::unique_ptr<Node> 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<const SVGElement*>(this);
+        if(element->has(PropertyID::ViewBox))
+            return element->viewBox(); 
+        return Rect{0, 0, 300, 150};
+    }
+
+    if(parent()->id() == ElementID::Svg) {
+        auto element = static_cast<SVGElement*>(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<Element*>(child.get());
+        element->build(document);
+    }
+}
+
+std::unique_ptr<Node> 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

+ 222 - 0
svg.mod/lunasvg/source/element.h

@@ -0,0 +1,222 @@
+#ifndef ELEMENT_H
+#define ELEMENT_H
+
+#include <memory>
+#include <list>
+
+#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<Property>;
+
+template<typename T, typename... Args>
+inline std::unique_ptr<T> makeUnique(Args&&... args)
+{
+    return std::unique_ptr<T>(new T(std::forward<Args>(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<Node> 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<Node> 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<std::unique_ptr<Node>>;
+
+class Element : public Node {
+public:
+    static std::unique_ptr<Element> 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<Node> 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<typename T>
+    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<Element*>(child.get());
+            element->transverse(callback);
+        }
+    }
+
+    std::unique_ptr<Node> clone() const final;
+
+protected:
+    Element(ElementID id);
+    ElementID m_id;
+    NodeList m_children;
+    PropertyList m_properties;
+};
+
+} // namespace lunasvg
+
+#endif // ELEMENT_H

+ 24 - 0
svg.mod/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<LayoutGroup>(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

+ 17 - 0
svg.mod/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

+ 297 - 0
svg.mod/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<LayoutShape>(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

+ 99 - 0
svg.mod/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

+ 17 - 0
svg.mod/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

+ 17 - 0
svg.mod/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

+ 710 - 0
svg.mod/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 <cmath>
+
+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<LayoutObject> child)
+{
+    m_children.push_back(std::move(child));
+    return &*m_children.back();
+}
+
+LayoutObject* LayoutContainer::addChildIfNotEmpty(std::unique_ptr<LayoutContainer> 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<LayoutObject> 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<LayoutMask*>(ref);
+
+    auto element = getElementById(id);
+    if(element == nullptr || element->id() != ElementID::Mask)
+        return nullptr;
+
+    auto masker = static_cast<MaskElement*>(element)->getMasker(this);
+    return static_cast<LayoutMask*>(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<LayoutClipPath*>(ref);
+
+    auto element = getElementById(id);
+    if(element == nullptr || element->id() != ElementID::ClipPath)
+        return nullptr;
+
+    auto clipper = static_cast<ClipPathElement*>(element)->getClipper(this);
+    return static_cast<LayoutClipPath*>(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<LayoutMarker*>(ref);
+
+    auto element = getElementById(id);
+    if(element == nullptr || element->id() != ElementID::Marker)
+        return nullptr;
+
+    auto marker = static_cast<MarkerElement*>(element)->getMarker(this);
+    return static_cast<LayoutMarker*>(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<PaintElement*>(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<Point, 3> 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

+ 379 - 0
svg.mod/lunasvg/source/layoutcontext.h

@@ -0,0 +1,379 @@
+#ifndef LAYOUTCONTEXT_H
+#define LAYOUTCONTEXT_H
+
+#include "property.h"
+#include "canvas.h"
+
+#include <list>
+#include <map>
+#include <set>
+
+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<std::unique_ptr<LayoutObject>>;
+
+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<LayoutObject> child);
+    LayoutObject* addChildIfNotEmpty(std::unique_ptr<LayoutContainer> 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<MarkerPosition>;
+
+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> 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<LayoutObject> 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<std::string, LayoutObject*> m_resourcesCache;
+    std::set<const Element*> 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

+ 507 - 0
svg.mod/lunasvg/source/lunasvg.cpp

@@ -0,0 +1,507 @@
+#include "lunasvg.h"
+#include "layoutcontext.h"
+#include "parser.h"
+#include "svgelement.h"
+
+#include <fstream>
+#include <cstring>
+#include <cmath>
+
+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<std::uint8_t[]> 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<std::uint8_t>(pb);
+            data[1] = static_cast<std::uint8_t>(pg);
+            data[2] = static_cast<std::uint8_t>(pr);
+            data[3] = static_cast<std::uint8_t>(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::uint32_t>(std::ceil(elementBounds.w));
+        height = static_cast<std::uint32_t>(std::ceil(elementBounds.h));
+    } else if(width != 0 && height == 0) {
+        height = static_cast<std::uint32_t>(std::ceil(width * elementBounds.h / elementBounds.w));
+    } else if(height != 0 && width == 0) {
+        width = static_cast<std::uint32_t>(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> 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> Document::loadFromData(const std::string& string)
+{
+    return loadFromData(string.data(), string.size());
+}
+
+std::unique_ptr<Document> Document::loadFromData(const char* data, std::size_t size)
+{
+    std::unique_ptr<Document> document(new Document);
+    if(!document->parse(data, size))
+        return nullptr;
+    document->updateLayout();
+    return document;
+}
+
+std::unique_ptr<Document> 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::uint32_t>(std::ceil(m_rootBox->width));
+        height = static_cast<std::uint32_t>(std::ceil(m_rootBox->height));
+    } else if(width != 0 && height == 0) {
+        height = static_cast<std::uint32_t>(std::ceil(width * m_rootBox->height / m_rootBox->width));
+    } else if(height != 0 && width == 0) {
+        width = static_cast<std::uint32_t>(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

+ 93 - 0
svg.mod/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<LayoutMarker> 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<LayoutMarker>(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

+ 28 - 0
svg.mod/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<LayoutMarker> getMarker(LayoutContext* context);
+};
+
+} // namespace lunasvg
+
+#endif // MARKERELEMENT_H

+ 72 - 0
svg.mod/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<LayoutMask> 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<LayoutMask>(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

+ 25 - 0
svg.mod/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<LayoutMask> getMasker(LayoutContext* context);
+};
+
+} // namespace lunasvg
+
+#endif // MASKELEMENT_H

+ 405 - 0
svg.mod/lunasvg/source/paintelement.cpp

@@ -0,0 +1,405 @@
+#include "paintelement.h"
+#include "stopelement.h"
+#include "parser.h"
+#include "layoutcontext.h"
+
+#include <set>
+
+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<Element*>(child.get());
+        if(element->id() != ElementID::Stop)
+            continue;
+        auto stop = static_cast<StopElement*>(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<LayoutObject> LinearGradientElement::getPainter(LayoutContext* context)
+{
+    LinearGradientAttributes attributes;
+    std::set<const GradientElement*> 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<const LinearGradientElement*>(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<const GradientElement*>(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<LayoutSolidColor>(this);
+        solid->color = std::get<1>(stops.back());
+        return std::move(solid);
+    }
+
+    auto gradient = makeUnique<LayoutLinearGradient>(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<LayoutObject> RadialGradientElement::getPainter(LayoutContext* context)
+{
+    RadialGradientAttributes attributes;
+    std::set<const GradientElement*> 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<const RadialGradientElement*>(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<const GradientElement*>(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<LayoutSolidColor>(this);
+        solid->color = std::get<1>(stops.back());
+        return std::move(solid);
+    }
+
+    auto gradient = makeUnique<LayoutRadialGradient>(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<LayoutObject> PatternElement::getPainter(LayoutContext* context)
+{
+    if(context->hasReference(this))
+        return nullptr;
+
+    PatternAttributes attributes;
+    std::set<const PatternElement*> 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<PatternElement*>(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<LayoutPattern>(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<LayoutObject> SolidColorElement::getPainter(LayoutContext*)
+{
+    auto solid = makeUnique<LayoutSolidColor>(this);
+    solid->color = solid_color();
+    solid->color.combine(solid_opacity());
+    return std::move(solid);
+}
+
+} // namespace lunasvg

+ 331 - 0
svg.mod/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<LayoutObject> 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<LayoutObject> 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<LayoutObject> 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<LayoutObject> getPainter(LayoutContext* context) final;
+};
+
+class SolidColorElement final : public PaintElement {
+public:
+    SolidColorElement();
+
+    std::unique_ptr<LayoutObject> 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

+ 1892 - 0
svg.mod/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<std::string, unsigned int> 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<uint8_t>(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<std::string, ElementID> 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<std::string, PropertyID> 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<std::string, PropertyID> 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<char>(cp);
+            } else if(cp < 0x800) {
+                c[2] = 0;
+                c[1] = static_cast<char>((cp & 0x3F) | 0x80);
+                cp >>= 6;
+                c[0] = static_cast<char>(cp | 0xC0);
+            } else if(cp < 0x10000) {
+                c[3] = 0;
+                c[2] = static_cast<char>((cp & 0x3F) | 0x80);
+                cp >>= 6;
+                c[1] = static_cast<char>((cp & 0x3F) | 0x80);
+                cp >>= 6;
+                c[0] = static_cast<char>(cp | 0xE0);
+            } else if(cp < 0x200000) {
+                c[4] = 0;
+                c[3] = static_cast<char>((cp & 0x3F) | 0x80);
+                cp >>= 6;
+                c[2] = static_cast<char>((cp & 0x3F) | 0x80);
+                cp >>= 6;
+                c[1] = static_cast<char>((cp & 0x3F) | 0x80);
+                cp >>= 6;
+                c[0] = static_cast<char>(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<SVGElement>();
+                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<Element*>(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

+ 182 - 0
svg.mod/lunasvg/source/parser.h

@@ -0,0 +1,182 @@
+#ifndef PARSER_H
+#define PARSER_H
+
+#include <map>
+#include <set>
+
+#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<SimpleSelector>;
+using SelectorList = std::vector<Selector>;
+
+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<AttributeSelector> attributeSelectors;
+    std::vector<PseudoClassSelector> pseudoClassSelectors;
+};
+
+struct Declaration {
+    int specificity;
+    PropertyID id;
+    std::string value;
+};
+
+using DeclarationList = std::vector<Declaration>;
+
+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<RuleData>& 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<RuleData> m_rules;
+    uint32_t m_position{0};
+};
+
+} // namespace lunasvg
+
+#endif // PARSER_H

+ 257 - 0
svg.mod/lunasvg/source/parserutils.h

@@ -0,0 +1,257 @@
+#ifndef PARSERUTILS_H
+#define PARSERUTILS_H
+
+#include <cstring>
+#include <cmath>
+#include <limits>
+#include <string>
+#include <algorithm>
+
+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<typename T>
+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<T>::max();
+    static const bool isSigned = std::numeric_limits<T>::is_signed;
+    using signed_t = typename std::make_signed<T>::type;
+    const T maxMultiplier = intMax / static_cast<T>(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<T>(digitValue) > (intMax % static_cast<T>(base)) + isNegative))
+            return false;
+        value = static_cast<T>(base) * value + static_cast<T>(digitValue);
+    } while(ptr < end && isIntegralDigit(*ptr, base));
+
+    if(isNegative)
+        integer = -static_cast<signed_t>(value);
+    else
+        integer = value;
+
+    return true;
+}
+
+template<typename T>
+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<T>::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<T>(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<T>(10) * fraction + (*ptr - '0');
+            divisor *= static_cast<T>(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<T>(pow(10.0, expsign*exponent));
+
+    return number >= -numberMax && number <= numberMax;
+}
+
+} // namespace Utils
+
+} // namespace lunasvg
+
+#endif // PARSERUTILS_H

+ 732 - 0
svg.mod/lunasvg/source/property.cpp

@@ -0,0 +1,732 @@
+#include "property.h"
+#include "element.h"
+#include "lunasvg.h"
+
+#include <algorithm>
+#include <cmath>
+
+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<int>(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<int>(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<Point, 3>& 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

+ 357 - 0
svg.mod/lunasvg/source/property.h

@@ -0,0 +1,357 @@
+#ifndef PROPERTY_H
+#define PROPERTY_H
+
+#include <vector>
+#include <string>
+#include <array>
+#include <cstdint>
+
+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<typename T>
+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<Point>;
+
+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<PathCommand>& commands() const { return m_commands; }
+    const std::vector<Point>& points() const { return m_points; }
+
+private:
+    std::vector<PathCommand> m_commands;
+    std::vector<Point> m_points;
+};
+
+class PathIterator {
+public:
+   PathIterator(const Path& path);
+
+   PathCommand currentSegment(std::array<Point, 3>& points) const;
+   bool isDone() const;
+   void next();
+
+private:
+   mutable Point m_startPoint;
+   const std::vector<PathCommand>& 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<Length>;
+
+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

+ 24 - 0
svg.mod/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

+ 18 - 0
svg.mod/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

+ 177 - 0
svg.mod/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

+ 52 - 0
svg.mod/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

+ 10 - 0
svg.mod/lunasvg/source/styleelement.cpp

@@ -0,0 +1,10 @@
+#include "styleelement.h"
+
+namespace lunasvg {
+
+StyleElement::StyleElement()
+    : Element(ElementID::Style)
+{
+}
+
+} // namespace lunasvg

+ 15 - 0
svg.mod/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

+ 124 - 0
svg.mod/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<LayoutSymbol> 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<LayoutSymbol>(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<LayoutSymbol>(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

+ 28 - 0
svg.mod/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<LayoutSymbol> layoutTree(const Document* document);
+    void layout(LayoutContext* context, LayoutContainer* current) final;
+};
+
+} // namespace lunasvg
+
+#endif // SVGELEMENT_H

+ 47 - 0
svg.mod/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

+ 22 - 0
svg.mod/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

+ 127 - 0
svg.mod/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<LayoutGroup>(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<Element> 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

+ 26 - 0
svg.mod/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<Element> cloneTargetElement(const Element* targetElement) const;
+    void build(const Document* document) final;
+};
+
+} // namespace lunasvg
+
+#endif // USEELEMENT_H

+ 69 - 0
svg.mod/lunasvg/svg2png.cpp

@@ -0,0 +1,69 @@
+#define STB_IMAGE_WRITE_IMPLEMENTATION
+#include "stb_image_write.h"
+
+#include <iostream>
+#include <sstream>
+
+#include <lunasvg.h>
+
+using namespace lunasvg;
+
+int help()
+{
+    std::cout << "Usage: \n"
+                 "   svg2png [filename] [resolution] [bgColor]\n\n"
+                 "Examples: \n"
+                 "    $ svg2png input.svg\n"
+                 "    $ svg2png input.svg 512x512\n"
+                 "    $ svg2png input.svg 512x512 0xff00ffff\n\n";
+    return 1;
+}
+
+bool setup(int argc, char** argv, std::string& filename, std::uint32_t& width, std::uint32_t& height, std::uint32_t& bgColor)
+{
+    if(argc > 1) filename.assign(argv[1]);
+    if(argc > 2) {
+        std::stringstream ss;
+
+        ss << argv[2];
+        ss >> width;
+
+        if(ss.fail() || ss.get() != 'x')
+            return false;
+
+        ss >> height;
+    }
+
+    if(argc > 3) {
+        std::stringstream ss;
+
+        ss << std::hex << argv[3];
+        ss >> std::hex >> bgColor;
+    }
+
+    return argc > 1;
+}
+
+int main(int argc, char** argv)
+{
+    std::string filename;
+    std::uint32_t width = 0, height = 0;
+    std::uint32_t bgColor = 0x00000000;
+    if(!setup(argc, argv, filename, width, height, bgColor)) return help();
+
+    auto document = Document::loadFromFile(filename);
+    if(!document) return help();
+
+    auto bitmap = document->renderToBitmap(width, height, bgColor);
+    if(!bitmap.valid()) return help();
+    bitmap.convertToRGBA();
+
+    auto basename = filename.substr(filename.find_last_of("/\\") + 1);
+    basename.append(".png");
+
+    stbi_write_png(basename.c_str(), bitmap.width(), bitmap.height(), 4, bitmap.data(), 0);
+
+    std::cout << "Generated PNG file : " << basename << std::endl;
+
+    return 0;
+}

+ 0 - 75
svg.mod/nanosvg/CMakeLists.txt

@@ -1,75 +0,0 @@
-cmake_minimum_required(VERSION 3.10)
-
-project(NanoSVG C)
-
-# CMake needs *.c files to do something useful
-configure_file(src/nanosvg.h ${CMAKE_CURRENT_BINARY_DIR}/nanosvg.c)
-configure_file(src/nanosvgrast.h ${CMAKE_CURRENT_BINARY_DIR}/nanosvgrast.c)
-
-add_library(nanosvg ${CMAKE_CURRENT_BINARY_DIR}/nanosvg.c)
-
-find_library(MATH_LIBRARY m) # Business as usual
-if(MATH_LIBRARY)
-    target_link_libraries(nanosvg PUBLIC ${MATH_LIBRARY})
-endif()
-
-target_include_directories(nanosvg PUBLIC $<INSTALL_INTERFACE:include/nanosvg>)
-target_compile_definitions(nanosvg PRIVATE NANOSVG_IMPLEMENTATION)
-
-# Same for nanosvgrast
-add_library(nanosvgrast ${CMAKE_CURRENT_BINARY_DIR}/nanosvgrast.c)
-target_link_libraries(nanosvgrast PUBLIC nanosvg)
-target_include_directories(nanosvgrast PRIVATE src)
-target_compile_definitions(nanosvgrast PRIVATE NANOSVGRAST_IMPLEMENTATION)
-
-# Installation and export:
-
-include(CMakePackageConfigHelpers)
-
-write_basic_package_version_file(
-    "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
-    VERSION 1.0
-    COMPATIBILITY AnyNewerVersion
-)
-
-install(TARGETS nanosvg nanosvgrast
-        EXPORT ${PROJECT_NAME}Targets
-)
-
-export(EXPORT ${PROJECT_NAME}Targets 
-       FILE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake" 
-       NAMESPACE ${PROJECT_NAME}::
-)
-
-set(ConfigPackageLocation lib/cmake/${PROJECT_NAME})
-
-configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
-  "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
-  INSTALL_DESTINATION ${ConfigPackageLocation}
-  NO_CHECK_REQUIRED_COMPONENTS_MACRO
-)
-
-install(
-    FILES
-      src/nanosvg.h
-      src/nanosvgrast.h
-    DESTINATION
-      include/nanosvg
-  )
-
-install(EXPORT ${PROJECT_NAME}Targets
-  FILE
-    ${PROJECT_NAME}Targets.cmake
-  NAMESPACE
-    ${PROJECT_NAME}::
-  DESTINATION
-    ${ConfigPackageLocation}
-)
-
-install(
-  FILES
-    "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
-    "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
-  DESTINATION
-    ${ConfigPackageLocation}
-)

+ 0 - 5
svg.mod/nanosvg/Config.cmake.in

@@ -1,5 +0,0 @@
-@PACKAGE_INIT@
-
-if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/NanoSVGTargets.cmake)
-    include("${CMAKE_CURRENT_LIST_DIR}/NanoSVGTargets.cmake")
-endif ()

+ 0 - 18
svg.mod/nanosvg/LICENSE.txt

@@ -1,18 +0,0 @@
-Copyright (c) 2013-14 Mikko Mononen [email protected]
-
-This software is provided 'as-is', without any express or implied
-warranty.  In no event will the authors be held liable for any damages
-arising from the use of this software.
-
-Permission is granted to anyone to use this software for any purpose,
-including commercial applications, and to alter it and redistribute it
-freely, subject to the following restrictions:
-
-1. The origin of this software must not be misrepresented; you must not
-claim that you wrote the original software. If you use this software
-in a product, an acknowledgment in the product documentation would be
-appreciated but is not required.
-2. Altered source versions must be plainly marked as such, and must not be
-misrepresented as being the original software.
-3. This notice may not be removed or altered from any source distribution.
-

+ 0 - 112
svg.mod/nanosvg/README.md

@@ -1,112 +0,0 @@
-*This project is not actively maintained.*
-
-Nano SVG
-==========
-
-## Parser
-
-![screenshot of some splines rendered with the sample program](/example/screenshot-1.png?raw=true)
-
-NanoSVG is a simple stupid single-header-file SVG parse. The output of the parser is a list of cubic bezier shapes.
-
-The library suits well for anything from rendering scalable icons in your editor application to prototyping a game.
-
-NanoSVG supports a wide range of SVG features, but something may be missing, feel free to create a pull request!
-
-The shapes in the SVG images are transformed by the viewBox and converted to specified units.
-That is, you should get the same looking data as your designed in your favorite app.
-
-NanoSVG can return the paths in few different units. For example if you want to render an image, you may choose
-to get the paths in pixels, or if you are feeding the data into a CNC-cutter, you may want to use millimeters. 
-
-The units passed to NanoSVG should be one of: 'px', 'pt', 'pc' 'mm', 'cm', or 'in'.
-DPI (dots-per-inch) controls how the unit conversion is done.
-
-If you don't know or care about the units stuff, "px" and 96 should get you going.
-
-## Rasterizer
-
-![screenshot of tiger.svg rendered with NanoSVG rasterizer](/example/screenshot-2.png?raw=true)
-
-The parser library is accompanied with really simpler SVG rasterizer. Currently it only renders flat filled shapes.
-
-The intended usage for the rasterizer is to for example bake icons of different size into a texture. The rasterizer is not particular fast or accurate, but it's small and packed in one header file.
-
-
-## Example Usage
-
-``` C
-// Load
-struct NSVGimage* image;
-image = nsvgParseFromFile("test.svg", "px", 96);
-printf("size: %f x %f\n", image->width, image->height);
-// Use...
-for (shape = image->shapes; shape != NULL; shape = shape->next) {
-	for (path = shape->paths; path != NULL; path = path->next) {
-		for (i = 0; i < path->npts-1; i += 3) {
-			float* p = &path->pts[i*2];
-			drawCubicBez(p[0],p[1], p[2],p[3], p[4],p[5], p[6],p[7]);
-		}
-	}
-}
-// Delete
-nsvgDelete(image);
-```
-
-## Using NanoSVG in your project
-
-In order to use NanoSVG in your own project, just copy nanosvg.h to your project.
-In one C/C++ define `NANOSVG_IMPLEMENTATION` before including the library to expand the NanoSVG implementation in that file.
-NanoSVG depends on `stdio.h` ,`string.h` and `math.h`, they should be included where the implementation is expanded before including NanoSVG. 
-
-``` C
-#include <stdio.h>
-#include <string.h>
-#include <math.h>
-#define NANOSVG_IMPLEMENTATION	// Expands implementation
-#include "nanosvg.h"
-```
-
-By default, NanoSVG parses only the most common colors. In order to get support for full list of [SVG color keywords](http://www.w3.org/TR/SVG11/types.html#ColorKeywords), define `NANOSVG_ALL_COLOR_KEYWORDS` before expanding the implementation.
-
-``` C
-#include <stdio.h>
-#include <string.h>
-#include <math.h>
-#define NANOSVG_ALL_COLOR_KEYWORDS	// Include full list of color keywords.
-#define NANOSVG_IMPLEMENTATION		// Expands implementation
-#include "nanosvg.h"
-```
-
-Alternatively, you can install the library using CMake and import it into your project using the standard CMake `find_package` command.
-
-```CMake
-add_executable(myexe main.c)
-
-find_package(NanoSVG REQUIRED)
-
-target_link_libraries(myexe NanoSVG::nanosvg NanoSVG::nanosvgrast)
-```
-
-## Compiling Example Project
-
-In order to compile the demo project, your will need to install [GLFW](http://www.glfw.org/) to compile.
-
-NanoSVG demo project uses [premake4](http://industriousone.com/premake) to build platform specific projects, now is good time to install it if you don't have it already. To build the example, navigate into the root folder in your favorite terminal, then:
-
-- *OS X*: `premake4 xcode4`
-- *Windows*: `premake4 vs2010`
-- *Linux*: `premake4 gmake`
-
-See premake4 documentation for full list of supported build file types. The projects will be created in `build` folder. An example of building and running the example on OS X:
-
-```bash
-$ premake4 gmake
-$ cd build/
-$ make
-$ ./example
-```
-
-# License
-
-The library is licensed under [zlib license](LICENSE.txt)

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 44
svg.mod/nanosvg/example/23.svg


+ 0 - 97
svg.mod/nanosvg/example/drawing.svg

@@ -1,97 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
-   xmlns:dc="http://purl.org/dc/elements/1.1/"
-   xmlns:cc="http://creativecommons.org/ns#"
-   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-   xmlns:svg="http://www.w3.org/2000/svg"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
-   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   width="1000"
-   height="1000"
-   id="svg2"
-   version="1.1"
-   inkscape:version="0.48.2 r9819"
-   sodipodi:docname="drawing.svg">
-  <defs
-     id="defs4" />
-  <sodipodi:namedview
-     id="base"
-     pagecolor="#ffffff"
-     bordercolor="#666666"
-     borderopacity="1.0"
-     inkscape:pageopacity="0.0"
-     inkscape:pageshadow="2"
-     inkscape:zoom="0.35"
-     inkscape:cx="375"
-     inkscape:cy="520"
-     inkscape:document-units="px"
-     inkscape:current-layer="layer1"
-     showgrid="false"
-     inkscape:window-width="751"
-     inkscape:window-height="578"
-     inkscape:window-x="0"
-     inkscape:window-y="22"
-     inkscape:window-maximized="0" />
-  <metadata
-     id="metadata7">
-    <rdf:RDF>
-      <cc:Work
-         rdf:about="">
-        <dc:format>image/svg+xml</dc:format>
-        <dc:type
-           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
-        <dc:title></dc:title>
-      </cc:Work>
-    </rdf:RDF>
-  </metadata>
-  <g
-     inkscape:label="Layer 1"
-     inkscape:groupmode="layer"
-     id="layer1"
-     transform="translate(0,-52.362183)">
-    <path
-       style="fill:#ff5555;stroke:#000000;stroke-width:10.62107277px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
-       d="m 131.73911,422.01626 c 0,146.85769 43.82213,215.39128 201.5818,141.96244 157.75968,-73.42885 188.43518,-107.69564 354.95926,78.32409 166.5241,186.01973 210.34624,244.76282 162.1419,-122.3814 -48.20435,-367.1442 -4.38221,34.26679 -131.46641,-24.47627 C 591.87149,436.70204 732.10231,191.93923 543.66715,187.04398 355.23198,182.14871 574.34264,265.36807 534.90271,368.16845 495.4628,470.96883 355.23198,627.61702 311.40985,475.8641 267.58772,324.11115 193.09009,333.90166 131.73911,422.01626 z"
-       id="path2985"
-       inkscape:connector-curvature="0" />
-    <rect
-       style="fill:#00ffff;stroke:#000000;stroke-width:10.62107277px;"
-       id="rect2987"
-       width="390.01697"
-       height="200.70551"
-       x="228.14781"
-       y="539.50238" />
-    <path
-       sodipodi:type="arc"
-       style="fill:#00ffff"
-       id="path3008"
-       sodipodi:cx="157.14285"
-       sodipodi:cy="168.57143"
-       sodipodi:rx="57.142857"
-       sodipodi:ry="88.571426"
-       d="m 214.28571,168.57143 a 57.142857,88.571426 0 1 1 -114.285714,0 57.142857,88.571426 0 1 1 114.285714,0 z"
-       transform="translate(188.57143,138.07647)" />
-    <rect
-       style="fill:#00ff00"
-       id="rect3010"
-       width="371.42856"
-       height="145.71428"
-       x="261.66104"
-       y="945.44141"
-       transform="matrix(0.948958,-0.31540248,0.31540248,0.948958,0,0)"
-       ry="51.42857" />
-    <path
-       sodipodi:type="arc"
-       style="fill:#00ff00"
-       id="path3038"
-       sodipodi:cx="200"
-       sodipodi:cy="177.14285"
-       sodipodi:rx="54.285713"
-       sodipodi:ry="54.285713"
-       d="m 254.28571,177.14285 a 54.285713,54.285713 0 1 1 -108.57142,0 54.285713,54.285713 0 1 1 108.57142,0 z"
-       transform="translate(0,52.362183)" />
-  </g>
-</svg>

+ 0 - 258
svg.mod/nanosvg/example/example1.c

@@ -1,258 +0,0 @@
-//
-// Copyright (c) 2013 Mikko Mononen [email protected]
-//
-// This software is provided 'as-is', without any express or implied
-// warranty.  In no event will the authors be held liable for any damages
-// arising from the use of this software.
-// Permission is granted to anyone to use this software for any purpose,
-// including commercial applications, and to alter it and redistribute it
-// freely, subject to the following restrictions:
-// 1. The origin of this software must not be misrepresented; you must not
-//    claim that you wrote the original software. If you use this software
-//    in a product, an acknowledgment in the product documentation would be
-//    appreciated but is not required.
-// 2. Altered source versions must be plainly marked as such, and must not be
-//    misrepresented as being the original software.
-// 3. This notice may not be removed or altered from any source distribution.
-//
-
-#include <stdio.h>
-#include <string.h>
-#include <float.h>
-#include <GLFW/glfw3.h>
-
-#define NANOSVG_IMPLEMENTATION
-#include "nanosvg.h"
-
-NSVGimage* g_image = NULL;
-
-static unsigned char bgColor[4] = {205,202,200,255};
-static unsigned char lineColor[4] = {0,160,192,255};
-
-static float distPtSeg(float x, float y, float px, float py, float qx, float qy)
-{
-	float pqx, pqy, dx, dy, d, t;
-	pqx = qx-px;
-	pqy = qy-py;
-	dx = x-px;
-	dy = y-py;
-	d = pqx*pqx + pqy*pqy;
-	t = pqx*dx + pqy*dy;
-	if (d > 0) t /= d;
-	if (t < 0) t = 0;
-	else if (t > 1) t = 1;
-	dx = px + t*pqx - x;
-	dy = py + t*pqy - y;
-	return dx*dx + dy*dy;
-}
-
-static void cubicBez(float x1, float y1, float x2, float y2,
-					 float x3, float y3, float x4, float y4,
-					 float tol, int level)
-{
-	float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234;
-	float d;
-	
-	if (level > 12) return;
-
-	x12 = (x1+x2)*0.5f;
-	y12 = (y1+y2)*0.5f;
-	x23 = (x2+x3)*0.5f;
-	y23 = (y2+y3)*0.5f;
-	x34 = (x3+x4)*0.5f;
-	y34 = (y3+y4)*0.5f;
-	x123 = (x12+x23)*0.5f;
-	y123 = (y12+y23)*0.5f;
-	x234 = (x23+x34)*0.5f;
-	y234 = (y23+y34)*0.5f;
-	x1234 = (x123+x234)*0.5f;
-	y1234 = (y123+y234)*0.5f;
-
-	d = distPtSeg(x1234, y1234, x1,y1, x4,y4);
-	if (d > tol*tol) {
-		cubicBez(x1,y1, x12,y12, x123,y123, x1234,y1234, tol, level+1); 
-		cubicBez(x1234,y1234, x234,y234, x34,y34, x4,y4, tol, level+1); 
-	} else {
-		glVertex2f(x4, y4);
-	}
-}
-
-void drawPath(float* pts, int npts, char closed, float tol)
-{
-	int i;
-	glBegin(GL_LINE_STRIP);
-	glColor4ubv(lineColor);
-	glVertex2f(pts[0], pts[1]);
-	for (i = 0; i < npts-1; i += 3) {
-		float* p = &pts[i*2];
-		cubicBez(p[0],p[1], p[2],p[3], p[4],p[5], p[6],p[7], tol, 0);
-	}
-	if (closed) {
-		glVertex2f(pts[0], pts[1]);
-	}
-	glEnd();
-}
-
-void drawControlPts(float* pts, int npts)
-{
-	int i;
-
-	// Control lines
-	glColor4ubv(lineColor);
-	glBegin(GL_LINES);
-	for (i = 0; i < npts-1; i += 3) {
-		float* p = &pts[i*2];
-		glVertex2f(p[0],p[1]);
-		glVertex2f(p[2],p[3]);
-		glVertex2f(p[4],p[5]);
-		glVertex2f(p[6],p[7]);
-	}
-	glEnd();
-
-	// Points
-	glPointSize(6.0f);
-	glColor4ubv(lineColor);
-
-	glBegin(GL_POINTS);
-	glVertex2f(pts[0],pts[1]);
-	for (i = 0; i < npts-1; i += 3) {
-		float* p = &pts[i*2];
-		glVertex2f(p[6],p[7]);
-	}
-	glEnd();
-
-	// Points
-	glPointSize(3.0f);
-
-	glBegin(GL_POINTS);
-	glColor4ubv(bgColor);
-	glVertex2f(pts[0],pts[1]);
-	for (i = 0; i < npts-1; i += 3) {
-		float* p = &pts[i*2];
-		glColor4ubv(lineColor);
-		glVertex2f(p[2],p[3]);
-		glVertex2f(p[4],p[5]);
-		glColor4ubv(bgColor);
-		glVertex2f(p[6],p[7]);
-	}
-	glEnd();
-}
-
-void drawframe(GLFWwindow* window)
-{
-	int width = 0, height = 0;
-	float view[4], cx, cy, hw, hh, aspect, px;
-	NSVGshape* shape;
-	NSVGpath* path;
-
-	glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
-	glfwGetFramebufferSize(window, &width, &height);
-
-	glViewport(0, 0, width, height);
-	glClearColor(220.0f/255.0f, 220.0f/255.0f, 220.0f/255.0f, 1.0f);
-	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
-	glEnable(GL_BLEND);
-	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-	glDisable(GL_TEXTURE_2D);
-	glMatrixMode(GL_PROJECTION);
-	glLoadIdentity();
-
-	// Fit view to bounds
-	cx = g_image->width*0.5f;
-	cy = g_image->height*0.5f;
-	hw = g_image->width*0.5f;
-	hh = g_image->height*0.5f;
-
-	if (width/hw < height/hh) {
-		aspect = (float)height / (float)width;
-		view[0] = cx - hw * 1.2f;
-		view[2] = cx + hw * 1.2f;
-		view[1] = cy - hw * 1.2f * aspect;
-		view[3] = cy + hw * 1.2f * aspect;
-	} else {
-		aspect = (float)width / (float)height;
-		view[0] = cx - hh * 1.2f * aspect;
-		view[2] = cx + hh * 1.2f * aspect;
-		view[1] = cy - hh * 1.2f;
-		view[3] = cy + hh * 1.2f;
-	}
-	// Size of one pixel.
-	px = (view[2] - view[1]) / (float)width;
-
-	glOrtho(view[0], view[2], view[3], view[1], -1, 1);
-
-	glMatrixMode(GL_MODELVIEW);
-	glLoadIdentity();
-	glDisable(GL_DEPTH_TEST);
-	glColor4ub(255,255,255,255);
-	glEnable(GL_BLEND);
-	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
-
-	// Draw bounds
-	glColor4ub(0,0,0,64);
-	glBegin(GL_LINE_LOOP);
-	glVertex2f(0, 0);
-	glVertex2f(g_image->width, 0);
-	glVertex2f(g_image->width, g_image->height);
-	glVertex2f(0, g_image->height);
-	glEnd();
-
-	for (shape = g_image->shapes; shape != NULL; shape = shape->next) {
-		for (path = shape->paths; path != NULL; path = path->next) {
-			drawPath(path->pts, path->npts, path->closed, px * 1.5f);
-			drawControlPts(path->pts, path->npts);
-		}
-	}
-
-	glfwSwapBuffers(window);
-}
-
-void resizecb(GLFWwindow* window, int width, int height)
-{
-	// Update and render
-	NSVG_NOTUSED(width);
-	NSVG_NOTUSED(height);
-	drawframe(window);
-}
-
-int main()
-{
-	GLFWwindow* window;
-	const GLFWvidmode* mode;
-
-	if (!glfwInit())
-		return -1;
-
-	mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
-    window = glfwCreateWindow(mode->width - 40, mode->height - 80, "Nano SVG", NULL, NULL);
-	if (!window)
-	{
-		printf("Could not open window\n");
-		glfwTerminate();
-		return -1;
-	}
-
-	glfwSetFramebufferSizeCallback(window, resizecb);
-	glfwMakeContextCurrent(window);
-	glEnable(GL_POINT_SMOOTH);
-	glEnable(GL_LINE_SMOOTH);
-
-
-	g_image = nsvgParseFromFile("../example/nano.svg", "px", 96.0f);
-	if (g_image == NULL) {
-		printf("Could not open SVG image.\n");
-		glfwTerminate();
-		return -1;
-	}
-
-	while (!glfwWindowShouldClose(window))
-	{
-		drawframe(window);
-		glfwPollEvents();
-	}
-
-	nsvgDelete(g_image);
-
-	glfwTerminate();
-	return 0;
-}

+ 0 - 69
svg.mod/nanosvg/example/example2.c

@@ -1,69 +0,0 @@
-//
-// Copyright (c) 2013 Mikko Mononen [email protected]
-//
-// This software is provided 'as-is', without any express or implied
-// warranty.  In no event will the authors be held liable for any damages
-// arising from the use of this software.
-// Permission is granted to anyone to use this software for any purpose,
-// including commercial applications, and to alter it and redistribute it
-// freely, subject to the following restrictions:
-// 1. The origin of this software must not be misrepresented; you must not
-//    claim that you wrote the original software. If you use this software
-//    in a product, an acknowledgment in the product documentation would be
-//    appreciated but is not required.
-// 2. Altered source versions must be plainly marked as such, and must not be
-//    misrepresented as being the original software.
-// 3. This notice may not be removed or altered from any source distribution.
-//
-
-#include <stdio.h>
-#include <string.h>
-#include <float.h>
-#define STB_IMAGE_WRITE_IMPLEMENTATION
-#include "stb_image_write.h"
-#define NANOSVG_IMPLEMENTATION
-#include "nanosvg.h"
-#define NANOSVGRAST_IMPLEMENTATION
-#include "nanosvgrast.h"
-
-int main()
-{
-	NSVGimage *image = NULL;
-	NSVGrasterizer *rast = NULL;
-	unsigned char* img = NULL;
-	int w, h;
-	const char* filename = "../example/23.svg";
-
-	printf("parsing %s\n", filename);
-	image = nsvgParseFromFile(filename, "px", 96.0f);
-	if (image == NULL) {
-		printf("Could not open SVG image.\n");
-		goto error;
-	}
-	w = (int)image->width;
-	h = (int)image->height;
-
-	rast = nsvgCreateRasterizer();
-	if (rast == NULL) {
-		printf("Could not init rasterizer.\n");
-		goto error;
-	}
-
-	img = malloc(w*h*4);
-	if (img == NULL) {
-		printf("Could not alloc image buffer.\n");
-		goto error;
-	}
-
-	printf("rasterizing image %d x %d\n", w, h);
-	nsvgRasterize(rast, image, 0,0,1, img, w, h, w*4);
-
-	printf("writing svg.png\n");
- 	stbi_write_png("svg.png", w, h, 4, img, w*4);
-
-error:
-	nsvgDeleteRasterizer(rast);
-	nsvgDelete(image);
-
-	return 0;
-}

+ 0 - 27
svg.mod/nanosvg/example/nano.svg

@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948)  -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-	 width="640px" height="480px" viewBox="0 0 640 480" enable-background="new 0 0 640 480" xml:space="preserve">
-<path d="M282.658,250.271c0,5.31-1.031,10.156-3.087,14.543c-2.059,4.387-4.984,8.152-8.774,11.293
-	c-3.793,3.144-8.477,5.58-14.055,7.312c-5.581,1.731-11.836,2.601-18.767,2.601c-9.968,0-18.605-1.572-25.917-4.713
-	s-13.299-6.986-17.955-11.536l13.812-15.111c4.116,3.684,8.584,6.499,13.405,8.449c4.819,1.95,9.993,2.925,15.518,2.925
-	c5.525,0,9.856-1.219,12.999-3.656c3.141-2.438,4.712-5.769,4.712-9.993c0-2.056-0.3-3.844-0.894-5.361
-	c-0.596-1.517-1.653-2.925-3.168-4.226c-1.518-1.3-3.549-2.519-6.093-3.655c-2.546-1.138-5.768-2.301-9.668-3.494
-	c-6.5-2.056-11.943-4.25-16.33-6.58c-4.387-2.328-7.937-4.9-10.643-7.719c-2.709-2.815-4.659-5.931-5.849-9.343
-	c-1.193-3.412-1.788-7.23-1.788-11.455c0-5.2,1.082-9.831,3.25-13.893c2.166-4.062,5.144-7.5,8.937-10.318
-	c3.791-2.815,8.178-4.956,13.162-6.418c4.981-1.462,10.343-2.193,16.086-2.193c8.449,0,15.842,1.247,22.179,3.737
-	c6.337,2.493,11.997,6.121,16.98,10.887l-12.674,14.624c-7.583-6.281-15.655-9.424-24.21-9.424c-4.875,0-8.721,0.95-11.537,2.844
-	c-2.818,1.896-4.225,4.578-4.225,8.043c0,1.843,0.297,3.412,0.894,4.712c0.594,1.3,1.65,2.519,3.168,3.656
-	c1.516,1.137,3.656,2.249,6.418,3.331c2.763,1.084,6.309,2.33,10.643,3.736c5.306,1.734,10.046,3.631,14.218,5.688
-	c4.169,2.06,7.662,4.524,10.48,7.394c2.815,2.871,4.981,6.174,6.5,9.911C281.898,240.603,282.658,245.071,282.658,250.271z
-	 M335.953,260.833l20.637-90.181h27.46l-32.011,112.604h-33.634l-32.173-112.604h28.598l20.311,90.181H335.953z M437.832,286.019
-	c-16.357,0-28.896-5.01-37.615-15.03c-8.722-10.019-13.081-24.779-13.081-44.278c0-9.531,1.407-17.98,4.225-25.348
-	c2.815-7.366,6.688-13.54,11.618-18.524c4.928-4.981,10.668-8.747,17.223-11.293c6.555-2.544,13.568-3.818,21.043-3.818
-	c8.23,0,15.436,1.3,21.611,3.899c6.174,2.6,11.537,5.959,16.086,10.075l-14.137,14.624c-3.467-3.032-6.906-5.281-10.318-6.744
-	s-7.393-2.193-11.941-2.193c-4.01,0-7.693,0.731-11.051,2.193s-6.256,3.793-8.691,6.987c-2.438,3.196-4.334,7.287-5.688,12.268
-	c-1.355,4.984-2.031,10.996-2.031,18.037c0,7.367,0.486,13.567,1.463,18.604c0.975,5.037,2.408,9.1,4.305,12.187
-	c1.895,3.087,4.307,5.309,7.23,6.662c2.926,1.355,6.338,2.031,10.238,2.031c5.631,0,10.613-1.244,14.947-3.737v-25.186h-14.785
-	l-2.6-18.849h43.547v55.57c-5.85,3.793-12.297,6.718-19.336,8.774C453.051,284.987,445.631,286.019,437.832,286.019z M523.5,151.5
-	c0-6.627-5.373-12-12-12h-343c-6.627,0-12,5.373-12,12v150c0,6.627,5.373,12,12,12h343c6.627,0,12-5.373,12-12V151.5z"/>
-</svg>

BIN
svg.mod/nanosvg/example/screenshot-1.png


BIN
svg.mod/nanosvg/example/screenshot-2.png


+ 0 - 511
svg.mod/nanosvg/example/stb_image_write.h

@@ -1,511 +0,0 @@
-/* stbiw-0.92 - public domain - http://nothings.org/stb/stb_image_write.h
-   writes out PNG/BMP/TGA images to C stdio - Sean Barrett 2010
-                            no warranty implied; use at your own risk
-
-
-Before including,
-
-    #define STB_IMAGE_WRITE_IMPLEMENTATION
-
-in the file that you want to have the implementation.
-
-
-ABOUT:
-
-   This header file is a library for writing images to C stdio. It could be
-   adapted to write to memory or a general streaming interface; let me know.
-
-   The PNG output is not optimal; it is 20-50% larger than the file
-   written by a decent optimizing implementation. This library is designed
-   for source code compactness and simplicitly, not optimal image file size
-   or run-time performance.
-
-USAGE:
-
-   There are three functions, one for each image file format:
-
-     int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes);
-     int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data);
-     int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data);
-
-   Each function returns 0 on failure and non-0 on success.
-   
-   The functions create an image file defined by the parameters. The image
-   is a rectangle of pixels stored from left-to-right, top-to-bottom.
-   Each pixel contains 'comp' channels of data stored interleaved with 8-bits
-   per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is
-   monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall.
-   The *data pointer points to the first byte of the top-left-most pixel.
-   For PNG, "stride_in_bytes" is the distance in bytes from the first byte of
-   a row of pixels to the first byte of the next row of pixels.
-
-   PNG creates output files with the same number of components as the input.
-   The BMP and TGA formats expand Y to RGB in the file format. BMP does not
-   output alpha.
-   
-   PNG supports writing rectangles of data even when the bytes storing rows of
-   data are not consecutive in memory (e.g. sub-rectangles of a larger image),
-   by supplying the stride between the beginning of adjacent rows. The other
-   formats do not. (Thus you cannot write a native-format BMP through the BMP
-   writer, both because it is in BGR order and because it may have padding
-   at the end of the line.)
-*/
-
-#ifndef INCLUDE_STB_IMAGE_WRITE_H
-#define INCLUDE_STB_IMAGE_WRITE_H
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-extern int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes);
-extern int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data);
-extern int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif//INCLUDE_STB_IMAGE_WRITE_H
-
-#ifdef STB_IMAGE_WRITE_IMPLEMENTATION
-
-#include <stdarg.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <assert.h>
-
-typedef unsigned int stbiw_uint32;
-typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1];
-
-static void writefv(FILE *f, const char *fmt, va_list v)
-{
-   while (*fmt) {
-      switch (*fmt++) {
-         case ' ': break;
-         case '1': { unsigned char x = (unsigned char) va_arg(v, int); fputc(x,f); break; }
-         case '2': { int x = va_arg(v,int); unsigned char b[2];
-                     b[0] = (unsigned char) x; b[1] = (unsigned char) (x>>8);
-                     fwrite(b,2,1,f); break; }
-         case '4': { stbiw_uint32 x = va_arg(v,int); unsigned char b[4];
-                     b[0]=(unsigned char)x; b[1]=(unsigned char)(x>>8);
-                     b[2]=(unsigned char)(x>>16); b[3]=(unsigned char)(x>>24);
-                     fwrite(b,4,1,f); break; }
-         default:
-            assert(0);
-            return;
-      }
-   }
-}
-
-static void write3(FILE *f, unsigned char a, unsigned char b, unsigned char c)
-{
-   unsigned char arr[3];
-   arr[0] = a, arr[1] = b, arr[2] = c;
-   fwrite(arr, 3, 1, f);
-}
-
-static void write_pixels(FILE *f, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad)
-{
-   unsigned char bg[3] = { 255, 0, 255}, px[3];
-   stbiw_uint32 zero = 0;
-   int i,j,k, j_end;
-
-   if (y <= 0)
-      return;
-
-   if (vdir < 0) 
-      j_end = -1, j = y-1;
-   else
-      j_end =  y, j = 0;
-
-   for (; j != j_end; j += vdir) {
-      for (i=0; i < x; ++i) {
-         unsigned char *d = (unsigned char *) data + (j*x+i)*comp;
-         if (write_alpha < 0)
-            fwrite(&d[comp-1], 1, 1, f);
-         switch (comp) {
-            case 1:
-            case 2: write3(f, d[0],d[0],d[0]);
-                    break;
-            case 4:
-               if (!write_alpha) {
-                  // composite against pink background
-                  for (k=0; k < 3; ++k)
-                     px[k] = bg[k] + ((d[k] - bg[k]) * d[3])/255;
-                  write3(f, px[1-rgb_dir],px[1],px[1+rgb_dir]);
-                  break;
-               }
-               /* FALLTHROUGH */
-            case 3:
-               write3(f, d[1-rgb_dir],d[1],d[1+rgb_dir]);
-               break;
-         }
-         if (write_alpha > 0)
-            fwrite(&d[comp-1], 1, 1, f);
-      }
-      fwrite(&zero,scanline_pad,1,f);
-   }
-}
-
-static int outfile(char const *filename, int rgb_dir, int vdir, int x, int y, int comp, void *data, int alpha, int pad, const char *fmt, ...)
-{
-   FILE *f;
-   if (y < 0 || x < 0) return 0;
-   f = fopen(filename, "wb");
-   if (f) {
-      va_list v;
-      va_start(v, fmt);
-      writefv(f, fmt, v);
-      va_end(v);
-      write_pixels(f,rgb_dir,vdir,x,y,comp,data,alpha,pad);
-      fclose(f);
-   }
-   return f != NULL;
-}
-
-int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data)
-{
-   int pad = (-x*3) & 3;
-   return outfile(filename,-1,-1,x,y,comp,(void *) data,0,pad,
-           "11 4 22 4" "4 44 22 444444",
-           'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40,  // file header
-            40, x,y, 1,24, 0,0,0,0,0,0);             // bitmap header
-}
-
-int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data)
-{
-   int has_alpha = !(comp & 1);
-   return outfile(filename, -1,-1, x, y, comp, (void *) data, has_alpha, 0,
-                  "111 221 2222 11", 0,0,2, 0,0,0, 0,0,x,y, 24+8*has_alpha, 8*has_alpha);
-}
-
-// stretchy buffer; stbi__sbpush() == vector<>::push_back() -- stbi__sbcount() == vector<>::size()
-#define stbi__sbraw(a) ((int *) (a) - 2)
-#define stbi__sbm(a)   stbi__sbraw(a)[0]
-#define stbi__sbn(a)   stbi__sbraw(a)[1]
-
-#define stbi__sbneedgrow(a,n)  ((a)==0 || stbi__sbn(a)+n >= stbi__sbm(a))
-#define stbi__sbmaybegrow(a,n) (stbi__sbneedgrow(a,(n)) ? stbi__sbgrow(a,n) : 0)
-#define stbi__sbgrow(a,n)  stbi__sbgrowf((void **) &(a), (n), sizeof(*(a)))
-
-#define stbi__sbpush(a, v)      (stbi__sbmaybegrow(a,1), (a)[stbi__sbn(a)++] = (v))
-#define stbi__sbcount(a)        ((a) ? stbi__sbn(a) : 0)
-#define stbi__sbfree(a)         ((a) ? free(stbi__sbraw(a)),0 : 0)
-
-static void *stbi__sbgrowf(void **arr, int increment, int itemsize)
-{
-   int m = *arr ? 2*stbi__sbm(*arr)+increment : increment+1;
-   void *p = realloc(*arr ? stbi__sbraw(*arr) : 0, itemsize * m + sizeof(int)*2);
-   assert(p);
-   if (p) {
-      if (!*arr) ((int *) p)[1] = 0;
-      *arr = (void *) ((int *) p + 2);
-      stbi__sbm(*arr) = m;
-   }
-   return *arr;
-}
-
-static unsigned char *stbi__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount)
-{
-   while (*bitcount >= 8) {
-      stbi__sbpush(data, (unsigned char) *bitbuffer);
-      *bitbuffer >>= 8;
-      *bitcount -= 8;
-   }
-   return data;
-}
-
-static int stbi__zlib_bitrev(int code, int codebits)
-{
-   int res=0;
-   while (codebits--) {
-      res = (res << 1) | (code & 1);
-      code >>= 1;
-   }
-   return res;
-}
-
-static unsigned int stbi__zlib_countm(unsigned char *a, unsigned char *b, int limit)
-{
-   int i;
-   for (i=0; i < limit && i < 258; ++i)
-      if (a[i] != b[i]) break;
-   return i;
-}
-
-static unsigned int stbi__zhash(unsigned char *data)
-{
-   stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16);
-   hash ^= hash << 3;
-   hash += hash >> 5;
-   hash ^= hash << 4;
-   hash += hash >> 17;
-   hash ^= hash << 25;
-   hash += hash >> 6;
-   return hash;
-}
-
-#define stbi__zlib_flush() (out = stbi__zlib_flushf(out, &bitbuf, &bitcount))
-#define stbi__zlib_add(code,codebits) \
-      (bitbuf |= (code) << bitcount, bitcount += (codebits), stbi__zlib_flush())
-#define stbi__zlib_huffa(b,c)  stbi__zlib_add(stbi__zlib_bitrev(b,c),c)
-// default huffman tables
-#define stbi__zlib_huff1(n)  stbi__zlib_huffa(0x30 + (n), 8)
-#define stbi__zlib_huff2(n)  stbi__zlib_huffa(0x190 + (n)-144, 9)
-#define stbi__zlib_huff3(n)  stbi__zlib_huffa(0 + (n)-256,7)
-#define stbi__zlib_huff4(n)  stbi__zlib_huffa(0xc0 + (n)-280,8)
-#define stbi__zlib_huff(n)  ((n) <= 143 ? stbi__zlib_huff1(n) : (n) <= 255 ? stbi__zlib_huff2(n) : (n) <= 279 ? stbi__zlib_huff3(n) : stbi__zlib_huff4(n))
-#define stbi__zlib_huffb(n) ((n) <= 143 ? stbi__zlib_huff1(n) : stbi__zlib_huff2(n))
-
-#define stbi__ZHASH   16384
-
-unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality)
-{
-   static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 };
-   static unsigned char  lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4,  4,  5,  5,  5,  5,  0 };
-   static unsigned short distc[]   = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 };
-   static unsigned char  disteb[]  = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 };
-   unsigned int bitbuf=0;
-   int i,j, bitcount=0;
-   unsigned char *out = NULL;
-   unsigned char **hash_table[stbi__ZHASH]; // 64KB on the stack!
-   if (quality < 5) quality = 5;
-
-   stbi__sbpush(out, 0x78);   // DEFLATE 32K window
-   stbi__sbpush(out, 0x5e);   // FLEVEL = 1
-   stbi__zlib_add(1,1);  // BFINAL = 1
-   stbi__zlib_add(1,2);  // BTYPE = 1 -- fixed huffman
-
-   for (i=0; i < stbi__ZHASH; ++i)
-      hash_table[i] = NULL;
-
-   i=0;
-   while (i < data_len-3) {
-      // hash next 3 bytes of data to be compressed 
-      int h = stbi__zhash(data+i)&(stbi__ZHASH-1), best=3;
-      unsigned char *bestloc = 0;
-      unsigned char **hlist = hash_table[h];
-      int n = stbi__sbcount(hlist);
-      for (j=0; j < n; ++j) {
-         if (hlist[j]-data > i-32768) { // if entry lies within window
-            int d = stbi__zlib_countm(hlist[j], data+i, data_len-i);
-            if (d >= best) best=d,bestloc=hlist[j];
-         }
-      }
-      // when hash table entry is too long, delete half the entries
-      if (hash_table[h] && stbi__sbn(hash_table[h]) == 2*quality) {
-         memcpy(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality);
-         stbi__sbn(hash_table[h]) = quality;
-      }
-      stbi__sbpush(hash_table[h],data+i);
-
-      if (bestloc) {
-         // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal
-         h = stbi__zhash(data+i+1)&(stbi__ZHASH-1);
-         hlist = hash_table[h];
-         n = stbi__sbcount(hlist);
-         for (j=0; j < n; ++j) {
-            if (hlist[j]-data > i-32767) {
-               int e = stbi__zlib_countm(hlist[j], data+i+1, data_len-i-1);
-               if (e > best) { // if next match is better, bail on current match
-                  bestloc = NULL;
-                  break;
-               }
-            }
-         }
-      }
-
-      if (bestloc) {
-         int d = data+i - bestloc; // distance back
-         assert(d <= 32767 && best <= 258);
-         for (j=0; best > lengthc[j+1]-1; ++j);
-         stbi__zlib_huff(j+257);
-         if (lengtheb[j]) stbi__zlib_add(best - lengthc[j], lengtheb[j]);
-         for (j=0; d > distc[j+1]-1; ++j);
-         stbi__zlib_add(stbi__zlib_bitrev(j,5),5);
-         if (disteb[j]) stbi__zlib_add(d - distc[j], disteb[j]);
-         i += best;
-      } else {
-         stbi__zlib_huffb(data[i]);
-         ++i;
-      }
-   }
-   // write out final bytes
-   for (;i < data_len; ++i)
-      stbi__zlib_huffb(data[i]);
-   stbi__zlib_huff(256); // end of block
-   // pad with 0 bits to byte boundary
-   while (bitcount)
-      stbi__zlib_add(0,1);
-
-   for (i=0; i < stbi__ZHASH; ++i)
-      (void) stbi__sbfree(hash_table[i]);
-
-   {
-      // compute adler32 on input
-      unsigned int i=0, s1=1, s2=0, blocklen = data_len % 5552;
-      int j=0;
-      while (j < data_len) {
-         for (i=0; i < blocklen; ++i) s1 += data[j+i], s2 += s1;
-         s1 %= 65521, s2 %= 65521;
-         j += blocklen;
-         blocklen = 5552;
-      }
-      stbi__sbpush(out, (unsigned char) (s2 >> 8));
-      stbi__sbpush(out, (unsigned char) s2);
-      stbi__sbpush(out, (unsigned char) (s1 >> 8));
-      stbi__sbpush(out, (unsigned char) s1);
-   }
-   *out_len = stbi__sbn(out);
-   // make returned pointer freeable
-   memmove(stbi__sbraw(out), out, *out_len);
-   return (unsigned char *) stbi__sbraw(out);
-}
-
-unsigned int stbi__crc32(unsigned char *buffer, int len)
-{
-   static unsigned int crc_table[256];
-   unsigned int crc = ~0u;
-   int i,j;
-   if (crc_table[1] == 0)
-      for(i=0; i < 256; i++)
-         for (crc_table[i]=i, j=0; j < 8; ++j)
-            crc_table[i] = (crc_table[i] >> 1) ^ (crc_table[i] & 1 ? 0xedb88320 : 0);
-   for (i=0; i < len; ++i)
-      crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)];
-   return ~crc;
-}
-
-#define stbi__wpng4(o,a,b,c,d) ((o)[0]=(unsigned char)(a),(o)[1]=(unsigned char)(b),(o)[2]=(unsigned char)(c),(o)[3]=(unsigned char)(d),(o)+=4)
-#define stbi__wp32(data,v) stbi__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v));
-#define stbi__wptag(data,s) stbi__wpng4(data, s[0],s[1],s[2],s[3])
-
-static void stbi__wpcrc(unsigned char **data, int len)
-{
-   unsigned int crc = stbi__crc32(*data - len - 4, len+4);
-   stbi__wp32(*data, crc);
-}
-
-static unsigned char stbi__paeth(int a, int b, int c)
-{
-   int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c);
-   if (pa <= pb && pa <= pc) return (unsigned char) a;
-   if (pb <= pc) return (unsigned char) b;
-   return (unsigned char) c;
-}
-
-unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len)
-{
-   int ctype[5] = { -1, 0, 4, 2, 6 };
-   unsigned char sig[8] = { 137,80,78,71,13,10,26,10 };
-   unsigned char *out,*o, *filt, *zlib;
-   signed char *line_buffer;
-   int i,j,k,p,zlen;
-
-   if (stride_bytes == 0)
-      stride_bytes = x * n;
-
-   filt = (unsigned char *) malloc((x*n+1) * y); if (!filt) return 0;
-   line_buffer = (signed char *) malloc(x * n); if (!line_buffer) { free(filt); return 0; }
-   for (j=0; j < y; ++j) {
-      static int mapping[] = { 0,1,2,3,4 };
-      static int firstmap[] = { 0,1,0,5,6 };
-      int *mymap = j ? mapping : firstmap;
-      int best = 0, bestval = 0x7fffffff;
-      for (p=0; p < 2; ++p) {
-         for (k= p?best:0; k < 5; ++k) {
-            int type = mymap[k],est=0;
-            unsigned char *z = pixels + stride_bytes*j;
-            for (i=0; i < n; ++i)
-               switch (type) {
-                  case 0: line_buffer[i] = z[i]; break;
-                  case 1: line_buffer[i] = z[i]; break;
-                  case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break;
-                  case 3: line_buffer[i] = z[i] - (z[i-stride_bytes]>>1); break;
-                  case 4: line_buffer[i] = (signed char) (z[i] - stbi__paeth(0,z[i-stride_bytes],0)); break;
-                  case 5: line_buffer[i] = z[i]; break;
-                  case 6: line_buffer[i] = z[i]; break;
-               }
-            for (i=n; i < x*n; ++i) {
-               switch (type) {
-                  case 0: line_buffer[i] = z[i]; break;
-                  case 1: line_buffer[i] = z[i] - z[i-n]; break;
-                  case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break;
-                  case 3: line_buffer[i] = z[i] - ((z[i-n] + z[i-stride_bytes])>>1); break;
-                  case 4: line_buffer[i] = z[i] - stbi__paeth(z[i-n], z[i-stride_bytes], z[i-stride_bytes-n]); break;
-                  case 5: line_buffer[i] = z[i] - (z[i-n]>>1); break;
-                  case 6: line_buffer[i] = z[i] - stbi__paeth(z[i-n], 0,0); break;
-               }
-            }
-            if (p) break;
-            for (i=0; i < x*n; ++i)
-               est += abs((signed char) line_buffer[i]);
-            if (est < bestval) { bestval = est; best = k; }
-         }
-      }
-      // when we get here, best contains the filter type, and line_buffer contains the data
-      filt[j*(x*n+1)] = (unsigned char) best;
-      memcpy(filt+j*(x*n+1)+1, line_buffer, x*n);
-   }
-   free(line_buffer);
-   zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, 8); // increase 8 to get smaller but use more memory
-   free(filt);
-   if (!zlib) return 0;
-
-   // each tag requires 12 bytes of overhead
-   out = (unsigned char *) malloc(8 + 12+13 + 12+zlen + 12); 
-   if (!out) return 0;
-   *out_len = 8 + 12+13 + 12+zlen + 12;
-
-   o=out;
-   memcpy(o,sig,8); o+= 8;
-   stbi__wp32(o, 13); // header length
-   stbi__wptag(o, "IHDR");
-   stbi__wp32(o, x);
-   stbi__wp32(o, y);
-   *o++ = 8;
-   *o++ = (unsigned char) ctype[n];
-   *o++ = 0;
-   *o++ = 0;
-   *o++ = 0;
-   stbi__wpcrc(&o,13);
-
-   stbi__wp32(o, zlen);
-   stbi__wptag(o, "IDAT");
-   memcpy(o, zlib, zlen); o += zlen; free(zlib);
-   stbi__wpcrc(&o, zlen);
-
-   stbi__wp32(o,0);
-   stbi__wptag(o, "IEND");
-   stbi__wpcrc(&o,0);
-
-   assert(o == out + *out_len);
-
-   return out;
-}
-
-int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes)
-{
-   FILE *f;
-   int len;
-   unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len);
-   if (!png) return 0;
-   f = fopen(filename, "wb");
-   if (!f) { free(png); return 0; }
-   fwrite(png, 1, len, f);
-   fclose(f);
-   free(png);
-   return 1;
-}
-#endif // STB_IMAGE_WRITE_IMPLEMENTATION
-
-/* Revision history
-
-      0.92 (2010-08-01)
-             casts to unsigned char to fix warnings
-      0.91 (2010-07-17)
-             first public release
-      0.90   first internal release
-*/

+ 0 - 56
svg.mod/nanosvg/premake4.lua

@@ -1,56 +0,0 @@
-
-local action = _ACTION or ""
-
-solution "nanosvg"
-	location ( "build" )
-	configurations { "Debug", "Release" }
-	platforms {"native", "x64", "x32"}
-  
-	project "example1"
-		kind "ConsoleApp"
-		language "C++"
-		files { "example/example1.c", "example/*.h", "src/*.h" }
-		includedirs { "example", "src" }
-		targetdir("build")
-	 
-		configuration { "linux" }
-			 links { "X11","Xrandr", "rt", "GL", "GLU", "pthread", "glfw" }
-
-		configuration { "windows" }
-			 links { "glu32","opengl32", "gdi32", "winmm", "user32" }
-
-		configuration { "macosx" }
-			links { "glfw3" }
-			linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit", "-framework CoreVideo" }
-
-		configuration "Debug"
-			defines { "DEBUG" }
-			flags { "Symbols", "ExtraWarnings"}
-
-		configuration "Release"
-			defines { "NDEBUG" }
-			flags { "Optimize", "ExtraWarnings"}    
-
-	project "example2"
-		kind "ConsoleApp"
-		language "C++"
-		files { "example/example2.c", "example/*.h", "src/*.h" }
-		includedirs { "example", "src" }
-		targetdir("build")
-	 
-		configuration { "linux" }
-			 links { "X11","Xrandr", "rt", "pthread" }
-
-		configuration { "windows" }
-			 links { "winmm", "user32" }
-
-		configuration { "macosx" }
-			linkoptions { "-framework Cocoa", "-framework IOKit" }
-
-		configuration "Debug"
-			defines { "DEBUG" }
-			flags { "Symbols", "ExtraWarnings"}
-
-		configuration "Release"
-			defines { "NDEBUG" }
-			flags { "Optimize", "ExtraWarnings"}    

+ 0 - 3098
svg.mod/nanosvg/src/nanosvg.h

@@ -1,3098 +0,0 @@
-/*
- * Copyright (c) 2013-14 Mikko Mononen [email protected]
- *
- * This software is provided 'as-is', without any express or implied
- * warranty.  In no event will the authors be held liable for any damages
- * arising from the use of this software.
- *
- * Permission is granted to anyone to use this software for any purpose,
- * including commercial applications, and to alter it and redistribute it
- * freely, subject to the following restrictions:
- *
- * 1. The origin of this software must not be misrepresented; you must not
- * claim that you wrote the original software. If you use this software
- * in a product, an acknowledgment in the product documentation would be
- * appreciated but is not required.
- * 2. Altered source versions must be plainly marked as such, and must not be
- * misrepresented as being the original software.
- * 3. This notice may not be removed or altered from any source distribution.
- *
- * The SVG parser is based on Anti-Grain Geometry 2.4 SVG example
- * Copyright (C) 2002-2004 Maxim Shemanarev (McSeem) (http://www.antigrain.com/)
- *
- * Arc calculation code based on canvg (https://code.google.com/p/canvg/)
- *
- * Bounding box calculation based on http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
- *
- */
-
-#ifndef NANOSVG_H
-#define NANOSVG_H
-
-#ifndef NANOSVG_CPLUSPLUS
-#ifdef __cplusplus
-extern "C" {
-#endif
-#endif
-
-// NanoSVG is a simple stupid single-header-file SVG parse. The output of the parser is a list of cubic bezier shapes.
-//
-// The library suits well for anything from rendering scalable icons in your editor application to prototyping a game.
-//
-// NanoSVG supports a wide range of SVG features, but something may be missing, feel free to create a pull request!
-//
-// The shapes in the SVG images are transformed by the viewBox and converted to specified units.
-// That is, you should get the same looking data as your designed in your favorite app.
-//
-// NanoSVG can return the paths in few different units. For example if you want to render an image, you may choose
-// to get the paths in pixels, or if you are feeding the data into a CNC-cutter, you may want to use millimeters.
-//
-// The units passed to NanoSVG should be one of: 'px', 'pt', 'pc' 'mm', 'cm', or 'in'.
-// DPI (dots-per-inch) controls how the unit conversion is done.
-//
-// If you don't know or care about the units stuff, "px" and 96 should get you going.
-
-
-/* Example Usage:
-	// Load SVG
-	NSVGimage* image;
-	image = nsvgParseFromFile("test.svg", "px", 96);
-	printf("size: %f x %f\n", image->width, image->height);
-	// Use...
-	for (NSVGshape *shape = image->shapes; shape != NULL; shape = shape->next) {
-		for (NSVGpath *path = shape->paths; path != NULL; path = path->next) {
-			for (int i = 0; i < path->npts-1; i += 3) {
-				float* p = &path->pts[i*2];
-				drawCubicBez(p[0],p[1], p[2],p[3], p[4],p[5], p[6],p[7]);
-			}
-		}
-	}
-	// Delete
-	nsvgDelete(image);
-*/
-
-enum NSVGpaintType {
-	NSVG_PAINT_UNDEF = -1,
-	NSVG_PAINT_NONE = 0,
-	NSVG_PAINT_COLOR = 1,
-	NSVG_PAINT_LINEAR_GRADIENT = 2,
-	NSVG_PAINT_RADIAL_GRADIENT = 3
-};
-
-enum NSVGspreadType {
-	NSVG_SPREAD_PAD = 0,
-	NSVG_SPREAD_REFLECT = 1,
-	NSVG_SPREAD_REPEAT = 2
-};
-
-enum NSVGlineJoin {
-	NSVG_JOIN_MITER = 0,
-	NSVG_JOIN_ROUND = 1,
-	NSVG_JOIN_BEVEL = 2
-};
-
-enum NSVGlineCap {
-	NSVG_CAP_BUTT = 0,
-	NSVG_CAP_ROUND = 1,
-	NSVG_CAP_SQUARE = 2
-};
-
-enum NSVGfillRule {
-	NSVG_FILLRULE_NONZERO = 0,
-	NSVG_FILLRULE_EVENODD = 1
-};
-
-enum NSVGflags {
-	NSVG_FLAGS_VISIBLE = 0x01
-};
-
-typedef struct NSVGgradientStop {
-	unsigned int color;
-	float offset;
-} NSVGgradientStop;
-
-typedef struct NSVGgradient {
-	float xform[6];
-	char spread;
-	float fx, fy;
-	int nstops;
-	NSVGgradientStop stops[1];
-} NSVGgradient;
-
-typedef struct NSVGpaint {
-	signed char type;
-	union {
-		unsigned int color;
-		NSVGgradient* gradient;
-	};
-} NSVGpaint;
-
-typedef struct NSVGpath
-{
-	float* pts;					// Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ...
-	int npts;					// Total number of bezier points.
-	char closed;				// Flag indicating if shapes should be treated as closed.
-	float bounds[4];			// Tight bounding box of the shape [minx,miny,maxx,maxy].
-	struct NSVGpath* next;		// Pointer to next path, or NULL if last element.
-} NSVGpath;
-
-typedef struct NSVGshape
-{
-	char id[64];				// Optional 'id' attr of the shape or its group
-	NSVGpaint fill;				// Fill paint
-	NSVGpaint stroke;			// Stroke paint
-	float opacity;				// Opacity of the shape.
-	float strokeWidth;			// Stroke width (scaled).
-	float strokeDashOffset;		// Stroke dash offset (scaled).
-	float strokeDashArray[8];	// Stroke dash array (scaled).
-	char strokeDashCount;		// Number of dash values in dash array.
-	char strokeLineJoin;		// Stroke join type.
-	char strokeLineCap;			// Stroke cap type.
-	float miterLimit;			// Miter limit
-	char fillRule;				// Fill rule, see NSVGfillRule.
-	unsigned char flags;		// Logical or of NSVG_FLAGS_* flags
-	float bounds[4];			// Tight bounding box of the shape [minx,miny,maxx,maxy].
-	char fillGradient[64];		// Optional 'id' of fill gradient
-	char strokeGradient[64];	// Optional 'id' of stroke gradient
-	float xform[6];				// Root transformation for fill/stroke gradient
-	NSVGpath* paths;			// Linked list of paths in the image.
-	struct NSVGshape* next;		// Pointer to next shape, or NULL if last element.
-} NSVGshape;
-
-typedef struct NSVGimage
-{
-	float width;				// Width of the image.
-	float height;				// Height of the image.
-	NSVGshape* shapes;			// Linked list of shapes in the image.
-} NSVGimage;
-
-// Parses SVG file from a file, returns SVG image as paths.
-NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi);
-
-// Parses SVG file from a null terminated string, returns SVG image as paths.
-// Important note: changes the string.
-NSVGimage* nsvgParse(char* input, const char* units, float dpi);
-
-// Duplicates a path.
-NSVGpath* nsvgDuplicatePath(NSVGpath* p);
-
-// Deletes an image.
-void nsvgDelete(NSVGimage* image);
-
-#ifndef NANOSVG_CPLUSPLUS
-#ifdef __cplusplus
-}
-#endif
-#endif
-
-#ifdef NANOSVG_IMPLEMENTATION
-
-#include <string.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <math.h>
-
-#define NSVG_PI (3.14159265358979323846264338327f)
-#define NSVG_KAPPA90 (0.5522847493f)	// Length proportional to radius of a cubic bezier handle for 90deg arcs.
-
-#define NSVG_ALIGN_MIN 0
-#define NSVG_ALIGN_MID 1
-#define NSVG_ALIGN_MAX 2
-#define NSVG_ALIGN_NONE 0
-#define NSVG_ALIGN_MEET 1
-#define NSVG_ALIGN_SLICE 2
-
-#define NSVG_NOTUSED(v) do { (void)(1 ? (void)0 : ( (void)(v) ) ); } while(0)
-#define NSVG_RGB(r, g, b) (((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16))
-
-#ifdef _MSC_VER
-	#pragma warning (disable: 4996) // Switch off security warnings
-	#pragma warning (disable: 4100) // Switch off unreferenced formal parameter warnings
-	#ifdef __cplusplus
-	#define NSVG_INLINE inline
-	#else
-	#define NSVG_INLINE
-	#endif
-#else
-	#define NSVG_INLINE inline
-#endif
-
-
-static int nsvg__isspace(char c)
-{
-	return strchr(" \t\n\v\f\r", c) != 0;
-}
-
-static int nsvg__isdigit(char c)
-{
-	return c >= '0' && c <= '9';
-}
-
-static NSVG_INLINE float nsvg__minf(float a, float b) { return a < b ? a : b; }
-static NSVG_INLINE float nsvg__maxf(float a, float b) { return a > b ? a : b; }
-
-
-// Simple XML parser
-
-#define NSVG_XML_TAG 1
-#define NSVG_XML_CONTENT 2
-#define NSVG_XML_MAX_ATTRIBS 256
-
-static void nsvg__parseContent(char* s,
-							   void (*contentCb)(void* ud, const char* s),
-							   void* ud)
-{
-	// Trim start white spaces
-	while (*s && nsvg__isspace(*s)) s++;
-	if (!*s) return;
-
-	if (contentCb)
-		(*contentCb)(ud, s);
-}
-
-static void nsvg__parseElement(char* s,
-							   void (*startelCb)(void* ud, const char* el, const char** attr),
-							   void (*endelCb)(void* ud, const char* el),
-							   void* ud)
-{
-	const char* attr[NSVG_XML_MAX_ATTRIBS];
-	int nattr = 0;
-	char* name;
-	int start = 0;
-	int end = 0;
-	char quote;
-
-	// Skip white space after the '<'
-	while (*s && nsvg__isspace(*s)) s++;
-
-	// Check if the tag is end tag
-	if (*s == '/') {
-		s++;
-		end = 1;
-	} else {
-		start = 1;
-	}
-
-	// Skip comments, data and preprocessor stuff.
-	if (!*s || *s == '?' || *s == '!')
-		return;
-
-	// Get tag name
-	name = s;
-	while (*s && !nsvg__isspace(*s)) s++;
-	if (*s) { *s++ = '\0'; }
-
-	// Get attribs
-	while (!end && *s && nattr < NSVG_XML_MAX_ATTRIBS-3) {
-		char* name = NULL;
-		char* value = NULL;
-
-		// Skip white space before the attrib name
-		while (*s && nsvg__isspace(*s)) s++;
-		if (!*s) break;
-		if (*s == '/') {
-			end = 1;
-			break;
-		}
-		name = s;
-		// Find end of the attrib name.
-		while (*s && !nsvg__isspace(*s) && *s != '=') s++;
-		if (*s) { *s++ = '\0'; }
-		// Skip until the beginning of the value.
-		while (*s && *s != '\"' && *s != '\'') s++;
-		if (!*s) break;
-		quote = *s;
-		s++;
-		// Store value and find the end of it.
-		value = s;
-		while (*s && *s != quote) s++;
-		if (*s) { *s++ = '\0'; }
-
-		// Store only well formed attributes
-		if (name && value) {
-			attr[nattr++] = name;
-			attr[nattr++] = value;
-		}
-	}
-
-	// List terminator
-	attr[nattr++] = 0;
-	attr[nattr++] = 0;
-
-	// Call callbacks.
-	if (start && startelCb)
-		(*startelCb)(ud, name, attr);
-	if (end && endelCb)
-		(*endelCb)(ud, name);
-}
-
-int nsvg__parseXML(char* input,
-				   void (*startelCb)(void* ud, const char* el, const char** attr),
-				   void (*endelCb)(void* ud, const char* el),
-				   void (*contentCb)(void* ud, const char* s),
-				   void* ud)
-{
-	char* s = input;
-	char* mark = s;
-	int state = NSVG_XML_CONTENT;
-	while (*s) {
-		if (*s == '<' && state == NSVG_XML_CONTENT) {
-			// Start of a tag
-			*s++ = '\0';
-			nsvg__parseContent(mark, contentCb, ud);
-			mark = s;
-			state = NSVG_XML_TAG;
-		} else if (*s == '>' && state == NSVG_XML_TAG) {
-			// Start of a content or new tag.
-			*s++ = '\0';
-			nsvg__parseElement(mark, startelCb, endelCb, ud);
-			mark = s;
-			state = NSVG_XML_CONTENT;
-		} else {
-			s++;
-		}
-	}
-
-	return 1;
-}
-
-
-/* Simple SVG parser. */
-
-#define NSVG_MAX_ATTR 128
-
-enum NSVGgradientUnits {
-	NSVG_USER_SPACE = 0,
-	NSVG_OBJECT_SPACE = 1
-};
-
-#define NSVG_MAX_DASHES 8
-
-enum NSVGunits {
-	NSVG_UNITS_USER,
-	NSVG_UNITS_PX,
-	NSVG_UNITS_PT,
-	NSVG_UNITS_PC,
-	NSVG_UNITS_MM,
-	NSVG_UNITS_CM,
-	NSVG_UNITS_IN,
-	NSVG_UNITS_PERCENT,
-	NSVG_UNITS_EM,
-	NSVG_UNITS_EX
-};
-
-typedef struct NSVGcoordinate {
-	float value;
-	int units;
-} NSVGcoordinate;
-
-typedef struct NSVGlinearData {
-	NSVGcoordinate x1, y1, x2, y2;
-} NSVGlinearData;
-
-typedef struct NSVGradialData {
-	NSVGcoordinate cx, cy, r, fx, fy;
-} NSVGradialData;
-
-typedef struct NSVGgradientData
-{
-	char id[64];
-	char ref[64];
-	signed char type;
-	union {
-		NSVGlinearData linear;
-		NSVGradialData radial;
-	};
-	char spread;
-	char units;
-	float xform[6];
-	int nstops;
-	NSVGgradientStop* stops;
-	struct NSVGgradientData* next;
-} NSVGgradientData;
-
-typedef struct NSVGattrib
-{
-	char id[64];
-	float xform[6];
-	unsigned int fillColor;
-	unsigned int strokeColor;
-	float opacity;
-	float fillOpacity;
-	float strokeOpacity;
-	char fillGradient[64];
-	char strokeGradient[64];
-	float strokeWidth;
-	float strokeDashOffset;
-	float strokeDashArray[NSVG_MAX_DASHES];
-	int strokeDashCount;
-	char strokeLineJoin;
-	char strokeLineCap;
-	float miterLimit;
-	char fillRule;
-	float fontSize;
-	unsigned int stopColor;
-	float stopOpacity;
-	float stopOffset;
-	char hasFill;
-	char hasStroke;
-	char visible;
-} NSVGattrib;
-
-typedef struct NSVGparser
-{
-	NSVGattrib attr[NSVG_MAX_ATTR];
-	int attrHead;
-	float* pts;
-	int npts;
-	int cpts;
-	NSVGpath* plist;
-	NSVGimage* image;
-	NSVGgradientData* gradients;
-	NSVGshape* shapesTail;
-	float viewMinx, viewMiny, viewWidth, viewHeight;
-	int alignX, alignY, alignType;
-	float dpi;
-	char pathFlag;
-	char defsFlag;
-} NSVGparser;
-
-static void nsvg__xformIdentity(float* t)
-{
-	t[0] = 1.0f; t[1] = 0.0f;
-	t[2] = 0.0f; t[3] = 1.0f;
-	t[4] = 0.0f; t[5] = 0.0f;
-}
-
-static void nsvg__xformSetTranslation(float* t, float tx, float ty)
-{
-	t[0] = 1.0f; t[1] = 0.0f;
-	t[2] = 0.0f; t[3] = 1.0f;
-	t[4] = tx; t[5] = ty;
-}
-
-static void nsvg__xformSetScale(float* t, float sx, float sy)
-{
-	t[0] = sx; t[1] = 0.0f;
-	t[2] = 0.0f; t[3] = sy;
-	t[4] = 0.0f; t[5] = 0.0f;
-}
-
-static void nsvg__xformSetSkewX(float* t, float a)
-{
-	t[0] = 1.0f; t[1] = 0.0f;
-	t[2] = tanf(a); t[3] = 1.0f;
-	t[4] = 0.0f; t[5] = 0.0f;
-}
-
-static void nsvg__xformSetSkewY(float* t, float a)
-{
-	t[0] = 1.0f; t[1] = tanf(a);
-	t[2] = 0.0f; t[3] = 1.0f;
-	t[4] = 0.0f; t[5] = 0.0f;
-}
-
-static void nsvg__xformSetRotation(float* t, float a)
-{
-	float cs = cosf(a), sn = sinf(a);
-	t[0] = cs; t[1] = sn;
-	t[2] = -sn; t[3] = cs;
-	t[4] = 0.0f; t[5] = 0.0f;
-}
-
-static void nsvg__xformMultiply(float* t, float* s)
-{
-	float t0 = t[0] * s[0] + t[1] * s[2];
-	float t2 = t[2] * s[0] + t[3] * s[2];
-	float t4 = t[4] * s[0] + t[5] * s[2] + s[4];
-	t[1] = t[0] * s[1] + t[1] * s[3];
-	t[3] = t[2] * s[1] + t[3] * s[3];
-	t[5] = t[4] * s[1] + t[5] * s[3] + s[5];
-	t[0] = t0;
-	t[2] = t2;
-	t[4] = t4;
-}
-
-static void nsvg__xformInverse(float* inv, float* t)
-{
-	double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1];
-	if (det > -1e-6 && det < 1e-6) {
-		nsvg__xformIdentity(t);
-		return;
-	}
-	invdet = 1.0 / det;
-	inv[0] = (float)(t[3] * invdet);
-	inv[2] = (float)(-t[2] * invdet);
-	inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet);
-	inv[1] = (float)(-t[1] * invdet);
-	inv[3] = (float)(t[0] * invdet);
-	inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet);
-}
-
-static void nsvg__xformPremultiply(float* t, float* s)
-{
-	float s2[6];
-	memcpy(s2, s, sizeof(float)*6);
-	nsvg__xformMultiply(s2, t);
-	memcpy(t, s2, sizeof(float)*6);
-}
-
-static void nsvg__xformPoint(float* dx, float* dy, float x, float y, float* t)
-{
-	*dx = x*t[0] + y*t[2] + t[4];
-	*dy = x*t[1] + y*t[3] + t[5];
-}
-
-static void nsvg__xformVec(float* dx, float* dy, float x, float y, float* t)
-{
-	*dx = x*t[0] + y*t[2];
-	*dy = x*t[1] + y*t[3];
-}
-
-#define NSVG_EPSILON (1e-12)
-
-static int nsvg__ptInBounds(float* pt, float* bounds)
-{
-	return pt[0] >= bounds[0] && pt[0] <= bounds[2] && pt[1] >= bounds[1] && pt[1] <= bounds[3];
-}
-
-
-static double nsvg__evalBezier(double t, double p0, double p1, double p2, double p3)
-{
-	double it = 1.0-t;
-	return it*it*it*p0 + 3.0*it*it*t*p1 + 3.0*it*t*t*p2 + t*t*t*p3;
-}
-
-static void nsvg__curveBounds(float* bounds, float* curve)
-{
-	int i, j, count;
-	double roots[2], a, b, c, b2ac, t, v;
-	float* v0 = &curve[0];
-	float* v1 = &curve[2];
-	float* v2 = &curve[4];
-	float* v3 = &curve[6];
-
-	// Start the bounding box by end points
-	bounds[0] = nsvg__minf(v0[0], v3[0]);
-	bounds[1] = nsvg__minf(v0[1], v3[1]);
-	bounds[2] = nsvg__maxf(v0[0], v3[0]);
-	bounds[3] = nsvg__maxf(v0[1], v3[1]);
-
-	// Bezier curve fits inside the convex hull of it's control points.
-	// If control points are inside the bounds, we're done.
-	if (nsvg__ptInBounds(v1, bounds) && nsvg__ptInBounds(v2, bounds))
-		return;
-
-	// Add bezier curve inflection points in X and Y.
-	for (i = 0; i < 2; i++) {
-		a = -3.0 * v0[i] + 9.0 * v1[i] - 9.0 * v2[i] + 3.0 * v3[i];
-		b = 6.0 * v0[i] - 12.0 * v1[i] + 6.0 * v2[i];
-		c = 3.0 * v1[i] - 3.0 * v0[i];
-		count = 0;
-		if (fabs(a) < NSVG_EPSILON) {
-			if (fabs(b) > NSVG_EPSILON) {
-				t = -c / b;
-				if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON)
-					roots[count++] = t;
-			}
-		} else {
-			b2ac = b*b - 4.0*c*a;
-			if (b2ac > NSVG_EPSILON) {
-				t = (-b + sqrt(b2ac)) / (2.0 * a);
-				if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON)
-					roots[count++] = t;
-				t = (-b - sqrt(b2ac)) / (2.0 * a);
-				if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON)
-					roots[count++] = t;
-			}
-		}
-		for (j = 0; j < count; j++) {
-			v = nsvg__evalBezier(roots[j], v0[i], v1[i], v2[i], v3[i]);
-			bounds[0+i] = nsvg__minf(bounds[0+i], (float)v);
-			bounds[2+i] = nsvg__maxf(bounds[2+i], (float)v);
-		}
-	}
-}
-
-static NSVGparser* nsvg__createParser(void)
-{
-	NSVGparser* p;
-	p = (NSVGparser*)malloc(sizeof(NSVGparser));
-	if (p == NULL) goto error;
-	memset(p, 0, sizeof(NSVGparser));
-
-	p->image = (NSVGimage*)malloc(sizeof(NSVGimage));
-	if (p->image == NULL) goto error;
-	memset(p->image, 0, sizeof(NSVGimage));
-
-	// Init style
-	nsvg__xformIdentity(p->attr[0].xform);
-	memset(p->attr[0].id, 0, sizeof p->attr[0].id);
-	p->attr[0].fillColor = NSVG_RGB(0,0,0);
-	p->attr[0].strokeColor = NSVG_RGB(0,0,0);
-	p->attr[0].opacity = 1;
-	p->attr[0].fillOpacity = 1;
-	p->attr[0].strokeOpacity = 1;
-	p->attr[0].stopOpacity = 1;
-	p->attr[0].strokeWidth = 1;
-	p->attr[0].strokeLineJoin = NSVG_JOIN_MITER;
-	p->attr[0].strokeLineCap = NSVG_CAP_BUTT;
-	p->attr[0].miterLimit = 4;
-	p->attr[0].fillRule = NSVG_FILLRULE_NONZERO;
-	p->attr[0].hasFill = 1;
-	p->attr[0].visible = 1;
-
-	return p;
-
-error:
-	if (p) {
-		if (p->image) free(p->image);
-		free(p);
-	}
-	return NULL;
-}
-
-static void nsvg__deletePaths(NSVGpath* path)
-{
-	while (path) {
-		NSVGpath *next = path->next;
-		if (path->pts != NULL)
-			free(path->pts);
-		free(path);
-		path = next;
-	}
-}
-
-static void nsvg__deletePaint(NSVGpaint* paint)
-{
-	if (paint->type == NSVG_PAINT_LINEAR_GRADIENT || paint->type == NSVG_PAINT_RADIAL_GRADIENT)
-		free(paint->gradient);
-}
-
-static void nsvg__deleteGradientData(NSVGgradientData* grad)
-{
-	NSVGgradientData* next;
-	while (grad != NULL) {
-		next = grad->next;
-		free(grad->stops);
-		free(grad);
-		grad = next;
-	}
-}
-
-static void nsvg__deleteParser(NSVGparser* p)
-{
-	if (p != NULL) {
-		nsvg__deletePaths(p->plist);
-		nsvg__deleteGradientData(p->gradients);
-		nsvgDelete(p->image);
-		free(p->pts);
-		free(p);
-	}
-}
-
-static void nsvg__resetPath(NSVGparser* p)
-{
-	p->npts = 0;
-}
-
-static void nsvg__addPoint(NSVGparser* p, float x, float y)
-{
-	if (p->npts+1 > p->cpts) {
-		p->cpts = p->cpts ? p->cpts*2 : 8;
-		p->pts = (float*)realloc(p->pts, p->cpts*2*sizeof(float));
-		if (!p->pts) return;
-	}
-	p->pts[p->npts*2+0] = x;
-	p->pts[p->npts*2+1] = y;
-	p->npts++;
-}
-
-static void nsvg__moveTo(NSVGparser* p, float x, float y)
-{
-	if (p->npts > 0) {
-		p->pts[(p->npts-1)*2+0] = x;
-		p->pts[(p->npts-1)*2+1] = y;
-	} else {
-		nsvg__addPoint(p, x, y);
-	}
-}
-
-static void nsvg__lineTo(NSVGparser* p, float x, float y)
-{
-	float px,py, dx,dy;
-	if (p->npts > 0) {
-		px = p->pts[(p->npts-1)*2+0];
-		py = p->pts[(p->npts-1)*2+1];
-		dx = x - px;
-		dy = y - py;
-		nsvg__addPoint(p, px + dx/3.0f, py + dy/3.0f);
-		nsvg__addPoint(p, x - dx/3.0f, y - dy/3.0f);
-		nsvg__addPoint(p, x, y);
-	}
-}
-
-static void nsvg__cubicBezTo(NSVGparser* p, float cpx1, float cpy1, float cpx2, float cpy2, float x, float y)
-{
-	if (p->npts > 0) {
-		nsvg__addPoint(p, cpx1, cpy1);
-		nsvg__addPoint(p, cpx2, cpy2);
-		nsvg__addPoint(p, x, y);
-	}
-}
-
-static NSVGattrib* nsvg__getAttr(NSVGparser* p)
-{
-	return &p->attr[p->attrHead];
-}
-
-static void nsvg__pushAttr(NSVGparser* p)
-{
-	if (p->attrHead < NSVG_MAX_ATTR-1) {
-		p->attrHead++;
-		memcpy(&p->attr[p->attrHead], &p->attr[p->attrHead-1], sizeof(NSVGattrib));
-	}
-}
-
-static void nsvg__popAttr(NSVGparser* p)
-{
-	if (p->attrHead > 0)
-		p->attrHead--;
-}
-
-static float nsvg__actualOrigX(NSVGparser* p)
-{
-	return p->viewMinx;
-}
-
-static float nsvg__actualOrigY(NSVGparser* p)
-{
-	return p->viewMiny;
-}
-
-static float nsvg__actualWidth(NSVGparser* p)
-{
-	return p->viewWidth;
-}
-
-static float nsvg__actualHeight(NSVGparser* p)
-{
-	return p->viewHeight;
-}
-
-static float nsvg__actualLength(NSVGparser* p)
-{
-	float w = nsvg__actualWidth(p), h = nsvg__actualHeight(p);
-	return sqrtf(w*w + h*h) / sqrtf(2.0f);
-}
-
-static float nsvg__convertToPixels(NSVGparser* p, NSVGcoordinate c, float orig, float length)
-{
-	NSVGattrib* attr = nsvg__getAttr(p);
-	switch (c.units) {
-		case NSVG_UNITS_USER:		return c.value;
-		case NSVG_UNITS_PX:			return c.value;
-		case NSVG_UNITS_PT:			return c.value / 72.0f * p->dpi;
-		case NSVG_UNITS_PC:			return c.value / 6.0f * p->dpi;
-		case NSVG_UNITS_MM:			return c.value / 25.4f * p->dpi;
-		case NSVG_UNITS_CM:			return c.value / 2.54f * p->dpi;
-		case NSVG_UNITS_IN:			return c.value * p->dpi;
-		case NSVG_UNITS_EM:			return c.value * attr->fontSize;
-		case NSVG_UNITS_EX:			return c.value * attr->fontSize * 0.52f; // x-height of Helvetica.
-		case NSVG_UNITS_PERCENT:	return orig + c.value / 100.0f * length;
-		default:					return c.value;
-	}
-	return c.value;
-}
-
-static NSVGgradientData* nsvg__findGradientData(NSVGparser* p, const char* id)
-{
-	NSVGgradientData* grad = p->gradients;
-	if (id == NULL || *id == '\0')
-		return NULL;
-	while (grad != NULL) {
-		if (strcmp(grad->id, id) == 0)
-			return grad;
-		grad = grad->next;
-	}
-	return NULL;
-}
-
-static NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const float* localBounds, float *xform, signed char* paintType)
-{
-	NSVGgradientData* data = NULL;
-	NSVGgradientData* ref = NULL;
-	NSVGgradientStop* stops = NULL;
-	NSVGgradient* grad;
-	float ox, oy, sw, sh, sl;
-	int nstops = 0;
-	int refIter;
-
-	data = nsvg__findGradientData(p, id);
-	if (data == NULL) return NULL;
-
-	// TODO: use ref to fill in all unset values too.
-	ref = data;
-	refIter = 0;
-	while (ref != NULL) {
-		NSVGgradientData* nextRef = NULL;
-		if (stops == NULL && ref->stops != NULL) {
-			stops = ref->stops;
-			nstops = ref->nstops;
-			break;
-		}
-		nextRef = nsvg__findGradientData(p, ref->ref);
-		if (nextRef == ref) break; // prevent infite loops on malformed data
-		ref = nextRef;
-		refIter++;
-		if (refIter > 32) break; // prevent infite loops on malformed data
-	}
-	if (stops == NULL) return NULL;
-
-	grad = (NSVGgradient*)malloc(sizeof(NSVGgradient) + sizeof(NSVGgradientStop)*(nstops-1));
-	if (grad == NULL) return NULL;
-
-	// The shape width and height.
-	if (data->units == NSVG_OBJECT_SPACE) {
-		ox = localBounds[0];
-		oy = localBounds[1];
-		sw = localBounds[2] - localBounds[0];
-		sh = localBounds[3] - localBounds[1];
-	} else {
-		ox = nsvg__actualOrigX(p);
-		oy = nsvg__actualOrigY(p);
-		sw = nsvg__actualWidth(p);
-		sh = nsvg__actualHeight(p);
-	}
-	sl = sqrtf(sw*sw + sh*sh) / sqrtf(2.0f);
-
-	if (data->type == NSVG_PAINT_LINEAR_GRADIENT) {
-		float x1, y1, x2, y2, dx, dy;
-		x1 = nsvg__convertToPixels(p, data->linear.x1, ox, sw);
-		y1 = nsvg__convertToPixels(p, data->linear.y1, oy, sh);
-		x2 = nsvg__convertToPixels(p, data->linear.x2, ox, sw);
-		y2 = nsvg__convertToPixels(p, data->linear.y2, oy, sh);
-		// Calculate transform aligned to the line
-		dx = x2 - x1;
-		dy = y2 - y1;
-		grad->xform[0] = dy; grad->xform[1] = -dx;
-		grad->xform[2] = dx; grad->xform[3] = dy;
-		grad->xform[4] = x1; grad->xform[5] = y1;
-	} else {
-		float cx, cy, fx, fy, r;
-		cx = nsvg__convertToPixels(p, data->radial.cx, ox, sw);
-		cy = nsvg__convertToPixels(p, data->radial.cy, oy, sh);
-		fx = nsvg__convertToPixels(p, data->radial.fx, ox, sw);
-		fy = nsvg__convertToPixels(p, data->radial.fy, oy, sh);
-		r = nsvg__convertToPixels(p, data->radial.r, 0, sl);
-		// Calculate transform aligned to the circle
-		grad->xform[0] = r; grad->xform[1] = 0;
-		grad->xform[2] = 0; grad->xform[3] = r;
-		grad->xform[4] = cx; grad->xform[5] = cy;
-		grad->fx = fx / r;
-		grad->fy = fy / r;
-	}
-
-	nsvg__xformMultiply(grad->xform, data->xform);
-	nsvg__xformMultiply(grad->xform, xform);
-
-	grad->spread = data->spread;
-	memcpy(grad->stops, stops, nstops*sizeof(NSVGgradientStop));
-	grad->nstops = nstops;
-
-	*paintType = data->type;
-
-	return grad;
-}
-
-static float nsvg__getAverageScale(float* t)
-{
-	float sx = sqrtf(t[0]*t[0] + t[2]*t[2]);
-	float sy = sqrtf(t[1]*t[1] + t[3]*t[3]);
-	return (sx + sy) * 0.5f;
-}
-
-static void nsvg__getLocalBounds(float* bounds, NSVGshape *shape, float* xform)
-{
-	NSVGpath* path;
-	float curve[4*2], curveBounds[4];
-	int i, first = 1;
-	for (path = shape->paths; path != NULL; path = path->next) {
-		nsvg__xformPoint(&curve[0], &curve[1], path->pts[0], path->pts[1], xform);
-		for (i = 0; i < path->npts-1; i += 3) {
-			nsvg__xformPoint(&curve[2], &curve[3], path->pts[(i+1)*2], path->pts[(i+1)*2+1], xform);
-			nsvg__xformPoint(&curve[4], &curve[5], path->pts[(i+2)*2], path->pts[(i+2)*2+1], xform);
-			nsvg__xformPoint(&curve[6], &curve[7], path->pts[(i+3)*2], path->pts[(i+3)*2+1], xform);
-			nsvg__curveBounds(curveBounds, curve);
-			if (first) {
-				bounds[0] = curveBounds[0];
-				bounds[1] = curveBounds[1];
-				bounds[2] = curveBounds[2];
-				bounds[3] = curveBounds[3];
-				first = 0;
-			} else {
-				bounds[0] = nsvg__minf(bounds[0], curveBounds[0]);
-				bounds[1] = nsvg__minf(bounds[1], curveBounds[1]);
-				bounds[2] = nsvg__maxf(bounds[2], curveBounds[2]);
-				bounds[3] = nsvg__maxf(bounds[3], curveBounds[3]);
-			}
-			curve[0] = curve[6];
-			curve[1] = curve[7];
-		}
-	}
-}
-
-static void nsvg__addShape(NSVGparser* p)
-{
-	NSVGattrib* attr = nsvg__getAttr(p);
-	float scale = 1.0f;
-	NSVGshape* shape;
-	NSVGpath* path;
-	int i;
-
-	if (p->plist == NULL)
-		return;
-
-	shape = (NSVGshape*)malloc(sizeof(NSVGshape));
-	if (shape == NULL) goto error;
-	memset(shape, 0, sizeof(NSVGshape));
-
-	memcpy(shape->id, attr->id, sizeof shape->id);
-	memcpy(shape->fillGradient, attr->fillGradient, sizeof shape->fillGradient);
-	memcpy(shape->strokeGradient, attr->strokeGradient, sizeof shape->strokeGradient);
-	memcpy(shape->xform, attr->xform, sizeof shape->xform);
-	scale = nsvg__getAverageScale(attr->xform);
-	shape->strokeWidth = attr->strokeWidth * scale;
-	shape->strokeDashOffset = attr->strokeDashOffset * scale;
-	shape->strokeDashCount = (char)attr->strokeDashCount;
-	for (i = 0; i < attr->strokeDashCount; i++)
-		shape->strokeDashArray[i] = attr->strokeDashArray[i] * scale;
-	shape->strokeLineJoin = attr->strokeLineJoin;
-	shape->strokeLineCap = attr->strokeLineCap;
-	shape->miterLimit = attr->miterLimit;
-	shape->fillRule = attr->fillRule;
-	shape->opacity = attr->opacity;
-
-	shape->paths = p->plist;
-	p->plist = NULL;
-
-	// Calculate shape bounds
-	shape->bounds[0] = shape->paths->bounds[0];
-	shape->bounds[1] = shape->paths->bounds[1];
-	shape->bounds[2] = shape->paths->bounds[2];
-	shape->bounds[3] = shape->paths->bounds[3];
-	for (path = shape->paths->next; path != NULL; path = path->next) {
-		shape->bounds[0] = nsvg__minf(shape->bounds[0], path->bounds[0]);
-		shape->bounds[1] = nsvg__minf(shape->bounds[1], path->bounds[1]);
-		shape->bounds[2] = nsvg__maxf(shape->bounds[2], path->bounds[2]);
-		shape->bounds[3] = nsvg__maxf(shape->bounds[3], path->bounds[3]);
-	}
-
-	// Set fill
-	if (attr->hasFill == 0) {
-		shape->fill.type = NSVG_PAINT_NONE;
-	} else if (attr->hasFill == 1) {
-		shape->fill.type = NSVG_PAINT_COLOR;
-		shape->fill.color = attr->fillColor;
-		shape->fill.color |= (unsigned int)(attr->fillOpacity*255) << 24;
-	} else if (attr->hasFill == 2) {
-		shape->fill.type = NSVG_PAINT_UNDEF;
-	}
-
-	// Set stroke
-	if (attr->hasStroke == 0) {
-		shape->stroke.type = NSVG_PAINT_NONE;
-	} else if (attr->hasStroke == 1) {
-		shape->stroke.type = NSVG_PAINT_COLOR;
-		shape->stroke.color = attr->strokeColor;
-		shape->stroke.color |= (unsigned int)(attr->strokeOpacity*255) << 24;
-	} else if (attr->hasStroke == 2) {
-		shape->stroke.type = NSVG_PAINT_UNDEF;
-	}
-
-	// Set flags
-	shape->flags = (attr->visible ? NSVG_FLAGS_VISIBLE : 0x00);
-
-	// Add to tail
-	if (p->image->shapes == NULL)
-		p->image->shapes = shape;
-	else
-		p->shapesTail->next = shape;
-	p->shapesTail = shape;
-
-	return;
-
-error:
-	if (shape) free(shape);
-}
-
-static void nsvg__addPath(NSVGparser* p, char closed)
-{
-	NSVGattrib* attr = nsvg__getAttr(p);
-	NSVGpath* path = NULL;
-	float bounds[4];
-	float* curve;
-	int i;
-
-	if (p->npts < 4)
-		return;
-
-	if (closed)
-		nsvg__lineTo(p, p->pts[0], p->pts[1]);
-
-	// Expect 1 + N*3 points (N = number of cubic bezier segments).
-	if ((p->npts % 3) != 1)
-		return;
-
-	path = (NSVGpath*)malloc(sizeof(NSVGpath));
-	if (path == NULL) goto error;
-	memset(path, 0, sizeof(NSVGpath));
-
-	path->pts = (float*)malloc(p->npts*2*sizeof(float));
-	if (path->pts == NULL) goto error;
-	path->closed = closed;
-	path->npts = p->npts;
-
-	// Transform path.
-	for (i = 0; i < p->npts; ++i)
-		nsvg__xformPoint(&path->pts[i*2], &path->pts[i*2+1], p->pts[i*2], p->pts[i*2+1], attr->xform);
-
-	// Find bounds
-	for (i = 0; i < path->npts-1; i += 3) {
-		curve = &path->pts[i*2];
-		nsvg__curveBounds(bounds, curve);
-		if (i == 0) {
-			path->bounds[0] = bounds[0];
-			path->bounds[1] = bounds[1];
-			path->bounds[2] = bounds[2];
-			path->bounds[3] = bounds[3];
-		} else {
-			path->bounds[0] = nsvg__minf(path->bounds[0], bounds[0]);
-			path->bounds[1] = nsvg__minf(path->bounds[1], bounds[1]);
-			path->bounds[2] = nsvg__maxf(path->bounds[2], bounds[2]);
-			path->bounds[3] = nsvg__maxf(path->bounds[3], bounds[3]);
-		}
-	}
-
-	path->next = p->plist;
-	p->plist = path;
-
-	return;
-
-error:
-	if (path != NULL) {
-		if (path->pts != NULL) free(path->pts);
-		free(path);
-	}
-}
-
-// We roll our own string to float because the std library one uses locale and messes things up.
-static double nsvg__atof(const char* s)
-{
-	char* cur = (char*)s;
-	char* end = NULL;
-	double res = 0.0, sign = 1.0;
-	long long intPart = 0, fracPart = 0;
-	char hasIntPart = 0, hasFracPart = 0;
-
-	// Parse optional sign
-	if (*cur == '+') {
-		cur++;
-	} else if (*cur == '-') {
-		sign = -1;
-		cur++;
-	}
-
-	// Parse integer part
-	if (nsvg__isdigit(*cur)) {
-		// Parse digit sequence
-		intPart = strtoll(cur, &end, 10);
-		if (cur != end) {
-			res = (double)intPart;
-			hasIntPart = 1;
-			cur = end;
-		}
-	}
-
-	// Parse fractional part.
-	if (*cur == '.') {
-		cur++; // Skip '.'
-		if (nsvg__isdigit(*cur)) {
-			// Parse digit sequence
-			fracPart = strtoll(cur, &end, 10);
-			if (cur != end) {
-				res += (double)fracPart / pow(10.0, (double)(end - cur));
-				hasFracPart = 1;
-				cur = end;
-			}
-		}
-	}
-
-	// A valid number should have integer or fractional part.
-	if (!hasIntPart && !hasFracPart)
-		return 0.0;
-
-	// Parse optional exponent
-	if (*cur == 'e' || *cur == 'E') {
-		long expPart = 0;
-		cur++; // skip 'E'
-		expPart = strtol(cur, &end, 10); // Parse digit sequence with sign
-		if (cur != end) {
-			res *= pow(10.0, (double)expPart);
-		}
-	}
-
-	return res * sign;
-}
-
-
-static const char* nsvg__parseNumber(const char* s, char* it, const int size)
-{
-	const int last = size-1;
-	int i = 0;
-
-	// sign
-	if (*s == '-' || *s == '+') {
-		if (i < last) it[i++] = *s;
-		s++;
-	}
-	// integer part
-	while (*s && nsvg__isdigit(*s)) {
-		if (i < last) it[i++] = *s;
-		s++;
-	}
-	if (*s == '.') {
-		// decimal point
-		if (i < last) it[i++] = *s;
-		s++;
-		// fraction part
-		while (*s && nsvg__isdigit(*s)) {
-			if (i < last) it[i++] = *s;
-			s++;
-		}
-	}
-	// exponent
-	if ((*s == 'e' || *s == 'E') && (s[1] != 'm' && s[1] != 'x')) {
-		if (i < last) it[i++] = *s;
-		s++;
-		if (*s == '-' || *s == '+') {
-			if (i < last) it[i++] = *s;
-			s++;
-		}
-		while (*s && nsvg__isdigit(*s)) {
-			if (i < last) it[i++] = *s;
-			s++;
-		}
-	}
-	it[i] = '\0';
-
-	return s;
-}
-
-static const char* nsvg__getNextPathItemWhenArcFlag(const char* s, char* it)
-{
-	it[0] = '\0';
-	while (*s && (nsvg__isspace(*s) || *s == ',')) s++;
-	if (!*s) return s;
-	if (*s == '0' || *s == '1') {
-		it[0] = *s++;
-		it[1] = '\0';
-		return s;
-	}
-	return s;
-}
-
-static const char* nsvg__getNextPathItem(const char* s, char* it)
-{
-	it[0] = '\0';
-	// Skip white spaces and commas
-	while (*s && (nsvg__isspace(*s) || *s == ',')) s++;
-	if (!*s) return s;
-	if (*s == '-' || *s == '+' || *s == '.' || nsvg__isdigit(*s)) {
-		s = nsvg__parseNumber(s, it, 64);
-	} else {
-		// Parse command
-		it[0] = *s++;
-		it[1] = '\0';
-		return s;
-	}
-
-	return s;
-}
-
-static unsigned int nsvg__parseColorHex(const char* str)
-{
-	unsigned int r=0, g=0, b=0;
-	if (sscanf(str, "#%2x%2x%2x", &r, &g, &b) == 3 )		// 2 digit hex
-		return NSVG_RGB(r, g, b);
-	if (sscanf(str, "#%1x%1x%1x", &r, &g, &b) == 3 )		// 1 digit hex, e.g. #abc -> 0xccbbaa
-		return NSVG_RGB(r*17, g*17, b*17);			// same effect as (r<<4|r), (g<<4|g), ..
-	return NSVG_RGB(128, 128, 128);
-}
-
-// Parse rgb color. The pointer 'str' must point at "rgb(" (4+ characters).
-// This function returns gray (rgb(128, 128, 128) == '#808080') on parse errors
-// for backwards compatibility. Note: other image viewers return black instead.
-
-static unsigned int nsvg__parseColorRGB(const char* str)
-{
-	int i;
-	unsigned int rgbi[3];
-	float rgbf[3];
-	// try decimal integers first
-	if (sscanf(str, "rgb(%u, %u, %u)", &rgbi[0], &rgbi[1], &rgbi[2]) != 3) {
-		// integers failed, try percent values (float, locale independent)
-		const char delimiter[3] = {',', ',', ')'};
-		str += 4; // skip "rgb("
-		for (i = 0; i < 3; i++) {
-			while (*str && (nsvg__isspace(*str))) str++; 	// skip leading spaces
-			if (*str == '+') str++;				// skip '+' (don't allow '-')
-			if (!*str) break;
-			rgbf[i] = nsvg__atof(str);
-
-			// Note 1: it would be great if nsvg__atof() returned how many
-			// bytes it consumed but it doesn't. We need to skip the number,
-			// the '%' character, spaces, and the delimiter ',' or ')'.
-
-			// Note 2: The following code does not allow values like "33.%",
-			// i.e. a decimal point w/o fractional part, but this is consistent
-			// with other image viewers, e.g. firefox, chrome, eog, gimp.
-
-			while (*str && nsvg__isdigit(*str)) str++;		// skip integer part
-			if (*str == '.') {
-				str++;
-				if (!nsvg__isdigit(*str)) break;		// error: no digit after '.'
-				while (*str && nsvg__isdigit(*str)) str++;	// skip fractional part
-			}
-			if (*str == '%') str++; else break;
-			while (nsvg__isspace(*str)) str++;
-			if (*str == delimiter[i]) str++;
-			else break;
-		}
-		if (i == 3) {
-			rgbi[0] = roundf(rgbf[0] * 2.55f);
-			rgbi[1] = roundf(rgbf[1] * 2.55f);
-			rgbi[2] = roundf(rgbf[2] * 2.55f);
-		} else {
-			rgbi[0] = rgbi[1] = rgbi[2] = 128;
-		}
-	}
-	// clip values as the CSS spec requires
-	for (i = 0; i < 3; i++) {
-		if (rgbi[i] > 255) rgbi[i] = 255;
-	}
-	return NSVG_RGB(rgbi[0], rgbi[1], rgbi[2]);
-}
-
-typedef struct NSVGNamedColor {
-	const char* name;
-	unsigned int color;
-} NSVGNamedColor;
-
-NSVGNamedColor nsvg__colors[] = {
-
-	{ "red", NSVG_RGB(255, 0, 0) },
-	{ "green", NSVG_RGB( 0, 128, 0) },
-	{ "blue", NSVG_RGB( 0, 0, 255) },
-	{ "yellow", NSVG_RGB(255, 255, 0) },
-	{ "cyan", NSVG_RGB( 0, 255, 255) },
-	{ "magenta", NSVG_RGB(255, 0, 255) },
-	{ "black", NSVG_RGB( 0, 0, 0) },
-	{ "grey", NSVG_RGB(128, 128, 128) },
-	{ "gray", NSVG_RGB(128, 128, 128) },
-	{ "white", NSVG_RGB(255, 255, 255) },
-
-#ifdef NANOSVG_ALL_COLOR_KEYWORDS
-	{ "aliceblue", NSVG_RGB(240, 248, 255) },
-	{ "antiquewhite", NSVG_RGB(250, 235, 215) },
-	{ "aqua", NSVG_RGB( 0, 255, 255) },
-	{ "aquamarine", NSVG_RGB(127, 255, 212) },
-	{ "azure", NSVG_RGB(240, 255, 255) },
-	{ "beige", NSVG_RGB(245, 245, 220) },
-	{ "bisque", NSVG_RGB(255, 228, 196) },
-	{ "blanchedalmond", NSVG_RGB(255, 235, 205) },
-	{ "blueviolet", NSVG_RGB(138, 43, 226) },
-	{ "brown", NSVG_RGB(165, 42, 42) },
-	{ "burlywood", NSVG_RGB(222, 184, 135) },
-	{ "cadetblue", NSVG_RGB( 95, 158, 160) },
-	{ "chartreuse", NSVG_RGB(127, 255, 0) },
-	{ "chocolate", NSVG_RGB(210, 105, 30) },
-	{ "coral", NSVG_RGB(255, 127, 80) },
-	{ "cornflowerblue", NSVG_RGB(100, 149, 237) },
-	{ "cornsilk", NSVG_RGB(255, 248, 220) },
-	{ "crimson", NSVG_RGB(220, 20, 60) },
-	{ "darkblue", NSVG_RGB( 0, 0, 139) },
-	{ "darkcyan", NSVG_RGB( 0, 139, 139) },
-	{ "darkgoldenrod", NSVG_RGB(184, 134, 11) },
-	{ "darkgray", NSVG_RGB(169, 169, 169) },
-	{ "darkgreen", NSVG_RGB( 0, 100, 0) },
-	{ "darkgrey", NSVG_RGB(169, 169, 169) },
-	{ "darkkhaki", NSVG_RGB(189, 183, 107) },
-	{ "darkmagenta", NSVG_RGB(139, 0, 139) },
-	{ "darkolivegreen", NSVG_RGB( 85, 107, 47) },
-	{ "darkorange", NSVG_RGB(255, 140, 0) },
-	{ "darkorchid", NSVG_RGB(153, 50, 204) },
-	{ "darkred", NSVG_RGB(139, 0, 0) },
-	{ "darksalmon", NSVG_RGB(233, 150, 122) },
-	{ "darkseagreen", NSVG_RGB(143, 188, 143) },
-	{ "darkslateblue", NSVG_RGB( 72, 61, 139) },
-	{ "darkslategray", NSVG_RGB( 47, 79, 79) },
-	{ "darkslategrey", NSVG_RGB( 47, 79, 79) },
-	{ "darkturquoise", NSVG_RGB( 0, 206, 209) },
-	{ "darkviolet", NSVG_RGB(148, 0, 211) },
-	{ "deeppink", NSVG_RGB(255, 20, 147) },
-	{ "deepskyblue", NSVG_RGB( 0, 191, 255) },
-	{ "dimgray", NSVG_RGB(105, 105, 105) },
-	{ "dimgrey", NSVG_RGB(105, 105, 105) },
-	{ "dodgerblue", NSVG_RGB( 30, 144, 255) },
-	{ "firebrick", NSVG_RGB(178, 34, 34) },
-	{ "floralwhite", NSVG_RGB(255, 250, 240) },
-	{ "forestgreen", NSVG_RGB( 34, 139, 34) },
-	{ "fuchsia", NSVG_RGB(255, 0, 255) },
-	{ "gainsboro", NSVG_RGB(220, 220, 220) },
-	{ "ghostwhite", NSVG_RGB(248, 248, 255) },
-	{ "gold", NSVG_RGB(255, 215, 0) },
-	{ "goldenrod", NSVG_RGB(218, 165, 32) },
-	{ "greenyellow", NSVG_RGB(173, 255, 47) },
-	{ "honeydew", NSVG_RGB(240, 255, 240) },
-	{ "hotpink", NSVG_RGB(255, 105, 180) },
-	{ "indianred", NSVG_RGB(205, 92, 92) },
-	{ "indigo", NSVG_RGB( 75, 0, 130) },
-	{ "ivory", NSVG_RGB(255, 255, 240) },
-	{ "khaki", NSVG_RGB(240, 230, 140) },
-	{ "lavender", NSVG_RGB(230, 230, 250) },
-	{ "lavenderblush", NSVG_RGB(255, 240, 245) },
-	{ "lawngreen", NSVG_RGB(124, 252, 0) },
-	{ "lemonchiffon", NSVG_RGB(255, 250, 205) },
-	{ "lightblue", NSVG_RGB(173, 216, 230) },
-	{ "lightcoral", NSVG_RGB(240, 128, 128) },
-	{ "lightcyan", NSVG_RGB(224, 255, 255) },
-	{ "lightgoldenrodyellow", NSVG_RGB(250, 250, 210) },
-	{ "lightgray", NSVG_RGB(211, 211, 211) },
-	{ "lightgreen", NSVG_RGB(144, 238, 144) },
-	{ "lightgrey", NSVG_RGB(211, 211, 211) },
-	{ "lightpink", NSVG_RGB(255, 182, 193) },
-	{ "lightsalmon", NSVG_RGB(255, 160, 122) },
-	{ "lightseagreen", NSVG_RGB( 32, 178, 170) },
-	{ "lightskyblue", NSVG_RGB(135, 206, 250) },
-	{ "lightslategray", NSVG_RGB(119, 136, 153) },
-	{ "lightslategrey", NSVG_RGB(119, 136, 153) },
-	{ "lightsteelblue", NSVG_RGB(176, 196, 222) },
-	{ "lightyellow", NSVG_RGB(255, 255, 224) },
-	{ "lime", NSVG_RGB( 0, 255, 0) },
-	{ "limegreen", NSVG_RGB( 50, 205, 50) },
-	{ "linen", NSVG_RGB(250, 240, 230) },
-	{ "maroon", NSVG_RGB(128, 0, 0) },
-	{ "mediumaquamarine", NSVG_RGB(102, 205, 170) },
-	{ "mediumblue", NSVG_RGB( 0, 0, 205) },
-	{ "mediumorchid", NSVG_RGB(186, 85, 211) },
-	{ "mediumpurple", NSVG_RGB(147, 112, 219) },
-	{ "mediumseagreen", NSVG_RGB( 60, 179, 113) },
-	{ "mediumslateblue", NSVG_RGB(123, 104, 238) },
-	{ "mediumspringgreen", NSVG_RGB( 0, 250, 154) },
-	{ "mediumturquoise", NSVG_RGB( 72, 209, 204) },
-	{ "mediumvioletred", NSVG_RGB(199, 21, 133) },
-	{ "midnightblue", NSVG_RGB( 25, 25, 112) },
-	{ "mintcream", NSVG_RGB(245, 255, 250) },
-	{ "mistyrose", NSVG_RGB(255, 228, 225) },
-	{ "moccasin", NSVG_RGB(255, 228, 181) },
-	{ "navajowhite", NSVG_RGB(255, 222, 173) },
-	{ "navy", NSVG_RGB( 0, 0, 128) },
-	{ "oldlace", NSVG_RGB(253, 245, 230) },
-	{ "olive", NSVG_RGB(128, 128, 0) },
-	{ "olivedrab", NSVG_RGB(107, 142, 35) },
-	{ "orange", NSVG_RGB(255, 165, 0) },
-	{ "orangered", NSVG_RGB(255, 69, 0) },
-	{ "orchid", NSVG_RGB(218, 112, 214) },
-	{ "palegoldenrod", NSVG_RGB(238, 232, 170) },
-	{ "palegreen", NSVG_RGB(152, 251, 152) },
-	{ "paleturquoise", NSVG_RGB(175, 238, 238) },
-	{ "palevioletred", NSVG_RGB(219, 112, 147) },
-	{ "papayawhip", NSVG_RGB(255, 239, 213) },
-	{ "peachpuff", NSVG_RGB(255, 218, 185) },
-	{ "peru", NSVG_RGB(205, 133, 63) },
-	{ "pink", NSVG_RGB(255, 192, 203) },
-	{ "plum", NSVG_RGB(221, 160, 221) },
-	{ "powderblue", NSVG_RGB(176, 224, 230) },
-	{ "purple", NSVG_RGB(128, 0, 128) },
-	{ "rosybrown", NSVG_RGB(188, 143, 143) },
-	{ "royalblue", NSVG_RGB( 65, 105, 225) },
-	{ "saddlebrown", NSVG_RGB(139, 69, 19) },
-	{ "salmon", NSVG_RGB(250, 128, 114) },
-	{ "sandybrown", NSVG_RGB(244, 164, 96) },
-	{ "seagreen", NSVG_RGB( 46, 139, 87) },
-	{ "seashell", NSVG_RGB(255, 245, 238) },
-	{ "sienna", NSVG_RGB(160, 82, 45) },
-	{ "silver", NSVG_RGB(192, 192, 192) },
-	{ "skyblue", NSVG_RGB(135, 206, 235) },
-	{ "slateblue", NSVG_RGB(106, 90, 205) },
-	{ "slategray", NSVG_RGB(112, 128, 144) },
-	{ "slategrey", NSVG_RGB(112, 128, 144) },
-	{ "snow", NSVG_RGB(255, 250, 250) },
-	{ "springgreen", NSVG_RGB( 0, 255, 127) },
-	{ "steelblue", NSVG_RGB( 70, 130, 180) },
-	{ "tan", NSVG_RGB(210, 180, 140) },
-	{ "teal", NSVG_RGB( 0, 128, 128) },
-	{ "thistle", NSVG_RGB(216, 191, 216) },
-	{ "tomato", NSVG_RGB(255, 99, 71) },
-	{ "turquoise", NSVG_RGB( 64, 224, 208) },
-	{ "violet", NSVG_RGB(238, 130, 238) },
-	{ "wheat", NSVG_RGB(245, 222, 179) },
-	{ "whitesmoke", NSVG_RGB(245, 245, 245) },
-	{ "yellowgreen", NSVG_RGB(154, 205, 50) },
-#endif
-};
-
-static unsigned int nsvg__parseColorName(const char* str)
-{
-	int i, ncolors = sizeof(nsvg__colors) / sizeof(NSVGNamedColor);
-
-	for (i = 0; i < ncolors; i++) {
-		if (strcmp(nsvg__colors[i].name, str) == 0) {
-			return nsvg__colors[i].color;
-		}
-	}
-
-	return NSVG_RGB(128, 128, 128);
-}
-
-static unsigned int nsvg__parseColor(const char* str)
-{
-	size_t len = 0;
-	while(*str == ' ') ++str;
-	len = strlen(str);
-	if (len >= 1 && *str == '#')
-		return nsvg__parseColorHex(str);
-	else if (len >= 4 && str[0] == 'r' && str[1] == 'g' && str[2] == 'b' && str[3] == '(')
-		return nsvg__parseColorRGB(str);
-	return nsvg__parseColorName(str);
-}
-
-static float nsvg__parseOpacity(const char* str)
-{
-	float val = nsvg__atof(str);
-	if (val < 0.0f) val = 0.0f;
-	if (val > 1.0f) val = 1.0f;
-	return val;
-}
-
-static float nsvg__parseMiterLimit(const char* str)
-{
-	float val = nsvg__atof(str);
-	if (val < 0.0f) val = 0.0f;
-	return val;
-}
-
-static int nsvg__parseUnits(const char* units)
-{
-	if (units[0] == 'p' && units[1] == 'x')
-		return NSVG_UNITS_PX;
-	else if (units[0] == 'p' && units[1] == 't')
-		return NSVG_UNITS_PT;
-	else if (units[0] == 'p' && units[1] == 'c')
-		return NSVG_UNITS_PC;
-	else if (units[0] == 'm' && units[1] == 'm')
-		return NSVG_UNITS_MM;
-	else if (units[0] == 'c' && units[1] == 'm')
-		return NSVG_UNITS_CM;
-	else if (units[0] == 'i' && units[1] == 'n')
-		return NSVG_UNITS_IN;
-	else if (units[0] == '%')
-		return NSVG_UNITS_PERCENT;
-	else if (units[0] == 'e' && units[1] == 'm')
-		return NSVG_UNITS_EM;
-	else if (units[0] == 'e' && units[1] == 'x')
-		return NSVG_UNITS_EX;
-	return NSVG_UNITS_USER;
-}
-
-static int nsvg__isCoordinate(const char* s)
-{
-	// optional sign
-	if (*s == '-' || *s == '+')
-		s++;
-	// must have at least one digit, or start by a dot
-	return (nsvg__isdigit(*s) || *s == '.');
-}
-
-static NSVGcoordinate nsvg__parseCoordinateRaw(const char* str)
-{
-	NSVGcoordinate coord = {0, NSVG_UNITS_USER};
-	char buf[64];
-	coord.units = nsvg__parseUnits(nsvg__parseNumber(str, buf, 64));
-	coord.value = nsvg__atof(buf);
-	return coord;
-}
-
-static NSVGcoordinate nsvg__coord(float v, int units)
-{
-	NSVGcoordinate coord = {v, units};
-	return coord;
-}
-
-static float nsvg__parseCoordinate(NSVGparser* p, const char* str, float orig, float length)
-{
-	NSVGcoordinate coord = nsvg__parseCoordinateRaw(str);
-	return nsvg__convertToPixels(p, coord, orig, length);
-}
-
-static int nsvg__parseTransformArgs(const char* str, float* args, int maxNa, int* na)
-{
-	const char* end;
-	const char* ptr;
-	char it[64];
-
-	*na = 0;
-	ptr = str;
-	while (*ptr && *ptr != '(') ++ptr;
-	if (*ptr == 0)
-		return 1;
-	end = ptr;
-	while (*end && *end != ')') ++end;
-	if (*end == 0)
-		return 1;
-
-	while (ptr < end) {
-		if (*ptr == '-' || *ptr == '+' || *ptr == '.' || nsvg__isdigit(*ptr)) {
-			if (*na >= maxNa) return 0;
-			ptr = nsvg__parseNumber(ptr, it, 64);
-			args[(*na)++] = (float)nsvg__atof(it);
-		} else {
-			++ptr;
-		}
-	}
-	return (int)(end - str);
-}
-
-
-static int nsvg__parseMatrix(float* xform, const char* str)
-{
-	float t[6];
-	int na = 0;
-	int len = nsvg__parseTransformArgs(str, t, 6, &na);
-	if (na != 6) return len;
-	memcpy(xform, t, sizeof(float)*6);
-	return len;
-}
-
-static int nsvg__parseTranslate(float* xform, const char* str)
-{
-	float args[2];
-	float t[6];
-	int na = 0;
-	int len = nsvg__parseTransformArgs(str, args, 2, &na);
-	if (na == 1) args[1] = 0.0;
-
-	nsvg__xformSetTranslation(t, args[0], args[1]);
-	memcpy(xform, t, sizeof(float)*6);
-	return len;
-}
-
-static int nsvg__parseScale(float* xform, const char* str)
-{
-	float args[2];
-	int na = 0;
-	float t[6];
-	int len = nsvg__parseTransformArgs(str, args, 2, &na);
-	if (na == 1) args[1] = args[0];
-	nsvg__xformSetScale(t, args[0], args[1]);
-	memcpy(xform, t, sizeof(float)*6);
-	return len;
-}
-
-static int nsvg__parseSkewX(float* xform, const char* str)
-{
-	float args[1];
-	int na = 0;
-	float t[6];
-	int len = nsvg__parseTransformArgs(str, args, 1, &na);
-	nsvg__xformSetSkewX(t, args[0]/180.0f*NSVG_PI);
-	memcpy(xform, t, sizeof(float)*6);
-	return len;
-}
-
-static int nsvg__parseSkewY(float* xform, const char* str)
-{
-	float args[1];
-	int na = 0;
-	float t[6];
-	int len = nsvg__parseTransformArgs(str, args, 1, &na);
-	nsvg__xformSetSkewY(t, args[0]/180.0f*NSVG_PI);
-	memcpy(xform, t, sizeof(float)*6);
-	return len;
-}
-
-static int nsvg__parseRotate(float* xform, const char* str)
-{
-	float args[3];
-	int na = 0;
-	float m[6];
-	float t[6];
-	int len = nsvg__parseTransformArgs(str, args, 3, &na);
-	if (na == 1)
-		args[1] = args[2] = 0.0f;
-	nsvg__xformIdentity(m);
-
-	if (na > 1) {
-		nsvg__xformSetTranslation(t, -args[1], -args[2]);
-		nsvg__xformMultiply(m, t);
-	}
-
-	nsvg__xformSetRotation(t, args[0]/180.0f*NSVG_PI);
-	nsvg__xformMultiply(m, t);
-
-	if (na > 1) {
-		nsvg__xformSetTranslation(t, args[1], args[2]);
-		nsvg__xformMultiply(m, t);
-	}
-
-	memcpy(xform, m, sizeof(float)*6);
-
-	return len;
-}
-
-static void nsvg__parseTransform(float* xform, const char* str)
-{
-	float t[6];
-	int len;
-	nsvg__xformIdentity(xform);
-	while (*str)
-	{
-		if (strncmp(str, "matrix", 6) == 0)
-			len = nsvg__parseMatrix(t, str);
-		else if (strncmp(str, "translate", 9) == 0)
-			len = nsvg__parseTranslate(t, str);
-		else if (strncmp(str, "scale", 5) == 0)
-			len = nsvg__parseScale(t, str);
-		else if (strncmp(str, "rotate", 6) == 0)
-			len = nsvg__parseRotate(t, str);
-		else if (strncmp(str, "skewX", 5) == 0)
-			len = nsvg__parseSkewX(t, str);
-		else if (strncmp(str, "skewY", 5) == 0)
-			len = nsvg__parseSkewY(t, str);
-		else{
-			++str;
-			continue;
-		}
-		if (len != 0) {
-			str += len;
-		} else {
-			++str;
-			continue;
-		}
-
-		nsvg__xformPremultiply(xform, t);
-	}
-}
-
-static void nsvg__parseUrl(char* id, const char* str)
-{
-	int i = 0;
-	str += 4; // "url(";
-	if (*str && *str == '#')
-		str++;
-	while (i < 63 && *str && *str != ')') {
-		id[i] = *str++;
-		i++;
-	}
-	id[i] = '\0';
-}
-
-static char nsvg__parseLineCap(const char* str)
-{
-	if (strcmp(str, "butt") == 0)
-		return NSVG_CAP_BUTT;
-	else if (strcmp(str, "round") == 0)
-		return NSVG_CAP_ROUND;
-	else if (strcmp(str, "square") == 0)
-		return NSVG_CAP_SQUARE;
-	// TODO: handle inherit.
-	return NSVG_CAP_BUTT;
-}
-
-static char nsvg__parseLineJoin(const char* str)
-{
-	if (strcmp(str, "miter") == 0)
-		return NSVG_JOIN_MITER;
-	else if (strcmp(str, "round") == 0)
-		return NSVG_JOIN_ROUND;
-	else if (strcmp(str, "bevel") == 0)
-		return NSVG_JOIN_BEVEL;
-	// TODO: handle inherit.
-	return NSVG_JOIN_MITER;
-}
-
-static char nsvg__parseFillRule(const char* str)
-{
-	if (strcmp(str, "nonzero") == 0)
-		return NSVG_FILLRULE_NONZERO;
-	else if (strcmp(str, "evenodd") == 0)
-		return NSVG_FILLRULE_EVENODD;
-	// TODO: handle inherit.
-	return NSVG_FILLRULE_NONZERO;
-}
-
-static const char* nsvg__getNextDashItem(const char* s, char* it)
-{
-	int n = 0;
-	it[0] = '\0';
-	// Skip white spaces and commas
-	while (*s && (nsvg__isspace(*s) || *s == ',')) s++;
-	// Advance until whitespace, comma or end.
-	while (*s && (!nsvg__isspace(*s) && *s != ',')) {
-		if (n < 63)
-			it[n++] = *s;
-		s++;
-	}
-	it[n++] = '\0';
-	return s;
-}
-
-static int nsvg__parseStrokeDashArray(NSVGparser* p, const char* str, float* strokeDashArray)
-{
-	char item[64];
-	int count = 0, i;
-	float sum = 0.0f;
-
-	// Handle "none"
-	if (str[0] == 'n')
-		return 0;
-
-	// Parse dashes
-	while (*str) {
-		str = nsvg__getNextDashItem(str, item);
-		if (!*item) break;
-		if (count < NSVG_MAX_DASHES)
-			strokeDashArray[count++] = fabsf(nsvg__parseCoordinate(p, item, 0.0f, nsvg__actualLength(p)));
-	}
-
-	for (i = 0; i < count; i++)
-		sum += strokeDashArray[i];
-	if (sum <= 1e-6f)
-		count = 0;
-
-	return count;
-}
-
-static void nsvg__parseStyle(NSVGparser* p, const char* str);
-
-static int nsvg__parseAttr(NSVGparser* p, const char* name, const char* value)
-{
-	float xform[6];
-	NSVGattrib* attr = nsvg__getAttr(p);
-	if (!attr) return 0;
-
-	if (strcmp(name, "style") == 0) {
-		nsvg__parseStyle(p, value);
-	} else if (strcmp(name, "display") == 0) {
-		if (strcmp(value, "none") == 0)
-			attr->visible = 0;
-		// Don't reset ->visible on display:inline, one display:none hides the whole subtree
-
-	} else if (strcmp(name, "fill") == 0) {
-		if (strcmp(value, "none") == 0) {
-			attr->hasFill = 0;
-		} else if (strncmp(value, "url(", 4) == 0) {
-			attr->hasFill = 2;
-			nsvg__parseUrl(attr->fillGradient, value);
-		} else {
-			attr->hasFill = 1;
-			attr->fillColor = nsvg__parseColor(value);
-		}
-	} else if (strcmp(name, "opacity") == 0) {
-		attr->opacity = nsvg__parseOpacity(value);
-	} else if (strcmp(name, "fill-opacity") == 0) {
-		attr->fillOpacity = nsvg__parseOpacity(value);
-	} else if (strcmp(name, "stroke") == 0) {
-		if (strcmp(value, "none") == 0) {
-			attr->hasStroke = 0;
-		} else if (strncmp(value, "url(", 4) == 0) {
-			attr->hasStroke = 2;
-			nsvg__parseUrl(attr->strokeGradient, value);
-		} else {
-			attr->hasStroke = 1;
-			attr->strokeColor = nsvg__parseColor(value);
-		}
-	} else if (strcmp(name, "stroke-width") == 0) {
-		attr->strokeWidth = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));
-	} else if (strcmp(name, "stroke-dasharray") == 0) {
-		attr->strokeDashCount = nsvg__parseStrokeDashArray(p, value, attr->strokeDashArray);
-	} else if (strcmp(name, "stroke-dashoffset") == 0) {
-		attr->strokeDashOffset = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));
-	} else if (strcmp(name, "stroke-opacity") == 0) {
-		attr->strokeOpacity = nsvg__parseOpacity(value);
-	} else if (strcmp(name, "stroke-linecap") == 0) {
-		attr->strokeLineCap = nsvg__parseLineCap(value);
-	} else if (strcmp(name, "stroke-linejoin") == 0) {
-		attr->strokeLineJoin = nsvg__parseLineJoin(value);
-	} else if (strcmp(name, "stroke-miterlimit") == 0) {
-		attr->miterLimit = nsvg__parseMiterLimit(value);
-	} else if (strcmp(name, "fill-rule") == 0) {
-		attr->fillRule = nsvg__parseFillRule(value);
-	} else if (strcmp(name, "font-size") == 0) {
-		attr->fontSize = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));
-	} else if (strcmp(name, "transform") == 0) {
-		nsvg__parseTransform(xform, value);
-		nsvg__xformPremultiply(attr->xform, xform);
-	} else if (strcmp(name, "stop-color") == 0) {
-		attr->stopColor = nsvg__parseColor(value);
-	} else if (strcmp(name, "stop-opacity") == 0) {
-		attr->stopOpacity = nsvg__parseOpacity(value);
-	} else if (strcmp(name, "offset") == 0) {
-		attr->stopOffset = nsvg__parseCoordinate(p, value, 0.0f, 1.0f);
-	} else if (strcmp(name, "id") == 0) {
-		strncpy(attr->id, value, 63);
-		attr->id[63] = '\0';
-	} else {
-		return 0;
-	}
-	return 1;
-}
-
-static int nsvg__parseNameValue(NSVGparser* p, const char* start, const char* end)
-{
-	const char* str;
-	const char* val;
-	char name[512];
-	char value[512];
-	int n;
-
-	str = start;
-	while (str < end && *str != ':') ++str;
-
-	val = str;
-
-	// Right Trim
-	while (str > start &&  (*str == ':' || nsvg__isspace(*str))) --str;
-	++str;
-
-	n = (int)(str - start);
-	if (n > 511) n = 511;
-	if (n) memcpy(name, start, n);
-	name[n] = 0;
-
-	while (val < end && (*val == ':' || nsvg__isspace(*val))) ++val;
-
-	n = (int)(end - val);
-	if (n > 511) n = 511;
-	if (n) memcpy(value, val, n);
-	value[n] = 0;
-
-	return nsvg__parseAttr(p, name, value);
-}
-
-static void nsvg__parseStyle(NSVGparser* p, const char* str)
-{
-	const char* start;
-	const char* end;
-
-	while (*str) {
-		// Left Trim
-		while(*str && nsvg__isspace(*str)) ++str;
-		start = str;
-		while(*str && *str != ';') ++str;
-		end = str;
-
-		// Right Trim
-		while (end > start &&  (*end == ';' || nsvg__isspace(*end))) --end;
-		++end;
-
-		nsvg__parseNameValue(p, start, end);
-		if (*str) ++str;
-	}
-}
-
-static void nsvg__parseAttribs(NSVGparser* p, const char** attr)
-{
-	int i;
-	for (i = 0; attr[i]; i += 2)
-	{
-		if (strcmp(attr[i], "style") == 0)
-			nsvg__parseStyle(p, attr[i + 1]);
-		else
-			nsvg__parseAttr(p, attr[i], attr[i + 1]);
-	}
-}
-
-static int nsvg__getArgsPerElement(char cmd)
-{
-	switch (cmd) {
-		case 'v':
-		case 'V':
-		case 'h':
-		case 'H':
-			return 1;
-		case 'm':
-		case 'M':
-		case 'l':
-		case 'L':
-		case 't':
-		case 'T':
-			return 2;
-		case 'q':
-		case 'Q':
-		case 's':
-		case 'S':
-			return 4;
-		case 'c':
-		case 'C':
-			return 6;
-		case 'a':
-		case 'A':
-			return 7;
-		case 'z':
-		case 'Z':
-			return 0;
-	}
-	return -1;
-}
-
-static void nsvg__pathMoveTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel)
-{
-	if (rel) {
-		*cpx += args[0];
-		*cpy += args[1];
-	} else {
-		*cpx = args[0];
-		*cpy = args[1];
-	}
-	nsvg__moveTo(p, *cpx, *cpy);
-}
-
-static void nsvg__pathLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel)
-{
-	if (rel) {
-		*cpx += args[0];
-		*cpy += args[1];
-	} else {
-		*cpx = args[0];
-		*cpy = args[1];
-	}
-	nsvg__lineTo(p, *cpx, *cpy);
-}
-
-static void nsvg__pathHLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel)
-{
-	if (rel)
-		*cpx += args[0];
-	else
-		*cpx = args[0];
-	nsvg__lineTo(p, *cpx, *cpy);
-}
-
-static void nsvg__pathVLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel)
-{
-	if (rel)
-		*cpy += args[0];
-	else
-		*cpy = args[0];
-	nsvg__lineTo(p, *cpx, *cpy);
-}
-
-static void nsvg__pathCubicBezTo(NSVGparser* p, float* cpx, float* cpy,
-								 float* cpx2, float* cpy2, float* args, int rel)
-{
-	float x2, y2, cx1, cy1, cx2, cy2;
-
-	if (rel) {
-		cx1 = *cpx + args[0];
-		cy1 = *cpy + args[1];
-		cx2 = *cpx + args[2];
-		cy2 = *cpy + args[3];
-		x2 = *cpx + args[4];
-		y2 = *cpy + args[5];
-	} else {
-		cx1 = args[0];
-		cy1 = args[1];
-		cx2 = args[2];
-		cy2 = args[3];
-		x2 = args[4];
-		y2 = args[5];
-	}
-
-	nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2);
-
-	*cpx2 = cx2;
-	*cpy2 = cy2;
-	*cpx = x2;
-	*cpy = y2;
-}
-
-static void nsvg__pathCubicBezShortTo(NSVGparser* p, float* cpx, float* cpy,
-									  float* cpx2, float* cpy2, float* args, int rel)
-{
-	float x1, y1, x2, y2, cx1, cy1, cx2, cy2;
-
-	x1 = *cpx;
-	y1 = *cpy;
-	if (rel) {
-		cx2 = *cpx + args[0];
-		cy2 = *cpy + args[1];
-		x2 = *cpx + args[2];
-		y2 = *cpy + args[3];
-	} else {
-		cx2 = args[0];
-		cy2 = args[1];
-		x2 = args[2];
-		y2 = args[3];
-	}
-
-	cx1 = 2*x1 - *cpx2;
-	cy1 = 2*y1 - *cpy2;
-
-	nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2);
-
-	*cpx2 = cx2;
-	*cpy2 = cy2;
-	*cpx = x2;
-	*cpy = y2;
-}
-
-static void nsvg__pathQuadBezTo(NSVGparser* p, float* cpx, float* cpy,
-								float* cpx2, float* cpy2, float* args, int rel)
-{
-	float x1, y1, x2, y2, cx, cy;
-	float cx1, cy1, cx2, cy2;
-
-	x1 = *cpx;
-	y1 = *cpy;
-	if (rel) {
-		cx = *cpx + args[0];
-		cy = *cpy + args[1];
-		x2 = *cpx + args[2];
-		y2 = *cpy + args[3];
-	} else {
-		cx = args[0];
-		cy = args[1];
-		x2 = args[2];
-		y2 = args[3];
-	}
-
-	// Convert to cubic bezier
-	cx1 = x1 + 2.0f/3.0f*(cx - x1);
-	cy1 = y1 + 2.0f/3.0f*(cy - y1);
-	cx2 = x2 + 2.0f/3.0f*(cx - x2);
-	cy2 = y2 + 2.0f/3.0f*(cy - y2);
-
-	nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2);
-
-	*cpx2 = cx;
-	*cpy2 = cy;
-	*cpx = x2;
-	*cpy = y2;
-}
-
-static void nsvg__pathQuadBezShortTo(NSVGparser* p, float* cpx, float* cpy,
-									 float* cpx2, float* cpy2, float* args, int rel)
-{
-	float x1, y1, x2, y2, cx, cy;
-	float cx1, cy1, cx2, cy2;
-
-	x1 = *cpx;
-	y1 = *cpy;
-	if (rel) {
-		x2 = *cpx + args[0];
-		y2 = *cpy + args[1];
-	} else {
-		x2 = args[0];
-		y2 = args[1];
-	}
-
-	cx = 2*x1 - *cpx2;
-	cy = 2*y1 - *cpy2;
-
-	// Convert to cubix bezier
-	cx1 = x1 + 2.0f/3.0f*(cx - x1);
-	cy1 = y1 + 2.0f/3.0f*(cy - y1);
-	cx2 = x2 + 2.0f/3.0f*(cx - x2);
-	cy2 = y2 + 2.0f/3.0f*(cy - y2);
-
-	nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2);
-
-	*cpx2 = cx;
-	*cpy2 = cy;
-	*cpx = x2;
-	*cpy = y2;
-}
-
-static float nsvg__sqr(float x) { return x*x; }
-static float nsvg__vmag(float x, float y) { return sqrtf(x*x + y*y); }
-
-static float nsvg__vecrat(float ux, float uy, float vx, float vy)
-{
-	return (ux*vx + uy*vy) / (nsvg__vmag(ux,uy) * nsvg__vmag(vx,vy));
-}
-
-static float nsvg__vecang(float ux, float uy, float vx, float vy)
-{
-	float r = nsvg__vecrat(ux,uy, vx,vy);
-	if (r < -1.0f) r = -1.0f;
-	if (r > 1.0f) r = 1.0f;
-	return ((ux*vy < uy*vx) ? -1.0f : 1.0f) * acosf(r);
-}
-
-static void nsvg__pathArcTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel)
-{
-	// Ported from canvg (https://code.google.com/p/canvg/)
-	float rx, ry, rotx;
-	float x1, y1, x2, y2, cx, cy, dx, dy, d;
-	float x1p, y1p, cxp, cyp, s, sa, sb;
-	float ux, uy, vx, vy, a1, da;
-	float x, y, tanx, tany, a, px = 0, py = 0, ptanx = 0, ptany = 0, t[6];
-	float sinrx, cosrx;
-	int fa, fs;
-	int i, ndivs;
-	float hda, kappa;
-
-	rx = fabsf(args[0]);				// y radius
-	ry = fabsf(args[1]);				// x radius
-	rotx = args[2] / 180.0f * NSVG_PI;		// x rotation angle
-	fa = fabsf(args[3]) > 1e-6 ? 1 : 0;	// Large arc
-	fs = fabsf(args[4]) > 1e-6 ? 1 : 0;	// Sweep direction
-	x1 = *cpx;							// start point
-	y1 = *cpy;
-	if (rel) {							// end point
-		x2 = *cpx + args[5];
-		y2 = *cpy + args[6];
-	} else {
-		x2 = args[5];
-		y2 = args[6];
-	}
-
-	dx = x1 - x2;
-	dy = y1 - y2;
-	d = sqrtf(dx*dx + dy*dy);
-	if (d < 1e-6f || rx < 1e-6f || ry < 1e-6f) {
-		// The arc degenerates to a line
-		nsvg__lineTo(p, x2, y2);
-		*cpx = x2;
-		*cpy = y2;
-		return;
-	}
-
-	sinrx = sinf(rotx);
-	cosrx = cosf(rotx);
-
-	// Convert to center point parameterization.
-	// http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
-	// 1) Compute x1', y1'
-	x1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f;
-	y1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f;
-	d = nsvg__sqr(x1p)/nsvg__sqr(rx) + nsvg__sqr(y1p)/nsvg__sqr(ry);
-	if (d > 1) {
-		d = sqrtf(d);
-		rx *= d;
-		ry *= d;
-	}
-	// 2) Compute cx', cy'
-	s = 0.0f;
-	sa = nsvg__sqr(rx)*nsvg__sqr(ry) - nsvg__sqr(rx)*nsvg__sqr(y1p) - nsvg__sqr(ry)*nsvg__sqr(x1p);
-	sb = nsvg__sqr(rx)*nsvg__sqr(y1p) + nsvg__sqr(ry)*nsvg__sqr(x1p);
-	if (sa < 0.0f) sa = 0.0f;
-	if (sb > 0.0f)
-		s = sqrtf(sa / sb);
-	if (fa == fs)
-		s = -s;
-	cxp = s * rx * y1p / ry;
-	cyp = s * -ry * x1p / rx;
-
-	// 3) Compute cx,cy from cx',cy'
-	cx = (x1 + x2)/2.0f + cosrx*cxp - sinrx*cyp;
-	cy = (y1 + y2)/2.0f + sinrx*cxp + cosrx*cyp;
-
-	// 4) Calculate theta1, and delta theta.
-	ux = (x1p - cxp) / rx;
-	uy = (y1p - cyp) / ry;
-	vx = (-x1p - cxp) / rx;
-	vy = (-y1p - cyp) / ry;
-	a1 = nsvg__vecang(1.0f,0.0f, ux,uy);	// Initial angle
-	da = nsvg__vecang(ux,uy, vx,vy);		// Delta angle
-
-//	if (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI;
-//	if (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0;
-
-	if (fs == 0 && da > 0)
-		da -= 2 * NSVG_PI;
-	else if (fs == 1 && da < 0)
-		da += 2 * NSVG_PI;
-
-	// Approximate the arc using cubic spline segments.
-	t[0] = cosrx; t[1] = sinrx;
-	t[2] = -sinrx; t[3] = cosrx;
-	t[4] = cx; t[5] = cy;
-
-	// Split arc into max 90 degree segments.
-	// The loop assumes an iteration per end point (including start and end), this +1.
-	ndivs = (int)(fabsf(da) / (NSVG_PI*0.5f) + 1.0f);
-	hda = (da / (float)ndivs) / 2.0f;
-	// Fix for ticket #179: division by 0: avoid cotangens around 0 (infinite)
-	if ((hda < 1e-3f) && (hda > -1e-3f))
-		hda *= 0.5f;
-	else
-		hda = (1.0f - cosf(hda)) / sinf(hda);
-	kappa = fabsf(4.0f / 3.0f * hda);
-	if (da < 0.0f)
-		kappa = -kappa;
-
-	for (i = 0; i <= ndivs; i++) {
-		a = a1 + da * ((float)i/(float)ndivs);
-		dx = cosf(a);
-		dy = sinf(a);
-		nsvg__xformPoint(&x, &y, dx*rx, dy*ry, t); // position
-		nsvg__xformVec(&tanx, &tany, -dy*rx * kappa, dx*ry * kappa, t); // tangent
-		if (i > 0)
-			nsvg__cubicBezTo(p, px+ptanx,py+ptany, x-tanx, y-tany, x, y);
-		px = x;
-		py = y;
-		ptanx = tanx;
-		ptany = tany;
-	}
-
-	*cpx = x2;
-	*cpy = y2;
-}
-
-static void nsvg__parsePath(NSVGparser* p, const char** attr)
-{
-	const char* s = NULL;
-	char cmd = '\0';
-	float args[10];
-	int nargs;
-	int rargs = 0;
-	char initPoint;
-	float cpx, cpy, cpx2, cpy2;
-	const char* tmp[4];
-	char closedFlag;
-	int i;
-	char item[64];
-
-	for (i = 0; attr[i]; i += 2) {
-		if (strcmp(attr[i], "d") == 0) {
-			s = attr[i + 1];
-		} else {
-			tmp[0] = attr[i];
-			tmp[1] = attr[i + 1];
-			tmp[2] = 0;
-			tmp[3] = 0;
-			nsvg__parseAttribs(p, tmp);
-		}
-	}
-
-	if (s) {
-		nsvg__resetPath(p);
-		cpx = 0; cpy = 0;
-		cpx2 = 0; cpy2 = 0;
-		initPoint = 0;
-		closedFlag = 0;
-		nargs = 0;
-
-		while (*s) {
-			item[0] = '\0';
-			if ((cmd == 'A' || cmd == 'a') && (nargs == 3 || nargs == 4))
-				s = nsvg__getNextPathItemWhenArcFlag(s, item);
-			if (!*item)
-				s = nsvg__getNextPathItem(s, item);
-			if (!*item) break;
-			if (cmd != '\0' && nsvg__isCoordinate(item)) {
-				if (nargs < 10)
-					args[nargs++] = (float)nsvg__atof(item);
-				if (nargs >= rargs) {
-					switch (cmd) {
-						case 'm':
-						case 'M':
-							nsvg__pathMoveTo(p, &cpx, &cpy, args, cmd == 'm' ? 1 : 0);
-							// Moveto can be followed by multiple coordinate pairs,
-							// which should be treated as linetos.
-							cmd = (cmd == 'm') ? 'l' : 'L';
-							rargs = nsvg__getArgsPerElement(cmd);
-							cpx2 = cpx; cpy2 = cpy;
-							initPoint = 1;
-							break;
-						case 'l':
-						case 'L':
-							nsvg__pathLineTo(p, &cpx, &cpy, args, cmd == 'l' ? 1 : 0);
-							cpx2 = cpx; cpy2 = cpy;
-							break;
-						case 'H':
-						case 'h':
-							nsvg__pathHLineTo(p, &cpx, &cpy, args, cmd == 'h' ? 1 : 0);
-							cpx2 = cpx; cpy2 = cpy;
-							break;
-						case 'V':
-						case 'v':
-							nsvg__pathVLineTo(p, &cpx, &cpy, args, cmd == 'v' ? 1 : 0);
-							cpx2 = cpx; cpy2 = cpy;
-							break;
-						case 'C':
-						case 'c':
-							nsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0);
-							break;
-						case 'S':
-						case 's':
-							nsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0);
-							break;
-						case 'Q':
-						case 'q':
-							nsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0);
-							break;
-						case 'T':
-						case 't':
-							nsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 't' ? 1 : 0);
-							break;
-						case 'A':
-						case 'a':
-							nsvg__pathArcTo(p, &cpx, &cpy, args, cmd == 'a' ? 1 : 0);
-							cpx2 = cpx; cpy2 = cpy;
-							break;
-						default:
-							if (nargs >= 2) {
-								cpx = args[nargs-2];
-								cpy = args[nargs-1];
-								cpx2 = cpx; cpy2 = cpy;
-							}
-							break;
-					}
-					nargs = 0;
-				}
-			} else {
-				cmd = item[0];
-				if (cmd == 'M' || cmd == 'm') {
-					// Commit path.
-					if (p->npts > 0)
-						nsvg__addPath(p, closedFlag);
-					// Start new subpath.
-					nsvg__resetPath(p);
-					closedFlag = 0;
-					nargs = 0;
-				} else if (initPoint == 0) {
-					// Do not allow other commands until initial point has been set (moveTo called once).
-					cmd = '\0';
-				}
-				if (cmd == 'Z' || cmd == 'z') {
-					closedFlag = 1;
-					// Commit path.
-					if (p->npts > 0) {
-						// Move current point to first point
-						cpx = p->pts[0];
-						cpy = p->pts[1];
-						cpx2 = cpx; cpy2 = cpy;
-						nsvg__addPath(p, closedFlag);
-					}
-					// Start new subpath.
-					nsvg__resetPath(p);
-					nsvg__moveTo(p, cpx, cpy);
-					closedFlag = 0;
-					nargs = 0;
-				}
-				rargs = nsvg__getArgsPerElement(cmd);
-				if (rargs == -1) {
-					// Command not recognized
-					cmd = '\0';
-					rargs = 0;
-				}
-			}
-		}
-		// Commit path.
-		if (p->npts)
-			nsvg__addPath(p, closedFlag);
-	}
-
-	nsvg__addShape(p);
-}
-
-static void nsvg__parseRect(NSVGparser* p, const char** attr)
-{
-	float x = 0.0f;
-	float y = 0.0f;
-	float w = 0.0f;
-	float h = 0.0f;
-	float rx = -1.0f; // marks not set
-	float ry = -1.0f;
-	int i;
-
-	for (i = 0; attr[i]; i += 2) {
-		if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
-			if (strcmp(attr[i], "x") == 0) x = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
-			if (strcmp(attr[i], "y") == 0) y = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
-			if (strcmp(attr[i], "width") == 0) w = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p));
-			if (strcmp(attr[i], "height") == 0) h = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p));
-			if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p)));
-			if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p)));
-		}
-	}
-
-	if (rx < 0.0f && ry > 0.0f) rx = ry;
-	if (ry < 0.0f && rx > 0.0f) ry = rx;
-	if (rx < 0.0f) rx = 0.0f;
-	if (ry < 0.0f) ry = 0.0f;
-	if (rx > w/2.0f) rx = w/2.0f;
-	if (ry > h/2.0f) ry = h/2.0f;
-
-	if (w != 0.0f && h != 0.0f) {
-		nsvg__resetPath(p);
-
-		if (rx < 0.00001f || ry < 0.0001f) {
-			nsvg__moveTo(p, x, y);
-			nsvg__lineTo(p, x+w, y);
-			nsvg__lineTo(p, x+w, y+h);
-			nsvg__lineTo(p, x, y+h);
-		} else {
-			// Rounded rectangle
-			nsvg__moveTo(p, x+rx, y);
-			nsvg__lineTo(p, x+w-rx, y);
-			nsvg__cubicBezTo(p, x+w-rx*(1-NSVG_KAPPA90), y, x+w, y+ry*(1-NSVG_KAPPA90), x+w, y+ry);
-			nsvg__lineTo(p, x+w, y+h-ry);
-			nsvg__cubicBezTo(p, x+w, y+h-ry*(1-NSVG_KAPPA90), x+w-rx*(1-NSVG_KAPPA90), y+h, x+w-rx, y+h);
-			nsvg__lineTo(p, x+rx, y+h);
-			nsvg__cubicBezTo(p, x+rx*(1-NSVG_KAPPA90), y+h, x, y+h-ry*(1-NSVG_KAPPA90), x, y+h-ry);
-			nsvg__lineTo(p, x, y+ry);
-			nsvg__cubicBezTo(p, x, y+ry*(1-NSVG_KAPPA90), x+rx*(1-NSVG_KAPPA90), y, x+rx, y);
-		}
-
-		nsvg__addPath(p, 1);
-
-		nsvg__addShape(p);
-	}
-}
-
-static void nsvg__parseCircle(NSVGparser* p, const char** attr)
-{
-	float cx = 0.0f;
-	float cy = 0.0f;
-	float r = 0.0f;
-	int i;
-
-	for (i = 0; attr[i]; i += 2) {
-		if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
-			if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
-			if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
-			if (strcmp(attr[i], "r") == 0) r = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualLength(p)));
-		}
-	}
-
-	if (r > 0.0f) {
-		nsvg__resetPath(p);
-
-		nsvg__moveTo(p, cx+r, cy);
-		nsvg__cubicBezTo(p, cx+r, cy+r*NSVG_KAPPA90, cx+r*NSVG_KAPPA90, cy+r, cx, cy+r);
-		nsvg__cubicBezTo(p, cx-r*NSVG_KAPPA90, cy+r, cx-r, cy+r*NSVG_KAPPA90, cx-r, cy);
-		nsvg__cubicBezTo(p, cx-r, cy-r*NSVG_KAPPA90, cx-r*NSVG_KAPPA90, cy-r, cx, cy-r);
-		nsvg__cubicBezTo(p, cx+r*NSVG_KAPPA90, cy-r, cx+r, cy-r*NSVG_KAPPA90, cx+r, cy);
-
-		nsvg__addPath(p, 1);
-
-		nsvg__addShape(p);
-	}
-}
-
-static void nsvg__parseEllipse(NSVGparser* p, const char** attr)
-{
-	float cx = 0.0f;
-	float cy = 0.0f;
-	float rx = 0.0f;
-	float ry = 0.0f;
-	int i;
-
-	for (i = 0; attr[i]; i += 2) {
-		if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
-			if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
-			if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
-			if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p)));
-			if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p)));
-		}
-	}
-
-	if (rx > 0.0f && ry > 0.0f) {
-
-		nsvg__resetPath(p);
-
-		nsvg__moveTo(p, cx+rx, cy);
-		nsvg__cubicBezTo(p, cx+rx, cy+ry*NSVG_KAPPA90, cx+rx*NSVG_KAPPA90, cy+ry, cx, cy+ry);
-		nsvg__cubicBezTo(p, cx-rx*NSVG_KAPPA90, cy+ry, cx-rx, cy+ry*NSVG_KAPPA90, cx-rx, cy);
-		nsvg__cubicBezTo(p, cx-rx, cy-ry*NSVG_KAPPA90, cx-rx*NSVG_KAPPA90, cy-ry, cx, cy-ry);
-		nsvg__cubicBezTo(p, cx+rx*NSVG_KAPPA90, cy-ry, cx+rx, cy-ry*NSVG_KAPPA90, cx+rx, cy);
-
-		nsvg__addPath(p, 1);
-
-		nsvg__addShape(p);
-	}
-}
-
-static void nsvg__parseLine(NSVGparser* p, const char** attr)
-{
-	float x1 = 0.0;
-	float y1 = 0.0;
-	float x2 = 0.0;
-	float y2 = 0.0;
-	int i;
-
-	for (i = 0; attr[i]; i += 2) {
-		if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
-			if (strcmp(attr[i], "x1") == 0) x1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
-			if (strcmp(attr[i], "y1") == 0) y1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
-			if (strcmp(attr[i], "x2") == 0) x2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
-			if (strcmp(attr[i], "y2") == 0) y2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
-		}
-	}
-
-	nsvg__resetPath(p);
-
-	nsvg__moveTo(p, x1, y1);
-	nsvg__lineTo(p, x2, y2);
-
-	nsvg__addPath(p, 0);
-
-	nsvg__addShape(p);
-}
-
-static void nsvg__parsePoly(NSVGparser* p, const char** attr, int closeFlag)
-{
-	int i;
-	const char* s;
-	float args[2];
-	int nargs, npts = 0;
-	char item[64];
-
-	nsvg__resetPath(p);
-
-	for (i = 0; attr[i]; i += 2) {
-		if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
-			if (strcmp(attr[i], "points") == 0) {
-				s = attr[i + 1];
-				nargs = 0;
-				while (*s) {
-					s = nsvg__getNextPathItem(s, item);
-					args[nargs++] = (float)nsvg__atof(item);
-					if (nargs >= 2) {
-						if (npts == 0)
-							nsvg__moveTo(p, args[0], args[1]);
-						else
-							nsvg__lineTo(p, args[0], args[1]);
-						nargs = 0;
-						npts++;
-					}
-				}
-			}
-		}
-	}
-
-	nsvg__addPath(p, (char)closeFlag);
-
-	nsvg__addShape(p);
-}
-
-static void nsvg__parseSVG(NSVGparser* p, const char** attr)
-{
-	int i;
-	for (i = 0; attr[i]; i += 2) {
-		if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
-			if (strcmp(attr[i], "width") == 0) {
-				p->image->width = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f);
-			} else if (strcmp(attr[i], "height") == 0) {
-				p->image->height = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f);
-			} else if (strcmp(attr[i], "viewBox") == 0) {
-				const char *s = attr[i + 1];
-				char buf[64];
-				s = nsvg__parseNumber(s, buf, 64);
-				p->viewMinx = nsvg__atof(buf);
-				while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++;
-				if (!*s) return;
-				s = nsvg__parseNumber(s, buf, 64);
-				p->viewMiny = nsvg__atof(buf);
-				while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++;
-				if (!*s) return;
-				s = nsvg__parseNumber(s, buf, 64);
-				p->viewWidth = nsvg__atof(buf);
-				while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++;
-				if (!*s) return;
-				s = nsvg__parseNumber(s, buf, 64);
-				p->viewHeight = nsvg__atof(buf);
-			} else if (strcmp(attr[i], "preserveAspectRatio") == 0) {
-				if (strstr(attr[i + 1], "none") != 0) {
-					// No uniform scaling
-					p->alignType = NSVG_ALIGN_NONE;
-				} else {
-					// Parse X align
-					if (strstr(attr[i + 1], "xMin") != 0)
-						p->alignX = NSVG_ALIGN_MIN;
-					else if (strstr(attr[i + 1], "xMid") != 0)
-						p->alignX = NSVG_ALIGN_MID;
-					else if (strstr(attr[i + 1], "xMax") != 0)
-						p->alignX = NSVG_ALIGN_MAX;
-					// Parse X align
-					if (strstr(attr[i + 1], "yMin") != 0)
-						p->alignY = NSVG_ALIGN_MIN;
-					else if (strstr(attr[i + 1], "yMid") != 0)
-						p->alignY = NSVG_ALIGN_MID;
-					else if (strstr(attr[i + 1], "yMax") != 0)
-						p->alignY = NSVG_ALIGN_MAX;
-					// Parse meet/slice
-					p->alignType = NSVG_ALIGN_MEET;
-					if (strstr(attr[i + 1], "slice") != 0)
-						p->alignType = NSVG_ALIGN_SLICE;
-				}
-			}
-		}
-	}
-}
-
-static void nsvg__parseGradient(NSVGparser* p, const char** attr, signed char type)
-{
-	int i;
-	NSVGgradientData* grad = (NSVGgradientData*)malloc(sizeof(NSVGgradientData));
-	if (grad == NULL) return;
-	memset(grad, 0, sizeof(NSVGgradientData));
-	grad->units = NSVG_OBJECT_SPACE;
-	grad->type = type;
-	if (grad->type == NSVG_PAINT_LINEAR_GRADIENT) {
-		grad->linear.x1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT);
-		grad->linear.y1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT);
-		grad->linear.x2 = nsvg__coord(100.0f, NSVG_UNITS_PERCENT);
-		grad->linear.y2 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT);
-	} else if (grad->type == NSVG_PAINT_RADIAL_GRADIENT) {
-		grad->radial.cx = nsvg__coord(50.0f, NSVG_UNITS_PERCENT);
-		grad->radial.cy = nsvg__coord(50.0f, NSVG_UNITS_PERCENT);
-		grad->radial.r = nsvg__coord(50.0f, NSVG_UNITS_PERCENT);
-	}
-
-	nsvg__xformIdentity(grad->xform);
-
-	for (i = 0; attr[i]; i += 2) {
-		if (strcmp(attr[i], "id") == 0) {
-			strncpy(grad->id, attr[i+1], 63);
-			grad->id[63] = '\0';
-		} else if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
-			if (strcmp(attr[i], "gradientUnits") == 0) {
-				if (strcmp(attr[i+1], "objectBoundingBox") == 0)
-					grad->units = NSVG_OBJECT_SPACE;
-				else
-					grad->units = NSVG_USER_SPACE;
-			} else if (strcmp(attr[i], "gradientTransform") == 0) {
-				nsvg__parseTransform(grad->xform, attr[i + 1]);
-			} else if (strcmp(attr[i], "cx") == 0) {
-				grad->radial.cx = nsvg__parseCoordinateRaw(attr[i + 1]);
-			} else if (strcmp(attr[i], "cy") == 0) {
-				grad->radial.cy = nsvg__parseCoordinateRaw(attr[i + 1]);
-			} else if (strcmp(attr[i], "r") == 0) {
-				grad->radial.r = nsvg__parseCoordinateRaw(attr[i + 1]);
-			} else if (strcmp(attr[i], "fx") == 0) {
-				grad->radial.fx = nsvg__parseCoordinateRaw(attr[i + 1]);
-			} else if (strcmp(attr[i], "fy") == 0) {
-				grad->radial.fy = nsvg__parseCoordinateRaw(attr[i + 1]);
-			} else if (strcmp(attr[i], "x1") == 0) {
-				grad->linear.x1 = nsvg__parseCoordinateRaw(attr[i + 1]);
-			} else if (strcmp(attr[i], "y1") == 0) {
-				grad->linear.y1 = nsvg__parseCoordinateRaw(attr[i + 1]);
-			} else if (strcmp(attr[i], "x2") == 0) {
-				grad->linear.x2 = nsvg__parseCoordinateRaw(attr[i + 1]);
-			} else if (strcmp(attr[i], "y2") == 0) {
-				grad->linear.y2 = nsvg__parseCoordinateRaw(attr[i + 1]);
-			} else if (strcmp(attr[i], "spreadMethod") == 0) {
-				if (strcmp(attr[i+1], "pad") == 0)
-					grad->spread = NSVG_SPREAD_PAD;
-				else if (strcmp(attr[i+1], "reflect") == 0)
-					grad->spread = NSVG_SPREAD_REFLECT;
-				else if (strcmp(attr[i+1], "repeat") == 0)
-					grad->spread = NSVG_SPREAD_REPEAT;
-			} else if (strcmp(attr[i], "xlink:href") == 0) {
-				const char *href = attr[i+1];
-				strncpy(grad->ref, href+1, 62);
-				grad->ref[62] = '\0';
-			}
-		}
-	}
-
-	grad->next = p->gradients;
-	p->gradients = grad;
-}
-
-static void nsvg__parseGradientStop(NSVGparser* p, const char** attr)
-{
-	NSVGattrib* curAttr = nsvg__getAttr(p);
-	NSVGgradientData* grad;
-	NSVGgradientStop* stop;
-	int i, idx;
-
-	curAttr->stopOffset = 0;
-	curAttr->stopColor = 0;
-	curAttr->stopOpacity = 1.0f;
-
-	for (i = 0; attr[i]; i += 2) {
-		nsvg__parseAttr(p, attr[i], attr[i + 1]);
-	}
-
-	// Add stop to the last gradient.
-	grad = p->gradients;
-	if (grad == NULL) return;
-
-	grad->nstops++;
-	grad->stops = (NSVGgradientStop*)realloc(grad->stops, sizeof(NSVGgradientStop)*grad->nstops);
-	if (grad->stops == NULL) return;
-
-	// Insert
-	idx = grad->nstops-1;
-	for (i = 0; i < grad->nstops-1; i++) {
-		if (curAttr->stopOffset < grad->stops[i].offset) {
-			idx = i;
-			break;
-		}
-	}
-	if (idx != grad->nstops-1) {
-		for (i = grad->nstops-1; i > idx; i--)
-			grad->stops[i] = grad->stops[i-1];
-	}
-
-	stop = &grad->stops[idx];
-	stop->color = curAttr->stopColor;
-	stop->color |= (unsigned int)(curAttr->stopOpacity*255) << 24;
-	stop->offset = curAttr->stopOffset;
-}
-
-static void nsvg__startElement(void* ud, const char* el, const char** attr)
-{
-	NSVGparser* p = (NSVGparser*)ud;
-
-	if (p->defsFlag) {
-		// Skip everything but gradients in defs
-		if (strcmp(el, "linearGradient") == 0) {
-			nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT);
-		} else if (strcmp(el, "radialGradient") == 0) {
-			nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT);
-		} else if (strcmp(el, "stop") == 0) {
-			nsvg__parseGradientStop(p, attr);
-		}
-		return;
-	}
-
-	if (strcmp(el, "g") == 0) {
-		nsvg__pushAttr(p);
-		nsvg__parseAttribs(p, attr);
-	} else if (strcmp(el, "path") == 0) {
-		if (p->pathFlag)	// Do not allow nested paths.
-			return;
-		nsvg__pushAttr(p);
-		nsvg__parsePath(p, attr);
-		nsvg__popAttr(p);
-	} else if (strcmp(el, "rect") == 0) {
-		nsvg__pushAttr(p);
-		nsvg__parseRect(p, attr);
-		nsvg__popAttr(p);
-	} else if (strcmp(el, "circle") == 0) {
-		nsvg__pushAttr(p);
-		nsvg__parseCircle(p, attr);
-		nsvg__popAttr(p);
-	} else if (strcmp(el, "ellipse") == 0) {
-		nsvg__pushAttr(p);
-		nsvg__parseEllipse(p, attr);
-		nsvg__popAttr(p);
-	} else if (strcmp(el, "line") == 0)  {
-		nsvg__pushAttr(p);
-		nsvg__parseLine(p, attr);
-		nsvg__popAttr(p);
-	} else if (strcmp(el, "polyline") == 0)  {
-		nsvg__pushAttr(p);
-		nsvg__parsePoly(p, attr, 0);
-		nsvg__popAttr(p);
-	} else if (strcmp(el, "polygon") == 0)  {
-		nsvg__pushAttr(p);
-		nsvg__parsePoly(p, attr, 1);
-		nsvg__popAttr(p);
-	} else  if (strcmp(el, "linearGradient") == 0) {
-		nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT);
-	} else if (strcmp(el, "radialGradient") == 0) {
-		nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT);
-	} else if (strcmp(el, "stop") == 0) {
-		nsvg__parseGradientStop(p, attr);
-	} else if (strcmp(el, "defs") == 0) {
-		p->defsFlag = 1;
-	} else if (strcmp(el, "svg") == 0) {
-		nsvg__parseSVG(p, attr);
-	}
-}
-
-static void nsvg__endElement(void* ud, const char* el)
-{
-	NSVGparser* p = (NSVGparser*)ud;
-
-	if (strcmp(el, "g") == 0) {
-		nsvg__popAttr(p);
-	} else if (strcmp(el, "path") == 0) {
-		p->pathFlag = 0;
-	} else if (strcmp(el, "defs") == 0) {
-		p->defsFlag = 0;
-	}
-}
-
-static void nsvg__content(void* ud, const char* s)
-{
-	NSVG_NOTUSED(ud);
-	NSVG_NOTUSED(s);
-	// empty
-}
-
-static void nsvg__imageBounds(NSVGparser* p, float* bounds)
-{
-	NSVGshape* shape;
-	shape = p->image->shapes;
-	if (shape == NULL) {
-		bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0;
-		return;
-	}
-	bounds[0] = shape->bounds[0];
-	bounds[1] = shape->bounds[1];
-	bounds[2] = shape->bounds[2];
-	bounds[3] = shape->bounds[3];
-	for (shape = shape->next; shape != NULL; shape = shape->next) {
-		bounds[0] = nsvg__minf(bounds[0], shape->bounds[0]);
-		bounds[1] = nsvg__minf(bounds[1], shape->bounds[1]);
-		bounds[2] = nsvg__maxf(bounds[2], shape->bounds[2]);
-		bounds[3] = nsvg__maxf(bounds[3], shape->bounds[3]);
-	}
-}
-
-static float nsvg__viewAlign(float content, float container, int type)
-{
-	if (type == NSVG_ALIGN_MIN)
-		return 0;
-	else if (type == NSVG_ALIGN_MAX)
-		return container - content;
-	// mid
-	return (container - content) * 0.5f;
-}
-
-static void nsvg__scaleGradient(NSVGgradient* grad, float tx, float ty, float sx, float sy)
-{
-	float t[6];
-	nsvg__xformSetTranslation(t, tx, ty);
-	nsvg__xformMultiply (grad->xform, t);
-
-	nsvg__xformSetScale(t, sx, sy);
-	nsvg__xformMultiply (grad->xform, t);
-}
-
-static void nsvg__scaleToViewbox(NSVGparser* p, const char* units)
-{
-	NSVGshape* shape;
-	NSVGpath* path;
-	float tx, ty, sx, sy, us, bounds[4], t[6], avgs;
-	int i;
-	float* pt;
-
-	// Guess image size if not set completely.
-	nsvg__imageBounds(p, bounds);
-
-	if (p->viewWidth == 0) {
-		if (p->image->width > 0) {
-			p->viewWidth = p->image->width;
-		} else {
-			p->viewMinx = bounds[0];
-			p->viewWidth = bounds[2] - bounds[0];
-		}
-	}
-	if (p->viewHeight == 0) {
-		if (p->image->height > 0) {
-			p->viewHeight = p->image->height;
-		} else {
-			p->viewMiny = bounds[1];
-			p->viewHeight = bounds[3] - bounds[1];
-		}
-	}
-	if (p->image->width == 0)
-		p->image->width = p->viewWidth;
-	if (p->image->height == 0)
-		p->image->height = p->viewHeight;
-
-	tx = -p->viewMinx;
-	ty = -p->viewMiny;
-	sx = p->viewWidth > 0 ? p->image->width / p->viewWidth : 0;
-	sy = p->viewHeight > 0 ? p->image->height / p->viewHeight : 0;
-	// Unit scaling
-	us = 1.0f / nsvg__convertToPixels(p, nsvg__coord(1.0f, nsvg__parseUnits(units)), 0.0f, 1.0f);
-
-	// Fix aspect ratio
-	if (p->alignType == NSVG_ALIGN_MEET) {
-		// fit whole image into viewbox
-		sx = sy = nsvg__minf(sx, sy);
-		tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx;
-		ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy;
-	} else if (p->alignType == NSVG_ALIGN_SLICE) {
-		// fill whole viewbox with image
-		sx = sy = nsvg__maxf(sx, sy);
-		tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx;
-		ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy;
-	}
-
-	// Transform
-	sx *= us;
-	sy *= us;
-	avgs = (sx+sy) / 2.0f;
-	for (shape = p->image->shapes; shape != NULL; shape = shape->next) {
-		shape->bounds[0] = (shape->bounds[0] + tx) * sx;
-		shape->bounds[1] = (shape->bounds[1] + ty) * sy;
-		shape->bounds[2] = (shape->bounds[2] + tx) * sx;
-		shape->bounds[3] = (shape->bounds[3] + ty) * sy;
-		for (path = shape->paths; path != NULL; path = path->next) {
-			path->bounds[0] = (path->bounds[0] + tx) * sx;
-			path->bounds[1] = (path->bounds[1] + ty) * sy;
-			path->bounds[2] = (path->bounds[2] + tx) * sx;
-			path->bounds[3] = (path->bounds[3] + ty) * sy;
-			for (i =0; i < path->npts; i++) {
-				pt = &path->pts[i*2];
-				pt[0] = (pt[0] + tx) * sx;
-				pt[1] = (pt[1] + ty) * sy;
-			}
-		}
-
-		if (shape->fill.type == NSVG_PAINT_LINEAR_GRADIENT || shape->fill.type == NSVG_PAINT_RADIAL_GRADIENT) {
-			nsvg__scaleGradient(shape->fill.gradient, tx,ty, sx,sy);
-			memcpy(t, shape->fill.gradient->xform, sizeof(float)*6);
-			nsvg__xformInverse(shape->fill.gradient->xform, t);
-		}
-		if (shape->stroke.type == NSVG_PAINT_LINEAR_GRADIENT || shape->stroke.type == NSVG_PAINT_RADIAL_GRADIENT) {
-			nsvg__scaleGradient(shape->stroke.gradient, tx,ty, sx,sy);
-			memcpy(t, shape->stroke.gradient->xform, sizeof(float)*6);
-			nsvg__xformInverse(shape->stroke.gradient->xform, t);
-		}
-
-		shape->strokeWidth *= avgs;
-		shape->strokeDashOffset *= avgs;
-		for (i = 0; i < shape->strokeDashCount; i++)
-			shape->strokeDashArray[i] *= avgs;
-	}
-}
-
-static void nsvg__createGradients(NSVGparser* p)
-{
-	NSVGshape* shape;
-
-	for (shape = p->image->shapes; shape != NULL; shape = shape->next) {
-		if (shape->fill.type == NSVG_PAINT_UNDEF) {
-			if (shape->fillGradient[0] != '\0') {
-				float inv[6], localBounds[4];
-				nsvg__xformInverse(inv, shape->xform);
-				nsvg__getLocalBounds(localBounds, shape, inv);
-				shape->fill.gradient = nsvg__createGradient(p, shape->fillGradient, localBounds, shape->xform, &shape->fill.type);
-			}
-			if (shape->fill.type == NSVG_PAINT_UNDEF) {
-				shape->fill.type = NSVG_PAINT_NONE;
-			}
-		}
-		if (shape->stroke.type == NSVG_PAINT_UNDEF) {
-			if (shape->strokeGradient[0] != '\0') {
-				float inv[6], localBounds[4];
-				nsvg__xformInverse(inv, shape->xform);
-				nsvg__getLocalBounds(localBounds, shape, inv);
-				shape->stroke.gradient = nsvg__createGradient(p, shape->strokeGradient, localBounds, shape->xform, &shape->stroke.type);
-			}
-			if (shape->stroke.type == NSVG_PAINT_UNDEF) {
-				shape->stroke.type = NSVG_PAINT_NONE;
-			}
-		}
-	}
-}
-
-NSVGimage* nsvgParse(char* input, const char* units, float dpi)
-{
-	NSVGparser* p;
-	NSVGimage* ret = 0;
-
-	p = nsvg__createParser();
-	if (p == NULL) {
-		return NULL;
-	}
-	p->dpi = dpi;
-
-	nsvg__parseXML(input, nsvg__startElement, nsvg__endElement, nsvg__content, p);
-
-	// Create gradients after all definitions have been parsed
-	nsvg__createGradients(p);
-
-	// Scale to viewBox
-	nsvg__scaleToViewbox(p, units);
-
-	ret = p->image;
-	p->image = NULL;
-
-	nsvg__deleteParser(p);
-
-	return ret;
-}
-
-NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi)
-{
-	FILE* fp = NULL;
-	size_t size;
-	char* data = NULL;
-	NSVGimage* image = NULL;
-
-	fp = fopen(filename, "rb");
-	if (!fp) goto error;
-	fseek(fp, 0, SEEK_END);
-	size = ftell(fp);
-	fseek(fp, 0, SEEK_SET);
-	data = (char*)malloc(size+1);
-	if (data == NULL) goto error;
-	if (fread(data, 1, size, fp) != size) goto error;
-	data[size] = '\0';	// Must be null terminated.
-	fclose(fp);
-	image = nsvgParse(data, units, dpi);
-	free(data);
-
-	return image;
-
-error:
-	if (fp) fclose(fp);
-	if (data) free(data);
-	if (image) nsvgDelete(image);
-	return NULL;
-}
-
-NSVGpath* nsvgDuplicatePath(NSVGpath* p)
-{
-    NSVGpath* res = NULL;
-
-    if (p == NULL)
-        return NULL;
-
-    res = (NSVGpath*)malloc(sizeof(NSVGpath));
-    if (res == NULL) goto error;
-    memset(res, 0, sizeof(NSVGpath));
-
-    res->pts = (float*)malloc(p->npts*2*sizeof(float));
-    if (res->pts == NULL) goto error;
-    memcpy(res->pts, p->pts, p->npts * sizeof(float) * 2);
-    res->npts = p->npts;
-
-    memcpy(res->bounds, p->bounds, sizeof(p->bounds));
-
-    res->closed = p->closed;
-
-    return res;
-
-error:
-    if (res != NULL) {
-        free(res->pts);
-        free(res);
-    }
-    return NULL;
-}
-
-void nsvgDelete(NSVGimage* image)
-{
-	NSVGshape *snext, *shape;
-	if (image == NULL) return;
-	shape = image->shapes;
-	while (shape != NULL) {
-		snext = shape->next;
-		nsvg__deletePaths(shape->paths);
-		nsvg__deletePaint(&shape->fill);
-		nsvg__deletePaint(&shape->stroke);
-		free(shape);
-		shape = snext;
-	}
-	free(image);
-}
-
-#endif // NANOSVG_IMPLEMENTATION
-
-#endif // NANOSVG_H

+ 0 - 1459
svg.mod/nanosvg/src/nanosvgrast.h

@@ -1,1459 +0,0 @@
-/*
- * Copyright (c) 2013-14 Mikko Mononen [email protected]
- *
- * This software is provided 'as-is', without any express or implied
- * warranty.  In no event will the authors be held liable for any damages
- * arising from the use of this software.
- *
- * Permission is granted to anyone to use this software for any purpose,
- * including commercial applications, and to alter it and redistribute it
- * freely, subject to the following restrictions:
- *
- * 1. The origin of this software must not be misrepresented; you must not
- * claim that you wrote the original software. If you use this software
- * in a product, an acknowledgment in the product documentation would be
- * appreciated but is not required.
- * 2. Altered source versions must be plainly marked as such, and must not be
- * misrepresented as being the original software.
- * 3. This notice may not be removed or altered from any source distribution.
- *
- * The polygon rasterization is heavily based on stb_truetype rasterizer
- * by Sean Barrett - http://nothings.org/
- *
- */
-
-#ifndef NANOSVGRAST_H
-#define NANOSVGRAST_H
-
-#include "nanosvg.h"
-
-#ifndef NANOSVGRAST_CPLUSPLUS
-#ifdef __cplusplus
-extern "C" {
-#endif
-#endif
-
-typedef struct NSVGrasterizer NSVGrasterizer;
-
-/* Example Usage:
-	// Load SVG
-	NSVGimage* image;
-	image = nsvgParseFromFile("test.svg", "px", 96);
-
-	// Create rasterizer (can be used to render multiple images).
-	struct NSVGrasterizer* rast = nsvgCreateRasterizer();
-	// Allocate memory for image
-	unsigned char* img = malloc(w*h*4);
-	// Rasterize
-	nsvgRasterize(rast, image, 0,0,1, img, w, h, w*4);
-*/
-
-// Allocated rasterizer context.
-NSVGrasterizer* nsvgCreateRasterizer(void);
-
-// Rasterizes SVG image, returns RGBA image (non-premultiplied alpha)
-//   r - pointer to rasterizer context
-//   image - pointer to image to rasterize
-//   tx,ty - image offset (applied after scaling)
-//   scale - image scale
-//   dst - pointer to destination image data, 4 bytes per pixel (RGBA)
-//   w - width of the image to render
-//   h - height of the image to render
-//   stride - number of bytes per scaleline in the destination buffer
-void nsvgRasterize(NSVGrasterizer* r,
-				   NSVGimage* image, float tx, float ty, float scale,
-				   unsigned char* dst, int w, int h, int stride);
-
-// Deletes rasterizer context.
-void nsvgDeleteRasterizer(NSVGrasterizer*);
-
-
-#ifndef NANOSVGRAST_CPLUSPLUS
-#ifdef __cplusplus
-}
-#endif
-#endif
-
-#ifdef NANOSVGRAST_IMPLEMENTATION
-
-#include <math.h>
-#include <stdlib.h>
-#include <string.h>
-
-#define NSVG__SUBSAMPLES	5
-#define NSVG__FIXSHIFT		10
-#define NSVG__FIX			(1 << NSVG__FIXSHIFT)
-#define NSVG__FIXMASK		(NSVG__FIX-1)
-#define NSVG__MEMPAGE_SIZE	1024
-
-typedef struct NSVGedge {
-	float x0,y0, x1,y1;
-	int dir;
-	struct NSVGedge* next;
-} NSVGedge;
-
-typedef struct NSVGpoint {
-	float x, y;
-	float dx, dy;
-	float len;
-	float dmx, dmy;
-	unsigned char flags;
-} NSVGpoint;
-
-typedef struct NSVGactiveEdge {
-	int x,dx;
-	float ey;
-	int dir;
-	struct NSVGactiveEdge *next;
-} NSVGactiveEdge;
-
-typedef struct NSVGmemPage {
-	unsigned char mem[NSVG__MEMPAGE_SIZE];
-	int size;
-	struct NSVGmemPage* next;
-} NSVGmemPage;
-
-typedef struct NSVGcachedPaint {
-	signed char type;
-	char spread;
-	float xform[6];
-	unsigned int colors[256];
-} NSVGcachedPaint;
-
-struct NSVGrasterizer
-{
-	float px, py;
-
-	float tessTol;
-	float distTol;
-
-	NSVGedge* edges;
-	int nedges;
-	int cedges;
-
-	NSVGpoint* points;
-	int npoints;
-	int cpoints;
-
-	NSVGpoint* points2;
-	int npoints2;
-	int cpoints2;
-
-	NSVGactiveEdge* freelist;
-	NSVGmemPage* pages;
-	NSVGmemPage* curpage;
-
-	unsigned char* scanline;
-	int cscanline;
-
-	unsigned char* bitmap;
-	int width, height, stride;
-};
-
-NSVGrasterizer* nsvgCreateRasterizer(void)
-{
-	NSVGrasterizer* r = (NSVGrasterizer*)malloc(sizeof(NSVGrasterizer));
-	if (r == NULL) goto error;
-	memset(r, 0, sizeof(NSVGrasterizer));
-
-	r->tessTol = 0.25f;
-	r->distTol = 0.01f;
-
-	return r;
-
-error:
-	nsvgDeleteRasterizer(r);
-	return NULL;
-}
-
-void nsvgDeleteRasterizer(NSVGrasterizer* r)
-{
-	NSVGmemPage* p;
-
-	if (r == NULL) return;
-
-	p = r->pages;
-	while (p != NULL) {
-		NSVGmemPage* next = p->next;
-		free(p);
-		p = next;
-	}
-
-	if (r->edges) free(r->edges);
-	if (r->points) free(r->points);
-	if (r->points2) free(r->points2);
-	if (r->scanline) free(r->scanline);
-
-	free(r);
-}
-
-static NSVGmemPage* nsvg__nextPage(NSVGrasterizer* r, NSVGmemPage* cur)
-{
-	NSVGmemPage *newp;
-
-	// If using existing chain, return the next page in chain
-	if (cur != NULL && cur->next != NULL) {
-		return cur->next;
-	}
-
-	// Alloc new page
-	newp = (NSVGmemPage*)malloc(sizeof(NSVGmemPage));
-	if (newp == NULL) return NULL;
-	memset(newp, 0, sizeof(NSVGmemPage));
-
-	// Add to linked list
-	if (cur != NULL)
-		cur->next = newp;
-	else
-		r->pages = newp;
-
-	return newp;
-}
-
-static void nsvg__resetPool(NSVGrasterizer* r)
-{
-	NSVGmemPage* p = r->pages;
-	while (p != NULL) {
-		p->size = 0;
-		p = p->next;
-	}
-	r->curpage = r->pages;
-}
-
-static unsigned char* nsvg__alloc(NSVGrasterizer* r, int size)
-{
-	unsigned char* buf;
-	if (size > NSVG__MEMPAGE_SIZE) return NULL;
-	if (r->curpage == NULL || r->curpage->size+size > NSVG__MEMPAGE_SIZE) {
-		r->curpage = nsvg__nextPage(r, r->curpage);
-	}
-	buf = &r->curpage->mem[r->curpage->size];
-	r->curpage->size += size;
-	return buf;
-}
-
-static int nsvg__ptEquals(float x1, float y1, float x2, float y2, float tol)
-{
-	float dx = x2 - x1;
-	float dy = y2 - y1;
-	return dx*dx + dy*dy < tol*tol;
-}
-
-static void nsvg__addPathPoint(NSVGrasterizer* r, float x, float y, int flags)
-{
-	NSVGpoint* pt;
-
-	if (r->npoints > 0) {
-		pt = &r->points[r->npoints-1];
-		if (nsvg__ptEquals(pt->x,pt->y, x,y, r->distTol)) {
-			pt->flags = (unsigned char)(pt->flags | flags);
-			return;
-		}
-	}
-
-	if (r->npoints+1 > r->cpoints) {
-		r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64;
-		r->points = (NSVGpoint*)realloc(r->points, sizeof(NSVGpoint) * r->cpoints);
-		if (r->points == NULL) return;
-	}
-
-	pt = &r->points[r->npoints];
-	pt->x = x;
-	pt->y = y;
-	pt->flags = (unsigned char)flags;
-	r->npoints++;
-}
-
-static void nsvg__appendPathPoint(NSVGrasterizer* r, NSVGpoint pt)
-{
-	if (r->npoints+1 > r->cpoints) {
-		r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64;
-		r->points = (NSVGpoint*)realloc(r->points, sizeof(NSVGpoint) * r->cpoints);
-		if (r->points == NULL) return;
-	}
-	r->points[r->npoints] = pt;
-	r->npoints++;
-}
-
-static void nsvg__duplicatePoints(NSVGrasterizer* r)
-{
-	if (r->npoints > r->cpoints2) {
-		r->cpoints2 = r->npoints;
-		r->points2 = (NSVGpoint*)realloc(r->points2, sizeof(NSVGpoint) * r->cpoints2);
-		if (r->points2 == NULL) return;
-	}
-
-	memcpy(r->points2, r->points, sizeof(NSVGpoint) * r->npoints);
-	r->npoints2 = r->npoints;
-}
-
-static void nsvg__addEdge(NSVGrasterizer* r, float x0, float y0, float x1, float y1)
-{
-	NSVGedge* e;
-
-	// Skip horizontal edges
-	if (y0 == y1)
-		return;
-
-	if (r->nedges+1 > r->cedges) {
-		r->cedges = r->cedges > 0 ? r->cedges * 2 : 64;
-		r->edges = (NSVGedge*)realloc(r->edges, sizeof(NSVGedge) * r->cedges);
-		if (r->edges == NULL) return;
-	}
-
-	e = &r->edges[r->nedges];
-	r->nedges++;
-
-	if (y0 < y1) {
-		e->x0 = x0;
-		e->y0 = y0;
-		e->x1 = x1;
-		e->y1 = y1;
-		e->dir = 1;
-	} else {
-		e->x0 = x1;
-		e->y0 = y1;
-		e->x1 = x0;
-		e->y1 = y0;
-		e->dir = -1;
-	}
-}
-
-static float nsvg__normalize(float *x, float* y)
-{
-	float d = sqrtf((*x)*(*x) + (*y)*(*y));
-	if (d > 1e-6f) {
-		float id = 1.0f / d;
-		*x *= id;
-		*y *= id;
-	}
-	return d;
-}
-
-static float nsvg__absf(float x) { return x < 0 ? -x : x; }
-static float nsvg__roundf(float x) { return (x >= 0) ? floorf(x + 0.5) : ceilf(x - 0.5); }
-
-static void nsvg__flattenCubicBez(NSVGrasterizer* r,
-								  float x1, float y1, float x2, float y2,
-								  float x3, float y3, float x4, float y4,
-								  int level, int type)
-{
-	float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234;
-	float dx,dy,d2,d3;
-
-	if (level > 10) return;
-
-	x12 = (x1+x2)*0.5f;
-	y12 = (y1+y2)*0.5f;
-	x23 = (x2+x3)*0.5f;
-	y23 = (y2+y3)*0.5f;
-	x34 = (x3+x4)*0.5f;
-	y34 = (y3+y4)*0.5f;
-	x123 = (x12+x23)*0.5f;
-	y123 = (y12+y23)*0.5f;
-
-	dx = x4 - x1;
-	dy = y4 - y1;
-	d2 = nsvg__absf(((x2 - x4) * dy - (y2 - y4) * dx));
-	d3 = nsvg__absf(((x3 - x4) * dy - (y3 - y4) * dx));
-
-	if ((d2 + d3)*(d2 + d3) < r->tessTol * (dx*dx + dy*dy)) {
-		nsvg__addPathPoint(r, x4, y4, type);
-		return;
-	}
-
-	x234 = (x23+x34)*0.5f;
-	y234 = (y23+y34)*0.5f;
-	x1234 = (x123+x234)*0.5f;
-	y1234 = (y123+y234)*0.5f;
-
-	nsvg__flattenCubicBez(r, x1,y1, x12,y12, x123,y123, x1234,y1234, level+1, 0);
-	nsvg__flattenCubicBez(r, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type);
-}
-
-static void nsvg__flattenShape(NSVGrasterizer* r, NSVGshape* shape, float scale)
-{
-	int i, j;
-	NSVGpath* path;
-
-	for (path = shape->paths; path != NULL; path = path->next) {
-		r->npoints = 0;
-		// Flatten path
-		nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0);
-		for (i = 0; i < path->npts-1; i += 3) {
-			float* p = &path->pts[i*2];
-			nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, 0);
-		}
-		// Close path
-		nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0);
-		// Build edges
-		for (i = 0, j = r->npoints-1; i < r->npoints; j = i++)
-			nsvg__addEdge(r, r->points[j].x, r->points[j].y, r->points[i].x, r->points[i].y);
-	}
-}
-
-enum NSVGpointFlags
-{
-	NSVG_PT_CORNER = 0x01,
-	NSVG_PT_BEVEL = 0x02,
-	NSVG_PT_LEFT = 0x04
-};
-
-static void nsvg__initClosed(NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth)
-{
-	float w = lineWidth * 0.5f;
-	float dx = p1->x - p0->x;
-	float dy = p1->y - p0->y;
-	float len = nsvg__normalize(&dx, &dy);
-	float px = p0->x + dx*len*0.5f, py = p0->y + dy*len*0.5f;
-	float dlx = dy, dly = -dx;
-	float lx = px - dlx*w, ly = py - dly*w;
-	float rx = px + dlx*w, ry = py + dly*w;
-	left->x = lx; left->y = ly;
-	right->x = rx; right->y = ry;
-}
-
-static void nsvg__buttCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect)
-{
-	float w = lineWidth * 0.5f;
-	float px = p->x, py = p->y;
-	float dlx = dy, dly = -dx;
-	float lx = px - dlx*w, ly = py - dly*w;
-	float rx = px + dlx*w, ry = py + dly*w;
-
-	nsvg__addEdge(r, lx, ly, rx, ry);
-
-	if (connect) {
-		nsvg__addEdge(r, left->x, left->y, lx, ly);
-		nsvg__addEdge(r, rx, ry, right->x, right->y);
-	}
-	left->x = lx; left->y = ly;
-	right->x = rx; right->y = ry;
-}
-
-static void nsvg__squareCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect)
-{
-	float w = lineWidth * 0.5f;
-	float px = p->x - dx*w, py = p->y - dy*w;
-	float dlx = dy, dly = -dx;
-	float lx = px - dlx*w, ly = py - dly*w;
-	float rx = px + dlx*w, ry = py + dly*w;
-
-	nsvg__addEdge(r, lx, ly, rx, ry);
-
-	if (connect) {
-		nsvg__addEdge(r, left->x, left->y, lx, ly);
-		nsvg__addEdge(r, rx, ry, right->x, right->y);
-	}
-	left->x = lx; left->y = ly;
-	right->x = rx; right->y = ry;
-}
-
-#ifndef NSVG_PI
-#define NSVG_PI (3.14159265358979323846264338327f)
-#endif
-
-static void nsvg__roundCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int ncap, int connect)
-{
-	int i;
-	float w = lineWidth * 0.5f;
-	float px = p->x, py = p->y;
-	float dlx = dy, dly = -dx;
-	float lx = 0, ly = 0, rx = 0, ry = 0, prevx = 0, prevy = 0;
-
-	for (i = 0; i < ncap; i++) {
-		float a = (float)i/(float)(ncap-1)*NSVG_PI;
-		float ax = cosf(a) * w, ay = sinf(a) * w;
-		float x = px - dlx*ax - dx*ay;
-		float y = py - dly*ax - dy*ay;
-
-		if (i > 0)
-			nsvg__addEdge(r, prevx, prevy, x, y);
-
-		prevx = x;
-		prevy = y;
-
-		if (i == 0) {
-			lx = x; ly = y;
-		} else if (i == ncap-1) {
-			rx = x; ry = y;
-		}
-	}
-
-	if (connect) {
-		nsvg__addEdge(r, left->x, left->y, lx, ly);
-		nsvg__addEdge(r, rx, ry, right->x, right->y);
-	}
-
-	left->x = lx; left->y = ly;
-	right->x = rx; right->y = ry;
-}
-
-static void nsvg__bevelJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth)
-{
-	float w = lineWidth * 0.5f;
-	float dlx0 = p0->dy, dly0 = -p0->dx;
-	float dlx1 = p1->dy, dly1 = -p1->dx;
-	float lx0 = p1->x - (dlx0 * w), ly0 = p1->y - (dly0 * w);
-	float rx0 = p1->x + (dlx0 * w), ry0 = p1->y + (dly0 * w);
-	float lx1 = p1->x - (dlx1 * w), ly1 = p1->y - (dly1 * w);
-	float rx1 = p1->x + (dlx1 * w), ry1 = p1->y + (dly1 * w);
-
-	nsvg__addEdge(r, lx0, ly0, left->x, left->y);
-	nsvg__addEdge(r, lx1, ly1, lx0, ly0);
-
-	nsvg__addEdge(r, right->x, right->y, rx0, ry0);
-	nsvg__addEdge(r, rx0, ry0, rx1, ry1);
-
-	left->x = lx1; left->y = ly1;
-	right->x = rx1; right->y = ry1;
-}
-
-static void nsvg__miterJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth)
-{
-	float w = lineWidth * 0.5f;
-	float dlx0 = p0->dy, dly0 = -p0->dx;
-	float dlx1 = p1->dy, dly1 = -p1->dx;
-	float lx0, rx0, lx1, rx1;
-	float ly0, ry0, ly1, ry1;
-
-	if (p1->flags & NSVG_PT_LEFT) {
-		lx0 = lx1 = p1->x - p1->dmx * w;
-		ly0 = ly1 = p1->y - p1->dmy * w;
-		nsvg__addEdge(r, lx1, ly1, left->x, left->y);
-
-		rx0 = p1->x + (dlx0 * w);
-		ry0 = p1->y + (dly0 * w);
-		rx1 = p1->x + (dlx1 * w);
-		ry1 = p1->y + (dly1 * w);
-		nsvg__addEdge(r, right->x, right->y, rx0, ry0);
-		nsvg__addEdge(r, rx0, ry0, rx1, ry1);
-	} else {
-		lx0 = p1->x - (dlx0 * w);
-		ly0 = p1->y - (dly0 * w);
-		lx1 = p1->x - (dlx1 * w);
-		ly1 = p1->y - (dly1 * w);
-		nsvg__addEdge(r, lx0, ly0, left->x, left->y);
-		nsvg__addEdge(r, lx1, ly1, lx0, ly0);
-
-		rx0 = rx1 = p1->x + p1->dmx * w;
-		ry0 = ry1 = p1->y + p1->dmy * w;
-		nsvg__addEdge(r, right->x, right->y, rx1, ry1);
-	}
-
-	left->x = lx1; left->y = ly1;
-	right->x = rx1; right->y = ry1;
-}
-
-static void nsvg__roundJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth, int ncap)
-{
-	int i, n;
-	float w = lineWidth * 0.5f;
-	float dlx0 = p0->dy, dly0 = -p0->dx;
-	float dlx1 = p1->dy, dly1 = -p1->dx;
-	float a0 = atan2f(dly0, dlx0);
-	float a1 = atan2f(dly1, dlx1);
-	float da = a1 - a0;
-	float lx, ly, rx, ry;
-
-	if (da < NSVG_PI) da += NSVG_PI*2;
-	if (da > NSVG_PI) da -= NSVG_PI*2;
-
-	n = (int)ceilf((nsvg__absf(da) / NSVG_PI) * (float)ncap);
-	if (n < 2) n = 2;
-	if (n > ncap) n = ncap;
-
-	lx = left->x;
-	ly = left->y;
-	rx = right->x;
-	ry = right->y;
-
-	for (i = 0; i < n; i++) {
-		float u = (float)i/(float)(n-1);
-		float a = a0 + u*da;
-		float ax = cosf(a) * w, ay = sinf(a) * w;
-		float lx1 = p1->x - ax, ly1 = p1->y - ay;
-		float rx1 = p1->x + ax, ry1 = p1->y + ay;
-
-		nsvg__addEdge(r, lx1, ly1, lx, ly);
-		nsvg__addEdge(r, rx, ry, rx1, ry1);
-
-		lx = lx1; ly = ly1;
-		rx = rx1; ry = ry1;
-	}
-
-	left->x = lx; left->y = ly;
-	right->x = rx; right->y = ry;
-}
-
-static void nsvg__straightJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p1, float lineWidth)
-{
-	float w = lineWidth * 0.5f;
-	float lx = p1->x - (p1->dmx * w), ly = p1->y - (p1->dmy * w);
-	float rx = p1->x + (p1->dmx * w), ry = p1->y + (p1->dmy * w);
-
-	nsvg__addEdge(r, lx, ly, left->x, left->y);
-	nsvg__addEdge(r, right->x, right->y, rx, ry);
-
-	left->x = lx; left->y = ly;
-	right->x = rx; right->y = ry;
-}
-
-static int nsvg__curveDivs(float r, float arc, float tol)
-{
-	float da = acosf(r / (r + tol)) * 2.0f;
-	int divs = (int)ceilf(arc / da);
-	if (divs < 2) divs = 2;
-	return divs;
-}
-
-static void nsvg__expandStroke(NSVGrasterizer* r, NSVGpoint* points, int npoints, int closed, int lineJoin, int lineCap, float lineWidth)
-{
-	int ncap = nsvg__curveDivs(lineWidth*0.5f, NSVG_PI, r->tessTol);	// Calculate divisions per half circle.
-	NSVGpoint left = {0,0,0,0,0,0,0,0}, right = {0,0,0,0,0,0,0,0}, firstLeft = {0,0,0,0,0,0,0,0}, firstRight = {0,0,0,0,0,0,0,0};
-	NSVGpoint* p0, *p1;
-	int j, s, e;
-
-	// Build stroke edges
-	if (closed) {
-		// Looping
-		p0 = &points[npoints-1];
-		p1 = &points[0];
-		s = 0;
-		e = npoints;
-	} else {
-		// Add cap
-		p0 = &points[0];
-		p1 = &points[1];
-		s = 1;
-		e = npoints-1;
-	}
-
-	if (closed) {
-		nsvg__initClosed(&left, &right, p0, p1, lineWidth);
-		firstLeft = left;
-		firstRight = right;
-	} else {
-		// Add cap
-		float dx = p1->x - p0->x;
-		float dy = p1->y - p0->y;
-		nsvg__normalize(&dx, &dy);
-		if (lineCap == NSVG_CAP_BUTT)
-			nsvg__buttCap(r, &left, &right, p0, dx, dy, lineWidth, 0);
-		else if (lineCap == NSVG_CAP_SQUARE)
-			nsvg__squareCap(r, &left, &right, p0, dx, dy, lineWidth, 0);
-		else if (lineCap == NSVG_CAP_ROUND)
-			nsvg__roundCap(r, &left, &right, p0, dx, dy, lineWidth, ncap, 0);
-	}
-
-	for (j = s; j < e; ++j) {
-		if (p1->flags & NSVG_PT_CORNER) {
-			if (lineJoin == NSVG_JOIN_ROUND)
-				nsvg__roundJoin(r, &left, &right, p0, p1, lineWidth, ncap);
-			else if (lineJoin == NSVG_JOIN_BEVEL || (p1->flags & NSVG_PT_BEVEL))
-				nsvg__bevelJoin(r, &left, &right, p0, p1, lineWidth);
-			else
-				nsvg__miterJoin(r, &left, &right, p0, p1, lineWidth);
-		} else {
-			nsvg__straightJoin(r, &left, &right, p1, lineWidth);
-		}
-		p0 = p1++;
-	}
-
-	if (closed) {
-		// Loop it
-		nsvg__addEdge(r, firstLeft.x, firstLeft.y, left.x, left.y);
-		nsvg__addEdge(r, right.x, right.y, firstRight.x, firstRight.y);
-	} else {
-		// Add cap
-		float dx = p1->x - p0->x;
-		float dy = p1->y - p0->y;
-		nsvg__normalize(&dx, &dy);
-		if (lineCap == NSVG_CAP_BUTT)
-			nsvg__buttCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1);
-		else if (lineCap == NSVG_CAP_SQUARE)
-			nsvg__squareCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1);
-		else if (lineCap == NSVG_CAP_ROUND)
-			nsvg__roundCap(r, &right, &left, p1, -dx, -dy, lineWidth, ncap, 1);
-	}
-}
-
-static void nsvg__prepareStroke(NSVGrasterizer* r, float miterLimit, int lineJoin)
-{
-	int i, j;
-	NSVGpoint* p0, *p1;
-
-	p0 = &r->points[r->npoints-1];
-	p1 = &r->points[0];
-	for (i = 0; i < r->npoints; i++) {
-		// Calculate segment direction and length
-		p0->dx = p1->x - p0->x;
-		p0->dy = p1->y - p0->y;
-		p0->len = nsvg__normalize(&p0->dx, &p0->dy);
-		// Advance
-		p0 = p1++;
-	}
-
-	// calculate joins
-	p0 = &r->points[r->npoints-1];
-	p1 = &r->points[0];
-	for (j = 0; j < r->npoints; j++) {
-		float dlx0, dly0, dlx1, dly1, dmr2, cross;
-		dlx0 = p0->dy;
-		dly0 = -p0->dx;
-		dlx1 = p1->dy;
-		dly1 = -p1->dx;
-		// Calculate extrusions
-		p1->dmx = (dlx0 + dlx1) * 0.5f;
-		p1->dmy = (dly0 + dly1) * 0.5f;
-		dmr2 = p1->dmx*p1->dmx + p1->dmy*p1->dmy;
-		if (dmr2 > 0.000001f) {
-			float s2 = 1.0f / dmr2;
-			if (s2 > 600.0f) {
-				s2 = 600.0f;
-			}
-			p1->dmx *= s2;
-			p1->dmy *= s2;
-		}
-
-		// Clear flags, but keep the corner.
-		p1->flags = (p1->flags & NSVG_PT_CORNER) ? NSVG_PT_CORNER : 0;
-
-		// Keep track of left turns.
-		cross = p1->dx * p0->dy - p0->dx * p1->dy;
-		if (cross > 0.0f)
-			p1->flags |= NSVG_PT_LEFT;
-
-		// Check to see if the corner needs to be beveled.
-		if (p1->flags & NSVG_PT_CORNER) {
-			if ((dmr2 * miterLimit*miterLimit) < 1.0f || lineJoin == NSVG_JOIN_BEVEL || lineJoin == NSVG_JOIN_ROUND) {
-				p1->flags |= NSVG_PT_BEVEL;
-			}
-		}
-
-		p0 = p1++;
-	}
-}
-
-static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float scale)
-{
-	int i, j, closed;
-	NSVGpath* path;
-	NSVGpoint* p0, *p1;
-	float miterLimit = shape->miterLimit;
-	int lineJoin = shape->strokeLineJoin;
-	int lineCap = shape->strokeLineCap;
-	float lineWidth = shape->strokeWidth * scale;
-
-	for (path = shape->paths; path != NULL; path = path->next) {
-		// Flatten path
-		r->npoints = 0;
-		nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, NSVG_PT_CORNER);
-		for (i = 0; i < path->npts-1; i += 3) {
-			float* p = &path->pts[i*2];
-			nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, NSVG_PT_CORNER);
-		}
-		if (r->npoints < 2)
-			continue;
-
-		closed = path->closed;
-
-		// If the first and last points are the same, remove the last, mark as closed path.
-		p0 = &r->points[r->npoints-1];
-		p1 = &r->points[0];
-		if (nsvg__ptEquals(p0->x,p0->y, p1->x,p1->y, r->distTol)) {
-			r->npoints--;
-			p0 = &r->points[r->npoints-1];
-			closed = 1;
-		}
-
-		if (shape->strokeDashCount > 0) {
-			int idash = 0, dashState = 1;
-			float totalDist = 0, dashLen, allDashLen, dashOffset;
-			NSVGpoint cur;
-
-			if (closed)
-				nsvg__appendPathPoint(r, r->points[0]);
-
-			// Duplicate points -> points2.
-			nsvg__duplicatePoints(r);
-
-			r->npoints = 0;
- 			cur = r->points2[0];
-			nsvg__appendPathPoint(r, cur);
-
-			// Figure out dash offset.
-			allDashLen = 0;
-			for (j = 0; j < shape->strokeDashCount; j++)
-				allDashLen += shape->strokeDashArray[j];
-			if (shape->strokeDashCount & 1)
-				allDashLen *= 2.0f;
-			// Find location inside pattern
-			dashOffset = fmodf(shape->strokeDashOffset, allDashLen);
-			if (dashOffset < 0.0f)
-				dashOffset += allDashLen;
-
-			while (dashOffset > shape->strokeDashArray[idash]) {
-				dashOffset -= shape->strokeDashArray[idash];
-				idash = (idash + 1) % shape->strokeDashCount;
-			}
-			dashLen = (shape->strokeDashArray[idash] - dashOffset) * scale;
-
-			for (j = 1; j < r->npoints2; ) {
-				float dx = r->points2[j].x - cur.x;
-				float dy = r->points2[j].y - cur.y;
-				float dist = sqrtf(dx*dx + dy*dy);
-
-				if ((totalDist + dist) > dashLen) {
-					// Calculate intermediate point
-					float d = (dashLen - totalDist) / dist;
-					float x = cur.x + dx * d;
-					float y = cur.y + dy * d;
-					nsvg__addPathPoint(r, x, y, NSVG_PT_CORNER);
-
-					// Stroke
-					if (r->npoints > 1 && dashState) {
-						nsvg__prepareStroke(r, miterLimit, lineJoin);
-						nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth);
-					}
-					// Advance dash pattern
-					dashState = !dashState;
-					idash = (idash+1) % shape->strokeDashCount;
-					dashLen = shape->strokeDashArray[idash] * scale;
-					// Restart
-					cur.x = x;
-					cur.y = y;
-					cur.flags = NSVG_PT_CORNER;
-					totalDist = 0.0f;
-					r->npoints = 0;
-					nsvg__appendPathPoint(r, cur);
-				} else {
-					totalDist += dist;
-					cur = r->points2[j];
-					nsvg__appendPathPoint(r, cur);
-					j++;
-				}
-			}
-			// Stroke any leftover path
-			if (r->npoints > 1 && dashState)
-				nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth);
-		} else {
-			nsvg__prepareStroke(r, miterLimit, lineJoin);
-			nsvg__expandStroke(r, r->points, r->npoints, closed, lineJoin, lineCap, lineWidth);
-		}
-	}
-}
-
-static int nsvg__cmpEdge(const void *p, const void *q)
-{
-	const NSVGedge* a = (const NSVGedge*)p;
-	const NSVGedge* b = (const NSVGedge*)q;
-
-	if (a->y0 < b->y0) return -1;
-	if (a->y0 > b->y0) return  1;
-	return 0;
-}
-
-
-static NSVGactiveEdge* nsvg__addActive(NSVGrasterizer* r, NSVGedge* e, float startPoint)
-{
-	 NSVGactiveEdge* z;
-
-	if (r->freelist != NULL) {
-		// Restore from freelist.
-		z = r->freelist;
-		r->freelist = z->next;
-	} else {
-		// Alloc new edge.
-		z = (NSVGactiveEdge*)nsvg__alloc(r, sizeof(NSVGactiveEdge));
-		if (z == NULL) return NULL;
-	}
-
-	float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0);
-//	STBTT_assert(e->y0 <= start_point);
-	// round dx down to avoid going too far
-	if (dxdy < 0)
-		z->dx = (int)(-nsvg__roundf(NSVG__FIX * -dxdy));
-	else
-		z->dx = (int)nsvg__roundf(NSVG__FIX * dxdy);
-	z->x = (int)nsvg__roundf(NSVG__FIX * (e->x0 + dxdy * (startPoint - e->y0)));
-//	z->x -= off_x * FIX;
-	z->ey = e->y1;
-	z->next = 0;
-	z->dir = e->dir;
-
-	return z;
-}
-
-static void nsvg__freeActive(NSVGrasterizer* r, NSVGactiveEdge* z)
-{
-	z->next = r->freelist;
-	r->freelist = z;
-}
-
-static void nsvg__fillScanline(unsigned char* scanline, int len, int x0, int x1, int maxWeight, int* xmin, int* xmax)
-{
-	int i = x0 >> NSVG__FIXSHIFT;
-	int j = x1 >> NSVG__FIXSHIFT;
-	if (i < *xmin) *xmin = i;
-	if (j > *xmax) *xmax = j;
-	if (i < len && j >= 0) {
-		if (i == j) {
-			// x0,x1 are the same pixel, so compute combined coverage
-			scanline[i] = (unsigned char)(scanline[i] + ((x1 - x0) * maxWeight >> NSVG__FIXSHIFT));
-		} else {
-			if (i >= 0) // add antialiasing for x0
-				scanline[i] = (unsigned char)(scanline[i] + (((NSVG__FIX - (x0 & NSVG__FIXMASK)) * maxWeight) >> NSVG__FIXSHIFT));
-			else
-				i = -1; // clip
-
-			if (j < len) // add antialiasing for x1
-				scanline[j] = (unsigned char)(scanline[j] + (((x1 & NSVG__FIXMASK) * maxWeight) >> NSVG__FIXSHIFT));
-			else
-				j = len; // clip
-
-			for (++i; i < j; ++i) // fill pixels between x0 and x1
-				scanline[i] = (unsigned char)(scanline[i] + maxWeight);
-		}
-	}
-}
-
-// note: this routine clips fills that extend off the edges... ideally this
-// wouldn't happen, but it could happen if the truetype glyph bounding boxes
-// are wrong, or if the user supplies a too-small bitmap
-static void nsvg__fillActiveEdges(unsigned char* scanline, int len, NSVGactiveEdge* e, int maxWeight, int* xmin, int* xmax, char fillRule)
-{
-	// non-zero winding fill
-	int x0 = 0, w = 0;
-
-	if (fillRule == NSVG_FILLRULE_NONZERO) {
-		// Non-zero
-		while (e != NULL) {
-			if (w == 0) {
-				// if we're currently at zero, we need to record the edge start point
-				x0 = e->x; w += e->dir;
-			} else {
-				int x1 = e->x; w += e->dir;
-				// if we went to zero, we need to draw
-				if (w == 0)
-					nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax);
-			}
-			e = e->next;
-		}
-	} else if (fillRule == NSVG_FILLRULE_EVENODD) {
-		// Even-odd
-		while (e != NULL) {
-			if (w == 0) {
-				// if we're currently at zero, we need to record the edge start point
-				x0 = e->x; w = 1;
-			} else {
-				int x1 = e->x; w = 0;
-				nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax);
-			}
-			e = e->next;
-		}
-	}
-}
-
-static float nsvg__clampf(float a, float mn, float mx) { return a < mn ? mn : (a > mx ? mx : a); }
-
-static unsigned int nsvg__RGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
-{
-	return ((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16) | ((unsigned int)a << 24);
-}
-
-static unsigned int nsvg__lerpRGBA(unsigned int c0, unsigned int c1, float u)
-{
-	int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f);
-	int r = (((c0) & 0xff)*(256-iu) + (((c1) & 0xff)*iu)) >> 8;
-	int g = (((c0>>8) & 0xff)*(256-iu) + (((c1>>8) & 0xff)*iu)) >> 8;
-	int b = (((c0>>16) & 0xff)*(256-iu) + (((c1>>16) & 0xff)*iu)) >> 8;
-	int a = (((c0>>24) & 0xff)*(256-iu) + (((c1>>24) & 0xff)*iu)) >> 8;
-	return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a);
-}
-
-static unsigned int nsvg__applyOpacity(unsigned int c, float u)
-{
-	int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f);
-	int r = (c) & 0xff;
-	int g = (c>>8) & 0xff;
-	int b = (c>>16) & 0xff;
-	int a = (((c>>24) & 0xff)*iu) >> 8;
-	return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a);
-}
-
-static inline int nsvg__div255(int x)
-{
-    return ((x+1) * 257) >> 16;
-}
-
-static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* cover, int x, int y,
-								float tx, float ty, float scale, NSVGcachedPaint* cache)
-{
-
-	if (cache->type == NSVG_PAINT_COLOR) {
-		int i, cr, cg, cb, ca;
-		cr = cache->colors[0] & 0xff;
-		cg = (cache->colors[0] >> 8) & 0xff;
-		cb = (cache->colors[0] >> 16) & 0xff;
-		ca = (cache->colors[0] >> 24) & 0xff;
-
-		for (i = 0; i < count; i++) {
-			int r,g,b;
-			int a = nsvg__div255((int)cover[0] * ca);
-			int ia = 255 - a;
-			// Premultiply
-			r = nsvg__div255(cr * a);
-			g = nsvg__div255(cg * a);
-			b = nsvg__div255(cb * a);
-
-			// Blend over
-			r += nsvg__div255(ia * (int)dst[0]);
-			g += nsvg__div255(ia * (int)dst[1]);
-			b += nsvg__div255(ia * (int)dst[2]);
-			a += nsvg__div255(ia * (int)dst[3]);
-
-			dst[0] = (unsigned char)r;
-			dst[1] = (unsigned char)g;
-			dst[2] = (unsigned char)b;
-			dst[3] = (unsigned char)a;
-
-			cover++;
-			dst += 4;
-		}
-	} else if (cache->type == NSVG_PAINT_LINEAR_GRADIENT) {
-		// TODO: spread modes.
-		// TODO: plenty of opportunities to optimize.
-		float fx, fy, dx, gy;
-		float* t = cache->xform;
-		int i, cr, cg, cb, ca;
-		unsigned int c;
-
-		fx = ((float)x - tx) / scale;
-		fy = ((float)y - ty) / scale;
-		dx = 1.0f / scale;
-
-		for (i = 0; i < count; i++) {
-			int r,g,b,a,ia;
-			gy = fx*t[1] + fy*t[3] + t[5];
-			c = cache->colors[(int)nsvg__clampf(gy*255.0f, 0, 255.0f)];
-			cr = (c) & 0xff;
-			cg = (c >> 8) & 0xff;
-			cb = (c >> 16) & 0xff;
-			ca = (c >> 24) & 0xff;
-
-			a = nsvg__div255((int)cover[0] * ca);
-			ia = 255 - a;
-
-			// Premultiply
-			r = nsvg__div255(cr * a);
-			g = nsvg__div255(cg * a);
-			b = nsvg__div255(cb * a);
-
-			// Blend over
-			r += nsvg__div255(ia * (int)dst[0]);
-			g += nsvg__div255(ia * (int)dst[1]);
-			b += nsvg__div255(ia * (int)dst[2]);
-			a += nsvg__div255(ia * (int)dst[3]);
-
-			dst[0] = (unsigned char)r;
-			dst[1] = (unsigned char)g;
-			dst[2] = (unsigned char)b;
-			dst[3] = (unsigned char)a;
-
-			cover++;
-			dst += 4;
-			fx += dx;
-		}
-	} else if (cache->type == NSVG_PAINT_RADIAL_GRADIENT) {
-		// TODO: spread modes.
-		// TODO: plenty of opportunities to optimize.
-		// TODO: focus (fx,fy)
-		float fx, fy, dx, gx, gy, gd;
-		float* t = cache->xform;
-		int i, cr, cg, cb, ca;
-		unsigned int c;
-
-		fx = ((float)x - tx) / scale;
-		fy = ((float)y - ty) / scale;
-		dx = 1.0f / scale;
-
-		for (i = 0; i < count; i++) {
-			int r,g,b,a,ia;
-			gx = fx*t[0] + fy*t[2] + t[4];
-			gy = fx*t[1] + fy*t[3] + t[5];
-			gd = sqrtf(gx*gx + gy*gy);
-			c = cache->colors[(int)nsvg__clampf(gd*255.0f, 0, 255.0f)];
-			cr = (c) & 0xff;
-			cg = (c >> 8) & 0xff;
-			cb = (c >> 16) & 0xff;
-			ca = (c >> 24) & 0xff;
-
-			a = nsvg__div255((int)cover[0] * ca);
-			ia = 255 - a;
-
-			// Premultiply
-			r = nsvg__div255(cr * a);
-			g = nsvg__div255(cg * a);
-			b = nsvg__div255(cb * a);
-
-			// Blend over
-			r += nsvg__div255(ia * (int)dst[0]);
-			g += nsvg__div255(ia * (int)dst[1]);
-			b += nsvg__div255(ia * (int)dst[2]);
-			a += nsvg__div255(ia * (int)dst[3]);
-
-			dst[0] = (unsigned char)r;
-			dst[1] = (unsigned char)g;
-			dst[2] = (unsigned char)b;
-			dst[3] = (unsigned char)a;
-
-			cover++;
-			dst += 4;
-			fx += dx;
-		}
-	}
-}
-
-static void nsvg__rasterizeSortedEdges(NSVGrasterizer *r, float tx, float ty, float scale, NSVGcachedPaint* cache, char fillRule)
-{
-	NSVGactiveEdge *active = NULL;
-	int y, s;
-	int e = 0;
-	int maxWeight = (255 / NSVG__SUBSAMPLES);  // weight per vertical scanline
-	int xmin, xmax;
-
-	for (y = 0; y < r->height; y++) {
-		memset(r->scanline, 0, r->width);
-		xmin = r->width;
-		xmax = 0;
-		for (s = 0; s < NSVG__SUBSAMPLES; ++s) {
-			// find center of pixel for this scanline
-			float scany = (float)(y*NSVG__SUBSAMPLES + s) + 0.5f;
-			NSVGactiveEdge **step = &active;
-
-			// update all active edges;
-			// remove all active edges that terminate before the center of this scanline
-			while (*step) {
-				NSVGactiveEdge *z = *step;
-				if (z->ey <= scany) {
-					*step = z->next; // delete from list
-//					NSVG__assert(z->valid);
-					nsvg__freeActive(r, z);
-				} else {
-					z->x += z->dx; // advance to position for current scanline
-					step = &((*step)->next); // advance through list
-				}
-			}
-
-			// resort the list if needed
-			for (;;) {
-				int changed = 0;
-				step = &active;
-				while (*step && (*step)->next) {
-					if ((*step)->x > (*step)->next->x) {
-						NSVGactiveEdge* t = *step;
-						NSVGactiveEdge* q = t->next;
-						t->next = q->next;
-						q->next = t;
-						*step = q;
-						changed = 1;
-					}
-					step = &(*step)->next;
-				}
-				if (!changed) break;
-			}
-
-			// insert all edges that start before the center of this scanline -- omit ones that also end on this scanline
-			while (e < r->nedges && r->edges[e].y0 <= scany) {
-				if (r->edges[e].y1 > scany) {
-					NSVGactiveEdge* z = nsvg__addActive(r, &r->edges[e], scany);
-					if (z == NULL) break;
-					// find insertion point
-					if (active == NULL) {
-						active = z;
-					} else if (z->x < active->x) {
-						// insert at front
-						z->next = active;
-						active = z;
-					} else {
-						// find thing to insert AFTER
-						NSVGactiveEdge* p = active;
-						while (p->next && p->next->x < z->x)
-							p = p->next;
-						// at this point, p->next->x is NOT < z->x
-						z->next = p->next;
-						p->next = z;
-					}
-				}
-				e++;
-			}
-
-			// now process all active edges in non-zero fashion
-			if (active != NULL)
-				nsvg__fillActiveEdges(r->scanline, r->width, active, maxWeight, &xmin, &xmax, fillRule);
-		}
-		// Blit
-		if (xmin < 0) xmin = 0;
-		if (xmax > r->width-1) xmax = r->width-1;
-		if (xmin <= xmax) {
-			nsvg__scanlineSolid(&r->bitmap[y * r->stride] + xmin*4, xmax-xmin+1, &r->scanline[xmin], xmin, y, tx,ty, scale, cache);
-		}
-	}
-
-}
-
-static void nsvg__unpremultiplyAlpha(unsigned char* image, int w, int h, int stride)
-{
-	int x,y;
-
-	// Unpremultiply
-	for (y = 0; y < h; y++) {
-		unsigned char *row = &image[y*stride];
-		for (x = 0; x < w; x++) {
-			int r = row[0], g = row[1], b = row[2], a = row[3];
-			if (a != 0) {
-				row[0] = (unsigned char)(r*255/a);
-				row[1] = (unsigned char)(g*255/a);
-				row[2] = (unsigned char)(b*255/a);
-			}
-			row += 4;
-		}
-	}
-
-	// Defringe
-	for (y = 0; y < h; y++) {
-		unsigned char *row = &image[y*stride];
-		for (x = 0; x < w; x++) {
-			int r = 0, g = 0, b = 0, a = row[3], n = 0;
-			if (a == 0) {
-				if (x-1 > 0 && row[-1] != 0) {
-					r += row[-4];
-					g += row[-3];
-					b += row[-2];
-					n++;
-				}
-				if (x+1 < w && row[7] != 0) {
-					r += row[4];
-					g += row[5];
-					b += row[6];
-					n++;
-				}
-				if (y-1 > 0 && row[-stride+3] != 0) {
-					r += row[-stride];
-					g += row[-stride+1];
-					b += row[-stride+2];
-					n++;
-				}
-				if (y+1 < h && row[stride+3] != 0) {
-					r += row[stride];
-					g += row[stride+1];
-					b += row[stride+2];
-					n++;
-				}
-				if (n > 0) {
-					row[0] = (unsigned char)(r/n);
-					row[1] = (unsigned char)(g/n);
-					row[2] = (unsigned char)(b/n);
-				}
-			}
-			row += 4;
-		}
-	}
-}
-
-
-static void nsvg__initPaint(NSVGcachedPaint* cache, NSVGpaint* paint, float opacity)
-{
-	int i, j;
-	NSVGgradient* grad;
-
-	cache->type = paint->type;
-
-	if (paint->type == NSVG_PAINT_COLOR) {
-		cache->colors[0] = nsvg__applyOpacity(paint->color, opacity);
-		return;
-	}
-
-	grad = paint->gradient;
-
-	cache->spread = grad->spread;
-	memcpy(cache->xform, grad->xform, sizeof(float)*6);
-
-	if (grad->nstops == 0) {
-		for (i = 0; i < 256; i++)
-			cache->colors[i] = 0;
-	} else if (grad->nstops == 1) {
-		for (i = 0; i < 256; i++)
-			cache->colors[i] = nsvg__applyOpacity(grad->stops[i].color, opacity);
-	} else {
-		unsigned int ca, cb = 0;
-		float ua, ub, du, u;
-		int ia, ib, count;
-
-		ca = nsvg__applyOpacity(grad->stops[0].color, opacity);
-		ua = nsvg__clampf(grad->stops[0].offset, 0, 1);
-		ub = nsvg__clampf(grad->stops[grad->nstops-1].offset, ua, 1);
-		ia = (int)(ua * 255.0f);
-		ib = (int)(ub * 255.0f);
-		for (i = 0; i < ia; i++) {
-			cache->colors[i] = ca;
-		}
-
-		for (i = 0; i < grad->nstops-1; i++) {
-			ca = nsvg__applyOpacity(grad->stops[i].color, opacity);
-			cb = nsvg__applyOpacity(grad->stops[i+1].color, opacity);
-			ua = nsvg__clampf(grad->stops[i].offset, 0, 1);
-			ub = nsvg__clampf(grad->stops[i+1].offset, 0, 1);
-			ia = (int)(ua * 255.0f);
-			ib = (int)(ub * 255.0f);
-			count = ib - ia;
-			if (count <= 0) continue;
-			u = 0;
-			du = 1.0f / (float)count;
-			for (j = 0; j < count; j++) {
-				cache->colors[ia+j] = nsvg__lerpRGBA(ca,cb,u);
-				u += du;
-			}
-		}
-
-		for (i = ib; i < 256; i++)
-			cache->colors[i] = cb;
-	}
-
-}
-
-/*
-static void dumpEdges(NSVGrasterizer* r, const char* name)
-{
-	float xmin = 0, xmax = 0, ymin = 0, ymax = 0;
-	NSVGedge *e = NULL;
-	int i;
-	if (r->nedges == 0) return;
-	FILE* fp = fopen(name, "w");
-	if (fp == NULL) return;
-
-	xmin = xmax = r->edges[0].x0;
-	ymin = ymax = r->edges[0].y0;
-	for (i = 0; i < r->nedges; i++) {
-		e = &r->edges[i];
-		xmin = nsvg__minf(xmin, e->x0);
-		xmin = nsvg__minf(xmin, e->x1);
-		xmax = nsvg__maxf(xmax, e->x0);
-		xmax = nsvg__maxf(xmax, e->x1);
-		ymin = nsvg__minf(ymin, e->y0);
-		ymin = nsvg__minf(ymin, e->y1);
-		ymax = nsvg__maxf(ymax, e->y0);
-		ymax = nsvg__maxf(ymax, e->y1);
-	}
-
-	fprintf(fp, "<svg viewBox=\"%f %f %f %f\" xmlns=\"http://www.w3.org/2000/svg\">", xmin, ymin, (xmax - xmin), (ymax - ymin));
-
-	for (i = 0; i < r->nedges; i++) {
-		e = &r->edges[i];
-		fprintf(fp ,"<line x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\" style=\"stroke:#000;\" />", e->x0,e->y0, e->x1,e->y1);
-	}
-
-	for (i = 0; i < r->npoints; i++) {
-		if (i+1 < r->npoints)
-			fprintf(fp ,"<line x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\" style=\"stroke:#f00;\" />", r->points[i].x, r->points[i].y, r->points[i+1].x, r->points[i+1].y);
-		fprintf(fp ,"<circle cx=\"%f\" cy=\"%f\" r=\"1\" style=\"fill:%s;\" />", r->points[i].x, r->points[i].y, r->points[i].flags == 0 ? "#f00" : "#0f0");
-	}
-
-	fprintf(fp, "</svg>");
-	fclose(fp);
-}
-*/
-
-void nsvgRasterize(NSVGrasterizer* r,
-				   NSVGimage* image, float tx, float ty, float scale,
-				   unsigned char* dst, int w, int h, int stride)
-{
-	NSVGshape *shape = NULL;
-	NSVGedge *e = NULL;
-	NSVGcachedPaint cache;
-	int i;
-
-	r->bitmap = dst;
-	r->width = w;
-	r->height = h;
-	r->stride = stride;
-
-	if (w > r->cscanline) {
-		r->cscanline = w;
-		r->scanline = (unsigned char*)realloc(r->scanline, w);
-		if (r->scanline == NULL) return;
-	}
-
-	for (i = 0; i < h; i++)
-		memset(&dst[i*stride], 0, w*4);
-
-	for (shape = image->shapes; shape != NULL; shape = shape->next) {
-		if (!(shape->flags & NSVG_FLAGS_VISIBLE))
-			continue;
-
-		if (shape->fill.type != NSVG_PAINT_NONE) {
-			nsvg__resetPool(r);
-			r->freelist = NULL;
-			r->nedges = 0;
-
-			nsvg__flattenShape(r, shape, scale);
-
-			// Scale and translate edges
-			for (i = 0; i < r->nedges; i++) {
-				e = &r->edges[i];
-				e->x0 = tx + e->x0;
-				e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES;
-				e->x1 = tx + e->x1;
-				e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES;
-			}
-
-			// Rasterize edges
-			if (r->nedges != 0)
-				qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge);
-
-			// now, traverse the scanlines and find the intersections on each scanline, use non-zero rule
-			nsvg__initPaint(&cache, &shape->fill, shape->opacity);
-
-			nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, shape->fillRule);
-		}
-		if (shape->stroke.type != NSVG_PAINT_NONE && (shape->strokeWidth * scale) > 0.01f) {
-			nsvg__resetPool(r);
-			r->freelist = NULL;
-			r->nedges = 0;
-
-			nsvg__flattenShapeStroke(r, shape, scale);
-
-//			dumpEdges(r, "edge.svg");
-
-			// Scale and translate edges
-			for (i = 0; i < r->nedges; i++) {
-				e = &r->edges[i];
-				e->x0 = tx + e->x0;
-				e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES;
-				e->x1 = tx + e->x1;
-				e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES;
-			}
-
-			// Rasterize edges
-			if (r->nedges != 0)
-				qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge);
-
-			// now, traverse the scanlines and find the intersections on each scanline, use non-zero rule
-			nsvg__initPaint(&cache, &shape->stroke, shape->opacity);
-
-			nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, NSVG_FILLRULE_NONZERO);
-		}
-	}
-
-	nsvg__unpremultiplyAlpha(dst, w, h, stride);
-
-	r->bitmap = NULL;
-	r->width = 0;
-	r->height = 0;
-	r->stride = 0;
-}
-
-#endif // NANOSVGRAST_IMPLEMENTATION
-
-#endif // NANOSVGRAST_H

+ 57 - 0
svg.mod/source.bmx

@@ -0,0 +1,57 @@
+' Copyright (c) 2024 Bruce A Henderson
+' 
+' 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.
+'
+SuperStrict
+
+Import "lunasvg/include/*.h"
+Import "lunasvg/3rdparty/plutovg/*.h"
+
+Import "lunasvg/source/lunasvg.cpp"
+Import "lunasvg/source/element.cpp"
+Import "lunasvg/source/property.cpp"
+Import "lunasvg/source/parser.cpp"
+Import "lunasvg/source/layoutcontext.cpp"
+Import "lunasvg/source/canvas.cpp"
+Import "lunasvg/source/clippathelement.cpp"
+Import "lunasvg/source/defselement.cpp"
+Import "lunasvg/source/gelement.cpp"
+Import "lunasvg/source/geometryelement.cpp"
+Import "lunasvg/source/graphicselement.cpp"
+Import "lunasvg/source/maskelement.cpp"
+Import "lunasvg/source/markerelement.cpp"
+Import "lunasvg/source/paintelement.cpp"
+Import "lunasvg/source/stopelement.cpp"
+Import "lunasvg/source/styledelement.cpp"
+Import "lunasvg/source/styleelement.cpp"
+Import "lunasvg/source/svgelement.cpp"
+Import "lunasvg/source/symbolelement.cpp"
+Import "lunasvg/source/useelement.cpp"
+
+Import "lunasvg/3rdparty/plutovg/plutovg.c"
+Import "lunasvg/3rdparty/plutovg/plutovg-paint.c"
+Import "lunasvg/3rdparty/plutovg/plutovg-geometry.c"
+Import "lunasvg/3rdparty/plutovg/plutovg-blend.c"
+Import "lunasvg/3rdparty/plutovg/plutovg-rle.c"
+Import "lunasvg/3rdparty/plutovg/plutovg-dash.c"
+Import "lunasvg/3rdparty/plutovg/plutovg-ft-raster.c"
+Import "lunasvg/3rdparty/plutovg/plutovg-ft-stroker.c"
+Import "lunasvg/3rdparty/plutovg/plutovg-ft-math.c"
+
+Import "glue.cpp"

+ 96 - 75
svg.mod/svg.bmx

@@ -1,21 +1,23 @@
-' Copyright (c) 2022-2024 Bruce A Henderson
+' Copyright (c) 2024 Bruce A Henderson
 ' 
-' This software is provided 'as-is', without any express or implied
-' warranty. In no event will the authors be held liable for any damages
-' arising from the use of this software.
+' 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:
 ' 
-' Permission is granted to anyone to use this software for any purpose,
-' including commercial applications, and to alter it and redistribute it
-' freely, subject to the following restrictions:
-' 
-' 1. The origin of this software must not be misrepresented; you must not
-'    claim that you wrote the original software. If you use this software
-'    in a product, an acknowledgment in the product documentation would be
-'    appreciated but is not required.
-' 2. Altered source versions must be plainly marked as such, and must not be
-'    misrepresented as being the original software.
-' 3. This notice may not be removed or altered from any source distribution.
+' 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.
+'
 SuperStrict
 
 Rem
@@ -25,11 +27,13 @@ The SVG loader module provides the ability to load SVG format #pixmaps.
 End Rem
 Module Image.SVG
 
-ModuleInfo "Version: 1.02"
-ModuleInfo "Author: 2013-14 Mikko Mononen"
-ModuleInfo "License: ZLib/PNG License"
+ModuleInfo "Version: 1.03"
+ModuleInfo "Author: 2020 Nwutobo Samuel Ugochukwu <[email protected]>"
+ModuleInfo "License: MIT License"
 ModuleInfo "Credit: Adapted for BlitzMax by Bruce A Henderson"
 
+ModuleInfo "History: 1.03"
+ModuleInfo "History: Changed to use lunasvg, which supports more SVG features."
 ModuleInfo "History: 1.02"
 ModuleInfo "History: Added support for scaling images into a pixmap of a specified size."
 ModuleInfo "History: 1.01"
@@ -37,115 +41,114 @@ ModuleInfo "History: Update to nanosvg 706eb06"
 ModuleInfo "History: 1.00"
 ModuleInfo "History: Initial Release. nanosvg"
 
-Import BRL.Pixmap
+ModuleInfo "CPP_OPTS: -DLUNASVG_BUILD_STATIC"
+ModuleInfo "CPP_OPTS: -std=c++11"
+ModuleInfo "C_OPTS: -std=c11"
 
+Import BRL.Pixmap
 Import "common.bmx"
 
+
 Rem
 bbdoc: An SVG image.
 End Rem
 Type TSvgImage
 
-	Field svgImage:SNSVGimage Ptr
-
-	Method Free()
-		If svgImage Then
-			nsvgDelete(svgImage)
-			svgImage = Null
-		End If
-	End Method
-
-	Method Delete()
-		Free()
-	End Method
+	Field svgDocument:Byte Ptr
 
-	Method Load:TPixmap(stream:TStream, units:String, dpi:Float)
+	Rem
+	bbdoc: Loads an SVG image from a stream.
+	End Rem
+	Method Load:TPixmap(stream:TStream)
 		Local data:Byte[] = LoadByteArray( stream )
-		Local u:Byte Ptr = units.ToCString()
-		svgImage = nsvgParse(data, u, dpi)
-		MemFree(u)
+		
+		svgDocument = bmx_svg_loadFromData(data, data.Length)
 
-		If Not svgImage Or Not svgImage.shapes Then
+		If Not svgDocument
 			Return Null
 		End If
 
-		Return GetPixmap(Int(svgImage.width), Int(svgImage.height))
+		Return GetPixmap(Int(Width()), Int(Height()))
 	End Method
 
 	Rem
-	bbdoc: Loads the SVG image from the specified object.
-	about: The units and DPI are used to determine the size of the image.
+	bbdoc: Loads an SVG image from a file, either a path or a stream.
 	End Rem
-	Function LoadImage:TSvgImage(obj:Object, units:String = "px", dpi:Float = 96)
+	Function LoadImage:TSvgImage(obj:Object)
 		Local image:TSvgImage = New TSvgImage()
 
 		Local data:Byte[] = LoadByteArray( obj )
-		Local u:Byte Ptr = units.ToCString()
-		image.svgImage = nsvgParse(data, u, dpi)
-		MemFree(u)
+
+		image.svgDocument = bmx_svg_loadFromData(data, data.Length)
 
 		Return image
 	End Function
 
 	Rem
-	bbdoc: Returns the dimensions of the SVG image.
-	about: The width and height are in the units specified when the SVG image was loaded.
+	bbdoc: Loads an SVG image from a string.
 	End Rem
-	Method Dimensions(width:Float Var, height:Float Var)
-		If svgImage Then
-			width = svgImage.width
-			height = svgImage.height
-		End If
-	End Method
+	Function LoadFromData:TSvgImage(txt:String)
+		Local image:TSvgImage = New TSvgImage()
+
+		Local s:Byte Ptr = txt.ToUTF8String()
+		Local length:Int = strlen_(s)
+
+		image.svgDocument = bmx_svg_loadFromData(s, length)
+
+		MemFree(s)
+
+		Return image
+	End Function
 
 	Rem
 	bbdoc: Returns the width of the SVG image.
-	about: The width is in the units specified when the SVG image was loaded.
 	End Rem
-	Method Width:Float()
-		If svgImage Then
-			Return svgImage.width
+	Method Width:Double()
+		If svgDocument Then
+			Return bmx_svg_width(svgDocument)
 		End If
-		Return 0
 	End Method
 
 	Rem
 	bbdoc: Returns the height of the SVG image.
-	about: The height is in the units specified when the SVG image was loaded.
 	End Rem
-	Method Height:Float()
-		If svgImage Then
-			Return svgImage.height
+	Method Height:Double()
+		If svgDocument Then
+			Return bmx_svg_height(svgDocument)
 		End If
-		Return 0
 	End Method
 
 	Rem
-	bbdoc: Gets a rasterized representation of the SVG image of the specified size and scale.
-	about: A scale of 1 will render the image at the original size - which can be determined by calling the #Width, #Height or #Dimensions methods.
+	bbdoc: Gets a rasterized representation of the SVG image of the specified size.
 	End Rem
-	Method GetPixmap:TPixmap(width:Int, height:Int, scale:Float = 1.0)
-		If svgImage Then
+	Method GetPixmap:TPixmap(width:Int, height:Int)
+		If svgDocument Then
 			Local pix:TPixmap = CreatePixmap( width, height, PF_RGBA8888, 4 )
 
-			Local raster:Byte Ptr = nsvgCreateRasterizer()
-
-			nsvgRasterize(raster, svgImage, 0, 0, scale, pix.pixels, pix.width, pix.height, pix.pitch);
-
-			nsvgDeleteRasterizer(raster)
+			bmx_svg_renderToPixmap(svgDocument, pix.pixels, pix.width, pix.height, pix.pitch)
 
 			Return pix
 		End If
 	End Method
 
+	Rem
+	bbdoc: Returns the dimensions of the SVG image.
+	End Rem
+	Method Dimensions(width:Double Var, height:Double Var)
+		If svgDocument Then
+			width = Self.Width()
+			height = Self.Height()
+		End If
+	End Method
+
 	Rem
 	bbdoc: Gets a rasterized representation of the SVG image that fits within the specified maximum width and height.
 	about: The aspect ratio of the image is maintained.
 	If the height is not specified, it will be the same as the width.
 	End Rem
 	Method FitPixmap:TPixmap(maxWidth:Int, maxHeight:Int = 0)
-		Local width:Float
-		Local height:Float
+		Local width:Double
+		Local height:Double
 		Dimensions(width, height)
 
 		If maxHeight = 0 Then
@@ -154,19 +157,37 @@ Type TSvgImage
 
 		Local ratio:Float = Min(maxWidth / width, maxHeight / height)
 
-		Return GetPixmap(Int(width * ratio), Int(height * ratio), ratio)
+		Return GetPixmap(Int(width * ratio), Int(height * ratio))
 	End Method
 
+	Rem
+	bbdoc: Sets the dimensions of the SVG image.
+	End Rem
+	Method SetDimensions(width:Double, height:Double)
+		If svgDocument
+			bmx_svg_setDimensions(svgDocument, width, height)
+		End If
+	End Method
+
+	Method Free()
+		If svgDocument
+			bmx_svg_free(svgDocument)
+			svgDocument = Null
+		End If
+	End Method
+
+	Method Delete()
+		Free()
+	End Method
 End Type
 
 Private
 
-
 Type TPixmapLoaderSvg Extends TPixmapLoader
 
 	Method LoadPixmap:TPixmap( stream:TStream ) Override
 		Local image:TSvgImage = New TSvgImage()
-		Local pix:TPixmap = image.Load(stream, "px", 96)
+		Local pix:TPixmap = image.Load(stream)
 		image.Free()
 		Return pix
 	End Method

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.