Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

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.

share|improve this question
2  
Would this help? –  morbusg Apr 4 '12 at 12:28
    
@morbusg: Yes. My android barcode scanner likes it. –  Martin Schröder Apr 4 '12 at 12:42
add comment

1 Answer

up vote 12 down vote accepted

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}
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.