add libttf

This commit is contained in:
aiju 2018-06-09 14:33:19 +00:00
parent 198f10bb25
commit db71e19005
10 changed files with 3951 additions and 0 deletions

168
sys/include/ttf.h Normal file
View file

@ -0,0 +1,168 @@
#pragma src "/sys/src/libttf"
#pragma lib "libttf.a"
typedef struct TTTable TTTable;
typedef struct TTChMap TTChMap;
typedef struct TTPoint TTPoint;
typedef struct TTGlyph TTGlyph;
typedef struct TTGlyphInfo TTGlyphInfo;
typedef struct TTFontU TTFontU;
typedef struct TTFont TTFont;
typedef struct TTFunction TTFunction;
typedef struct TTGState TTGState;
typedef struct TTBitmap TTBitmap;
typedef struct TTKern TTKern;
typedef struct Biobuf Biobuf;
struct TTTable {
u32int tag;
u32int csum;
u32int offset;
u32int len;
};
struct TTChMap {
int start, end, delta;
int *tab;
enum {
TTCDELTA16 = 1,
TTCINVALID = 2,
} flags;
int temp;
};
struct TTPoint {
int x, y;
u8int flags;
};
struct TTBitmap {
u8int *bit;
int width, height, stride;
};
struct TTGlyph {
TTBitmap;
int idx;
int xmin, xmax, ymin, ymax;
int xminpx, xmaxpx, yminpx, ymaxpx;
int advanceWidthpx;
TTPoint *pt;
TTPoint *ptorg;
int npt;
u16int *confst;
int ncon;
u8int *hint;
int nhint;
TTFont *font;
TTGlyphInfo *info;
};
struct TTGlyphInfo {
int loca;
u16int advanceWidth;
short lsb;
};
struct TTFunction {
u8int *pgm;
int npgm;
};
struct TTGState {
int pvx, pvy;
int dpvx, dpvy;
int fvx, fvy;
u32int instctrl;
u32int scanctrl;
u32int scantype;
int rperiod, rphase, rthold;
u8int zp;
int rp[3];
int cvci;
int loop;
int mindist;
int deltabase, deltashift;
u8int autoflip;
u32int singlewval, singlewci;
};
struct TTKern {
u32int idx;
int val;
};
struct TTFontU {
int ref;
Biobuf *bin;
TTTable *tab;
u16int ntab;
TTChMap *cmap;
int ncmap;
short *cvtu;
int ncvtu;
u16int flags;
int emsize;
short xmin, ymin, xmax, ymax;
u16int longloca;
TTGlyphInfo *ginfo;
u16int numGlyphs;
u16int maxPoints;
u16int maxCountours;
u16int maxComponentPoints;
u16int maxComponentCountours;
u16int maxZones;
u16int maxTwilightPoints;
u16int maxStorage;
u16int maxFunctionDefs;
u16int maxInstructionDefs;
u16int maxStackElements;
u16int maxSizeOfInstructions;
u16int maxComponentElements;
u16int maxComponentDepth;
int ascent, descent;
u16int advanceWidthMax;
u16int minLeftSideBearing;
u16int minRightSideBearing;
u16int xMaxExtent;
u16int numOfLongHorMetrics;
TTKern *kern;
int nkern;
};
struct TTFont {
TTFontU *u;
int ascentpx, descentpx;
int ppem;
TTGState;
TTGState defstate;
TTPoint *twilight, *twiorg;
u32int *hintstack;
TTFunction *func;
u32int *storage;
int *cvt;
int ncvt;
};
TTFont *ttfopen(char *, int, int);
TTFont *ttfscale(TTFont *, int, int);
void ttfclose(TTFont *);
int ttffindchar(TTFont *, Rune);
int ttfenumchar(TTFont *, Rune, Rune *);
TTGlyph *ttfgetglyph(TTFont *, int, int);
void ttfputglyph(TTGlyph *);
int ttfgetcontour(TTGlyph *, int, float **, int *);
enum {
TTFLALIGN = 0,
TTFRALIGN = 1,
TTFCENTER = 2,
TTFMODE = 3,
TTFJUSTIFY = 4,
};
TTBitmap *ttfrender(TTFont *, char *, char *, int, int, int, char **);
TTBitmap *ttfrunerender(TTFont *, Rune *, Rune *, int, int, int, Rune **);
TTBitmap *ttfnewbitmap(int, int);
void ttffreebitmap(TTBitmap *);
void ttfblit(TTBitmap *, int, int, TTBitmap *, int, int, int, int);

211
sys/man/2/ttf Normal file
View file

@ -0,0 +1,211 @@
.TH TTF 2
.SH NAME
ttfopen, ttfscale, ttfclose, ttffindchar, ttfenumchar, ttfgetglyph,
ttfputglyph, ttfgetcontour, ttfrender, ttfrunerender, ttfnewbitmap,
ttffreebitmap, ttfblit \- TrueType renderer
.SH SYNOPSIS
.de PB
.PP
.ft L
.nf
..
.PB
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ttf.h>
.PB
struct TTBitmap {
u8int *bit;
int width, height, stride;
};
.PB
struct TTGlyph {
TTBitmap;
int xminpx, xmaxpx, yminpx, ymaxpx, advanceWidthpx;
/* + internals */
};
.PB
struct TTFont {
int ppem, ascentpx, descentpx;
/* + internals */
};
.PB
.ta +\w'\fLTTBitmap* \fP'u
TTFont* ttfopen(char *filename, int size, int flags);
TTFont* ttfscale(TTFont *f, int size, int flags);
void ttfclose(TTFont *f);
.PB
int ttffindchar(TTFont *f, Rune r);
int ttfenumchar(TTFont *f, Rune r, Rune *rp);
.PB
TTGlyph* ttfgetglyph(TTFont *f, int glyphidx, int render);
void ttfputglyph(TTGlyph *g);
int ttfgetcontour(TTGlyph *g, int idx, float **fp, int *nfp);
.PB
TTBitmap* ttfrender(TTFont *f, char *s, char *e, int w, int h,
int flags, char **pp);
TTBitmap* ttfrunerender(TTFont *f, Rune *s, Rune *e, int w, int h,
int flags, char **pp);
.PB
TTBitmap* ttfnewbitmap(int w, int h);
void ttfblit(TTBitmap *dst, int dstx, int dsty, TTBitmap *src,
int srcx, int srcy, int w, int h);
void ttffreebitmap(TTBitmap *);
.SH DESCRIPTION
.PP
.I Libttf
is a parser and renderer of TrueType fonts.
Given a \fLttf\fR font file it can produce the rendered versions of characters at a given size.
.PP
.I Ttfopen
opens the font at
.I filename
and initialises it for rendering at size
.I size
(specified in pixels per em).
.I Flags
is reserved for future use and should be zero.
If rendering at multiple sizes is desired,
.I ttfscale
reopens the font at a different size (internally the size-independent data is shared).
.I TTfclose
closes an opened font.
Each instance of a font created by
.I ttfopen
and
.I ttfscale
must be closed separately.
.PP
A character in a TrueType font is called a glyph.
Glyphs are numbered starting from 0 and the glyph indices do not need to follow any established coding scheme.
.I Ttffindchar
finds the glyph number of a given rune (Unicode codepoint).
If the character does not exist in the font, zero is returned.
Note that, in TrueType fonts, glyph 0 conventionally contains the "glyph not found" character.
.I Ttfenumchar
is like
.I ttffindchar
but will continue searching if the character is not in the font, returning the rune number for which it found a glyph in
.BR *rp .
It returns character in ascending Unicode order and it can be used to enumerate the characters in a font.
Zero is returned if there are no further characters.
.PP
.I Ttfgetglyph
interprets the actual data for a glyph specified by its index
.IR glyphidx .
With
.I render
set to zero, the data is left uninterpreted; currently its only use is
.I ttfgetcontour.
With
.I render
set to one, the glyph is also rendered, i.e. a pixel representation is produced and stored in the
.I TTBitmap
embedded in the
.I TTGlyph
structure it returns.
Although TrueType uses a right handed coordinate system (y increases going up), the bitmap data returns follows Plan 9 conventions (and is compatible with the
.IR draw (3)
mask argument).
The bottom left hand corner is at position (\fIxmin\fR, \fIymin\fR) in the TrueType coordinate system.
.I Ttfputglyph
should be used to return
.I TTGlyph
structures for cleanup.
.PP
.I Ttfgetcontour
can be used to obtain raw contour data for a glyph.
Given an index
.I i
it returns the corresponding contour (counting from zero), storing a pointer to a list of (\fIx\fR, \fIy\fR) pairs in
.BR *fp .
The array is allocated with
.BR malloc (2).
The (always odd) number of points is stored in
.BR *np .
The contours correspond to closed quadratic Bézier curves and the points with odd indices are the control points.
For an invalid index, zero is returned and
.B *fp
and
.B *np
are not accessed.
For a valid index, the number returned is the number of contours with index ≥ \fIi\fR.
.PP
.I Ttfrender
and
.I ttfrunerender
typeset a string of text (specified as UTF-8 or raw Unicode, respectively) and return a bitmap of size
.I w
and
.IR h .
It attempts to typeset text starting from
.I s
and up to and not including
.IR e .
If
.I e
is
.BR nil ,
text is typeset until a null byte is encountered.
.I Flags
specifies the alignment.
.BR TTFLALIGN ,
.BR TTFRALIGN
and
.B TTFCENTER
specify left-aligned, right-aligned and centered text, respectively.
.B TTFJUSTIFY
can be or'ed with these three options to produce text where any ``wrapped'' line is justified.
.PP
For reasons of efficiency and simplicity,
.I libttf
includes its own format for 1 bpp bitmaps.
In these bitmaps,
0 corresponds to transparent and 1 corresponds to opaque.
Otherwise, the format is identical to
.B k1
.IR image (6)
bitmaps.
.I Ttfnewbitmap
and
.I ttffreebitmap
allocate and deallocate such bitmaps, respectively.
.I TTGlyph
structures can be used in place of bitmaps but must be deallocated with
.IR ttfputglyph ,
not
.IR ttffreebitmap .
.I Ttfblit
copies part of one bitmap onto another.
Note that bits are or'ed together \(-- blitting a transparent over an opaque pixel does not produce an transparent pixel.
.SH SOURCE
.B /sys/src/libttf
.SH "SEE ALSO"
Apple, ``TrueType™ Reference Manual''.
.br
Microsoft, ``OpenType® specification''.
.br
FreeType, source code (the only accurate source).
.br
.IR ttfrender (1).
.SH DIAGNOSTICS
Following standard conventions, routines returning pointers return
.B nil
on error and return an error message in
.BR errstr .
.SH BUGS
Both ``standards'' are packages of contradictions and lies.
.PP
Apple Advanced Typography and Microsoft OpenType extensions are not supported; similarly non-TrueType (Postscript, Bitmap) fonts packaged as
.B .ttf
files are not supported.
.PP
The library is immature and interfaces are virtually guaranteed to change.
.PP
Fonts packaged as
.B .ttc
files are not supported.
.SH HISTORY
.I Libttf
first appeared in 9front in June, 2018.

92
sys/src/libttf/bit.c Normal file
View file

@ -0,0 +1,92 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ttf.h>
#include "impl.h"
TTBitmap *
ttfnewbitmap(int w, int h)
{
TTBitmap *b;
b = mallocz(sizeof(TTBitmap), 1);
if(b == nil) return nil;
b->width = w;
b->height = h;
b->stride = w + 7 >> 3;
b->bit = mallocz(b->stride * h, 1);
if(b->bit == nil){
free(b);
return nil;
}
return b;
}
void
ttffreebitmap(TTBitmap *b)
{
if(b == nil) return;
free(b->bit);
free(b);
}
void
ttfblit(TTBitmap *dst, int dx, int dy, TTBitmap *src, int sx0, int sy0, int sx1, int sy1)
{
uchar *sp, *dp;
u32int b;
int x, y, ss, ds, dx1, dy1;
if(sx0 < 0) sx0 = 0;
if(sy0 < 0) sy0 = 0;
if(sx1 > src->width) sx1 = src->width;
if(sy1 > src->height) sy1 = src->height;
if(dx < 0){
sx0 -= dx;
dx = 0;
}
if(dy < 0){
sy0 -= dy;
dy = 0;
}
dx1 = dx + sx1 - sx0;
dy1 = dy + sy1 - sy0;
if(dx1 > dst->width){
sx1 -= dx1 - dst->width;
dx1 = dst->width;
}
if(dy1 > dst->height) sy1 -= dy1 - dst->height;
if(sx1 <= sx0 || sy1 <= sy0) return;
ss = src->stride - ((sx1-1 >> 3) - (sx0 >> 3) + 1);
ds = dst->stride - ((dx1-1 >> 3) - (dx >> 3) + 1);
sp = src->bit + sy0 * src->stride + (sx0 >> 3);
dp = dst->bit + dy * dst->stride + (dx >> 3);
y = sy1 - sy0;
do{
if(sx0 >> 3 == sx1 >> 3){
b = (*sp++ << 8 & 0xff << 8-(sx0 & 7)) & -0x10000 >> (sx1 & 7);
if((sx0 & 7) == 0) b >>= 8;
x = (dx & 7) + (sx1 - sx0);
}else{
if((sx0 & 7) != 0)
b = *sp++ << 8 & 0xff << (-sx0 & 7);
else
b = 0;
x = (sx1 >> 3) - (sx0+7 >> 3);
while(--x >= 0){
b |= *sp++;
*dp++ |= b >> (dx & 7) + (-sx0 & 7);
b <<= 8;
}
if((sx1 & 7) != 0)
b |= *sp++ & -0x100 >> (sx1 & 7);
x = (dx & 7) + (-sx0 & 7) + (sx1 & 7);
}
for(; x > 0; x -= 8){
*dp++ |= b >> (dx & 7) + (-sx0 & 7);
b <<= 8;
}
sp += ss;
dp += ds;
}while(--y > 0);
}

322
sys/src/libttf/cmap.c Normal file
View file

@ -0,0 +1,322 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ttf.h>
#include "impl.h"
int
ttffindchar(TTFont *fx, Rune r)
{
int i, j, k, rv;
TTChMap *p;
TTFontU *f;
f = fx->u;
i = 0;
j = f->ncmap - 1;
if(r < f->cmap[0].start || r > f->cmap[j].end) return 0;
while(i < j){
k = (i + j) / 2;
if(f->cmap[k].end < r)
i = k+1;
else if(f->cmap[k].start > r)
j = k-1;
else
i = j = k;
}
if(i > j) return 0;
p = &f->cmap[i];
if(r < p->start || r > p->end) return 0;
if((p->flags & TTCINVALID) != 0) return 0;
if(p->tab != nil)
return p->tab[r - p->start];
rv = r + p->delta;
if((p->flags & TTCDELTA16) != 0)
rv = (u16int)rv;
return rv;
}
int
ttfenumchar(TTFont *fx, Rune r, Rune *rp)
{
int i, j, k, rv;
TTChMap *p;
TTFontU *f;
f = fx->u;
i = 0;
j = f->ncmap - 1;
if(r > f->cmap[j].end) return 0;
while(i < j){
k = (i + j) / 2;
if(f->cmap[k].end < r)
i = k+1;
else if(f->cmap[k].start > r)
j = k-1;
else
i = j = k;
}
if(j < 0) j = 0;
for(p = &f->cmap[j]; p < &f->cmap[f->ncmap]; p++){
if((p->flags & TTCINVALID) != 0)
continue;
if(r < p->start)
r = p->start;
if(p->tab != nil){
SET(rv);
while(r <= p->end && (rv = p->tab[r - p->start], rv == 0))
r++;
if(r > p->end)
continue;
if(rp != nil)
*rp = r;
return rv;
}
while(r < p->end){
rv = r + p->delta;
if((p->flags & TTCDELTA16) != 0)
rv = (u16int) rv;
if(rv != 0){
if(rp != nil)
*rp = r;
return rv;
}
}
}
return 0;
}
static int
ttfgotosub(TTFontU *f)
{
int i;
u16int nsub, id, sid, off;
int rank, maxrank;
u32int maxoff;
#define SUBID(a,b) ((a)<<16|(b))
if(ttfgototable(f, "cmap") < 0)
return -1;
ttfunpack(f, ".. w", &nsub);
maxrank = 0;
maxoff = 0;
for(i = 0; i < nsub; i++){
ttfunpack(f, "wwl", &id, &sid, &off);
switch(id << 16 | sid){
case SUBID(0, 4): /* Unicode 2.0 or later (BMP and non-BMP) */
rank = 100;
break;
case SUBID(0, 0): /* Unicode default */
case SUBID(0, 1): /* Unicode 1.1 */
case SUBID(0, 2): /* ISO 10646 */
case SUBID(0, 3): /* Unicode 2.0 (BMP) */
rank = 80;
break;
case SUBID(3, 10): /* Windows, UCS-4 */
rank = 60;
break;
case SUBID(3, 1): /* Windows, UCS-2 */
rank = 40;
break;
case SUBID(3, 0): /* Windows, Symbol */
rank = 20;
break;
default:
rank = 0;
break;
}
if(rank > maxrank){
maxrank = rank;
maxoff = off;
}
}
if(maxrank == 0){
werrstr("no suitable character table");
return -1;
}
if(ttfgototable(f, "cmap") < 0)
return -1;
Bseek(f->bin, maxoff, 1);
return 0;
}
static int
cmap0(TTFontU *f)
{
u16int len;
int i;
u8int *p;
int *q;
ttfunpack(f, "w2", &len);
if(len < 262){
werrstr("character table too short");
return -1;
}
f->cmap = mallocz(sizeof(TTChMap), 1);
if(f->cmap == nil)
return -1;
f->ncmap = 1;
f->cmap->start = 0;
f->cmap->end = 0xff;
f->cmap->tab = mallocz(256 * sizeof(int), 1);
if(f->cmap->tab == nil)
return -1;
Bread(f->bin, f->cmap->tab, 256 * sizeof(int));
p = (u8int*)f->cmap->tab + 256;
q = f->cmap->tab + 256;
for(i = 255; i >= 0; i--)
*--q = *--p;
return 0;
}
static int
cmap4(TTFontU *f)
{
u16int len, ncmap;
int i, j, n, n0, off;
u16int v;
u8int *buf;
ttfunpack(f, "w2", &len);
if(len < 16){
werrstr("character table too short");
return -1;
}
ttfunpack(f, "w6", &ncmap);
ncmap /= 2;
if(len < 16 + 8 * ncmap){
werrstr("character table too short");
return -1;
}
f->cmap = mallocz(sizeof(TTChMap) * ncmap, 1);
if(f->cmap == nil) return -1;
f->ncmap = ncmap;
for(i = 0; i < ncmap; i++)
f->cmap[i].flags = TTCDELTA16;
for(i = 0; i < ncmap; i++)
ttfunpack(f, "W", &f->cmap[i].end);
ttfunpack(f, "..");
for(i = 0; i < ncmap; i++)
ttfunpack(f, "W", &f->cmap[i].start);
for(i = 0; i < ncmap; i++)
ttfunpack(f, "W", &f->cmap[i].delta);
for(i = 0; i < ncmap; i++)
ttfunpack(f, "W", &f->cmap[i].temp);
len -= 10 + 8 * ncmap;
buf = malloc(len);
if(buf == nil)
return -1;
Bread(f->bin, buf, len);
for(i = 0; i < ncmap; i++){
if(f->cmap[i].temp == 0) continue;
n0 = f->cmap[i].end - f->cmap[i].start + 1;
n = n0;
off = f->cmap[i].temp - (ncmap - i) * 2;
if(off + 2 * n > len) n = (len - off) / 2;
if(off < 0 || n <= 0){
f->cmap[i].flags |= TTCINVALID;
continue;
}
f->cmap[i].tab = mallocz(n0 * sizeof(int), 1);
if(f->cmap[i].tab == nil)
return -1;
for(j = 0; j < n; j++){
v = buf[off + 2*j] << 8 | buf[off + 2*j + 1];
if(v != 0) v += f->cmap[i].delta;
f->cmap[i].tab[j] = v;
}
}
free(buf);
return 0;
}
static int
cmap6(TTFontU *f)
{
u16int len, first, cnt, v;
int *p;
u8int *q;
ttfunpack(f, "w2", &len);
if(len < 12){
werrstr("character table too short");
return -1;
}
ttfunpack(f, "ww", &first, &cnt);
f->cmap = mallocz(sizeof(TTChMap), 1);
if(f->cmap == nil)
return -1;
f->ncmap = 1;
f->cmap->start = first;
f->cmap->end = first + len - 1;
f->cmap->tab = mallocz(cnt * sizeof(int), 1);
if(f->cmap->tab == nil)
return -1;
if(len < 10 + 2 * cnt){
werrstr("character table too short");
return -1;
}
Bread(f->bin, f->cmap->tab, 2 * cnt);
p = f->cmap->tab + cnt;
q = (u8int*) f->cmap->tab + 2 * cnt;
while(p > f->cmap->tab){
v = *--q;
v |= *--q << 8;
*--p = v;
}
return 0;
}
static int
cmap12(TTFontU *f)
{
u32int len;
u32int ncmap;
int i;
ttfunpack(f, "2l4", &len);
if(len < 16){
werrstr("character table too short");
return -1;
}
ttfunpack(f, "l", &ncmap);
if(len < 16 + 12 * ncmap){
werrstr("character table too short");
return -1;
}
f->cmap = mallocz(sizeof(TTChMap) * ncmap, 1);
if(f->cmap == nil)
return -1;
f->ncmap = ncmap;
for(i = 0; i < ncmap; i++){
ttfunpack(f, "lll", &f->cmap[i].start, &f->cmap[i].end, &f->cmap[i].delta);
f->cmap[i].delta -= f->cmap[i].start;
}
return 0;
}
int (*cmaphand[])(TTFontU *) = {
[0] cmap0,
[4] cmap4,
[6] cmap6,
[12] cmap12,
};
int
ttfparsecmap(TTFontU *f)
{
u16int format;
if(ttfgotosub(f) < 0)
return -1;
ttfunpack(f, "w", &format);
if(format >= nelem(cmaphand) || cmaphand[format] == nil){
werrstr("character table in unknown format %d", format);
return -1;
}
if(cmaphand[format](f) < 0)
return -1;
return 0;
}

371
sys/src/libttf/glyf.c Normal file
View file

@ -0,0 +1,371 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <draw.h>
#include <ctype.h>
#include <ttf.h>
#include "impl.h"
void
ttfputglyph(TTGlyph *g)
{
if(g == nil) return;
free(g->pt);
free(g->ptorg);
free(g->confst);
free(g->bit);
free(g->hint);
free(g);
}
static void
glyphscale(TTGlyph *g)
{
TTFont *f;
int i;
TTPoint *p;
f = g->font;
for(i = 0; i < g->npt; i++){
p = &g->pt[i];
p->x = ttfrounddiv(p->x * f->ppem * 64, f->u->emsize);
p->y = ttfrounddiv(p->y * f->ppem * 64, f->u->emsize);
}
memmove(g->ptorg, g->pt, sizeof(TTPoint) * g->npt);
g->pt[g->npt - 1].x = g->pt[g->npt - 1].x + 32 & -64;
}
static TTGlyph *
emptyglyph(TTFont *fs, int glyph, int render)
{
TTGlyph *g;
g = mallocz(sizeof(TTGlyph), 1);
if(g == nil)
return nil;
g->font = fs;
g->info = &fs->u->ginfo[glyph];
g->confst = malloc(sizeof(int));
g->npt = 2;
g->pt = mallocz(sizeof(TTPoint) * 2, 1);
g->ptorg = mallocz(sizeof(TTPoint) * 2, 1);
if(g->confst == nil || g->pt == nil || g->ptorg == nil){
ttfputglyph(g);
return nil;
}
g->pt[1].x = g->info->advanceWidth;
g->npt = 2;
if(render)
glyphscale(g);
g->xmin = 0;
g->ymin = 0;
g->xmax = g->info->advanceWidth;
g->ymax = 1;
if(render){
g->xminpx = 0;
g->xmaxpx = (g->xmax * fs->ppem + fs->u->emsize - 1) / fs->u->emsize;
g->yminpx = 0;
g->ymaxpx = 1;
}
return g;
}
static TTGlyph *
simpglyph(TTFont *fs, int glyph, int nc, int render)
{
u16int np;
short x;
u16int len;
u16int temp16;
u8int temp8;
u8int *flags, *fp, *fq;
TTPoint *p;
int i, j, r;
short lastx, lasty;
TTFontU *f;
TTGlyph *g;
flags = nil;
f = fs->u;
g = mallocz(sizeof(TTGlyph), 1);
if(g == nil)
return nil;
g->font = fs;
g->info = &f->ginfo[glyph];
g->confst = malloc(sizeof(u16int) * (nc + 1));
if(g->confst == nil)
goto err;
x = -1;
for(i = g->ncon; i < nc; i++){
g->confst[i] = x + 1;
ttfunpack(f, "w", &x);
}
g->confst[i] = x + 1;
g->ncon = nc;
np = x + 1;
ttfunpack(f, "w", &len);
g->nhint = len;
g->hint = mallocz(len, 1);
if(g->hint == nil)
goto err;
Bread(f->bin, g->hint, len);
flags = mallocz(np, 1);
if(flags == nil)
goto err;
for(i = 0; i < np; i++){
j = Bgetc(f->bin);
flags[i] = j;
if((j & 8) != 0){
r = Bgetc(f->bin);
while(r-- > 0)
flags[++i] = j;
}
}
fp = flags;
fq = flags;
lastx = lasty = 0;
g->pt = malloc(sizeof(TTPoint) * (np + 2));
if(g->pt == nil)
goto err;
g->ptorg = malloc(sizeof(TTPoint) * (np + 2));
if(g->ptorg == nil)
goto err;
for(i = 0; i < np; i++){
p = &g->pt[g->npt + i];
p->flags = *fp & 1;
switch(*fp++ & 0x12){
case 0x00: ttfunpack(f, "w", &temp16); p->x = lastx += temp16; break;
case 0x02: ttfunpack(f, "b", &temp8); p->x = lastx -= temp8; break;
case 0x10: p->x = lastx; break;
case 0x12: ttfunpack(f, "b", &temp8); p->x = lastx += temp8; break;
}
}
for(i = 0; i < np; i++){
p = &g->pt[g->npt + i];
switch(*fq++ & 0x24){
case 0x00: ttfunpack(f, "w", &temp16); p->y = lasty += temp16; break;
case 0x04: ttfunpack(f, "b", &temp8); p->y = lasty -= temp8; break;
case 0x20: p->y = lasty; break;
case 0x24: ttfunpack(f, "b", &temp8); p->y = lasty += temp8; break;
}
}
g->pt[np] = (TTPoint){0,0,0};
g->pt[np+1] = (TTPoint){f->ginfo[glyph].advanceWidth,0,0};
g->npt = np + 2;
free(flags);
if(render){
glyphscale(g);
ttfhint(g);
}
return g;
err:
free(flags);
ttfputglyph(g);
return nil;
}
static TTGlyph *getglyph(TTFont *, int, int);
enum {
ARG_1_AND_2_ARE_WORDS = 1<<0,
ARGS_ARE_XY_VALUES = 1<<1,
ROUND_XY_TO_GRID = 1<<2,
WE_HAVE_A_SCALE = 1<<3,
MORE_COMPONENTS = 1<<5,
WE_HAVE_AN_X_AND_Y_SCALE = 1<<6,
WE_HAVE_A_TWO_BY_TWO = 1<<7,
WE_HAVE_INSTRUCTIONS = 1<<8,
USE_MY_METRICS = 1<<9,
OVERLAP_COMPOUND = 1<<10,
};
static int
mergeglyph(TTGlyph *g, TTGlyph *h, int flags, int x, int y, int a, int b, int c, int d, int render)
{
int i, m;
TTPoint *p;
TTFont *f;
int dx, dy;
f = g->font;
g->confst = realloc(g->confst, sizeof(int) * (g->ncon + h->ncon + 1));
for(i = 1; i <= h->ncon; i++)
g->confst[g->ncon + i] = g->confst[g->ncon] + h->confst[i];
g->ncon += h->ncon;
g->pt = realloc(g->pt, sizeof(TTPoint) * (g->npt + h->npt - 2));
if((flags & USE_MY_METRICS) == 0){
memmove(g->pt + g->npt + h->npt - 4, g->pt + g->npt - 2, 2 * sizeof(TTPoint));
m = h->npt - 2;
}else
m = h->npt;
for(i = 0; i < m; i++){
p = &g->pt[g->npt - 2 + i];
*p = h->pt[i];
dx = ttfrounddiv(p->x * a + p->y * b, 16384);
dy = ttfrounddiv(p->x * c + p->y * d, 16384);
p->x = dx;
p->y = dy;
if((flags & ARGS_ARE_XY_VALUES) != 0){
if(render){
dx = ttfrounddiv(x * f->ppem * 64, f->u->emsize);
dy = ttfrounddiv(y * f->ppem * 64, f->u->emsize);
if((flags & ROUND_XY_TO_GRID) != 0){
dx = dx + 32 & -64;
dy = dy + 32 & -64;
}
}
p->x += dx;
p->y += dy;
}else
abort();
}
g->npt += h->npt - 2;
return 0;
}
static TTGlyph *
compglyph(TTFont *fs, int glyph, int render)
{
u16int flags, idx;
int x, y;
int a, b, c, d;
TTFontU *f;
uvlong off;
TTGlyph *g, *h;
u16int len;
f = fs->u;
g = mallocz(sizeof(TTGlyph), 1);
if(g == nil)
return nil;
g->font = fs;
g->info = &f->ginfo[glyph];
g->pt = mallocz(sizeof(TTPoint) * 2, 1);
if(g->pt == nil){
err:
ttfputglyph(g);
return nil;
}
g->pt[1].x = ttfrounddiv(f->ginfo[glyph].advanceWidth * fs->ppem * 64, f->emsize);
g->npt = 2;
g->confst = mallocz(sizeof(int), 1);
if(g->confst == nil)
goto err;
do{
ttfunpack(f, "ww", &flags, &idx);
switch(flags & (ARG_1_AND_2_ARE_WORDS | ARGS_ARE_XY_VALUES)){
case 0: ttfunpack(f, "BB", &x, &y); break;
case ARGS_ARE_XY_VALUES: ttfunpack(f, "BB", &x, &y); x = (char)x; y = (char)y; break;
case ARG_1_AND_2_ARE_WORDS: ttfunpack(f, "WW", &x, &y); break;
case ARG_1_AND_2_ARE_WORDS | ARGS_ARE_XY_VALUES: ttfunpack(f, "WW", &x, &y); x = (short)x; y = (short)y; break;
}
if((flags & WE_HAVE_A_SCALE) != 0){
ttfunpack(f, "S", &a);
d = a;
b = c = 0;
}else if((flags & WE_HAVE_AN_X_AND_Y_SCALE) != 0){
ttfunpack(f, "SS", &a, &d);
b = c = 0;
}else if((flags & WE_HAVE_A_TWO_BY_TWO) != 0)
ttfunpack(f, "SSSS", &a, &b, &c, &d);
else{
a = d = 1<<14;
b = c = 0;
}
off = Bseek(f->bin, 0, 1);
h = getglyph(fs, idx, render);
if(h == nil){
ttfputglyph(g);
return nil;
}
if(mergeglyph(g, h, flags, x, y, a, b, c, d, render) < 0){
ttfputglyph(h);
ttfputglyph(g);
return nil;
}
ttfputglyph(h);
Bseek(f->bin, off, 0);
}while((flags & MORE_COMPONENTS) != 0);
g->ptorg = malloc(sizeof(TTPoint) * g->npt);
memmove(g->ptorg, g->pt, sizeof(TTPoint) * g->npt);
// g->pt[g->npt - 1].x = g->pt[g->npt - 1].x + 32 & -64;
if(render && (flags & WE_HAVE_INSTRUCTIONS) != 0){
ttfunpack(f, "w", &len);
g->nhint = len;
g->hint = mallocz(len, 1);
if(g->hint == nil)
goto err;
Bread(f->bin, g->hint, len);
ttfhint(g);
}
return g;
}
static TTGlyph *
getglyph(TTFont *fs, int glyph, int render)
{
int i;
short xmin, ymin, xmax, ymax, nc;
TTFontU *f;
TTGlyph *g;
f = fs->u;
if((uint)glyph >= f->numGlyphs){
werrstr("no such glyph %d", glyph);
return nil;
}
if(f->ginfo[glyph].loca == f->ginfo[glyph+1].loca){
return emptyglyph(fs, glyph, render);
}
if(ttfgototable(f, "glyf") < 0)
return nil;
Bseek(f->bin, f->ginfo[glyph].loca, 1);
ttfunpack(f, "wwwww", &nc, &xmin, &ymin, &xmax, &ymax);
if(nc < 0)
g = compglyph(fs, glyph, render);
else
g = simpglyph(fs, glyph, nc, render);
if(g == nil)
return nil;
g->xmin = g->pt[0].x;
g->xmax = g->pt[0].x;
g->ymin = g->pt[0].y;
g->ymax = g->pt[0].y;
for(i = 1; i < g->npt - 2; i++){
if(g->pt[i].x < g->xmin)
g->xmin = g->pt[i].x;
if(g->pt[i].x > g->xmax)
g->xmax = g->pt[i].x;
if(g->pt[i].y < g->ymin)
g->ymin = g->pt[i].y;
if(g->pt[i].y > g->ymax)
g->ymax = g->pt[i].y;
}
if(render){
g->xminpx = g->xmin >> 6;
g->xmaxpx = g->xmax + 63 >> 6;
g->yminpx = g->ymin >> 6;
g->ymaxpx = g->ymax + 63 >> 6;
}
return g;
}
TTGlyph *
ttfgetglyph(TTFont *fs, int glyph, int render)
{
TTGlyph *g;
g = getglyph(fs, glyph, render);
if(g == nil)
return nil;
g->idx = glyph;
if(render){
ttfscan(g);
g->advanceWidthpx = (g->pt[g->npt - 1].x - g->pt[g->npt - 2].x + 63) / 64;
}
setmalloctag(g, getcallerpc(&fs));
return g;
}

344
sys/src/libttf/head.c Normal file
View file

@ -0,0 +1,344 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <draw.h>
#include <ctype.h>
#include <ttf.h>
#include "impl.h"
void
ttfunpack(TTFontU *f, char *p, ...)
{
va_list va;
int n;
uchar *p1;
u16int *p2;
u32int *p4;
va_start(va, p);
for(; *p != 0; p++)
switch(*p){
case 'b':
p1 = va_arg(va, u8int *);
*p1 = Bgetc(f->bin);
break;
case 'B':
p4 = va_arg(va, u32int *);
*p4 = Bgetc(f->bin);
break;
case 'w':
p2 = va_arg(va, u16int *);
*p2 = Bgetc(f->bin) << 8;
*p2 |= Bgetc(f->bin);
break;
case 'W':
p4 = va_arg(va, u32int *);
*p4 = Bgetc(f->bin) << 8;
*p4 |= Bgetc(f->bin);
break;
case 'S':
p4 = va_arg(va, u32int *);
*p4 = (char)Bgetc(f->bin) << 8;
*p4 |= Bgetc(f->bin);
break;
case 'l':
p4 = va_arg(va, u32int *);
*p4 = Bgetc(f->bin) << 24;
*p4 |= Bgetc(f->bin) << 16;
*p4 |= Bgetc(f->bin) << 8;
*p4 |= Bgetc(f->bin);
break;
case '.': Bgetc(f->bin); break;
case ' ': break;
default:
if(isdigit(*p)){
n = strtol(p, &p, 10);
p--;
Bseek(f->bin, n, 1);
}else abort();
break;
}
}
static int
directory(TTFontU *f)
{
u32int scaler;
int i;
ttfunpack(f, "lw .. .. ..", &scaler, &f->ntab);
if(scaler != 0x74727565 && scaler != 0x10000){
werrstr("unknown scaler type %#ux", scaler);
return -1;
}
f->tab = mallocz(sizeof(TTTable) * f->ntab, 1);
if(f->tab == nil) return -1;
for(i = 0; i < f->ntab; i++)
ttfunpack(f, "llll", &f->tab[i].tag, &f->tab[i].csum, &f->tab[i].offset, &f->tab[i].len);
return 0;
}
int
ttfgototable(TTFontU *f, char *str)
{
TTTable *t;
u32int tag;
tag = (u8int)str[0] << 24 | (u8int)str[1] << 16 | (u8int)str[2] << 8 | (u8int)str[3];
for(t = f->tab; t < f->tab + f->ntab; t++)
if(t->tag == tag){
Bseek(f->bin, t->offset, 0);
return t->len;
}
werrstr("no such table '%s'", str);
return -1;
}
static int
ttfparseloca(TTFontU *f)
{
int len, i;
u32int x;
len = ttfgototable(f, "loca");
if(len < 0) return -1;
x = 0;
if(f->longloca){
if(len > (f->numGlyphs + 1) * 4) len = (f->numGlyphs + 1) * 4;
for(i = 0; i < len/4; i++){
x = Bgetc(f->bin) << 24;
x |= Bgetc(f->bin) << 16;
x |= Bgetc(f->bin) << 8;
x |= Bgetc(f->bin);
f->ginfo[i].loca = x;
}
}else{
if(len > (f->numGlyphs + 1) * 2) len = (f->numGlyphs + 1) * 2;
for(i = 0; i < len/2; i++){
x = Bgetc(f->bin) << 8;
x |= Bgetc(f->bin);
f->ginfo[i].loca = x * 2;
}
}
for(; i < f->numGlyphs; i++)
f->ginfo[i].loca = x;
return 0;
}
static int
ttfparsehmtx(TTFontU *f)
{
int i;
u16int x, y;
int len;
int maxlsb;
len = ttfgototable(f, "hmtx");
if(len < 0)
return -1;
if(f->numOfLongHorMetrics > f->numGlyphs){
werrstr("nonsensical header: numOfLongHorMetrics > numGlyphs");
return -1;
}
for(i = 0; i < f->numOfLongHorMetrics; i++){
ttfunpack(f, "ww", &x, &y);
f->ginfo[i].advanceWidth = x;
f->ginfo[i].lsb = y;
}
maxlsb = (len - 2 * f->numOfLongHorMetrics) / 2;
if(maxlsb > f->numGlyphs){
werrstr("nonsensical header: maxlsb > f->numGlyphs");
return -1;
}
for(; i < maxlsb; i++){
ttfunpack(f, "w", &y);
f->ginfo[i].advanceWidth = x;
f->ginfo[i].lsb = y;
}
for(; i < f->numGlyphs; i++){
f->ginfo[i].advanceWidth = x;
f->ginfo[i].lsb = y;
}
return 0;
}
int
ttfparsecvt(TTFontU *f)
{
int len;
int i;
int x;
u8int *p;
short *w;
len = ttfgototable(f, "cvt ");
if(len <= 0) return 0;
f->cvtu = mallocz(len, 1);
if(f->cvtu == 0) return -1;
Bread(f->bin, f->cvtu, len);
p = (u8int *) f->cvtu;
f->ncvtu = len / 2;
w = f->cvtu;
for(i = 0; i < f->ncvtu; i++){
x = (short)(p[0] << 8 | p[1]);
p += 2;
*w++ = x;
}
return 0;
}
static int
ttfparseos2(TTFontU *f)
{
int len;
u16int usWinAscent, usWinDescent;
len = ttfgototable(f, "OS/2 ");
if(len < 0)
return -1;
if(len < 78){
werrstr("OS/2 table too short");
return -1;
}
ttfunpack(f, "68 6 ww", &usWinAscent, &usWinDescent);
f->ascent = usWinAscent;
f->descent = usWinDescent;
return 0;
}
static void
ttfcloseu(TTFontU *u)
{
int i;
if(u == nil) return;
Bterm(u->bin);
for(i = 0; i < u->ncmap; i++)
free(u->cmap[i].tab);
free(u->cmap);
free(u->ginfo);
free(u->tab);
free(u->cvtu);
free(u);
}
void
ttfclose(TTFont *f)
{
int i;
if(f == nil) return;
if(--f->u->ref <= 0)
ttfcloseu(f->u);
for(i = 0; i < f->u->maxFunctionDefs; i++)
free(f->func[i].pgm);
free(f->hintstack);
free(f->func);
free(f->storage);
free(f->twilight);
free(f->twiorg);
free(f->cvt);
free(f);
}
static TTFont *
ttfscaleu(TTFontU *u, int ppem)
{
TTFont *f;
int i;
f = mallocz(sizeof(TTFont), 1);
if(f == nil) return nil;
f->u = u;
u->ref++;
f->ppem = ppem;
f->ncvt = u->ncvtu;
f->cvt = malloc(sizeof(int) * u->ncvtu);
if(f->cvt == nil) goto error;
for(i = 0; i < u->ncvtu; i++)
f->cvt[i] = ttfrounddiv(u->cvtu[i] * ppem * 64, u->emsize);
f->hintstack = mallocz(sizeof(u32int) * u->maxStackElements, 1);
f->func = mallocz(sizeof(TTFunction) * u->maxFunctionDefs, 1);
f->storage = mallocz(sizeof(u32int) * u->maxStorage, 1);
f->twilight = mallocz(sizeof(TTPoint) * u->maxTwilightPoints, 1);
f->twiorg = mallocz(sizeof(TTPoint) * u->maxTwilightPoints, 1);
if(f->hintstack == nil || f->func == nil || f->storage == nil || f->twilight == nil || f->twiorg == nil) goto error;
f->ascentpx = (u->ascent * ppem + u->emsize - 1) / (u->emsize);
f->descentpx = (u->descent * ppem + u->emsize - 1) / (u->emsize);
if(ttfrunfpgm(f) < 0) goto error;
if(ttfruncvt(f) < 0) goto error;
return f;
error:
ttfclose(f);
return nil;
}
TTFont *
ttfopen(char *name, int ppem, int)
{
Biobuf *b;
TTFontU *u;
if(ppem < 0){
werrstr("invalid ppem argument");
return nil;
}
b = Bopen(name, OREAD);
if(b == nil)
return nil;
u = mallocz(sizeof(TTFontU), 1);
if(u == nil)
return nil;
u->bin = b;
u->nkern = -1;
directory(u);
if(ttfgototable(u, "head") < 0) goto error;
ttfunpack(u, "16 w W 16 wwww 6 w", &u->flags, &u->emsize, &u->xmin, &u->ymin, &u->xmax, &u->ymax, &u->longloca);
if(ttfgototable(u, "maxp") < 0) goto error;
ttfunpack(u, "4 wwwwwwwwwwwwww",
&u->numGlyphs, &u->maxPoints, &u->maxCountours, &u->maxComponentPoints, &u->maxComponentCountours,
&u->maxZones, &u->maxTwilightPoints, &u->maxStorage, &u->maxFunctionDefs, &u->maxInstructionDefs,
&u->maxStackElements, &u->maxSizeOfInstructions, &u->maxComponentElements, &u->maxComponentDepth);
u->ginfo = mallocz(sizeof(TTGlyphInfo) * (u->numGlyphs + 1), 1);
if(u->ginfo == nil)
goto error;
if(ttfgototable(u, "hhea") < 0) goto error;
ttfunpack(u, "10 wwww 16 w", &u->advanceWidthMax, &u->minLeftSideBearing, &u->minRightSideBearing, &u->xMaxExtent, &u->numOfLongHorMetrics);
if(ttfparseloca(u) < 0) goto error;
if(ttfparsehmtx(u) < 0) goto error;
if(ttfparsecvt(u) < 0) goto error;
if(ttfparsecmap(u) < 0) goto error;
if(ttfparseos2(u) < 0) goto error;
return ttfscaleu(u, ppem);
error:
ttfcloseu(u);
return nil;
}
TTFont *
ttfscale(TTFont *f, int ppem, int)
{
return ttfscaleu(f->u, ppem);
}
int
ttfrounddiv(int a, int b)
{
if(b < 0){ a = -a; b = -b; }
if(a > 0)
return (a + b/2) / b;
else
return (a - b/2) / b;
}
int
ttfvrounddiv(vlong a, int b)
{
if(b < 0){ a = -a; b = -b; }
if(a > 0)
return (a + b/2) / b;
else
return (a - b/2) / b;
}

1668
sys/src/libttf/hint.c Normal file

File diff suppressed because it is too large Load diff

23
sys/src/libttf/mkfile Normal file
View file

@ -0,0 +1,23 @@
</$objtype/mkfile
LIB=/$objtype/lib/libttf.a
OFILES=\
head.$O \
cmap.$O \
hint.$O \
scan.$O \
glyf.$O \
render.$O \
bit.$O \
HFILES=\
/sys/include/ttf.h\
impl.h\
UPDATE=\
mkfile\
$HFILES\
${OFILES:%.$O=%.c}\
</sys/src/cmd/mksyslib

274
sys/src/libttf/render.c Normal file
View file

@ -0,0 +1,274 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ttf.h>
#include "impl.h"
static int
ttfparsekern(TTFontU *f)
{
u16int ver, len, cov, ntab;
int i;
if(ttfgototable(f, "kern") < 0)
return -1;
ttfunpack(f, "ww", &ver, &ntab);
if(ver != 0)
return -1;
if(ntab == 0)
return -1;
for(i = 0; i < ntab; i++){
ttfunpack(f, "www", &ver, &len, &cov);
if((cov & 1) != 0) break;
Bseek(f->bin, len - 6, 1);
}
ttfunpack(f, "w6", &len);
f->nkern = len;
f->kern = mallocz(sizeof(TTKern) * len, 1);
for(i = 0; i < len; i++)
ttfunpack(f, "lS", &f->kern[i].idx, &f->kern[i].val);
return 0;
}
static int
ttfkern(TTFont *f, int l, int r)
{
u32int idx;
int a, b, c;
TTFontU *u;
u = f->u;
if(u->nkern == 0)
return 0;
idx = l << 16 | r;
a = 0;
b = u->nkern - 1;
if(u->kern[a].idx > idx || u->kern[b].idx < idx)
return 0;
while(a <= b){
c = (a + b) / 2;
if(u->kern[c].idx < idx){
a = c + 1;
}else if(u->kern[c].idx > idx){
b = c - 1;
}else
return ttfrounddiv(u->kern[c].val * f->ppem, u->emsize);
}
return 0;
}
typedef struct {
TTBitmap *b;
TTFont *font;
TTGlyph **glyph;
int *gwidth;
char **cpos;
char *pp;
int nglyph, aglyph;
int linew;
int adj;
int nspc;
int oy, lh;
int spcidx;
int flags;
int spcw;
int spcminus;
} Render;
static int
addglyph(Render *r, char *p, TTGlyph *g)
{
void *v;
int k;
if(r->nglyph >= r->aglyph){
r->aglyph += 32;
v = realloc(r->glyph, sizeof(TTGlyph *) * r->aglyph);
if(v == nil) return -1;
r->glyph = v;
v = realloc(r->gwidth, sizeof(int) * r->aglyph);
if(v == nil) return -1;
r->gwidth = v;
v = realloc(r->cpos, sizeof(char *) * r->aglyph);
if(v == nil) return -1;
r->cpos = v;
}
r->glyph[r->nglyph] = g;
r->cpos[r->nglyph] = p;
r->gwidth[r->nglyph] = g->advanceWidthpx;
if(r->nglyph > 0){
k = ttfkern(r->font, r->glyph[r->nglyph-1]->idx, g->idx);
r->gwidth[r->nglyph-1] += k;
r->linew += k;
}
r->nglyph++;
r->linew += r->gwidth[r->nglyph-1];
if(g->idx == r->spcidx)
r->nspc++;
return 0;
}
static void
flushglyphs(Render *r, int justify)
{
int i, n, k, x, y;
int llen;
int adj, spcw, nspc, c;
TTFont *f;
f = r->font;
if((r->flags & TTFMODE) == TTFLALIGN && !justify)
while(r->nglyph > 0 && r->glyph[r->nglyph - 1]->idx == r->spcidx){
r->linew -= r->gwidth[--r->nglyph];
r->nspc--;
}
llen = r->linew;
k = n = r->nglyph;
nspc = r->nspc;
adj = (nspc * r->spcminus + 63) / 64;
if(r->linew - adj > r->b->width){
n = r->nglyph;
while(n > 0 && r->glyph[n - 1]->idx != r->spcidx)
llen -= r->gwidth[--n];
k = n;
while(n > 0 && r->glyph[n - 1]->idx == r->spcidx){
llen -= r->gwidth[--n];
nspc--;
}
if(n == 0){
while(n < r->nglyph && llen + r->gwidth[n] < r->b->width)
llen += r->gwidth[n++];
k = n;
}
}
if(justify){
if(nspc == 0)
spcw = 0;
else
spcw = (r->b->width - llen + nspc * r->spcw) * 64 / nspc;
}else
spcw = r->spcw * 64;
switch(r->flags & TTFMODE | justify * TTFJUSTIFY){
case TTFRALIGN:
x = r->b->width - llen;
break;
case TTFCENTER:
x = (r->b->width - llen)/2;
break;
default:
x = 0;
}
y = r->oy + f->ascentpx;
c = 0;
for(i = 0; i < k; i++){
if(r->glyph[i]->idx == r->spcidx){
c += spcw;
x += c >> 6;
c &= 63;
r->nspc--;
}else{
ttfblit(r->b, x + r->glyph[i]->xminpx, y - r->glyph[i]->ymaxpx, r->glyph[i], 0, 0, r->glyph[i]->width, r->glyph[i]->height);
x += r->gwidth[i];
}
r->linew -= r->gwidth[i];
ttfputglyph(r->glyph[i]);
}
if(n > 0)
r->pp = r->cpos[n-1];
r->oy += r->lh;
memmove(r->glyph, r->glyph + k, (r->nglyph - k) * sizeof(TTGlyph *));
memmove(r->cpos, r->cpos + k, (r->nglyph - k) * sizeof(char *));
memmove(r->gwidth, r->gwidth + k, (r->nglyph - k) * sizeof(int));
r->nglyph -= k;
}
TTBitmap *
_ttfrender(TTFont *f, int (*getrune)(Rune *, char *), char *p, char *end, int w, int h, int flags, char **rp)
{
Render r;
Rune ch;
int i, adj;
TTGlyph *g;
if(rp != nil) *rp = p;
if(f->u->nkern < 0 && ttfparsekern(f->u) < 0)
f->u->nkern = 0;
memset(&r, 0, sizeof(Render));
r.flags = flags;
r.font = f;
r.b = ttfnewbitmap(w, h);
if(r.b == nil) goto error;
r.oy = 0;
r.lh = f->ascentpx + f->descentpx;
r.pp = p;
g = ttfgetglyph(f, ttffindchar(f, ' '), 1);
r.spcidx = g->idx;
r.spcw = g->advanceWidthpx;
if((flags & TTFJUSTIFY) != 0)
r.spcminus = r.spcw * 21;
else
r.spcminus = 0;
while(p < end && r.oy + r.lh < h){
p += getrune(&ch, p);
if(ch == '\n'){
flushglyphs(&r, 0);
continue;
}
g = ttfgetglyph(f, ttffindchar(f, ch), 1);
if(g == nil){
g = ttfgetglyph(f, 0, 1);
if(g == nil)
continue;
}
if(addglyph(&r, p, g) < 0)
goto error;
adj = (r.nspc * r.spcminus + 63) / 64;
if(r.linew - adj > r.b->width){
flushglyphs(&r, (flags & TTFJUSTIFY) != 0);
}
}
if(r.oy + r.lh < h)
flushglyphs(&r, 0);
for(i = 0; i < r.nglyph; i++)
ttfputglyph(r.glyph[i]);
free(r.glyph);
free(r.gwidth);
free(r.cpos);
if(rp != nil)
*rp = r.pp;
return r.b;
error:
ttffreebitmap(r.b);
free(r.glyph);
free(r.gwidth);
return nil;
}
TTBitmap *
ttfrender(TTFont *f, char *str, char *end, int w, int h, int flags, char **rstr)
{
if(str == nil)
end = nil;
else if(end == nil)
end = str + strlen(str);
return _ttfrender(f, chartorune, str, end, w, h, flags, rstr);
}
static int
incrune(Rune *r, char *s)
{
*r = *(Rune*)s;
return sizeof(Rune);
}
TTBitmap *
ttfrunerender(TTFont *f, Rune *str, Rune *end, int w, int h, int flags, Rune **rstr)
{
if(str == nil)
end = nil;
else if(end == nil)
end = str + runestrlen(str);
return _ttfrender(f, incrune, (char *) str, (char *) end, w, h, flags, (char **) rstr);
}

478
sys/src/libttf/scan.c Normal file
View file

@ -0,0 +1,478 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ttf.h>
#include "impl.h"
typedef struct Scan Scan;
typedef struct TTLine TTLine;
enum {
LINEBLOCK = 32,
PTBLOCK = 64,
};
struct TTLine {
int x0, y0;
int x1, y1;
int link;
u8int dir;
};
struct Scan {
enum {
DROPOUTS = 1,
STUBDET = 2,
SMART = 4,
} flags;
TTGlyph *g;
TTLine *lines;
int nlines;
int *hpts, *vpts;
int nhpts, nvpts;
int *hscanl, *vscanl;
u8int *bit;
int width, height;
int stride;
};
static void
dobezier(Scan *s, TTPoint p, TTPoint q, TTPoint r)
{
vlong m, n;
TTLine *l;
m = (vlong)(q.x - p.x) * (r.y - p.y) - (vlong)(q.y - p.y) * (r.x - p.x);
n = (vlong)(r.x - p.x) * (r.x - p.x) + (vlong)(r.y - p.y) * (r.y - p.y);
if(m * m > 4 * n){
dobezier(s, p, (TTPoint){(p.x+q.x+1)/2, (p.y+q.y+1)/2, 0}, (TTPoint){(p.x+2*q.x+r.x+2)/4, (p.y+2*q.y+r.y+2)/4, 0});
dobezier(s, (TTPoint){(p.x+2*q.x+r.x+2)/4, (p.y+2*q.y+r.y+2)/4, 0}, (TTPoint){(r.x+q.x+1)/2, (r.y+q.y+1)/2, 0}, r);
return;
}
if((s->nlines & LINEBLOCK - 1) == 0)
s->lines = realloc(s->lines, sizeof(TTLine) * (s->nlines + LINEBLOCK));
l = &s->lines[s->nlines++];
if(p.y < r.y){
l->x0 = p.x;
l->y0 = p.y;
l->x1 = r.x;
l->y1 = r.y;
l->dir = 0;
}else{
l->x0 = r.x;
l->y0 = r.y;
l->x1 = p.x;
l->y1 = p.y;
l->dir = 1;
}
l->link = -1;
}
static int
hlinecmp(void *va, void *vb)
{
TTLine *a, *b;
a = va;
b = vb;
if(a->y0 < b->y0) return -1;
if(a->y0 > b->y0) return 1;
return 0;
}
static int
vlinecmp(void *va, void *vb)
{
TTLine *a, *b;
a = va;
b = vb;
if(a->x0 < b->x0) return -1;
if(a->x0 > b->x0) return 1;
return 0;
}
static int
intcmp(void *va, void *vb)
{
int a, b;
a = *(int*)va;
b = *(int*)vb;
return (a>b) - (a<b);
}
static void
hprep(Scan *s)
{
int i, j, x, y;
TTLine *l;
int watch, act, *p;
qsort(s->lines, s->nlines, sizeof(TTLine), hlinecmp);
s->hscanl = calloc(sizeof(int), (s->height + 1));
act = -1;
watch = 0;
p = &act;
for(i = 0; i < s->height; i++){
y = 64 * i + 32;
for(; watch < s->nlines && s->lines[watch].y0 <= y; watch++){
if(s->lines[watch].y1 <= y || s->lines[watch].y0 == s->lines[watch].y1)
continue;
s->lines[watch].link = -1;
*p = watch;
p = &s->lines[watch].link;
}
s->hscanl[i] = s->nhpts;
p = &act;
while(j = *p, j >= 0){
l = &s->lines[j];
if(l->y1 <= y){
j = l->link;
l->link = -1;
*p = j;
continue;
}
x = l->x0 + ttfvrounddiv((vlong)(y - l->y0)*(l->x1 - l->x0), l->y1 - l->y0);
if((s->nhpts & PTBLOCK - 1) == 0)
s->hpts = realloc(s->hpts, (s->nhpts + PTBLOCK) * sizeof(int));
s->hpts[s->nhpts++] = x << 1 | l->dir;
p = &l->link;
}
qsort(s->hpts + s->hscanl[i], s->nhpts - s->hscanl[i], sizeof(int), intcmp);
}
s->hscanl[i] = s->nhpts;
}
static int
iswhite(Scan *s, int x, int y)
{
return (s->bit[(s->height - 1 - y) * s->stride + (x>>3)] >> 7-(x&7) & 1)==0;
}
static void
pixel(Scan *s, int x, int y)
{
assert(x >= 0 && x < s->width && y >= 0 && y < s->height);
s->bit[(s->height - 1 - y) * s->stride + (x>>3)] |= (1<<7-(x&7));
}
static int
intersectsh(Scan *s, int x, int y)
{
int a, b, c, vc, v;
a = s->hscanl[y];
b = s->hscanl[y+1]-1;
v = x * 64 + 32;
if(a > b || s->hpts[a]>>1 > v + 64 || s->hpts[b]>>1 < v) return 0;
while(a <= b){
c = (a + b) / 2;
vc = s->hpts[c]>>1;
if(vc < v)
a = c + 1;
else if(vc > v + 64)
b = c - 1;
else
return 1;
}
return 0;
}
static int
intersectsv(Scan *s, int x, int y)
{
int a, b, c, vc, v;
a = s->vscanl[x];
b = s->vscanl[x+1]-1;
v = y * 64 + 32;
if(a > b || s->vpts[a]>>1 > v + 64 || s->vpts[b]>>1 < v) return 0;
while(a <= b){
c = (a + b) / 2;
vc = s->vpts[c]>>1;
if(vc < v)
a = c + 1;
else if(vc > v + 64)
b = c - 1;
else
return 1;
}
return 0;
}
static void
hscan(Scan *s)
{
int i, j, k, e;
int wind, match, seen, x;
for(i = 0; i < s->height; i++){
e = s->hscanl[i+1];
k = s->hscanl[i];
if(k == e) continue;
wind = 0;
for(j = 0; j < s->width; j++){
x = 64 * j + 32;
match = 0;
seen = 0;
while(k < e && (s->hpts[k] >> 1) <= x){
wind += (s->hpts[k] & 1) * 2 - 1;
seen |= 1<<(s->hpts[k] & 1);
if((s->hpts[k] >> 1) == x)
match++;
k++;
}
if(match || wind)
pixel(s, j, i);
else if((s->flags & DROPOUTS) != 0 && seen == 3 && j > 0 && iswhite(s, j-1, i)){
if((s->flags & STUBDET) == 0){
pixel(s, j-1, i);
continue;
}
if(i <= 0 || i > s->height - 1 || j <= 0 || j > s->width - 1)
continue;
if(!intersectsv(s, j-1, i-1) && !intersectsh(s, j-1, i-1) && !intersectsv(s, j, i-1) || !intersectsv(s, j-1, i) && !intersectsh(s, j-1, i+1) && !intersectsv(s, j, i))
continue;
pixel(s, j-1, i);
}
}
}
}
static void
vprep(Scan *s)
{
int i, j, x, y;
TTLine *l;
int watch, act, *p;
for(i = 0; i < s->nlines; i++){
l = &s->lines[i];
if(l->x0 > l->x1){
x = l->x0, l->x0 = l->x1, l->x1 = x;
x = l->y0, l->y0 = l->y1, l->y1 = x;
l->dir ^= 1;
}
}
qsort(s->lines, s->nlines, sizeof(TTLine), vlinecmp);
s->vscanl = calloc(sizeof(int), (s->width + 1));
act = -1;
watch = 0;
p = &act;
for(i = 0; i < s->width; i++){
x = 64 * i + 32;
for(; watch < s->nlines && s->lines[watch].x0 <= x; watch++){
if(s->lines[watch].x1 <= x || s->lines[watch].x0 == s->lines[watch].x1)
continue;
s->lines[watch].link = -1;
*p = watch;
p = &s->lines[watch].link;
}
s->vscanl[i] = s->nvpts;
p = &act;
while(j = *p, j >= 0){
l = &s->lines[j];
if(l->x1 <= x){
j = l->link;
l->link = -1;
*p = j;
continue;
}
y = l->y0 + ttfvrounddiv((vlong)(x - l->x0) * (l->y1 - l->y0), l->x1 - l->x0);
if((s->nvpts & PTBLOCK - 1) == 0)
s->vpts = realloc(s->vpts, (s->nvpts + PTBLOCK) * sizeof(int));
s->vpts[s->nvpts++] = y << 1 | l->dir;
p = &l->link;
}
qsort(s->vpts + s->vscanl[i], s->nvpts - s->vscanl[i], sizeof(int), intcmp);
}
s->vscanl[i] = s->nvpts;
}
static void
vscan(Scan *s)
{
int i, j, k, e;
int seen, y;
for(i = 0; i < s->width; i++){
e = s->vscanl[i+1];
k = s->vscanl[i];
if(k == e) continue;
for(j = 0; j < s->height; j++){
y = 64 * j + 32;
seen = 0;
while(k < e && (s->vpts[k] >> 1) <= y){
seen |= 1<<(s->vpts[k] & 1);
k++;
}
if(seen == 3 && j > 0 && iswhite(s, i, j-1) && iswhite(s, i, j)){
if((s->flags & STUBDET) == 0){
pixel(s, j-1, i);
continue;
}
if(i <= 0 || i > s->width - 1 || j <= 0 || j > s->height - 1)
continue;
if(!intersectsv(s, i-1, j-1) & !intersectsh(s, i-1, j-1) & !intersectsh(s, i-1, j) | !intersectsv(s, i+1, j-1) & !intersectsh(s, i, j-1) & !intersectsh(s, i, j))
continue;
pixel(s, i, j-1);
}
}
}
}
void
ttfscan(TTGlyph *g)
{
int i, j, c;
TTPoint p, q, r;
Scan s;
memset(&s, 0, sizeof(s));
s.g = g;
s.flags = 0;
c = g->font->scanctrl;
if((c & 1<<8) != 0 && g->font->ppem <= (c & 0xff))
s.flags |= DROPOUTS;
if((c & 1<<11) != 0 && g->font->ppem > (c & 0xff))
s.flags &= ~DROPOUTS;
if((c & 3<<12) != 0)
s.flags &= ~DROPOUTS;
if((s.flags & DROPOUTS) != 0)
switch(g->font->scantype){
case 0: break;
case 1: s.flags |= STUBDET; break;
case 2: case 3: case 6: case 7: s.flags &= ~DROPOUTS; break;
case 4: s.flags |= SMART; break;
case 5: s.flags |= SMART | STUBDET; break;
}
// s.width = (g->pt[g->npt - 1].x + 63) / 64;
// s.height = g->font->ascentpx + g->font->descentpx;
s.width = -g->xminpx + g->xmaxpx;
s.height = -g->yminpx + g->ymaxpx;
s.stride = s.width + 7 >> 3;
s.bit = mallocz(s.height * s.stride, 1);
assert(s.bit != nil);
for(i = 0; i < g->npt; i++){
g->pt[i].x -= g->xminpx * 64;
g->pt[i].y -= g->yminpx * 64;
// g->pt[i].y += g->font->descentpx * 64;
}
for(i = 0; i < g->ncon; i++){
if(g->confst[i] + 1 >= g->confst[i+1]) continue;
p = g->pt[g->confst[i]];
assert((p.flags & 1) != 0);
for(j = g->confst[i]; j++ < g->confst[i+1]; ){
if(j < g->confst[i+1] && (g->pt[j].flags & 1) == 0)
q = g->pt[j++];
else
q = p;
if(j >= g->confst[i+1])
r = g->pt[g->confst[i]];
else{
r = g->pt[j];
if((g->pt[j].flags & 1) == 0){
r.x = (r.x + q.x) / 2;
r.y = (r.y + q.y) / 2;
}
}
dobezier(&s, p, q, r);
p = r;
if(j < g->confst[i+1] && (g->pt[j].flags & 1) == 0)
j--;
}
}
hprep(&s);
if((s.flags & DROPOUTS) != 0)
vprep(&s);
hscan(&s);
if((s.flags & DROPOUTS) != 0)
vscan(&s);
free(s.hpts);
free(s.vpts);
free(s.hscanl);
free(s.vscanl);
free(s.lines);
g->bit = s.bit;
g->width = s.width;
g->height = s.height;
g->stride = s.stride;
}
int
ttfgetcontour(TTGlyph *g, int i, float **fp, int *np)
{
float offx, offy, scale;
float *nf;
int n, j;
TTPoint p, q, r;
if((uint)i >= g->ncon)
return 0;
if(g->confst[i]+1 >= g->confst[i+1]){
if(np != nil)
*np = 0;
if(fp != nil)
*fp = malloc(0);
return g->ncon - i;
}
if(g->bit != nil){
scale = 1.0f / 64;
offx = g->xminpx;
offy = g->yminpx;
}else{
scale = 1.0f * g->font->ppem / g->font->u->emsize;
offx = 0;
offy = 0;
}
p = g->pt[g->confst[i]];
n = 1;
if(fp != nil){
*fp = malloc(2 * sizeof(float));
if(*fp == nil) return -1;
(*fp)[0] = p.x * scale;
(*fp)[1] = p.y * scale + offy;
}
assert((p.flags & 1) != 0);
for(j = g->confst[i]; j++ < g->confst[i+1]; ){
if(j < g->confst[i+1] && (g->pt[j].flags & 1) == 0)
q = g->pt[j++];
else
q = p;
if(j >= g->confst[i+1])
r = g->pt[g->confst[i]];
else{
r = g->pt[j];
if((g->pt[j].flags & 1) == 0){
r.x = (r.x + q.x) / 2;
r.y = (r.y + q.y) / 2;
}
}
if(fp != nil){
nf = realloc(*fp, sizeof(float) * 2 * (n + 2));
if(nf == nil){
free(*fp);
return -1;
}
*fp = nf;
nf[2*n] = q.x * scale;
nf[2*n+1] = q.y * scale + offy;
nf[2*n+2] = r.x * scale;
nf[2*n+3] = r.y * scale + offy;
}
p = r;
n += 2;
if(j < g->confst[i+1] && (g->pt[j].flags & 1) == 0)
j--;
}
if(np != nil)
*np = n;
return g->ncon - i;
}