aboutsummaryrefslogtreecommitdiffstats
path: root/symbols
diff options
context:
space:
mode:
Diffstat (limited to 'symbols')
-rw-r--r--symbols/symbols-gl.c12
-rw-r--r--symbols/symbols-gl.h7
-rw-r--r--symbols/symbols-tool.c104
-rw-r--r--symbols/symbols.c80
-rw-r--r--symbols/symbols.h20
5 files changed, 163 insertions, 60 deletions
diff --git a/symbols/symbols-gl.c b/symbols/symbols-gl.c
index c1ce64e..d91a5dc 100644
--- a/symbols/symbols-gl.c
+++ b/symbols/symbols-gl.c
@@ -87,14 +87,6 @@ bool vega_gl_open(VegaGl *gl, char *error, size_t error_size) {
return true;
}
-/* The pixels a symbol reaches at scale, one more on each side. */
-static VegaPlace place_of(const VegaSymbol *symbol, float scale) {
- if (!symbol->count) return (VegaPlace){0};
- int left = (int)floorf(symbol->left * scale) - 1, right = (int)ceilf(symbol->right * scale) + 1;
- int top = (int)ceilf(symbol->top * scale) + 1, bottom = (int)floorf(symbol->bottom * scale) - 1;
- return (VegaPlace){0, 0, right - left, top - bottom, left, top};
-}
-
bool vega_gl_atlas(VegaGl *gl, const VegaSymbols *s, float scale, const uint32_t *wanted, uint32_t count, VegaAtlas *out,
char *error, size_t error_size) {
(void)gl;
@@ -112,7 +104,7 @@ bool vega_gl_atlas(VegaGl *gl, const VegaSymbols *s, float scale, const uint32_t
uint32_t index = wanted ? wanted[i] : i;
if (index >= s->symbols) continue;
VegaSymbol symbol = vega_symbol_at(s, index);
- VegaPlace p = place_of(&symbol, scale);
+ VegaPlace p = vega_symbol_place(&symbol, scale);
area += (uint64_t)p.width * (uint64_t)p.height;
}
int width = 64;
@@ -122,7 +114,7 @@ bool vega_gl_atlas(VegaGl *gl, const VegaSymbols *s, float scale, const uint32_t
uint32_t index = wanted ? wanted[i] : i;
if (index >= s->symbols) continue;
VegaSymbol symbol = vega_symbol_at(s, index);
- VegaPlace p = place_of(&symbol, scale);
+ VegaPlace p = vega_symbol_place(&symbol, scale);
if (!p.width) continue;
if (p.width > width) width = p.width;
if (x + p.width > width) { x = 0; y += shelf; shelf = 0; }
diff --git a/symbols/symbols-gl.h b/symbols/symbols-gl.h
index 919a114..cc316a4 100644
--- a/symbols/symbols-gl.h
+++ b/symbols/symbols-gl.h
@@ -14,17 +14,12 @@ typedef struct {
GLint target;
} VegaGl;
-/* Where each symbol is in an atlas: its rectangle there (x, y, width, height)
- * and the pixel offset of that rectangle's top left from the symbol's origin
- * (right, up). A symbol with nothing to draw has an empty rectangle. */
-typedef struct { int32_t x, y, width, height, left, top; } VegaPlace;
-
typedef struct {
GLuint texture; /* 8-bit coverage */
int width, height;
float scale; /* pixels per unit */
uint32_t symbols;
- VegaPlace *places; /* per symbol index */
+ VegaPlace *places; /* per symbol index: its box and where the atlas keeps it */
} VegaAtlas;
/* A symbol at a pen position (whole pixels from the target's top left, on the
diff --git a/symbols/symbols-tool.c b/symbols/symbols-tool.c
index b003978..b705d84 100644
--- a/symbols/symbols-tool.c
+++ b/symbols/symbols-tool.c
@@ -1,7 +1,7 @@
#define _POSIX_C_SOURCE 200809L
/* vega-symbols: build symbol files from fonts and SVG icons, and measure them
* against FreeType (docs/design/002-symbol-format.md).
- * vega-symbols build [--font FONT.ttf --codes 0x20-0x7e,...] [--svg CODE=ICON.svg ...] [--units N] -o OUT.vsym
+ * vega-symbols build [--font FONT.ttf [--face N] --codes 0x20-0x7e,...]... [--svg CODE=ICON.svg ...] [--units N] -o OUT.vsym
* vega-symbols compare FONT.ttf OUT.vsym --sizes 12,16,32,64
* vega-symbols info OUT.vsym
* vega-symbols render OUT.vsym CODE PIXELS OUT.pgm
@@ -407,7 +407,9 @@ static int by_code(const void *a, const void *b) {
return (x > y) - (x < y);
}
-static void write_symbols(const char *out, uint16_t units, Built *built, size_t count, const Path *path) {
+/* line: a font's ascent and descent in the build's units (0 and 0 without a font). */
+static void write_symbols(const char *out, uint16_t units, const long line[2], Built *built, size_t count, const Path *path) {
+ long ascent = line[0], descent = line[1];
qsort(built, count, sizeof(*built), by_code);
for (size_t i = 1; i < count; ++i) if (built[i].code == built[i - 1].code) fail("a code is given twice");
Points points = {0};
@@ -422,16 +424,21 @@ static void write_symbols(const char *out, uint16_t units, Built *built, size_t
bool even = units % 2 == 0 && units > 16;
for (size_t i = 0; even && i < points.count; ++i) even = points.items[i].x % 2 == 0 && points.items[i].y % 2 == 0;
for (size_t i = 0; even && i < count; ++i) even = built[i].advance % 2 == 0;
+ even = even && ascent % 2 == 0 && descent % 2 == 0;
if (!even) break;
for (size_t i = 0; i < points.count; ++i) { points.items[i].x /= 2; points.items[i].y /= 2; }
for (size_t i = 0; i < count; ++i) built[i].advance = (int16_t)(built[i].advance / 2);
+ ascent /= 2, descent /= 2;
units /= 2;
}
+ if (ascent > INT16_MAX || descent < INT16_MIN) fail("the font's line does not fit 16 bits");
size_t size = VEGA_SYMBOLS_HEADER + count * VEGA_SYMBOL_BYTES + points.count * VEGA_POINT_BYTES;
unsigned char *bytes = calloc(1, size);
if (!bytes) fail("out of memory");
- memcpy(bytes, "VEGASYM2", 8);
+ memcpy(bytes, "VEGASYM3", 8);
put16(bytes + 8, units);
+ put16(bytes + 24, (int)ascent);
+ put16(bytes + 26, (int)descent);
put32(bytes + 12, (uint32_t)count);
put32(bytes + 16, (uint32_t)points.count);
put32(bytes + 20, (uint32_t)size);
@@ -506,48 +513,68 @@ static double seconds(void) {
return (double)t.tv_sec + (double)t.tv_nsec / 1e9;
}
-#define BUILD_USAGE "usage: vega-symbols build [--font FONT.ttf --codes 0x20-0x7e,...] [--svg CODE=ICON.svg ...] [--units N] -o OUT.vsym"
+#define BUILD_USAGE \
+ "usage: vega-symbols build [--font FONT.ttf [--face N] --codes 0x20-0x7e,...]... [--svg CODE=ICON.svg ...] [--units N] -o OUT.vsym"
+
+/* Fonts in order, each with the codes to take from it: a code an earlier
+ * font gave is not taken again, so later fonts fill what earlier ones lack
+ * (a monospace Latin font, then a CJK one for Hangul). */
+typedef struct { const char *path, *codes; long face; } Font;
static int build(int argc, char **argv) {
- const char *font = NULL, *out = NULL, *codes = NULL, **icons = calloc((size_t)argc, sizeof(*icons));
- size_t icon_count = 0;
+ const char *out = NULL, **icons = calloc((size_t)argc, sizeof(*icons));
+ Font *fonts = calloc((size_t)argc, sizeof(*fonts));
+ size_t icon_count = 0, font_count = 0;
long units = 0;
for (int i = 2; i < argc; i += 2) {
if (i + 1 == argc) fail(BUILD_USAGE);
if (!strcmp(argv[i], "-o")) out = argv[i + 1];
- else if (!strcmp(argv[i], "--font")) font = argv[i + 1];
- else if (!strcmp(argv[i], "--codes")) codes = argv[i + 1];
+ else if (!strcmp(argv[i], "--font")) fonts[font_count++] = (Font){argv[i + 1], NULL, 0};
+ else if (!strcmp(argv[i], "--face") && font_count) fonts[font_count - 1].face = strtol(argv[i + 1], NULL, 10); /* in a .ttc */
+ else if (!strcmp(argv[i], "--codes") && font_count) fonts[font_count - 1].codes = argv[i + 1];
else if (!strcmp(argv[i], "--units")) units = strtol(argv[i + 1], NULL, 10);
else if (!strcmp(argv[i], "--svg")) icons[icon_count++] = argv[i + 1];
else fail(BUILD_USAGE);
}
- if (!out || !font != !codes) fail(BUILD_USAGE);
+ if (!out) fail(BUILD_USAGE);
+ for (size_t f = 0; f < font_count; ++f) if (!fonts[f].codes) fail(BUILD_USAGE);
FT_Library library = NULL;
- FT_Face face = NULL;
- if (font && (FT_Init_FreeType(&library) || FT_New_Face(library, font, 0, &face))) fail("cannot open the font");
- /* Twice a font's units by default: TrueType's implied points, halfway
- * between two control points, then land on whole units. (FreeType finds
- * them by integer halving, so outlines are doubled before it walks them.) */
- if (!units) units = face ? 2 * face->units_per_EM : 4096;
+ FT_Face *faces = calloc(font_count + 1, sizeof(*faces));
+ if (font_count && FT_Init_FreeType(&library)) fail("cannot start FreeType");
+ for (size_t f = 0; f < font_count; ++f)
+ if (FT_New_Face(library, fonts[f].path, fonts[f].face, &faces[f])) fail("cannot open a font");
+ /* Twice the first font's units by default: TrueType's implied points,
+ * halfway between two control points, then land on whole units. (FreeType
+ * finds them by integer halving, so outlines are doubled before it walks
+ * them.) Other fonts are scaled into the same units. */
+ if (!units) units = font_count ? 2 * faces[0]->units_per_EM : 4096;
if (units < 16 || units > 16384) fail("--units: 16 to 16384");
- uint32_t *list = NULL;
- size_t count = codes ? codes_of(codes, &list) : 0;
- Built *built = calloc(count + icon_count + 1, sizeof(*built));
- Path path = {.font_scale = face ? (double)units / (2.0 * face->units_per_EM) : 1};
+ size_t capacity = icon_count + 1;
+ uint32_t **lists = calloc(font_count + 1, sizeof(*lists));
+ size_t *counts = calloc(font_count + 1, sizeof(*counts));
+ for (size_t f = 0; f < font_count; ++f) capacity += counts[f] = codes_of(fonts[f].codes, &lists[f]);
+ Built *built = calloc(capacity, sizeof(*built));
+ Path path = {.font_scale = 1};
size_t made = 0;
FT_Outline_Funcs walk = {ft_move, ft_line, ft_conic, ft_cubic, 0, 0};
- for (size_t i = 0; i < count; ++i) {
- FT_UInt glyph = FT_Get_Char_Index(face, list[i]);
- if (!glyph || FT_Load_Glyph(face, glyph, FT_LOAD_NO_SCALE)) continue; /* the font lacks it */
- size_t first = path.count;
- path.open = false;
- if (face->glyph->format == FT_GLYPH_FORMAT_OUTLINE) {
- FT_Outline_Transform(&face->glyph->outline, &(FT_Matrix){0x20000, 0, 0, 0x20000});
- if (FT_Outline_Decompose(&face->glyph->outline, &walk, &path)) fail("cannot walk an outline");
+ for (size_t f = 0; f < font_count; ++f) {
+ FT_Face face = faces[f];
+ path.font_scale = (double)units / (2.0 * face->units_per_EM);
+ for (size_t i = 0; i < counts[f]; ++i) {
+ bool given = false;
+ for (size_t k = 0; k < made && !given; ++k) given = built[k].code == lists[f][i];
+ FT_UInt glyph = given ? 0 : FT_Get_Char_Index(face, lists[f][i]);
+ if (!glyph || FT_Load_Glyph(face, glyph, FT_LOAD_NO_SCALE)) continue; /* given, or the font lacks it */
+ size_t first = path.count;
+ path.open = false;
+ if (face->glyph->format == FT_GLYPH_FORMAT_OUTLINE) {
+ FT_Outline_Transform(&face->glyph->outline, &(FT_Matrix){0x20000, 0, 0, 0x20000});
+ if (FT_Outline_Decompose(&face->glyph->outline, &walk, &path)) fail("cannot walk an outline");
+ }
+ close_path(&path);
+ long advance = lround((double)face->glyph->metrics.horiAdvance * units / face->units_per_EM);
+ built[made++] = (Built){lists[f][i], first, path.count - first, (int16_t)(advance > INT16_MAX ? INT16_MAX : advance)};
}
- close_path(&path);
- long advance = lround((double)face->glyph->metrics.horiAdvance * units / face->units_per_EM);
- built[made++] = (Built){list[i], first, path.count - first, (int16_t)(advance > INT16_MAX ? INT16_MAX : advance)};
}
for (size_t i = 0; i < icon_count; ++i) {
char *equals = strchr(icons[i], '=');
@@ -558,11 +585,20 @@ static int build(int argc, char **argv) {
svg_icon(&path, equals + 1, (uint16_t)units, &advance);
built[made++] = (Built){(uint32_t)strtoul(icons[i], NULL, 0), first, path.count - first, advance};
}
- write_symbols(out, (uint16_t)units, built, made, &path);
- if (face) FT_Done_Face(face);
+ /* The first font's line as FreeType reports it (hhea or typo metrics). */
+ long line[2] = {font_count ? lround((double)faces[0]->ascender * units / faces[0]->units_per_EM) : 0,
+ font_count ? lround((double)faces[0]->descender * units / faces[0]->units_per_EM) : 0};
+ write_symbols(out, (uint16_t)units, line, built, made, &path);
+ for (size_t f = 0; f < font_count; ++f) {
+ FT_Done_Face(faces[f]);
+ free(lists[f]);
+ }
if (library) FT_Done_FreeType(library);
+ free(faces);
+ free(lists);
+ free(counts);
+ free(fonts);
free(icons);
- free(list);
free(built);
free(path.items);
return 0;
@@ -698,7 +734,7 @@ static int info(int argc, char **argv) {
if (argc < 3) fail("usage: vega-symbols info SYMBOLS.vsym");
char *bytes;
VegaSymbols s = open_symbols(argv[2], &bytes);
- printf("{\"units\":%u,\"symbols\":[", s.units);
+ printf("{\"units\":%u,\"ascent\":%d,\"descent\":%d,\"symbols\":[", s.units, s.ascent, s.descent);
for (uint32_t i = 0; i < s.symbols; ++i) {
VegaSymbol symbol = vega_symbol_at(&s, i);
double area = 0;
diff --git a/symbols/symbols.c b/symbols/symbols.c
index 6f355f1..f00639a 100644
--- a/symbols/symbols.c
+++ b/symbols/symbols.c
@@ -4,7 +4,7 @@
#include <stdlib.h>
#include <string.h>
-static const unsigned char magic[8] = {'V', 'E', 'G', 'A', 'S', 'Y', 'M', '2'};
+static const unsigned char magic[8] = {'V', 'E', 'G', 'A', 'S', 'Y', 'M', '3'};
static uint16_t u16(const unsigned char *p) { return (uint16_t)(p[0] | p[1] << 8); }
static int16_t i16(const unsigned char *p) { return (int16_t)u16(p); }
@@ -17,7 +17,7 @@ VegaSymbol vega_symbol_at(const VegaSymbols *s, uint32_t index) {
/* A point is two i16s, each its coordinate times two plus a flag: off the
* curve in x, starting a contour in y. */
-VegaPoint vega_point(const VegaSymbols *s, uint32_t index) {
+VegaPoint vega_symbols_point(const VegaSymbols *s, uint32_t index) {
const unsigned char *p = s->bytes + VEGA_SYMBOLS_HEADER + (size_t)s->symbols * VEGA_SYMBOL_BYTES +
(size_t)index * VEGA_POINT_BYTES;
int x = i16(p), y = i16(p + 2);
@@ -29,9 +29,9 @@ VegaPoint vega_point(const VegaSymbols *s, uint32_t index) {
bool vega_symbols_open(VegaSymbols *s, const unsigned char *bytes, size_t size, char *error, size_t error_size) {
if (size < VEGA_SYMBOLS_HEADER || memcmp(bytes, magic, sizeof(magic))) REFUSE("not a symbol file");
- *s = (VegaSymbols){bytes, size, u16(bytes + 8), u32(bytes + 12), u32(bytes + 16)};
+ *s = (VegaSymbols){bytes, size, u16(bytes + 8), i16(bytes + 24), i16(bytes + 26), u32(bytes + 12), u32(bytes + 16)};
uint64_t expected = VEGA_SYMBOLS_HEADER + (uint64_t)s->symbols * VEGA_SYMBOL_BYTES + (uint64_t)s->points * VEGA_POINT_BYTES;
- if (u16(bytes + 10) || u32(bytes + 24) || !s->units) REFUSE("header");
+ if (u16(bytes + 10) || !s->units || s->ascent < s->descent) REFUSE("header");
if (u32(bytes + 20) != size || expected != size) REFUSE("size: %zu bytes for %u symbols and %u points", size, s->symbols, s->points);
for (uint32_t i = 0; i < s->symbols; ++i) {
VegaSymbol symbol = vega_symbol_at(s, i);
@@ -42,7 +42,7 @@ bool vega_symbols_open(VegaSymbols *s, const unsigned char *bytes, size_t size,
if (symbol.left > symbol.right || symbol.bottom > symbol.top) REFUSE("symbol %u: bounds", i);
/* An off point then always has both neighbours within the symbol. */
if (symbol.count) {
- VegaPoint first = vega_point(s, symbol.first), last = vega_point(s, symbol.first + symbol.count - 1);
+ VegaPoint first = vega_symbols_point(s, symbol.first), last = vega_symbols_point(s, symbol.first + symbol.count - 1);
if (first.off || !first.move) REFUSE("symbol %u: its first point does not start a contour on the curve", i);
if (last.off) REFUSE("symbol %u: its last point is off the curve", i);
}
@@ -62,15 +62,15 @@ bool vega_symbol(const VegaSymbols *s, uint32_t code, VegaSymbol *out) {
}
bool vega_symbol_curve(const VegaSymbols *s, const VegaSymbol *symbol, uint32_t index, VegaCurve *out) {
- VegaPoint p = vega_point(s, index);
+ VegaPoint p = vega_symbols_point(s, index);
if (p.off) {
- VegaPoint a = vega_point(s, index - 1), b = vega_point(s, index + 1);
+ VegaPoint a = vega_symbols_point(s, index - 1), b = vega_symbols_point(s, index + 1);
*out = (VegaCurve){a.off ? (a.x + p.x) * 0.5f : a.x, a.off ? (a.y + p.y) * 0.5f : a.y, p.x, p.y,
b.off ? (p.x + b.x) * 0.5f : b.x, b.off ? (p.y + b.y) * 0.5f : b.y};
return true;
}
if (index + 1 >= symbol->first + symbol->count) return false;
- VegaPoint b = vega_point(s, index + 1);
+ VegaPoint b = vega_symbols_point(s, index + 1);
if (b.off || b.move) return false;
*out = (VegaCurve){p.x, p.y, (p.x + b.x) * 0.5f, (p.y + b.y) * 0.5f, b.x, b.y};
return true;
@@ -183,3 +183,67 @@ void vega_symbol_raster(const VegaSymbols *s, const VegaSymbol *symbol, double s
}
free(curves);
}
+
+VegaPlace vega_symbol_place(const VegaSymbol *symbol, float scale) {
+ if (!symbol->count) return (VegaPlace){0};
+ int left = (int)floorf(symbol->left * scale) - 1, right = (int)ceilf(symbol->right * scale) + 1;
+ int top = (int)ceilf(symbol->top * scale) + 1, bottom = (int)floorf(symbol->bottom * scale) - 1;
+ return (VegaPlace){0, 0, right - left, top - bottom, left, top};
+}
+
+/* An open table from code to bitmap, twice as large as what it holds. */
+typedef struct { uint32_t code; bool used, missing; VegaBitmap bitmap; } Slot;
+struct VegaGlyphs {
+ const VegaSymbols *symbols;
+ float scale;
+ Slot *slots;
+ size_t capacity, count;
+};
+
+VegaGlyphs *vega_glyphs_open(const VegaSymbols *s, float scale) {
+ VegaGlyphs *glyphs = calloc(1, sizeof(*glyphs));
+ if (!glyphs) return NULL;
+ *glyphs = (VegaGlyphs){s, scale, calloc(256, sizeof(Slot)), 256, 0};
+ if (!glyphs->slots) { free(glyphs); return NULL; }
+ return glyphs;
+}
+
+static Slot *slot_of(Slot *slots, size_t capacity, uint32_t code) {
+ size_t at = (code * 2654435761u) & (capacity - 1);
+ while (slots[at].used && slots[at].code != code) at = (at + 1) & (capacity - 1);
+ return &slots[at];
+}
+
+const VegaBitmap *vega_glyph(VegaGlyphs *glyphs, uint32_t code) {
+ Slot *slot = slot_of(glyphs->slots, glyphs->capacity, code);
+ if (slot->used) return slot->missing ? NULL : &slot->bitmap;
+ if (2 * (glyphs->count + 1) > glyphs->capacity) {
+ Slot *larger = calloc(2 * glyphs->capacity, sizeof(Slot));
+ if (!larger) return NULL;
+ for (size_t i = 0; i < glyphs->capacity; ++i)
+ if (glyphs->slots[i].used) *slot_of(larger, 2 * glyphs->capacity, glyphs->slots[i].code) = glyphs->slots[i];
+ free(glyphs->slots);
+ glyphs->slots = larger;
+ glyphs->capacity *= 2;
+ slot = slot_of(glyphs->slots, glyphs->capacity, code);
+ }
+ VegaSymbol symbol;
+ *slot = (Slot){.code = code, .used = true};
+ ++glyphs->count;
+ if (!vega_symbol(glyphs->symbols, code, &symbol)) { slot->missing = true; return NULL; }
+ VegaPlace place = vega_symbol_place(&symbol, glyphs->scale);
+ unsigned char *pixels = place.width ? malloc((size_t)place.width * (size_t)place.height) : NULL;
+ if (place.width && !pixels) { slot->missing = true; return NULL; }
+ if (pixels)
+ vega_symbol_raster(glyphs->symbols, &symbol, glyphs->scale, place.left / glyphs->scale, place.top / glyphs->scale,
+ place.width, place.height, pixels, (size_t)place.width);
+ slot->bitmap = (VegaBitmap){place, pixels};
+ return &slot->bitmap;
+}
+
+void vega_glyphs_close(VegaGlyphs *glyphs) {
+ if (!glyphs) return;
+ for (size_t i = 0; i < glyphs->capacity; ++i) free((void *)glyphs->slots[i].bitmap.pixels);
+ free(glyphs->slots);
+ free(glyphs);
+}
diff --git a/symbols/symbols.h b/symbols/symbols.h
index 20fbd22..0af3412 100644
--- a/symbols/symbols.h
+++ b/symbols/symbols.h
@@ -23,7 +23,8 @@ typedef struct {
typedef struct {
const unsigned char *bytes;
size_t size;
- uint16_t units; /* per em */
+ uint16_t units; /* per em */
+ int16_t ascent, descent; /* a font's line, in units from the baseline, y up (0 and 0 for icons) */
uint32_t symbols, points;
} VegaSymbols;
@@ -35,7 +36,7 @@ bool vega_symbols_open(VegaSymbols *, const unsigned char *bytes, size_t size, c
/* The symbol of code (a binary search), or false. */
bool vega_symbol(const VegaSymbols *, uint32_t code, VegaSymbol *out);
VegaSymbol vega_symbol_at(const VegaSymbols *, uint32_t index);
-VegaPoint vega_point(const VegaSymbols *, uint32_t index);
+VegaPoint vega_symbols_point(const VegaSymbols *, uint32_t index);
/* The curve that point index of symbol begins, if any: an off point is the
* control of one, and an on point followed by another on point in the same
* contour starts a line. Every curve of a symbol is had once this way, from
@@ -47,9 +48,24 @@ uint32_t vega_symbol_curves(const VegaSymbols *, const VegaSymbol *, VegaCurve *
/* The coverage (0 to 1) of the pixel whose center is (x, y) in units, at
* scale pixels per unit: the exact area the curves' outline covers. */
double vega_coverage(const VegaCurve *, uint32_t count, double scale, double x, double y);
+/* The pixels a symbol reaches at scale (pixels per unit), one more on each
+ * side: width and height, and the offset of the box's top left from the
+ * symbol's origin (right, up). x and y are where a set of bitmaps (an atlas)
+ * keeps it. A symbol with nothing to draw has an empty box. */
+typedef struct { int32_t x, y, width, height, left, top; } VegaPlace;
+VegaPlace vega_symbol_place(const VegaSymbol *, float scale);
/* width x height 8-bit coverage, rows top first, stride bytes apart; pixel
* (i, j) has its center at (left + (i + 0.5) / scale, top - (j + 0.5) / scale)
* in units. */
void vega_symbol_raster(const VegaSymbols *, const VegaSymbol *, double scale, double left, double top, int width,
int height, unsigned char *out, size_t stride);
+
+/* Symbols drawn once at one scale and kept, for surfaces that compose them
+ * on the CPU: each the first time it is asked for. */
+typedef struct { VegaPlace place; const unsigned char *pixels; } VegaBitmap; /* pixels: place.width per row */
+typedef struct VegaGlyphs VegaGlyphs;
+VegaGlyphs *vega_glyphs_open(const VegaSymbols *, float scale);
+/* The bitmap of code, or NULL when the file has no such symbol. */
+const VegaBitmap *vega_glyph(VegaGlyphs *, uint32_t code);
+void vega_glyphs_close(VegaGlyphs *);
#endif