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