Add support of .svgz image files to fluid

The  prototype of the public Fl_SVG_Image constructor is expanded
to allow construction from in-memory, gzip'ed binary data.
This commit is contained in:
ManoloFLTK 2022-11-15 10:09:01 +01:00
parent 433a8e71e8
commit 7f8f7c5b85
8 changed files with 200 additions and 45 deletions

View File

@ -1,7 +1,7 @@
//
// SVG Image header file for the Fast Light Tool Kit (FLTK).
//
// Copyright 2017-2020 by Bill Spitzak and others.
// Copyright 2017-2022 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file. If this
@ -147,13 +147,14 @@ private:
float svg_scaling_(int W, int H);
void rasterize_(int W, int H);
virtual void cache_size_(int &width, int &height);
void init_(const char *filename, const char *filedata, const Fl_SVG_Image *copy_source);
void init_(const char *filename, const unsigned char *filedata, const Fl_SVG_Image *copy_source,
size_t length);
Fl_SVG_Image(const Fl_SVG_Image *source);
public:
/** Set this to \c false to allow image re-scaling that alters the image aspect ratio.
Upon object creation, proportional is set to \c true, and the aspect ratio is kept constant.*/
bool proportional;
Fl_SVG_Image(const char *filename, const char *svg_data = NULL);
Fl_SVG_Image(const char *filename, const char *svg_data = NULL, size_t length = 0);
virtual ~Fl_SVG_Image();
virtual Fl_Image *copy(int W, int H) const;
Fl_Image *copy() const {

View File

@ -1,7 +1,7 @@
//
// Pixmap (and other images) label support for the Fast Light Tool Kit (FLTK).
//
// Copyright 1998-2021 by Bill Spitzak and others.
// Copyright 1998-2022 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file. If this
@ -124,35 +124,45 @@ void Fluid_Image::write_static() {
write_c(";\n");
write_initializer("Fl_JPEG_Image", "\"%s\", %s", fl_filename_name(name()), idata_name);
} else if (strcmp(fl_filename_ext(name()), ".svg")==0) {
} else if (strcmp(fl_filename_ext(name()), ".svg")==0 || strcmp(fl_filename_ext(name()), ".svgz")==0) {
bool gzipped = (strcmp(fl_filename_ext(name()), ".svgz") == 0);
// Write svg image data...
write_c("\n");
if (svg_header_written != write_number) {
write_c("#include <FL/Fl_SVG_Image.H>\n");
svg_header_written = write_number;
}
write_c("static const char %s[] =\n", idata_name);
write_c(
(gzipped ? "static const unsigned char %s[] =\n" : "static const char %s[] =\n"),
idata_name);
goto_designfile_dir();
FILE *f = fl_fopen(name(), "rb");
leave_designfile_dir();
size_t nData = 0;
if (!f) {
write_file_error("SVG");
} else {
fseek(f, 0, SEEK_END);
size_t nData = ftell(f);
nData = ftell(f);
fseek(f, 0, SEEK_SET);
if (nData) {
char *data = (char*)calloc(nData+1, 1);
if (fread(data, nData, 1, f)==0) { /* ignore */ }
write_cstring(data, (int)nData);
if (gzipped)
write_cdata(data, (int)nData);
else
write_cstring(data, (int)nData);
free(data);
}
fclose(f);
}
write_c(";\n");
write_initializer("Fl_SVG_Image", "NULL, %s", idata_name);
if (gzipped)
write_initializer("Fl_SVG_Image", "NULL, (const char*)%s, %ld", idata_name, nData);
else
write_initializer("Fl_SVG_Image", "NULL, %s", idata_name);
} else {
// Write image data...
write_c("\n");
@ -291,7 +301,13 @@ const char *ui_find_image_name;
Fluid_Image *ui_find_image(const char *oldname) {
goto_designfile_dir();
fl_file_chooser_ok_label("Use Image");
const char *name = fl_file_chooser("Image?","Image Files (*.{bm,bmp,gif,jpg,pbm,pgm,png,ppm,xbm,xpm,svg})",oldname,1);
const char *name = fl_file_chooser("Image?",
"Image Files (*.{bm,bmp,gif,jpg,pbm,pgm,png,ppm,xbm,xpm,svg"
#ifdef HAVE_LIBZ
",svgz"
#endif
"})",
oldname,1);
fl_file_chooser_ok_label(NULL);
ui_find_image_name = name;
Fluid_Image *ret = (name && *name) ? Fluid_Image::find(name) : 0;

View File

@ -1,7 +1,7 @@
//
// SVG image code for the Fast Light Tool Kit (FLTK).
//
// Copyright 2017 by Bill Spitzak and others.
// Copyright 2017-2022 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file. If this
@ -23,6 +23,7 @@
#include <FL/fl_draw.H>
#include <FL/fl_string_functions.h>
#include "Fl_Screen_Driver.H"
#include "Fl_System_Driver.H"
#include <stdio.h>
#include <stdlib.h>
@ -56,16 +57,19 @@ static double strtoll(const char *str, char **endptr, int base) {
\param filename Name of a .svg or .svgz file, or NULL.
\param svg_data A pointer to the memory location of the SVG image data.
This parameter allows to load an SVG image from in-memory data, and is used when \p filename is NULL.
\param length When 0, indicates that \p svg_data contains SVG text, otherwise \p svg_data is
a buffer of \p length bytes containing GZ-compressed SVG data.
\note In-memory SVG data is parsed by the object constructor and is not used after construction.
When \p length > 0, parameter \p svg_data may safely be cast from data of type <em>const unsigned char *</em>.
*/
Fl_SVG_Image::Fl_SVG_Image(const char *filename, const char *svg_data) : Fl_RGB_Image(NULL, 0, 0, 4) {
init_(filename, svg_data, NULL);
Fl_SVG_Image::Fl_SVG_Image(const char *filename, const char *svg_data, size_t length) : Fl_RGB_Image(NULL, 0, 0, 4) {
init_(filename, (const unsigned char *)svg_data, NULL, length);
}
// private constructor
Fl_SVG_Image::Fl_SVG_Image(const Fl_SVG_Image *source) : Fl_RGB_Image(NULL, 0, 0, 4) {
init_(NULL, NULL, source);
init_(NULL, NULL, source, 0);
}
@ -86,47 +90,63 @@ float Fl_SVG_Image::svg_scaling_(int W, int H) {
#if defined(HAVE_LIBZ)
static char *svg_inflate(const char *fname) {
FILE *in = fl_fopen(fname, "r");
if (!in) return NULL;
unsigned char header[2];
if (fread(header, 2, 1, in) < 1) {
fclose(in);
return NULL;
}
int direct = (header[0] != 0x1f || header[1] != 0x8b);
fseek(in, 0, SEEK_END);
long size = ftell(in);
fclose(in);
int fd = fl_open_ext(fname, 1, 0);
if (fd < 0) return NULL;
gzFile gzf = gzdopen(fd, "r");
if (!gzf) return NULL;
/* Implementation note about decompression of svgz file or in-memory data.
It seems necessary to use the gzdopen()/gzread() API to inflate a gzip'ed
file or byte buffer. Writing the in-memory gzip'ed data to an anonymous pipe
and calling gzread() on the read end of this pipe is a solution for the in-memory case.
But non-blocking write to the pipe is needed to do that in the main thread,
and that seems impossible with Windows anonymous pipes.
Therefore, the anonymous pipe is handled in 2 ways:
1) Under Windows, a child thread writes to the write end of the pipe and
the main thread reads from the read end with gzread().
2) Under Posix systems, the write end of the pipe is made non-blocking
with a fcntl() call, and the main thread successively writes to the write
end and reads from the read end with gzread(). This allows to not have
libfltk_images requiring a threading library.
*/
static char *svg_inflate(gzFile gzf, // can be a file or the read end of a pipe
size_t size, // size of compressed data or of file
bool is_compressed, // true when file or byte buffer is gzip'ed
int fdwrite, // write end of pipe if >= 0
const unsigned char *bytes // byte buffer to write to pipe
) {
size_t rest_bytes = size;
int l;
long out_size = direct ? size + 1 : 3*size + 1;
size_t out_size = is_compressed ? 3 * size + 1 : size + 1;
char *out = (char*)malloc(out_size);
char *p = out;
do {
if ((!direct) && p + size > out + out_size) {
if (is_compressed && p + size > out + out_size) {
out_size += size;
char *tmp = (char*)realloc(out, out_size + 1);
p = tmp + (p - out);
out = tmp;
}
if ( fdwrite >= 0 && Fl::system_driver()->write_nonblocking_fd(fdwrite, bytes, rest_bytes) ) {
free(out);
out = NULL;
is_compressed = false;
break;
}
l = gzread(gzf, p, size);
if (l > 0) {
p += l; *p = 0;
}
} while ((!direct) && l >0);
} while (is_compressed && l >0);
gzclose(gzf);
if (!direct) out = (char*)realloc(out, (p-out)+1);
if (is_compressed) out = (char*)realloc(out, (p-out)+1);
return out;
}
#endif
void Fl_SVG_Image::init_(const char *filename, const char *in_filedata, const Fl_SVG_Image *copy_source) {
#endif // defined(HAVE_LIBZ)
void Fl_SVG_Image::init_(const char *filename, const unsigned char *in_filedata, const Fl_SVG_Image *copy_source, size_t length) {
if (copy_source) {
filename = in_filedata = NULL;
filename = NULL;
in_filedata = NULL;
counted_svg_image_ = copy_source->counted_svg_image_;
counted_svg_image_->ref_count++;
} else {
@ -138,10 +158,35 @@ void Fl_SVG_Image::init_(const char *filename, const char *in_filedata, const Fl
to_desaturate_ = false;
average_weight_ = 1;
proportional = true;
if (filename) {
bool is_compressed = true;
if (filename || length) { // process file or byte buffer
#if defined(HAVE_LIBZ)
filedata = svg_inflate(filename);
#else
int fdread, fdwrite = -1;
if (length) { // process gzip'ed byte buffer
// Pipe gzip'ed byte buffer into gzlib inflate algorithm.
// Under Windows, gzip'ed byte buffer is written to pipe by child thread.
// Under Posix, gzip'ed byte buffer is written to pipe by non-blocking write
// done by main thread.
Fl::system_driver()->pipe_support(fdread, fdwrite, in_filedata, length);
} else { // read or decompress a .svg or .svgz file
struct stat svg_file_stat;
fl_stat(filename, &svg_file_stat); // get file size
fdread = fl_open_ext(filename, 1, 0);
// read a possibly gzip'ed file and return result as char string
length = svg_file_stat.st_size;
is_compressed = (strcmp(filename + strlen(filename) - 5, ".svgz") == 0);
}
gzFile gzf = (fdread >= 0 ? gzdopen(fdread, "rb") : NULL);
if (gzf) {
filedata = svg_inflate(gzf, length, is_compressed, fdwrite, in_filedata);
} else {
if (fdread >= 0) Fl::system_driver()->close_fd(fdread);
if (fdwrite >= 0) Fl::system_driver()->close_fd(fdwrite);
}
#else // ! HAVE_LIBZ
// without libz, read .svg file
FILE *fp = fl_fopen(filename, "rb");
if (fp) {
fseek(fp, 0, SEEK_END);
@ -160,10 +205,12 @@ void Fl_SVG_Image::init_(const char *filename, const char *in_filedata, const Fl
}
#endif // HAVE_LIBZ
if (!filedata) ld(ERR_FILE_ACCESS);
} else {
} else { // handle non-gzip'ed svg data as a char string
// XXX: Make internal copy -- nsvgParse() modifies filedata during parsing (!)
filedata = in_filedata ? fl_strdup(in_filedata) : NULL;
filedata = in_filedata ? fl_strdup((const char *)in_filedata) : NULL;
}
// filedata is NULL or contains SVG data as a char string
if (filedata) {
counted_svg_image_->svg_image = nsvgParse(filedata, "px", 96);
free(filedata); // made with svg_inflate|malloc|strdup

View File

@ -221,6 +221,12 @@ public:
virtual void unlock_ring() {}
virtual double wait(double); // must override
virtual int ready() { return 0; } // must override
virtual int close_fd(int) {return -1;} // to close a file descriptor
// next 2 for support of Fl_SVG_Image
virtual int write_nonblocking_fd(int , const unsigned char *&, size_t &) {return 0;}
virtual void pipe_support(int &fdread, int &fdwrite, const unsigned char *, size_t ) {
fdread = fdwrite = -1;
}
};
#endif // FL_SYSTEM_DRIVER_H

View File

@ -82,6 +82,10 @@ public:
virtual int dot_file_hidden() {return 1;}
virtual void gettime(time_t *sec, int *usec);
virtual char* strdup(const char *s) {return ::strdup(s);}
virtual int close_fd(int fd);
// next 2 for support of Fl_SVG_Image
virtual int write_nonblocking_fd(int , const unsigned char *&, size_t &);
virtual void pipe_support(int &, int &, const unsigned char *, size_t );
#if defined(HAVE_PTHREAD)
virtual void lock_ring();
virtual void unlock_ring();

View File

@ -1,7 +1,7 @@
//
// Definition of Posix system driver (used by the X11, Wayland and macOS platforms).
//
// Copyright 1998-2021 by Bill Spitzak and others.
// Copyright 1998-2022 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file. If this
@ -308,6 +308,33 @@ bool Fl_Posix_System_Driver::probe_for_GTK(int major, int minor, void **p_ptr_gt
#endif // HAVE_DLSYM && HAVE_DLFCN_H
int Fl_Posix_System_Driver::close_fd(int fd) { return close(fd); }
int Fl_Posix_System_Driver::write_nonblocking_fd(int fdwrite, const unsigned char *&bytes, size_t &rest_bytes) {
if (rest_bytes > 0) {
ssize_t nw = write(fdwrite, bytes, rest_bytes);
if (nw == -1) {
close(fdwrite);
return 1; // error
}
bytes += nw;
rest_bytes -= nw;
if (rest_bytes == 0) close(fdwrite);
}
return 0; // success
}
void Fl_Posix_System_Driver::pipe_support(int &fdread, int &fdwrite, const unsigned char *unused, size_t unused_s) {
int fds[2];
if (pipe(fds)) { // create anonymous pipe
Fl_System_Driver::pipe_support(fdread, fdwrite, NULL, 0); // indicates error
} else {
fdread = fds[0];
fdwrite = fds[1];
fcntl(fdwrite, F_SETFL, O_NONBLOCK); // make pipe's write end non-blocking
}
}
////////////////////////////////////////////////////////////////
// POSIX threading...
#if defined(HAVE_PTHREAD)

View File

@ -1,7 +1,7 @@
//
// Windows system driver for the Fast Light Tool Kit (FLTK).
//
// Copyright 2010-2021 by Bill Spitzak and others.
// Copyright 2010-2022 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file. If this
@ -114,6 +114,8 @@ public:
virtual void unlock_ring();
virtual double wait(double time_to_wait);
virtual int ready();
virtual void pipe_support(int &, int &, const unsigned char *, size_t );
virtual int close_fd(int fd);
};
#endif // FL_WINAPI_SYSTEM_DRIVER_H

View File

@ -1,7 +1,7 @@
//
// Definition of Windows system driver for the Fast Light Tool Kit (FLTK).
//
// Copyright 1998-2021 by Bill Spitzak and others.
// Copyright 1998-2022 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file. If this
@ -1026,3 +1026,55 @@ void Fl_WinAPI_System_Driver::unlock() {
void Fl_WinAPI_System_Driver::awake(void* msg) {
PostThreadMessage( main_thread, fl_wake_msg, (WPARAM)msg, 0);
}
// create anonymous pipe in the form of 2 unix-style file descriptors
static void pipe_win32(int fds[2]) {
HANDLE read_h, write_h;
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = true;
if (CreatePipe(&read_h, &write_h, &sa, 0) == 0) {
fds[0] = fds[1] = -1; // indicates error
} else { // create unix-style file descriptors from handles
fds[0] = _open_osfhandle((fl_intptr_t)read_h, _O_RDONLY | _O_BINARY);
fds[1] = _open_osfhandle((fl_intptr_t)write_h, _O_WRONLY | _O_BINARY);
}
}
struct gunz_data { // data transmitted to thread
const unsigned char *data;
unsigned length;
int fd;
};
static void __cdecl write_func(struct gunz_data *pdata) { // will run in child thread
//const unsigned char *from = pdata->data;
while (pdata->length > 0) {
int done = _write(pdata->fd, pdata->data, pdata->length);
if (done == -1) break;
pdata->length -= done;
pdata->data += done;
}
_close(pdata->fd);
free(pdata);
}
int Fl_WinAPI_System_Driver::close_fd(int fd) {
return _close(fd);
}
void Fl_WinAPI_System_Driver::pipe_support(int &fdread, int &fdwrite, const unsigned char *bytes, size_t length) {
int fds[2];
pipe_win32(fds); // create anonymous pipe
if (fds[0] == -1) return Fl_System_Driver::pipe_support(fdread, fdwrite, NULL, 0);
fdread = fds[0];
fdwrite = -1;
// prepare data transmitted to child thread
struct gunz_data *thread_data = (struct gunz_data*)malloc(sizeof(struct gunz_data));
thread_data->data = bytes;
thread_data->length = (unsigned)length;
thread_data->fd = fds[1];
// launch child thread that will write byte buffer to pipe's write end
_beginthread((void( __cdecl * )( void * ))write_func, 0, thread_data);
}