rdpl 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/label.rb ADDED
@@ -0,0 +1,99 @@
1
+ module Rdpl
2
+ class Label
3
+ attr_reader :state, :quantity
4
+ attr_writer :job
5
+
6
+ include Commandable
7
+
8
+ START = 'L'
9
+ FINISH = 'E'
10
+ DEFAULT_DOT_SIZE = 11
11
+ DEFAULT_HEAT = 14
12
+
13
+ def initialize(options = {})
14
+ @contents = ''
15
+ start options
16
+ end
17
+
18
+ def dump
19
+ @contents.dup
20
+ end
21
+
22
+ def end!
23
+ self << formatted_quantity unless quantity.nil?
24
+ self << FINISH
25
+ self.state = :finished
26
+ end
27
+
28
+ def [](arg)
29
+ @contents[arg]
30
+ end
31
+
32
+ def <<(arg)
33
+ raise EndedElementError unless state == :open
34
+ @contents << arg.to_s << NEW_LINE
35
+ end
36
+
37
+ def dot_size
38
+ @dot_size || DEFAULT_DOT_SIZE
39
+ end
40
+
41
+ def heat
42
+ @heat || DEFAULT_HEAT
43
+ end
44
+
45
+ def mm?
46
+ @job ? @job.mm? : false
47
+ end
48
+
49
+ # Returns the start of print position for the label.
50
+ # If the option wasn't specified in the label's constructor, nil will be returned
51
+ # and the printer will assume the default start of print.
52
+ #
53
+ # It works this way since the default value for this parameter depends on the printer's model.
54
+ def start_of_print
55
+ return nil if @start_of_print.nil?
56
+ @start_of_print.to_f / (mm? ? 10 : 100)
57
+ end
58
+
59
+ {:line => 'Line',
60
+ :box => 'Box',
61
+ :barcode => 'Barcode',
62
+ :bitmapped_text => 'BitmappedText'
63
+ }.each_pair do |kind, klass|
64
+ define_method "add_#{kind}" do |&block|
65
+ element = Rdpl.const_get(klass).new
66
+ block.call element
67
+ self << element.to_s
68
+ end
69
+ end
70
+
71
+ private
72
+ def start(options = {})
73
+ self.state = :open
74
+ command START
75
+ self << formatted_heat
76
+ self << formatted_dot_size
77
+ options.each_pair { |option, value| self.send("#{option}=", value) }
78
+ end
79
+
80
+ [:state, :heat, :dot_size, :start_of_print, :quantity].each do |method|
81
+ define_method "#{method}=" do |value|
82
+ self.instance_variable_set :"@#{method}", value
83
+ end
84
+ end
85
+ public :quantity=
86
+
87
+ def formatted_dot_size
88
+ "D#{dot_size}"
89
+ end
90
+
91
+ def formatted_heat
92
+ "H#{heat}"
93
+ end
94
+
95
+ def formatted_quantity
96
+ "Q#{'%04d' % quantity}"
97
+ end
98
+ end
99
+ end
data/lib/rdpl.rb ADDED
@@ -0,0 +1,35 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ module Rdpl
5
+ STX = 2.chr
6
+ CR = 13.chr
7
+ LF = 10.chr
8
+ NEW_LINE = CR + LF
9
+ FEED = 'F'
10
+
11
+ module Commandable
12
+ def command(param)
13
+ raise EndedElementError if self.state == :finished
14
+ @contents << STX << param << NEW_LINE
15
+ end
16
+ end
17
+
18
+ module Sensor
19
+ REFLEXIVE = 'r'
20
+ EDGE = 'e'
21
+ end
22
+
23
+ class MissingPrinterNameError < StandardError; end
24
+ class EndedElementError < StandardError; end
25
+ end
26
+
27
+ require 'job'
28
+ require 'label'
29
+ require 'elements/element'
30
+ require 'elements/graphic'
31
+ require 'elements/barcode'
32
+ require 'elements/bitmapped_text'
33
+ require 'elements/lines_and_boxes'
34
+ require 'elements/line'
35
+ require 'elements/box'
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rdpl::Barcode do
4
+ let(:barcode) { Rdpl::Barcode.new }
5
+
6
+ describe "::CODE_128" do
7
+ it "equals 'e'" do
8
+ Rdpl::Barcode::CODE_128.should == 'e'
9
+ end
10
+ end
11
+
12
+ describe "::CODE_128_HUMAN" do
13
+ it "equals 'E'" do
14
+ Rdpl::Barcode::CODE_128_HUMAN.should == 'E'
15
+ end
16
+ end
17
+
18
+ describe "#height=" do
19
+ it "defines the barcode height" do
20
+ barcode.height = 100
21
+ barcode.instance_variable_get(:@height).should == 100
22
+ end
23
+
24
+ it "raises InvalidBarcodeHeightError if the height is less than 0" do
25
+ lambda do
26
+ barcode.height = -1
27
+ end.should raise_error(Rdpl::Element::InvalidBarcodeHeightError)
28
+ end
29
+
30
+ it "raises InvalidBarcodeHeightError if the height is above 999" do
31
+ lambda do
32
+ barcode.height = 1000
33
+ end.should raise_error(Rdpl::Element::InvalidBarcodeHeightError)
34
+ end
35
+ end
36
+
37
+ describe "#height" do
38
+ let(:barcode) { Rdpl::Barcode.new }
39
+
40
+ it "returns the barcode height" do
41
+ barcode.height = 100
42
+ barcode.height.should == 100
43
+ end
44
+
45
+ it "returns 25 by default" do
46
+ barcode.height.should == 25
47
+ end
48
+ end
49
+
50
+ describe "#to_s" do
51
+ it "returns the barcode's string representation" do
52
+ barcode = Rdpl::Barcode.new(
53
+ :rotation => 4,
54
+ :font_id => Rdpl::Barcode::CODE_128,
55
+ :data => 'SOME DATA 12345',
56
+ :height => 123,
57
+ :wide_bar_multiplier => 3,
58
+ :narrow_bar_multiplier => 4,
59
+ :row_position => 123,
60
+ :column_position => 234
61
+ )
62
+ barcode.to_s.should == '4e3412301230234SOME DATA 12345'
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rdpl::BitmappedText do
4
+
5
+ it_should_behave_like "element"
6
+
7
+ describe "#to_s" do
8
+ it "should return a string represention of the text element" do
9
+ text = Rdpl::BitmappedText.new(
10
+ :font_id => 2,
11
+ :width_multiplier => 2,
12
+ :height_multiplier => 3,
13
+ :row_position => 20,
14
+ :column_position => 30,
15
+ :data => 'HEY LOOK AT ME'
16
+ )
17
+ text.to_s.should == '122300000200030HEY LOOK AT ME'
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rdpl::Box do
4
+ let(:element) { Rdpl::Box.new }
5
+
6
+ it_should_behave_like 'lines and boxes'
7
+
8
+ describe "#bottom_and_top_thickness=" do
9
+ it "defines the thickness of bottom and top box edges" do
10
+ element.bottom_and_top_thickness = 23.4
11
+ element.instance_variable_get(:@bottom_and_top_thickness).should == 23.4
12
+ end
13
+ end
14
+
15
+ describe "#bottom_and_top_thickness" do
16
+ it "returns the thickness of bottom and top box edges" do
17
+ element.bottom_and_top_thickness = 23.4
18
+ element.bottom_and_top_thickness.should == 23.4
19
+ end
20
+
21
+ it "returns 0 by default" do
22
+ element.bottom_and_top_thickness.should be_zero
23
+ end
24
+ end
25
+
26
+ describe "#sides_thickness=" do
27
+ it "defines the thickness of the box' sides" do
28
+ element.sides_thickness = 23.5
29
+ element.instance_variable_get(:@sides_thickness).should == 23.5
30
+ end
31
+ end
32
+
33
+ describe "#sides_thickness" do
34
+ it "returns the thickness of the box' sides" do
35
+ element.sides_thickness = 23.5
36
+ element.sides_thickness.should == 23.5
37
+ end
38
+
39
+ it "returns 0 by default" do
40
+ element.sides_thickness.should be_zero
41
+ end
42
+ end
43
+
44
+ describe "#to_s" do
45
+ it "should return a string represention of the graphic element" do
46
+ box = Rdpl::Box.new(
47
+ :horizontal_width => 12.2,
48
+ :vertical_width => 14.3,
49
+ :row_position => 23.4,
50
+ :column_position => 24.5,
51
+ :bottom_and_top_thickness => 34.6,
52
+ :sides_thickness => 45.6
53
+ )
54
+ box.to_s.should == "1X1100002340245b0122014303460456"
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rdpl::Line do
4
+ let(:element) { Rdpl::Line.new }
5
+
6
+ it_should_behave_like 'lines and boxes'
7
+
8
+ describe "#to_s" do
9
+ it "should return a string represention of the graphic element" do
10
+ line = Rdpl::Line.new(
11
+ :horizontal_width => 12.2,
12
+ :vertical_width => 14.3,
13
+ :row_position => 23.4,
14
+ :column_position => 24.5
15
+ )
16
+ line.to_s.should == "1X1100002340245l01220143"
17
+ end
18
+ end
19
+ end
data/spec/job_spec.rb ADDED
@@ -0,0 +1,136 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rdpl::Job do
4
+ def new_job(options = {})
5
+ Rdpl::Job.new({:printer => 'foobar'}.merge(options))
6
+ end
7
+
8
+ subject { new_job }
9
+
10
+ its(:state) { should == :open }
11
+
12
+ describe "sensor" do
13
+ it "returns the current sensor" do
14
+ new_job(:sensor => Rdpl::Sensor::REFLEXIVE).sensor.should == Rdpl::Sensor::REFLEXIVE
15
+ end
16
+ end
17
+
18
+ it "uses the edge sensor by default" do
19
+ new_job.dump[0..1].should == Rdpl::STX + Rdpl::Sensor::EDGE
20
+ end
21
+
22
+ describe "#measurement" do
23
+ it "returns the current job measurement mode" do
24
+ job = new_job :measurement => :metric
25
+ job.measurement.should == :metric
26
+ end
27
+
28
+ it "returns :inches by default" do
29
+ new_job.measurement.should == :inches
30
+ end
31
+
32
+ it "do not accepts values different from inches or metric" do
33
+ lambda do
34
+ new_job(:measurement => :foo)
35
+ end.should raise_error(ArgumentError)
36
+ end
37
+ end
38
+
39
+ describe "#in?" do
40
+ it "returns true if the current measurement mode is :inches" do
41
+ new_job.should be_in
42
+ end
43
+
44
+ it "returns false if the current measurement mode is not :inches" do
45
+ new_job(:measurement => :metric).should_not be_in
46
+ end
47
+ end
48
+
49
+ describe "#mm?" do
50
+ it "returns true if the current measurement mode is :metric" do
51
+ new_job(:measurement => :metric).should be_mm
52
+ end
53
+
54
+ it "returns false if the current measurement mode is not :metric" do
55
+ new_job.should_not be_mm
56
+ end
57
+ end
58
+
59
+ it "has a list of labels" do
60
+ new_job.labels.should be_instance_of(Array)
61
+ end
62
+
63
+ it "requires a CUPS printer name" do
64
+ lambda do
65
+ Rdpl::Job.new
66
+ end.should raise_error(Rdpl::MissingPrinterNameError)
67
+ end
68
+
69
+ describe "#<<" do
70
+ it "adds a new label to the job" do
71
+ job = new_job
72
+ label = Rdpl::Label.new
73
+ expect {
74
+ job << label
75
+ }.to change { job.labels.size }.by(1)
76
+ end
77
+
78
+ it "sets itself as the label's job" do
79
+ job = Rdpl::Job.new :printer => 'foobar'
80
+ label = Rdpl::Label.new
81
+ expect {
82
+ job << label
83
+ }.to change { label.instance_variable_get :@job }.from(nil).to(job)
84
+ end
85
+ end
86
+
87
+ it "allows iteration through the labels" do
88
+ job = new_job
89
+ job << (label1 = Rdpl::Label.new)
90
+ job << (label2 = Rdpl::Label.new)
91
+ job.map.should == [label1, label2]
92
+ end
93
+
94
+ describe "#dump" do
95
+ it "returns the job's contents" do
96
+ job = new_job
97
+ label1 = Rdpl::Label.new
98
+ label1 << 'label1'
99
+ label1.end!
100
+ job << label1
101
+ label2 = Rdpl::Label.new
102
+ label2 << 'label2'
103
+ label2.end!
104
+ job << label2
105
+ expected = "\002e\r\n\002L\r\nH14\r\nD11\r\nlabel1\r\nE\r\n\002F\r\n\002L\r\nH14\r\nD11\r\nlabel2\r\nE\r\n\002F\r\n"
106
+ job.dump.should == expected
107
+ end
108
+ end
109
+
110
+ describe "#feed" do
111
+ it "adds the form feed command to the job" do
112
+ job = new_job
113
+ job.feed
114
+ job.dump[-4..-3].should == Rdpl::STX + Rdpl::FEED
115
+ end
116
+ end
117
+ describe "#print" do
118
+ let(:temp_file) { "" }
119
+
120
+ before :each do
121
+ temp_file.stub!(:close).and_return(true)
122
+ temp_file.stub!(:path).and_return("/tmp/datamax_label123")
123
+ Tempfile.stub!(:new).and_return(temp_file)
124
+ end
125
+
126
+ it "creates a temporary file with its contents" do
127
+ Tempfile.should_receive(:new).with('datamax_label').and_return(temp_file)
128
+ new_job.print
129
+ end
130
+
131
+ it "sends the job to the printer" do
132
+ Kernel.should_receive(:system).with('lpr -P foobar /tmp/datamax_label123')
133
+ new_job.print
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,210 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ describe Rdpl::Label do
5
+ def end_with_new_line
6
+ simple_matcher('creates a new line') { |actual| actual[-2..-1] == CR + LF }
7
+ end
8
+
9
+ describe "::DEFAULT_DOT_SIZE" do
10
+ subject { Rdpl::Label::DEFAULT_DOT_SIZE }
11
+ it { should == 11 }
12
+ end
13
+
14
+ describe "::DEFAULT_HEAT" do
15
+ subject { Rdpl::Label::DEFAULT_HEAT }
16
+ it { should == 14 }
17
+ end
18
+
19
+ its(:state) { should == :open }
20
+
21
+ describe "mm?" do
22
+ it "delegates to the printing job" do
23
+ label = Rdpl::Label.new
24
+ label.job = Rdpl::Job.new :printer => 'foobar', :measurement => :metric
25
+ label.should be_mm
26
+ end
27
+
28
+ it "returns false if the label has no job" do
29
+ Rdpl::Label.new.should_not be_mm
30
+ end
31
+ end
32
+
33
+ describe "#contents" do
34
+ it "is always started with STX L (for Label start), plus the dot size and heating settings" do
35
+ label = Rdpl::Label.new
36
+ expected = Rdpl::STX +
37
+ Rdpl::Label::START +
38
+ Rdpl::NEW_LINE +
39
+ "H#{Rdpl::Label::DEFAULT_HEAT}" +
40
+ Rdpl::NEW_LINE +
41
+ "D#{Rdpl::Label::DEFAULT_DOT_SIZE}" +
42
+ Rdpl::NEW_LINE
43
+ label.dump.should == expected
44
+ end
45
+ end
46
+
47
+ describe "#end!" do
48
+ let(:label) { Rdpl::Label.new }
49
+
50
+ it "marks the label's end" do
51
+ label.end!
52
+ label[-3..-1].should == 'E' + Rdpl::NEW_LINE
53
+ end
54
+
55
+ it "alters the state to :closed" do
56
+ expect {
57
+ label.end!
58
+ }.to change { label.state }.to(:finished)
59
+ end
60
+
61
+ it "adds the quantity command if a quantity was specified" do
62
+ label = Rdpl::Label.new
63
+ label.quantity = 5
64
+ label.end!
65
+ label.dump[-10..-6].should == 'Q0005'
66
+ end
67
+ end
68
+
69
+ it "raises Rdpl::EndedElementError if it's ended andwe try to add new content" do
70
+ label = Rdpl::Label.new
71
+ label.end!
72
+ lambda do
73
+ label << 'some content'
74
+ end.should raise_error(Rdpl::EndedElementError)
75
+ end
76
+
77
+ describe "#command" do
78
+ let(:label) { Rdpl::Label.new }
79
+ before(:each) { label.command 'FOO' }
80
+
81
+ it { end_with_new_line }
82
+
83
+ it "records the command in the label's contents" do
84
+ expected = Rdpl::STX + 'FOO'
85
+ label.dump.should include(expected)
86
+ end
87
+
88
+ it "ends with CR/LF" do
89
+ expected = Rdpl::CR + Rdpl::LF
90
+ label[-2..-1].should == expected
91
+ end
92
+ end
93
+
94
+ describe "#<<" do
95
+ let(:label) { Rdpl::Label.new }
96
+
97
+ before(:each) { label << 'BAR' }
98
+
99
+ it { end_with_new_line }
100
+
101
+ it "puts the text inside the label" do
102
+ label.dump.should include('BAR')
103
+ end
104
+
105
+ it "allows inserting non-string elements" do
106
+ label << Rdpl::Barcode.new(:data => 'BARCODE')
107
+ label.dump.should include('BARCODE')
108
+ end
109
+ end
110
+
111
+ describe "#dot_size" do
112
+ it "returns the current dot size" do
113
+ Rdpl::Label.new(:dot_size => 20).dot_size.should == 20
114
+ end
115
+
116
+ it "returns Label::DEFAULT_DOT_SIZE by default" do
117
+ Rdpl::Label.new.dot_size.should == Rdpl::Label::DEFAULT_DOT_SIZE
118
+ end
119
+ end
120
+
121
+ describe "#heat" do
122
+ it "returns the current heat setting" do
123
+ Rdpl::Label.new(:heat => 25).heat.should == 25
124
+ end
125
+
126
+ it "returns Label::DEFAULT_HEAT by default" do
127
+ Rdpl::Label.new.heat.should == Rdpl::Label::DEFAULT_HEAT
128
+ end
129
+ end
130
+
131
+ describe "#start_of_print" do
132
+ it "returns nil if start_of_print was not specified" do
133
+ Rdpl::Label.new.start_of_print.should be_nil
134
+ end
135
+
136
+ describe "when the measurenemt mode is inches" do
137
+ it "returns the configured start of print" do
138
+ Rdpl::Label.new(:start_of_print => '0123').start_of_print.should == 1.23
139
+ end
140
+ end
141
+
142
+ describe "when the measurement mode is metric" do
143
+ it "returns the configured start of print" do
144
+ label = Rdpl::Label.new(:start_of_print => '0123')
145
+ label.job = Rdpl::Job.new :printer => 'foo', :measurement => :metric
146
+ label.start_of_print.should == 12.3
147
+ end
148
+ end
149
+ end
150
+
151
+ describe "#add_line" do
152
+ it "should add a line element to the label's contents" do
153
+ label = Rdpl::Label.new
154
+ label.add_line do |line|
155
+ line.horizontal_width = 12.2
156
+ line.vertical_width = 14.3
157
+ line.row_position = 23.4
158
+ line.column_position = 24.5
159
+ end
160
+ label.dump.should include("1X1100002340245l01220143#{Rdpl::NEW_LINE}")
161
+ end
162
+ end
163
+
164
+ describe "#add_box" do
165
+ it "should add a box element to the label's contents" do
166
+ label = Rdpl::Label.new
167
+ label.add_box do |box|
168
+ box.horizontal_width = 12.2
169
+ box.vertical_width = 14.3
170
+ box.row_position = 23.4
171
+ box.column_position = 24.5
172
+ box.bottom_and_top_thickness = 34.6
173
+ box.sides_thickness = 45.6
174
+ end
175
+ label.dump.should include("1X1100002340245b0122014303460456#{Rdpl::NEW_LINE}")
176
+ end
177
+ end
178
+
179
+ describe "#add_barcode" do
180
+ it "should add a barcode element to the label's contents" do
181
+ label = Rdpl::Label.new
182
+ label.add_barcode do |barcode|
183
+ barcode.rotation = 4
184
+ barcode.font_id = 'e'
185
+ barcode.data = 'SOME DATA 12345'
186
+ barcode.height = 123
187
+ barcode.wide_bar_multiplier = 3
188
+ barcode.narrow_bar_multiplier = 4
189
+ barcode.row_position = 123
190
+ barcode.column_position = 234
191
+ end
192
+ label.dump.should include('4e3412301230234SOME DATA 12345')
193
+ end
194
+ end
195
+
196
+ describe "#add_bitmapped_text" do
197
+ it "should add a bitmapped text element to the labe's contents" do
198
+ label = Rdpl::Label.new
199
+ label.add_bitmapped_text do |text|
200
+ text.font_id = 2
201
+ text.width_multiplier = 2
202
+ text.height_multiplier = 3
203
+ text.row_position = 20
204
+ text.column_position = 30
205
+ text.data = 'HEY LOOK AT ME'
206
+ end
207
+ label.dump.should include('122300000200030HEY LOOK AT ME')
208
+ end
209
+ end
210
+ end