diff options
Diffstat (limited to 'symbols')
| -rw-r--r-- | symbols/symbols-gl.c | 12 | ||||
| -rw-r--r-- | symbols/symbols-gl.h | 7 | ||||
| -rw-r--r-- | symbols/symbols-tool.c | 104 | ||||
| -rw-r--r-- | symbols/symbols.c | 80 | ||||
| -rw-r--r-- | symbols/symbols.h | 20 |
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 |
