summaryrefslogtreecommitdiff
path: root/misc/ttf2woff/ttf2woff.c
diff options
context:
space:
mode:
Diffstat (limited to 'misc/ttf2woff/ttf2woff.c')
-rw-r--r--misc/ttf2woff/ttf2woff.c523
1 files changed, 523 insertions, 0 deletions
diff --git a/misc/ttf2woff/ttf2woff.c b/misc/ttf2woff/ttf2woff.c
new file mode 100644
index 000000000..f1f2f80db
--- /dev/null
+++ b/misc/ttf2woff/ttf2woff.c
@@ -0,0 +1,523 @@
+/*
+ * 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;
+}