Got carried away, wrote a basic SVG viewer.
Should be able to pass it multiple files, and it should deal with resizing "on the fly" and stuff. Seems to work.
Longer than I'd intended (about 250 lines), but I thought I'd post it anyway in case others wanted to improve upon it...
/************************************************************************/
// compile as:
// fltk-config --use-images --compile fl_svg.cxx
// Needs to have the NanoSVG headers available.
//
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <float.h>
#include <FL/Fl.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Group.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_RGB_Image.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Output.H>
#include <FL/fl_draw.H>
#define NANOSVG_ALL_COLOR_KEYWORDS // Include full list of colour keywords
#define NANOSVG_IMPLEMENTATION // Implement NanoSVG implementation
#include "../src/nanosvg.h"
#define NANOSVGRAST_IMPLEMENTATION
#include "../src/nanosvgrast.h"
// FLTK assets we need
static Fl_Double_Window *main_win = NULL;
static Fl_RGB_Image *img_3 = NULL;
static Fl_Output *stat_bar = NULL;
// NanoSVG assets
static NSVGimage *svg_image = NULL;
static NSVGrasterizer *raster_engine = NULL;
// Raster image data buffer
static unsigned char *img_data = NULL;
static const int depth = 4; // Assume always RGBA for now
// Hack to read multiple files from the command line...
static int first_arg = 0;
static int arg_count = 0;
static char **arg_list;
static int next_arg = 0;
static int view_width;
static int view_height;
static void rasterize_image(void);
/************************************************************************/
class vw_box : public Fl_Box
{
public:
vw_box(int X, int Y, int W, int H, const char *L=0) :
Fl_Box(X,Y,W,H,L),
wo(W),
ho(H) {}
// If the box is resized, we may need to re-rasterize the image to fit
void resize(int x, int y, int w, int h)
{
Fl_Box::resize(x,y,w,h);
if ((wo != w) || (ho != h)) // box has changed
{
if (svg_image) rasterize_image(); // trigger a resizing of the raster image
wo = w;
ho = h;
}
}
private:
int wo, ho;
};
static vw_box *box_1 = NULL;
/************************************************************************/
static void reload_svg(const char *filename)
{
// Release any previous image
if (svg_image) nsvgDelete(svg_image);
svg_image = nsvgParseFromFile(filename, "px", 96.0f);
} // reload_svg
/************************************************************************/
static void rasterize_image(void)
{
// determine the "natural" size of the SVG image
int svg_width = (int)svg_image->width;
int svg_height = (int)svg_image->height;
// Do we scale the image to fit our window?
// Specifically: Do we need to shrink the SVG image to fit the view?
int img_width = view_width = box_1->w();
int img_height = view_height = box_1->h();
double w_scale = (double)view_width / (double)svg_width;
double h_scale = (double)view_height / (double)svg_height;
double i_scale;
if ((w_scale >= 1.0) && (h_scale >= 1.0))
{
// image fits entirely inside the view
img_width = svg_width;
img_height = svg_height;
i_scale = 1.0;
}
else if (w_scale >= h_scale) // H as the dominant axis
{
i_scale = h_scale;
img_height = view_height;
img_width = (int)(svg_width * i_scale);
}
else // W as the dominant axis
{
i_scale = w_scale;
img_width = view_width;
img_height = (int)(svg_height * i_scale);
}
// allocate the image data buffer at a suitable size
if (img_data) free (img_data); // discard any previous image data
img_data = (unsigned char*)malloc(view_width * view_height * depth);
if (img_data == NULL)
{
fprintf(stderr, "Could not allocate view image buffer.\n");
fflush(stderr);
return;
}
// Now rasterize the SVG image at the size we want
nsvgRasterize(raster_engine, svg_image, // image to rasterize
0, 0, // x,y offset
(float)i_scale, // scale factor
img_data, // image data array to fill
img_width, img_height, // image size
(img_width * depth)); // image line stride
// assign the image data to the view box
if (img_3) delete img_3; // release any previous FL_RGB_Image we had
img_3 = new Fl_RGB_Image(img_data, img_width, img_height, depth);
box_1->image(img_3);
// fill in the status bar
static char stat_msg[256];
if ((svg_width == 0) && (svg_height == 0))
{
snprintf(stat_msg, 256, "Input file has no SVG image I can read");
}
else
{
snprintf(stat_msg, 256, "SVG %dx%d, IMG %dx%d, Scale %.3f", svg_width, svg_height,
img_width, img_height,
i_scale);
}
stat_bar->value(stat_msg);
main_win->redraw();
} // rasterize_image
/************************************************************************/
static void next_cb(Fl_Button *btn, void *)
{
if (next_arg >= arg_count) // time to quit, run out of files to view
{
if (svg_image) nsvgDelete(svg_image);
svg_image = NULL;
if (main_win) main_win->hide();
}
else
{
// load the SVG vector image data
reload_svg(arg_list[next_arg]);
++next_arg;
// update the button label accordingly
if (next_arg < arg_count)
{
btn->label("Next");
}
else
{
btn->label("Quit");
}
if (!svg_image)
{
if (main_win) main_win->hide();
return;
}
if (svg_image) rasterize_image();
}
} // next_cb
/************************************************************************/
int main(int argc, char **argv)
{
// Parse the command line arguments
first_arg = 0;
Fl::args(argc, argv, first_arg);
arg_count = argc;
arg_list = argv;
next_arg = first_arg;
// Initialize the SVG rasterizer engine
raster_engine = nsvgCreateRasterizer();
if (raster_engine == NULL)
{
fprintf(stderr, "Could not initialize the SVG rasterizer.\n");
fflush(stderr);
return (-1);
}
// Set a size for our view window
view_width = 512;
view_height = 512;
// create the fltk elements
main_win = new Fl_Double_Window((view_width + 20), (view_height + 20 + 40));
box_1 = new vw_box(10, 10, view_width, view_height);
box_1->box(FL_BORDER_BOX);
Fl_Group *stat_grp = new Fl_Group(1, (view_height + 11), (view_width + 18), 38);
stat_grp->begin();
stat_bar = new Fl_Output(10, (main_win->h() - 40), (main_win->w() - 90), 30);
stat_bar->clear_visible_focus();
Fl_Button *btn = new Fl_Button((main_win->w() - 70), (main_win->h() - 40), 60, 30, "Quit");
btn->callback((Fl_Callback *)next_cb);
stat_grp->end();
stat_grp->resizable(stat_bar);
main_win->end();
main_win->resizable(box_1);
// trigger loading of the first image
next_cb(btn, NULL);
// show the window
main_win->show(argc, argv);
int ret = Fl::run();
// clean-up the image resources
if (img_data) free (img_data);
if (raster_engine) nsvgDeleteRasterizer(raster_engine);
if (svg_image) nsvgDelete(svg_image);