Skip to content

Instantly share code, notes, and snippets.

@Miouyouyou
Created April 3, 2018 13:37
Show Gist options
  • Save Miouyouyou/a3083b6b51ebfcccaf64ccf38b0cf355 to your computer and use it in GitHub Desktop.
Save Miouyouyou/a3083b6b51ebfcccaf64ccf38b0cf355 to your computer and use it in GitHub Desktop.
Generate EPL code allowing you to print a QRCode with a Zebra 2746e (European and American versions)
; !! IF YOU'RE OPENING THIS FILE ON UNIX/LINUX SYSTEMS, KEEP THE CRLF !!
; !! USE unix2dos IF YOUR EDITOR IS TOO DUMB TO KEEP THEM !!
; This is a comment (Assembly-like comments)
; Set the form length (Distance between each print ??)
Q0001,0
; The label width in dots
q831
; Set the double buffer mode (For what purpose, I have no idea)
rN
; Print speed : [2-6] = [level_selected*25] mm/s -> (2 == 2*25 mm/s) -> 50 mm/s
S2
; Print density (quality. 5 = draft, 10 = normal, 15 = HQ)
D10
; Print from top or bottom (T = TOP - B = BOTTOM)
ZT
; Disable Top of Form Backup (???)
JB
; Disable all options
O
; Move the reference point (offset). Values in points.
R671,0
; Cut position (???)
f100
# This requires the rqrcode GEM
# This does not use the internal QR code generation command
# since this command only exists in Japanese versions of the
# printer.
# Instead, it generates a graphic representing the QR code,
# using the GW command, and prints it.
# To actually print the QR code, you'll need to add the printer
# using CUPS, then use the following command :
# lpr -P TheCUPSPrinterName -o raw test.qr
# This uses a header file provided below. The header file must
# keep the CRLF line endings.
require 'rqrcode' # gem install rqrcode
# In this documentation : pixel and dot are the same thing.
#
# First, in EPL (Eltron Programming Language), every dot (pixel) in a
# picture is encoded on one *bit*.
# Not an octet. A single bit.
# This means that 0xff -> 0b11111111 = 8 dots !
# Obviously pictures are printed in monochrome.
#
# 0 means black, 1 means white.
#
# Now, after instigating how Zebra Designer generated QR-Code pictures,
# it seems that the QR Code is scaled 4 times.
#
# We'll use the same scale here.
# That means that each dot is printed 4 times in width and height.
# So basically, each dot will take half of an octet (0b0000) and each line
# will be printed 4 times.
# Padding will consist of one more pixel when the width is odd.
# 1 byte = 8 dots. Each scaled dot equals 4 dots. Meaning that a
# byte contains 2 scaled dots.
# (odd-number-of-dots / 2 scaled dots per byte -> n + half a byte
# (odd-number-of-dots + 1 dot of padding) / 2 scaled dots per byte -> n bytes)
# Remember that the print support width is tiny, so you cannot scale too
# much.
black = 0
white = 1
gw_data = []
scaling = 4
# QR Codes limits are defined by the level of security applied
# and their initial size.
# Security repeats the pattern more times, making the QR code more
# resilient to time.
# Obviously, repeating the pattern diminishes the available space
# in a given size.
# The different levels are :
# :l => 7% of code can be restored
# :m => 15% of code can be restored
# :q => 25% of code can be restored
# :h => 30% of code can be restored
qr = RQRCode::QRCode.new('My hamster knows kung-fu !', :size => 2, :level => :l)
# "modules" are pixels rows.
pixels_rows = qr.modules
# The width and height of the QR code.
height = pixels_rows.length
# All lines of a QR code have the same width, so
# the first column width == QR code width
width = pixels_rows.first.length
# Padding that will be added to each row in order to deal with
# unused space inside a byte at the end of a row.
# There's other ways to deal with this, but this way is hassle free
dots_per_byte = (8/scaling)
padding = [false] * (width%dots_per_byte)
current_byte = 0 # Current byte value.
current_i = 7 # Current bit inside the current byte value
# Each pixel row contains boolean values.
# True for a pixel, False for no pixel.
pixels_rows.each {|row|
# Add the necessary padding
row.push(*padding)
# p row # This just prints the current padded row, for debugging
current_byte = 0 # Start with an all-black byte
# We draw left to right, so we start from the higher bit in a byte
current_i = 7
# Print each line x times, where x is the scaling
scaling.times {
# Start a line
row.each {|pixel|
# Printing with black ink on white.
pixel_value = (pixel ? black : white)
# Print each pixel x times, where x is the scaling
scaling.times {
# If pixel_value is white, this set the dot to white
# Black pixels are already set up, so this has no effect
# when pixel_value is black.
current_byte |= (pixel_value << current_i)
if (current_i != 0)
# We're still inside a byte.
current_i -= 1 # Let's move to the next bit
else
# We have a full byte, store it.
gw_data << current_byte
# In order to DRY this, refactoring everything into a
# Module or Class is needed.
current_byte = 0
current_i = 7
end
}
}
}
}
# The width and height stored in the printing listing are used
# by the printer to determine the number of bytes to parse.
# Example 13,100 tells the printer that the next 13 * 100 bytes
# are graphics data.
#
# We need to compute the width in bytes, based on the number of
# dots stored inside a byte, and round up to the next value
# since in-byte padding is automatically added when the width isn't
# aligned on byte boundaries.
stored_width = (width * scaling / 8.0).ceil
# Height values is just the number of rows to draw.
stored_height = height * scaling
puts "#{width} x #{height} (#{stored_width} x #{stored_height})"
# Arbitrary start position of the printed graphic
x = 18
y = 9
# Add the header. This header tells the printer to print with good
# quality. The default being "garbage quality".
header = File.read("zebra_print_header.txt")
# The command to draw graphics is GW
# The syntax is :
# GWx,y,bytes_per_row,rows,graphics_data
#
# N just tells the printer to start a new print buffer (New)
# P1 prints the data stored inside the buffer (written by GW)
File.write("test.qr", "#{header}N\r\nGW#{x},#{y},#{stored_width},#{stored_height},#{gw_data.pack("C*")}\r\nP1\r\n")
# The GW data are stored inside qr.raw for examination.
File.write("qr.raw", gw_data.pack("C*"))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment