sent-font

sent: font engine test
git clone git://git.codemadness.org/sent-font
Log | Files | Refs | LICENSE

commit 4289d39f4c9d8d4f93c9f6a937e19fcbbab4458b
parent 72d33d463fed7ba271961a6f91cae1fed8faa454
Author: Hiltjo Posthuma <hiltjo@codemadness.org>
Date:   Sun, 29 Oct 2017 20:11:07 +0100

test using new font library

Diffstat:
DejaVuSans.ttf | 0
Makefile | 2+-
config.def.h | 7+++----
config.mk | 10+++++-----
drw.c | 474++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
drw.h | 36+++++++++++++++++++++++++++---------
font.c | 2010+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
font.h | 263+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
sent.c | 26+++++++++-----------------
9 files changed, 2597 insertions(+), 231 deletions(-)

diff --git a/DejaVuSans.ttf b/DejaVuSans.ttf Binary files differ. diff --git a/Makefile b/Makefile @@ -3,7 +3,7 @@ include config.mk -SRC = sent.c drw.c util.c +SRC = sent.c drw.c font.c util.c OBJ = ${SRC:.c=.o} all: options sent diff --git a/config.def.h b/config.def.h @@ -1,10 +1,9 @@ /* See LICENSE file for copyright and license details. */ -static char *fontfallbacks[] = { - "dejavu sans", - "roboto", - "ubuntu", +static const char *fontfallbacks[] = { + "DejaVuSans.ttf", }; +int fontsize = 16; #define NUMFONTSCALES 42 #define FONTSZ(x) ((int)(10.0 * powf(1.1288, (x)))) /* x in [0, NUMFONTSCALES-1] */ diff --git a/config.mk b/config.mk @@ -11,13 +11,13 @@ X11INC = /usr/X11R6/include X11LIB = /usr/X11R6/lib # includes and libs -INCS = -I. -I/usr/include -I/usr/include/freetype2 -I${X11INC} -LIBS = -L/usr/lib -lc -lm -L${X11LIB} -lXft -lfontconfig -lX11 +INCS = -I. -I/usr/include -I${X11INC} +LIBS = -L/usr/lib -lc -lm -L${X11LIB} -lX11 # OpenBSD (uncomment) -#INCS = -I. -I${X11INC} -I${X11INC}/freetype2 +#INCS = -I. -I${X11INC} # FreeBSD (uncomment) -#INCS = -I. -I/usr/local/include -I/usr/local/include/freetype2 -I${X11INC} -#LIBS = -L/usr/local/lib -lc -lm -L${X11LIB} -lXft -lfontconfig -lX11 +#INCS = -I. -I/usr/local/include -I${X11INC} +#LIBS = -L/usr/local/lib -lc -lm -L${X11LIB} -lX11 # flags CPPFLAGS = -DVERSION=\"${VERSION}\" -D_XOPEN_SOURCE=600 diff --git a/drw.c b/drw.c @@ -1,13 +1,27 @@ /* See LICENSE file for copyright and license details. */ +#include <sys/stat.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <unistd.h> + #include <X11/Xlib.h> -#include <X11/Xft/Xft.h> +#include <X11/Xutil.h> + +#include "font.h" #include "drw.h" #include "util.h" +extern int fontsize; + +static void readfiledata(const char *, unsigned char **, size_t *); + #define UTF_INVALID 0xFFFD #define UTF_SIZ 4 @@ -64,14 +78,20 @@ Drw * drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) { Drw *drw = ecalloc(1, sizeof(Drw)); + int depth; drw->dpy = dpy; drw->screen = screen; drw->root = root; drw->w = w; drw->h = h; - drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); + depth = DefaultDepth(dpy, screen); + drw->drawable = XCreatePixmap(dpy, root, w, h, depth); drw->gc = XCreateGC(dpy, root, 0, NULL); + drw->image = XCreateImage(dpy, CopyFromParent, depth, ZPixmap, 0, + NULL, w, h, 32, 0); + drw->image->data = ecalloc(drw->image->bytes_per_line, drw->image->height); + XInitImage(drw->image); XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); return drw; @@ -87,7 +107,15 @@ drw_resize(Drw *drw, unsigned int w, unsigned int h) drw->h = h; if (drw->drawable) XFreePixmap(drw->dpy, drw->drawable); - drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); + drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, + DefaultDepth(drw->dpy, drw->screen)); + if (drw->image) + XDestroyImage(drw->image); + drw->image = XCreateImage(drw->dpy, CopyFromParent, + DefaultDepth(drw->dpy, drw->screen), + ZPixmap, 0, NULL, w, h, 32, 0); + drw->image->data = ecalloc(drw->image->bytes_per_line, drw->image->height); + XInitImage(drw->image); } void @@ -98,57 +126,14 @@ drw_free(Drw *drw) free(drw); } -/* This function is an implementation detail. Library users should use - * drw_fontset_create instead. - */ -static Fnt * -xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) -{ - Fnt *font; - XftFont *xfont = NULL; - FcPattern *pattern = NULL; - - if (fontname) { - /* Using the pattern found at font->xfont->pattern does not yield the - * same substitution results as using the pattern returned by - * FcNameParse; using the latter results in the desired fallback - * behaviour whereas the former just results in missing-character - * rectangles being drawn, at least with some fonts. */ - if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { - fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); - return NULL; - } - if (!(pattern = FcNameParse((FcChar8 *) fontname))) { - fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); - XftFontClose(drw->dpy, xfont); - return NULL; - } - } else if (fontpattern) { - if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { - fprintf(stderr, "error, cannot load font from pattern.\n"); - return NULL; - } - } else { - die("no font specified."); - } - - font = ecalloc(1, sizeof(Fnt)); - font->xfont = xfont; - font->pattern = pattern; - font->h = xfont->ascent + xfont->descent; - font->dpy = drw->dpy; - - return font; -} - static void -xfont_free(Fnt *font) +font_free(Fnt *font) { if (!font) return; - if (font->pattern) - FcPatternDestroy(font->pattern); - XftFontClose(font->dpy, font->xfont); + free(font->filedata); + font->filedata = NULL; + font->filedatasiz = 0; free(font); } @@ -156,16 +141,24 @@ Fnt* drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) { Fnt *cur, *ret = NULL; - size_t i; + unsigned char *data = NULL; + size_t i, datasiz = 0; if (!drw || !fonts) return NULL; for (i = 1; i <= fontcount; i++) { - if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { + readfiledata(fonts[fontcount - i], &data, &datasiz); + if ((cur = font_create(drw, data, fontsize))) { cur->next = ret; ret = cur; } + cur->filedata = data; + cur->filedatasiz = datasiz; + + /* allocate new pointer */ + data = NULL; + datasiz = 0; } return (drw->fonts = ret); } @@ -175,20 +168,22 @@ drw_fontset_free(Fnt *font) { if (font) { drw_fontset_free(font->next); - xfont_free(font); + font_free(font); } } void drw_clr_create(Drw *drw, Clr *dest, const char *clrname) { + XColor xcolor_closest, xcolor_exact; + if (!drw || !dest || !clrname) return; - if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), - DefaultColormap(drw->dpy, drw->screen), - clrname, dest)) + if (!XAllocNamedColor(drw->dpy, DefaultColormap(drw->dpy, drw->screen), + clrname, &xcolor_closest, &xcolor_exact)) die("error, cannot allocate color '%s'", clrname); + memcpy(dest, &xcolor_closest, sizeof(Clr)); } /* Wrapper to create color schemes. The caller has to call free(3) on the @@ -200,10 +195,10 @@ drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) Clr *ret; /* need at least two colors for a scheme */ - if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor)))) + if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(Clr)))) return NULL; - for (i = 0; i < clrcount; i++) + for (i = 0; i < clrcount; ++i) drw_clr_create(drw, &ret[i], clrnames[i]); return ret; } @@ -227,143 +222,13 @@ drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int { if (!drw || !drw->scheme) return; - XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); + XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColBg : ColFg].pixel); if (filled) XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); else XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); } -int -drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) -{ - char buf[1024]; - int ty; - unsigned int ew; - XftDraw *d = NULL; - Fnt *usedfont, *curfont, *nextfont; - size_t i, len; - int utf8strlen, utf8charlen, render = x || y || w || h; - long utf8codepoint = 0; - const char *utf8str; - FcCharSet *fccharset; - FcPattern *fcpattern; - FcPattern *match; - XftResult result; - int charexists = 0; - - if (!drw || (render && !drw->scheme) || !text || !drw->fonts) - return 0; - - if (!render) { - w = ~w; - } else { - XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); - XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); - d = XftDrawCreate(drw->dpy, drw->drawable, - DefaultVisual(drw->dpy, drw->screen), - DefaultColormap(drw->dpy, drw->screen)); - x += lpad; - w -= lpad; - } - - usedfont = drw->fonts; - while (1) { - utf8strlen = 0; - utf8str = text; - nextfont = NULL; - while (*text) { - utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); - for (curfont = drw->fonts; curfont; curfont = curfont->next) { - charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); - if (charexists) { - if (curfont == usedfont) { - utf8strlen += utf8charlen; - text += utf8charlen; - } else { - nextfont = curfont; - } - break; - } - } - - if (!charexists || nextfont) - break; - else - charexists = 0; - } - - if (utf8strlen) { - drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL); - /* shorten text if necessary */ - for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--) - drw_font_getexts(usedfont, utf8str, len, &ew, NULL); - - if (len) { - memcpy(buf, utf8str, len); - buf[len] = '\0'; - if (len < utf8strlen) - for (i = len; i && i > len - 3; buf[--i] = '.') - ; /* NOP */ - - if (render) { - ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; - XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], - usedfont->xfont, x, ty, (XftChar8 *)buf, len); - } - x += ew; - w -= ew; - } - } - - if (!*text) { - break; - } else if (nextfont) { - charexists = 0; - usedfont = nextfont; - } else { - /* Regardless of whether or not a fallback font is found, the - * character must be drawn. */ - charexists = 1; - - fccharset = FcCharSetCreate(); - FcCharSetAddChar(fccharset, utf8codepoint); - - if (!drw->fonts->pattern) { - /* Refer to the comment in xfont_create for more information. */ - die("the first font in the cache must be loaded from a font string."); - } - - fcpattern = FcPatternDuplicate(drw->fonts->pattern); - FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); - FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); - - FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); - FcDefaultSubstitute(fcpattern); - match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); - - FcCharSetDestroy(fccharset); - FcPatternDestroy(fcpattern); - - if (match) { - usedfont = xfont_create(drw, NULL, match); - if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { - for (curfont = drw->fonts; curfont->next; curfont = curfont->next) - ; /* NOP */ - curfont->next = usedfont; - } else { - xfont_free(usedfont); - usedfont = drw->fonts; - } - } - } - } - if (d) - XftDrawDestroy(d); - - return x + (render ? w : 0); -} - void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) { @@ -377,24 +242,49 @@ drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) unsigned int drw_fontset_getwidth(Drw *drw, const char *text) { + unsigned int w; + if (!drw || !drw->fonts || !text) return 0; - return drw_text(drw, 0, 0, 0, 0, 0, text, 0); + + drw_font_getexts(drw->fonts, text, strlen(text), &w, NULL); + + return w; } +/* TODO: multiple fonts ? */ void -drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) +drw_font_getexts(Fnt *fnt, const char *text, unsigned int len, + unsigned int *w, unsigned int *h) { - XGlyphInfo ext; + stbtt_fontinfo *font = &(fnt->font); + long wc, pc; + float xpos = 0.0; + int i, r, advance, lsb; if (!font || !text) return; - XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); + for (i = 0; i < len; i += r) { + r = utf8decode(text, &wc, len - 4 < UTF_SIZ ? len - i : UTF_SIZ); + if (r <= 0) + r = 1; + text += r; + + /* font kerning */ + if (i) + xpos += fnt->scale * stbtt_GetCodepointKernAdvance(font, + pc, wc); + pc = wc; + + // TODO: remove lsb? + stbtt_GetCodepointHMetrics(font, wc, &advance, &lsb); + xpos += advance * fnt->scale; + } if (w) - *w = ext.xOff; + *w = (int)xpos; if (h) - *h = font->h; + *h = fnt->h; } Cur * @@ -419,3 +309,197 @@ drw_cur_free(Drw *drw, Cur *cursor) XFreeCursor(drw->dpy, cursor->cursor); free(cursor); } + +/* TODO: handle when text is longer than w ? */ +int +drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, + unsigned int lpad, const char *text, int invert) +{ + Fnt *fnt = drw->fonts; + unsigned char *p; + int render = x || y || w || h; + float xpos = 0.0, xshift, a; + stbtt_fontinfo *font = &(fnt->font); + int d, i, j, k, r, v, len; + int advance, x0, y0, x1, y1, lsb; + int charwidth, charheight; + long wc, pc; + Clr fg, bg; + + if (!drw || (render && !drw->scheme) || !text || !drw->fonts) + return 0; + + len = strlen(text); + if (render) { + if (!invert) { + fg = drw->scheme[ColFg]; + bg = drw->scheme[ColBg]; + } else { + fg = drw->scheme[ColBg]; + bg = drw->scheme[ColFg]; + } + XSetForeground(drw->dpy, drw->gc, bg.pixel); + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + x += lpad; + w -= lpad; + + /* background of image */ + for (j = 0; j < h; ++j) { + d = drw->image->bytes_per_line * j; + for (k = 0; k < w; ++k) { + drw->image->data[d++] = bg.blue / 257; + drw->image->data[d++] = bg.green / 257; + drw->image->data[d++] = bg.red / 257; + d++; /* skip alpha */ + } + } + } + + for (i = 0; i < len; i += r) { + r = utf8decode(text, &wc, UTF_SIZ); + if (r <= 0) + r = 1; + text += r; + + /* font kerning */ + if (i) + xpos += fnt->scale * stbtt_GetCodepointKernAdvance(font, + pc, wc); + pc = wc; + + xshift = xpos - (float)floor(xpos); + + /* TODO: yshift? */ + // TODO: remove lsb? + stbtt_GetCodepointHMetrics(font, wc, &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(font, wc, + fnt->scale, fnt->scale, xshift, 0, &x0, &y0, &x1, &y1); + + if (render) { + charwidth = x1 - x0; + charheight = y1 - y0; + + /* expand chardata buffer if needed */ + if (!fnt->chardatasiz || charwidth * charheight > fnt->chardatasiz) { + fnt->chardatasiz = charwidth * charheight; + if (!(fnt->chardata = realloc(fnt->chardata, ++(fnt->chardatasiz)))) + err(1, "realloc"); + } + memset(fnt->chardata, 0, fnt->chardatasiz); + stbtt_MakeCodepointBitmapSubpixel(font, fnt->chardata, + charwidth, charheight, charwidth, fnt->scale, fnt->scale, + xshift, 0, wc); + +#if 0 + /* draw baseline (blue) */ + for (j = 0, d = (fnt->baseline * drw->image->bytes_per_line); j < w; j++, d += 4) { + drw->image->data[d+0] = 0xff; + drw->image->data[d+1] = 0; + drw->image->data[d+2] = 0; + } + + /* draw ascent (red) */ + for (j = 0, d = 0; j < w; j++, d += 4) { + drw->image->data[d+0] = 0; + drw->image->data[d+1] = 0; + drw->image->data[d+2] = 0xff; + } + + /* draw descent (green) */ + for (j = 0, d = (-fnt->descent + fnt->baseline) * drw->image->bytes_per_line; j < w; j++, d += 4) { + drw->image->data[d+0] = 0; + drw->image->data[d+1] = 0xff; + drw->image->data[d+2] = 0; + } +#endif + + /* Due to rounding it can happen the draw position is + outside the bounds. In this case only draw the + visible pixels inside the bounds. */ + for (j = 0; j < charheight; ++j) { + int _posy = j + fnt->baseline + y0; + if (_posy < 0 || _posy >= drw->h) + continue; /* ignore */ + + d = _posy * drw->image->bytes_per_line; + d += ((int)xpos + x0) * 4; + + p = fnt->chardata + (j * charwidth); + for (k = 0; k < charwidth; ++k, d += 4) { + if ((int)xpos + x0 + k < 0 || + (int)xpos + x0 + k >= drw->w) + continue; /* ignore */ + + v = p[k]; + if (!v) + continue; /* nothing to draw */ + a = v / 255.0; /* TODO: simplify/optimize: no float */ + + /* blend with previous drawn background */ + drw->image->data[d] = (fg.blue * a + + (1 - a) * (uint16_t)drw->image->data[d] * 257) / 257; + drw->image->data[d+1] = (fg.green * a + + (1 - a) * (uint16_t)drw->image->data[d+1] * 257) / 257; + drw->image->data[d+2] = (fg.red * a + + (1 - a) * (uint16_t)drw->image->data[d+2] * 257) / 257; + /* skip alpha */ + } + } + } + xpos += advance * fnt->scale; + } + + if (render && (int)xpos <= w) { + /* TODO performance: only update dirty region */ + XPutImage(drw->dpy, drw->drawable, drw->gc, drw->image, 0, 0, + x, y, (int)xpos, h); + } + + return x + (render ? (int)xpos : 0); +} + +Fnt * +font_create(Drw *drw, unsigned char *data, int height) +{ + Fnt *fnt; + stbtt_fontinfo *font; + + if (!(fnt = calloc(1, sizeof(Fnt)))) + err(1, "calloc"); + + font = &(fnt->font); + stbtt_InitFont(font, data, stbtt_GetFontOffsetForIndex(data, 0)); + + fnt->scale = stbtt_ScaleForPixelHeight(font, height); + stbtt_GetFontVMetrics(font, &(fnt->ascent), &(fnt->descent), + &(fnt->linegap)); + + /* line height */ + fnt->baseline = fnt->ascent *= fnt->scale; + fnt->descent *= fnt->scale; + fnt->h = fnt->ascent - fnt->descent; + + return fnt; +} + +static void +readfiledata(const char *path, unsigned char **data, size_t *datasiz) +{ + const int bufsiz = 16384; + size_t off; + int fd, r; + + if ((fd = open(path, O_RDONLY)) < 0) + err(1, "open: %s", path); + for (off = 0, r = 1; r > 0; off += r) { + if (off + bufsiz > *datasiz) { + if (!(*data = realloc(*data, *datasiz + bufsiz + 1))) + err(1, "realloc"); + *datasiz += bufsiz; + } + r = read(fd, *data + off, bufsiz); + } + if (r == -1) + err(1, "read"); + close(fd); +} diff --git a/drw.h b/drw.h @@ -5,15 +5,23 @@ typedef struct { } Cur; typedef struct Fnt { - Display *dpy; unsigned int h; - XftFont *xfont; - FcPattern *pattern; + int ascent, descent, linegap; + float scale; + stbtt_fontinfo font; + + int baseline; + unsigned char *chardata; + size_t chardatasiz; + + unsigned char *filedata; + size_t filedatasiz; struct Fnt *next; } Fnt; enum { ColFg, ColBg }; /* Clr scheme index */ -typedef XftColor Clr; + +typedef XColor Clr; typedef struct { unsigned int w, h; @@ -24,10 +32,12 @@ typedef struct { GC gc; Clr *scheme; Fnt *fonts; + XImage *image; } Drw; /* Drawable abstraction */ -Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); +Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, + unsigned int h); void drw_resize(Drw *drw, unsigned int w, unsigned int h); void drw_free(Drw *drw); @@ -35,7 +45,8 @@ void drw_free(Drw *drw); Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); void drw_fontset_free(Fnt* set); unsigned int drw_fontset_getwidth(Drw *drw, const char *text); -void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); +void drw_font_getexts(Fnt *font, const char *text, unsigned int len, + unsigned int *w, unsigned int *h); /* Colorscheme abstraction */ void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); @@ -50,8 +61,15 @@ void drw_setfontset(Drw *drw, Fnt *set); void drw_setscheme(Drw *drw, Clr *scm); /* Drawing functions */ -void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); -int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert); +void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, + int filled, int invert); +int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, + unsigned int lpad, const char *text, int invert); /* Map functions */ -void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); +void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, + unsigned int h); + +/* specific to this implementation of the drw "API" + can be useful to re-use font data information. */ +Fnt *font_create(Drw *, unsigned char *, int); diff --git a/font.c b/font.c @@ -0,0 +1,2010 @@ +/* See LICENSE file for copyright and license details. */ +#include <assert.h> +#include <math.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "font.h" + +static uint16_t ttUSHORT(uint8_t *p) { return p[0]*256 + p[1]; } +static int16_t ttSHORT(uint8_t *p) { return p[0]*256 + p[1]; } +static uint32_t ttULONG(uint8_t *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static int32_t ttLONG(uint8_t *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +static uint8_t +stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static uint8_t +stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void +stbtt__buf_seek(stbtt__buf *b, int o) +{ + assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void +stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static uint32_t +stbtt__buf_get(stbtt__buf *b, int n) +{ + uint32_t v = 0; + int i; + + assert(n >= 1 && n <= 4); + for (i = 0; i < n; ++i) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf +stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + + assert(size < 0x40000000); + r.data = (uint8_t*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +static stbtt__buf +stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf +stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static uint32_t +stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + assert(0); + + return 0; +} + +static void +stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + + assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf +stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void +stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, uint32_t *out) +{ + int i; + + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; ++i) + out[i] = stbtt__cff_int(&operands); +} + +static int +stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + + return stbtt__buf_get16(b); +} + +static stbtt__buf +stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + assert(i >= 0 && i < count); + assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +static int +stbtt__isfont(uint8_t *font) +{ + /* check the version number */ + if (stbtt_tag4(font, '1', 0, 0, 0)) return 1; /* TrueType 1 */ + if (stbtt_tag(font, "typ1")) return 1; /* TrueType with type 1 font -- we don't support this! */ + if (stbtt_tag(font, "OTTO")) return 1; /* OpenType with CFF */ + if (stbtt_tag4(font, 0,1,0,0)) return 1; /* OpenType 1.0 */ + if (stbtt_tag(font, "true")) return 1; /* Apple specification for TrueType fonts */ + + return 0; +} + +// @OPTIMIZE: binary search +static uint32_t +stbtt__find_table(uint8_t *data, uint32_t fontstart, const char *tag) +{ + int32_t num_tables = ttUSHORT(data+fontstart+4); + uint32_t tabledir = fontstart + 12; + int32_t i, loc; + + for (i = 0; i < num_tables; ++i) { + loc = tabledir + 16 * i; + if (stbtt_tag(data + loc, tag)) + return ttULONG(data + loc + 8); + } + return 0; +} + +static int +stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + /* TODO: byte-order issues? - hiltjo */ + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + int32_t n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static stbtt__buf +stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + uint32_t subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + + return stbtt__cff_get_index(&cff); +} + +int stbtt_InitFont(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + uint32_t cmap, t; + int32_t i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + uint32_t cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + uint32_t cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i = 0; i < numTables; ++i) { + uint32_t encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + + return 1; +} + +int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + uint8_t *data = info->data; + uint32_t index_map = info->index_map; + + uint16_t format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + int32_t bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + uint32_t first = ttUSHORT(data + index_map + 6); + uint32_t count = ttUSHORT(data + index_map + 8); + if ((uint32_t) unicode_codepoint >= first && (uint32_t) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + uint16_t segcount = ttUSHORT(data+index_map+6) >> 1; + uint16_t searchRange = ttUSHORT(data+index_map+8) >> 1; + uint16_t entrySelector = ttUSHORT(data+index_map+10); + uint16_t rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + uint32_t endCount = index_map + 14; + uint32_t search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + uint16_t end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + uint16_t offset, start; + uint16_t item = (uint16_t) ((search - endCount) >> 1); + + assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (uint16_t) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + uint32_t ngroups = ttULONG(data+index_map+12); + int32_t low,high; + low = 0; high = (int32_t)ngroups; + // Binary search the right group. + while (low < high) { + int32_t mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + uint32_t start_char = ttULONG(data+index_map+16+mid*12); + uint32_t end_char = ttULONG(data+index_map+16+mid*12+4); + if ((uint32_t) unicode_codepoint < start_char) + high = mid; + else if ((uint32_t) unicode_codepoint > end_char) + low = mid+1; + else { + uint32_t start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } else { + fprintf(stderr, "unsupported format: %d\n", format); + } + + return 0; +} + +static void +stbtt_setvertex(stbtt_vertex *v, uint8_t type, int32_t x, + int32_t y, int32_t cx, int32_t cy) +{ + v->type = type; + v->x = (int16_t) x; + v->y = (int16_t) y; + v->cx = (int16_t) cx; + v->cy = (int16_t) cy; +} + +static int +stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + /* TODO: can overflow if sizeof(int) != sizeof(uint32_t) ? */ + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +int +stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, + int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +static int +stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, + int was_off, int start_off, int32_t sx, int32_t sy, int32_t scx, + int32_t scy, int32_t cx, int32_t cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], + STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx, sy, scx, scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx, sy, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, sx, sy, 0, 0); + } + return num_vertices; +} + +static int +stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, + stbtt_vertex **pvertices) +{ + int16_t numberOfContours; + uint8_t *endPtsOfContours; + uint8_t *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + uint8_t flags=0,flagcount; + int32_t ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + int32_t x,y,cx,cy,sx,sy, scx,scy; + uint8_t *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2 * numberOfContours; // a loose bound on how many vertices we might need + /* TODO: overflow check - hiltjo */ + vertices = (stbtt_vertex *) malloc(m * sizeof(vertices[0])); + if (vertices == 0) + return 0; /* TODO: handle it */ + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + for (i = 0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i = 0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + int16_t dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (int16_t) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (int16_t) x; + } + + // now load y coordinates + y=0; + for (i = 0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + int16_t dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (int16_t) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (int16_t) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i = 0; i < n; ++i) { + flags = vertices[off+i].type; + x = (int16_t) vertices[off+i].x; + y = (int16_t) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (int32_t) vertices[off+i+1].x) >> 1; + sy = (y + (int32_t) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (int32_t) vertices[off+i+1].x; + sy = (int32_t) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + uint8_t *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + uint16_t flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + /* TODO: overflow check - hiltjo */ + tmp = (stbtt_vertex*)malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex)); + if (!tmp) { + free(vertices); + free(comp_verts); + return 0; + } + if (num_vertices > 0) memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + free(vertices); + vertices = tmp; + free(comp_verts); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + + return num_vertices; +} + +static void +stbtt__track_vertex(stbtt__csctx *c, int32_t x, int32_t y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void +stbtt__csctx_v(stbtt__csctx *c, uint8_t type, int32_t x, int32_t y, + int32_t cx, int32_t cy, int32_t cx1, int32_t cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (int16_t) cx1; + c->pvertices[c->num_vertices].cy1 = (int16_t) cy1; + } + c->num_vertices++; +} + +static void +stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, + (int)ctx->first_y, 0, 0, 0, 0); +} + +static void +stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void +stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void +stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, + float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, + (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf +stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf +stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; ++i) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + + return stbtt__get_subrs(info->cff, + stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int +stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; +vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; +hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (fabs(dx) > fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(int16_t)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, + stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)malloc(count_ctx.num_vertices*sizeof(stbtt_vertex)); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + + return 0; +} + +int +stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, + int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) { + *x0 = r ? c.min_x : 0; + *y0 = r ? c.min_y : 0; + *x1 = r ? c.max_x : 0; + *y1 = r ? c.max_y : 0; + } + return r ? c.num_vertices : 0; +} + +int +stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, + stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +void +stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, + int *advanceWidth, int *leftSideBearing) +{ + uint16_t numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +int +stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + uint8_t *data = info->data + info->kern; + uint32_t needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + /* TODO: fix? broken on SPARC probably - hiltjo */ + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +int +stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + /* if no kerning table, don't waste time looking up both + codepoint->glyphs. */ + if (!info->kern) + return 0; + + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info, ch1), + stbtt_FindGlyphIndex(info, ch2)); +} + +void +stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, + int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info, codepoint), + advanceWidth, leftSideBearing); +} + +void +stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, + int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +float +stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - + ttSHORT(info->data + info->hhea + 6); + + return (float) height / fheight; +} + +/* + * antialiasing software rasterizer + */ +void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, + int glyph, float scale_x, float scale_y, float shift_x, + float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0 = 0, y0 = 0, x1, y1; /* =0 suppresses compiler warning */ + + if (!stbtt_GetGlyphBox(font, glyph, &x0, &y0, &x1, &y1)) { + /* e.g. space character */ + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + /* move to integral bboxes (treating pixels as little squares, + what pixels get touched)? */ + if (ix0) *ix0 = (int)floor( x0 * scale_x + shift_x); + if (iy0) *iy0 = (int)floor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = (int)ceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = (int)ceil (-y0 * scale_y + shift_y); + } +} + +void +stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, + int codepoint, float scale_x, float scale_y, float shift_x, + float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, + stbtt_FindGlyphIndex(font, codepoint), scale_x, scale_y, + shift_x, shift_y, ix0, iy0, ix1, iy1); +} + +/* Rasterizer */ + +static void +*stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) malloc(sizeof(stbtt__hheap_chunk) + size * count); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + size * hh->num_remaining_in_head_chunk; + } +} + +static void +stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void +stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head, *t; + while (c) { + t = c->next; + free(c); + c = t; + } +} + +static stbtt__active_edge * +stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, + float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + assert(z != NULL); + //assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + + return z; +} + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void +stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, + float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + assert(y0 < y1); + assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + assert(x1 <= x+1); + else if (x0 == x+1) + assert(x1 >= x); + else if (x0 <= x) + assert(x1 <= x); + else if (x0 >= x+1) + assert(x1 >= x+1); + else + assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void +stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, + stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + assert(fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +/* directly AA rasterize edges w/o supersampling */ +static void +stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, + int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y, j = 0, i; + float scanline_data[129], *scanline, *scanline2; + + (void)vsubsample; + + if (result->w > 64) + scanline = (float *) malloc((result->w*2+1) * sizeof(float)); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + memset(scanline , 0, result->w*sizeof(scanline[0])); + memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + assert(z->ey >= scan_y_top); + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + free(scanline); +} + +static void +stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i, j; + + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void +stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshhold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && mid<n: 0>n => n; 0<n => 0 */ + /* 0<mid && mid>n: 0>n => 0; 0<n => n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i = 1; + j = n - 1; + for (;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void +stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +static void +stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, + int windings, float scale_x, float scale_y, float shift_x, + float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; + int vsubsample = 1; + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i = 0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) malloc(sizeof(*e) * (n+1)); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m = 0; + for (i = 0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + free(e); +} + +static void +stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +static int +stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, + float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2 * x1 + x2) / 4; + float my = (y0 + 2 * y1 + y2) / 4; + // versus directly drawn line + float dx = (x0 + x2) / 2 - mx; + float dy = (y0 + y2) / 2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void +stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, + float y0, float x1, float y1, float x2, float y2, float x3, float y3, + float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (sqrt(dx0*dx0+dy0*dy0) + sqrt(dx1*dx1+dy1*dy1) + sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0, y0, x01, y01, + xa, ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb, yb, + x23, y23, x3, y3, objspace_flatness_squared, n + 1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +stbtt__point * +stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, + int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) malloc(sizeof(**contour_lengths) * n); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) malloc(num_points * sizeof(points[0])); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + free(points); + free(*contour_lengths); + *contour_lengths = 0; + *num_contours = 0; + + return NULL; +} + +void +stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, + stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, + float shift_x, float shift_y, int x_off, int y_off, int invert, + void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count, *winding_lengths; + + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, + flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, + winding_count, scale_x, scale_y, shift_x, shift_y, + x_off, y_off, invert, userdata); + free(winding_lengths); + free(windings); + } +} + +void +stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, + int out_w, int out_h, int out_stride, float scale_x, float scale_y, + float shift_x, float shift_y, int glyph) +{ + int ix0, iy0; + + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, + shift_y, &ix0, &iy0, 0, 0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, + scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + + free(vertices); +} + +void +stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, + unsigned char *output, int out_w, int out_h, int out_stride, + float scale_x, float scale_y, float shift_x, float shift_y, + int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, + scale_x, scale_y, shift_x, shift_y, + stbtt_FindGlyphIndex(info, codepoint)); +} + +int +stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} diff --git a/font.h b/font.h @@ -0,0 +1,263 @@ +/* See LICENSE file for copyright and license details. */ +#include <assert.h> +#include <math.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +// private structure +typedef struct +{ + uint8_t *data; + int cursor; + int size; +} stbtt__buf; + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// The following structure is defined publically so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +typedef struct stbtt_fontinfo stbtt_fontinfo; + +/* + * GLYPH SHAPES (you probably don't need these, but they have to go before + * the bitmaps for C declaration-order reasons) + */ + +enum { + STBTT_vmove = 1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic +}; + +// can't use int16_t because that's not visible in the header file +#define stbtt_vertex_type short +typedef struct +{ + stbtt_vertex_type x, y, cx, cy, cx1, cy1; + unsigned char type, padding; +} stbtt_vertex; + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +/* + * accessors to parse data from file + */ + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (uint8_t *) (p)) +#define ttCHAR(p) (* (int8_t *) (p)) +#define ttFixed(p) ttLONG(p) + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + int32_t min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +/* + * Rasterizer + */ +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + float fx,fdx,fdy; + float direction; + float sy; + float ey; +} stbtt__active_edge; + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +typedef struct +{ + float x,y; +} stbtt__point; + +/* + * FONT LOADING + */ +int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); + +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. +int stbtt_InitFont(stbtt_fontinfo *info, unsigned char *data, int offset); + +// +// CHARACTER TO GLYPH-INDEX CONVERSION +// + +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. +int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); + +// +// CHARACTER PROPERTIES +// + +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. +float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); + +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size +void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, + int *descent, int *lineGap); + +// leftSideBearing is the offset from the current horizontal position to the +// left edge of the character advanceWidth is the offset from the current +// horizontal position to the next horizontal position these are expressed in +// unscaled coordinates +void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, + int codepoint, int *advanceWidth, int *leftSideBearing); + +// an additional amount to add to the 'advance' value between ch1 and ch2 +int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); + +void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, + int *advanceWidth, int *leftSideBearing); +int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, + int glyph2); +// as above, but takes one or more glyph indices for greater efficiency +int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, + int *x0, int *y0, int *x1, int *y1); + +int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, + int *x0, int *y0, int *x1, int *y1); + +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of countours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. +int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); + +// +// BITMAP RENDERING +// + +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character +void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, + unsigned char *output, int out_w, int out_h, int out_stride, + float scale_x, float scale_y, float shift_x, float shift_y, + int codepoint); + +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character +void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, + int codepoint, float scale_x, float scale_y, float shift_x, + float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, + unsigned char *output, int out_w, int out_h, int out_stride, + float scale_x, float scale_y, float shift_x, float shift_y, int glyph); + +// rasterize a shape with quadratic beziers into a bitmap +void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for malloc() diff --git a/sent.c b/sent.c @@ -17,7 +17,8 @@ #include <X11/Xatom.h> #include <X11/Xlib.h> #include <X11/Xutil.h> -#include <X11/Xft/Xft.h> + +#include "font.h" #include "arg.h" #include "util.h" @@ -28,7 +29,6 @@ char *argv0; /* macros */ #define LEN(a) (sizeof(a) / sizeof(a)[0]) #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) -#define MAXFONTSTRLEN 128 typedef enum { NONE = 0, @@ -606,25 +606,17 @@ xinit() void xloadfonts() { - int i, j; - char *fstrs[LEN(fontfallbacks)]; + Fnt *fnt; + int i; - for (j = 0; j < LEN(fontfallbacks); j++) { - fstrs[j] = ecalloc(1, MAXFONTSTRLEN); - } + if (!(fnt = drw_fontset_create(d, fontfallbacks, 1))) + die("sent: Unable to load font: %s", fontfallbacks[0]); for (i = 0; i < NUMFONTSCALES; i++) { - for (j = 0; j < LEN(fontfallbacks); j++) { - if (MAXFONTSTRLEN < snprintf(fstrs[j], MAXFONTSTRLEN, "%s:size=%d", fontfallbacks[j], FONTSZ(i))) - die("sent: Font string too long"); - } - if (!(fonts[i] = drw_fontset_create(d, (const char**)fstrs, LEN(fstrs)))) - die("sent: Unable to load any font for size %d", FONTSZ(i)); + if (!(fonts[i] = font_create(d, fnt->filedata, FONTSZ(i)))) + die("sent: Unable to use font %s for size %d", + fontfallbacks[0], FONTSZ(i)); } - - for (j = 0; j < LEN(fontfallbacks); j++) - if (fstrs[j]) - free(fstrs[j]); } void