summaryrefslogtreecommitdiff
path: root/macros/luatex/generic/barracuda/src/lib-barcode/brcd-code128.lua
blob: 317b3de8f8e7c8ca26faea13e6df61e1d8a4a469 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
-- Code128 barcode generator module
-- Copyright (C) 2019 Roberto Giacomelli
--
-- All dimension must be in scaled point (sp)
-- every fields that starts with an undercore sign are intended as private

local Code128 = {
    _VERSION     = "code128 v0.0.5",
    _NAME        = "Code128",
    _DESCRIPTION = "Code128 barcode encoder",
}

Code128._int_def_bar = {-- code bar definitions
    [0] = 212222,   222122, 222221, 121223, 121322, 131222, 122213, 122312,
    132212, 221213, 221312, 231212, 112232, 122132, 122231, 113222, 123122,
    123221, 223211, 221132, 221231, 213212, 223112, 312131, 311222, 321122,
    321221, 312212, 322112, 322211, 212123, 212321, 232121, 111323, 131123,
    131321, 112313, 132113, 132311, 211313, 231113, 231311, 112133, 112331,
    132131, 113123, 113321, 133121, 313121, 211331, 231131, 213113, 213311,
    213131, 311123, 311321, 331121, 312113, 312311, 332111, 314111, 221411,
    431111, 111224, 111422, 121124, 121421, 141122, 141221, 112214, 112412,
    122114, 122411, 142112, 142211, 241211, 221114, 413111, 241112, 134111,
    111242, 121142, 121241, 114212, 124112, 124211, 411212, 421112, 421211,
    212141, 214121, 412121, 111143, 111341, 131141, 114113, 114311, 411113,
    411311, 113141, 114131, 311141, 411131, 211412, 211214, 211232,
    2331112, -- this is the stop char at index 106
}

Code128._codeset = {
    A        = 103, -- Start char for Codeset A
    B        = 104, -- Start char for Codeset B
    C        = 105, -- Start char for Codeset C
    stopChar = 106, -- Stop char
    shift    =  98, -- A to B or B to A
}

Code128._switch = { -- codes for switching from a codeset to another one
    [103] = {[104] = 100, [105] =  99}, -- from A to B or C
    [104] = {[103] = 101, [105] =  99}, -- from B to A or C
    [105] = {[103] = 101, [104] = 100}, -- from C to A or B
}

-- parameters definition
Code128._par_def = {}
local pardef = Code128._par_def

-- module main parameter
pardef.xdim = {
    default    = 0.21 * 186467, -- X dimension
    unit       = "sp", -- scaled point
    isReserved = true,
    order      = 1, -- the one first to be modified
    fncheck    = function (self, x, _) --> boolean, err
        if x >= self.default then
            return true, nil
        else
            return false, "[OutOfRange] too small value for xdim"
        end
    end,
}

pardef.ydim = {
    default    = 10 * 186467, -- Y dimension
    unit       = "sp",
    isReserved = false,
    order      = 2,
    fncheck    = function (self, y, tpar) --> boolean, err
        local xdim = tpar.xdim
        if y >= 10*xdim then
            return true, nil
        else
            return false, "[OutOfRange] too small value for ydim"
        end
    end,
}

pardef.quietzone_factor = {
    default    = 10,
    unit       = "absolute-number",
    isReserved = false,
    order      = 3,
    fncheck    = function (self, z, _) --> boolean, err
        if z >= 10 then
            return true, nil
        else
            return false, "[OutOfRange] too small value for quietzone_factor"
        end
    end,
}

-- create vbar objects
function Code128:config() --> ok, err
    -- build Vbar object for the start/stop symbol
    local mod = self.xdim
    local sc = self._codeset.stopChar -- build the stop char
    local n = self._int_def_bar[sc]
    local Vbar = self._libgeo.Vbar -- Vbar class
    self._vbar = {}
    local b = self._vbar
    b[sc] = Vbar:from_int(n, mod, true)
    return true, nil
end

-- utility functions

-- the number of consecutive digits from the index 'i' in the code array
local function count_digits_from(arr, i)
    local start = i
    local dim = #arr 
    while i <= dim and (arr[i] > 47 and arr[i] < 58) do
        i = i + 1
    end
    return i - start
end

-- evaluate the check digit of the data representation
local function check_digit(code)
    local sum = code[1] -- this is the start character
    for i = 2, #code do
        sum = sum + code[i]*(i-1)
    end
    return sum % 103
end

-- return a pair of boolean the first one is true
-- if a control char and a lower case char occurs in the data
-- and the second one is true if the control char occurs before
-- the lower case char
local function ctrl_or_lowercase(pos, data) --> boolean, boolean|nil
    local len = #data
    local ctrl_occur, lower_occur = false, false
    for i = pos, len do
        local c = data[i]
        if (not ctrl_occur) and (c >= 0 and c < 32) then
            -- [0,31] control chars
            if lower_occur then
                return true, false -- lowercase char < ctrl char
            else
                ctrl_occur = true
            end
        elseif (not lower_occur) and (c > 95 and c < 128) then
            -- [96, 127] lower case chars
            if ctrl_occur then
                return true, true -- ctrl char < lowercase char
            else
                lower_occur = true
            end
        end
    end
    return false -- no such data
end

-- encode the provided char against a codeset
-- in the future this function will consider FN data
local function encode_char(t, codesetAorB, char_code, codesetA)
    local code
    if codesetAorB == codesetA then -- codesetA
        if char_code < 32 then
            code = char_code + 64
        elseif char_code > 31 and char_code < 96 then
            code = char_code - 32
        else
            error("Not implemented or wrong code")
        end
    else -- codesetB
        if char_code > 31 and char_code < 128 then
            code = char_code - 32
        else
            error("Not implemented or wrong code")
        end
    end
    t[#t + 1] = code
end

-- encode the message in a sequence of Code128 symbol minimizing its lenght
local function encode128(arr, codeset, switch) --> data, err :TODO:
    local res = {} -- the result array (the check character will be appended)
    -- find the Start Character A, B, or C
    local cur_codeset
    local ndigit = count_digits_from(arr, 1)
    local len = #arr
    --local no_ctrl_lower_char
    if (ndigit == 2 and len == 2) or ndigit > 3 then -- start char code C
        cur_codeset = codeset.C
    else
        local ok, ctrl_first = ctrl_or_lowercase(1, arr)
        if ok and ctrl_first then
            cur_codeset = codeset.A
        else
            cur_codeset = codeset.B
        end
    end
    res[#res + 1] = cur_codeset
    local pos = 1 -- symbol's index to encode
    while pos <= len do
        if cur_codeset == codeset.C then
            if arr[pos] < 48 or arr[pos] > 57 then -- not numeric char
                local ok, ctrl_first = ctrl_or_lowercase(pos, arr)
                if ok and ctrl_first then
                    cur_codeset = codeset.A
                else
                    cur_codeset = codeset.B
                end
                res[#res + 1] = switch[codeset.C][cur_codeset]
            else
                local imax = pos + 2*math.floor(ndigit/2) - 1
                for idx = pos, imax, 2 do
                    res[#res + 1] = (arr[idx] - 48)*10 + arr[idx+1] - 48
                end
                pos = pos + imax
                ndigit = ndigit - imax
                if ndigit == 1 then
                    -- cur_codeset setup
                    local ok, ctrl_first = ctrl_or_lowercase(pos + 1, arr)
                    if ok and ctrl_first then
                        cur_codeset = codeset.A
                    else
                        cur_codeset = codeset.B
                    end
                    res[#res + 1] = switch[codeset.C][cur_codeset]
                end
            end
        else --- current codeset is A or B
            if ndigit > 3 then
                if ndigit % 2 > 1 then -- odd number of digits
                    encode_char(res, cur_codeset, arr[pos], codeset.A)
                    pos = pos + 1
                    ndigit = ndigit - 1
                end
                res[#res + 1] = switch[cur_codeset][codeset.C]
                cur_codeset = codeset.C
            elseif (cur_codeset == codeset.B) and
                (arr[pos] >= 0 and arr[pos] < 32) then -- ops a control char
                local ok, ctrl_first = ctrl_or_lowercase(pos + 1, arr)
                if ok and (not ctrl_first) then -- shift to codeset A
                    res[#res + 1] = codeset.shift
                    encode_char(res, codeset.A, arr[pos], codeset.A)
                    pos = pos + 1
                    ndigit = count_digits_from(pos, arr)
                else -- switch to code set A
                    res[#res + 1] = switch[cur_codeset][codeset.A]
                    cur_codeset = codeset.A
                end
            elseif (cur_codeset == codeset.A) and
                (arr[pos] > 95 and arr[pos] < 128) then -- ops a lower case char
                local ok, ctrl_first = ctrl_or_lowercase(pos+1, arr)
                if ok and ctrl_first then -- shift to codeset B
                    res[#res + 1] = codeset.shift
                    encode_char(res, codeset.B, arr[pos], codeset.A)
                    pos = pos + 1
                    ndigit = count_digits_from(arr, pos)
                else -- switch to code set B
                    res[#res + 1] = switch[cur_codeset][codeset.B]
                    cur_codeset = codeset.B
                end
            else
                -- insert char
                encode_char(res, cur_codeset, arr[pos], codeset.A)
                pos = pos + 1
                ndigit = count_digits_from(arr, pos)
            end
        end
    end
    res[#res + 1] = check_digit(res)
    res[#res + 1] = codeset.stopChar
    return res
end

-- Code 128 internal functions used by Barcode costructors

function Code128:_check_char(c) --> elem, err
    if type(c) ~= "string" or #c ~= 1 then
        return nil, "[InternalErr] invalid char"
    end
    local b = string.byte(c)
    if b > 127 then
        local fmt = "[unimplemented] the '%d' is an ASCII extented char"
        return nil, string.format(fmt, c)
    end
    return b, nil
end

function Code128:_check_digit(n) --> elem, err
    if type(n) ~= "number" then
        return nil, "[InternalErr] invalid digit"
    end
    return n + 48, nil
end

function Code128:_finalize() --> ok, err
    local chr = assert(self._code_data, "[InternalErr] '_code_data' field is nil")
    local data, err = encode128(chr, self._codeset, self._switch)
    if err then return false, err end
    -- load dynamically required Vbar objects
    local vbar = self._vbar
    local oVbar = self._libgeo.Vbar
    for _, c in ipairs(data) do
        if not vbar[c] then
            local n = self._int_def_bar[c]
            local mod = self.xdim
            vbar[c] = oVbar:from_int(n, mod, true)
        end
    end
    self._enc_data = data
    return true, nil
end

-- Drawing into the provided channel the geometrical barcode data
-- tx, ty is the optional translator vector
-- the function return the canvas reference to allow call chaining
function Code128:append_ga(canvas, tx, ty) --> canvas
    local xdim, h = self.xdim, self.ydim
    local sw = 11*xdim -- the width of a symbol
    local data = self._enc_data
    local w = #data * sw + 2 * xdim -- total symbol width
    local ax, ay = self.ax, self.ay
    local x0 = (tx or 0) - ax * w
    local y0 = (ty or 0) - ay * h
    local x1 = x0 + w
    local y1 = y0 + h
    local xpos = x0
    -- drawing the symbol
    local err
    err = canvas:start_bbox_group()
    assert(not err, err)
    for _, c in ipairs(data) do
        local vb = self._vbar[c]
        err = canvas:encode_Vbar(vb, xpos, y0, y1)
        assert(not err, err)
        xpos = xpos + sw
    end
    -- bounding box setting
    local qz = self.quietzone_factor * xdim
    -- { xmin, ymin, xmax, ymax }
    err = canvas:stop_bbox_group(x0 - qz, y0, x1 + qz, y1)
    assert(not err, err)
    return canvas
end

return Code128
--