Skip to content

Instantly share code, notes, and snippets.

@pgundlach
Created December 23, 2011 09:48
Show Gist options
  • Save pgundlach/1513746 to your computer and use it in GitHub Desktop.
Save pgundlach/1513746 to your computer and use it in GitHub Desktop.
barcodes with LuaTeX
module(...,package.seeall)
local add_checksum_if_necessary, mkpattern, split_number, calculate_unit, pattern_to_wd_dp
function generate_barcode( str )
-- If we only pass 12 digits, the 13th will be added
str = add_checksum_if_necessary(str)
-- The smallest bar/gap is 1/7th the width of a digit.
-- It is font dependent.
local u = calculate_unit()
-- We start with the hbox for the bars:
tex.sprint(
[[\newbox\barcodebox\setbox\barcodebox\hbox{%]]
)
-- The pattern is a string of numbers that represent
-- the width of a bar or a gap. 0 is a special marker
-- for a longer bar of width 1. The widths are
-- multiplied by 1/7th of the width of a digit, because
-- the sum of the widths of a single digit add up to 7.
-- A sample pattern starts with:
-- 80103211112312132113231132111010132...
local pattern = mkpattern(str)
-- For each entry in the pattern we generate a gap or a
-- bar of the width denoted by that entry. A depth > 0
-- is used for the bars in the middle and both sides.
-- This is technically not necessary, but added to have
-- visually pleasing barcodes.
local wd,dp -- width and depth of a bar
for i=1,string.len(pattern) do
wd,dp = pattern_to_wd_dp(pattern,i)
-- The even entries are the vertical bars (vrules),
-- the odd ones are the gaps (kerns).
if i % 2 == 0 then
tex.sprint(string.format(
[[\vrule width %dsp height 2cm depth %s]],
wd * u,dp))
else
tex.sprint(string.format(
[[\kern %dsp]],wd * u))
end
end
-- we now have the hbox with the bars and
-- add the hbox with the numbers.
tex.sprint([[}\vbox{\hsize\wd\barcodebox\box\barcodebox\kern -1.7mm\hbox{%]])
-- the numbers below the barcode are split into three
-- groups: one in front of the first bar, the first
-- half of the other digits are left of the center
-- bar, and the remaining digits are to the right
-- of the center bar.
local first,second,third = split_number(str)
tex.sprint(string.format(
[[%s\kern %dsp %s\kern %dsp%s}}]],
first, 5 * u, second, 4 * u, third ))
end
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. 3211 for
-- example means a gap of three units, a bar two units
-- wide, another gap of width one and a bar of width
-- one.
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. An entry 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-"}
-- We convert the number string into an array
local number = {}
for i=1,string.len(str) do
number[i] = tonumber(string.sub(str,i,i))
end
-- The first number in a barcode determines how the
-- next six number patterns are displayed
local prefix = table.remove(number,1)
local mirror_str = mirror_t[prefix + 1]
-- The variable pattern will hold the constructed
-- pattern. We start with a gap that is wide enough
-- for the first digit in the barcode and the special
-- code 111, here written as 010 as a signal to
-- create longer rules later.
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
-- The even entries are the rules, the odd ones
-- are the gaps
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
-- barcode_top will become the vbox as in the
-- first solution
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 now has three entries: the hbox
-- from the rules and kerns, the kern of -1.7mm
-- and the hbox with the digits below the bars.
barcode_top = add_to_nodelist(
barcode_top,barcode_bottom)
local bc = node.vpack(barcode_top)
-- node.write() puts a vbox into the PDF
node.write(bc)
end
function add_to_nodelist( head,entry )
if head then
-- add the entry to the end of the
-- nodelist and adjust prev/next pointers
local tail = node.tail(head)
tail.next = entry
entry.prev = tail
else
-- no nodelist yet, so we just return the new entry
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
\ProvidesPackage{ltxbarcode}
\directlua{require("ltxbarcode")}
\newcommand\barcode[1]{%
\directlua{ltxbarcode.generate_barcode("\luatexluaescapestring{#1}")}}
\newcommand\barcodelua[1]{%
\directlua{ltxbarcode.generate_barcode_lua("#1")}}
\documentclass{article}
\usepackage{ltxbarcode}
\begin{document}
\thispagestyle{empty}
\texttt{\barcode{424200251816}}
\barcode{590123412345}
\barcodelua{424200251816}
\end{document}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment