Adding initial implementation for texts and barcodes

Cássio Marques committed Apr 01, 2013
commit 2d6378749a2dfd3d5cad50511fb52615ab39c222
Showing 16 changed files with 753 additions and 5 deletions
zebra/epl.rb b/lib/zebra/epl.rb +10 -5
@@ @@ -1,7 +1,12 @@
+ require "cups"
require "zebra/epl/version"
+ require "zebra/epl/rotation"
+ require "zebra/epl/multipliers"
+ require "zebra/epl/print_mode"
+ require "zebra/epl/font"
+ require "zebra/epl/label"
+ require "zebra/epl/text"
+ require "zebra/epl/barcode"
+ require "zebra/epl/barcode_type"
+ require "zebra/print_job"
- module Zebra
- module Epl
- # Your code goes here...
- end
- end
zebra/epl/barcode.rb b/lib/zebra/epl/barcode.rb +51 -0
@@ @@ -0,0 +1,51 @@
+ require "zebra/epl/printable"
+
+ module Zebra
+ module Epl
+ class Barcode
+ include Printable
+
+ class InvalidNarrowBarWidthError < StandardError; end
+ class InvalidWideBarWidthError < StandardError; end
+
+ attr_accessor :height
+ attr_reader :type, :narrow_bar_width, :wide_bar_width
+ attr_writer :print_human_readable_code
+
+ def type=(type)
+ BarcodeType.validate_barcode_type(type)
+ @type = type
+ end
+
+ def narrow_bar_width=(width)
+ raise InvalidNarrowBarWidthError unless (1..10).include?(width.to_i)
+ @narrow_bar_width = width
+ end
+
+ def wide_bar_width=(width)
+ raise InvalidWideBarWidthError unless (2..30).include?(width.to_i)
+ @wide_bar_width = width
+ end
+
+ def print_human_readable_code
+ @print_human_readable_code || false
+ end
+
+ def to_epl
+ check_attributes
+ human_readable = print_human_readable_code ? "B" : "N"
+ ["B#{x}", y, rotation, type, narrow_bar_width, wide_bar_width, height, human_readable, "\"#{data}\""].join(",")
+ end
+
+ private
+
+ def check_attributes
+ super
+ raise MissingAttributeError.new("the barcode type to be used is not given") unless @type
+ raise MissingAttributeError.new("the height to be used is not given") unless @height
+ raise MissingAttributeError.new("the narrow bar width to be used is not given") unless @narrow_bar_width
+ raise MissingAttributeError.new("the wide bar width to be used is not given") unless @wide_bar_width
+ end
+ end
+ end
+ end
zebra/epl/barcode_type.rb b/lib/zebra/epl/barcode_type.rb +24 -0
@@ @@ -0,0 +1,24 @@
+ module Zebra
+ module Epl
+ module BarcodeType
+ class InvalidBarcodeTypeError < StandardError; end
+
+ CODE_39 = "3"
+ CODE_39_CHECK_DIGIT = "3C"
+ CODE_93 = "9"
+ CODE_128_AUTO = "1"
+ CODE_128_A = "1A"
+ CODE_128_B = "1B"
+ CODE_128_C = "1C"
+ CODABAR = "K"
+
+ def self.valid_barcode_type?(type)
+ %w(3 3C 9 1 1A 1B 1C K).include? type
+ end
+
+ def self.validate_barcode_type(type)
+ raise InvalidBarcodeTypeError unless valid_barcode_type?(type)
+ end
+ end
+ end
+ end
zebra/epl/font.rb b/lib/zebra/epl/font.rb +21 -0
@@ @@ -0,0 +1,21 @@
+ module Zebra
+ module Epl
+ module Font
+ class InvalidFontError < StandardError; end
+
+ SIZE_1 = 1
+ SIZE_2 = 2
+ SIZE_3 = 3
+ SIZE_4 = 4
+ SIZE_5 = 5
+
+ def self.valid_font?(font)
+ (1..5).include?(font.to_i) || ('A'..'Z').include?(font)
+ end
+
+ def self.validate_font(font)
+ raise InvalidFontError unless valid_font?(font)
+ end
+ end
+ end
+ end
zebra/epl/label.rb b/lib/zebra/epl/label.rb +41 -0
@@ @@ -0,0 +1,41 @@
+ # encoding: utf-8
+ module Zebra
+ module Epl
+ class Label
+ attr_reader :elements
+
+ def initialize
+ @elements = []
+ end
+
+ def <<(element)
+ elements << element
+ end
+
+ def dump_contents(io = STDOUT)
+ # Start options
+ io << "O\n"
+ # Q<label height in dots>,<space between labels in dots>
+ io << "Q240,24\n"
+ # q<label width in dots>
+ io << "q432\n"
+ # Print Speed (S command)
+ io << "S2\n"
+ # Density (D command)
+ io << "D5\n"
+ # ZT = Printing from top of image buffer.
+ io << "ZT\n"
+
+ io << "\n"
+ # Start new label
+ io << "N\n"
+
+ elements.each do |element|
+ io << element.to_s << "\n"
+ end
+
+ io << "P0\n"
+ end
+ end
+ end
+ end
zebra/epl/multipliers.rb b/lib/zebra/epl/multipliers.rb +42 -0
@@ @@ -0,0 +1,42 @@
+ module Zebra
+ module Epl
+ module BaseMultiplier
+ class InvalidMultiplierError < StandardError; end
+
+ VALUE_1 = 1
+ VALUE_2 = 2
+ VALUE_3 = 3
+ VALUE_4 = 4
+ VALUE_5 = 5
+ VALUE_6 = 6
+ VALUE_7 = 7
+ VALUE_8 = 8
+
+ def self.included(base_module)
+ base_module.instance_eval do
+ def validate_multiplier(multiplier)
+ raise InvalidMultiplierError unless valid_multiplier?(multiplier)
+ end
+ end
+ end
+ end
+
+ module HorizontalMultiplier
+ include BaseMultiplier
+
+ def self.valid_multiplier?(multiplier)
+ (1..8).include? multiplier
+ end
+ end
+
+ module VerticalMultiplier
+ include BaseMultiplier
+
+ VALUE_9 = 9
+
+ def self.valid_multiplier?(multiplier)
+ (1..9).include? multiplier
+ end
+ end
+ end
+ end
zebra/epl/print_mode.rb b/lib/zebra/epl/print_mode.rb +18 -0
@@ @@ -0,0 +1,18 @@
+ module Zebra
+ module Epl
+ module PrintMode
+ class InvalidPrintModeError < StandardError; end
+
+ NORMAL = "N"
+ REVERSE = "R"
+
+ def self.valid_mode?(mode)
+ %w(N R).include? mode
+ end
+
+ def self.validate_mode(mode)
+ raise InvalidPrintModeError unless valid_mode?(mode)
+ end
+ end
+ end
+ end
zebra/epl/printable.rb b/lib/zebra/epl/printable.rb +39 -0
@@ @@ -0,0 +1,39 @@
+ module Zebra
+ module Epl
+ module Printable
+ class MissingAttributeError < StandardError
+ def initialize(message)
+ super("Can't print if #{message}")
+ end
+ end
+
+ attr_reader :position, :x, :y
+ attr_accessor :data
+
+ def initialize(options = {})
+ options.each_pair { |attribute, value| self.__send__ "#{attribute}=", value }
+ end
+
+ def position=(coords)
+ @position, @x, @y = coords, coords[0], coords[1]
+ end
+
+ def rotation=(rot)
+ Rotation.validate_rotation rot
+ @rotation = rot
+ end
+
+ def rotation
+ @rotation || Rotation::NO_ROTATION
+ end
+
+ private
+
+ def check_attributes
+ raise MissingAttributeError.new("the X value is not given") unless @x
+ raise MissingAttributeError.new("the Y value is not given") unless @y
+ raise MissingAttributeError.new("the data to be printed is not given") unless @data
+ end
+ end
+ end
+ end
zebra/epl/rotation.rb b/lib/zebra/epl/rotation.rb +20 -0
@@ @@ -0,0 +1,20 @@
+ module Zebra
+ module Epl
+ module Rotation
+ class InvalidRotationError < StandardError; end
+
+ NO_ROTATION = 0
+ DEGREES_90 = 1
+ DEGREES_180 = 2
+ DEGREES_270 = 3
+
+ def self.valid_rotation?(rotation)
+ [NO_ROTATION, DEGREES_90, DEGREES_180, DEGREES_270].include? rotation
+ end
+
+ def self.validate_rotation(rotation)
+ raise InvalidRotationError unless valid_rotation?(rotation)
+ end
+ end
+ end
+ end
zebra/epl/text.rb b/lib/zebra/epl/text.rb +59 -0
@@ @@ -0,0 +1,59 @@
+ require "zebra/epl/printable"
+
+ module Zebra
+ module Epl
+ class Text
+ include Printable
+
+ attr_reader :font
+
+ def font=(f)
+ Font.validate_font f
+ @font = f
+ end
+
+ def print_mode=(mode)
+ PrintMode.validate_mode mode
+ @print_mode = mode
+ end
+
+ def print_mode
+ @print_mode || PrintMode::NORMAL
+ end
+
+ def h_multiplier
+ @h_multiplier || HorizontalMultiplier::VALUE_1
+ end
+
+ def v_multiplier
+ @v_multiplier || VerticalMultiplier::VALUE_1
+ end
+
+ def print_mode
+ @print_mode || PrintMode::NORMAL
+ end
+
+ def h_multiplier=(multiplier)
+ HorizontalMultiplier.validate_multiplier multiplier
+ @h_multiplier = multiplier
+ end
+
+ def v_multiplier=(multiplier)
+ VerticalMultiplier.validate_multiplier multiplier
+ @v_multiplier = multiplier
+ end
+
+ def to_epl
+ check_attributes
+ ["A#{x}", y, rotation, font, h_multiplier, v_multiplier, print_mode, "\"#{data}\""].join(",")
+ end
+
+ private
+
+ def check_attributes
+ super
+ raise MissingAttributeError.new("the font to be used is not given") unless @font
+ end
+ end
+ end
+ end
zebra/print_job.rb b/lib/zebra/print_job.rb +28 -0
@@ @@ -0,0 +1,28 @@
+ module Zebra
+ class PrintJob
+ class UnknownPrinter < StandardError
+ def initialize(printer)
+ super("Could not find a printer named #{printer}")
+ end
+ end
+
+ attr_reader :printer
+
+ def initialize(printer)
+ check_existent_printers printer
+
+ @printer = printer
+ end
+
+ def print(label)
+ Cups::PrintJob.new(label.path, @printer).print
+ end
+
+ private
+
+ def check_existent_printers(printer)
+ existent_printers = Cups.show_destinations
+ raise UnknownPrinter.new(printer) unless existent_printers.include?(printer)
+ end
+ end
+ end
spec/spec_helper.rb +3 -0
@@ @@ -4,6 +4,9 @@
# loaded once.
#
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
+
+ require "zebra/epl"
+
RSpec.configure do |config|
config.treat_symbols_as_metadata_keys_with_true_values = true
config.run_all_when_everything_filtered = true
spec/zebra/epl/barcode_spec.rb +196 -0
@@ @@ -0,0 +1,196 @@
+ require 'spec_helper'
+
+ describe Zebra::Epl::Barcode do
+ it "can be initialized with the position of the text to be printed" do
+ barcode = described_class.new :position => [20, 40]
+ barcode.position.should == [20,40]
+ barcode.x.should == 20
+ barcode.y.should == 40
+ end
+
+ it "can be initialized with the barcode rotation" do
+ rotation = Zebra::Epl::Rotation::DEGREES_90
+ barcode = described_class.new :rotation => rotation
+ barcode.rotation.should == rotation
+ end
+
+ it "can be initialized with the barcode rotation" do
+ rotation = Zebra::Epl::Rotation::DEGREES_90
+ barcode = described_class.new :rotation => rotation
+ barcode.rotation.should == rotation
+ end
+
+ it "can be initialized with the barcode type" do
+ type = Zebra::Epl::BarcodeType::CODE_128_C
+ barcode = described_class.new :type => type
+ barcode.type.should == type
+ end
+
+ it "can be initialized with the narrow bar width" do
+ barcode = described_class.new :narrow_bar_width => 3
+ barcode.narrow_bar_width.should == 3
+ end
+
+ it "can be initialized with the wide bar width" do
+ barcode = described_class.new :wide_bar_width => 10
+ barcode.wide_bar_width.should == 10
+ end
+
+ it "can be initialized with the barcode height" do
+ barcode = described_class.new :height => 20
+ barcode.height.should == 20
+ end
+
+ it "can be initialized informing if the human readable code should be printed" do
+ barcode = described_class.new :print_human_readable_code => true
+ barcode.print_human_readable_code.should == true
+ end
+
+ describe "#rotation=" do
+ it "raises an error if the received rotation is invalid" do
+ expect {
+ described_class.new.rotation = 4
+ }.to raise_error(Zebra::Epl::Rotation::InvalidRotationError)
+ end
+ end
+
+ describe "#type=" do
+ it "raises an error if the received type is invalid" do
+ expect {
+ described_class.new.type = "ZZZ"
+ }.to raise_error(Zebra::Epl::BarcodeType::InvalidBarcodeTypeError)
+ end
+ end
+
+ describe "#narrow_bar_width=" do
+ it "raises an error if the type is Code 128 and the width is invalid" do
+ expect {
+ described_class.new :type => Zebra::Epl::BarcodeType::CODE_128_AUTO, :narrow_bar_width => 20
+ }.to raise_error(Zebra::Epl::Barcode::InvalidNarrowBarWidthError)
+ end
+ end
+
+ describe "#wide_bar_width=" do
+ it "raises an error if the type is Code 128 and the width is invalid" do
+ expect {
+ described_class.new :type => Zebra::Epl::BarcodeType::CODE_128_AUTO, :wide_bar_width => 40
+ }.to raise_error(Zebra::Epl::Barcode::InvalidWideBarWidthError)
+ end
+ end
+
+ describe "#print_human_readable_code" do
+ it "defaults to false" do
+ described_class.new.print_human_readable_code.should == false
+ end
+ end
+
+ describe "#to_epl" do
+ let(:valid_attributes) { {
+ :position => [100, 150],
+ :type => Zebra::Epl::BarcodeType::CODE_128_AUTO,
+ :height => 20,
+ :narrow_bar_width => 4,
+ :wide_bar_width => 6,
+ :data => "foobar"
+ } }
+ let(:barcode) { described_class.new valid_attributes }
+ let(:tokens) { barcode.to_epl.split(",") }
+
+ it "raises an error if the X position was not informed" do
+ barcode = described_class.new :position => [nil, 100], :data => "foobar"
+ expect {
+ barcode.to_epl
+ }.to raise_error(Zebra::Epl::Printable::MissingAttributeError, "Can't print if the X value is not given")
+ end
+
+ it "raises an error if the Y position was not informed" do
+ barcode = described_class.new :position => [100, nil]
+ expect {
+ barcode.to_epl
+ }.to raise_error(Zebra::Epl::Printable::MissingAttributeError, "Can't print if the Y value is not given")
+ end
+
+ it "raises an error if the barcode type is not informed" do
+ barcode = described_class.new :position => [100, 100], :data => "foobar"
+ expect {
+ barcode.to_epl
+ }.to raise_error(Zebra::Epl::Printable::MissingAttributeError, "Can't print if the barcode type to be used is not given")
+ end
+
+ it "raises an error if the data to be printed was not informed" do
+ barcode.data = nil
+ expect {
+ barcode.to_epl
+ }.to raise_error(Zebra::Epl::Printable::MissingAttributeError, "Can't print if the data to be printed is not given")
+ end
+
+ it "raises an error if the height to be used was not informed" do
+ barcode.height = nil
+ expect {
+ barcode.to_epl
+ }.to raise_error(Zebra::Epl::Printable::MissingAttributeError, "Can't print if the height to be used is not given")
+ end
+
+ it "raises an error if the narrow bar width is not given" do
+ valid_attributes.delete :narrow_bar_width
+
+ expect {
+ barcode.to_epl
+ }.to raise_error(Zebra::Epl::Printable::MissingAttributeError, "Can't print if the narrow bar width to be used is not given")
+ end
+
+ it "raises an error if the wide bar width is not given" do
+ valid_attributes.delete :wide_bar_width
+
+ expect {
+ barcode.to_epl
+ }.to raise_error(Zebra::Epl::Printable::MissingAttributeError, "Can't print if the wide bar width to be used is not given")
+ end
+
+ it "begins with the command 'B'" do
+ barcode.to_epl.should =~ /\AB/
+ end
+
+ it "contains the X position" do
+ tokens[0].match(/B(\d+)/)[1].should == "100"
+ end
+
+ it "contains the Y position" do
+ tokens[1].should == "150"
+ end
+
+ it "contains the barcode rotation" do
+ tokens[2].should == Zebra::Epl::Rotation::NO_ROTATION.to_s
+ end
+
+ it "contains the barcode type" do
+ tokens[3].should == Zebra::Epl::BarcodeType::CODE_128_AUTO
+ end
+
+ it "contains the barcode narrow bar width" do
+ tokens[4].should == "4"
+ end
+
+ it "contains the barcode wide bar width" do
+ tokens[5].should == "6"
+ end
+
+ it "contains the barcode height" do
+ tokens[6].should == "20"
+ end
+
+ it "contains the correct indication when the human readable code should be printed" do
+ valid_attributes.merge! :print_human_readable_code => true
+ tokens[7].should == "B"
+ end
+
+ it "contains the correct indication when the human readable code should not be printed" do
+ valid_attributes.merge! :print_human_readable_code => false
+ tokens[7].should == "N"
+ end
+
+ it "contains the data to be printed in the barcode" do
+ tokens[8].should == "\"foobar\""
+ end
+ end
+ end
spec/zebra/epl/label_spec.rb +26 -0
@@ @@ -0,0 +1,26 @@
+ # encoding: utf-8
+
+ require 'spec_helper'
+
+ describe Zebra::Epl::Label do
+ subject(:label) { described_class.new }
+
+ describe "#<<" do
+ it "adds an item to the list of label elements" do
+ expect {
+ label << stub
+ }.to change { label.elements.count }.by 1
+ end
+
+ describe "#dump_contents" do
+ let(:io) { "" }
+
+ it "dumps its contents to the received IO" do
+ label << stub(:to_s => "foobar")
+ label << stub(:to_s => "blabla")
+ label.dump_contents(io)
+ io.should == "O\nQ240,24\nq432\nS2\nD5\nZT\n\nN\nfoobar\nblabla\nP0\n"
+ end
+ end
+ end
+ end
spec/zebra/epl/text_spec.rb +139 -0
@@ @@ -0,0 +1,139 @@
+ # encoding: utf-8
+ require 'spec_helper'
+
+ describe Zebra::Epl::Text do
+ it "can be initialized with the position of the text to be printed" do
+ text = described_class.new :position => [20, 40]
+ text.position.should == [20,40]
+ text.x.should == 20
+ text.y.should == 40
+ end
+
+ it "can be initialized with the text rotation" do
+ rotation = Zebra::Epl::Rotation::DEGREES_90
+ text = described_class.new :rotation => rotation
+ text.rotation.should == rotation
+ end
+
+ it "can be initialized with the font to be used" do
+ font = Zebra::Epl::Font::SIZE_1
+ text = described_class.new :font => font
+ text.font.should == font
+ end
+
+ it "can be initialized with the horizontal multiplier" do
+ multiplier = Zebra::Epl::HorizontalMultiplier::VALUE_1
+ text = described_class.new :h_multiplier => multiplier
+ text.h_multiplier.should == multiplier
+ end
+
+ it "can be initialized with the vertical multiplier" do
+ multiplier = Zebra::Epl::VerticalMultiplier::VALUE_1
+ text = described_class.new :v_multiplier => multiplier
+ text.v_multiplier.should == multiplier
+ end
+
+ it "can be initialized with the data to be printed" do
+ data = "foobar"
+ text = described_class.new :data => data
+ text.data.should == data
+ end
+
+ it "can be initialized with the printing mode" do
+ print_mode = Zebra::Epl::PrintMode::REVERSE
+ text = described_class.new :print_mode => print_mode
+ text.print_mode.should == print_mode
+ end
+
+ describe "#rotation=" do
+ it "raises an error if the received rotation is invalid" do
+ expect {
+ described_class.new.rotation = 4
+ }.to raise_error(Zebra::Epl::Rotation::InvalidRotationError)
+ end
+ end
+
+ describe "#font=" do
+ it "raises an error if the received font is invalid" do
+ expect {
+ described_class.new.font = 6
+ }.to raise_error(Zebra::Epl::Font::InvalidFontError)
+ end
+ end
+
+ describe "#h_multiplier=" do
+ it "raises an error if the received multiplier is invalid" do
+ expect {
+ described_class.new.h_multiplier = 9
+ }.to raise_error(Zebra::Epl::HorizontalMultiplier::InvalidMultiplierError)
+ end
+ end
+
+ describe "#v_multiplier=" do
+ it "raises an error if the received multiplier is invalid" do
+ expect {
+ described_class.new.v_multiplier = 10
+ }.to raise_error(Zebra::Epl::VerticalMultiplier::InvalidMultiplierError)
+ end
+ end
+
+ describe "#print_mode=" do
+ it "raises an error if the received print mode is invalid" do
+ expect {
+ described_class.new.print_mode = "foo"
+ }.to raise_error(Zebra::Epl::PrintMode::InvalidPrintModeError)
+ end
+ end
+
+ describe "#to_epl" do
+ subject(:text) { described_class.new :position => [100, 150], :font => Zebra::Epl::Font::SIZE_3, :data => "foobar" }
+
+ it "raises an error if the X position was not informed" do
+ text = described_class.new :position => [nil, 100], :data => "foobar"
+ expect {
+ text.to_epl
+ }.to raise_error(Zebra::Epl::Printable::MissingAttributeError, "Can't print if the X value is not given")
+ end
+
+ it "raises an error if the Y position was not informed" do
+ text = described_class.new :position => [100, nil]
+ expect {
+ text.to_epl
+ }.to raise_error(Zebra::Epl::Printable::MissingAttributeError, "Can't print if the Y value is not given")
+ end
+
+ it "raises an error if the font is not informed" do
+ text = described_class.new :position => [100, 100], :data => "foobar"
+ expect {
+ text.to_epl
+ }.to raise_error(Zebra::Epl::Printable::MissingAttributeError, "Can't print if the font to be used is not given")
+ end
+
+ it "raises an error if the data to be printed was not informed" do
+ text.data = nil
+ expect {
+ text.to_epl
+ }.to raise_error(Zebra::Epl::Printable::MissingAttributeError, "Can't print if the data to be printed is not given")
+ end
+
+ it "begins width the 'A' command" do
+ text.to_epl.should =~ /\AA/
+ end
+
+ it "assumes 1 as the default horizontal multipler" do
+ text.to_epl.split(",")[4].to_i.should == Zebra::Epl::HorizontalMultiplier::VALUE_1
+ end
+
+ it "assumes 1 as the default vertical multiplier" do
+ text.to_epl.split(",")[5].to_i.should == Zebra::Epl::VerticalMultiplier::VALUE_1
+ end
+
+ it "assumes the normal print mode as the default" do
+ text.to_epl.split(",")[6].should == Zebra::Epl::PrintMode::NORMAL
+ end
+
+ it "assumes no rotation by default" do
+ text.to_epl.split(",")[2].to_i.should == Zebra::Epl::Rotation::NO_ROTATION
+ end
+ end
+ end
spec/zebra/print_job_spec.rb +36 -0
@@ @@ -0,0 +1,36 @@
+ require 'spec_helper'
+
+ describe Zebra::PrintJob do
+ before do
+ Cups.stub(:show_destinations).and_return(["Zebra", "Foobar"])
+ end
+
+ it "receives the name of a printer" do
+ described_class.new("Zebra").printer.should == "Zebra"
+ end
+
+ it "raises an error if the printer does not exists" do
+ expect {
+ described_class.new("Wrong")
+ }.to raise_error(Zebra::PrintJob::UnknownPrinter)
+ end
+
+ describe "#print" do
+ let(:label) { stub :path => "/foo/bar" }
+ let(:cups_job) { stub :print => true }
+
+ subject(:print_job) { described_class.new "Zebra" }
+
+ before { Cups::PrintJob.stub(:new).and_return(cups_job) }
+
+ it "creates a cups print job with the correct arguments" do
+ Cups::PrintJob.should_receive(:new).with("/foo/bar", "Zebra").and_return(cups_job)
+ print_job.print label
+ end
+
+ it "prints the label" do
+ cups_job.should_receive(:print)
+ print_job.print label
+ end
+ end
+ end