summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/scripts/barracuda/lib-barcode/brcd-code128.lua
blob: 294162f6b69e87a6734ae1088202a541b5f045d4 (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
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
-- Code128 barcode generator module
-- Copyright (C) 2019-2022 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.6",
    _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, -- the last number 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_order = {
    "xdim",
    "ydim",
    "quietzone_factor",
}
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,
    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,
    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,
    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 repo = self._libgeo.Archive:new()
    self._vbar_archive = repo
    local Vbar = self._libgeo.Vbar
    repo:insert(Vbar:from_int(n, mod, true), 106)
    return true, nil
end

-- utility functions

-- evaluate the check digit of encoded data
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
    code[#code + 1] = sum % 103
end

local function isdigit(char) --> true/false
    assert(char)
    return (char > 47) and (char < 58)
end

local function iscontrol(char) --> true/false
    assert(char)
    -- [0,31] control chars interval
    return char < 32
end

local function islower(char)
    assert(char)
    -- [96, 127] lower case chars
    return (char > 95) and (char < 128)
end

-- count digits
local function digits_group(data, len) --> counting digits
    local res = {}
    local last = false
    for i = len, 1, -1 do
        local digit = isdigit(data[i])
        if last then
            if digit then
                res[i] = res[i+1] + 1
            else
                res[i] = 0
                last = false
            end
        else
            if digit then
                res[i] = 1
                last = true
            else
                res[i] = 0
            end
        end
    end
    return res
end

-- find the first char in the codeset that adhere
-- with the function argument 'filter'
local function indexof_char_by(filter, arr, counter) --> index or nil
    counter = counter or 1
    local char = arr[counter]
    while char do
        if filter(char) then
            return counter
        end
        counter = counter + 1
        char = arr[counter]
    end
end

-- determine the Start character
local function start_codeset_char(codeset, arr, len)
    assert(len>0)
    local ctrl = indexof_char_by(iscontrol, arr)
    local lowc = indexof_char_by(islower, arr)
    local t_digits = digits_group(arr, len)
    local first_digits = t_digits[1]
    -- case 1
    if (len == 2) and (first_digits == 2) then
        return lowc, ctrl, codeset.C, t_digits
    end
    -- case 2
    if first_digits >= 4 then
        return lowc, ctrl, codeset.C, t_digits
    end
    -- case 3
    local cs = codeset.B
    if (ctrl and lowc) and (ctrl < lowc) then
        cs = codeset.A
    end
    -- case 4
    return lowc, ctrl, cs, t_digits
end

-- codeset A char
local function encode_char_A(res, char)
    local code
    if char < 32 then
        code = char + 64
    elseif char > 31 and char < 96 then
        code = char - 32
    else
        error("[InternalErr] Not implemented or wrong code" )
    end
    res[#res + 1] = code
end
-- codeset B char
local function encode_char_B(res, char)
    local code
    if char > 31 and char < 128 then
        code = char - 32
    else
        error("[InternalErr] Not implemented or wrong code")
    end
    res[#res + 1] = code
end

-- every function encodes a group of chars
-- A = 103, -- Start char for Codeset A
-- B = 104, -- Start char for Codeset B
-- C = 105, -- Start char for Codeset C
local encode_codeset = {
    -- A
    [103] = function (codeset, res, data, index, t_digits, i_low, _ctrl)
        assert(t_digits[index] < 4, "[InternalErr] in codeset A digits must be less than 4")
        while data[index] do
            local char = data[index]
            if i_low and islower(char) then -- ops a lower case char
                local next = data[index + 1]
                local next_next = data[index + 2]
                if next and next_next then
                    -- case 5a
                    if iscontrol(char) and islower(next_next) then
                        res[#res+1] = codeset.shift
                        encode_char_B(res, char)
                        index = index + 1
                    end
                else
                    -- case 5b
                    return codeset.B, index
                end
            else
                local digits = t_digits[index]
                if digits > 3 then -- go to codeset C
                    if (digits % 2) == 1 then -- odd number of a group of digits
                        encode_char_A(res, char)
                        digits = digits - 1
                        index = index + 1
                    end
                    return codeset.C, index
                end
                encode_char_A(res, char)
                index = index + 1
            end
        end
        return nil, index
    end,
    -- B
    [104] = function (codeset, res, data, index, t_digits, _low, i_ctrl)
        assert(t_digits[index] < 4, "[InternalErr] in codeset B digits must be less than 4")
        while data[index] do
            local char = data[index]
            if i_ctrl and iscontrol(char) then -- ops a control char
                local next = data[index + 1]
                local next_next = data[index + 2]
                if next and next_next then
                    -- case 4a
                    if islower(next) and iscontrol(next_next) then
                        res[#res+1] = codeset.shift
                        encode_char_A(res, char)
                        index = index + 1
                    end
                else
                    -- case 4b
                    return codeset.A, index
                end
            else
                local digits = t_digits[index]
                if digits > 3 then -- go to codeset C
                    if (digits % 2) == 1 then -- odd number of a group of digits
                        encode_char_B(res, char)
                        digits = digits - 1
                        index = index + 1
                    end
                    return codeset.C, index
                end
                encode_char_B(res, char)
                index = index + 1
            end
        end
        return nil, index
    end,
    -- C
    [105] = function (codeset, res, data, index, t_digits, i_low, i_ctrl)
        local digits = t_digits[index]
        assert(digits > 1, "[InternalErr] at least a pair of digit is required")
        while digits > 1 do
            local d1, d2 = data[index], data[index + 1]
            res[#res + 1] = (d1 - 48)*10 + d2 - 48
            digits = digits - 2
            index = index + 2
        end
        local res_codeset
        if i_ctrl and i_low then
            local ctrl = indexof_char_by(iscontrol, data, index)
            local low = indexof_char_by(islower, data, index)
            if low and (ctrl < low) then
                res_codeset = codeset.A
            end
        else
            res_codeset = codeset.B
        end
        return res_codeset, index
    end,
}

-- encode the message in a sequence of Code128 symbol minimizing the symbol width
local function encode128(arr, codeset, switch) --> data, err
    local len = #arr
    local i_low, i_ctrl, cur_codeset, t_digits = start_codeset_char(codeset, arr, len)
    local res = {cur_codeset} -- the result array (the check character will be appended later)
    local switch_codeset
    local cur_index = 1
    while cur_index <= len do
        if switch_codeset then
            res[#res+1] = switch[cur_codeset][switch_codeset]
            cur_codeset = switch_codeset
        end
        local fn = assert(encode_codeset[cur_codeset], "[InternalErr] cur_codeset is "..(cur_codeset or "nil"))
        switch_codeset, cur_index = fn(codeset, res, arr, cur_index, t_digits, i_low, i_ctrl)
    end
    check_digit(res)
    return res, nil
end

-- Code 128 internal functions used by Barcode costructors

function Code128:_process_char(c) --> char_code, char_text, err
    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, c, nil
end

function Code128:_process_digit(n) --> digit_code, char_text, err
    local res = n + 48
    return res, string.char(res), 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
    self._enc_data = data
    -- dynamically load the required Vbar objects
    local Repo = self._vbar_archive
    local Vbar = self._libgeo.Vbar
    for _, c in ipairs(data) do
        if not Repo:contains_key(c) then
            local n = self._int_def_bar[c]
            local mod = self.xdim
            assert(Repo:insert(Vbar:from_int(n, mod, true), c))
        end
    end
    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) --> bbox
    local data = self._enc_data
    local Repo = self._vbar_archive
    local queue = self._libgeo.Vbar_queue:new()
    for _, c in ipairs(data) do
        queue = queue + Repo:get(c)
    end
    local stop = self._codeset.stopChar
    queue = queue + Repo:get(stop)
    local xdim, h = self.xdim, self.ydim
    local ns = #data + 1
    local w = (11*ns + 2) * xdim -- total symbol width
    local ax, ay = self.ax, self.ay
    local x0 = tx - ax * w
    local y0 = ty - ay * h
    local x1 = x0 + w
    local y1 = y0 + h
    -- drawing the symbol
    assert(canvas:encode_disable_bbox())
    assert(canvas:encode_vbar_queue(queue, x0, y0, y1))
    -- bounding box setting
    local qz = self.quietzone_factor * xdim
    -- { xmin, ymin, xmax, ymax }
    assert(canvas:encode_set_bbox(x0 - qz, y0, x1 + qz, y1))
    assert(canvas:encode_enable_bbox())
    return {x0, y0, x1, y1, qz, nil, qz, nil,}
end

return Code128