font-imp-scripts.lmt /size: 9 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['font-imp-scripts'] = {
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
9local getrange           = characters.getrange
10
11local settings_to_hash   = utilities.parsers.settings_to_hash
12
13if not context then return end
14
15-- the defaults are not yet ok
16
17local next, type, tonumber = next, type, tonumber
18local gmatch = string.gmatch
19local max = math.max
20
21local fonts              = fonts
22local utilities          = utilities
23
24local helpers            = fonts.helpers
25local charcommand        = helpers.commands.char
26local downcommand        = helpers.commands.down
27local upcommand          = helpers.commands.up
28
29local handlers           = fonts.handlers
30local otf                = handlers.otf
31local afm                = handlers.afm
32local registerotffeature = otf.features.register
33local registerafmfeature = afm.features.register
34local addotffeature      = otf.addfeature
35
36local settings_to_hash   = utilities.parsers.settings_to_hash
37local sortedhash         = table.sortedhash
38
39local handlers           = fonts.handlers
40
41local sup                = nil
42local sub                = nil
43
44local function initialize(tfmdata,key,value)
45    --
46    if not sup then
47        sup = { }
48        sub = { }
49        for unicode, data in next, characters.data do
50            local specials = data.specials
51            if specials then
52                local what = specials[1]
53                if what == "super" then
54                    sup[unicode] = specials[2]
55                elseif what == "sub" then
56                    sub[unicode] = specials[2]
57                end
58            end
59        end
60    end
61    --
62    local spec
63    if value == true then
64        spec = { factor = 3/5, up = 5/4, down = 1/4 }
65    elseif type(value) == "number" then
66        spec = { factor = value, up = 5/4, down = 1/4 }
67    else
68        spec = settings_to_hash(value)
69    end
70    local factor = tonumber(spec.factor) or 3/5
71    local up     = tonumber(spec.up)     or 5/4
72    local down   = tonumber(spec.down)   or 1/4
73    --
74    local characters = tfmdata.characters
75    local parameters = tfmdata.parameters
76    local up         =  parameters.xheight * up
77    local down       = -parameters.xheight * down
78    -- disable sups/subs
79    local function add(unicode,other,go_up)
80        local old = characters[other]
81        if old then
82            local shift  = go_up and up or down
83            local width  = (old.width or 0) * factor
84            local height = (old.height or 0) * factor + shift
85            local depth  = go_up and 0 or max((old.depth  or 0) * factor + down,0)
86            characters[unicode] = {
87                width    = width,
88                height   = height,
89                depth    = depth,
90                commands = { { "offset", 0, shift, other, factor, factor } },
91             -- yoffset  = shift,
92             -- xscale   = factor,
93             -- yscale   = factor,
94             -- commands = { { "slot", 0, other } }, -- { slotcommand[0][other] or charcommand[other] }
95            }
96        end
97    end
98    for unicode, other in sortedhash(sup) do
99        add(unicode,other,true)
100    end
101    for unicode, other in sortedhash(sub) do
102        add(unicode,other,false)
103    end
104end
105
106local specification = {
107    name        = "scripts",
108    description = "add superiors and inferiors",
109    manipulators = {
110        base = initialize,
111        node = initialize,
112    }
113}
114
115registerotffeature(specification)
116registerafmfeature(specification)
117
118-- a different kind of scripts support
119
120local function initialize(tfmdata,key,value)
121    if value then
122        local detail      = type(value) == "string" and settings_to_hash(value) or { }
123        local orientation = tonumber(detail.orientation) or 0
124        if orientation == 1 or orientation == 3 then
125           local characters = tfmdata.characters
126            local parameters = tfmdata.parameters
127            local emwidth    = parameters.quad
128            local exheight   = parameters.xheight
129            local ranges     = detail.ranges
130            local downshift  = exheight * (tonumber(detail.down) or 0)
131            local rightshift = exheight * (tonumber(detail.right) or 0)
132            local orientate
133            if orientation == 1 then
134                orientate = function(character)
135                    local width  = character.width or 0
136                    local height = character.height or 0
137                    local depth  = character.depth or 0
138                    character.width       = height + depth + rightshift + rightshift
139                    character.height      = width - downshift
140                    character.depth       = shift
141                    character.xoffset     = depth + rightshift
142                    character.yoffset     = width - downshift
143                    character.orientation = orientation
144                end
145            else
146                orientate = function(character)
147                    local width  = character.width or 0
148                    local height = character.height or 0
149                    local depth  = character.depth or 0
150--                     character.width       = height + depth + rightshift + rightshift
151                    character.height      = width - downshift
152                    character.depth       = shift
153--                     character.xoffset     = height + rightshift
154--                     character.yoffset     = - downshift
155--                     character.orientation = orientation
156                end
157            end
158            if ranges then
159                for s in gmatch(ranges,"[^, ]+") do
160                    local start, stop, description, gaps = getrange(s,true)
161                    if start and stop then
162                        for unicode=start,stop do
163                            local character = characters[unicode]
164                            if character then
165                                orientate(character)
166                            end
167                        end
168                    end
169                end
170            else
171                for unicode, character in next, characters do
172                    orientate(character)
173                end
174            end
175        end
176    end
177end
178
179local specification = {
180    name        = "vertical",
181    description = "vertical",
182    manipulators = {
183        base = initialize,
184        node = initialize,
185    }
186}
187
188registerotffeature(specification)
189registerafmfeature(specification)
190
191do
192
193    -- See Wolfgang Schusters patches for Japanese etc.
194
195    local left   <const> = 1
196    local middle <const> = 2
197    local right  <const> = 3
198
199    local mapping = {
200       [0x02010] = middle,
201       [0x02027] = middle,
202       [0x02329] = left,
203       [0x0232A] = right,
204       [0x03001] = right,
205       [0x03002] = right,
206       [0x03008] = left,
207       [0x03009] = right,
208       [0x0300A] = left,
209       [0x0300B] = right,
210       [0x0300C] = left,
211       [0x0300D] = right,
212       [0x0300E] = left,
213       [0x0300F] = right,
214       [0x03010] = left,
215       [0x03011] = right,
216       [0x03014] = left,
217       [0x03015] = right,
218       [0x03016] = left,
219       [0x03017] = right,
220       [0x03018] = left,
221       [0x03019] = right,
222       [0x0301A] = left,
223       [0x0301B] = right,
224       [0x0301D] = left,
225       [0x0301E] = right,
226       [0x0301F] = right,
227       [0x030FB] = middle,
228       [0x0FF01] = middle,
229       [0x0FF02] = middle,
230       [0x0FF07] = middle,
231       [0x0FF08] = left,
232       [0x0FF09] = right,
233       [0x0FF0C] = right,
234       [0x0FF0E] = right,
235       [0x0FF1A] = middle,
236       [0x0FF1B] = middle,
237       [0x0FF3B] = left,
238       [0x0FF3D] = right,
239       [0x0FF5B] = left,
240       [0x0FF5C] = middle,
241       [0x0FF5D] = right,
242       [0x0FF5F] = left,
243       [0x0FF60] = right,
244       [0x0FFE4] = middle,
245
246       [0xF054C] = left,
247       [0xF054D] = right,
248       [0xF054E] = left,
249       [0xF054F] = right,
250    }
251
252    local firstprivate <const> = fonts.privateoffsets and fonts.privateoffsets.textbase or 0xF0000
253
254    addotffeature {
255        name = "halt",
256        type = "single",
257        data = function(tfmdata)
258            local resources = tfmdata.resources
259            if not resources then
260                return
261            end
262            local features = resources.features
263            if not features then
264                return
265            end
266            local gpos = features.gpos
267            if gpos and gpos.halt then
268               return
269            end
270            local descriptions = tfmdata.descriptions
271            if not descriptions[0x3002] then
272                -- no fullstop means it's not a cjk font
273                return
274            end
275            local positions = { }
276            local reposition
277            for u, v in next, mapping do
278                local d = descriptions[u]
279                if d then
280                    if not reposition then
281                        local w = d.width
282                        reposition = {
283                            { -w/2, 0, -w/2, 0 },
284                            { -w/4, 0, -w/2, 0 },
285                            {    0, 0, -w/2, 0 },
286                        }
287                    end
288                    positions[u] = reposition[v]
289                else
290                 -- print(string.format("! %X",u))
291                end
292            end
293            if reposition then
294                local p = firstprivate
295                while true do
296                    local d = descriptions[p]
297                    if d then
298                        local v = mapping[d.unicode or 0]
299                        if v then
300                            positions[p] = reposition[v]
301                        end
302                        p = p + 1
303                    else
304                        break
305                    end
306                end
307            end
308            return next(positions) and positions
309        end,
310    }
311
312end
313