summaryrefslogtreecommitdiffstats
path: root/lib/silfont/scripts/psfcompressgr.py
blob: 5802cce7edadab4a91fb2b0970ab4744fd2689c9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#!/usr/bin/env python
__doc__ = 'Compress Graphite tables in a font'
__url__ = 'http://github.com/silnrsi/pysilfont'
__copyright__ = 'Copyright (c) 2017 SIL International (http://www.sil.org)'
__license__ = 'Released under the MIT License (http://opensource.org/licenses/MIT)'
__author__ = 'Martin Hosken'

argspec = [
    ('ifont',{'help': 'Input TTF'}, {'type': 'infont'}),
    ('ofont',{'help': 'Output TTF','nargs': '?' }, {'type': 'outfont'}),
    ('-l','--log',{'help': 'Optional log file'}, {'type': 'outfile', 'def': '_compressgr', 'optlog': True})
]

from silfont.core import execute
from fontTools.ttLib.tables.DefaultTable import DefaultTable
import lz4.block
import sys, struct

class lz4tuple(object) :
    def __init__(self, start) :
        self.start = start
        self.literal = start
        self.literal_len = 0
        self.match_dist = 0
        self.match_len = 0
        self.end = 0

    def __str__(self) :
        return "lz4tuple(@{},{}+{},-{}+{})={}".format(self.start, self.literal, self.literal_len, self.match_dist, self.match_len, self.end)

def read_literal(t, dat, start, datlen) :
    if t == 15 and start < datlen :
        v = ord(dat[start:start+1])
        t += v
        while v == 0xFF and start < datlen :
            start += 1
            v = ord(dat[start:start+1])
            t += v
        start += 1
    return (t, start)

def write_literal(num, shift) :
    res = []
    if num > 14 :
        res.append(15 << shift)
        num -= 15
        while num > 255 :
            res.append(255)
            num -= 255
        res.append(num)
    else :
        res.append(num << shift)
    return bytearray(res)

def parseTuple(dat, start, datlen) :
    res = lz4tuple(start)
    token = ord(dat[start:start+1])
    (res.literal_len, start) = read_literal(token >> 4, dat, start+1, datlen)
    res.literal = start
    start += res.literal_len
    res.end = start
    if start > datlen - 2 : 
        return res
    res.match_dist = ord(dat[start:start+1]) + (ord(dat[start+1:start+2]) << 8)
    start += 2
    (res.match_len, start) = read_literal(token & 0xF, dat, start, datlen)
    res.end = start
    return res

def compressGr(dat, version) :
    if ord(dat[1:2]) < version :
        vstr = bytes([version]) if sys.version_info.major > 2 else chr(version)
        dat = dat[0:1] + vstr + dat[2:]
    datc = lz4.block.compress(dat[:-4], mode='high_compression', compression=16, store_size=False)
    # now find the final tuple
    end = len(datc)
    start = 0
    curr = lz4tuple(start)
    while curr.end < end :
        start = curr.end
        curr = parseTuple(datc, start, end)
    if curr.end > end :
        print("Sync error: {!s}".format(curr))
    newend = write_literal(curr.literal_len + 4, 4) + datc[curr.literal:curr.literal+curr.literal_len+1] + dat[-4:]
    lz4hdr = struct.pack(">L", (1 << 27) + (len(dat) & 0x7FFFFFF))
    return dat[0:4] + lz4hdr + datc[0:curr.start] + newend

def doit(args) :
    infont = args.ifont
    for tag, version in (('Silf', 5), ('Glat', 3)) :
        dat = infont.getTableData(tag)
        newdat = bytes(compressGr(dat, version))
        table = DefaultTable(tag)
        table.decompile(newdat, infont)
        infont[tag] = table
    return infont

def cmd() : execute('FT', doit, argspec)
if __name__ == "__main__" : cmd()