%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
