feat: SVG support in Image viewer app

This commit is contained in:
2026-05-18 20:07:37 +02:00
parent 47e3bb54a5
commit 6ac9ae01b0
3 changed files with 93 additions and 1 deletions
+40
View File
@@ -1626,6 +1626,46 @@ inline SvgIcon svg_render(const char* svg_data, int svg_len, int target_w, int t
// ---------------------------------------------------------------------------
// Load SVG from VFS and render
// ---------------------------------------------------------------------------
// Extract intrinsic dimensions from an SVG document. Prefers width/height
// attributes, falls back to viewBox, then to a 256x256 default. Used by the
// image viewer to render SVGs at their natural size rather than a fixed icon
// size.
inline void svg_get_natural_size(const char* svg_data, int svg_len,
int* out_w, int* out_h) {
int w = 0, h = 0;
const char* svg_tag = svg_strstr(svg_data, svg_len, "<svg");
if (svg_tag) {
int tag_offset = (int)(svg_tag - svg_data);
int tag_end = tag_offset;
while (tag_end < svg_len && svg_data[tag_end] != '>') ++tag_end;
int tag_len = tag_end - tag_offset + 1;
char attr_buf[64];
if (svg_get_attr(svg_tag, tag_len, " width", attr_buf, sizeof(attr_buf)) > 0)
w = svg_parse_int(attr_buf);
if (svg_get_attr(svg_tag, tag_len, " height", attr_buf, sizeof(attr_buf)) > 0)
h = svg_parse_int(attr_buf);
if ((w <= 0 || h <= 0) &&
svg_get_attr(svg_tag, tag_len, " viewBox", attr_buf, sizeof(attr_buf)) > 0) {
fixed_t vbx, vby, vbw, vbh;
const char* vp = attr_buf;
int c;
c = svg_parse_fixed(vp, &vbx); vp += c; while (*vp && svg_char_is_sep(*vp)) ++vp;
c = svg_parse_fixed(vp, &vby); vp += c; while (*vp && svg_char_is_sep(*vp)) ++vp;
c = svg_parse_fixed(vp, &vbw); vp += c; while (*vp && svg_char_is_sep(*vp)) ++vp;
svg_parse_fixed(vp, &vbh);
(void)vbx; (void)vby;
if (w <= 0) w = fixed_to_int(vbw);
if (h <= 0) h = fixed_to_int(vbh);
}
}
if (w <= 0) w = 256;
if (h <= 0) h = 256;
if (out_w) *out_w = w;
if (out_h) *out_h = h;
}
inline SvgIcon svg_load(const char* vfs_path, int target_w, int target_h, Color fill_color) {
int fd = montauk::open(vfs_path);
if (fd < 0) {
@@ -45,7 +45,9 @@ bool str_ends_with(const char* s, const char* suffix) {
}
bool is_image_file(const char* name) {
return str_ends_with(name, ".jpg") || str_ends_with(name, ".jpeg");
return str_ends_with(name, ".jpg")
|| str_ends_with(name, ".jpeg")
|| str_ends_with(name, ".svg");
}
bool is_font_file(const char* name) {
+50
View File
@@ -10,6 +10,7 @@
#include <gui/gui.hpp>
#include <gui/standalone.hpp>
#include <gui/truetype.hpp>
#include <gui/svg.hpp>
extern "C" {
#include <string.h>
@@ -99,6 +100,20 @@ static const char* basename(const char* path) {
return last;
}
static bool path_has_ext_ci(const char* path, const char* ext) {
int pl = 0; while (path[pl]) pl++;
int el = 0; while (ext[el]) el++;
if (pl < el) return false;
for (int i = 0; i < el; i++) {
char a = path[pl - el + i];
char b = ext[i];
if (a >= 'A' && a <= 'Z') a = (char)(a - 'A' + 'a');
if (b >= 'A' && b <= 'Z') b = (char)(b - 'A' + 'a');
if (a != b) return false;
}
return true;
}
// ============================================================================
// Image loading
// ============================================================================
@@ -135,6 +150,41 @@ static bool load_image(const char* path) {
return false;
}
// SVG: vector path through our own rasterizer. stb_image can't decode SVG.
if (path_has_ext_ci(path, ".svg")) {
int nat_w = 0, nat_h = 0;
svg_get_natural_size((const char*)filedata, bytes_read, &nat_w, &nat_h);
// Cap longest edge to keep memory bounded (4 bytes/pixel).
constexpr int MAX_EDGE = 2048;
int max_edge = nat_w > nat_h ? nat_w : nat_h;
if (max_edge > MAX_EDGE) {
nat_w = (int)((int64_t)nat_w * MAX_EDGE / max_edge);
nat_h = (int)((int64_t)nat_h * MAX_EDGE / max_edge);
}
if (nat_w < 1) nat_w = 1;
if (nat_h < 1) nat_h = 1;
// fill_color is the currentColor substitute. Real SVG files mostly
// specify their own fills, so this only matters for paths that use
// currentColor; default to black for sensible fallback.
SvgIcon icon = svg_render((const char*)filedata, bytes_read,
nat_w, nat_h, Color::from_rgb(0, 0, 0));
montauk::mfree(filedata);
if (!icon.pixels) {
montauk::strcpy(g_status, "Error: SVG render failed");
return false;
}
g_image = icon.pixels;
g_img_w = icon.width;
g_img_h = icon.height;
g_has_alpha = true;
snprintf(g_status, 256, "%s %dx%d SVG", basename(path), g_img_w, g_img_h);
return true;
}
int w, h, channels;
unsigned char* rgba = stbi_load_from_memory(filedata, bytes_read, &w, &h, &channels, 4);
montauk::mfree(filedata);