1
1
mirror of https://github.com/rsms/inter.git synced 2024-12-25 16:44:27 +03:00
inter/misc/ttf2woff/ttf2woff.c
2017-08-22 02:23:08 -07:00

524 lines
10 KiB
C

/*
* Copyright (C) 2013 Jan Bobrowski <jb@wizard.ae.krakow.pl>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h>
#include <stdio.h>
#include <stdarg.h>
#include <strings.h>
#include <errno.h>
#include "ttf2woff.h"
#ifndef O_BINARY
#define O_BINARY 0
#endif
struct flags g;
void echo(char *f, ...)
{
FILE *o = g.stdout_used ? stderr : stdout;
va_list va;
va_start(va, f);
vfprintf(o, f, va);
va_end(va);
fputc('\n',o);
}
void *my_alloc(size_t sz)
{
void *p = malloc(sz);
if(!p) errx(1,"Out of memory");
return p;
}
void *my_free(void *p)
{
free(p);
return 0;
}
void *my_realloc(void *p, size_t sz)
{
p = realloc(p, sz);
if(!p) errx(1,"Out of memory");
return p;
}
static struct buf read_file(char *path)
{
struct buf file = {0};
int v, fd = 0;
if(path[0]!='-' || path[1]) {
fd = open(path, O_RDONLY|O_BINARY);
if(fd<0)
err(1, "%s", path);
}
{
struct stat st;
if(fstat(fd, &st) < 0)
err(1, "fstat");
file.len = st.st_size;
}
if(file.len) {
file.ptr = malloc(file.len);
v = read(fd, file.ptr, file.len);
if(v < file.len) {
if(v<0) err(1, "read");
errx(1, "Truncated");
}
} else {
size_t alen = 0;
file.ptr = 0;
for(;;) {
if(file.len == alen) {
if(alen > 64<<20)
errx(1,"Too much data - aborting");
alen += 1<<16;
file.ptr = my_realloc(file.ptr, alen);
}
v = read(fd, file.ptr+file.len, alen-file.len);
if(v<=0) {
if(v) err(1, "read");
break;
}
file.len += v;
}
}
if(fd) close(fd);
return file;
}
static int open_temporary(char *pt, char **pnm)
{
int l = strlen(pt);
char *nm = malloc(l+5);
char *p = nm + l;
int i, fd;
memcpy(nm, pt, l);
*p++ = '.';
for(i=0;;) {
sprintf(p, "%d", i);
fd = open(nm, O_WRONLY|O_TRUNC|O_CREAT|O_BINARY|O_EXCL, 0666);
if(fd>=0)
break;
if(errno!=EEXIST)
err(1, "%s", nm);
if(++i>999)
errx(1, "Can't create temporary file");
}
*pnm = nm;
return fd;
}
void alloc_tables(struct ttf *ttf)
{
int sz = ttf->ntables*sizeof *ttf->tables;
ttf->tables = my_alloc(sz);
memset(ttf->tables, 0, sz);
}
void name_table(struct table *t) {
char *d = t->name;
int i;
for(i=24; i>=0; i-=8) {
char c = t->tag>>i;
if(c>' ' && c<127)
*d++ = c;
}
*d = 0;
}
static u32 calc_csum(u8 *p, size_t n)
{
u32 s=0;
if(n) for(;;) {
s += p[0]<<24;
if(!--n) break;
s += p[1]<<16;
if(!--n) break;
s += p[2]<<8;
if(!--n) break;
s += p[3];
if(!--n) break;
p += 4;
}
return s;
}
enum {
tag_head = 0x68656164,
tag_DSIG = 0x44534947
};
static void recalc_checksums(struct ttf *ttf)
{
u8 h[12];
u32 font_csum, off;
int i, modified;
struct table *head = 0;
struct table *DSIG = 0;
modified = ttf->modified;
for(i=0; i<ttf->ntables; i++) {
struct table *t = ttf->tab_pos[i];
u8 *p = t->buf.ptr;
u32 csum;
if(t->tag == tag_DSIG && t->buf.len>8)
DSIG = t;
if(t->tag != tag_head)
csum = calc_csum(p, t->buf.len);
else {
head = t;
csum = calc_csum(p, 8);
csum += calc_csum(p+12, t->buf.len-12);
}
modified |= t->modified;
if(csum != t->csum) {
modified = 1;
t->csum = csum;
if(!t->modified)
echo("Corrected checksum of table %s", t->name);
}
}
if(modified && DSIG) {
remove_signature:
if(DSIG->free_buf)
free(DSIG->buf.ptr);
DSIG->buf.len = 8;
DSIG->buf.ptr = (u8*)"\0\0\0\1\0\0\0"; // empty DSIG
DSIG->free_buf = 0;
DSIG->csum = calc_csum(DSIG->buf.ptr, DSIG->buf.len);
DSIG = 0;
if(g.verbose)
echo("Digital signature removed");
}
put_ttf_header(h, ttf);
font_csum = calc_csum(h, 12);
off = 12 + 16*ttf->ntables;
for(i=0; i<ttf->ntables; i++) {
struct table *t = ttf->tab_pos[i];
font_csum += t->tag + t->csum + off + t->buf.len;
font_csum += t->csum;
off += t->buf.len+3 & ~3;
}
if(!head || head->buf.len<16)
errx(1, "No head table");
{
u8 *p = head->buf.ptr + 8;
font_csum = 0xB1B0AFBA - font_csum;
if(font_csum != g32(p)) {
if(DSIG)
goto remove_signature;
p32(p, font_csum);
if(!modified)
echo("Corrected checkSumAdjustment");
}
}
}
static int usage(FILE *f, int y)
{
if(!y) {
fprintf(f, "usage:"
"\tttf2woff [-v] font.ttf [font.woff]\n"
"\tttf2woff [-v] font.woff [font.ttf]\n"
"\tttf2woff [-v] -i font\n"
"\tttf2woff -h\n");
} else {
fprintf(f,"TTF2WOFF "STR(VERSION)" by Jan Bobrowski\n"
"usage:\n"
" ttf2woff [-v] [-O|-S] [-t type] [-X table]... [-m file] [-p file] [-u font] input [output]\n"
" ttf2woff -i [-v] [-O|-S] [-X table]... [-m file] [-p file] file\n"
" ttf2woff -l input\n"
" -v be verbose\n"
" -i in place modification\n"
" -O optimize (default unless signed)\n"
" -S don't optimize\n"
" -t fmt output format: woff, ttf\n"
" -u num font number in collection (TTC), 0-based\n"
" -m xml metadata\n"
" -p priv private data\n"
" -X tag remove table\n"
" -l list tables\n"
"Use `-' to indicate standard input/output.\n"
"Skip output for dry run.\n"
"Compressor: %s.\n",
copression_by);
}
return 1;
}
static int type_by_name(char *s)
{
if(strcasecmp(s,"TTF")==0 || strcasecmp(s,"OTF")==0) return fmt_TTF;
if(strcasecmp(s,"WOFF")==0) return fmt_WOFF;
return fmt_UNKNOWN;
}
static int cmp_tab_pos(const void *a, const void *b) {
return (*(struct table**)a)->pos - (*(struct table**)b)->pos;
}
int main(int argc, char *argv[])
{
struct ttf ttf = {0};
char *iname, *itype_name, *oname, *otype_name, *mname=0, *pname=0;
struct buf input, output;
struct buf xtab = {0};
int i, v, itype, fontn;
g.otype = fmt_UNKNOWN;
g.dryrun = 1; // no output
g.mayoptim = 1;
fontn = 0;
for(;;) switch(getopt(argc, argv, "vt:u:SOX:lm:p:ihV")) {
case 'v': g.verbose = 1; break;
case 'l': g.listonly = 1; break;
case 'i': g.inplace = 1; break;
case 't':
v = type_by_name(optarg);
if(v==fmt_UNKNOWN)
errx(1, "Unsupported font type: %s", optarg);
g.otype = v;
break;
case 'u':
fontn = atoi(optarg);
break;
case 'S':
g.mayoptim = g.optimize = 0;
break;
case 'O':
g.mayoptim = g.optimize = 1;
break;
case 'X':
v = strlen(optarg) + 1;
xtab.ptr = my_realloc(xtab.ptr, xtab.len+v);
strcpy(xtab.ptr+xtab.len, optarg);
xtab.len += v;
break;
case 'm': mname = optarg; break;
case 'p': pname = optarg; break;
case '?':
if(optopt!='?')
break;
case 'h': return usage(stdout,1);
case 'V': printf(STR(VERSION)"\n"); return 0;
case -1: goto gotopt;
}
gotopt:
if(optind==argc)
return usage(stderr,0);
iname = argv[optind++];
oname = 0;
if(g.inplace) {
if(iname[0]=='-' && !iname[1])
errx(1, "-i is not compatible with -");
g.dryrun = 0;
if(optind < argc)
warnx("Too many args");
}
if(optind < argc) {
g.dryrun = 0;
oname = argv[optind++];
if(optind < argc)
warnx("Too many args");
if(oname[0]=='-' && !oname[1]) {
oname = 0;
g.stdout_used = 1;
} else if(g.otype==fmt_UNKNOWN) {
char *p = strrchr(oname, '.');
if(p)
g.otype = type_by_name(p+1);
}
}
input = read_file(iname);
if(input.len < 28)
errx(1,"File too short");
itype = fmt_UNKNOWN;
if(g32(input.ptr) == g32("wOFF")) {
read_woff(&ttf, input.ptr, input.len);
itype_name = "WOFF";
itype = fmt_WOFF;
} else if(g32(input.ptr) == g32("ttcf")) {
if(g.inplace)
errx(1, "Can't optimize collection");
read_ttc(&ttf, input.ptr, input.len, fontn);
itype_name = "TTC";
} else if(g32(input.ptr) == g32("wOF2")) {
errx(1, "WOFF2 is not supported");
} else {
read_ttf(&ttf, input.ptr, input.len, 0);
itype_name = "TTF";
itype = fmt_TTF;
}
if(g.inplace)
g.otype = itype;
if(g.otype==fmt_UNKNOWN || g.otype==fmt_WOFF) {
g.otype = fmt_WOFF;
if(mname)
ttf.woff_meta = read_file(mname);
if(pname)
ttf.woff_priv = read_file(pname);
}
// all read
if(xtab.len) {
char *p=xtab.ptr, *e=p+xtab.len;
for(; p<e; p=strchr(p,0)+1) {
struct table *t;
struct buf *b;
if(strcmp(p,"metadata")==0) {
b = &ttf.woff_meta;
rm_meta:
if(b->len) {
b->len = 0;
ttf.modified_meta = 1;
}
continue;
}
if(strcmp(p,"private")==0) {
b = &ttf.woff_priv;
goto rm_meta;
}
for(i=0; i<ttf.ntables; i++) {
t = &ttf.tables[i];
if(strcmp(t->name, p)==0)
goto rm_tab;
}
echo("Table %s not found", p);
if(0) {
rm_tab:
memmove(t, t+1, (char*)(ttf.tables+ttf.ntables) - (char*)(t+1));
ttf.ntables--;
ttf.modified = 1;
if(g.verbose)
echo("Table %s removed", p);
}
}
free(xtab.ptr);
}
ttf.tab_pos = malloc(ttf.ntables * sizeof *ttf.tab_pos);
for(i=0; i<ttf.ntables; i++)
ttf.tab_pos[i] = &ttf.tables[i];
qsort(ttf.tab_pos, ttf.ntables, sizeof *ttf.tab_pos, cmp_tab_pos);
if(g.listonly) {
unsigned size = 12 + 16*ttf.ntables;
for(i=0; i<ttf.ntables; i++) {
struct table *t = ttf.tab_pos[i];
size += t->buf.len;
echo("%-4s %6u", t->name, t->buf.len);
}
echo("%-4s %6u", "", size);
return 0;
}
if(!ttf.modified) {
struct table *t = find_table(&ttf, "DSIG");
if(t && t->buf.len>8)
g.mayoptim = g.optimize;
}
if(g.mayoptim)
optimize(&ttf);
recalc_checksums(&ttf);
switch(g.otype) {
case fmt_TTF:
gen_ttf(&output, &ttf);
otype_name = "TTF";
break;
case fmt_WOFF:
gen_woff(&output, &ttf);
otype_name = "WOFF";
break;
}
if(g.verbose || g.dryrun)
echo("input: %s %u bytes, output: %s %u bytes (%.1f%%)",
itype_name, input.len, otype_name, output.len, 100.*output.len/input.len);
if(g.dryrun)
return 0;
if(g.inplace && !ttf.modified && !ttf.modified_meta) {
if(output.len >= input.len) {
if(g.verbose)
echo("Not modified");
return 0;
}
}
{
u8 *p=output.ptr, *e=p+output.len;
int fd = 1;
if(g.inplace)
fd = open_temporary(iname, &oname);
else if(oname) {
fd = open(oname, O_WRONLY|O_TRUNC|O_CREAT|O_BINARY, 0666);
if(fd<0) err(1, "%s", oname);
}
do {
v = write(fd, p, e-p);
if(v<=0) {
if(v) err(1, "write");
errx(1, "Short write");
}
p += v;
} while(p < e);
close(fd);
}
if(g.inplace) {
#ifdef WIN32
unlink(iname);
#endif
v = rename(oname, iname);
if(v<0) {
warn("Rename %s to %s", oname, iname);
unlink(oname);
return 1;
}
// free(oname);
}
return 0;
}