font-imp-reorder.lua /size: 5989 b    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['font-imp-reorder'] = {
2    version   = 1.001,
3    comment   = "companion to font-ini.mkiv and hand-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
9if not context then return end
10
11local next = next
12local find = string.find
13local sortedhash, sortedkeys, sort = table.sortedhash, table.sortedkeys, table.sort
14
15local fonts              = fonts
16local otf                = fonts.handlers.otf
17local registerotffeature = otf.features.register
18
19-- This is a rather special test-only feature that I added for the sake of testing
20-- Idris's husayni. We wanted to know if uniscribe obeys the order of lookups in a
21-- font, in spite of what the description of handling arabic suggests. And indeed,
22-- mixed-in lookups of other features (like all these ss* in husayni) are handled
23-- the same in context as in uniscribe. If one sets reorderlookups=arab then we sort
24-- according to the "assumed" order so e.g. the ss* move to after the standard
25-- features. The observed difference in rendering is an indication that uniscribe is
26-- quite faithful to the font (while e.g. tests with the hb plugin demonstrate some
27-- interference, apart from some hard coded init etc expectations). Anyway, it means
28-- that we're okay with the (generic) node processor. A pitfall is that in context
29-- we can actually control more, so we can trigger an analyze pass with e.g.
30-- dflt/dflt while the libraries depend on the script settings for that. Uniscribe
31-- probably also parses the string and when seeing arabic will follow a different
32-- code path, although it seems to treat all features equal.
33
34local trace_reorder  = trackers.register("fonts.reorderlookups",function(v) trace_reorder = v end)
35local report_reorder = logs.reporter("fonts","reorder")
36
37local vectors = { }
38otf.vectors   = vectors -- kind of private
39
40vectors.arab = {
41    gsub = {
42        ccmp =  1,
43        isol =  2,
44        fina =  3,
45        medi =  4,
46        init =  5,
47        rlig =  6,
48        rclt =  7,
49        calt =  8,
50        liga =  9,
51        dlig = 10,
52        cswh = 11,
53        mset = 12,
54    },
55    gpos = {
56        curs =  1,
57        kern =  2,
58        mark =  3,
59        mkmk =  4,
60    },
61}
62
63local function compare(a,b)
64    local what_a = a.what
65    local what_b = b.what
66    if what_a ~= what_b then
67        return a.index < b.index
68    end
69    local when_a = a.when
70    local when_b = b.when
71    if when_a == when_b then
72        return a.index < b.index
73    else
74        return when_a < when_b
75    end
76end
77
78function otf.reorderlookups(tfmdata,vector)
79    local order = vectors[vector]
80    if not order then
81        return
82    end
83    local oldsequences = tfmdata.resources.sequences
84    if oldsequences then
85        local sequences = { }
86        for i=1,#oldsequences do
87            sequences[i] = oldsequences[i]
88        end
89        for i=1,#sequences do
90            local s = sequences[i]
91            local features = s.features
92            local kind     = s.type
93            local index    = s.index
94            if features then
95                local when
96                local what
97                for feature in sortedhash(features) do
98                    if not what then
99                        what = find(kind,"^gsub") and "gsub" or "gpos"
100                    end
101--                     local newwhen = order[what][feature]
102                    if not newwhen then
103                        -- skip
104                    elseif not when then
105                        when = newwhen
106                    elseif newwhen < when then
107                        when = newwhen
108                    end
109                end
110                s.ondex = s.index
111                s.index = i
112                s.what  = what == "gsub" and 1 or 2
113                s.when  = when or 99
114            else
115                s.ondex = s.index
116                s.index = i
117                s.what  = 1
118                s.when  = 99
119            end
120        end
121        sort(sequences,compare)
122        local swapped = 0
123        for i=1,#sequences do
124            local sequence = sequences[i]
125            local features = sequence.features
126            if features then
127                local index = sequence.index
128                if index ~= i then
129                    swapped = swapped + 1
130                end
131                if trace_reorder then
132                    if swapped == 1 then
133                        report_reorder()
134                        report_reorder("start swapping lookups in font %!font:name!",tfmdata)
135                        report_reorder()
136                        report_reorder("gsub order: % t",table.swapped(order.gsub))
137                        report_reorder("gpos order: % t",table.swapped(order.gpos))
138                        report_reorder()
139                    end
140                    report_reorder("%03i : lookup %03i, type %s, sorted %2i, moved %s, % t",
141                        i,index,sequence.what == 1 and "gsub" or "gpos",sequence.when or 99,
142                        (index > i and "-") or (index < i and "+") or "=",sortedkeys(features))
143                end
144            end
145            sequence.what  = nil
146            sequence.when  = nil
147            sequence.index = sequence.ondex
148        end
149        if swapped > 0 then
150            if trace_reorder then
151                report_reorder()
152                report_reorder("stop swapping lookups, %i lookups swapped",swapped)
153                report_reorder()
154            end
155            tfmdata.shared.reorderedsequences = sequences
156        end
157    end
158end
159
160-- maybe delay till ra is filled
161
162local function initialize(tfmdata,key,value)
163    if value then
164        otf.reorderlookups(tfmdata,value)
165    end
166end
167
168registerotffeature {
169    name        = "reorderlookups",
170    description = "reorder lookups",
171    manipulators = {
172        base = initialize,
173        node = initialize,
174    }
175}
176