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.
This commit is contained in:
Jordan Isaacs 2023-11-13 23:25:05 -08:00
parent 1a323c6d21
commit 6113f9b94e
No known key found for this signature in database
GPG key ID: 98983D44651F8116
51 changed files with 8473 additions and 40 deletions

75
3rd-party/nanosvg/CMakeLists.txt vendored Normal file
View file

@ -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 $<INSTALL_INTERFACE:include/nanosvg>)
target_compile_definitions(nanosvg PRIVATE NANOSVG_IMPLEMENTATION)
# Same for nanosvgrast
add_library(nanosvgrast ${CMAKE_CURRENT_BINARY_DIR}/nanosvgrast.c)
target_link_libraries(nanosvgrast PUBLIC nanosvg)
target_include_directories(nanosvgrast PRIVATE src)
target_compile_definitions(nanosvgrast PRIVATE NANOSVGRAST_IMPLEMENTATION)
# Installation and export:
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
VERSION 1.0
COMPATIBILITY AnyNewerVersion
)
install(TARGETS nanosvg nanosvgrast
EXPORT ${PROJECT_NAME}Targets
)
export(EXPORT ${PROJECT_NAME}Targets
FILE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake"
NAMESPACE ${PROJECT_NAME}::
)
set(ConfigPackageLocation lib/cmake/${PROJECT_NAME})
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
INSTALL_DESTINATION ${ConfigPackageLocation}
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
install(
FILES
src/nanosvg.h
src/nanosvgrast.h
DESTINATION
include/nanosvg
)
install(EXPORT ${PROJECT_NAME}Targets
FILE
${PROJECT_NAME}Targets.cmake
NAMESPACE
${PROJECT_NAME}::
DESTINATION
${ConfigPackageLocation}
)
install(
FILES
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
DESTINATION
${ConfigPackageLocation}
)

5
3rd-party/nanosvg/Config.cmake.in vendored Normal file
View file

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

18
3rd-party/nanosvg/LICENSE.txt vendored Normal file
View file

@ -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.

112
3rd-party/nanosvg/README.md vendored Normal file
View file

@ -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 <stdio.h>
#include <string.h>
#include <math.h>
#define NANOSVG_IMPLEMENTATION // Expands implementation
#include "nanosvg.h"
```
By default, NanoSVG parses only the most common colors. In order to get support for full list of [SVG color keywords](http://www.w3.org/TR/SVG11/types.html#ColorKeywords), define `NANOSVG_ALL_COLOR_KEYWORDS` before expanding the implementation.
``` C
#include <stdio.h>
#include <string.h>
#include <math.h>
#define NANOSVG_ALL_COLOR_KEYWORDS // Include full list of color keywords.
#define NANOSVG_IMPLEMENTATION // Expands implementation
#include "nanosvg.h"
```
Alternatively, you can install the library using CMake and import it into your project using the standard CMake `find_package` command.
```CMake
add_executable(myexe main.c)
find_package(NanoSVG REQUIRED)
target_link_libraries(myexe NanoSVG::nanosvg NanoSVG::nanosvgrast)
```
## Compiling Example Project
In order to compile the demo project, your will need to install [GLFW](http://www.glfw.org/) to compile.
NanoSVG demo project uses [premake4](http://industriousone.com/premake) to build platform specific projects, now is good time to install it if you don't have it already. To build the example, navigate into the root folder in your favorite terminal, then:
- *OS X*: `premake4 xcode4`
- *Windows*: `premake4 vs2010`
- *Linux*: `premake4 gmake`
See premake4 documentation for full list of supported build file types. The projects will be created in `build` folder. An example of building and running the example on OS X:
```bash
$ premake4 gmake
$ cd build/
$ make
$ ./example
```
# License
The library is licensed under [zlib license](LICENSE.txt)

730
3rd-party/nanosvg/example/23.svg vendored Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 94 KiB

97
3rd-party/nanosvg/example/drawing.svg vendored Normal file
View file

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

After

Width:  |  Height:  |  Size: 3.4 KiB

258
3rd-party/nanosvg/example/example1.c vendored Normal file
View file

@ -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 <stdio.h>
#include <string.h>
#include <float.h>
#include <GLFW/glfw3.h>
#define NANOSVG_IMPLEMENTATION
#include "nanosvg.h"
NSVGimage* g_image = NULL;
static unsigned char bgColor[4] = {205,202,200,255};
static unsigned char lineColor[4] = {0,160,192,255};
static float distPtSeg(float x, float y, float px, float py, float qx, float qy)
{
float pqx, pqy, dx, dy, d, t;
pqx = qx-px;
pqy = qy-py;
dx = x-px;
dy = y-py;
d = pqx*pqx + pqy*pqy;
t = pqx*dx + pqy*dy;
if (d > 0) t /= d;
if (t < 0) t = 0;
else if (t > 1) t = 1;
dx = px + t*pqx - x;
dy = py + t*pqy - y;
return dx*dx + dy*dy;
}
static void cubicBez(float x1, float y1, float x2, float y2,
float x3, float y3, float x4, float y4,
float tol, int level)
{
float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234;
float d;
if (level > 12) return;
x12 = (x1+x2)*0.5f;
y12 = (y1+y2)*0.5f;
x23 = (x2+x3)*0.5f;
y23 = (y2+y3)*0.5f;
x34 = (x3+x4)*0.5f;
y34 = (y3+y4)*0.5f;
x123 = (x12+x23)*0.5f;
y123 = (y12+y23)*0.5f;
x234 = (x23+x34)*0.5f;
y234 = (y23+y34)*0.5f;
x1234 = (x123+x234)*0.5f;
y1234 = (y123+y234)*0.5f;
d = distPtSeg(x1234, y1234, x1,y1, x4,y4);
if (d > tol*tol) {
cubicBez(x1,y1, x12,y12, x123,y123, x1234,y1234, tol, level+1);
cubicBez(x1234,y1234, x234,y234, x34,y34, x4,y4, tol, level+1);
} else {
glVertex2f(x4, y4);
}
}
void drawPath(float* pts, int npts, char closed, float tol)
{
int i;
glBegin(GL_LINE_STRIP);
glColor4ubv(lineColor);
glVertex2f(pts[0], pts[1]);
for (i = 0; i < npts-1; i += 3) {
float* p = &pts[i*2];
cubicBez(p[0],p[1], p[2],p[3], p[4],p[5], p[6],p[7], tol, 0);
}
if (closed) {
glVertex2f(pts[0], pts[1]);
}
glEnd();
}
void drawControlPts(float* pts, int npts)
{
int i;
// Control lines
glColor4ubv(lineColor);
glBegin(GL_LINES);
for (i = 0; i < npts-1; i += 3) {
float* p = &pts[i*2];
glVertex2f(p[0],p[1]);
glVertex2f(p[2],p[3]);
glVertex2f(p[4],p[5]);
glVertex2f(p[6],p[7]);
}
glEnd();
// Points
glPointSize(6.0f);
glColor4ubv(lineColor);
glBegin(GL_POINTS);
glVertex2f(pts[0],pts[1]);
for (i = 0; i < npts-1; i += 3) {
float* p = &pts[i*2];
glVertex2f(p[6],p[7]);
}
glEnd();
// Points
glPointSize(3.0f);
glBegin(GL_POINTS);
glColor4ubv(bgColor);
glVertex2f(pts[0],pts[1]);
for (i = 0; i < npts-1; i += 3) {
float* p = &pts[i*2];
glColor4ubv(lineColor);
glVertex2f(p[2],p[3]);
glVertex2f(p[4],p[5]);
glColor4ubv(bgColor);
glVertex2f(p[6],p[7]);
}
glEnd();
}
void drawframe(GLFWwindow* window)
{
int width = 0, height = 0;
float view[4], cx, cy, hw, hh, aspect, px;
NSVGshape* shape;
NSVGpath* path;
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
glfwGetFramebufferSize(window, &width, &height);
glViewport(0, 0, width, height);
glClearColor(220.0f/255.0f, 220.0f/255.0f, 220.0f/255.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_TEXTURE_2D);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
// Fit view to bounds
cx = g_image->width*0.5f;
cy = g_image->height*0.5f;
hw = g_image->width*0.5f;
hh = g_image->height*0.5f;
if (width/hw < height/hh) {
aspect = (float)height / (float)width;
view[0] = cx - hw * 1.2f;
view[2] = cx + hw * 1.2f;
view[1] = cy - hw * 1.2f * aspect;
view[3] = cy + hw * 1.2f * aspect;
} else {
aspect = (float)width / (float)height;
view[0] = cx - hh * 1.2f * aspect;
view[2] = cx + hh * 1.2f * aspect;
view[1] = cy - hh * 1.2f;
view[3] = cy + hh * 1.2f;
}
// Size of one pixel.
px = (view[2] - view[1]) / (float)width;
glOrtho(view[0], view[2], view[3], view[1], -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_DEPTH_TEST);
glColor4ub(255,255,255,255);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
// Draw bounds
glColor4ub(0,0,0,64);
glBegin(GL_LINE_LOOP);
glVertex2f(0, 0);
glVertex2f(g_image->width, 0);
glVertex2f(g_image->width, g_image->height);
glVertex2f(0, g_image->height);
glEnd();
for (shape = g_image->shapes; shape != NULL; shape = shape->next) {
for (path = shape->paths; path != NULL; path = path->next) {
drawPath(path->pts, path->npts, path->closed, px * 1.5f);
drawControlPts(path->pts, path->npts);
}
}
glfwSwapBuffers(window);
}
void resizecb(GLFWwindow* window, int width, int height)
{
// Update and render
NSVG_NOTUSED(width);
NSVG_NOTUSED(height);
drawframe(window);
}
int main()
{
GLFWwindow* window;
const GLFWvidmode* mode;
if (!glfwInit())
return -1;
mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
window = glfwCreateWindow(mode->width - 40, mode->height - 80, "Nano SVG", NULL, NULL);
if (!window)
{
printf("Could not open window\n");
glfwTerminate();
return -1;
}
glfwSetFramebufferSizeCallback(window, resizecb);
glfwMakeContextCurrent(window);
glEnable(GL_POINT_SMOOTH);
glEnable(GL_LINE_SMOOTH);
g_image = nsvgParseFromFile("../example/nano.svg", "px", 96.0f);
if (g_image == NULL) {
printf("Could not open SVG image.\n");
glfwTerminate();
return -1;
}
while (!glfwWindowShouldClose(window))
{
drawframe(window);
glfwPollEvents();
}
nsvgDelete(g_image);
glfwTerminate();
return 0;
}

69
3rd-party/nanosvg/example/example2.c vendored Normal file
View file

@ -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 <stdio.h>
#include <string.h>
#include <float.h>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
#define NANOSVG_IMPLEMENTATION
#include "nanosvg.h"
#define NANOSVGRAST_IMPLEMENTATION
#include "nanosvgrast.h"
int main()
{
NSVGimage *image = NULL;
NSVGrasterizer *rast = NULL;
unsigned char* img = NULL;
int w, h;
const char* filename = "../example/23.svg";
printf("parsing %s\n", filename);
image = nsvgParseFromFile(filename, "px", 96.0f);
if (image == NULL) {
printf("Could not open SVG image.\n");
goto error;
}
w = (int)image->width;
h = (int)image->height;
rast = nsvgCreateRasterizer();
if (rast == NULL) {
printf("Could not init rasterizer.\n");
goto error;
}
img = malloc(w*h*4);
if (img == NULL) {
printf("Could not alloc image buffer.\n");
goto error;
}
printf("rasterizing image %d x %d\n", w, h);
nsvgRasterize(rast, image, 0,0,1, img, w, h, w*4);
printf("writing svg.png\n");
stbi_write_png("svg.png", w, h, 4, img, w*4);
error:
nsvgDeleteRasterizer(rast);
nsvgDelete(image);
return 0;
}

27
3rd-party/nanosvg/example/nano.svg vendored Normal file
View file

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

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

View file

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

56
3rd-party/nanosvg/premake4.lua vendored Normal file
View file

@ -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"}

3098
3rd-party/nanosvg/src/nanosvg.h vendored Normal file

File diff suppressed because it is too large Load diff

1458
3rd-party/nanosvg/src/nanosvgrast.h vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -13,6 +13,7 @@ depends=(
'pixman' 'pixman'
'libyaml' 'libyaml'
'alsa-lib' 'alsa-lib'
'libpng'
'libudev.so' 'libudev.so'
'json-c' 'json-c'
'libmpdclient' 'libmpdclient'

View file

@ -1,4 +1,5 @@
#include "bar.h" #include "bar.h"
#include "icon.h"
#include "private.h" #include "private.h"
#include <assert.h> #include <assert.h>

View file

@ -9,6 +9,8 @@ struct private
char *monitor; char *monitor;
enum bar_layer layer; enum bar_layer layer;
enum bar_location location; enum bar_location location;
struct basedirs *basedirs;
struct themes *themes;
int height; int height;
int left_spacing, right_spacing; int left_spacing, right_spacing;
int left_margin, right_margin; int left_margin, right_margin;

View file

@ -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); 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 bool
conf_verify_decoration(keychain_t *chain, const struct yml_node *node) 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}, {"font-shaping", false, &conf_verify_font_shaping},
{"foreground", false, &conf_verify_color}, {"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}, {"left", false, &verify_module_list},
{"center", false, &verify_module_list}, {"center", false, &verify_module_list},
{"right", false, &verify_module_list}, {"right", false, &verify_module_list},

View file

@ -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_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_decoration(keychain_t *chain, const struct yml_node *node);
bool conf_verify_string_list(keychain_t *chain, const struct yml_node *node);

146
config.c
View file

@ -5,14 +5,18 @@
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <wordexp.h>
#include <dlfcn.h> #include <dlfcn.h>
#include "bar/bar.h" #include "bar/bar.h"
#include "color.h" #include "color.h"
#include "config-verify.h" #include "config-verify.h"
#include "icon.h"
#include "module.h" #include "module.h"
#include "plugin.h" #include "plugin.h"
#include "tllist.h"
#include "yml.h"
#define LOG_MODULE "config" #define LOG_MODULE "config"
#define LOG_ENABLE_DBG 0 #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 * struct deco *
conf_to_deco(const struct yml_node *node) 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); assert(particle_list_new != NULL);
} }
struct particle *common = particle_common_new(0, 0, NULL, fcft_clone(inherited.font), inherited.font_shaping, struct particle *common
inherited.foreground, NULL); = 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); 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 *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 *foreground_node = yml_get_value(pair.value, "foreground");
const struct yml_node *deco_node = yml_get_value(pair.value, "deco"); 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 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; 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; = 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; 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 */ /* Instantiate base/common particle */
struct particle *common struct particle *common = particle_common_new(left, right, on_click_templates, font, font_shaping, basedirs, themes,
= particle_common_new(left, right, on_click_templates, font, font_shaping, foreground, deco); icon_themes, icon_size, foreground, deco);
const struct particle_iface *iface = plugin_load_particle(type); 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) if (font_shaping_node != NULL)
font_shaping = conf_to_font_shaping(font_shaping_node); 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"); const struct yml_node *foreground_node = yml_get_value(bar, "foreground");
if (foreground_node != NULL) if (foreground_node != NULL)
foreground = conf_to_color(foreground_node); foreground = conf_to_color(foreground_node);
struct conf_inherit inherited = { struct conf_inherit inherited = {.font = font,
.font = font, .font_shaping = font_shaping,
.font_shaping = font_shaping, .basedirs = basedirs,
.foreground = foreground, .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 *left = yml_get_value(bar, "left");
const struct yml_node *center = yml_get_value(bar, "center"); 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 = 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_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_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 = { struct basedirs *inherit_basedirs = NULL;
.font = mod_font != NULL ? conf_to_font(mod_font) : inherited.font, struct themes *inherit_themes = NULL;
.font_shaping if (mod_basedirs) {
= mod_font_shaping != NULL ? conf_to_font_shaping(mod_font_shaping) : inherited.font_shaping, conf_to_themes(mod_basedirs, &inherit_basedirs, &inherit_themes);
.foreground = mod_foreground != NULL ? conf_to_color(mod_foreground) : inherited.foreground, } 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); const struct module_iface *iface = plugin_load_module(mod_name);
mods[idx] = iface->from_conf(m.value, mod_inherit); 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) { 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.center.mods);
free(conf.right.mods); free(conf.right.mods);
fcft_destroy(font); fcft_destroy(font);
themes_dec(themes);
basedirs_dec(basedirs);
string_list_dec(icon_themes);
return ret; return ret;
} }

View file

@ -2,6 +2,7 @@
#include "bar/bar.h" #include "bar/bar.h"
#include "font-shaping.h" #include "font-shaping.h"
#include "icon.h"
#include "yml.h" #include "yml.h"
#include <fcft/fcft.h> #include <fcft/fcft.h>
@ -22,6 +23,10 @@ enum font_shaping conf_to_font_shaping(const struct yml_node *node);
struct conf_inherit { struct conf_inherit {
const struct fcft_font *font; const struct fcft_font *font;
enum font_shaping font_shaping; enum font_shaping font_shaping;
struct themes *themes;
struct basedirs *basedirs;
struct string_list *icon_themes;
int icon_size;
pixman_color_t foreground; pixman_color_t foreground;
}; };

968
icon.c Normal file
View file

@ -0,0 +1,968 @@
#include "tllist.h"
#include <ctype.h>
#include <dirent.h>
#include <limits.h>
#include <nanosvg/nanosvg.h>
#include <nanosvg/nanosvgrast.h>
#include <pixman.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <wordexp.h>
#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(
&param_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;
}

122
icon.h Normal file
View file

@ -0,0 +1,122 @@
#pragma once
#include <nanosvg/nanosvg.h>
#include <pixman.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <tllist.h>
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);

View file

@ -52,6 +52,20 @@ libepoll = dependency('epoll-shim', required: false)
libinotify = dependency('libinotify', required: false) libinotify = dependency('libinotify', required: false)
pixman = dependency('pixman-1') pixman = dependency('pixman-1')
yaml = dependency('yaml-0.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 # X11/XCB dependencies
xcb_aux = dependency('xcb-aux', required: get_option('backend-x11')) xcb_aux = dependency('xcb-aux', required: get_option('backend-x11'))
@ -128,8 +142,11 @@ yambar = executable(
'plugin.c', 'plugin.h', 'plugin.c', 'plugin.h',
'tag.c', 'tag.h', 'tag.c', 'tag.h',
'yml.c', 'yml.h', 'yml.c', 'yml.h',
'icon.c', 'icon.h',
'png.c', 'png-yambar.h',
'stringop.c', 'stringop.h',
version, 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, decorations + particles + modules,
build_rpath: '$ORIGIN/modules:$ORIGIN/decorations:$ORIGIN/particles', build_rpath: '$ORIGIN/modules:$ORIGIN/decorations:$ORIGIN/particles',
export_dynamic: true, export_dynamic: true,

View file

@ -48,3 +48,5 @@ option('plugin-xkb', type: 'feature', value: 'auto',
description: 'keyboard support for X11') description: 'keyboard support for X11')
option('plugin-xwindow', type: 'feature', value: 'auto', option('plugin-xwindow', type: 'feature', value: 'auto',
description: 'XWindow (window tracking for X11) support') description: 'XWindow (window tracking for X11) support')
option('system-nanosvg', type: 'feature', value: 'disabled',
description: 'use system\'s nanosvg instead of the bundled version')

6
nanosvg.c Normal file
View file

@ -0,0 +1,6 @@
#include <math.h>
#include <stdio.h>
#include <string.h>
#define NANOSVG_ALL_COLOR_KEYWORDS
#define NANOSVG_IMPLEMENTATION
#include <3rd-party/nanosvg/src/nanosvg.h>

1
nanosvg/nanosvg.h Normal file
View file

@ -0,0 +1 @@
#include <3rd-party/nanosvg/src/nanosvg.h>

1
nanosvg/nanosvgrast.h Normal file
View file

@ -0,0 +1 @@
#include <3rd-party/nanosvg/src/nanosvgrast.h>

6
nanosvgrast.c Normal file
View file

@ -0,0 +1,6 @@
#include <stdlib.h>
#include <string.h>
#include <3rd-party/nanosvg/src/nanosvg.h>
#define NANOSVGRAST_IMPLEMENTATION
#include <3rd-party/nanosvg/src/nanosvgrast.h>

View file

@ -15,6 +15,7 @@
#define LOG_MODULE "particle" #define LOG_MODULE "particle"
#define LOG_ENABLE_DBG 0 #define LOG_ENABLE_DBG 0
#include "bar/bar.h" #include "bar/bar.h"
#include "icon.h"
#include "log.h" #include "log.h"
void void
@ -25,12 +26,17 @@ particle_default_destroy(struct particle *particle)
fcft_destroy(particle->font); fcft_destroy(particle->font);
for (size_t i = 0; i < MOUSE_BTN_COUNT; i++) for (size_t i = 0; i < MOUSE_BTN_COUNT; i++)
free(particle->on_click_templates[i]); free(particle->on_click_templates[i]);
themes_dec(particle->themes);
basedirs_dec(particle->basedirs);
string_list_dec(particle->icon_themes);
free(particle); free(particle);
} }
struct particle * struct particle *
particle_common_new(int left_margin, int right_margin, char **on_click_templates, struct fcft_font *font, 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)); struct particle *p = calloc(1, sizeof(*p));
p->left_margin = left_margin; 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 = font;
p->font_shaping = font_shaping; p->font_shaping = font_shaping;
p->deco = deco; p->deco = deco;
p->basedirs = basedirs;
p->themes = themes;
p->icon_themes = icon_themes;
p->icon_size = icon_size;
if (on_click_templates != NULL) { if (on_click_templates != NULL) {
for (size_t i = 0; i < MOUSE_BTN_COUNT; i++) { for (size_t i = 0; i < MOUSE_BTN_COUNT; i++) {

View file

@ -6,6 +6,7 @@
#include "color.h" #include "color.h"
#include "decoration.h" #include "decoration.h"
#include "font-shaping.h" #include "font-shaping.h"
#include "icon.h"
#include "tag.h" #include "tag.h"
enum mouse_event { enum mouse_event {
@ -41,6 +42,11 @@ struct particle {
enum font_shaping font_shaping; enum font_shaping font_shaping;
struct deco *deco; struct deco *deco;
struct themes *themes;
struct basedirs *basedirs;
struct string_list *icon_themes;
int icon_size;
void (*destroy)(struct particle *particle); void (*destroy)(struct particle *particle);
struct exposable *(*instantiate)(const struct particle *particle, const struct tag_set *tags); 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 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 fcft_font *font, enum font_shaping font_shaping, struct basedirs *basedirs,
struct deco *deco); 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); 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}, \ {"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}, \ {"font", false, &conf_verify_font}, {"font-shaping", false, &conf_verify_font_shaping}, \
{"foreground", false, &conf_verify_color}, {"deco", false, &conf_verify_decoration}, \ {"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 \ NULL, false, NULL \
} }

206
particles/icon.c Normal file
View file

@ -0,0 +1,206 @@
#include <stdlib.h>
#include <string.h>
#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

View file

@ -190,8 +190,9 @@ from_conf(const struct yml_node *node, struct particle *common)
size_t idx = 0; size_t idx = 0;
for (struct yml_list_iter it = yml_list_iter(items); it.node != NULL; yml_list_next(&it), idx++) { for (struct yml_list_iter it = yml_list_iter(items); it.node != NULL; yml_list_next(&it), idx++) {
parts[idx] parts[idx] = conf_to_particle(it.node, (struct conf_inherit){common->font, common->font_shaping, common->themes,
= conf_to_particle(it.node, (struct conf_inherit){common->font, common->font_shaping, common->foreground}); common->basedirs, common->icon_themes,
common->icon_size, common->foreground});
} }
return particle_list_new(common, parts, count, left_spacing, right_spacing); return particle_list_new(common, parts, count, left_spacing, right_spacing);

View file

@ -1,3 +1,5 @@
#include "tag.h"
#include "yml.h"
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <stdlib.h> #include <stdlib.h>
@ -85,6 +87,11 @@ str_condition(const char *tag_value, const char *cond_value, enum map_op op)
static bool static bool
eval_comparison(const struct map_condition *map_cond, const struct tag_set *tags) 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); const struct tag *tag = tag_for_name(tags, map_cond->tag);
if (tag == NULL) { if (tag == NULL) {
LOG_WARN("tag %s not found", map_cond->tag); LOG_WARN("tag %s not found", map_cond->tag);
@ -170,6 +177,7 @@ free_map_condition(struct map_condition *c)
free(c->value); free(c->value);
/* FALLTHROUGH */ /* FALLTHROUGH */
case MAP_OP_SELF: case MAP_OP_SELF:
case MAP_OP_ICON_TAG:
free(c->tag); free(c->tag);
break; break;
case MAP_OP_AND: 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 particle_map particle_map[yml_dict_length(conditions)];
struct conf_inherit inherited struct conf_inherit inherited = {
= {.font = common->font, .font_shaping = common->font_shaping, .foreground = common->foreground}; .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; size_t idx = 0;
for (struct yml_dict_iter it = yml_dict_iter(conditions); it.key != NULL; yml_dict_next(&it), idx++) { for (struct yml_dict_iter it = yml_dict_iter(conditions); it.key != NULL; yml_dict_next(&it), idx++) {

View file

@ -8,6 +8,7 @@ enum map_op {
MAP_OP_GE, MAP_OP_GE,
MAP_OP_GT, MAP_OP_GT,
MAP_OP_SELF, MAP_OP_SELF,
MAP_OP_ICON_TAG,
MAP_OP_NOT, MAP_OP_NOT,
MAP_OP_AND, MAP_OP_AND,

View file

@ -72,6 +72,7 @@ void yyerror(const char *s);
&& yylval.op = MAP_OP_AND; return BOOL_OP; && yylval.op = MAP_OP_AND; return BOOL_OP;
\|\| yylval.op = MAP_OP_OR; return BOOL_OP; \|\| yylval.op = MAP_OP_OR; return BOOL_OP;
~ return NOT; ~ return NOT;
\+ return AT;
\( return L_PAR; \( return L_PAR;
\) return R_PAR; \) return R_PAR;
[ \t\n] ; [ \t\n] ;

View file

@ -21,7 +21,7 @@ void yyerror(const char *str);
enum map_op op; 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 %left BOOL_OP
%precedence NOT %precedence NOT
@ -33,6 +33,12 @@ void yyerror(const char *str);
result: condition { MAP_CONDITION_PARSE_RESULT = $<condition>1; }; result: condition { MAP_CONDITION_PARSE_RESULT = $<condition>1; };
condition: condition:
AT WORD {
$<condition>$ = malloc(sizeof(struct map_condition));
$<condition>$->tag = $<str>2;
$<condition>$->op = MAP_OP_ICON_TAG;
}
|
WORD { WORD {
$<condition>$ = malloc(sizeof(struct map_condition)); $<condition>$ = malloc(sizeof(struct map_condition));
$<condition>$->tag = $<str>1; $<condition>$->tag = $<str>1;
@ -84,6 +90,7 @@ token_to_str(yysymbol_kind_t tkn)
case YYSYMBOL_L_PAR: return "("; case YYSYMBOL_L_PAR: return "(";
case YYSYMBOL_R_PAR: return ")"; case YYSYMBOL_R_PAR: return ")";
case YYSYMBOL_NOT: return "~"; case YYSYMBOL_NOT: return "~";
case YYSYMBOL_AT: return "+";
default: return yysymbol_name(tkn); default: return yysymbol_name(tkn);
} }
} }

View file

@ -20,7 +20,7 @@ pfiles = pgen.process('map.y')
map_parser = declare_dependency(sources: [pfiles, lfiles], include_directories: '.') 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_lib = build_target(
'dynlist', 'dynlist.c', 'dynlist.h', dependencies: particle_sdk, 'dynlist', 'dynlist.c', 'dynlist.h', dependencies: particle_sdk,
@ -40,6 +40,7 @@ deps = {
'progress-bar': [], 'progress-bar': [],
'ramp': [], 'ramp': [],
'string': [], 'string': [],
'icon': [],
} }
particles = [] particles = []

View file

@ -296,6 +296,10 @@ from_conf(const struct yml_node *node, struct particle *common)
.font = common->font, .font = common->font,
.font_shaping = common->font_shaping, .font_shaping = common->font_shaping,
.foreground = common->foreground, .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), return progress_bar_new(common, yml_value_as_string(tag), yml_value_as_int(length),

View file

@ -199,8 +199,9 @@ from_conf(const struct yml_node *node, struct particle *common)
size_t idx = 0; size_t idx = 0;
for (struct yml_list_iter it = yml_list_iter(items); it.node != NULL; yml_list_next(&it), idx++) { for (struct yml_list_iter it = yml_list_iter(items); it.node != NULL; yml_list_next(&it), idx++) {
parts[idx] parts[idx] = conf_to_particle(it.node, (struct conf_inherit){common->font, common->font_shaping, common->themes,
= conf_to_particle(it.node, (struct conf_inherit){common->font, common->font_shaping, common->foreground}); common->basedirs, common->icon_themes,
common->icon_size, common->foreground});
} }
long min_v = min != NULL ? yml_value_as_int(min) : 0; long min_v = min != NULL ? yml_value_as_int(min) : 0;

View file

@ -97,6 +97,7 @@ EXTERN_PARTICLE(map);
EXTERN_PARTICLE(progress_bar); EXTERN_PARTICLE(progress_bar);
EXTERN_PARTICLE(ramp); EXTERN_PARTICLE(ramp);
EXTERN_PARTICLE(string); EXTERN_PARTICLE(string);
EXTERN_PARTICLE(icon);
EXTERN_DECORATION(background); EXTERN_DECORATION(background);
EXTERN_DECORATION(border); EXTERN_DECORATION(border);
@ -227,6 +228,7 @@ static void __attribute__((constructor)) init(void)
REGISTER_CORE_PARTICLE(progress-bar, progress_bar); REGISTER_CORE_PARTICLE(progress-bar, progress_bar);
REGISTER_CORE_PARTICLE(ramp, ramp); REGISTER_CORE_PARTICLE(ramp, ramp);
REGISTER_CORE_PARTICLE(string, string); REGISTER_CORE_PARTICLE(string, string);
REGISTER_CORE_PARTICLE(icon, icon);
REGISTER_CORE_DECORATION(background, background); REGISTER_CORE_DECORATION(background, background);
REGISTER_CORE_DECORATION(border, border); REGISTER_CORE_DECORATION(border, border);

5
png-yambar.h Normal file
View file

@ -0,0 +1,5 @@
#pragma once
#include <pixman.h>
pixman_image_t *png_load(const char *path);

171
png.c Normal file
View file

@ -0,0 +1,171 @@
#include "png-yambar.h"
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pixman.h>
#include <png.h>
#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;
}

42
stringop.c Normal file
View file

@ -0,0 +1,42 @@
#include <stdio.h>
#include <stdlib.h>
#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;
}

12
stringop.h Normal file
View file

@ -0,0 +1,12 @@
#pragma once
#include <stdio.h>
#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);

61
svg.c Normal file
View file

@ -0,0 +1,61 @@
#include "svg.h"
#include <stdlib.h>
#define LOG_MODULE "svg"
#define LOG_ENABLE_DBG 0
#include "log.h"
#include <nanosvg.h>
#include <nanosvgrast.h>
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;
}

5
svg.h Normal file
View file

@ -0,0 +1,5 @@
#pragma once
#include <pixman.h>
pixman_image_t *svg_load(const char *path, int size);

88
tag.c
View file

@ -9,7 +9,8 @@
#include <string.h> #include <string.h>
#define LOG_MODULE "tag" #define LOG_MODULE "tag"
#define LOG_ENABLE_DBG 1 #define LOG_ENABLE_DBG 0
#include "icon.h"
#include "log.h" #include "log.h"
#include "module.h" #include "module.h"
@ -29,6 +30,11 @@ struct private
}; };
}; };
struct icon_private {
char *name;
struct icon_pixmaps *pixmaps;
};
static const char * static const char *
tag_name(const struct tag *tag) tag_name(const struct tag *tag)
{ {
@ -375,6 +381,50 @@ tag_new_string(struct module *owner, const char *name, const char *value)
return tag; 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 * const struct tag *
tag_for_name(const struct tag_set *set, const char *name) 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; return NULL;
} }
void const struct icon_tag *
tag_set_destroy(struct tag_set *set) icon_tag_for_name(const struct tag_set *set, const char *name)
{ {
for (size_t i = 0; i < set->count; i++) if (set == NULL)
set->tags[i]->destroy(set->tags[i]); return NULL;
set->tags = NULL; for (size_t i = 0; i < set->icon_count; i++) {
set->count = 0; 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 { 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++) for (size_t i = 0; i < nmemb; i++)
expanded[i] = tags_expand_template(template[i], tags); 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;
}

17
tag.h
View file

@ -1,8 +1,11 @@
#pragma once #pragma once
#include <pixman.h>
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include "icon.h"
enum tag_type { enum tag_type {
TAG_TYPE_BOOL, TAG_TYPE_BOOL,
TAG_TYPE_INT, TAG_TYPE_INT,
@ -37,9 +40,20 @@ struct tag {
bool (*refresh_in)(const struct tag *tag, long units); 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_set {
struct tag **tags; struct tag **tags;
struct icon_tag **icon_tags;
size_t count; size_t count;
size_t icon_count;
}; };
struct tag *tag_new_int(struct module *owner, const char *name, long value); 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_float(struct module *owner, const char *name, double value);
struct tag *tag_new_string(struct module *owner, const char *name, const char *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 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); void tag_set_destroy(struct tag_set *set);
/* Utility functions */ /* Utility functions */

0
xdg.c Normal file
View file