lpdf-grp.lmt /size: 9914 b    last modification: 2021-10-28 13:51
1if not modules then modules = { } end modules ['lpdf-grp'] = {
2    version   = 1.001,
3    comment   = "companion to lpdf-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
9local type, tonumber = type, tonumber
10local formatters, gsub = string.formatters, string.gsub
11local concat = table.concat
12local round = math.round
13
14local colors         = attributes.colors
15local basepoints     = number.dimenfactors.bp
16
17local pdfbackend     = backends.registered.pdf
18local nodeinjections = pdfbackend.nodeinjections
19local codeinjections = pdfbackend.codeinjections
20local registrations  = pdfbackend.registrations
21
22local lpdf           = lpdf
23local pdfdictionary  = lpdf.dictionary
24local pdfarray       = lpdf.array
25local pdfconstant    = lpdf.constant
26local pdfboolean     = lpdf.boolean
27local pdfreference   = lpdf.reference
28local pdfflushobject = lpdf.flushobject
29
30local createimage    = images.create
31local wrapimage      = images.wrap
32local embedimage     = images.embed
33
34-- can also be done indirectly:
35--
36-- 12 : << /AntiAlias false /ColorSpace  8 0 R /Coords [ 0.0 0.0 1.0 0.0 ] /Domain [ 0.0 1.0 ] /Extend [ true true ] /Function 22 0 R /ShadingType 2 >>
37-- 22 : << /Bounds [ ] /Domain [ 0.0 1.0 ] /Encode [ 0.0 1.0 ] /FunctionType 3 /Functions [ 31 0 R ] >>
38-- 31 : << /C0 [ 1.0 0.0 ] /C1 [ 0.0 1.0 ] /Domain [ 0.0 1.0 ] /FunctionType 2 /N 1.0 >>
39
40local function shade(stype,name,domain,color_a,color_b,n,colorspace,coordinates,separation,steps,fractions)
41    local func = nil
42    --
43    -- domain has to be consistently added in all dictionaries here otherwise
44    -- acrobat fails with a drawing error
45    --
46    domain = pdfarray(domain)
47    n      = tonumber(n)
48    --
49    if steps then
50        local list   = pdfarray()
51        local bounds = pdfarray()
52        local encode = pdfarray()
53        for i=1,steps do
54            if i < steps then
55                bounds[i] = fractions[i] or 1
56            end
57            encode[2*i-1] = 0
58            encode[2*i]   = 1
59            list  [i]     = pdfdictionary {
60                FunctionType = 2,
61                Domain       = domain,
62                C0           = pdfarray(color_a[i]),
63                C1           = pdfarray(color_b[i]),
64                N            = n,
65            }
66        end
67        func = pdfdictionary {
68            FunctionType = 3,
69            Bounds       = bounds,
70            Encode       = encode,
71            Functions    = list,
72            Domain       = domain,
73        }
74    else
75        func = pdfdictionary {
76            FunctionType = 2,
77            Domain       = domain,
78            C0           = pdfarray(color_a),
79            C1           = pdfarray(color_b),
80            N            = n,
81        }
82    end
83    separation = separation and registrations.getspotcolorreference(separation)
84    local s = pdfdictionary {
85        ShadingType = stype,
86        ColorSpace  = separation and pdfreference(separation) or pdfconstant(colorspace),
87        Domain      = domain,
88        Function    = pdfreference(pdfflushobject(func)),
89        Coords      = pdfarray(coordinates),
90        Extend      = pdfarray { true, true },
91        AntiAlias   = pdfboolean(true),
92    }
93    lpdf.adddocumentshade(name,pdfreference(pdfflushobject(s)))
94end
95
96function lpdf.circularshade(name,domain,color_a,color_b,n,colorspace,coordinates,separation,steps,fractions)
97    shade(3,name,domain,color_a,color_b,n,colorspace,coordinates,separation,steps,fractions)
98end
99
100function lpdf.linearshade(name,domain,color_a,color_b,n,colorspace,coordinates,separation,steps,fractions)
101    shade(2,name,domain,color_a,color_b,n,colorspace,coordinates,separation,steps,fractions)
102end
103
104-- inline bitmaps but xform'd
105--
106-- we could derive the colorspace if we strip the data
107-- and divide by x*y
108
109local template = "q BI %s ID %s > EI Q"
110local factor   = 72/300
111
112function nodeinjections.injectbitmap(t)
113    -- encoding is ascii hex, no checking here
114    local xresolution, yresolution = t.xresolution or 0, t.yresolution or 0
115    if xresolution == 0 or yresolution == 0 then
116        return -- fatal error
117    end
118    local colorspace = t.colorspace
119    if colorspace ~= "rgb" and colorspace ~= "cmyk" and colorspace ~= "gray" then
120        -- not that efficient but ok
121        local d = gsub(t.data,"[^0-9a-f]","")
122        local b = math.round(#d / (xresolution * yresolution))
123        if b == 2 then
124            colorspace = "gray"
125        elseif b == 6 then
126            colorspace = "rgb"
127        elseif b == 8 then
128            colorspace = "cmyk"
129        end
130    end
131    colorspace = lpdf.colorspaceconstants[colorspace]
132    if not colorspace then
133        return -- fatal error
134    end
135    local d = pdfdictionary {
136        W   = xresolution,
137        H   = yresolution,
138        CS  = colorspace,
139        BPC = 8,
140        F   = pdfconstant("AHx"),
141     -- CS  = nil,
142     -- BPC = 1,
143     -- IM = true,
144    }
145    -- for some reasons it only works well if we take a 1bp boundingbox
146    local urx, ury = 1/basepoints, 1/basepoints
147 -- urx = (xresolution/300)/basepoints
148 -- ury = (yresolution/300)/basepoints
149    local width, height = t.width or 0, t.height or 0
150    if width == 0 and height == 0 then
151        width  = factor * xresolution / basepoints
152        height = factor * yresolution / basepoints
153    elseif width == 0 then
154        width  = height * xresolution / yresolution
155    elseif height == 0 then
156        height = width  * yresolution / xresolution
157    end
158    local a = pdfdictionary {
159        BBox = pdfarray { 0, 0, urx * basepoints, ury * basepoints }
160    }
161    local image = createimage {
162        stream = formatters[template](d(),t.data),
163        width  = width,
164        height = height,
165        bbox   = { 0, 0, urx, ury },
166        attr   = a(),
167        nobbox = true,
168    }
169    return wrapimage(image)
170end
171
172-- general graphic helpers
173
174function codeinjections.setfigurealternative(data,figure)
175    local request = data.request
176    local display = request.display
177    if display and display ~= ""  then
178        local nested = figures.push {
179            name   = display,
180            page   = request.page,
181            size   = request.size,
182            prefix = request.prefix,
183            cache  = request.cache,
184            width  = request.width,
185            height = request.height,
186        }
187        figures.identify()
188        local displayfigure = figures.check()
189        if displayfigure then
190        --  figure.aform = true
191            embedimage(figure)
192            local a = pdfarray {
193                pdfdictionary {
194                    Image              = pdfreference(figure.objnum),
195                    DefaultForPrinting = true,
196                }
197            }
198            local d = pdfdictionary {
199                Alternates = pdfreference(pdfflushobject(a)),
200            }
201            displayfigure.attr = d()
202            figures.pop()
203            return displayfigure, nested
204        else
205            figures.pop()
206        end
207    end
208end
209
210function codeinjections.getpreviewfigure(request)
211    local figure = figures.initialize(request)
212    if not figure then
213        return
214    end
215    figure = figures.identify(figure)
216    if not (figure and figure.status and figure.status.fullname) then
217        return
218    end
219    figure = figures.check(figure)
220    if not (figure and figure.status and figure.status.fullname) then
221        return
222    end
223    local image = figure.status.private
224    if image then
225        embedimage(image)
226    end
227    return figure
228end
229
230function codeinjections.setfiguremask(data,figure) -- mark
231    local request = data.request
232    local mask    = request.mask
233    if mask and mask ~= ""  then
234        figures.push {
235            name   = mask,
236            page   = request.page,
237            size   = request.size,
238            prefix = request.prefix,
239            cache  = request.cache,
240            width  = request.width,
241            height = request.height,
242        }
243        mask = figures.identify()
244        mask = figures.check(mask)
245        if mask then
246            local image = mask.status.private
247            if image then
248                figures.include(mask)
249                embedimage(image)
250                local d = pdfdictionary {
251                    Interpolate  = false,
252                    SMask        = pdfreference(mask.status.objectnumber),
253                }
254                figure.attr = d()
255            end
256        end
257        figures.pop()
258    end
259end
260
261-- experimental (q Q is not really needed)
262
263local saveboxresource = tex.boxresources.save
264local nofpatterns     = 0
265local f_pattern       = formatters["q /Pattern cs /%s scn 0 0 %.6N %.6N re f Q"]
266
267function lpdf.registerpattern(specification)
268    nofpatterns = nofpatterns + 1
269    local d = pdfdictionary {
270        Type        = pdfconstant("Pattern"),
271        PatternType = 1,
272        PaintType   = 1,
273        TilingType  = 2,
274        XStep       = (specification.width  or 10) * basepoints,
275        YStep       = (specification.height or 10) * basepoints,
276        Matrix      = {
277            1, 0, 0, 1,
278            (specification.hoffset or 0) * basepoints,
279            (specification.voffset or 0) * basepoints,
280        },
281    }
282
283 -- local resources  = lpdf.collectedresources{ patterns = false } -- we don't want duplicates, so no serialize here:
284    local resources  = lpdf.collectedresources{ patterns = false, serialize = false }
285    local attributes = d -- () -- we need to check for patterns
286    local onlybounds = 1
287    local patternobj = saveboxresource(specification.number,attributes,resources,true,onlybounds)
288    lpdf.adddocumentpattern("Pt" .. nofpatterns,lpdf.reference(patternobj ))
289    return nofpatterns
290end
291
292function lpdf.patternstream(n,width,height)
293    return f_pattern("Pt" .. n,width*basepoints,height*basepoints)
294end
295
296codeinjections.registerpattern = lpdf.registerpattern
297