font-web.lua /size: 6154 b    last modification: 2020-07-01 14:35
1if not modules then modules = { } end modules ['font-otr'] = {
2    version   = 1.001,
3    comment   = "companion to font-ini.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- Okay, compressing fonts this way is rather simple but one might wonder what the gain
10-- is in this time of 4K youtube movies and most of the web pages wasting space and
11-- bandwidth on advertisements. For version 2 we can use "woff2_decompress" from google
12-- and in a tex environment one can as well store the ttf/otf files in the tex tree. So,
13-- eventually we might even remove this code when version 1 is obsolete.
14
15local ioopen         = io.open
16local replacesuffix  = file.replacesuffix
17
18local readers        = fonts and fonts.handlers.otf.readers
19
20local streamreader   = readers and readers.streamreader or utilities.files
21local streamwriter   = readers and readers.streamwriter or utilities.files
22
23local readstring     = streamreader.readstring
24local readcardinal2  = streamreader.readcardinal2
25local readcardinal4  = streamreader.readcardinal4
26local getsize        = streamreader.getsize
27local setposition    = streamreader.setposition
28local getposition    = streamreader.getposition
29
30local writestring    = streamwriter.writestring
31local writecardinal4 = streamwriter.writecardinal4
32local writecardinal2 = streamwriter.writecardinal2
33local writebyte      = streamwriter.writebyte
34
35local decompress     = zlib.decompress
36
37directives.register("fonts.streamreader",function()
38
39    streamreader  = utilities.streams
40
41    readstring    = streamreader.readstring
42    readcardinal2 = streamreader.readcardinal2
43    readcardinal4 = streamreader.readcardinal4
44    getsize       = streamreader.getsize
45    setposition   = streamreader.setposition
46    getposition   = streamreader.getposition
47
48end)
49
50local infotags = {
51    ["os/2"] = true,
52    ["head"] = true,
53    ["maxp"] = true,
54    ["hhea"] = true,
55    ["hmtx"] = true,
56    ["post"] = true,
57    ["cmap"] = true,
58}
59
60local report = logs.reporter("fonts","woff")
61
62local runner = sandbox.registerrunner {
63    name     = "woff2otf",
64    method   = "execute",
65    program  = "woff2_decompress",
66    template = "%inputfile% %outputfile%",
67    reporter = report,
68    checkers = {
69        inputfile  = "readable",
70        outputfile = "writable",
71    }
72}
73
74local function woff2otf(inpname,outname,infoonly)
75
76    local outname = outname or replacesuffix(inpname,"otf")
77    local inp     = ioopen(inpname,"rb")
78
79    if not inp then
80        report("invalid input file %a",inpname)
81        return
82    end
83
84    local signature = readstring(inp,4)
85
86    if not (signature == "wOFF" or signature == "wOF2") then
87        inp:close()
88        report("invalid signature in %a",inpname)
89        return
90    end
91
92    local flavor = readstring(inp,4)
93
94    if not (flavor == "OTTO" or flavor == "true" or flavor == "\0\1\0\0") then
95        inp:close()
96        report("unsupported flavor %a in %a",flavor,inpname)
97        return
98    end
99
100    if signature == "wOF2" then
101        inp:close()
102        if false then
103            if runner then
104                runner {
105                    inputfile  = inpname,
106                    outputfile = outname,
107                }
108            end
109            return outname, flavor
110        else
111            report("skipping version 2 file %a",inpname)
112            return
113        end
114    end
115
116    local out = ioopen(outname,"wb")
117
118    if not out then
119        inp:close()
120        report("invalid output file %a",outname)
121        return
122    end
123
124    local header = {
125        signature      = signature,
126        flavor         = flavor,
127        length         = readcardinal4(inp),
128        numtables      = readcardinal2(inp),
129        reserved       = readcardinal2(inp),
130        totalsfntsize  = readcardinal4(inp),
131        majorversion   = readcardinal2(inp),
132        minorversion   = readcardinal2(inp),
133        metaoffset     = readcardinal4(inp),
134        metalength     = readcardinal4(inp),
135        metaoriglength = readcardinal4(inp),
136        privoffset     = readcardinal4(inp),
137        privlength     = readcardinal4(inp),
138    }
139
140    local entries = { }
141
142    for i=1,header.numtables do
143        local entry = {
144            tag        = readstring   (inp,4),
145            offset     = readcardinal4(inp),
146            compressed = readcardinal4(inp),
147            size       = readcardinal4(inp),
148            checksum   = readcardinal4(inp),
149        }
150        if not infoonly or infotags[lower(entry.tag)] then
151            entries[#entries+1] = entry
152        end
153    end
154
155    local nofentries    = #entries
156    local entryselector = 0  -- we don't need these
157    local searchrange   = 0  -- we don't need these
158    local rangeshift    = 0  -- we don't need these
159
160    writestring   (out,flavor)
161    writecardinal2(out,nofentries)
162    writecardinal2(out,entryselector)
163    writecardinal2(out,searchrange)
164    writecardinal2(out,rangeshift)
165
166    local offset  = 12 + nofentries * 16
167    local offsets = { }
168
169    for i=1,nofentries do
170        local entry = entries[i]
171        local size  = entry.size
172        writestring(out,entry.tag)
173        writecardinal4(out,entry.checksum)
174        writecardinal4(out,offset) -- the new offset
175        writecardinal4(out,size)
176        offsets[i] = offset
177        offset = offset + size
178        local p = 4 - offset % 4
179        if p > 0 then
180            offset = offset + p
181        end
182    end
183
184    for i=1,nofentries do
185        local entry  = entries[i]
186        local offset = offsets[i]
187        local size   = entry.size
188        setposition(inp,entry.offset+1)
189        local data = readstring(inp,entry.compressed)
190        if #data ~= size then
191            data = decompress(data)
192        end
193        setposition(out,offset+1)
194        writestring(out,data)
195        local p = 4 - offset + size % 4
196        if p > 0 then
197            for i=1,p do
198                writebyte(out,0)
199            end
200        end
201    end
202
203    inp:close()
204    out:close()
205
206    return outname, flavor
207
208end
209
210if readers then
211    readers.woff2otf = woff2otf
212else
213    return woff2otf
214end
215