From 6113f9b94ea93eb73fa05c59e9547331a8173d06 Mon Sep 17 00:00:00 2001 From: Jordan Isaacs Date: Mon, 13 Nov 2023 23:25:05 -0800 Subject: [PATCH] particles/icon: init Introduce a new icon particle. It follows the icon spec (https://specifications.freedesktop.org/icon-theme-spec/latest/index.html). Rendering logic is taken from fuzzel (using nanosvg + libpng), while loading logic is taken from sway. Standard usage is with `use-tag = false` which expands the provided string template and then loads the string as the icon name. There are settings to manually override the base paths, themes, etc. The second usage which is required for tray support is a special icon tag that transfers raw pixmaps. With `use-tag = true` it first expands the string, and then uses that output to find an icon pixmap tag. To reduce memory usage, themes are reference counted so they can be passed down the configuration stack without having to load them in multiple times. For programmability, a fallback particle can be specified if no icon/tag is found `fallback: ...`. And the new icon pixmap tag can be existence checked in map conditions using `+{tag_name}`. Future work to be done in follow up diffs: 1. Icon caching. Currently performs an icon lookup on each instantiation & a render on each refresh. 2. Theme caching. Changing theme directories results in a new "theme collection" being created resulting in the possibility of duplicated theme loading. --- 3rd-party/nanosvg/CMakeLists.txt | 75 + 3rd-party/nanosvg/Config.cmake.in | 5 + 3rd-party/nanosvg/LICENSE.txt | 18 + 3rd-party/nanosvg/README.md | 112 + 3rd-party/nanosvg/example/23.svg | 730 +++++ 3rd-party/nanosvg/example/drawing.svg | 97 + 3rd-party/nanosvg/example/example1.c | 258 ++ 3rd-party/nanosvg/example/example2.c | 69 + 3rd-party/nanosvg/example/nano.svg | 27 + 3rd-party/nanosvg/example/screenshot-1.png | Bin 0 -> 60837 bytes 3rd-party/nanosvg/example/screenshot-2.png | Bin 0 -> 158111 bytes 3rd-party/nanosvg/example/stb_image_write.h | 511 +++ 3rd-party/nanosvg/premake4.lua | 56 + 3rd-party/nanosvg/src/nanosvg.h | 3098 +++++++++++++++++++ 3rd-party/nanosvg/src/nanosvgrast.h | 1458 +++++++++ PKGBUILD | 1 + bar/bar.c | 1 + bar/private.h | 2 + config-verify.c | 24 + config-verify.h | 2 + config.c | 146 +- config.h | 5 + icon.c | 968 ++++++ icon.h | 122 + meson.build | 19 +- meson_options.txt | 2 + nanosvg.c | 6 + nanosvg/nanosvg.h | 1 + nanosvg/nanosvgrast.h | 1 + nanosvgrast.c | 6 + particle.c | 12 +- particle.h | 13 +- particles/icon.c | 206 ++ particles/list.c | 5 +- particles/map.c | 19 +- particles/map.h | 1 + particles/map.l | 1 + particles/map.y | 21 +- particles/meson.build | 3 +- particles/progress-bar.c | 4 + particles/ramp.c | 5 +- plugin.c | 2 + png-yambar.h | 5 + png.c | 171 + stringop.c | 42 + stringop.h | 12 + svg.c | 61 + svg.h | 5 + tag.c | 88 +- tag.h | 17 + xdg.c | 0 51 files changed, 8473 insertions(+), 40 deletions(-) create mode 100644 3rd-party/nanosvg/CMakeLists.txt create mode 100644 3rd-party/nanosvg/Config.cmake.in create mode 100644 3rd-party/nanosvg/LICENSE.txt create mode 100644 3rd-party/nanosvg/README.md create mode 100644 3rd-party/nanosvg/example/23.svg create mode 100644 3rd-party/nanosvg/example/drawing.svg create mode 100644 3rd-party/nanosvg/example/example1.c create mode 100644 3rd-party/nanosvg/example/example2.c create mode 100644 3rd-party/nanosvg/example/nano.svg create mode 100644 3rd-party/nanosvg/example/screenshot-1.png create mode 100644 3rd-party/nanosvg/example/screenshot-2.png create mode 100644 3rd-party/nanosvg/example/stb_image_write.h create mode 100644 3rd-party/nanosvg/premake4.lua create mode 100644 3rd-party/nanosvg/src/nanosvg.h create mode 100644 3rd-party/nanosvg/src/nanosvgrast.h create mode 100644 icon.c create mode 100644 icon.h create mode 100644 nanosvg.c create mode 100644 nanosvg/nanosvg.h create mode 100644 nanosvg/nanosvgrast.h create mode 100644 nanosvgrast.c create mode 100644 particles/icon.c create mode 100644 png-yambar.h create mode 100644 png.c create mode 100644 stringop.c create mode 100644 stringop.h create mode 100644 svg.c create mode 100644 svg.h create mode 100644 xdg.c diff --git a/3rd-party/nanosvg/CMakeLists.txt b/3rd-party/nanosvg/CMakeLists.txt new file mode 100644 index 0000000..190b5da --- /dev/null +++ b/3rd-party/nanosvg/CMakeLists.txt @@ -0,0 +1,75 @@ +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 $) +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} +) \ No newline at end of file diff --git a/3rd-party/nanosvg/Config.cmake.in b/3rd-party/nanosvg/Config.cmake.in new file mode 100644 index 0000000..9311d80 --- /dev/null +++ b/3rd-party/nanosvg/Config.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/NanoSVGTargets.cmake) + include("${CMAKE_CURRENT_LIST_DIR}/NanoSVGTargets.cmake") +endif () \ No newline at end of file diff --git a/3rd-party/nanosvg/LICENSE.txt b/3rd-party/nanosvg/LICENSE.txt new file mode 100644 index 0000000..6fde401 --- /dev/null +++ b/3rd-party/nanosvg/LICENSE.txt @@ -0,0 +1,18 @@ +Copyright (c) 2013-14 Mikko Mononen memon@inside.org + +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. + diff --git a/3rd-party/nanosvg/README.md b/3rd-party/nanosvg/README.md new file mode 100644 index 0000000..b468fba --- /dev/null +++ b/3rd-party/nanosvg/README.md @@ -0,0 +1,112 @@ +*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 +#include +#include +#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 +#include +#include +#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) diff --git a/3rd-party/nanosvg/example/23.svg b/3rd-party/nanosvg/example/23.svg new file mode 100644 index 0000000..983e570 --- /dev/null +++ b/3rd-party/nanosvg/example/23.svg @@ -0,0 +1,730 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/3rd-party/nanosvg/example/drawing.svg b/3rd-party/nanosvg/example/drawing.svg new file mode 100644 index 0000000..428a8e1 --- /dev/null +++ b/3rd-party/nanosvg/example/drawing.svg @@ -0,0 +1,97 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/3rd-party/nanosvg/example/example1.c b/3rd-party/nanosvg/example/example1.c new file mode 100644 index 0000000..100831b --- /dev/null +++ b/3rd-party/nanosvg/example/example1.c @@ -0,0 +1,258 @@ +// +// Copyright (c) 2013 Mikko Mononen memon@inside.org +// +// 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 +#include +#include +#include + +#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; +} diff --git a/3rd-party/nanosvg/example/example2.c b/3rd-party/nanosvg/example/example2.c new file mode 100644 index 0000000..9ae9b59 --- /dev/null +++ b/3rd-party/nanosvg/example/example2.c @@ -0,0 +1,69 @@ +// +// Copyright (c) 2013 Mikko Mononen memon@inside.org +// +// 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 +#include +#include +#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; +} diff --git a/3rd-party/nanosvg/example/nano.svg b/3rd-party/nanosvg/example/nano.svg new file mode 100644 index 0000000..15635a1 --- /dev/null +++ b/3rd-party/nanosvg/example/nano.svg @@ -0,0 +1,27 @@ + + + + + + diff --git a/3rd-party/nanosvg/example/screenshot-1.png b/3rd-party/nanosvg/example/screenshot-1.png new file mode 100644 index 0000000000000000000000000000000000000000..f6526cfb47c1be351562c5884840f27b4eeb65d8 GIT binary patch literal 60837 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

`(r_ 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 z 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 iusA{#cX88zZrDcxpL#2*FDu~ze;TUVD& +#include +#include +#include +#include + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +static void writefv(FILE *f, const char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = (unsigned char) va_arg(v, int); fputc(x,f); break; } + case '2': { int x = va_arg(v,int); unsigned char b[2]; + b[0] = (unsigned char) x; b[1] = (unsigned char) (x>>8); + fwrite(b,2,1,f); break; } + case '4': { stbiw_uint32 x = va_arg(v,int); unsigned char b[4]; + b[0]=(unsigned char)x; b[1]=(unsigned char)(x>>8); + b[2]=(unsigned char)(x>>16); b[3]=(unsigned char)(x>>24); + fwrite(b,4,1,f); break; } + default: + assert(0); + return; + } + } +} + +static void write3(FILE *f, unsigned char a, unsigned char b, unsigned char c) +{ + unsigned char arr[3]; + arr[0] = a, arr[1] = b, arr[2] = c; + fwrite(arr, 3, 1, f); +} + +static void write_pixels(FILE *f, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad) +{ + unsigned char bg[3] = { 255, 0, 255}, px[3]; + stbiw_uint32 zero = 0; + int i,j,k, j_end; + + if (y <= 0) + return; + + if (vdir < 0) + j_end = -1, j = y-1; + else + j_end = y, j = 0; + + for (; j != j_end; j += vdir) { + for (i=0; i < x; ++i) { + unsigned char *d = (unsigned char *) data + (j*x+i)*comp; + if (write_alpha < 0) + fwrite(&d[comp-1], 1, 1, f); + switch (comp) { + case 1: + case 2: write3(f, d[0],d[0],d[0]); + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k=0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3])/255; + write3(f, px[1-rgb_dir],px[1],px[1+rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + write3(f, d[1-rgb_dir],d[1],d[1+rgb_dir]); + break; + } + if (write_alpha > 0) + fwrite(&d[comp-1], 1, 1, f); + } + fwrite(&zero,scanline_pad,1,f); + } +} + +static int outfile(char const *filename, int rgb_dir, int vdir, int x, int y, int comp, void *data, int alpha, int pad, const char *fmt, ...) +{ + FILE *f; + if (y < 0 || x < 0) return 0; + f = fopen(filename, "wb"); + if (f) { + va_list v; + va_start(v, fmt); + writefv(f, fmt, v); + va_end(v); + write_pixels(f,rgb_dir,vdir,x,y,comp,data,alpha,pad); + fclose(f); + } + return f != NULL; +} + +int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) +{ + int pad = (-x*3) & 3; + return outfile(filename,-1,-1,x,y,comp,(void *) data,0,pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header + 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header +} + +int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) +{ + int has_alpha = !(comp & 1); + return outfile(filename, -1,-1, x, y, comp, (void *) data, has_alpha, 0, + "111 221 2222 11", 0,0,2, 0,0,0, 0,0,x,y, 24+8*has_alpha, 8*has_alpha); +} + +// stretchy buffer; stbi__sbpush() == vector<>::push_back() -- stbi__sbcount() == vector<>::size() +#define stbi__sbraw(a) ((int *) (a) - 2) +#define stbi__sbm(a) stbi__sbraw(a)[0] +#define stbi__sbn(a) stbi__sbraw(a)[1] + +#define stbi__sbneedgrow(a,n) ((a)==0 || stbi__sbn(a)+n >= stbi__sbm(a)) +#define stbi__sbmaybegrow(a,n) (stbi__sbneedgrow(a,(n)) ? stbi__sbgrow(a,n) : 0) +#define stbi__sbgrow(a,n) stbi__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define stbi__sbpush(a, v) (stbi__sbmaybegrow(a,1), (a)[stbi__sbn(a)++] = (v)) +#define stbi__sbcount(a) ((a) ? stbi__sbn(a) : 0) +#define stbi__sbfree(a) ((a) ? free(stbi__sbraw(a)),0 : 0) + +static void *stbi__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stbi__sbm(*arr)+increment : increment+1; + void *p = realloc(*arr ? stbi__sbraw(*arr) : 0, itemsize * m + sizeof(int)*2); + assert(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stbi__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbi__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + stbi__sbpush(data, (unsigned char) *bitbuffer); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbi__zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbi__zlib_countm(unsigned char *a, unsigned char *b, int limit) +{ + int i; + for (i=0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int stbi__zhash(unsigned char *data) +{ + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define stbi__zlib_flush() (out = stbi__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbi__zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbi__zlib_flush()) +#define stbi__zlib_huffa(b,c) stbi__zlib_add(stbi__zlib_bitrev(b,c),c) +// default huffman tables +#define stbi__zlib_huff1(n) stbi__zlib_huffa(0x30 + (n), 8) +#define stbi__zlib_huff2(n) stbi__zlib_huffa(0x190 + (n)-144, 9) +#define stbi__zlib_huff3(n) stbi__zlib_huffa(0 + (n)-256,7) +#define stbi__zlib_huff4(n) stbi__zlib_huffa(0xc0 + (n)-280,8) +#define stbi__zlib_huff(n) ((n) <= 143 ? stbi__zlib_huff1(n) : (n) <= 255 ? stbi__zlib_huff2(n) : (n) <= 279 ? stbi__zlib_huff3(n) : stbi__zlib_huff4(n)) +#define stbi__zlib_huffb(n) ((n) <= 143 ? stbi__zlib_huff1(n) : stbi__zlib_huff2(n)) + +#define stbi__ZHASH 16384 + +unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ + static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; + static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; + static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; + unsigned int bitbuf=0; + int i,j, bitcount=0; + unsigned char *out = NULL; + unsigned char **hash_table[stbi__ZHASH]; // 64KB on the stack! + if (quality < 5) quality = 5; + + stbi__sbpush(out, 0x78); // DEFLATE 32K window + stbi__sbpush(out, 0x5e); // FLEVEL = 1 + stbi__zlib_add(1,1); // BFINAL = 1 + stbi__zlib_add(1,2); // BTYPE = 1 -- fixed huffman + + for (i=0; i < stbi__ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + // hash next 3 bytes of data to be compressed + int h = stbi__zhash(data+i)&(stbi__ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbi__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { // if entry lies within window + int d = stbi__zlib_countm(hlist[j], data+i, data_len-i); + if (d >= best) best=d,bestloc=hlist[j]; + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbi__sbn(hash_table[h]) == 2*quality) { + memcpy(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + stbi__sbn(hash_table[h]) = quality; + } + stbi__sbpush(hash_table[h],data+i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal + h = stbi__zhash(data+i+1)&(stbi__ZHASH-1); + hlist = hash_table[h]; + n = stbi__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = stbi__zlib_countm(hlist[j], data+i+1, data_len-i-1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = data+i - bestloc; // distance back + assert(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + stbi__zlib_huff(j+257); + if (lengtheb[j]) stbi__zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + stbi__zlib_add(stbi__zlib_bitrev(j,5),5); + if (disteb[j]) stbi__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbi__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (;i < data_len; ++i) + stbi__zlib_huffb(data[i]); + stbi__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbi__zlib_add(0,1); + + for (i=0; i < stbi__ZHASH; ++i) + (void) stbi__sbfree(hash_table[i]); + + { + // compute adler32 on input + unsigned int i=0, s1=1, s2=0, blocklen = data_len % 5552; + int j=0; + while (j < data_len) { + for (i=0; i < blocklen; ++i) s1 += data[j+i], s2 += s1; + s1 %= 65521, s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbi__sbpush(out, (unsigned char) (s2 >> 8)); + stbi__sbpush(out, (unsigned char) s2); + stbi__sbpush(out, (unsigned char) (s1 >> 8)); + stbi__sbpush(out, (unsigned char) s1); + } + *out_len = stbi__sbn(out); + // make returned pointer freeable + memmove(stbi__sbraw(out), out, *out_len); + return (unsigned char *) stbi__sbraw(out); +} + +unsigned int stbi__crc32(unsigned char *buffer, int len) +{ + static unsigned int crc_table[256]; + unsigned int crc = ~0u; + int i,j; + if (crc_table[1] == 0) + for(i=0; i < 256; i++) + for (crc_table[i]=i, j=0; j < 8; ++j) + crc_table[i] = (crc_table[i] >> 1) ^ (crc_table[i] & 1 ? 0xedb88320 : 0); + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +} + +#define stbi__wpng4(o,a,b,c,d) ((o)[0]=(unsigned char)(a),(o)[1]=(unsigned char)(b),(o)[2]=(unsigned char)(c),(o)[3]=(unsigned char)(d),(o)+=4) +#define stbi__wp32(data,v) stbi__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +#define stbi__wptag(data,s) stbi__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbi__wpcrc(unsigned char **data, int len) +{ + unsigned int crc = stbi__crc32(*data - len - 4, len+4); + stbi__wp32(*data, crc); +} + +static unsigned char stbi__paeth(int a, int b, int c) +{ + int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); + if (pa <= pb && pa <= pc) return (unsigned char) a; + if (pb <= pc) return (unsigned char) b; + return (unsigned char) c; +} + +unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int ctype[5] = { -1, 0, 4, 2, 6 }; + unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; + unsigned char *out,*o, *filt, *zlib; + signed char *line_buffer; + int i,j,k,p,zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + filt = (unsigned char *) malloc((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) malloc(x * n); if (!line_buffer) { free(filt); return 0; } + for (j=0; j < y; ++j) { + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = j ? mapping : firstmap; + int best = 0, bestval = 0x7fffffff; + for (p=0; p < 2; ++p) { + for (k= p?best:0; k < 5; ++k) { + int type = mymap[k],est=0; + unsigned char *z = pixels + stride_bytes*j; + for (i=0; i < n; ++i) + switch (type) { + case 0: line_buffer[i] = z[i]; break; + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; + case 3: line_buffer[i] = z[i] - (z[i-stride_bytes]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbi__paeth(0,z[i-stride_bytes],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + for (i=n; i < x*n; ++i) { + switch (type) { + case 0: line_buffer[i] = z[i]; break; + case 1: line_buffer[i] = z[i] - z[i-n]; break; + case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; + case 3: line_buffer[i] = z[i] - ((z[i-n] + z[i-stride_bytes])>>1); break; + case 4: line_buffer[i] = z[i] - stbi__paeth(z[i-n], z[i-stride_bytes], z[i-stride_bytes-n]); break; + case 5: line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: line_buffer[i] = z[i] - stbi__paeth(z[i-n], 0,0); break; + } + } + if (p) break; + for (i=0; i < x*n; ++i) + est += abs((signed char) line_buffer[i]); + if (est < bestval) { bestval = est; best = k; } + } + } + // when we get here, best contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) best; + memcpy(filt+j*(x*n+1)+1, line_buffer, x*n); + } + free(line_buffer); + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, 8); // increase 8 to get smaller but use more memory + free(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *) malloc(8 + 12+13 + 12+zlen + 12); + if (!out) return 0; + *out_len = 8 + 12+13 + 12+zlen + 12; + + o=out; + memcpy(o,sig,8); o+= 8; + stbi__wp32(o, 13); // header length + stbi__wptag(o, "IHDR"); + stbi__wp32(o, x); + stbi__wp32(o, y); + *o++ = 8; + *o++ = (unsigned char) ctype[n]; + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbi__wpcrc(&o,13); + + stbi__wp32(o, zlen); + stbi__wptag(o, "IDAT"); + memcpy(o, zlib, zlen); o += zlen; free(zlib); + stbi__wpcrc(&o, zlen); + + stbi__wp32(o,0); + stbi__wptag(o, "IEND"); + stbi__wpcrc(&o,0); + + assert(o == out + *out_len); + + return out; +} + +int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) +{ + FILE *f; + int len; + unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); + if (!png) return 0; + f = fopen(filename, "wb"); + if (!f) { free(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + free(png); + return 1; +} +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ diff --git a/3rd-party/nanosvg/premake4.lua b/3rd-party/nanosvg/premake4.lua new file mode 100644 index 0000000..8befd82 --- /dev/null +++ b/3rd-party/nanosvg/premake4.lua @@ -0,0 +1,56 @@ + +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"} diff --git a/3rd-party/nanosvg/src/nanosvg.h b/3rd-party/nanosvg/src/nanosvg.h new file mode 100644 index 0000000..60a3238 --- /dev/null +++ b/3rd-party/nanosvg/src/nanosvg.h @@ -0,0 +1,3098 @@ +/* + * Copyright (c) 2013-14 Mikko Mononen memon@inside.org + * + * 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 +#include +#include +#include + +#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 diff --git a/3rd-party/nanosvg/src/nanosvgrast.h b/3rd-party/nanosvg/src/nanosvgrast.h new file mode 100644 index 0000000..17ba3b0 --- /dev/null +++ b/3rd-party/nanosvg/src/nanosvgrast.h @@ -0,0 +1,1458 @@ +/* + * Copyright (c) 2013-14 Mikko Mononen memon@inside.org + * + * 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 +#include +#include + +#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 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)(-floorf(NSVG__FIX * -dxdy)); + else + z->dx = (int)floorf(NSVG__FIX * dxdy); + z->x = (int)floorf(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; + } 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, "", xmin, ymin, (xmax - xmin), (ymax - ymin)); + + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + fprintf(fp ,"", e->x0,e->y0, e->x1,e->y1); + } + + for (i = 0; i < r->npoints; i++) { + if (i+1 < r->npoints) + fprintf(fp ,"", r->points[i].x, r->points[i].y, r->points[i+1].x, r->points[i+1].y); + fprintf(fp ,"", r->points[i].x, r->points[i].y, r->points[i].flags == 0 ? "#f00" : "#0f0"); + } + + fprintf(fp, ""); + 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 diff --git a/PKGBUILD b/PKGBUILD index 6fc2d69..5652a0e 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -13,6 +13,7 @@ depends=( 'pixman' 'libyaml' 'alsa-lib' + 'libpng' 'libudev.so' 'json-c' 'libmpdclient' diff --git a/bar/bar.c b/bar/bar.c index 109f210..67731fa 100644 --- a/bar/bar.c +++ b/bar/bar.c @@ -1,4 +1,5 @@ #include "bar.h" +#include "icon.h" #include "private.h" #include diff --git a/bar/private.h b/bar/private.h index d48b07f..96bf822 100644 --- a/bar/private.h +++ b/bar/private.h @@ -9,6 +9,8 @@ struct private char *monitor; enum bar_layer layer; enum bar_location location; + struct basedirs *basedirs; + struct themes *themes; int height; int left_spacing, right_spacing; int left_margin, right_margin; diff --git a/config-verify.c b/config-verify.c index ed7d2f5..3062e4b 100644 --- a/config-verify.c +++ b/config-verify.c @@ -240,6 +240,24 @@ conf_verify_font_shaping(keychain_t *chain, const struct yml_node *node) return conf_verify_enum(chain, node, (const char *[]){"full", /*"graphemes",*/ "none"}, 2); } +bool +conf_verify_string_list(keychain_t *chain, const struct yml_node *node) +{ + if (!yml_is_list(node)) { + LOG_ERR("%s: must be a list of strings", conf_err_prefix(chain, node)); + return false; + } + + for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it)) { + if (!yml_is_scalar(it.node)) { + LOG_ERR("%s: must be a string", conf_err_prefix(chain, node)); + return false; + } + } + + return true; +} + bool conf_verify_decoration(keychain_t *chain, const struct yml_node *node) { @@ -451,6 +469,12 @@ conf_verify_bar(const struct yml_node *bar) {"font-shaping", false, &conf_verify_font_shaping}, {"foreground", false, &conf_verify_color}, + {"icon-basedirs", false, &conf_verify_string_list}, + // TODO verify icon name + // + {"icon-themes", false, &conf_verify_string_list}, + {"icon-size", false, &conf_verify_unsigned}, + {"left", false, &verify_module_list}, {"center", false, &verify_module_list}, {"right", false, &verify_module_list}, diff --git a/config-verify.h b/config-verify.h index 8ad44ce..5222e01 100644 --- a/config-verify.h +++ b/config-verify.h @@ -48,3 +48,5 @@ bool conf_verify_particle(keychain_t *chain, const struct yml_node *node); bool conf_verify_particle_list_items(keychain_t *chain, const struct yml_node *node); bool conf_verify_decoration(keychain_t *chain, const struct yml_node *node); + +bool conf_verify_string_list(keychain_t *chain, const struct yml_node *node); diff --git a/config.c b/config.c index 0f80364..1ded889 100644 --- a/config.c +++ b/config.c @@ -5,14 +5,18 @@ #include #include #include +#include #include #include "bar/bar.h" #include "color.h" #include "config-verify.h" +#include "icon.h" #include "module.h" #include "plugin.h" +#include "tllist.h" +#include "yml.h" #define LOG_MODULE "config" #define LOG_ENABLE_DBG 0 @@ -130,6 +134,42 @@ conf_to_font_shaping(const struct yml_node *node) } } +static void +conf_to_themes(const struct yml_node *node, struct basedirs **basedirs, struct themes **themes) +{ + *basedirs = basedirs_new(); + + size_t idx = 0; + for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it), idx++) { + const char *s = yml_value_as_string(it.node); + wordexp_t p; + if (wordexp(s, &p, WRDE_UNDEF) == 0) { + if (dir_exists(p.we_wordv[0])) { + tll_push_back((*basedirs)->basedirs, strdup(p.we_wordv[0])); + continue; + } + wordfree(&p); + } + LOG_WARN("%s is not a directory", s); + } + + if (tll_length((*basedirs)->basedirs) == 0) { + LOG_WARN("No valid base directories provided, no themes initialized"); + } + + *themes = init_themes(*basedirs); +} + +static void +conf_to_string_list(const struct yml_node *node, struct string_list **string_list) +{ + *string_list = string_list_new(); + size_t idx = 0; + for (struct yml_list_iter it = yml_list_iter(node); it.node != NULL; yml_list_next(&it), idx++) { + tll_push_back((*string_list)->strings, strdup(yml_value_as_string(it.node))); + } +} + struct deco * conf_to_deco(const struct yml_node *node) { @@ -167,8 +207,10 @@ particle_simple_list_from_config(const struct yml_node *node, struct conf_inheri assert(particle_list_new != NULL); } - struct particle *common = particle_common_new(0, 0, NULL, fcft_clone(inherited.font), inherited.font_shaping, - inherited.foreground, NULL); + struct particle *common + = particle_common_new(0, 0, NULL, fcft_clone(inherited.font), inherited.font_shaping, + basedirs_inc(inherited.basedirs), themes_inc(inherited.themes), + string_list_inc(inherited.icon_themes), inherited.icon_size, inherited.foreground, NULL); return particle_list_new(common, parts, count, 0, 2); } @@ -190,6 +232,9 @@ conf_to_particle(const struct yml_node *node, struct conf_inherit inherited) const struct yml_node *font_shaping_node = yml_get_value(pair.value, "font-shaping"); const struct yml_node *foreground_node = yml_get_value(pair.value, "foreground"); const struct yml_node *deco_node = yml_get_value(pair.value, "deco"); + const struct yml_node *basedirs_node = yml_get_value(pair.value, "icon-basedirs"); + const struct yml_node *icon_themes_node = yml_get_value(pair.value, "icon-themes"); + const struct yml_node *icon_size_node = yml_get_value(pair.value, "icon-size"); int left = margin != NULL ? yml_value_as_int(margin) : left_margin != NULL ? yml_value_as_int(left_margin) : 0; int right = margin != NULL ? yml_value_as_int(margin) : right_margin != NULL ? yml_value_as_int(right_margin) : 0; @@ -271,9 +316,27 @@ conf_to_particle(const struct yml_node *node, struct conf_inherit inherited) = font_shaping_node != NULL ? conf_to_font_shaping(font_shaping_node) : inherited.font_shaping; pixman_color_t foreground = foreground_node != NULL ? conf_to_color(foreground_node) : inherited.foreground; + struct themes *themes = NULL; + struct basedirs *basedirs = NULL; + if (basedirs_node) { + conf_to_themes(basedirs_node, &basedirs, &themes); + } else { + basedirs = basedirs_inc(inherited.basedirs); + themes = themes_inc(inherited.themes); + } + + struct string_list *icon_themes = NULL; + if (icon_themes_node) { + conf_to_string_list(icon_themes_node, &icon_themes); + } else { + icon_themes = string_list_inc(inherited.icon_themes); + } + + int icon_size = icon_size_node != NULL ? yml_value_as_int(icon_size_node) : inherited.icon_size; + /* Instantiate base/common particle */ - struct particle *common - = particle_common_new(left, right, on_click_templates, font, font_shaping, foreground, deco); + struct particle *common = particle_common_new(left, right, on_click_templates, font, font_shaping, basedirs, themes, + icon_themes, icon_size, foreground, deco); const struct particle_iface *iface = plugin_load_particle(type); @@ -421,15 +484,40 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend) if (font_shaping_node != NULL) font_shaping = conf_to_font_shaping(font_shaping_node); + // Get a default icon basedirs + struct basedirs *basedirs = NULL; + struct themes *themes = NULL; + const struct yml_node *basedirs_node = yml_get_value(bar, "icon-basedirs"); + if (basedirs_node != NULL) { + conf_to_themes(basedirs_node, &basedirs, &themes); + } else { + basedirs = get_basedirs(); + themes = init_themes(basedirs); + } + + const struct yml_node *icon_themes_node = yml_get_value(bar, "icon-themes"); + struct string_list *icon_themes = NULL; + if (icon_themes_node != NULL) { + conf_to_string_list(icon_themes_node, &icon_themes); + } else { + // Just use an empty list by default. + icon_themes = string_list_new(); + } + + const struct yml_node *icon_size_node = yml_get_value(bar, "icon-size"); + int icon_size = icon_size_node ? yml_value_as_int(icon_size_node) : conf.height; + const struct yml_node *foreground_node = yml_get_value(bar, "foreground"); if (foreground_node != NULL) foreground = conf_to_color(foreground_node); - struct conf_inherit inherited = { - .font = font, - .font_shaping = font_shaping, - .foreground = foreground, - }; + struct conf_inherit inherited = {.font = font, + .font_shaping = font_shaping, + .basedirs = basedirs, + .themes = themes, + .icon_themes = icon_themes, + .icon_size = icon_size, + .foreground = foreground}; const struct yml_node *left = yml_get_value(bar, "left"); const struct yml_node *center = yml_get_value(bar, "center"); @@ -456,16 +544,41 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend) const struct yml_node *mod_font = yml_get_value(m.value, "font"); const struct yml_node *mod_font_shaping = yml_get_value(m.value, "font-shaping"); const struct yml_node *mod_foreground = yml_get_value(m.value, "foreground"); + const struct yml_node *mod_basedirs = yml_get_value(m.value, "icon-basedirs"); + const struct yml_node *mod_icon_themes = yml_get_value(m.value, "icon-themes"); + const struct yml_node *mod_icon_size = yml_get_value(m.value, "icon-size"); - struct conf_inherit mod_inherit = { - .font = mod_font != NULL ? conf_to_font(mod_font) : inherited.font, - .font_shaping - = mod_font_shaping != NULL ? conf_to_font_shaping(mod_font_shaping) : inherited.font_shaping, - .foreground = mod_foreground != NULL ? conf_to_color(mod_foreground) : inherited.foreground, - }; + struct basedirs *inherit_basedirs = NULL; + struct themes *inherit_themes = NULL; + if (mod_basedirs) { + conf_to_themes(mod_basedirs, &inherit_basedirs, &inherit_themes); + } else { + inherit_themes = themes_inc(inherited.themes); + inherit_basedirs = basedirs_inc(inherited.basedirs); + } + + struct string_list *inherit_icon_themes = NULL; + if (mod_icon_themes) { + conf_to_string_list(mod_icon_themes, &inherit_icon_themes); + } else { + inherit_icon_themes = string_list_inc(inherited.icon_themes); + } + + struct conf_inherit mod_inherit + = {.font = mod_font != NULL ? conf_to_font(mod_font) : inherited.font, + .font_shaping + = mod_font_shaping != NULL ? conf_to_font_shaping(mod_font_shaping) : inherited.font_shaping, + .basedirs = inherit_basedirs, + .themes = inherit_themes, + .icon_themes = inherit_icon_themes, + .icon_size = mod_icon_size != NULL ? yml_value_as_int(mod_icon_size) : inherited.icon_size, + .foreground = mod_foreground != NULL ? conf_to_color(mod_foreground) : inherited.foreground}; const struct module_iface *iface = plugin_load_module(mod_name); mods[idx] = iface->from_conf(m.value, mod_inherit); + themes_dec(inherit_themes); + basedirs_dec(inherit_basedirs); + string_list_dec(inherit_icon_themes); } if (i == 0) { @@ -487,6 +600,9 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend) free(conf.center.mods); free(conf.right.mods); fcft_destroy(font); + themes_dec(themes); + basedirs_dec(basedirs); + string_list_dec(icon_themes); return ret; } diff --git a/config.h b/config.h index 86c2f4e..9c1d124 100644 --- a/config.h +++ b/config.h @@ -2,6 +2,7 @@ #include "bar/bar.h" #include "font-shaping.h" +#include "icon.h" #include "yml.h" #include @@ -22,6 +23,10 @@ enum font_shaping conf_to_font_shaping(const struct yml_node *node); struct conf_inherit { const struct fcft_font *font; enum font_shaping font_shaping; + struct themes *themes; + struct basedirs *basedirs; + struct string_list *icon_themes; + int icon_size; pixman_color_t foreground; }; diff --git a/icon.c b/icon.c new file mode 100644 index 0000000..e4cee99 --- /dev/null +++ b/icon.c @@ -0,0 +1,968 @@ +#include "tllist.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_MODULE "icon" +#define LOG_ENABLE_DBG 0 +#include "icon.h" +#include "log.h" +#include "png-yambar.h" +#include "stride.h" +#include "stringop.h" + +void +icon_pixmaps_free(const struct ref *ref) +{ + struct icon_pixmaps *p = (struct icon_pixmaps *)ref; + tll_free_and_free(p->list, free); + free(p); +} + +void +icon_pixmaps_dec(struct icon_pixmaps *p) +{ + ref_dec(&p->refcount); +} + +struct icon_pixmaps * +icon_pixmaps_inc(struct icon_pixmaps *p) +{ + ref_inc(&p->refcount); + return p; +} + +struct icon_pixmaps * +new_icon_pixmaps() +{ + struct icon_pixmaps *p = malloc(sizeof(*p)); + p->refcount = (struct ref){icon_pixmaps_free, 1}; + icon_pixmaps_t tmp = tll_init(); + p->list = tmp; + return p; +} + +void +icon_from_pixmaps(struct icon *icon, struct icon_pixmaps *p) +{ + icon->pixmaps = icon_pixmaps_inc(p); + icon->type = ICON_PIXMAP; +} + +bool +icon_from_png(struct icon *icon, const char *file_name) +{ + pixman_image_t *png = png_load(file_name); + if (png == NULL) + return false; + + icon->type = ICON_PNG; + icon->png = png; + return true; +} + +bool +icon_from_svg(struct icon *icon, const char *file_name) +{ + /* TODO: DPI */ + NSVGimage *svg = nsvgParseFromFile(file_name, "px", 96); + if (svg == NULL) + return false; + + if (svg->width == 0 || svg->height == 0) { + nsvgDelete(svg); + return false; + } + + icon->type = ICON_SVG; + icon->svg = svg; + return true; +} + +static void +scale_if_necessary(pixman_image_t *img, int x, int y, int size, pixman_image_t *dest) +{ + pixman_format_code_t fmt = pixman_image_get_format(img); + int height = pixman_image_get_height(img); + int width = pixman_image_get_width(img); + + bool scale_img = height > size; + + // Exposable is const so have to recalculate every-time... + // + if (scale_img) { + double scale = (double)size / height; + + pixman_f_transform_t _scale_transform; + pixman_f_transform_init_scale(&_scale_transform, 1. / scale, 1. / scale); + + pixman_transform_t scale_transform; + pixman_transform_from_pixman_f_transform(&scale_transform, &_scale_transform); + pixman_image_set_transform(img, &scale_transform); + + int param_count = 0; + pixman_kernel_t kernel = PIXMAN_KERNEL_LANCZOS3; + pixman_fixed_t *params = pixman_filter_create_separable_convolution( + ¶m_count, pixman_double_to_fixed(1. / scale), pixman_double_to_fixed(1. / scale), kernel, kernel, + kernel, kernel, pixman_int_to_fixed(1), pixman_int_to_fixed(1)); + + if (params != NULL || param_count == 0) { + pixman_image_set_filter(img, PIXMAN_FILTER_SEPARABLE_CONVOLUTION, params, param_count); + } + + free(params); + + width *= scale; + height *= scale; + + int stride = stride_for_format_and_width(fmt, width); + uint8_t *data = malloc(height * stride); + pixman_image_t *scaled_img = pixman_image_create_bits_no_clear(fmt, width, height, (uint32_t *)data, stride); + + pixman_image_composite32(PIXMAN_OP_SRC, img, NULL, scaled_img, 0, 0, 0, 0, 0, 0, width, height); + img = scaled_img; + } + + pixman_image_composite32(PIXMAN_OP_OVER, img, NULL, dest, 0, 0, 0, 0, x, y, width, height); + + if (scale_img) { + free(pixman_image_get_data(img)); + pixman_image_unref(img); + } +} + +static void +render_png(const struct icon *icon, int x, int y, int size, pixman_image_t *dest) +{ + assert(icon->type == ICON_PNG); + assert(icon->png != NULL); + + pixman_image_t *png = icon->png; + + scale_if_necessary(png, x, y, size, dest); +} + +static void +render_svg(const struct icon *icon, int x, int y, int size, pixman_image_t *dest) +{ + assert(icon->type == ICON_SVG); + assert(icon->svg != NULL); + + NSVGimage *svg = icon->svg; + struct NSVGrasterizer *rast = nsvgCreateRasterizer(); + + if (rast == NULL) + return; + + float scale = svg->width > svg->height ? size / svg->width : size / svg->height; + + uint8_t *data = malloc(size * size * 4); + nsvgRasterize(rast, svg, 0, 0, scale, data, size, size, size * 4); + + pixman_image_t *img = pixman_image_create_bits_no_clear(PIXMAN_a8b8g8r8, size, size, (uint32_t *)data, size * 4); + + /* Nanosvg produces non-premultiplied ABGR, while pixman expects + * premultiplied */ + for (uint32_t *abgr = (uint32_t *)data; abgr < (uint32_t *)(data + size * size * 4); abgr++) { + uint8_t alpha = (*abgr >> 24) & 0xff; + uint8_t blue = (*abgr >> 16) & 0xff; + uint8_t green = (*abgr >> 8) & 0xff; + uint8_t red = (*abgr >> 0) & 0xff; + + if (alpha == 0xff) + continue; + + if (alpha == 0x00) + blue = green = red = 0x00; + else { + blue = blue * alpha / 0xff; + green = green * alpha / 0xff; + red = red * alpha / 0xff; + } + + *abgr = (uint32_t)alpha << 24 | blue << 16 | green << 8 | red; + } + + nsvgDeleteRasterizer(rast); + + pixman_image_composite32(PIXMAN_OP_OVER, img, NULL, dest, 0, 0, 0, 0, x, y, size, size); + free(pixman_image_get_data(img)); + pixman_image_unref(img); +} + +static void +render_pixmap(const struct icon *icon, int x, int y, int size, pixman_image_t *dest) +{ + assert(icon->type == ICON_PIXMAP); + assert(icon->pixmaps != NULL); + + struct icon_pixmap *icon_pixmap = NULL; + int min_error = INT_MAX; + tll_foreach(icon->pixmaps->list, it) + { + int e = abs(size - it->item->size); + if (e < min_error) { + icon_pixmap = it->item; + min_error = e; + } + } + + assert(icon_pixmap != NULL); + pixman_image_t *img = pixman_image_create_bits_no_clear( + PIXMAN_a8r8g8b8, icon_pixmap->size, icon_pixmap->size, (uint32_t *)icon_pixmap->pixels, + stride_for_format_and_width(PIXMAN_a8r8g8b8, icon_pixmap->size)); + scale_if_necessary(img, x, y, size, dest); + pixman_image_unref(img); +} + +void +render_icon(const struct icon *icon, int x, int y, int size, pixman_image_t *dest) +{ + + switch (icon->type) { + case ICON_NONE: + break; + case ICON_PNG: + render_png(icon, x, y, size, dest); + break; + case ICON_SVG: + render_svg(icon, x, y, size, dest); + break; + case ICON_PIXMAP: + render_pixmap(icon, x, y, size, dest); + break; + } +} + +void +reset_icon(struct icon *icon) +{ + switch (icon->type) { + case ICON_NONE: + break; + case ICON_PNG: + free(pixman_image_get_data(icon->png)); + pixman_image_unref(icon->png); + icon->png = NULL; + break; + case ICON_SVG: + nsvgDelete(icon->svg); + icon->svg = NULL; + break; + case ICON_PIXMAP: + icon_pixmaps_dec(icon->pixmaps); + icon->pixmaps = NULL; + break; + } + + icon->type = ICON_NONE; +} + +void +string_list_free(const struct ref *ref) +{ + struct string_list *b = (struct string_list *)ref; + tll_free_and_free(b->strings, free); + free(b); +} + +void +string_list_dec(struct string_list *b) +{ + ref_dec(&b->refcount); +} + +struct string_list * +string_list_inc(struct string_list *p) +{ + ref_inc(&p->refcount); + return p; +} + +struct string_list * +string_list_new() +{ + string_list_t strings = tll_init(); + + struct string_list *out = malloc(sizeof(*out)); + out->strings = strings; + out->refcount = (struct ref){string_list_free, 1}; + return out; +} + +bool +dir_exists(char *path) +{ + struct stat sb; + return stat(path, &sb) == 0 && S_ISDIR(sb.st_mode); +} + +static void +destroy_theme(struct icon_theme *theme) +{ + if (!theme) { + return; + } + free(theme->name); + free(theme->comment); + tll_free_and_free(theme->inherits, free); + tll_free_and_free(theme->directories, free); + free(theme->dir); + + tll_foreach(theme->subdirs, it) + { + free(it->item->name); + free(it->item); + tll_remove(theme->subdirs, it); + } + free(theme); +} + +void +basedirs_free(const struct ref *ref) +{ + struct basedirs *b = (struct basedirs *)ref; + tll_free_and_free(b->basedirs, free); + free(b); +} + +void +basedirs_dec(struct basedirs *b) +{ + ref_dec(&b->refcount); +} + +struct basedirs * +basedirs_inc(struct basedirs *p) +{ + ref_inc(&p->refcount); + return p; +} + +struct basedirs * +basedirs_new(void) +{ + string_list_t basedirs = tll_init(); + + struct basedirs *out = malloc(sizeof(*out)); + out->basedirs = basedirs; + out->refcount = (struct ref){basedirs_free, 1}; + return out; +} + +struct basedirs * +get_basedirs(void) +{ + string_list_t basedirs = tll_init(); + + // Follows freedesktop specs: + // + tll_push_back(basedirs, strdup("$HOME/.icons")); // deprecated + + char *data_home = getenv("XDG_DATA_HOME"); + tll_push_back(basedirs, strdup(data_home && *data_home ? "$XDG_DATA_HOME/icons" : "$HOME/.local/share/icons")); + + tll_push_back(basedirs, strdup("/usr/share/pixmaps")); + + char *data_dirs = getenv("XDG_DATA_DIRS"); + if (!(data_dirs && *data_dirs)) { + data_dirs = "/usr/local/share:/usr/share"; + } + data_dirs = strdup(data_dirs); + char *dir = strtok(data_dirs, ":"); + do { + char *path = format_str("%s/icons", dir); + tll_push_back(basedirs, path); + } while ((dir = strtok(NULL, ":"))); + free(data_dirs); + + string_list_t basedirs_expanded = tll_init(); + tll_foreach(basedirs, it) + { + wordexp_t p; + if (wordexp(it->item, &p, WRDE_UNDEF) == 0) { + if (dir_exists(p.we_wordv[0])) { + tll_push_back(basedirs_expanded, strdup(p.we_wordv[0])); + } + wordfree(&p); + } + }; + + tll_free_and_free(basedirs, free); + struct basedirs *out = malloc(sizeof(*out)); + out->basedirs = basedirs_expanded; + out->refcount = (struct ref){basedirs_free, 1}; + return out; +} + +static const char * +group_handler(char *old_group, char *new_group, struct icon_theme *theme) +{ + if (!old_group) { + return new_group && strcasecmp(new_group, "icon theme") == 0 ? NULL : "first group must be 'Icon Theme'"; + } + + if (strcasecmp(old_group, "icon theme") == 0) { + if (!theme->name) { + return "missing required key 'Name'"; + } else if (!theme->comment) { + return "missing required key 'Comment'"; + } else if (tll_length(theme->directories) == 0) { + return "missing required key 'Directories'"; + } + } else { + if (tll_length(theme->subdirs) == 0) { // skip + return NULL; + } + + struct icon_theme_subdir *subdir = tll_back(theme->subdirs); + if (!subdir->size) { + return "missing required key 'Size'"; + } + + switch (subdir->type) { + case ICON_DIR_FIXED: + subdir->max_size = subdir->min_size = subdir->size; + break; + case ICON_DIR_SCALABLE: { + if (!subdir->max_size) + subdir->max_size = subdir->size; + if (!subdir->min_size) + subdir->min_size = subdir->size; + break; + } + case ICON_DIR_THRESHOLD: + subdir->max_size = subdir->size + subdir->threshold; + subdir->min_size = subdir->size - subdir->threshold; + } + } + + if (new_group) { + bool found = false; + tll_foreach(theme->directories, it) + { + if (strcmp(it->item, new_group)) { + found = true; + break; + } + } + + if (found) { + struct icon_theme_subdir *subdir = calloc(1, sizeof(struct icon_theme_subdir)); + if (!subdir) { + return "out of memory"; + } + subdir->name = strdup(new_group); + subdir->threshold = 2; + tll_push_back(theme->subdirs, subdir); + } + } + + return NULL; +} + +string_list_t +split_string(const char *str, const char *delims) +{ + string_list_t res = tll_init(); + char *copy = strdup(str); + + char *token = strtok(copy, delims); + while (token) { + tll_push_back(res, strdup(token)); + token = strtok(NULL, delims); + } + free(copy); + return res; +} + +#define OOM_ERROR(val) \ + if (!val) \ + return "out of memory"; + +static const char * +entry_handler(char *group, char *key, char *value, struct icon_theme *theme) +{ + if (strcmp(group, "Icon Theme") == 0) { + if (strcmp(key, "Name") == 0) { + theme->name = strdup(value); + OOM_ERROR(theme->name); + } else if (strcmp(key, "Comment") == 0) { + theme->comment = strdup(value); + OOM_ERROR(theme->comment); + } else if (strcmp(key, "Inherits") == 0) { + theme->inherits = split_string(value, ","); + } else if (strcmp(key, "Directories") == 0) { + theme->directories = split_string(value, ","); + } // Ignored: ScaledDirectories, Hidden, Example + } else { + if (tll_length(theme->subdirs) == 0) { // skip + return NULL; + } + + struct icon_theme_subdir *subdir = tll_back(theme->subdirs); + if (strcmp(subdir->name, group) != 0) { // skip + return NULL; + } + + if (strcmp(key, "Context") == 0) { + return NULL; // ignored, but explicitly handled to not fail parsing + } else if (strcmp(key, "Type") == 0) { + if (strcmp(value, "Fixed") == 0) { + subdir->type = ICON_DIR_FIXED; + } else if (strcmp(value, "Scalable") == 0) { + subdir->type = ICON_DIR_SCALABLE; + } else if (strcmp(value, "Threshold") == 0) { + subdir->type = ICON_DIR_THRESHOLD; + } else { + return "invalid value - expected 'Fixed', 'Scalable', or 'Threshold"; + } + return NULL; + } + + char *end; + int n = strtol(value, &end, 10); + if (*end != '\0') { + return "invalid value - expected a number"; + } + + if (strcmp(key, "Size") == 0) { + subdir->size = n; + } else if (strcmp(key, "MaxSize") == 0) { + subdir->max_size = n; + } else if (strcmp(key, "MinSize") == 0) { + subdir->min_size = n; + } else if (strcmp(key, "Threshold") == 0) { + subdir->threshold = n; + } else if (strcmp(key, "Scale") == 0) { + subdir->scale = n; + } + } + return NULL; +} + +/* + * This is a Freedesktop Desktop Entry parser (essentially INI) + * It calls entry_handler for every entry + * and group_handler between every group (as well as at both ends) + * Handlers return whether an error occurred, which stops parsing + */ +struct icon_theme * +read_theme_file(char *basedir, char *theme_name) +{ + struct icon_theme *theme = calloc(1, sizeof(struct icon_theme)); + if (!theme) { + return NULL; + } + string_list_t tmp1 = tll_init(); + subdirs_t tmp3 = tll_init(); + theme->directories = tmp1; + theme->inherits = tmp1; + theme->subdirs = tmp3; + + // look for index.theme file + char *path = format_str("%s/%s/index.theme", basedir, theme_name); + if (!path) { + return NULL; + } + FILE *theme_file = fopen(path, "r"); + free(path); + if (!theme_file) { + free(theme); + return NULL; + } + + string_list_t groups = tll_init(); + char *full_line = NULL; + const char *error = NULL; + int line_no = 0; + size_t sz = 0; + while (true) { + const char *warning = NULL; + ssize_t nread = getline(&full_line, &sz, theme_file); + if (nread == -1) { + break; + } + + ++line_no; + + char *line = full_line - 1; + while (isspace(*++line)) { + } // remove leading whitespace + if (!*line || line[0] == '#') + goto next; // ignore blank lines & comments + + int len = nread - (line - full_line); + while (isspace(line[--len])) { + } + line[++len] = '\0'; // Remove trailing whitespace + + if (line[0] == '[') { // Group handler + // check well-formed + int i = 1; + for (; !iscntrl(line[i]) && line[i] != '[' && line[i] != ']'; ++i) { + } + if (i != --len || line[i] != ']') { + warning = "malformed group header"; + goto warn; + } + + line[len] = '\0'; + line = &line[1]; + + tll_foreach(groups, it) + { + // If duplicate move to back and continue + // + if (strcmp(it->item, line) == 0) { + tll_push_back(groups, it->item); + tll_remove(groups, it); + goto next; + } + } + + char *last_group = tll_length(groups) ? tll_back(groups) : NULL; + error = group_handler(last_group, line, theme); + if (error) { + break; + } + + tll_push_back(groups, strdup(line)); + } else { + if (tll_length(groups) == 0) { + error = "unexpected content before first header"; + break; + } + + // check well-formed + int eok = 0; + for (; isalnum(line[eok]) || line[eok] == '-'; ++eok) { + } + int i = eok - 1; + while (isspace(line[++i])) { + } + if (line[i] == '[') { + // not handling localized values: + // https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s05.html + // + goto next; + } + if (line[i] != '=') { + warning = "malformed key-value pair"; + goto warn; + } + + line[eok] = '\0'; // split into key-value pair + + char *value = &line[i]; + while (isspace(*++value)) { + } + + error = entry_handler(tll_back(groups), line, value, theme); + if (error) { + break; + } + } + + next: + continue; + + warn:; + assert(warning != NULL); + char *group = tll_length(groups) > 0 ? tll_back(groups) : "n/a"; + LOG_INFO("Error during load of theme '%s' - parsing of file " + "'%s/%s/index.theme' encountered '%s' on line %d (group '%s') - continuing", + theme_name, basedir, theme_name, warning, line_no, group); + } + + if (!error) { + if (tll_length(groups) > 0) { + error = group_handler(tll_back(groups), NULL, theme); + } else { + error = "empty file"; + } + } + + if (error) { + char *group = tll_length(groups) > 0 ? tll_back(groups) : "n/a"; + LOG_WARN("Failed to load theme '%s' - parsing of file " + "'%s/%s/index.theme' failed on line %d (group '%s'): %s", + theme_name, basedir, theme_name, line_no, group, error); + destroy_theme(theme); + theme = NULL; + } else { + theme->dir = strdup(theme_name); + } + + free(full_line); + fclose(theme_file); + tll_free_and_free(groups, free); + return theme; +} + +themes_t +load_themes_in_dir(char *basedir) +{ + themes_t themes = tll_init(); + + DIR *dir; + if (!(dir = opendir(basedir))) { + return themes; + } + + struct dirent *entry; + while ((entry = readdir(dir))) { + if (entry->d_name[0] == '.') + continue; + + struct icon_theme *theme = read_theme_file(basedir, entry->d_name); + if (theme) { + LOG_DBG("Found theme [%s]: dir: %s", theme->name, theme->dir); + tll_push_back(themes, theme); + } + } + closedir(dir); + return themes; +} + +void +log_loaded_themes(themes_t themes) +{ + LOG_INFO("Logging themes"); + if (tll_length(themes) == 0) { + LOG_INFO("Warning: no icon themes loaded"); + return; + } + const char sep[] = ", "; + size_t sep_len = strlen(sep); + + size_t len = 0; + tll_foreach(themes, it) { len += strlen(it->item->name) + sep_len; } + + char *str = malloc(len + 1); + if (!str) { + return; + } + char *p = str; + bool start = true; + tll_foreach(themes, it) + { + if (!start) { + memcpy(p, sep, sep_len); + p += sep_len; + } + start = false; + + struct icon_theme *theme = it->item; + size_t name_len = strlen(theme->name); + memcpy(p, theme->name, name_len); + p += name_len; + } + *p = '\0'; + + LOG_INFO("Loaded icon themes: %s", str); + free(str); +} + +void +themes_free(const struct ref *ref) +{ + LOG_DBG("themes free"); + struct themes *p = (struct themes *)ref; + tll_free_and_free(p->themes, destroy_theme); + free(p); +} + +void +themes_dec(struct themes *t) +{ + ref_dec(&t->refcount); +} + +struct themes * +themes_inc(struct themes *t) +{ + ref_inc(&t->refcount); + return t; +} + +struct themes * +init_themes(struct basedirs *basedirs) +{ + + themes_t themes = tll_init(); + tll_foreach(basedirs->basedirs, it) + { + themes_t dir_themes = load_themes_in_dir(it->item); + tll_foreach(dir_themes, it) + { + struct icon_theme *theme = it->item; + tll_remove(dir_themes, it); + tll_push_back(themes, theme); + } + } + + log_loaded_themes(themes); + struct themes *out = malloc(sizeof(*out)); + out->themes = themes; + out->refcount = (struct ref){themes_free, 1}; + return out; +} + +static char * +find_icon_in_subdir(char *name, char *basedir, char *theme, char *subdir) +{ + static const char *extensions[] = { + "svg", + "png", + }; + + for (size_t i = 0; i < sizeof(extensions) / sizeof(*extensions); ++i) { + char *path = format_str("%s/%s/%s/%s.%s", basedir, theme, subdir, name, extensions[i]); + if (path && access(path, R_OK) == 0) { + return path; + } + free(path); + } + + return NULL; +} + +static bool +theme_exists_in_basedir(char *theme, char *basedir) +{ + char *path = format_str("%s/%s", basedir, theme); + bool ret = dir_exists(path); + free(path); + return ret; +} + +static char * +find_icon_with_theme(string_list_t basedirs, themes_t themes, char *name, int size, char *theme_name, int *min_size, + int *max_size) +{ + LOG_DBG("Looking for icon [%s] in theme [%s]", name, theme_name); + struct icon_theme *theme = NULL; + tll_foreach(themes, it) + { + theme = it->item; + if (strcmp(theme->name, theme_name) == 0) { + break; + } + theme = NULL; + } + if (!theme) + return NULL; + + char *icon = NULL; + tll_foreach(basedirs, bd_it) + { + if (!theme_exists_in_basedir(theme->dir, bd_it->item)) { + continue; + } + + tll_rforeach(theme->subdirs, sd_it) + { + struct icon_theme_subdir *subdir = sd_it->item; + if (size >= subdir->min_size && size <= subdir->max_size) { + if ((icon = find_icon_in_subdir(name, bd_it->item, theme->dir, subdir->name))) { + *min_size = subdir->min_size; + *max_size = subdir->max_size; + LOG_DBG("Found icon [%s] in theme [%s]", name, theme_name); + return icon; + } + } + } + } + + // inexact match + unsigned smallest_error = -1; // UINT_MAX + tll_foreach(basedirs, bd_it) + { + if (!theme_exists_in_basedir(theme->dir, bd_it->item)) { + continue; + } + + tll_rforeach(theme->subdirs, sd_it) + { + struct icon_theme_subdir *subdir = sd_it->item; + unsigned error = (size > subdir->max_size ? size - subdir->max_size : 0) + + (size < subdir->min_size ? subdir->min_size - size : 0); + if (error < smallest_error) { + char *test_icon = find_icon_in_subdir(name, bd_it->item, theme->dir, subdir->name); + if (test_icon) { + if (icon) + free(icon); + icon = test_icon; + smallest_error = error; + *min_size = subdir->min_size; + *max_size = subdir->max_size; + } + } + } + } + + if (!icon) { + tll_foreach(theme->inherits, it) + { + icon = find_icon_with_theme(basedirs, themes, name, size, it->item, min_size, max_size); + if (icon) { + break; + } + } + } + + return icon; +} + +static char * +find_fallback_icon(string_list_t basedirs, char *name, int *min_size, int *max_size) +{ + tll_foreach(basedirs, it) + { + char *icon = find_icon_in_subdir(name, it->item, "", ""); + if (icon) { + *min_size = 1; + *max_size = 512; + LOG_DBG("Found icon [%s] in fallback theme [%s]", name, it->item); + return icon; + } + } + return NULL; +} + +char * +find_icon(themes_t themes, string_list_t basedirs, char *name, int size, string_list_t icon_themes, int *min_size, + int *max_size) +{ + // TODO https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#implementation_notes + // + char *icon = NULL; + bool seenHicolor = false; + tll_foreach(icon_themes, it) + { + icon = find_icon_with_theme(basedirs, themes, name, size, it->item, min_size, max_size); + if (icon) { + return icon; + } + seenHicolor |= strcmp(it->item, "Hicolor") == 0; + } + if (!seenHicolor) { + icon = find_icon_with_theme(basedirs, themes, name, size, "Hicolor", min_size, max_size); + } + if (!icon) { + icon = find_fallback_icon(basedirs, name, min_size, max_size); + } + + return icon; +} diff --git a/icon.h b/icon.h new file mode 100644 index 0000000..eb6ae9c --- /dev/null +++ b/icon.h @@ -0,0 +1,122 @@ +#pragma once + +#include +#include +#include +#include +#include + +struct ref { + void (*free)(const struct ref *); + atomic_size_t count; +}; + +static inline void +ref_inc(const struct ref *ref) +{ + atomic_fetch_add((atomic_size_t *)&ref->count, 1); +} + +static inline void +ref_dec(const struct ref *ref) +{ + if (atomic_fetch_sub((atomic_size_t *)&ref->count, 1) == 1) { + ref->free(ref); + } +} + +struct icon_pixmap { + int size; + unsigned char pixels[]; +}; + +typedef tll(struct icon_pixmap *) icon_pixmaps_t; + +struct icon_pixmaps { + struct ref refcount; + icon_pixmaps_t list; +}; + +enum icon_type { ICON_NONE, ICON_PNG, ICON_SVG, ICON_PIXMAP }; + +struct icon { + enum icon_type type; + union { + NSVGimage *svg; + pixman_image_t *png; + struct icon_pixmaps *pixmaps; + }; +}; + +enum icon_dir_type { ICON_DIR_FIXED, ICON_DIR_SCALABLE, ICON_DIR_THRESHOLD }; + +struct icon_theme_subdir { + char *name; + char *context; + + int size; + int max_size; + int min_size; + int scale; + int threshold; + enum icon_dir_type type; +}; + +typedef tll(struct icon_theme *) themes_t; +typedef tll(char *) string_list_t; +typedef tll(struct icon_theme_subdir *) subdirs_t; + +struct themes { + struct ref refcount; + themes_t themes; +}; + +struct basedirs { + struct ref refcount; + string_list_t basedirs; +}; + +struct string_list { + struct ref refcount; + string_list_t strings; +}; + +struct icon_theme { + char *name; + char *comment; + string_list_t inherits; // char * + string_list_t directories; // char * + + char *dir; + subdirs_t subdirs; // struct icon_theme_subdir * +}; + +bool dir_exists(char *path); + +void icon_from_pixmaps(struct icon *icon, struct icon_pixmaps *p); +bool icon_from_png(struct icon *icon, const char *file_name); +bool icon_from_svg(struct icon *icon, const char *file_name); +void render_icon(const struct icon *icon, int x, int y, int size, pixman_image_t *dest); +void reset_icon(struct icon *icon); + +struct string_list *string_list_new(); +void string_list_dec(struct string_list *s); +struct string_list *string_list_inc(struct string_list *s); + +struct icon_pixmaps *new_icon_pixmaps(); +void icon_pixmaps_dec(struct icon_pixmaps *p); +struct icon_pixmaps *icon_pixmaps_inc(struct icon_pixmaps *ip); + +struct basedirs *get_basedirs(); +struct basedirs *basedirs_new(); +void basedirs_dec(struct basedirs *p); +struct basedirs *basedirs_inc(struct basedirs *p); + +struct themes *init_themes(struct basedirs *basedirs); +void themes_dec(struct themes *p); +struct themes *themes_inc(struct themes *p); + +char *find_icon(themes_t themes, string_list_t basedirs, char *name, int size, string_list_t icon_themes, int *min_size, + int *max_size); + +void log_loaded_themes(themes_t themes); diff --git a/meson.build b/meson.build index d760e94..1d617f0 100644 --- a/meson.build +++ b/meson.build @@ -52,6 +52,20 @@ libepoll = dependency('epoll-shim', required: false) libinotify = dependency('libinotify', required: false) pixman = dependency('pixman-1') yaml = dependency('yaml-0.1') +system_nanosvg = cc.find_library('nanosvg', required: get_option('system-nanosvg')) +system_nanosvgrast = cc.find_library('nanosvgrast', required: get_option('system-nanosvg')) +if system_nanosvg.found() and system_nanosvgrast.found() + nanosvg = declare_dependency( + dependencies: [system_nanosvg, system_nanosvgrast] + ) +else + nanosvg = declare_dependency( + sources: ['nanosvg.c', '3rd-party/nanosvg/src/nanosvg.h', + 'nanosvgrast.c', '3rd-party/nanosvg/src/nanosvgrast.h'], + include_directories: '.', + dependencies: m) +endif +png = dependency('libpng') # X11/XCB dependencies xcb_aux = dependency('xcb-aux', required: get_option('backend-x11')) @@ -128,8 +142,11 @@ yambar = executable( 'plugin.c', 'plugin.h', 'tag.c', 'tag.h', 'yml.c', 'yml.h', + 'icon.c', 'icon.h', + 'png.c', 'png-yambar.h', + 'stringop.c', 'stringop.h', version, - dependencies: [bar, libepoll, libinotify, pixman, yaml, threads, dl, tllist, fcft] + + dependencies: [bar, libepoll, libinotify, pixman, yaml, nanosvg, png, threads, dl, tllist, fcft] + decorations + particles + modules, build_rpath: '$ORIGIN/modules:$ORIGIN/decorations:$ORIGIN/particles', export_dynamic: true, diff --git a/meson_options.txt b/meson_options.txt index a9aac05..84d32ae 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -48,3 +48,5 @@ option('plugin-xkb', type: 'feature', value: 'auto', description: 'keyboard support for X11') option('plugin-xwindow', type: 'feature', value: 'auto', description: 'XWindow (window tracking for X11) support') +option('system-nanosvg', type: 'feature', value: 'disabled', + description: 'use system\'s nanosvg instead of the bundled version') diff --git a/nanosvg.c b/nanosvg.c new file mode 100644 index 0000000..a540d8b --- /dev/null +++ b/nanosvg.c @@ -0,0 +1,6 @@ +#include +#include +#include +#define NANOSVG_ALL_COLOR_KEYWORDS +#define NANOSVG_IMPLEMENTATION +#include <3rd-party/nanosvg/src/nanosvg.h> diff --git a/nanosvg/nanosvg.h b/nanosvg/nanosvg.h new file mode 100644 index 0000000..5c08f32 --- /dev/null +++ b/nanosvg/nanosvg.h @@ -0,0 +1 @@ +#include <3rd-party/nanosvg/src/nanosvg.h> diff --git a/nanosvg/nanosvgrast.h b/nanosvg/nanosvgrast.h new file mode 100644 index 0000000..c6b63dd --- /dev/null +++ b/nanosvg/nanosvgrast.h @@ -0,0 +1 @@ +#include <3rd-party/nanosvg/src/nanosvgrast.h> diff --git a/nanosvgrast.c b/nanosvgrast.c new file mode 100644 index 0000000..07d85b6 --- /dev/null +++ b/nanosvgrast.c @@ -0,0 +1,6 @@ +#include +#include + +#include <3rd-party/nanosvg/src/nanosvg.h> +#define NANOSVGRAST_IMPLEMENTATION +#include <3rd-party/nanosvg/src/nanosvgrast.h> diff --git a/particle.c b/particle.c index f35b5d1..030f555 100644 --- a/particle.c +++ b/particle.c @@ -15,6 +15,7 @@ #define LOG_MODULE "particle" #define LOG_ENABLE_DBG 0 #include "bar/bar.h" +#include "icon.h" #include "log.h" void @@ -25,12 +26,17 @@ particle_default_destroy(struct particle *particle) fcft_destroy(particle->font); for (size_t i = 0; i < MOUSE_BTN_COUNT; i++) free(particle->on_click_templates[i]); + + themes_dec(particle->themes); + basedirs_dec(particle->basedirs); + string_list_dec(particle->icon_themes); free(particle); } struct particle * particle_common_new(int left_margin, int right_margin, char **on_click_templates, struct fcft_font *font, - enum font_shaping font_shaping, pixman_color_t foreground, struct deco *deco) + enum font_shaping font_shaping, struct basedirs *basedirs, struct themes *themes, + struct string_list *icon_themes, int icon_size, pixman_color_t foreground, struct deco *deco) { struct particle *p = calloc(1, sizeof(*p)); p->left_margin = left_margin; @@ -39,6 +45,10 @@ particle_common_new(int left_margin, int right_margin, char **on_click_templates p->font = font; p->font_shaping = font_shaping; p->deco = deco; + p->basedirs = basedirs; + p->themes = themes; + p->icon_themes = icon_themes; + p->icon_size = icon_size; if (on_click_templates != NULL) { for (size_t i = 0; i < MOUSE_BTN_COUNT; i++) { diff --git a/particle.h b/particle.h index bc8648d..f0d566d 100644 --- a/particle.h +++ b/particle.h @@ -6,6 +6,7 @@ #include "color.h" #include "decoration.h" #include "font-shaping.h" +#include "icon.h" #include "tag.h" enum mouse_event { @@ -41,6 +42,11 @@ struct particle { enum font_shaping font_shaping; struct deco *deco; + struct themes *themes; + struct basedirs *basedirs; + struct string_list *icon_themes; + int icon_size; + void (*destroy)(struct particle *particle); struct exposable *(*instantiate)(const struct particle *particle, const struct tag_set *tags); }; @@ -61,8 +67,9 @@ struct exposable { }; struct particle *particle_common_new(int left_margin, int right_margin, char *on_click_templates[], - struct fcft_font *font, enum font_shaping font_shaping, pixman_color_t foreground, - struct deco *deco); + struct fcft_font *font, enum font_shaping font_shaping, struct basedirs *basedirs, + struct themes *themes, struct string_list *icon_themes, int icon_size, + pixman_color_t foreground, struct deco *deco); void particle_default_destroy(struct particle *particle); @@ -79,6 +86,8 @@ void exposable_default_on_mouse(struct exposable *exposable, struct bar *bar, en {"right-margin", false, &conf_verify_unsigned}, {"on-click", false, &conf_verify_on_click}, \ {"font", false, &conf_verify_font}, {"font-shaping", false, &conf_verify_font_shaping}, \ {"foreground", false, &conf_verify_color}, {"deco", false, &conf_verify_decoration}, \ + {"icon-basedirs", false, &conf_verify_string_list}, {"icon-themes", false, &conf_verify_string_list}, \ + {"icon-size", false, &conf_verify_unsigned}, \ { \ NULL, false, NULL \ } diff --git a/particles/icon.c b/particles/icon.c new file mode 100644 index 0000000..3ff010d --- /dev/null +++ b/particles/icon.c @@ -0,0 +1,206 @@ +#include +#include + +#define LOG_MODULE "particles/icon" +#define LOG_ENABLE_DBG 0 +#include "../config-verify.h" +#include "../config.h" +#include "../icon.h" +#include "../log.h" +#include "../particle.h" +#include "../plugin.h" +#include "../tag.h" + +struct private +{ + char *text; + bool use_tag; + struct particle *fallback; +}; + +struct eprivate { + struct exposable *exposable; + struct icon icon; + pixman_image_t *rasterized; +}; + +static void +exposable_destroy(struct exposable *exposable) +{ + struct eprivate *e = exposable->private; + if (e->exposable) + e->exposable->destroy(e->exposable); + + reset_icon(&e->icon); + free(e); + exposable_default_destroy(exposable); +} + +static int +begin_expose(struct exposable *exposable) +{ + struct eprivate *e = exposable->private; + + if (e->icon.type == ICON_NONE) { + if (e->exposable) { + exposable->width = e->exposable->begin_expose(e->exposable); + } else { + exposable->width = 0; + } + } else { + assert(e->exposable == NULL); + exposable->width = exposable->particle->right_margin + exposable->particle->left_margin; + exposable->width += exposable->particle->icon_size; + } + + return exposable->width; +} + +static void +expose(const struct exposable *exposable, pixman_image_t *pix, int x, int y, int height) +{ + exposable_render_deco(exposable, pix, x, y, height); + + const struct eprivate *e = exposable->private; + const struct particle *p = exposable->particle; + + int target_size = p->icon_size; + double baseline = (double)y + (double)(height - target_size) / 2.0; + + if (e->exposable != NULL) { + assert(e->icon.type == ICON_NONE); + e->exposable->expose(e->exposable, pix, x, y, height); + } else { + render_icon(&e->icon, x, baseline, target_size, pix); + } +} + +static struct exposable * +instantiate(const struct particle *particle, const struct tag_set *tags) +{ + struct private *p = (struct private *)particle->private; + struct eprivate *e = calloc(1, sizeof(*e)); + + char *name = tags_expand_template(p->text, tags); + e->icon.type = ICON_NONE; + e->exposable = NULL; + char *icon_path = NULL; + if (strlen(name) == 0) { + LOG_WARN("No icon name/tag available"); + goto fallback; + } + + // TODO: An icon cache + if (p->use_tag) { + const struct icon_tag *tag = icon_tag_for_name(tags, name); + + if (!tag) { + LOG_WARN("No icon tag for %s", name); + goto fallback; + } + + struct icon_pixmaps *pixmaps = tag->pixmaps(tag); + icon_from_pixmaps(&e->icon, pixmaps); + goto out; + } + + int min_size = 0, max_size = 0; + icon_path = find_icon(particle->themes->themes, particle->basedirs->basedirs, name, particle->icon_size, + particle->icon_themes->strings, &min_size, &max_size); + if (icon_path) { + int len = strlen(icon_path); + if ((icon_path[len - 3] == 's' && icon_from_svg(&e->icon, icon_path)) + || (icon_path[len - 3] == 'p' && icon_from_png(&e->icon, icon_path))) { + LOG_DBG("Loaded %s", icon_path); + goto out; + } + + LOG_WARN("Failed to load icon path, not png or svg: %s", icon_path); + goto fallback; + } + LOG_WARN("Icon not found: %s", name); + +fallback: + if (p->fallback) { + e->exposable = p->fallback->instantiate(p->fallback, tags); + assert(e->exposable != NULL); + } + +out: + free(icon_path); + free(name); + + struct exposable *exposable = exposable_common_new(particle, tags); + exposable->private = e; + exposable->destroy = &exposable_destroy; + exposable->begin_expose = &begin_expose; + exposable->expose = &expose; + return exposable; +} + +static void +particle_destroy(struct particle *particle) +{ + struct private *p = particle->private; + if (p->fallback) + p->fallback->destroy(p->fallback); + free(p->text); + free(p); + particle_default_destroy(particle); +} + +static struct particle * +icon_new(struct particle *common, const char *name, bool use_tag, struct particle *fallback) +{ + struct private *p = calloc(1, sizeof(*p)); + p->text = strdup(name); + p->use_tag = use_tag; + p->fallback = fallback; + + common->private = p; + common->destroy = &particle_destroy; + common->instantiate = &instantiate; + return common; +} + +static struct particle * +from_conf(const struct yml_node *node, struct particle *common) +{ + const struct yml_node *name = yml_get_value(node, "name"); + const struct yml_node *use_tag_node = yml_get_value(node, "use-tag"); + const struct yml_node *_fallback = yml_get_value(node, "fallback"); + + struct particle *fallback = NULL; + bool use_tag = false; + + if (use_tag_node) + use_tag = yml_value_as_bool(use_tag_node); + if (_fallback) + fallback = conf_to_particle(_fallback, (struct conf_inherit){common->font, common->font_shaping, common->themes, + common->basedirs, common->icon_themes, + common->icon_size, common->foreground}); + + return icon_new(common, yml_value_as_string(name), use_tag, fallback); +} + +static bool +verify_conf(keychain_t *chain, const struct yml_node *node) +{ + static const struct attr_info attrs[] = { + {"name", true, &conf_verify_string}, + {"use-tag", false, &conf_verify_bool}, + {"fallback", false, &conf_verify_particle}, + PARTICLE_COMMON_ATTRS, + }; + + return conf_verify_dict(chain, node, attrs); +} + +const struct particle_iface particle_icon_iface = { + .verify_conf = &verify_conf, + .from_conf = &from_conf, +}; + +#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) +extern const struct particle_iface iface __attribute__((weak, alias("particle_icon_iface"))); +#endif diff --git a/particles/list.c b/particles/list.c index 83b5d0c..f885eba 100644 --- a/particles/list.c +++ b/particles/list.c @@ -190,8 +190,9 @@ from_conf(const struct yml_node *node, struct particle *common) size_t idx = 0; for (struct yml_list_iter it = yml_list_iter(items); it.node != NULL; yml_list_next(&it), idx++) { - parts[idx] - = conf_to_particle(it.node, (struct conf_inherit){common->font, common->font_shaping, common->foreground}); + parts[idx] = conf_to_particle(it.node, (struct conf_inherit){common->font, common->font_shaping, common->themes, + common->basedirs, common->icon_themes, + common->icon_size, common->foreground}); } return particle_list_new(common, parts, count, left_spacing, right_spacing); diff --git a/particles/map.c b/particles/map.c index 51fc744..dbbd613 100644 --- a/particles/map.c +++ b/particles/map.c @@ -1,3 +1,5 @@ +#include "tag.h" +#include "yml.h" #include #include #include @@ -85,6 +87,11 @@ str_condition(const char *tag_value, const char *cond_value, enum map_op op) static bool eval_comparison(const struct map_condition *map_cond, const struct tag_set *tags) { + if (map_cond->op == MAP_OP_ICON_TAG) { + // Do the fallback check if MAP_OP_SELF for if an icon tag exists. + return icon_tag_for_name(tags, map_cond->tag) != NULL; + } + const struct tag *tag = tag_for_name(tags, map_cond->tag); if (tag == NULL) { LOG_WARN("tag %s not found", map_cond->tag); @@ -170,6 +177,7 @@ free_map_condition(struct map_condition *c) free(c->value); /* FALLTHROUGH */ case MAP_OP_SELF: + case MAP_OP_ICON_TAG: free(c->tag); break; case MAP_OP_AND: @@ -374,8 +382,15 @@ from_conf(const struct yml_node *node, struct particle *common) struct particle_map particle_map[yml_dict_length(conditions)]; - struct conf_inherit inherited - = {.font = common->font, .font_shaping = common->font_shaping, .foreground = common->foreground}; + struct conf_inherit inherited = { + .font = common->font, + .font_shaping = common->font_shaping, + .foreground = common->foreground, + .basedirs = common->basedirs, + .themes = common->themes, + .icon_themes = common->icon_themes, + .icon_size = common->icon_size, + }; size_t idx = 0; for (struct yml_dict_iter it = yml_dict_iter(conditions); it.key != NULL; yml_dict_next(&it), idx++) { diff --git a/particles/map.h b/particles/map.h index 23670a5..0ef02c7 100644 --- a/particles/map.h +++ b/particles/map.h @@ -8,6 +8,7 @@ enum map_op { MAP_OP_GE, MAP_OP_GT, MAP_OP_SELF, + MAP_OP_ICON_TAG, MAP_OP_NOT, MAP_OP_AND, diff --git a/particles/map.l b/particles/map.l index d34f086..5478be5 100644 --- a/particles/map.l +++ b/particles/map.l @@ -72,6 +72,7 @@ void yyerror(const char *s); && yylval.op = MAP_OP_AND; return BOOL_OP; \|\| yylval.op = MAP_OP_OR; return BOOL_OP; ~ return NOT; +\+ return AT; \( return L_PAR; \) return R_PAR; [ \t\n] ; diff --git a/particles/map.y b/particles/map.y index ee426da..e1becce 100644 --- a/particles/map.y +++ b/particles/map.y @@ -21,7 +21,7 @@ void yyerror(const char *str); enum map_op op; } -%token WORD STRING CMP_OP L_PAR R_PAR +%token WORD STRING CMP_OP L_PAR R_PAR AT %left BOOL_OP %precedence NOT @@ -33,29 +33,35 @@ void yyerror(const char *str); result: condition { MAP_CONDITION_PARSE_RESULT = $1; }; condition: + AT WORD { + $$ = malloc(sizeof(struct map_condition)); + $$->tag = $2; + $$->op = MAP_OP_ICON_TAG; + } + | WORD { $$ = malloc(sizeof(struct map_condition)); - $$->tag = $1; + $$->tag = $1; $$->op = MAP_OP_SELF; } | WORD CMP_OP WORD { $$ = malloc(sizeof(struct map_condition)); - $$->tag = $1; + $$->tag = $1; $$->op = $2; - $$->value = $3; + $$->value = $3; } | WORD CMP_OP STRING { $$ = malloc(sizeof(struct map_condition)); - $$->tag = $1; + $$->tag = $1; $$->op = $2; - $$->value = $3; + $$->value = $3; } | L_PAR condition R_PAR { $$ = $2; } | - NOT condition { + NOT condition { $$ = malloc(sizeof(struct map_condition)); $$->cond1 = $2; $$->op = MAP_OP_NOT; @@ -84,6 +90,7 @@ token_to_str(yysymbol_kind_t tkn) case YYSYMBOL_L_PAR: return "("; case YYSYMBOL_R_PAR: return ")"; case YYSYMBOL_NOT: return "~"; + case YYSYMBOL_AT: return "+"; default: return yysymbol_name(tkn); } } diff --git a/particles/meson.build b/particles/meson.build index 091f551..5479e12 100644 --- a/particles/meson.build +++ b/particles/meson.build @@ -20,7 +20,7 @@ pfiles = pgen.process('map.y') map_parser = declare_dependency(sources: [pfiles, lfiles], include_directories: '.') -particle_sdk = declare_dependency(dependencies: [pixman, tllist, fcft]) +particle_sdk = declare_dependency(dependencies: [pixman, tllist, fcft, nanosvg]) dynlist_lib = build_target( 'dynlist', 'dynlist.c', 'dynlist.h', dependencies: particle_sdk, @@ -40,6 +40,7 @@ deps = { 'progress-bar': [], 'ramp': [], 'string': [], + 'icon': [], } particles = [] diff --git a/particles/progress-bar.c b/particles/progress-bar.c index f0bacbf..c663eaa 100644 --- a/particles/progress-bar.c +++ b/particles/progress-bar.c @@ -296,6 +296,10 @@ from_conf(const struct yml_node *node, struct particle *common) .font = common->font, .font_shaping = common->font_shaping, .foreground = common->foreground, + .basedirs = common->basedirs, + .themes = common->themes, + .icon_themes = common->icon_themes, + .icon_size = common->icon_size, }; return progress_bar_new(common, yml_value_as_string(tag), yml_value_as_int(length), diff --git a/particles/ramp.c b/particles/ramp.c index befe1d9..b00db98 100644 --- a/particles/ramp.c +++ b/particles/ramp.c @@ -199,8 +199,9 @@ from_conf(const struct yml_node *node, struct particle *common) size_t idx = 0; for (struct yml_list_iter it = yml_list_iter(items); it.node != NULL; yml_list_next(&it), idx++) { - parts[idx] - = conf_to_particle(it.node, (struct conf_inherit){common->font, common->font_shaping, common->foreground}); + parts[idx] = conf_to_particle(it.node, (struct conf_inherit){common->font, common->font_shaping, common->themes, + common->basedirs, common->icon_themes, + common->icon_size, common->foreground}); } long min_v = min != NULL ? yml_value_as_int(min) : 0; diff --git a/plugin.c b/plugin.c index 8e75389..9d1ef74 100644 --- a/plugin.c +++ b/plugin.c @@ -97,6 +97,7 @@ EXTERN_PARTICLE(map); EXTERN_PARTICLE(progress_bar); EXTERN_PARTICLE(ramp); EXTERN_PARTICLE(string); +EXTERN_PARTICLE(icon); EXTERN_DECORATION(background); EXTERN_DECORATION(border); @@ -227,6 +228,7 @@ static void __attribute__((constructor)) init(void) REGISTER_CORE_PARTICLE(progress-bar, progress_bar); REGISTER_CORE_PARTICLE(ramp, ramp); REGISTER_CORE_PARTICLE(string, string); + REGISTER_CORE_PARTICLE(icon, icon); REGISTER_CORE_DECORATION(background, background); REGISTER_CORE_DECORATION(border, border); diff --git a/png-yambar.h b/png-yambar.h new file mode 100644 index 0000000..bf15f9d --- /dev/null +++ b/png-yambar.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +pixman_image_t *png_load(const char *path); diff --git a/png.c b/png.c new file mode 100644 index 0000000..2338111 --- /dev/null +++ b/png.c @@ -0,0 +1,171 @@ +#include "png-yambar.h" +#include +#include +#include +#include +#include + +#include +#include + +#define LOG_MODULE "png" +#define LOG_ENABLE_DBG 0 +#include "log.h" +#include "stride.h" + +static void +png_warning_cb(png_structp png_ptr, png_const_charp warning_msg) +{ + LOG_WARN("libpng: %s", warning_msg); +} + +pixman_image_t * +png_load(const char *path) +{ + pixman_image_t *pix = NULL; + + FILE *fp = NULL; + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + png_bytepp row_pointers = NULL; + uint8_t *image_data = NULL; + + /* open file and test for it being a png */ + if ((fp = fopen(path, "rb")) == NULL) { + // LOG_ERRNO("%s: failed to open", path); + goto err; + } + + /* Verify PNG header */ + uint8_t header[8] = {0}; + if (fread(header, 1, 8, fp) != 8 || png_sig_cmp(header, 0, 8)) { + // LOG_ERR("%s: not a PNG", path); + goto err; + } + + /* Prepare for reading the PNG */ + if ((png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)) == NULL + || (info_ptr = png_create_info_struct(png_ptr)) == NULL) { + LOG_ERR("%s: failed to initialize libpng", path); + goto err; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + LOG_ERR("%s: libpng error", path); + goto err; + } + + /* Set custom “warning” function */ + png_set_error_fn(png_ptr, png_get_error_ptr(png_ptr), NULL, &png_warning_cb); + + png_init_io(png_ptr, fp); + png_set_sig_bytes(png_ptr, 8); + + /* Get meta data */ + png_read_info(png_ptr, info_ptr); + int width = png_get_image_width(png_ptr, info_ptr); + int height = png_get_image_height(png_ptr, info_ptr); + png_byte color_type = png_get_color_type(png_ptr, info_ptr); + png_byte bit_depth __attribute__((unused)) = png_get_bit_depth(png_ptr, info_ptr); + int channels __attribute__((unused)) = png_get_channels(png_ptr, info_ptr); + + LOG_DBG("%s: %dx%d@%hhubpp, %d channels", path, width, height, bit_depth, channels); + + png_set_packing(png_ptr); + png_set_interlace_handling(png_ptr); + png_set_strip_16(png_ptr); /* "pack" 16-bit colors to 8-bit */ + png_set_bgr(png_ptr); + + /* pixman expects pre-multiplied alpha */ + + /* Tell libpng to expand to RGB(A) when necessary, and tell pixman + * whether we have alpha or not */ + pixman_format_code_t format; + switch (color_type) { + case PNG_COLOR_TYPE_GRAY: + case PNG_COLOR_TYPE_GRAY_ALPHA: + LOG_DBG("%d-bit gray%s", bit_depth, color_type == PNG_COLOR_TYPE_GRAY_ALPHA ? "+alpha" : ""); + + if (bit_depth < 8) + png_set_expand_gray_1_2_4_to_8(png_ptr); + + png_set_gray_to_rgb(png_ptr); + format = color_type == PNG_COLOR_TYPE_GRAY ? PIXMAN_r8g8b8 : PIXMAN_a8r8g8b8; + break; + + case PNG_COLOR_TYPE_PALETTE: + LOG_DBG("%d-bit colormap%s", bit_depth, png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) ? "+tRNS" : ""); + + png_set_palette_to_rgb(png_ptr); + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(png_ptr); + format = PIXMAN_a8r8g8b8; + } else + format = PIXMAN_r8g8b8; + break; + + case PNG_COLOR_TYPE_RGB: + LOG_DBG("RGB"); + format = PIXMAN_r8g8b8; + break; + + case PNG_COLOR_TYPE_RGBA: + LOG_DBG("RGBA"); + format = PIXMAN_a8r8g8b8; + break; + } + + png_read_update_info(png_ptr, info_ptr); + + size_t row_bytes __attribute__((unused)) = png_get_rowbytes(png_ptr, info_ptr); + int stride = stride_for_format_and_width(format, width); + image_data = malloc(height * stride); + + LOG_DBG("stride=%d, row-bytes=%zu", stride, row_bytes); + assert(stride >= row_bytes); + + row_pointers = malloc(height * sizeof(png_bytep)); + for (int i = 0; i < height; i++) + row_pointers[i] = &image_data[i * stride]; + + png_read_image(png_ptr, row_pointers); + + /* pixman expects pre-multiplied alpha */ + if (format == PIXMAN_a8r8g8b8) { + for (int i = 0; i < height; i++) { + uint32_t *p = (uint32_t *)row_pointers[i]; + for (int j = 0; j < width; j++, p++) { + uint8_t a = (*p >> 24) & 0xff; + uint8_t r = (*p >> 16) & 0xff; + uint8_t g = (*p >> 8) & 0xff; + uint8_t b = (*p >> 0) & 0xff; + + if (a == 0xff) + continue; + + if (a == 0) { + r = g = b = 0; + } else { + r = r * a / 0xff; + g = g * a / 0xff; + b = b * a / 0xff; + } + + *p = (uint32_t)a << 24 | r << 16 | g << 8 | b; + } + } + } + + pix = pixman_image_create_bits_no_clear(format, width, height, (uint32_t *)image_data, stride); + +err: + if (pix == NULL) + free(image_data); + free(row_pointers); + if (png_ptr != NULL) + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + if (fp != NULL) + fclose(fp); + + return pix; +} diff --git a/stringop.c b/stringop.c new file mode 100644 index 0000000..33f8ae7 --- /dev/null +++ b/stringop.c @@ -0,0 +1,42 @@ +#include +#include + +#define LOG_MODULE "icon" +#define LOG_ENABLE_DBG 0 +#include "log.h" + +char * +vformat_str(const char *fmt, va_list args) +{ + char *str = NULL; + va_list args_copy; + va_copy(args_copy, args); + + int len = vsnprintf(NULL, 0, fmt, args); + if (len < 0) { + LOG_ERR("vsnprintf(\"%s\") failed", fmt); + goto out; + } + + str = malloc(len + 1); + if (str == NULL) { + LOG_ERR("malloc() failed"); + goto out; + } + + vsnprintf(str, len + 1, fmt, args_copy); + +out: + va_end(args_copy); + return str; +} + +char * +format_str(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + char *str = vformat_str(fmt, args); + va_end(args); + return str; +} diff --git a/stringop.h b/stringop.h new file mode 100644 index 0000000..3bcefe5 --- /dev/null +++ b/stringop.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#ifdef __GNUC__ +#define _SWAY_ATTRIB_PRINTF(start, end) __attribute__((format(printf, start, end))) +#else +#define _SWAY_ATTRIB_PRINTF(start, end) +#endif + +char *vformat_str(const char *fmt, va_list args) _SWAY_ATTRIB_PRINTF(1, 0); +char *format_str(const char *fmt, ...) _SWAY_ATTRIB_PRINTF(1, 2); diff --git a/svg.c b/svg.c new file mode 100644 index 0000000..0dd0c67 --- /dev/null +++ b/svg.c @@ -0,0 +1,61 @@ +#include "svg.h" + +#include + +#define LOG_MODULE "svg" +#define LOG_ENABLE_DBG 0 +#include "log.h" + +#include +#include + +pixman_image_t * +svg_load(const char *path, int size) +{ + NSVGimage *svg = nsvgParseFromFile(path, "px", 96); + if (svg == NULL) + return NULL; + + if (svg->width == 0 || svg->height == 0) { + nsvgDelete(svg); + return NULL; + } + + struct NSVGrasterizer *rast = nsvgCreateRasterizer(); + + const int w = size; + const int h = size; + float scale = w > h ? w / svg->width : h / svg->height; + + uint8_t *data = malloc(h * w * 4); + nsvgRasterize(rast, svg, 0, 0, scale, data, w, h, w * 4); + + nsvgDeleteRasterizer(rast); + nsvgDelete(svg); + + pixman_image_t *img = pixman_image_create_bits_no_clear(PIXMAN_a8b8g8r8, w, h, (uint32_t *)data, w * 4); + + /* Nanosvg produces non-premultiplied ABGR, while pixman expects + * premultiplied */ + for (uint32_t *abgr = (uint32_t *)data; abgr < (uint32_t *)(data + h * w * 4); abgr++) { + uint8_t alpha = (*abgr >> 24) & 0xff; + uint8_t blue = (*abgr >> 16) & 0xff; + uint8_t green = (*abgr >> 8) & 0xff; + uint8_t red = (*abgr >> 0) & 0xff; + + if (alpha == 0xff) + continue; + + if (alpha == 0x00) + blue = green = red = 0x00; + else { + blue = blue * alpha / 0xff; + green = green * alpha / 0xff; + red = red * alpha / 0xff; + } + + *abgr = (uint32_t)alpha << 24 | blue << 16 | green << 8 | red; + } + + return img; +} diff --git a/svg.h b/svg.h new file mode 100644 index 0000000..b1e315f --- /dev/null +++ b/svg.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +pixman_image_t *svg_load(const char *path, int size); diff --git a/tag.c b/tag.c index e95b1c7..481b7c5 100644 --- a/tag.c +++ b/tag.c @@ -9,7 +9,8 @@ #include #define LOG_MODULE "tag" -#define LOG_ENABLE_DBG 1 +#define LOG_ENABLE_DBG 0 +#include "icon.h" #include "log.h" #include "module.h" @@ -29,6 +30,11 @@ struct private }; }; +struct icon_private { + char *name; + struct icon_pixmaps *pixmaps; +}; + static const char * tag_name(const struct tag *tag) { @@ -375,6 +381,50 @@ tag_new_string(struct module *owner, const char *name, const char *value) return tag; } +static const char * +icon_tag_name(const struct icon_tag *icon_tag) +{ + const struct icon_private *priv = icon_tag->private; + return priv->name; +} + +static struct icon_pixmaps * +pixmaps_as_pixmaps(const struct icon_tag *icon_tag) +{ + const struct icon_private *priv = icon_tag->private; + return priv->pixmaps; +} + +void +pixmap_destroy(struct icon_tag *icon_tag) +{ + struct icon_private *priv = icon_tag->private; + icon_pixmaps_dec(priv->pixmaps); + free(priv->name); + free(priv); + free(icon_tag); +} + +struct icon_tag * +icon_tag_new_pixmap(struct module *owner, const char *name, struct icon_pixmaps *pixmaps) +{ + if (pixmaps == NULL) { + return NULL; + } + struct icon_private *priv = malloc(sizeof(*priv)); + priv->name = strdup(name); + priv->pixmaps = icon_pixmaps_inc(pixmaps); + + struct icon_tag *icon_tag = malloc(sizeof(*icon_tag)); + icon_tag->private = priv; + icon_tag->owner = owner; + icon_tag->name = &icon_tag_name; + icon_tag->pixmaps = &pixmaps_as_pixmaps; + icon_tag->destroy = &pixmap_destroy; + + return icon_tag; +} + const struct tag * tag_for_name(const struct tag_set *set, const char *name) { @@ -390,14 +440,22 @@ tag_for_name(const struct tag_set *set, const char *name) return NULL; } -void -tag_set_destroy(struct tag_set *set) +const struct icon_tag * +icon_tag_for_name(const struct tag_set *set, const char *name) { - for (size_t i = 0; i < set->count; i++) - set->tags[i]->destroy(set->tags[i]); + if (set == NULL) + return NULL; - set->tags = NULL; - set->count = 0; + for (size_t i = 0; i < set->icon_count; i++) { + const struct icon_tag *tag = set->icon_tags[i]; + if (!tag) + continue; + + if (strcmp(tag->name(tag), name) == 0) + return tag; + } + + return NULL; } struct sbuf { @@ -744,3 +802,19 @@ tags_expand_templates(char *expanded[], const char *template[], size_t nmemb, co for (size_t i = 0; i < nmemb; i++) expanded[i] = tags_expand_template(template[i], tags); } + +void +tag_set_destroy(struct tag_set *set) +{ + for (size_t i = 0; i < set->count; i++) + set->tags[i]->destroy(set->tags[i]); + + for (size_t i = 0; i < set->icon_count; i++) { + if (set->icon_tags[i] != NULL) { + set->icon_tags[i]->destroy(set->icon_tags[i]); + } + } + + set->tags = NULL; + set->count = 0; +} diff --git a/tag.h b/tag.h index 6149b1e..df60b62 100644 --- a/tag.h +++ b/tag.h @@ -1,8 +1,11 @@ #pragma once +#include #include #include +#include "icon.h" + enum tag_type { TAG_TYPE_BOOL, TAG_TYPE_INT, @@ -37,9 +40,20 @@ struct tag { bool (*refresh_in)(const struct tag *tag, long units); }; +struct icon_tag { + void *private; + struct module *owner; + + void (*destroy)(struct icon_tag *destroy); + const char *(*name)(const struct icon_tag *tag); + struct icon_pixmaps *(*pixmaps)(const struct icon_tag *tag); +}; + struct tag_set { struct tag **tags; + struct icon_tag **icon_tags; size_t count; + size_t icon_count; }; struct tag *tag_new_int(struct module *owner, const char *name, long value); @@ -50,7 +64,10 @@ struct tag *tag_new_bool(struct module *owner, const char *name, bool value); struct tag *tag_new_float(struct module *owner, const char *name, double value); struct tag *tag_new_string(struct module *owner, const char *name, const char *value); +struct icon_tag *icon_tag_new_pixmap(struct module *owner, const char *name, struct icon_pixmaps *icon_pixmap); + const struct tag *tag_for_name(const struct tag_set *set, const char *name); +const struct icon_tag *icon_tag_for_name(const struct tag_set *set, const char *name); void tag_set_destroy(struct tag_set *set); /* Utility functions */ diff --git a/xdg.c b/xdg.c new file mode 100644 index 0000000..e69de29