%D \module %D [ file=meta-imp-barcodes, %D version=2025.07.02, %D title=\METAPOST\ Graphics, %D subtitle=Barcodes, %D author=Hans Hagen, %D date=\currentdate, %D copyright={PRAGMA ADE \& \CONTEXT\ Development Team}] %C %C This module is part of the \CONTEXT\ macro||package and is %C therefore copyrighted by \PRAGMA. See mreadme.pdf for %C details. %D There are many barcodes and till now we used zint (the program or library) for %D generating them. For QR codes at some poitn we built-in a generator because we %D also wanted to have some fun with error messages. Of course we could use zint but %D because it had chaged a few times and because I saw new pushes I decided at that %D point to just add a dedicated solution. After all, QR codes are widely used. %D %D So how about other codes? How stable is a solution? Of course we can't implement %D all these barcodes but the most popular we can. That makes us a bit more %D independent and if needed users can always fall back on zint anyway. Compiling %D zint into the binary makes no sense. And just in case the API changes (or one %D doesn't have the library) a few fallbacks make sense. For datamatrix we could %D actually use a stripped down (encoding only) library but who uses that in the %D perspective of typesetting. %D %D The \WIKIPEDIA\ is a good source of information so if you need more ask on the %D mailing list and privide the right information. Also, the codes below are not %D yet tested (only compared to the pictures on \WIKIPEDIA) so when you use them, %D please test! \startluacode local tonumber = tonumber local gmatch = string.gmatch local sub = string.sub local byte = string.byte local char = string.char local insert = table.insert local concat = table.concat local split = utf.split local newbytemap = bytemap.new local setgray = bytemap.setgray local fillgray = bytemap.fillgray local barcodes = document.barcodes or { } document.barcodes = barcodes local trace = false local generators = { } trackers.register("metapost.barcode", function(v) trace = v end) do -- code128 -- There are three code sets. We use "set b" because it has the lowercase -- characters. When we have only numbers, we can decide to support "set c" -- (double digits). It makes little sense to go for "set a" or switch sets mid -- stream. The table is taken from Wikipedia. We can of course implement the -- other sets and even adaptive coding but it makes little sense here. local encoding = { ["start"] = { 104, "11010010000" }, ["stop"] = { 0, "1100011101011" }, -- no value [" "] = { 0, "11011001100" }, ["!"] = { 1, "11001101100" }, ['"'] = { 2, "11001100110" }, ["#"] = { 3, "10010011000" }, ["$"] = { 4, "10010001100" }, ["%"] = { 5, "10001001100" }, ["&"] = { 6, "10011001000" }, ["'"] = { 7, "10011000100" }, ["("] = { 8, "10001100100" }, [")"] = { 9, "11001001000" }, ["*"] = { 10, "11001000100" }, ["+"] = { 11, "11000100100" }, [","] = { 12, "10110011100" }, ["-"] = { 13, "10011011100" }, ["."] = { 14, "10011001110" }, ["/"] = { 15, "10111001100" }, ["0"] = { 16, "10011101100" }, ["1"] = { 17, "10011100110" }, ["2"] = { 18, "11001110010" }, ["3"] = { 19, "11001011100" }, ["4"] = { 20, "11001001110" }, ["5"] = { 21, "11011100100" }, ["6"] = { 22, "11001110100" }, ["7"] = { 23, "11101101110" }, ["8"] = { 24, "11101001100" }, ["9"] = { 25, "11100101100" }, [":"] = { 26, "11100100110" }, [";"] = { 27, "11101100100" }, ["<"] = { 28, "11100110100" }, ["="] = { 29, "11100110010" }, [">"] = { 30, "11011011000" }, ["?"] = { 31, "11011000110" }, ["@"] = { 32, "11000110110" }, ["A"] = { 33, "10100011000" }, ["B"] = { 34, "10001011000" }, ["C"] = { 35, "10001000110" }, ["D"] = { 36, "10110001000" }, ["E"] = { 37, "10001101000" }, ["F"] = { 38, "10001100010" }, ["G"] = { 39, "11010001000" }, ["H"] = { 40, "11000101000" }, ["I"] = { 41, "11000100010" }, ["J"] = { 42, "10110111000" }, ["K"] = { 43, "10110001110" }, ["L"] = { 44, "10001101110" }, ["M"] = { 45, "10111011000" }, ["N"] = { 46, "10111000110" }, ["O"] = { 47, "10001110110" }, ["P"] = { 48, "11101110110" }, ["Q"] = { 49, "11010001110" }, ["R"] = { 50, "11000101110" }, ["S"] = { 51, "11011101000" }, ["T"] = { 52, "11011100010" }, ["U"] = { 53, "11011101110" }, ["V"] = { 54, "11101011000" }, ["W"] = { 55, "11101000110" }, ["X"] = { 56, "11100010110" }, ["Y"] = { 57, "11101101000" }, ["Z"] = { 58, "11101100010" }, ["["] = { 59, "11100011010" }, ["\\"] = { 60, "11101111010" }, ["]"] = { 61, "11001000010" }, ["^"] = { 62, "11110001010" }, ["_"] = { 63, "10100110000" }, ["`"] = { 64, "10100001100" }, ["a"] = { 65, "10010110000" }, ["b"] = { 66, "10010000110" }, ["c"] = { 67, "10000101100" }, ["d"] = { 68, "10000100110" }, ["e"] = { 69, "10110010000" }, ["f"] = { 70, "10110000100" }, ["g"] = { 71, "10011010000" }, ["h"] = { 72, "10011000010" }, ["i"] = { 73, "10000110100" }, ["j"] = { 74, "10000110010" }, ["k"] = { 75, "11000010010" }, ["l"] = { 76, "11001010000" }, ["m"] = { 77, "11110111010" }, ["n"] = { 78, "11000010100" }, ["o"] = { 79, "10001111010" }, ["p"] = { 80, "10100111100" }, ["q"] = { 81, "10010111100" }, ["r"] = { 82, "10010011110" }, ["s"] = { 83, "10111100100" }, ["t"] = { 84, "10011110100" }, ["u"] = { 85, "10011110010" }, ["v"] = { 86, "11110100100" }, ["w"] = { 87, "11110010100" }, ["x"] = { 88, "11110010010" }, ["y"] = { 89, "11011011110" }, ["z"] = { 90, "11011110110" }, ["{"] = { 91, "11110110110" }, ["|"] = { 92, "10101111000" }, ["}"] = { 93, "10100011110" }, ["~"] = { 94, "10001011110" }, ["DEL"] = { 95, "10111101000" }, } generators["code128"] = function(str) local list = split(str) local start = encoding.start[2] local stop = encoding.stop[2] local check = "" local valid = 0 local checksum = encoding.start[1] for i=1,#list do local c = list[i] local e = encoding[c] if e then valid = valid + 1 checksum = checksum + i * e[1] end end checksum = checksum % 103 for k, v in next, encoding do if v[1] == checksum then check = v[2] break end end -- We could just append the string and do a replace by 0 or 1 -- but for tracing it is nicer to just set the bytes. local quiet = 10 local width = quiet -- mandate quiet + 11 -- start + 11 * valid + 11 -- check + 13 -- stop + quiet -- mandate quiet local height = 1 -- we scale afterwards local depth = 1 -- grayscale local column = quiet -- llcorner (0+quiet,0) local row = 0 -- a single row anyway local bytes = newbytemap(width,height,depth) fillgray(bytes,255) local function set(str,value) for s in gmatch(str,".") do if s == '1' then setgray(bytes,column,row,value) end column = column + 1 end end local vfence = trace and 150 or 0 local vcheck = trace and 100 or 0 local vcharacter = trace and 0 or 0 set(start,vfence) for i=1,#list do local c = list[i] local e = encoding[c] if e then set(e[2],vcharacter) end end set(check,vcheck) set(stop,vfence) return { bytes = bytes } end end do -- ean13 etc local left_odd = { [0] = -- L "0001101", "0011001", "0010011", "0111101", "0100011", "0110001", "0101111", "0111011", "0110111", "0001011", } local left_even = { [0] = -- G "0100111", "0110011", "0011011", "0100001", "0011101", "0111001", "0000101", "0010001", "0001001", "0010111", } local right = { [0] = -- R "1110010", "1100110", "1101100", "1000010", "1011100", "1001110", "1010000", "1000100", "1001000", "1110100", } local patterns_13 = { [0] = "LLLLLL", "LLGLGG", "LLGGLG", "LLGGGL", "LGLLGG", "LGGLLG", "LGGGLL", "LGLGLG", "LGLGGL", "LGGLGL", } local patterns_05 = { [0] = "GGLLL", "GLGLL", "GLLGL", "GLLLG", "LGGLL", "LLGGL", "LLLGG", "LGLGL", "LGLLG", "LLGLG", } local patterns_02 = { [0] = "LL", "LG", "GL", "GG" } local zero = byte("0") local function checksum_one(list) local sum = 0 for i=1,#list do local digit = tonumber(list[i]) if digit then list[i] = digit if (i % 2) == 0 then sum = sum + digit * 3 else sum = sum + digit end else return 0 end end return (10 - (sum % 10)) % 10 end local function checksum_two(list) local sum = 0 for i=1,#list do local digit = tonumber(list[i]) if digit then list[i] = digit if (i % 2) == 0 then sum = sum + digit * 3 else sum = sum + digit * 9 end else return 0 end end return sum % 10 end generators["ean13"] = function(str) local first, second = string.match(str,"^(.-)%-(.-)$") if not first then first = str second = "" end local list_one = split(first) local list_two = split(second) local sum_one = checksum_one(list_one) local sum_two = checksum_two(list_two) if #list_one == 12 then list_one[13] = sum_one else list_one = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } end local quiet = 10 local distance = -10 -- nicer local extra = false if #list_two == 5 then extra = 5 -- ean-5 elseif #list_two == 2 then extra = 2 -- ean-2 else extra = false end local width_one = quiet -- quiet + 3 -- left fence + 6 * 7 -- first half + 5 -- middle fence + 6 * 7 -- second half + 3 -- right fence + quiet -- quiet local width_two = not extra and 0 or quiet -- quiet + 5 -- left fence + extra * 7 -- digits + extra * 2 - 2 -- separators + quiet -- quiet local width = width_one + (extra and (distance + width_two) or 0) local height = 100 local depth = 1 local column = quiet -- llcorner (0+quiet,0) local row = 0 -- a single row anyway local bytes = newbytemap(width,height,depth) -- print(width_one,distance,width_two,width) fillgray(bytes,255) local vfence = trace and 150 or 0 local vcheck = trace and 100 or 0 local vcharacter = trace and 0 or 0 local function set(str,n,value) for s in gmatch(str,".") do if s == '1' then for i=n-1,height-1 do setgray(bytes,column,i,value) end end column = column + 1 end end local pattern = patterns_13[list_one[1]] set("101",0,vfence) for i=2,7 do local li = list_one[i] local pi = sub(pattern,i-1,i-1) local left = pi == "L" and left_odd or left_even set(left[li],10,vcharacter) end set("01010",0,vfence) for i=8,13 do set(right[list_one[i]],10,i == 13 and vcheck or vcharacter) end set("101",0,vfence) if extra then column = width_one + distance + quiet height = 70 set("01011",10,vfence) if #list_two == 2 then pattern = patterns_02[(list_two[1] * 10 + list_two[2]) % 4] for i=1,2 do local li = list_two[i] local pi = sub(pattern,i,i) local left = pi == "L" and left_odd or left_even set(left[li],10,vcharacter) if i < 1 then set("01",10,vfence) end end else -- 5 pattern = patterns_05[sum_two] for i=1,5 do local li = list_two[i] local pi = sub(pattern,i,i) local left = pi == "L" and left_odd or left_even set(left[li],10,i == 5 and vcheck or vcharacter) if i < 5 then set("01",10,vfence) end end end end insert(list_one,2," ") insert(list_one,9," ") insert(list_one," ") insert(list_one,">") -- Ok, a somewhat curious way to communicate information to the MetaPost -- end. But it's not like we're into making a masterpiece of hybrid code -- here. return { bytes = bytes, text = concat(list_one), more = list_two and concat(list_two) or "", widthone = width_one, distance = distance, widthtwo = width_two, width = width, } end end local barstate = false function mp.barcode(index,code,str) local generate = generators[code] if generate then barstate = generate(str) if barstate then local bytes = barstate.bytes -- inspect(bytemap.totable(bytes)) mp.newbytemap(index,bytes) end end end function mp.barstate(key) mp.inject.whatever(barstate and barstate[key] or "") end \stopluacode \definefont[EANfont][DejavuSansMono @ 10bp] \startMPdefinitions def mfun_barcode_code_one_two_eight(expr txt, width, height) = lua.mp.barcode(1,"code128",txt) ; draw bytemap 1 xysized (width, height) withbytemasked 255 ; enddef ; def mfun_barcode_ean_one_three(expr txt, width, height) = lua.mp.barcode(1,"ean13",txt) ; draw image ( save p, q, w, b ; picture p, q ; numeric w ; boolean b ; b := lua.mp.barstate("more") <> "" ; p := textext.llft("\EANfont " & lua.mp.barstate("text")) ; w := if b : (lua.mp.barstate("widthone") * width/lua.mp.barstate("width")) else : width fi ; if b : q := textext.top("\EANfont " & lua.mp.barstate("more")) ; fi ; draw bytemap 1 xysized (width,height) shifted (0,-height/10 + height/20) withbytemasked 255 ; draw p xsized w shifted (w,0) ; % show(state); if b : draw q ysized bbheight(p) shifted ( ( lua.mp.barstate("widthone") + lua.mp.barstate("distance") + lua.mp.barstate("widthtwo") / 2 ) * width/lua.mp.barstate("width"), .7height ) ; fi ; ) % yshifted height/20 ; enddef ; def mfun_barcode(expr code, txt, width, height) = if code == "ean13" : mfun_barcode_ean_one_three(txt, width, height) ; elseif code == "code128" : mfun_barcode_code_one_two_eight(txt, width, height) ; else : draw textext ("invalid barcode") ; fi enddef ; \stopMPdefinitions \starttexdefinition permanent protected barcode [#1] \begingroup \getdummyparameters[ code=code128, text=0123456789, width=6cm, height=2cm, #1 ] \startMPcode mfun_barcode( "\dummyparameter{code}", "\dummyparameter{text}", \dummyparameter{width}, \dummyparameter{height} ) ; \stopMPcode \endgroup \stoptexdefinition \continueifinputfile{meta-imp-barcodes.mkxl} \starttext \enabletrackers[metapost.barcode] \setupTEXpage[background=color,backgroundcolor=blue] \startTEXpage[offset=1ts] \barcode[code=code128,text={Wikipedia}] \stopTEXpage \startTEXpage[offset=1ts] \barcode[code=code128,text={Wikipedia},width=4cm,height=1cm] \stopTEXpage \startTEXpage[offset=1ts] \barcode[code=ean13,text={400399415548}] \stopTEXpage \startTEXpage[offset=1ts] \dontleavehmode \barcode[code=ean13,text={400399415548},width=9cm] \vskip1cm \barcode[code=ean13,text={400399415548},width=9cm,height=1cm] \stopTEXpage \startTEXpage[offset=1ts] \barcode[code=ean13,text={400399415548-54495},width=9cm,height=3cm] \vskip1cm \barcode[code=ean13,text={400399415548-12},width=9cm,height=3cm] \stopTEXpage \stoptext