Skip to content

Commit

Permalink
WIP: Add SVG font rendering support (dead end)
Browse files Browse the repository at this point in the history
I'm abandoning this as the SVG emoji font I wanted to load contains one
huge 14MB SVG, with a 3MB defs block, which is referenced by all glyphs.
Glyphs are stored as groups an id attribute specifying the glyph name.
SVG fonts seem to support glyphs as separate SVG files, but the emoji
font has a lot of reuse for different variants (skin tone, etc), which
I assume is why they use one massive SVG file.

Due to this organisation, it's not really feasible to read SVG emoji
glyphs on the Pico (at least for someone who values their time).
Caching offsets in the SVG to build smaller SVGs dynamically would
probably also run into memory limitations as the 11,300 def nodes in
this font have 88KB of ID strings (ignoring null bytes) that would need
an additional 44KB of pointers to index - so almost half the Pico's memory
just to index this one font.

The combined length of any one glyph plus the defs it uses is only
around 24KB, so if the SVGs were organised for low-memory reads instead
of element reuse, it wouldn't be a problem to render them using lunasvg
as I intended (assuming lunasvg also keeps its memory use under control).
  • Loading branch information
stecman committed May 19, 2023
1 parent b78feba commit 892d077
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 5 deletions.
13 changes: 12 additions & 1 deletion firmware/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ set(FETCHCONTENT_QUIET FALSE)
# Source dependencies
FetchContent_Declare(freetype URL https://download.savannah.gnu.org/releases/freetype/freetype-2.12.1.tar.gz)
FetchContent_Declare(libpng URL https://cytranet.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz)
FetchContent_Declare(lunasvg URL https://github.com/sammycage/lunasvg/archive/refs/tags/v2.3.5.tar.gz)
FetchContent_Declare(unicode_blocks URL https://unicode.org/Public/UNIDATA/Blocks.txt DOWNLOAD_NO_EXTRACT TRUE)
FetchContent_Declare(unicode_codepoints URL https://unicode.org/Public/UNIDATA/UnicodeData.txt DOWNLOAD_NO_EXTRACT TRUE)
FetchContent_Declare(zlib URL https://versaweb.dl.sourceforge.net/project/libpng/zlib/1.2.11/zlib-1.2.11.tar.xz)
Expand Down Expand Up @@ -110,6 +111,14 @@ set(PNG_INCLUDE_DIRS ${libpng_SOURCE_DIR} ${libpng_BINARY_DIR} CACHE INTERNAL ""

add_subdirectory(${freetype_SOURCE_DIR} ${freetype_BINARY_DIR} EXCLUDE_FROM_ALL)

# lunasvg
FetchContent_GetProperties(lunasvg)
if(NOT lunasvg_POPULATED)
FetchContent_Populate(lunasvg)
endif()

add_subdirectory(${lunasvg_SOURCE_DIR} ${lunasvg_BINARY_DIR} EXCLUDE_FROM_ALL)


#
# Support embedding resources in the binary (GCC-specific)
Expand Down Expand Up @@ -170,6 +179,7 @@ add_subdirectory(fatfs_spi)
set(BASE_SOURCES
embeds.cpp
font_indexer.cpp
svg.cpp
ui/codepoint_view.cpp
ui/common.cpp
ui/font.cpp
Expand Down Expand Up @@ -201,6 +211,7 @@ set(DEVICE_SOURCES

set(FIRMWARE_LIBS
freetype
lunasvg
png_static
zlibstatic
)
Expand Down Expand Up @@ -269,7 +280,7 @@ endif()
# Explicitly select targets to build from libpng and zlib (since we're EXCLUDE_FROM_ALL)
add_dependencies(png_static zlibstatic)
add_dependencies(freetype png_static zlibstatic)
add_dependencies(firmware png_static zlibstatic freetype)
add_dependencies(firmware png_static zlibstatic freetype lunasvg)

target_include_directories(firmware PRIVATE
${libpng_SOURCE_DIR}
Expand Down
73 changes: 73 additions & 0 deletions firmware/svg.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include "svg.hh"

#include <lunasvg.h>

#ifndef FT_CONFIG_OPTION_SVG
#error "SVG support is disabled in this build of FreeType"
#endif

FT_Error lunasvg_port_init(FT_Pointer *state)
{
printf("lunasvg_port_init\n");
return FT_Err_Ok;
}

void lunasvg_port_free(FT_Pointer *state)
{
printf("lunasvg_port_free\n");
}

FT_Error lunasvg_port_render(FT_GlyphSlot slot, FT_Pointer *state)
{
// Note these are pixel sizes, not points
const int width = slot->bitmap.width;
const int height = slot->bitmap.rows;
const int stride = slot->bitmap.pitch;

printf("Size: %d x %d x %d\n", width, height);
printf("Buffer: %lu\n", slot->bitmap.buffer);

// auto document = lunasvg::Document::loadFromData();
// auto bitmap = document->renderToBitmap(width, height);

// const double rootWidth = document->width();
// const double rootHeight = document->height();

// // LunaSVG uses ARGB, so this is probably wrong for FreeType
// lunasvg::Bitmap bitmap((uint8_t*) slot->bitmap.buffer, width, height, stride);
// lunasvg::Matrix matrix(width / rootWidth, 0, 0, height / rootHeight, 0, 0);

// bitmap.clear(0);
// document->render(bitmap, matrix);

// slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
// slot->bitmap.num_grays = 256;
// slot->format = FT_GLYPH_FORMAT_BITMAP;

printf("lunasvg_port_render\n");

return FT_Err_Ok;
}

FT_Error lunasvg_port_preset_slot(FT_GlyphSlot slot, FT_Bool cache, FT_Pointer *state)
{
// Example: https://gitlab.freedesktop.org/freetype/freetype-demos/-/blob/master/src/rsvg-port.c
// API docs: https://freetype.org/freetype2/docs/reference/ft2-svg_fonts.html

FT_SVG_Document document = (FT_SVG_Document) slot->other;
FT_Size_Metrics metrics = document->metrics;

// document->svg_document;
// document->svg_document_length;

printf("lunasvg_port_preset_slot with SVG size %lu\n", document->svg_document_length);

return FT_Err_Ok;
}

SVG_RendererHooks lunasvg_hooks = {
(SVG_Lib_Init_Func) lunasvg_port_init,
(SVG_Lib_Free_Func) lunasvg_port_free,
(SVG_Lib_Render_Func) lunasvg_port_render,
(SVG_Lib_Preset_Slot_Func) lunasvg_port_preset_slot
};
12 changes: 12 additions & 0 deletions firmware/svg.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once

#include <ft2build.h>
#include <freetype/otsvg.h>
#include <freetype/freetype.h>

FT_Error lunasvg_port_init(FT_Pointer *state);
void lunasvg_port_free(FT_Pointer *state);
FT_Error lunasvg_port_render(FT_GlyphSlot slot, FT_Pointer *state);
FT_Error lunasvg_port_preset_slot(FT_GlyphSlot slot, FT_Bool cache, FT_Pointer *state);

extern SVG_RendererHooks lunasvg_hooks;
3 changes: 3 additions & 0 deletions firmware/ui/font.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "filesystem.hh"
#include "font.hh"
#include "st7789.h"
#include "svg.hh"

// FreeType
#include <freetype/ftoutln.h>
Expand Down Expand Up @@ -30,6 +31,8 @@ FontStore::FontStore()
printf("FATAL (%s): FT_Init_FreeType error: 0x%02X\n", __func__, error);
abort();
}

FT_Property_Set( m_ft_library, "ot-svg", "svg-hooks", &lunasvg_hooks );
}

FontStore::~FontStore()
Expand Down
12 changes: 8 additions & 4 deletions firmware/ui/glyph_display.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ void GlyphDisplay::draw(uint32_t codepoint, bool is_valid)
pen.set_render_mode(UIFontPen::kMode_DirectToScreen);

char _hex_string[12];
char* hex_string = (char*) &_hex_string;
char* hex_string = (char*) &_hex_string;
sprintf(hex_string, "0x%X", codepoint);

// Adjust font size to fit on screen
Expand Down Expand Up @@ -198,12 +198,16 @@ bool GlyphDisplay::drawGlyph(uint32_t codepoint)
}

FT_Select_Size(face, best_index);
error = FT_Load_Char(face, codepoint, FT_LOAD_DEFAULT | FT_LOAD_COLOR);
error = FT_Load_Char(face, codepoint, FT_LOAD_COLOR);

if (error) {
return false;
}

} else if (FT_HAS_SVG(face) && FT_Load_Char(face, codepoint, FT_LOAD_COLOR | FT_LOAD_NO_BITMAP) == FT_Err_Ok) {
// SVG loaded into slot
// Nothing more to do: fall through to render

} else {
// Load an outline glyph so that it will fit on screen

Expand All @@ -220,7 +224,7 @@ bool GlyphDisplay::drawGlyph(uint32_t codepoint)
// Load without auto-hinting, since hinting data isn't used with FT_Outline_Render
// and auto-hinting can be memory intensive on complex glyphs. FT_LOAD_NO_HINTING
// appears to make the font metrics inaccurate so I'm not using that here.
const uint32_t flags = FT_LOAD_DEFAULT | FT_LOAD_COMPUTE_METRICS | FT_LOAD_NO_AUTOHINT;
const uint32_t flags = FT_LOAD_COMPUTE_METRICS | FT_LOAD_NO_AUTOHINT;
error = FT_Load_Char(face, codepoint, flags);

// Get dimensions, rouded up
Expand Down Expand Up @@ -290,7 +294,7 @@ bool GlyphDisplay::drawGlyph(uint32_t codepoint)

} else {

// Use the built-in PNG rendering in FreeType
// Render PNG or SVG glyph
//
// TODO: This isn't ideal as it renders the entire image to a memory buffer.
// For Noto Emoji with 136 x 128 bitmaps, this uses about 70KB of memory.
Expand Down

0 comments on commit 892d077

Please sign in to comment.