diff options
author | fvogel <fvogelnew1@free.fr> | 2018-05-15 19:32:12 (GMT) |
---|---|---|
committer | fvogel <fvogelnew1@free.fr> | 2018-05-15 19:32:12 (GMT) |
commit | a7048fe8fec1e93713cefba5214cfa6a200761f8 (patch) | |
tree | 3f7ec0aac90524c3d1a87bdb6f76be3f3970a0cf /generic/tkImgSVGnano.c | |
parent | 30a9d67a0b9143300d7c3d929b76afd2fafed397 (diff) | |
download | tk-a7048fe8fec1e93713cefba5214cfa6a200761f8.zip tk-a7048fe8fec1e93713cefba5214cfa6a200761f8.tar.gz tk-a7048fe8fec1e93713cefba5214cfa6a200761f8.tar.bz2 |
Add missing files, as provided by René Zaumseil.
Diffstat (limited to 'generic/tkImgSVGnano.c')
-rw-r--r-- | generic/tkImgSVGnano.c | 703 |
1 files changed, 703 insertions, 0 deletions
diff --git a/generic/tkImgSVGnano.c b/generic/tkImgSVGnano.c new file mode 100644 index 0000000..26236da --- /dev/null +++ b/generic/tkImgSVGnano.c @@ -0,0 +1,703 @@ +/* + * tkImgSVGnano.c + * + * A photo file handler for SVG files. + * + * Copyright (c) 2013-14 Mikko Mononen memon@inside.org + * Copyright (c) 2018 Christian Gollwitzer auriocus@gmx.de + * Copyright (c) 2018 Rene Zaumseil r.zaumseil@freenet.de + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * This handler is build using the original nanosvg library files from + * https://github.com/memononen/nanosvg and the tcl extension files from + * https://github.com/auriocus/tksvg + * + */ + +#include "tkInt.h" +#define NANOSVG_malloc ckalloc +#define NANOSVG_realloc ckrealloc +#define NANOSVG_free ckfree +#define NANOSVG_SCOPE MODULE_SCOPE +#define NANOSVG_ALL_COLOR_KEYWORDS +#define NANOSVG_IMPLEMENTATION +#include "nanosvg.h" +#define NANOSVGRAST_IMPLEMENTATION +#include "nanosvgrast.h" + +/* Additional parameters to nsvgRasterize() */ + +typedef struct { + double x; + double y; + double scale; +} RastOpts; + +/* + * The format record for the SVG nano file format: + */ + +Tk_PhotoImageFormat tkImgFmtSVGnano = { + "svgnano", /* name */ + FileMatchSVG, /* fileMatchProc */ + StringMatchSVG, /* stringMatchProc */ + FileReadSVG, /* fileReadProc */ + StringReadSVG, /* stringReadProc */ + NULL, /* fileWriteProc */ + NULL, /* stringWriteProc */ + NULL +}; + +/* + * Per interp cache of last NSVGimage which was matched to + * be immediately rasterized after the match. This helps to + * eliminate double parsing of the SVG file/string. + */ + +typedef struct { + ClientData dataOrChan; + Tcl_DString formatString; + NSVGimage *nsvgImage; + RastOpts ropts; +} NSVGcache; + +static int FileMatchSVG(Tcl_Channel chan, const char *fileName, + Tcl_Obj *format, int *widthPtr, int *heightPtr, + Tcl_Interp *interp); +static int FileReadSVG(Tcl_Interp *interp, Tcl_Channel chan, + const char *fileName, Tcl_Obj *format, + Tk_PhotoHandle imageHandle, int destX, int destY, + int width, int height, int srcX, int srcY); +static int StringMatchSVG(Tcl_Obj *dataObj, Tcl_Obj *format, + int *widthPtr, int *heightPtr, Tcl_Interp *interp); +static int StringReadSVG(Tcl_Interp *interp, Tcl_Obj *dataObj, + Tcl_Obj *format, Tk_PhotoHandle imageHandle, + int destX, int destY, int width, int height, + int srcX, int srcY); +static NSVGimage * ParseSVGWithOptions(Tcl_Interp *interp, + const char *input, int length, Tcl_Obj *format, + RastOpts *ropts); +static int RasterizeSVG(Tcl_Interp *interp, + Tk_PhotoHandle imageHandle, NSVGimage *nsvgImage, + int destX, int destY, int width, int height, + int srcX, int srcY, RastOpts *ropts); +static NSVGcache * GetCachePtr(Tcl_Interp *interp); +static int CacheSVG(Tcl_Interp *interp, ClientData dataOrChan, + Tcl_Obj *formatObj, NSVGimage *nsvgImage, + RastOpts *ropts); +static NSVGimage * GetCachedSVG(Tcl_Interp *interp, ClientData dataOrChan, + Tcl_Obj *formatObj, RastOpts *ropts); +static void CleanCache(Tcl_Interp *interp); +static void FreeCache(ClientData clientData, Tcl_Interp *interp); + +/* + *---------------------------------------------------------------------- + * + * FileMatchSVG -- + * + * This function is invoked by the photo image type to see if a file + * contains image data in SVG format. + * + * Results: + * The return value is >0 if the file can be successfully parsed, + * and 0 otherwise. + * + * Side effects: + * The file is saved in the internal cache for further use. + * + *---------------------------------------------------------------------- + */ + +static int +FileMatchSVG( + Tcl_Channel chan, + const char *fileName, + Tcl_Obj *formatObj, + int *widthPtr, int *heightPtr, + Tcl_Interp *interp) +{ + int length; + Tcl_Obj *dataObj = Tcl_NewObj(); + const char *data; + RastOpts ropts; + NSVGimage *nsvgImage; + + CleanCache(interp); + if (Tcl_ReadChars(chan, dataObj, -1, 0) == -1) { + /* in case of an error reading the file */ + Tcl_DecrRefCount(dataObj); + return 0; + } + data = Tcl_GetStringFromObj(dataObj, &length); + nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, &ropts); + Tcl_DecrRefCount(dataObj); + if (nsvgImage != NULL) { + *widthPtr = ceil(nsvgImage->width * ropts.scale); + *heightPtr = ceil(nsvgImage->height * ropts.scale); + if ((*widthPtr <= 0) || (*heightPtr <= 0)) { + nsvgDelete(nsvgImage); + return 0; + } + if (!CacheSVG(interp, chan, formatObj, nsvgImage, &ropts)) { + nsvgDelete(nsvgImage); + } + return 1; + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * FileReadSVG -- + * + * This function is called by the photo image type to read SVG format + * data from a file and write it into a given photo image. + * + * Results: + * A standard TCL completion code. If TCL_ERROR is returned then an error + * message is left in the interp's result. + * + * Side effects: + * The access position in file f is changed, and new data is added to the + * image given by imageHandle. + * + *---------------------------------------------------------------------- + */ + +static int +FileReadSVG( + Tcl_Interp *interp, + Tcl_Channel chan, + const char *fileName, + Tcl_Obj *formatObj, + Tk_PhotoHandle imageHandle, + int destX, int destY, + int width, int height, + int srcX, int srcY) +{ + int length; + const char *data; + RastOpts ropts; + NSVGimage *nsvgImage = GetCachedSVG(interp, chan, formatObj, &ropts); + + if (nsvgImage == NULL) { + Tcl_Obj *dataObj = Tcl_NewObj(); + + if (Tcl_ReadChars(chan, dataObj, -1, 0) == -1) { + /* in case of an error reading the file */ + Tcl_DecrRefCount(dataObj); + Tcl_SetResult(interp, "read error", TCL_STATIC); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "READ_ERROR", NULL); + return TCL_ERROR; + } + data = Tcl_GetStringFromObj(dataObj, &length); + nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, + &ropts); + Tcl_DecrRefCount(dataObj); + if (nsvgImage == NULL) { + return TCL_ERROR; + } + } + return RasterizeSVG(interp, imageHandle, nsvgImage, destX, destY, + width, height, srcX, srcY, &ropts); +} + +/* + *---------------------------------------------------------------------- + * + * StringMatchSVG -- + * + * This function is invoked by the photo image type to see if a string + * contains image data in SVG format. + * + * Results: + * The return value is >0 if the file can be successfully parsed, + * and 0 otherwise. + * + * Side effects: + * The file is saved in the internal cache for further use. + * + *---------------------------------------------------------------------- + */ + +static int +StringMatchSVG( + Tcl_Obj *dataObj, + Tcl_Obj *formatObj, + int *widthPtr, int *heightPtr, + Tcl_Interp *interp) +{ + int length; + const char *data; + RastOpts ropts; + NSVGimage *nsvgImage; + + CleanCache(interp); + data = Tcl_GetStringFromObj(dataObj, &length); + nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, &ropts); + if (nsvgImage != NULL) { + *widthPtr = ceil(nsvgImage->width * ropts.scale); + *heightPtr = ceil(nsvgImage->height * ropts.scale); + if (!CacheSVG(interp, dataObj, formatObj, nsvgImage, &ropts)) { + nsvgDelete(nsvgImage); + } + return 1; + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * StringReadSVG -- + * + * This function is called by the photo image type to read SVG format + * data from a string and write it into a given photo image. + * + * Results: + * A standard TCL completion code. If TCL_ERROR is returned then an error + * message is left in the interp's result. + * + * Side effects: + * New data is added to the image given by imageHandle. + * + *---------------------------------------------------------------------- + */ + +static int +StringReadSVG( + Tcl_Interp *interp, + Tcl_Obj *dataObj, + Tcl_Obj *formatObj, + Tk_PhotoHandle imageHandle, + int destX, int destY, + int width, int height, + int srcX, int srcY) +{ + int length; + const char *data; + RastOpts ropts; + NSVGimage *nsvgImage = GetCachedSVG(interp, dataObj, formatObj, &ropts); + + if (nsvgImage == NULL) { + data = Tcl_GetStringFromObj(dataObj, &length); + nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, + &ropts); + } + if (nsvgImage == NULL) { + return TCL_ERROR; + } + return RasterizeSVG(interp, imageHandle, nsvgImage, destX, destY, + width, height, srcX, srcY, &ropts); +} + +/* + *---------------------------------------------------------------------- + * + * ParseSVGWithOptions -- + * + * This function is called to parse the given input string as SVG. + * + * Results: + * Return a newly create NSVGimage on success, and NULL otherwise. + * + * Side effects: + * + *---------------------------------------------------------------------- + */ + +static NSVGimage * +ParseSVGWithOptions( + Tcl_Interp *interp, + const char *input, + int length, + Tcl_Obj *formatObj, + RastOpts *ropts) +{ + Tcl_Obj **objv = NULL; + int objc = 0; + double dpi = 96.0; + char unit[3], *p; + char *inputCopy = NULL; + NSVGimage *nsvgImage; + static const char *const fmtOptions[] = { + "-dpi", "-scale", "-unit", "-x", "-y", NULL + }; + enum fmtOptions { + OPT_DPI, OPT_SCALE, OPT_UNIT, OPT_X, OPT_Y + }; + + /* + * The parser destroys the original input string, + * therefore first duplicate. + */ + + inputCopy = attemptckalloc(length+1); + if (inputCopy == NULL) { + Tcl_SetResult(interp, "cannot alloc data buffer", TCL_STATIC); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "OUT_OF_MEMORY", NULL); + goto error; + } + memcpy(inputCopy, input, length); + inputCopy[length] = '\0'; + + /* + * Process elements of format specification as a list. + */ + + strcpy(unit, "px"); + ropts->x = ropts->y = 0.0; + ropts->scale = 1.0; + if ((formatObj != NULL) && + Tcl_ListObjGetElements(interp, formatObj, &objc, &objv) != TCL_OK) { + goto error; + } + for (; objc > 0 ; objc--, objv++) { + int optIndex; + + /* + * Ignore the "svg" part of the format specification. + */ + + if (!strcasecmp(Tcl_GetString(objv[0]), "svg")) { + continue; + } + + if (Tcl_GetIndexFromObjStruct(interp, objv[0], fmtOptions, + sizeof(char *), "option", 0, &optIndex) == TCL_ERROR) { + goto error; + } + + if (objc < 2) { + ckfree(inputCopy); + Tcl_WrongNumArgs(interp, 1, objv, "value"); + goto error; + } + + objc--; + objv++; + + switch ((enum fmtOptions) optIndex) { + case OPT_DPI: + if (Tcl_GetDoubleFromObj(interp, objv[0], &dpi) == TCL_ERROR) { + goto error; + } + if (dpi < 0.0) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "-dpi value must be positive", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "BAD_DPI", + NULL); + goto error; + } + break; + case OPT_SCALE: + if (Tcl_GetDoubleFromObj(interp, objv[0], &ropts->scale) == + TCL_ERROR) { + goto error; + } + if (ropts->scale <= 0.0) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "-scale value must be positive", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "BAD_SCALE", + NULL); + goto error; + } + break; + case OPT_UNIT: + p = Tcl_GetString(objv[0]); + if ((p != NULL) && (p[0])) { + strncpy(unit, p, 3); + unit[2] = '\0'; + } + break; + case OPT_X: + if (Tcl_GetDoubleFromObj(interp, objv[0], &ropts->x) == TCL_ERROR) { + goto error; + } + break; + case OPT_Y: + if (Tcl_GetDoubleFromObj(interp, objv[0], &ropts->y) == TCL_ERROR) { + goto error; + } + break; + } + } + + nsvgImage = nsvgParse(inputCopy, unit, dpi); + if (nsvgImage == NULL) { + Tcl_SetResult(interp, "cannot parse SVG image", TCL_STATIC); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "PARSE_ERROR", NULL); + goto error; + } + ckfree(inputCopy); + return nsvgImage; + +error: + if (inputCopy != NULL) { + ckfree(inputCopy); + } + return NULL; +} + +/* + *---------------------------------------------------------------------- + * + * RasterizeSVG -- + * + * This function is called to rasterize the given nsvgImage and + * fill the imageHandle with data. + * + * Results: + * A standard TCL completion code. If TCL_ERROR is returned then an error + * message is left in the interp's result. + * + * + * Side effects: + * On error the given nsvgImage will be deleted. + * + *---------------------------------------------------------------------- + */ + +static int +RasterizeSVG( + Tcl_Interp *interp, + Tk_PhotoHandle imageHandle, + NSVGimage *nsvgImage, + int destX, int destY, + int width, int height, + int srcX, int srcY, + RastOpts *ropts) +{ + int w, h, c; + NSVGrasterizer *rast; + unsigned char *imgData; + Tk_PhotoImageBlock svgblock; + + w = ceil(nsvgImage->width * ropts->scale); + h = ceil(nsvgImage->height * ropts->scale); + rast = nsvgCreateRasterizer(); + if (rast == NULL) { + Tcl_SetResult(interp, "cannot initialize rasterizer", TCL_STATIC); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "RASTERIZER_ERROR", + NULL); + goto cleanAST; + } + imgData = attemptckalloc(w * h *4); + if (imgData == NULL) { + Tcl_SetResult(interp, "cannot alloc image buffer", TCL_STATIC); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "OUT_OF_MEMORY", NULL); + goto cleanRAST; + } + nsvgRasterize(rast, nsvgImage, ropts->x, ropts->y, + ropts->scale, imgData, w, h, w * 4); + /* transfer the data to a photo block */ + svgblock.pixelPtr = imgData; + svgblock.width = w; + svgblock.height = h; + svgblock.pitch = w * 4; + svgblock.pixelSize = 4; + for (c = 0; c <= 3; c++) { + svgblock.offset[c] = c; + } + if (Tk_PhotoExpand(interp, imageHandle, + destX + width, destY + height) != TCL_OK) { + goto cleanRAST; + } + if (Tk_PhotoPutBlock(interp, imageHandle, &svgblock, destX, destY, + width, height, TK_PHOTO_COMPOSITE_SET) != TCL_OK) { + goto cleanimg; + } + ckfree(imgData); + nsvgDeleteRasterizer(rast); + nsvgDelete(nsvgImage); + return TCL_OK; + +cleanimg: + ckfree(imgData); + +cleanRAST: + nsvgDeleteRasterizer(rast); + +cleanAST: + nsvgDelete(nsvgImage); + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * GetCachePtr -- + * + * This function is called to get the per interpreter used + * svg image cache. + * + * Results: + * Return a pointer to the used cache. + * + * Side effects: + * Initialize the cache on the first call. + * + *---------------------------------------------------------------------- + */ + +static NSVGcache * +GetCachePtr( + Tcl_Interp *interp +) { + NSVGcache *cachePtr = Tcl_GetAssocData(interp, "tksvgnano", NULL); + if (cachePtr == NULL) { + cachePtr = ckalloc(sizeof(NSVGcache)); + cachePtr->dataOrChan = NULL; + Tcl_DStringInit(&cachePtr->formatString); + cachePtr->nsvgImage = NULL; + Tcl_SetAssocData(interp, "tksvgnano", FreeCache, cachePtr); + } + return cachePtr; +} + +/* + *---------------------------------------------------------------------- + * + * CacheSVG -- + * + * Add the given svg image informations to the cache for further usage. + * + * Results: + * Return 1 on success, and 0 otherwise. + * + * Side effects: + * + *---------------------------------------------------------------------- + */ + +static int +CacheSVG( + Tcl_Interp *interp, + ClientData dataOrChan, + Tcl_Obj *formatObj, + NSVGimage *nsvgImage, + RastOpts *ropts) +{ + int length; + const char *data; + NSVGcache *cachePtr = GetCachePtr(interp); + + if (cachePtr != NULL) { + cachePtr->dataOrChan = dataOrChan; + if (formatObj != NULL) { + data = Tcl_GetStringFromObj(formatObj, &length); + Tcl_DStringAppend(&cachePtr->formatString, data, length); + } + cachePtr->nsvgImage = nsvgImage; + cachePtr->ropts = *ropts; + return 1; + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * GetCachedSVG -- + * + * Try to get the NSVGimage from the internal cache. + * + * Results: + * Return the found NSVGimage on success, and NULL otherwise. + * + * Side effects: + * Calls the CleanCache() function. + * + *---------------------------------------------------------------------- + */ + +static NSVGimage * +GetCachedSVG( + Tcl_Interp *interp, + ClientData dataOrChan, + Tcl_Obj *formatObj, + RastOpts *ropts) +{ + int length; + const char *data; + NSVGcache *cachePtr = GetCachePtr(interp); + NSVGimage *nsvgImage = NULL; + + if ((cachePtr != NULL) && (cachePtr->nsvgImage != NULL) && + (cachePtr->dataOrChan == dataOrChan)) { + if (formatObj != NULL) { + data = Tcl_GetStringFromObj(formatObj, &length); + if (strcmp(data, Tcl_DStringValue(&cachePtr->formatString)) == 0) { + nsvgImage = cachePtr->nsvgImage; + *ropts = cachePtr->ropts; + cachePtr->nsvgImage = NULL; + } + } else if (Tcl_DStringLength(&cachePtr->formatString) == 0) { + nsvgImage = cachePtr->nsvgImage; + *ropts = cachePtr->ropts; + cachePtr->nsvgImage = NULL; + } + } + CleanCache(interp); + return nsvgImage; +} + +/* + *---------------------------------------------------------------------- + * + * CleanCache -- + * + * Reset the cache and delete the saved image in it. + * + * Results: + * + * Side effects: + * + *---------------------------------------------------------------------- + */ + +static void +CleanCache(Tcl_Interp *interp) +{ + NSVGcache *cachePtr = GetCachePtr(interp); + + if (cachePtr != NULL) { + cachePtr->dataOrChan = NULL; + Tcl_DStringSetLength(&cachePtr->formatString, 0); + if (cachePtr->nsvgImage != NULL) { + nsvgDelete(cachePtr->nsvgImage); + cachePtr->nsvgImage = NULL; + } + } +} + +/* + *---------------------------------------------------------------------- + * + * FreeCache -- + * + * This function is called to clean up the internal cache data. + * + * Results: + * + * Side effects: + * Existing image data in the cache and the cache will be deleted. + * + *---------------------------------------------------------------------- + */ + +static void +FreeCache(ClientData clientData, Tcl_Interp *interp) +{ + NSVGcache *cachePtr = clientData; + + Tcl_DStringFree(&cachePtr->formatString); + if (cachePtr->nsvgImage != NULL) { + nsvgDelete(cachePtr->nsvgImage); + } + ckfree(cachePtr); +} + |