scrp-ini.lua /size: 31 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['scrp-ini'] = {
2    version   = 1.001,
3    comment   = "companion to scrp-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-- We need to rewrite this a bit ... rather old code ... will be done when japanese
10-- is finished.
11
12local tonumber, next = tonumber, next
13local setmetatableindex = table.setmetatableindex
14local utfbyte, utfsplit = utf.byte, utf.split
15local gmatch = string.gmatch
16
17local trace_analyzing    = false  trackers.register("scripts.analyzing",         function(v) trace_analyzing   = v end)
18local trace_injections   = false  trackers.register("scripts.injections",        function(v) trace_injections  = v end)
19local trace_splitting    = false  trackers.register("scripts.splitting",         function(v) trace_splitting   = v end)
20local trace_splitdetails = false  trackers.register("scripts.splitting.details", function(v) trace_splitdetails = v end)
21
22local report_preprocessing = logs.reporter("scripts","preprocessing")
23local report_splitting     = logs.reporter("scripts","splitting")
24
25
26local attributes       = attributes
27local nodes            = nodes
28local context          = context
29
30local nodecodes        = nodes.nodecodes
31
32local implement        = interfaces.implement
33
34local glyph_code       = nodecodes.glyph
35local glue_code        = nodecodes.glue
36
37local emwidths         = fonts.hashes.emwidths
38local exheights        = fonts.hashes.exheights
39
40local a_script         = attributes.private('script')
41
42local fontdata         = fonts.hashes.identifiers
43local allocate         = utilities.storage.allocate
44local setnodecolor     = nodes.tracers.colors.set
45
46local enableaction     = nodes.tasks.enableaction
47local disableaction    = nodes.tasks.disableaction
48
49local nuts             = nodes.nuts
50
51local getnext          = nuts.getnext
52local getchar          = nuts.getchar
53local getfont          = nuts.getfont
54local getid            = nuts.getid
55local getglyphdata     = nuts.getglyphdata
56local setglyphdata     = nuts.setglyphdata
57
58local isglyph          = nuts.isglyph
59
60local firstglyph       = nuts.firstglyph
61
62local nextglyph        = nuts.traversers.glyph
63local nextchar         = nuts.traversers.char
64
65local nodepool         = nuts.pool
66
67local new_glue         = nodepool.glue
68local new_rule         = nodepool.rule
69local new_penalty      = nodepool.penalty
70
71scripts                = scripts or { }
72local scripts          = scripts
73
74local handlers         = allocate()
75scripts.handlers       = handlers
76
77local injectors        = allocate()
78scripts.injectors      = handlers
79
80local splitters        = allocate()
81scripts.splitters      = splitters
82
83local helpers          = allocate()
84scripts.helpers        = helpers
85
86-- we need to fake it in luatex
87
88local getscript          = node.direct.getscript
89local texsetglyphscript  = tex.setglyphscript
90
91if not getscript then
92
93    local getattr         = nuts.getattr
94    local texsetattribute = tex.setattribute
95    local unsetvalue      = attributes.unsetvalue
96
97    getscript = function(n)
98        local a = getattr(n,a_script)
99        if a and a ~= unsetvalue and a > 0 then
100            return a
101        end
102    end
103
104    texsetglyphscript = function(a)
105        if not a or a == 0 then
106            a = unsetvalue
107        end
108        texsetattribute(a_script,a)
109    end
110
111    nuts.getscript      = getscript
112    tex .setglyphscript = texsetglyphscript
113
114end
115
116local insertnodebefore, insertnodeafter  do
117
118    local insertafter      = nuts.insertafter
119    local insertbefore     = nuts.insertbefore
120    local setattributelist = nuts.setattributelist
121
122    insertnodebefore = function (head,current,what)
123        head, current = insertbefore(head,current,what)
124        setattributelist(what,current)
125        return head, current
126    end
127
128    insertnodeafter = function(head,current,what)
129        head, current = insertafter(head,current,what)
130        setattributelist(what,current)
131        return head, current
132    end
133
134    helpers.insertnodebefore = insertnodebefore
135    helpers.insertnodeafter  = insertnodeafter
136
137end
138
139local hash = characters.scripthash
140
141local numbertodataset = allocate()
142local numbertohandler = allocate()
143
144scripts.numbertodataset = numbertodataset
145scripts.numbertohandler = numbertohandler
146
147local defaults = {
148    inter_char_shrink_factor          = 0,
149    inter_char_shrink_factor          = 0,
150    inter_char_stretch_factor         = 0,
151    inter_char_half_shrink_factor     = 0,
152    inter_char_half_stretch_factor    = 0,
153    inter_char_quarter_shrink_factor  = 0,
154    inter_char_quarter_stretch_factor = 0,
155    inter_char_hangul_penalty         = 0,
156
157    inter_word_stretch_factor         = 0,
158}
159
160scripts.defaults = defaults -- so we can add more
161
162-- todo: copy more efficient than metatable
163
164function scripts.installmethod(handler)
165    local name = handler.name
166    handlers[name] = handler
167    local attributes = { }
168    local datasets = handler.datasets
169    if not datasets or not datasets.default then
170        report_preprocessing("missing (default) dataset in script %a",name)
171        datasets.default = { } -- slower but an error anyway
172    end
173
174    for k, v in next, datasets do
175        setmetatableindex(v,defaults)
176    end
177    setmetatableindex(attributes, function(t,k)
178        local v = datasets[k] or datasets.default
179        local a = 0
180        if v then
181            v.name = name -- for tracing
182            a = #numbertodataset + 1
183            numbertodataset[a] = v
184            numbertohandler[a] = handler
185        end
186        t[k] = a
187        return a
188    end)
189    handler.attributes = attributes
190end
191
192function scripts.installdataset(specification) -- global overload
193    local method  = specification.method
194    local name    = specification.name
195    local dataset = specification.dataset
196    if method and name and dataset then
197        local parent  = specification.parent or ""
198        local handler = handlers[method]
199        if handler then
200            local datasets = handler.datasets
201            if datasets then
202                local defaultset = datasets.default
203                if defaultset then
204                    if parent ~= "" then
205                        local p = datasets[parent]
206                        if p then
207                            defaultset = p
208                        else
209                            report_preprocessing("dataset, unknown parent %a for method %a",parent,method)
210                        end
211                    end
212                    setmetatable(dataset,defaultset)
213                    local existing = datasets[name]
214                    if existing then
215                        for k, v in next, existing do
216                            existing[k] = dataset
217                        end
218                    else
219                        datasets[name] = dataset
220                    end
221                else
222                    report_preprocessing("dataset, no default for method %a",method)
223                end
224            else
225                report_preprocessing("dataset, no datasets for method %a",method)
226            end
227        else
228            report_preprocessing("dataset, no method %a",method)
229        end
230    else
231        report_preprocessing("dataset, invalid specification") -- maybe report table
232    end
233end
234
235local injectorenabled = false
236local splitterenabled = false
237
238local function getscriptdata(n)
239    local s = getscript(n)
240    if s then
241        return s and numbertodataset[s]
242    end
243end
244
245local function getinjector(n)
246    local s = getscript(n)
247    if s then
248        s = numbertohandler[s]
249        return s and s.injector
250    end
251end
252
253local function getsplitter(n)
254    local s = getscript(n)
255    if s then
256        s = numbertodataset[s]
257        return s and s.splitter
258    end
259end
260
261scripts.getdata     = getscriptdata
262scripts.getinjector = getinjector
263scripts.getsplitter = getsplitter
264
265function scripts.set(name,method,preset)
266    local handler = handlers[method]
267    if handler then
268        local index = handler.attributes[preset]
269        if handler.injector then
270            if not injectorenabled then
271                enableaction("processors","scripts.injectors.handler")
272                injectorenabled = true
273            end
274        end
275        if handler.splitter then
276            if not splitterenabled then
277                enableaction("processors","scripts.splitters.handler")
278                splitterenabled = true
279            end
280        end
281        if handler.initializer then
282            handler.initializer(handler)
283            handler.initializer = nil
284        end
285        texsetglyphscript(index)
286    else
287        texsetglyphscript()
288    end
289end
290
291function scripts.reset()
292    texsetglyphscript()
293end
294
295-- 0=gray 1=red 2=green 3=blue 4=yellow 5=magenta 6=cyan 7=x-yellow 8=x-magenta 9=x-cyan
296
297-- local categories = allocate { -- rather bound to cjk ... will be generalized
298--     "korean",
299--     "chinese",
300--     "katakana",
301--     "hiragana",
302--     "full_width_open",
303--     "full_width_close",
304--     "half_width_open",
305--     "half_width_close",
306--     "full_width_punct",
307--     "hyphen",
308--     "non_starter",
309--     "jamo_initial",
310--     "jamo_medial",
311--     "jamo_final",
312--     "ethiopic_syllable",
313--     "ethiopic_word",
314--     "ethiopic_sentence",
315--     "breaking_tsheg",
316--     "nonbreaking_tsheg",
317-- }
318--
319-- scripts.categories = categories
320
321local scriptcolors = allocate {
322    -- todo: just named colors
323    hyphen = "trace:5",
324}
325
326scripts.colors = scriptcolors
327
328-- this can become setprop ...
329
330local propertydata = nodes.properties.data
331
332local function setscriptstatus(n,s)
333    local p = propertydata[n]
334    if p then
335        p.scriptstatus = s
336    else
337        propertydata[n] = { scriptstatus = s }
338    end
339end
340
341function getscriptstatus(n)
342    local p = propertydata[n]
343    if p then
344        return p.scriptstatus
345    end
346end
347
348scripts.setstatus = setscriptstatus
349scripts.getstatus = getscriptstatus
350
351--
352
353local function colorize(start,stop)
354    for n in nextglyph, start do
355        local kind = getscriptstatus(n)
356        if kind then
357            local ac = scriptcolors[kind]
358            if ac then
359                setnodecolor(n,ac)
360            end
361        end
362        if n == stop then
363            break
364        end
365    end
366end
367
368local function traced_process(head,first,last,process,a)
369    if start ~= last then
370        local f, l = first, last
371        local name = numbertodataset[a]
372        name = name and name.name or "?"
373        report_preprocessing("before %s: %s",name,nodes.tosequence(f,l))
374        process(head,first,last)
375        report_preprocessing("after %s: %s", name,nodes.tosequence(f,l))
376    end
377end
378
379function scripts.injectors.handler(head)
380    local start = firstglyph(head) -- we already have glyphs here (subtype 1)
381    if not start then
382        return head
383    else
384        local last_a, normal_process, lastfont, originals, first, last
385        local ok = false
386        while start do
387            local char, id = isglyph(start)
388            if char then
389             -- local a = getinjector(start)
390                local a = getscript(start)
391                if a then
392                    if a ~= last_a then
393                        if first then
394                            if ok then
395                                if trace_analyzing then
396                                    colorize(first,last)
397                                end
398                                if trace_injections then
399                                    traced_process(head,first,last,normal_process,last_a)
400                                else
401                                    normal_process(head,first,last)
402                                end
403                                ok = false
404                            end
405                            first, last = nil, nil
406                        end
407                        last_a = a
408                     -- normal_process = a
409                        normal_process = getinjector(start)
410                    end
411                    if normal_process then
412                        -- id == font
413                        if id ~= lastfont then
414                            originals = fontdata[id].resources
415                            if resources then
416                                originals = resources.originals
417                            else
418                                originals = nil -- can't happen
419                            end
420                            lastfont = id
421                        end
422                        if originals and type(originals) == "number" then
423                            char = originals[char] or char
424                        end
425                        local h = hash[char]
426                        if h then
427                            setscriptstatus(start,h)
428                            if not first then
429                                first, last = start, start
430                            else
431                                last = start
432                            end
433                         -- if cjk == "chinese" or cjk == "korean" then -- we need to prevent too much ( ) processing
434                                ok = true
435                         -- end
436                        elseif first then
437                            if ok then
438                                if trace_analyzing then
439                                    colorize(first,last)
440                                end
441                                if trace_injections then
442                                    traced_process(head,first,last,normal_process,last_a)
443                                else
444                                    normal_process(head,first,last)
445                                end
446                                ok = false
447                            end
448                            first, last = nil, nil
449                        end
450                    end
451                elseif first then
452                    if ok then
453                        if trace_analyzing then
454                            colorize(first,last)
455                        end
456                        if trace_injections then
457                            traced_process(head,first,last,normal_process,last_a)
458                        else
459                            normal_process(head,first,last)
460                        end
461                        ok = false
462                    end
463                    first, last = nil, nil
464                end
465            elseif id == glue_code then
466                if ok then
467                    -- continue
468                elseif first then
469                    -- no chinese or korean
470                    first, last = nil, nil
471                end
472            elseif first then
473                if ok then
474                    -- some chinese or korean
475                    if trace_analyzing then
476                        colorize(first,last)
477                    end
478                    if trace_injections then
479                        traced_process(head,first,last,normal_process,last_a)
480                    else
481                        normal_process(head,first,last)
482                    end
483                    first, last, ok = nil, nil, false
484                elseif first then
485                    first, last = nil, nil
486                end
487            end
488            start = getnext(start)
489        end
490        if ok then
491            if trace_analyzing then
492                colorize(first,last)
493            end
494            if trace_injections then
495                traced_process(head,first,last,normal_process,last_a)
496            else
497                normal_process(head,first,last)
498            end
499        end
500        return head
501    end
502end
503
504-- kind of experimental .. might move to it's own module
505
506-- function scripts.splitters.handler(head)
507--     return head
508-- end
509
510local function addwords(tree,data)
511    if not tree then
512        tree = { }
513    end
514    for word in gmatch(data,"%S+") do
515        local root = tree
516        local list = utfsplit(word,true)
517        for i=1,#list do
518            local l = utfbyte(list[i])
519            local r = root[l]
520            if not r then
521                r = { }
522                root[l] = r
523            end
524            if i == #list then
525                r.final = word -- true -- could be something else, like word in case of tracing
526            else
527                root = r
528            end
529        end
530    end
531    return tree
532end
533
534local loaded = { }
535
536function splitters.load(handler,files)
537    local files  = handler.files
538    local tree   = handler.tree or { }
539    handler.tree = tree
540    if not files then
541        return
542    elseif type(files) == "string" then
543        files         = { files }
544        handler.files = files
545    end
546    if trace_splitting then
547        report_splitting("loading splitter data for language/script %a",handler.name)
548    end
549    loaded[handler.name or "unknown"] = (loaded[handler.name or "unknown"] or 0) + 1
550    statistics.starttiming(loaded)
551    for i=1,#files do
552        local filename = files[i]
553        local fullname = resolvers.findfile(filename)
554        if fullname == "" then
555            fullname = resolvers.findfile(filename .. ".gz")
556        end
557        if fullname ~= "" then
558            if trace_splitting then
559                report_splitting("loading file %a",fullname)
560            end
561            local suffix, gzipped = gzip.suffix(fullname)
562            if suffix == "lua" then
563                local specification = table.load(fullname,gzipped and gzip.load)
564                 if specification then
565                    local lists = specification.lists
566                    if lists then
567                        for i=1,#lists do
568                            local entry = lists[i]
569                            local data = entry.data
570                            if data then
571                                if entry.compression == "zlib" then
572                                    data = zlib.decompress(data)
573                                    if entry.length and entry.length ~= #data then
574                                        report_splitting("compression error in file %a",fullname)
575                                    end
576                                end
577                                if data then
578                                    addwords(tree,data)
579                                end
580                            end
581                        end
582                    end
583                end
584            else
585                local data = gzipped and io.loadgzip(fullname) or io.loaddata(fullname)
586                if data then
587                    addwords(tree,data)
588                end
589            end
590        else
591            report_splitting("unknown file %a",filename)
592        end
593    end
594    statistics.stoptiming(loaded)
595    return tree
596end
597
598statistics.register("loaded split lists", function()
599    if next(loaded) then
600        return string.format("%s, load time: %s",table.sequenced(loaded),statistics.elapsedtime(loaded))
601    end
602end)
603
604-- function splitters.addlist(name,filename)
605--     local handler = scripts.handlers[name]
606--     if handler and filename then
607--         local files = handler.files
608--         if not files then
609--             files = { }
610--         elseif type(files) == "string" then
611--             files = { files }
612--         end
613--         handler.files = files
614--         if type(filename) == "string" then
615--             filename = utilities.parsers.settings_to_array(filename)
616--         end
617--         if type(filename) == "table" then
618--             for i=1,#filename do
619--                 files[#files+1] = filenames[i]
620--             end
621--         end
622--     end
623-- end
624--
625-- commands.setscriptsplitterlist = splitters.addlist
626
627local categories = characters.categories or { }
628
629local function hit(root,head)
630    local current   = getnext(head)
631    local lastrun   = false
632    local lastfinal = false
633    while current do
634        local char = isglyph(current)
635        if char then
636            local newroot = root[char]
637            if newroot then
638                local final = newroot.final
639                if final then
640                    lastrun   = current
641                    lastfinal = final
642                end
643                root = newroot
644            elseif categories[char] == "mn" then
645                -- continue
646            else
647                return lastrun, lastfinal
648            end
649        else
650            break
651        end
652    end
653    if lastrun then
654        return lastrun, lastfinal
655    end
656end
657
658local tree, attr, proc
659
660function splitters.handler(head) -- todo: also firstglyph test
661    local current = head
662    while current do
663        if getid(current) == glyph_code then
664            local a = getsplitter(current)
665            if a then
666                if a ~= attr then
667                    local handler = numbertohandler[a]
668                    tree = handler.tree or { }
669                    attr = a
670                    proc = a
671                end
672                if proc then
673                    local root = tree[getchar(current)]
674                    if root then
675                        -- we don't check for attributes in the hitter (yet)
676                        local last, final = hit(root,current)
677                        if last then
678                            local next = getnext(last)
679                            if next then
680                                local nextchar = isglyph(next)
681                                if not nextchar then
682                                    -- we're done
683                                elseif tree[nextchar] then
684                                    if trace_splitdetails then
685                                        if type(final) == "string" then
686                                            report_splitting("advance %s processing between <%s> and <%c>","with",final,nextchar)
687                                        else
688                                            report_splitting("advance %s processing between <%c> and <%c>","with",char,nextchar)
689                                        end
690                                    end
691                                    head, current = proc(handler,head,current,last,1)
692                                else
693                                    if trace_splitdetails then
694                                        -- could be punctuation
695                                        if type(final) == "string" then
696                                            report_splitting("advance %s processing between <%s> and <%c>","without",final,nextchar)
697                                        else
698                                            report_splitting("advance %s processing between <%c> and <%c>","without",char,nextchar)
699                                        end
700                                    end
701                                    head, current = proc(handler,head,current,last,2)
702                                end
703                            end
704                        end
705                    end
706                end
707            end
708        end
709        current = getnext(current)
710    end
711    return head
712end
713
714local function marker(head,current,font,color) -- could become: nodes.tracers.marker
715    local ex = exheights[font]
716    local em = emwidths [font]
717    head, current = insertnodeafter(head,current,new_penalty(10000))
718    head, current = insertnodeafter(head,current,new_glue(-0.05*em))
719    head, current = insertnodeafter(head,current,new_rule(0.05*em,1.5*ex,0.5*ex))
720    setnodecolor(current,color)
721    return head, current
722end
723
724local last_a, last_f, last_s, last_q
725
726function splitters.insertafter(handler,head,first,last,detail)
727    local a = getscriptdata(first)
728    local f = getfont(first)
729    if a and a ~= last_a or f ~= last_f then
730        last_s = emwidths[f] * data.inter_word_stretch_factor
731        last_a = a
732        last_f = f
733    end
734    if trace_splitting then
735        head, last = marker(head,last,f,detail == 2 and "trace:r" or "trace:g")
736    end
737    if ignore then
738        return head, last
739    else
740        return insertnodeafter(head,last,new_glue(0,last_s))
741    end
742end
743
744-- word-xx.lua:
745--
746-- return {
747--     comment   = "test",
748--     copyright = "not relevant",
749--     language  = "en",
750--     timestamp = "2013-05-20 14:15:21",
751--     version   = "1.00",
752--     lists     = {
753--         {
754--          -- data = "we thrive information in thick worlds because of our marvelous and everyday capacity to select edit single out structure highlight group pair merge harmonize synthesize focus organize condense reduce boil down choose categorize catalog classify list abstract scan look into idealize isolate discriminate distinguish screen pigeonhole pick over sort integrate blend inspect filter lump skip smooth chunk average approximate cluster aggregate outline summarize itemize review dip into flip through browse glance into leaf through skim refine enumerate glean synopsize winnow the wheat from the chaff and separate the sheep from the goats",
755--             data = "abstract aggregate and approximate average because blend boil browse capacity catalog categorize chaff choose chunk classify cluster condense dip discriminate distinguish down edit enumerate everyday filter flip focus from glance glean goats group harmonize highlight idealize in information inspect integrate into isolate itemize leaf list look lump marvelous merge of organize our out outline over pair pick pigeonhole reduce refine review scan screen select separate sheep single skim skip smooth sort structure summarize synopsize synthesize the thick thrive through to we wheat winnow worlds",
756--         },
757--     },
758-- }
759
760scripts.installmethod {
761    name        = "test",
762    splitter    = splitters.insertafter,
763    initializer = splitters.load,
764    files       = {
765     -- "scrp-imp-word-test.lua",
766        "word-xx.lua",
767    },
768    datasets    = {
769        default = {
770            inter_word_stretch_factor = 0.25, -- of quad
771        },
772    },
773}
774
775-- new plugin:
776
777local registercontext   = fonts.specifiers.registercontext
778local mergecontext      = fonts.specifiers.mergecontext
779
780local otfscripts        = characters.otfscripts
781
782local report_scripts    = logs.reporter("scripts","auto feature")
783local trace_scripts     = false  trackers.register("scripts.autofeature",function(v) trace_scripts = v end)
784
785local autofontfeature   = scripts.autofontfeature or { }
786scripts.autofontfeature = autofontfeature
787
788local cache_yes         = { }
789local cache_nop         = { }
790
791setmetatableindex(cache_yes,function(t,k) local v = { } t[k] = v return v end)
792setmetatableindex(cache_nop,function(t,k) local v = { } t[k] = v return v end)
793
794-- beware: we need to tag a done (otherwise too many extra instances ... but how
795-- often unpack? wait till we have a bitmap
796--
797-- we can consider merging this in handlers.characters(head) at some point as there
798-- already check for the dynamic attribute so it saves a pass, however, then we also
799-- need to check for a_scriptinjection there which nils the benefit
800--
801-- we can consider cheating: set all glyphs in a word as the first one but it's not
802-- playing nice
803
804function autofontfeature.handler(head)
805    for n, char, font in nextchar, head do
806     -- if getscript(n) then
807     --     -- already tagged by script feature, maybe some day adapt
808     -- else
809            local script = otfscripts[char]
810            if script then
811                local dynamic = getglyphdata(n) or 0
812                if dynamic > 0 then
813                    local slot = cache_yes[font]
814                    local attr = slot[script]
815                    if not attr then
816                        attr = mergecontext(dynamic,name,2)
817                        slot[script] = attr
818                        if trace_scripts then
819                            report_scripts("script: %s, trigger %C, dynamic: %a, variant: %a",script,char,attr,"extended")
820                        end
821                    end
822                    if attr ~= 0 then
823                        n[0] = attr
824                        -- maybe set scriptinjection when associated
825                    end
826                else
827                    local slot = cache_nop[font]
828                    local attr = slot[script]
829                    if not attr then
830                        attr = registercontext(font,script,2)
831                        slot[script] = attr
832                        if trace_scripts then
833                            report_scripts("script: %s, trigger %C, dynamic: %s, variant: %a",script,char,attr,"normal")
834                        end
835                    end
836                    if attr ~= 0 then
837                        setglyphdata(n,attr)
838                        -- maybe set scriptinjection when associated
839                    end
840                end
841            end
842     -- end
843    end
844    return head
845end
846
847function autofontfeature.enable()
848    report_scripts("globally enabled")
849    enableaction("processors","scripts.autofontfeature.handler")
850end
851
852function autofontfeature.disable()
853    report_scripts("globally disabled")
854    disableaction("processors","scripts.autofontfeature.handler")
855end
856
857implement {
858    name      = "enableautofontscript",
859    actions   = autofontfeature.enable
860}
861
862implement {
863    name      = "disableautofontscript",
864    actions   = autofontfeature.disable }
865
866implement {
867    name      = "setscript",
868    actions   = scripts.set,
869    arguments = "3 strings",
870}
871
872implement {
873    name      = "resetscript",
874    actions   = scripts.reset
875}
876
877-- some common helpers
878
879do
880
881    local parameters = fonts.hashes.parameters
882
883    local space, stretch, shrink, lastfont
884
885    local inter_character_space_factor   = 1
886    local inter_character_stretch_factor = 1
887    local inter_character_shrink_factor  = 1
888
889    local function space_glue(current)
890     -- local data = numbertodataset[getattr(current,a_scriptinjection)]
891        local data = getscriptdata(current)
892        if data then
893            inter_character_space_factor   = data.inter_character_space_factor   or 1
894            inter_character_stretch_factor = data.inter_character_stretch_factor or 1
895            inter_character_shrink_factor  = data.inter_character_shrink_factor  or 1
896        end
897        local font = getfont(current)
898        if lastfont ~= font then
899            local pf = parameters[font]
900            space    = pf.space
901            stretch  = pf.space_stretch
902            shrink   = pf.space_shrink
903            lastfont = font
904        end
905        return new_glue(
906            inter_character_space_factor   * space,
907            inter_character_stretch_factor * stretch,
908            inter_character_shrink_factor  * shrink
909        )
910    end
911
912    scripts.inserters = {
913
914        space_before = function(head,current)
915            return insertnodebefore(head,current,space_glue(current))
916        end,
917        space_after = function(head,current)
918            return insertnodeafter(head,current,space_glue(current))
919        end,
920
921        zerowidthspace_before = function(head,current)
922            return insertnodebefore(head,current,new_glue(0))
923        end,
924        zerowidthspace_after = function(head,current)
925            return insertnodeafter(head,current,new_glue(0))
926        end,
927
928        nobreakspace_before = function(head,current)
929            local g = space_glue(current)
930            local p = new_penalty(10000)
931            head, current = insertnodebefore(head,current,p)
932            return insertnodebefore(head,current,g)
933        end,
934        nobreakspace_after = function(head,current)
935            local g = space_glue(current)
936            local p = new_penalty(10000)
937            head, current = insertnodeafter(head,current,g)
938            return insertnodeafter(head,current,p)
939        end,
940
941    }
942
943end
944
945-- end of helpers
946