font-phb-imp-internal.lmt /size: 7148 b    last modification: 2024-01-16 09:02
1if not modules then modules = { } end modules ['font-phb-imp-internal'] = {
2    version   = 1.000, -- 2016.10.10,
3    comment   = "companion to font-txt.mkiv",
4    original  = "derived from font-phb-imp-library",
5    author    = "Hans Hagen",
6    copyright = "PRAGMA ADE / ConTeXt Development Team",
7    license   = "see context related readme files",
8}
9
10-- The hb library comes in versions and the one I tested in 2016 was part of the inkscape
11-- suite. In principle one can have incompatibilities due to updates but that is the nature
12-- of a library. When a library ie expected one has better use the system version, if only
13-- to make sure that different programs behave the same.
14--
15-- The main reason for testing this approach was that when Idris was working on his fonts,
16-- we wanted to know how different shapers deal with it and the hb command line program
17-- could provide uniscribe output. For the context shaper uniscribe is the reference, also
18-- because Idris started out with Volt a decade ago.
19--
20-- We treat the lib as a black box as it should be. At some point Kai Eigner made an ffi
21-- binding and that one was adapted to the plugin approach of context. It saved me the
22-- trouble of looking at source files to figure it all out. Below is the adapted code.
23--
24-- This is basically the ffi variant but with the hb function calls delegated to a simple
25-- runtime library. That library was a side effect of playing a day with delayed loading
26-- like ffi does in luametatex, which seems to work ok for what we call optional libraries
27-- in lmtx. I didn't really test the next code well (and will probably do that when Idris
28-- needs some comparison with uniscribe etc). There are nowadays probably other ways to do
29-- this but this is what we had and so we can keep the test code that has been around for
30-- a while (which is needed because some old articles need it.)
31--
32-- The following setup doesn't really fit into the way we set up internal libraries
33-- but if isn't used in the same sense anyway so we stick to what we already had in
34-- the ffi variant (also because it uses helpers here and we want to keep the client
35-- variant too). We don't need to be generic as other macro packages follow a different
36-- route.
37--
38-- Last time I checked "fiets" got no ligature with the "ot" shaper but it did get one
39-- with the "uniscribe" shaper ... somewhat puzzling .. but "effe" worked okay. Maybe
40-- there is some built-in heuristic interfering? When Idris an I tested fonts we had
41-- similar differences with arabic so maybe we miss a point here.
42--
43-- native     font plugin > hb > string : fi-
44--            font plugin > hb > text   : U+00066 U+00069 U+0002D
45--            font plugin > hb > result : U+00066 U+00069 U+0002D
46--
47-- uniscribe  font plugin > hb > string : fi-
48--            font plugin > hb > text   : U+00066 U+00069 U+0002D
49--            font plugin > hb > result : U+0FB01 U+0002D
50--
51-- native     font plugin > hb > string : ets
52--            font plugin > hb > text   : U+00065 U+00074 U+00073
53--            font plugin > hb > result : U+00065 U+00074 U+00073
54--
55-- uniscribe  font plugin > hb > string : ets
56--            font plugin > hb > text   : U+00065 U+00074 U+00073
57--            font plugin > hb > result : U+00065 U+00074 U+00073
58--
59-- native     font plugin > hb > string : fiets
60--            font plugin > hb > text   : U+00066 U+00069 U+00065 U+00074 U+00073
61--            font plugin > hb > result : U+00066 U+00069 U+00065 U+00074 U+00073
62--
63-- uniscribe  font plugin > hb > string : fiets
64--            font plugin > hb > text   : U+00066 U+00069 U+00065 U+00074 U+00073
65--            font plugin > hb > result : U+0FB01 U+00065 U+00074 U+00073
66
67-- In the meantime a single dll is not enough and we need some more which interferes with
68-- the idea of a simple dll in the lib path ... too many dependencies now .. and having
69-- some shared bin path is not what we want in tex so ...
70
71local report = utilities.hb.report or print
72
73local hblib  = optional and (optional.hb or optional.test)
74
75if not hblib then
76    report("no hblib found, you can try the ffi variant")
77    return
78end
79
80local hb_initialize  = hblib.initialize
81local hb_getversion  = hblib.getversion
82local hb_getshapers  = hblib.getshapers
83local hb_loadfont    = hblib.loadfont
84local hb_shapestring = hblib.shapestring
85
86if not hb_initialize then
87    report("no functions in hblib found, you can try the ffi variant")
88    return
89end
90
91local loaddata    = io.loaddata
92local findlib     = resolvers.findlib
93local concat      = table.concat
94local utfchar     = utf.char
95local packtoutf8  = utilities.hb.helpers.packtoutf8
96local packtoutf32 = utilities.hb.helpers.packtoutf32
97local report      = utilities.hb.report or print
98local fontdata    = fonts.hashes.identifiers
99local initialized = nil
100local loaded      = { }
101local shared      = { }
102local libfile     = os.name == "windows" and "libharfbuzz-0" or "libharfbuzz"
103
104local shapers = {
105    native    = { "ot", "uniscribe", "fallback" },
106    uniscribe = { "uniscribe", "ot", "fallback" },
107 -- uniscribe = { "uniscribe", "fallback" }, -- stalls without fallback when no uniscribe present
108    fallback  = { "fallback" },
109}
110
111local mode = 8 -- now 32 bit crashes (random, needs checking, probably zero end of string issue)
112
113function utilities.hb.methods.internal(font,data,rlmode,text,leading,trailing)
114    if initialized == nil then
115        local filename = findlib(libfile)
116        initialized = hb_initialize(filename)
117        if initialized then
118            report("using hb library version %a, supported shapers: %,t",hb_getversion(),hb_getshapers())
119        else
120            report("unable to locate hb library")
121            initialize = false
122        end
123    end
124    if initialized then
125        local instance = loaded[font]
126        if instance == nil then
127            local tfmdata   = fontdata[font]
128            local resources = tfmdata.resources
129            local filename  = resources.filename
130                  instance  = shared[filename]
131            if instance == nil then
132                local wholefont = loaddata(filename)
133                if wholefont then
134                    instance = hb_loadfont(font,wholefont)
135                end
136                if not instance then
137                    instance = false
138                end
139                shared[filename] = instance
140            end
141            loaded[font] = instance
142        end
143        if instance then
144            if mode ==32 then
145                text = packtoutf32(text,leading,trailing)
146            else
147                text = packtoutf8(text,leading,trailing) -- doesn't work ok (no time not to figure it out)
148            end
149            local result = hb_shapestring (
150                instance,
151                data.script          or "dflt",
152                data.language        or "dflt",
153                rlmode < 0 and "rtl" or "ltr",
154                shapers[data.shaper] or shapers.native,
155                data.featureset      or { },
156                text,
157                rlmode < 0,
158                mode
159            )
160         -- inspect(result)
161            return result
162        end
163    end
164end
165