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