Created
December 23, 2011 09:48
-
-
Save pgundlach/1513746 to your computer and use it in GitHub Desktop.
barcodes with LuaTeX
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
\ProvidesPackage{ltxbarcode} | |
\directlua{require("ltxbarcode")} | |
\newcommand\barcode[1]{% | |
\directlua{ltxbarcode.generate_barcode("\luatexluaescapestring{#1}")}} | |
\newcommand\barcodelua[1]{% | |
\directlua{ltxbarcode.generate_barcode_lua("#1")}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
\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