9

I'm looking for way to generate EAN barcodes with LuaLaTeX. I'm aware of this question; the solution to that needs pstricks, which won't work easily with LuaLaTeX (I don't want to install GhostScript on the machines). And I know the code128 package, but I need EAN. Surely this is possible with TikZ?

EDIT: Since I'm using LuaTeX, I'm equally interested in a solution using mplib.

2
  • 2
    Would this help?
    – morbusg
    Apr 4, 2012 at 12:28
  • @morbusg: Yes. My android barcode scanner likes it. Apr 4, 2012 at 12:42

1 Answer 1

14

A rather long answer, but this does not need any external packages such as tikz or MetaPost. If you want a detailed explanation, you should get the next issue of the TUGBoat or DTK (German). A bonus feature: it will calculate the checksum if not given.

I'll show a picture of the resulting barcode first:

sample barcode

\documentclass[a4paper]{article}
\usepackage{luacode}
\begin{document}
\newcommand\barcodelua[1]{%
  \directlua{generate_barcode_lua("#1")}}

\begin{luacode*}

local add_checksum_if_necessary, mkpattern, split_number, calculate_unit, pattern_to_wd_dp



function calculate_unit()
  -- The relative widths of a digit represented by the barcode add up to 7.
  local currentfont = font.fonts[font.current()]
  local digit_zero = currentfont.characters[48]
  return digit_zero.width / 7
end

function pattern_to_wd_dp( pattern,pos )
  local wd,dp
  wd = tonumber(string.sub(pattern,pos,pos))
  if wd == 0 then
    dp = "2mm"
    wd = 1
  else
    dp = "0mm"
  end
  return wd,dp
end

function add_checksum_if_necessary( str )
  if string.len(str) == 13 then
    return str
  end

  local sum = 0
  local len = string.len(str)
  for i=len,1,-1 do
    if (len - i ) % 2 == 0 then
      sum = sum + tonumber(string.sub(str,i,i)) * 3
    else
      sum = sum + tonumber(string.sub(str,i,i))
    end
  end
  local checksum = (10 - sum % 10) % 10
  return str .. tostring(checksum)
end

function mkpattern( str )
  -- These are the digits represented by the bars.
  local digits_t = {"3211","2221","2122","1411","1132","1231","1114","1312","1213","3112"}

  -- The first digit is encoded by the appearance of the
  -- next six digits. A value of 1 means that the
  -- generated gaps/bars are to be inverted.
  local mirror_t = {"------","--1-11","--11-1","--111-","-1--11","-11--1","-111--","-1-1-1","-1-11-","-11-1-"}

  -- Convert the digit string into an array.
  local number = {}
  for i=1,string.len(str) do
    number[i] = tonumber(string.sub(str,i,i))
  end

  -- The first digit in a barcode determines how the next six digit patterns are displayed.
  local prefix = table.remove(number,1)
  local mirror_str = mirror_t[prefix + 1]

  local pattern = "8010"
  local digits_str

  for i=1,#number do
    digits_str = digits_t[number[i] + 1]
    if string.sub(mirror_str,i,i) == "1" then
      digits_str = string.reverse(digits_str)
    end
    pattern = pattern .. digits_str
    -- The middle two bars.
    if i==6 then pattern = pattern .. "10101" end
  end
  -- Append the right 111 pattern as above.
  return pattern .. "010"
end

function split_number( str )
  return string.match(str,"(%d)(%d%d%d%d%d%d)(%d%d%d%d%d%d)")
end


local add_to_nodelist, mkrule, mkkern, mkglyph
function generate_barcode_lua( str )
  str = add_checksum_if_necessary(str)

  local u = calculate_unit()
  local nodelist

  local pattern = mkpattern(str)
  local wd,dp
  for i=1,string.len(pattern) do
    wd,dp = pattern_to_wd_dp(pattern,i)
    if i % 2 == 0 then
      nodelist = add_to_nodelist(nodelist,mkrule(wd * u,tex.sp("2cm"),tex.sp(dp)))
    else
      nodelist = add_to_nodelist(nodelist,mkkern(wd * u))
    end
  end
  local barcode_top = node.hpack(nodelist)
  barcode_top = add_to_nodelist(barcode_top,mkkern(tex.sp("-1.7mm")))

  -- The following list holds the displayed digits
  nodelist = nil
  for i,v in ipairs({split_number(str)}) do
    for j=1,string.len(v) do
      nodelist = add_to_nodelist(nodelist,mkglyph(string.sub(v,j,j)))
    end
    if i == 1 then
      nodelist = add_to_nodelist(nodelist,mkkern(5 * u))
    elseif i == 2 then
      nodelist = add_to_nodelist(nodelist,mkkern(4 * u))
    end
  end
 local barcode_bottom = node.hpack(nodelist)
 barcode_top = add_to_nodelist(barcode_top,barcode_bottom)
 local bc = node.vpack(barcode_top)

 node.write(bc)
end

function add_to_nodelist( head,entry )
  if head then
    local tail = node.tail(head)
    tail.next = entry
    entry.prev = tail
  else
    head = entry
  end
  return head
end

function mkrule( wd,ht,dp )
  local r = node.new("rule")
  r.width = wd
  r.height = ht
  r.depth = dp
  return r
end

function mkkern( wd )
  local k = node.new("kern")
  k.kern = wd
  return k
end

function mkglyph( char )
  local g = node.new("glyph")
  g.char = string.byte(char)
  g.font = font.current()
  g.lang = tex.language
  return g
end

\end{luacode*}
\barcodelua{4242002518169}
\end{document}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .