escper 1.0.4 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -13,22 +13,18 @@ Or if you read the image from an image upload form in Rails, do this:
13
13
 
14
14
  Escper::Image.new(data.read, :blob).to_s
15
15
 
16
- ... where "data" is a variable containing the image data of a multipart HTML form.
17
-
18
- Or if you have an Magick::Image already loaded, call it like this:
19
-
20
- Escper::Image.new(magickobject, :object)
16
+ where "data" is a variable containing the image data of a multipart HTML form.
21
17
 
22
18
 
23
19
  For optimal visual results, image.png should previously be converted to an indexed, black and white 1-bit palette image. In Gimp, click on "Image -> Mode -> Indexed..." and select "Use black and white (1-bit) palette". For dithering, choose "Floyd-Steinberg (reduced color bleeding)". The image size depends on the resolution of the printer.
24
20
 
25
21
  To send an image directly to a thermal receipt printer:
26
22
 
27
- File.open('/dev/usb/lp0','w') { |f| f.write Escper::Image.new('image.png', :file).to_s }
23
+ File.open('/dev/usb/lp0','w') { |f| f.write Escper::Image.new('image.png').to_s }
28
24
 
29
25
  == Licence
30
26
 
31
- Copyright (C) 2011-2012 Michael Franzl <office@michaelfranzl.com>
27
+ Copyright (C) 2011-2012 Michael Franzl <michael@billgastro.com>
32
28
 
33
29
  This program is free software: you can redistribute it and/or modify
34
30
  it under the terms of the GNU Affero General Public License as
data/escper.gemspec CHANGED
@@ -18,6 +18,4 @@ Gem::Specification.new do |s|
18
18
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
-
22
- s.add_dependency "rmagick"
23
21
  end
data/lib/escper.rb CHANGED
@@ -1,73 +1,16 @@
1
- # Escper -- Convert an image to ESCPOS commands for thermal printers
2
- # Copyright (C) 2011-2012 Michael Franzl <michael@billgastro.com>
3
- #
4
- # This program is free software: you can redistribute it and/or modify
5
- # it under the terms of the GNU Affero General Public License as
6
- # published by the Free Software Foundation, either version 3 of the
7
- # License, or (at your option) any later version.
8
- #
9
- # This program is distributed in the hope that it will be useful,
10
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
- # GNU Affero General Public License for more details.
13
- #
14
- # You should have received a copy of the GNU Affero General Public License
15
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
-
17
1
  require 'RMagick'
18
2
 
19
- module Escper
20
- class Image
21
- def initialize(source, type)
22
- if type == :file
23
- @image = Magick::Image.read(source).first
24
- elsif type == :blob
25
- @image = Magick::Image.from_blob(source).first
26
- elsif type == :object
27
- @image = source
28
- end
29
- @x = (@image.columns / 8.0).round
30
- @y = (@image.rows / 8.0).round
31
- @x = 1 if @x.zero?
32
- @y = 1 if @y.zero?
33
- end
34
-
35
- def convert
36
- @image = @image.quantize 2, Magick::GRAYColorspace
37
- end
38
-
39
- def crop
40
- @image = @image.extent @x * 8, @y * 8
41
- end
3
+ dir = File.dirname(__FILE__)
4
+ Dir[File.expand_path("#{dir}/escper/*.rb")].uniq.each do |file|
5
+ require file
6
+ end
42
7
 
43
- def to_a
44
- @image.export_pixels
45
- end
8
+ module Escper
9
+ mattr_accessor :codepage_file
46
10
 
47
- def to_s
48
- self.convert
49
- self.crop
50
- colorarray = self.to_a
51
- bits = []
52
- mask = 0x80
53
- i = 0
54
- temp = 0
55
- (@x * @y * 8 * 3 * 8).times do |j|
56
- next unless (j % 3).zero?
57
- temp |= mask if colorarray[j] == 0 # put 1 in place if black
58
- mask = mask >> 1
59
- i += 3
60
- if i == 24
61
- bits << temp
62
- mask = 0x80
63
- i = 0
64
- temp = 0
65
- end
66
- end
67
- result = bits.collect{ |b| b.chr }.join
68
- escpos = "\x1D\x76\x30\x00#{@x.chr}\x00#{(@y*8).chr}\x00#{ result }"
69
- escpos.force_encoding('ISO-8859-15')
70
- return escpos
71
- end
11
+ @@codepage_file = File.join(File.dirname(__FILE__), 'escper', 'codepages.yml')
12
+
13
+ def self.setup
14
+ yield self
72
15
  end
73
16
  end
@@ -0,0 +1,42 @@
1
+ module Escper
2
+ class Asciifier
3
+ def initialize(codepage)
4
+ @codepage = codepage
5
+ @codepage_lookup_yaml = YAML::load(File.read(Escper.codepage_file))
6
+ end
7
+
8
+ def self.all_chars
9
+ out = "\e@" # Initialize Printer
10
+ out.encode!('ascii-8bit')
11
+ 20.upto(255) { |i| out += i.to_s(16) + i.chr + ' ' }
12
+ out += "\n\n\n\n\n\n" +
13
+ "\x1D\x56\x00" # paper cut
14
+ return out
15
+ end
16
+
17
+ def process(text)
18
+ output = ''
19
+ output.encode 'ASCII-8BIT'
20
+ 0.upto(text.length - 1) do |i|
21
+ char_utf8 = text[i]
22
+ char_ascii = text[i].force_encoding('ASCII-8BIT')
23
+ if char_ascii.length == 1
24
+ output += char_ascii
25
+ else
26
+ output += codepage_lookup(char_utf8)
27
+ end
28
+ end
29
+ return output
30
+ end
31
+
32
+ def codepage_lookup(char)
33
+ output = @codepage_lookup_yaml[@codepage][char]
34
+ if output
35
+ output = output.chr
36
+ else
37
+ output = '?'
38
+ end
39
+ return output.force_encoding('ASCII-8BIT')
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,26 @@
1
+ ---
2
+ # Codepage 0 corresponds to the codepage 0 of Metapace T family of thermal printers
3
+ 0:
4
+ ä: 0x84
5
+ ü: 0x81
6
+ ö: 0x94
7
+ Ä: 0x8E
8
+ Ü: 0x9A
9
+ Ö: 0x99
10
+ é: 0x82
11
+ è: 0x8A
12
+ ú: 0xA3
13
+ ù: 0x97
14
+ á: 0xA0
15
+ à: 0x85
16
+ í: 0xA1
17
+ ì: 0x8D
18
+ ó: 0xA2
19
+ ò: 0x95
20
+ â: 0x83
21
+ ê: 0x88
22
+ î: 0x8C
23
+ ô: 0x93
24
+ û: 0x96
25
+ ñ: 0xA4
26
+ ß: 0xE1
@@ -0,0 +1,67 @@
1
+ module Escper
2
+ class Image
3
+ def initialize(data, type)
4
+ if type == :file
5
+ @image = convert(Magick::Image.read(data).first)
6
+ elsif type == :blob
7
+ @image = convert(Magick::Image.from_blob(data).first)
8
+ end
9
+ end
10
+
11
+ def convert(img=nil)
12
+ if img.nil? and @image
13
+ @image = @image.quantize 2, Magick::GRAYColorspace
14
+ @image = crop(@image)
15
+ return @image
16
+ else
17
+ img = img.quantize 2, Magick::GRAYColorspace
18
+ img = crop(img)
19
+ return img
20
+ end
21
+ end
22
+
23
+ def crop(image)
24
+ @x = (image.columns / 8.0).round
25
+ @y = (image.rows / 8.0).round
26
+ @x = 1 if @x == 0
27
+ @y = 1 if @y == 0
28
+ image = image.extent @x * 8, @y * 8
29
+ return image
30
+ end
31
+
32
+ def to_a
33
+ colorarray = @image.export_pixels
34
+ return colorarray
35
+ end
36
+
37
+ def to_s
38
+ colorarray = self.to_a
39
+ bits = []
40
+ mask = 0x80
41
+ i = 0
42
+ temp = 0
43
+ (@x * @y * 8 * 3 * 8).times do |j|
44
+ next unless (j % 3).zero?
45
+ temp |= mask if colorarray[j] == 0 # put 1 in place if black
46
+ mask = mask >> 1 # shift mask
47
+ i += 3
48
+ if i == 24
49
+ bits << temp
50
+ mask = 0x80
51
+ i = 0
52
+ temp = 0
53
+ end
54
+ end
55
+ result = bits.collect{ |b| b.chr }.join
56
+ escpos = ''
57
+ escpos.encode! 'ASCII-8BIT'
58
+ escpos += "\x1D\x76\x30\x00" +
59
+ @x.chr +
60
+ "\x00" +
61
+ (@y*8).chr +
62
+ "\x00" +
63
+ result
64
+ return escpos
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,158 @@
1
+ module Escper
2
+ class Printer
3
+ # mode can be local or sass
4
+ # vendor_printers can either be a single VendorPrinter object, or an Array of VendorPrinter objects, or an ActiveRecord Relation containing VendorPrinter objects.
5
+ def initialize(mode, vendor_printers = nil)
6
+ @mode = mode
7
+ @open_printers = Hash.new
8
+ @codepages_lookup = YAML::load(File.read(Escper.codepage_file))
9
+ if vendor_printers.kind_of?(ActiveRecord::Relation) or vendor_printers.kind_of?(Array)
10
+ @vendor_printers = vendor_printers
11
+ elsif vendor_printers.kind_of? VendorPrinter
12
+ @vendor_printers = [vendor_printers]
13
+ else
14
+ # If no available VendorPrinters are initialized, create a set of temporary VendorPrinters with usual device paths.
15
+ puts "No VendorPrinters specified. Creating a set of temporary printers with usual device paths"
16
+ paths = ['/dev/ttyUSB0', '/dev/ttyUSB1', '/dev/ttyUSB2', '/dev/usb/lp0', '/dev/usb/lp1', '/dev/usb/lp2', '/dev/salor-hospitality-front', '/dev/salor-hospitality-top', '/dev/salor-hospitality-back-top-left', '/dev/salor-hospitality-back-top-right', '/dev/salor-hospitality-back-bottom-left', '/dev/salor-hospitality-back-bottom-right']
17
+ @vendor_printers = Array.new
18
+ paths.size.times do |i|
19
+ @vendor_printers << VendorPrinter.new(:name => paths[i].gsub(/^.*\//,''), :path => paths[i], :copies => 1, :codepage => 0)
20
+ end
21
+ end
22
+ end
23
+
24
+ def print(printer_id, text, raw_text_insertations={})
25
+ return if @open_printers == {}
26
+ ActiveRecord::Base.logger.info "[PRINTING]============"
27
+ ActiveRecord::Base.logger.info "[PRINTING]PRINTING..."
28
+ printer = @open_printers[printer_id]
29
+ raise 'Mismatch between open_printers and printer_id' if printer.nil?
30
+
31
+ codepage = printer[:codepage]
32
+ codepage ||= 0
33
+ output_text = merge_texts(text, raw_text_insertations, codepage)
34
+
35
+ ActiveRecord::Base.logger.info "[PRINTING] Printing on #{ printer[:name] } @ #{ printer[:device].inspect }."
36
+ bytes_written = nil
37
+ printer[:copies].times do |i|
38
+ # The method .write works both for SerialPort object and File object, so we don't have to distinguish here.
39
+ bytes_written = @open_printers[printer_id][:device].write output_text
40
+ ActiveRecord::Base.logger.info "[PRINTING]ERROR: Byte count mismatch: sent #{text.length} written #{bytes_written}" unless output_text.length == bytes_written
41
+ end
42
+ # The method .flush works both for SerialPort object and File object, so we don't have to distinguish here. It is not really neccessary, since the close method will theoretically flush also.
43
+ @open_printers[printer_id][:device].flush
44
+ return bytes_written, output_text
45
+ end
46
+
47
+ def merge_texts(text, raw_text_insertations, codepage)
48
+ asciifier = Escper::Asciifier.new(codepage)
49
+ asciified_text = asciifier.process(text)
50
+ raw_text_insertations.each do |key, value|
51
+ markup = "{::escper}#{key.to_s}{:/}".encode('ASCII-8BIT')
52
+ asciified_text.gsub!(markup, value)
53
+ end
54
+ return asciified_text
55
+ end
56
+
57
+ def identify(chartest=nil)
58
+ ActiveRecord::Base.logger.info "[PRINTING]============"
59
+ ActiveRecord::Base.logger.info "[PRINTING]TESTING Printers..."
60
+ open
61
+ @open_printers.each do |id, value|
62
+ init = "\e@"
63
+ cut = "\n\n\n\n\n\n" + "\x1D\x56\x00"
64
+ testtext =
65
+ "\e!\x38" + # double tall, double wide, bold
66
+ "#{ I18n.t :printing_test }\r\n" +
67
+ "\e!\x00" + # Font A
68
+ "#{ value[:name] }\r\n" +
69
+ "#{ value[:device].inspect }"
70
+
71
+ ActiveRecord::Base.logger.info "[PRINTING] Testing #{value[:device].inspect }"
72
+ if chartest
73
+ print(id, init + self.all_chars + cut)
74
+ else
75
+ ascifiier = Escper::Asciifier.new(value[:codepage])
76
+ print(id, init + ascifiier.process(testtext) + cut)
77
+ end
78
+ end
79
+ close
80
+ end
81
+
82
+ def open
83
+ ActiveRecord::Base.logger.info "[PRINTING]============"
84
+ ActiveRecord::Base.logger.info "[PRINTING]OPEN Printers..."
85
+ @vendor_printers.size.times do |i|
86
+ p = @vendor_printers[i]
87
+ name = p.name
88
+ path = p.path
89
+ if @mode != 'local' and SalorHospitality::Application::SH_DEBIAN_SITEID != 'none'
90
+ path = File.join('/', 'var', 'lib', 'salor-hospitality', SalorHospitality::Application::SH_DEBIAN_SITEID, 'public', 'uploads', "#{path}.salor")
91
+ end
92
+ ActiveRecord::Base.logger.info "[PRINTING] Trying to open #{ name } @ #{ path } ..."
93
+ pid = p.id ? p.id : i
94
+ begin
95
+ printer = SerialPort.new path, 9600
96
+ @open_printers.merge! pid => { :name => name, :path => path, :copies => p.copies, :device => printer }
97
+ ActiveRecord::Base.logger.info "[PRINTING] Success for SerialPort: #{ printer.inspect }"
98
+ next
99
+ rescue Exception => e
100
+ ActiveRecord::Base.logger.info "[PRINTING] Failed to open as SerialPort: #{ e.inspect }"
101
+ end
102
+
103
+ begin
104
+ printer = File.open path, 'wb'
105
+ @open_printers.merge! pid => { :name => name, :path => path, :copies => p.copies, :device => printer }
106
+ ActiveRecord::Base.logger.info "[PRINTING] Success for File: #{ printer.inspect }"
107
+ next
108
+ rescue Errno::EBUSY
109
+ ActiveRecord::Base.logger.info "[PRINTING] The File #{ path } is already open."
110
+ ActiveRecord::Base.logger.info "[PRINTING] Trying to reuse already opened printers."
111
+ previously_opened_printers = @open_printers.clone
112
+ previously_opened_printers.each do |key, val|
113
+ ActiveRecord::Base.logger.info "[PRINTING] Trying to reuse already opened File #{ key }: #{ val.inspect }"
114
+ if val[:path] == p[:path] and val[:device].class == File
115
+ ActiveRecord::Base.logger.info "[PRINTING] Reused."
116
+ @open_printers.merge! pid => { :name => name, :path => path, :copies => p.copies, :device => val[:device] }
117
+ break
118
+ end
119
+ end
120
+ unless @open_printers.has_key? p.id
121
+ if SalorHospitality::Application::SH_DEBIAN_SITEID == 'none'
122
+ path = File.join(Rails.root, 'tmp')
123
+ else
124
+ path = File.join('/', 'var', 'lib', 'salor-hospitality', SalorHospitality::Application::SH_DEBIAN_SITEID)
125
+ end
126
+ printer = File.open(File.join(path, "#{ p.id }-#{ name }-fallback-busy.salor"), 'wb')
127
+ @open_printers.merge! pid => { :name => name, :path => path, :copies => p.copies, :device => printer }
128
+ ActiveRecord::Base.logger.info "[PRINTING] Failed to open as either SerialPort or USB File and resource IS busy. This should not have happened. Created #{ printer.inspect } instead."
129
+ end
130
+ next
131
+ rescue Exception => e
132
+ if SalorHospitality::Application::SH_DEBIAN_SITEID == 'none'
133
+ path = File.join(Rails.root, 'tmp')
134
+ else
135
+ path = File.join('/', 'var', 'lib', 'salor-hospitality', SalorHospitality::Application::SH_DEBIAN_SITEID)
136
+ end
137
+ printer = File.open(File.join(path, "#{ p.id }-#{ name }-fallback-notbusy.salor"), 'wb')
138
+ @open_printers.merge! pid => { :name => name, :path => path, :copies => p.copies, :device => printer }
139
+ ActiveRecord::Base.logger.info "[PRINTING] Failed to open as either SerialPort or USB File and resource is NOT busy. Created #{ printer.inspect } instead."
140
+ end
141
+ end
142
+ end
143
+
144
+ def close
145
+ ActiveRecord::Base.logger.info "[PRINTING]============"
146
+ ActiveRecord::Base.logger.info "[PRINTING]CLOSING Printers..."
147
+ @open_printers.each do |key, value|
148
+ begin
149
+ value[:device].close
150
+ ActiveRecord::Base.logger.info "[PRINTING] Closing #{ value[:name] } @ #{ value[:device].inspect }"
151
+ @open_printers.delete(key)
152
+ rescue Exception => e
153
+ ActiveRecord::Base.logger.info "[PRINTING] Error during closing of #{ value[:device] }: #{ e.inspect }"
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -1,3 +1,3 @@
1
1
  module Escper
2
- VERSION = "1.0.4"
2
+ VERSION = "1.0.5"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: escper
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,24 +9,8 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-20 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: rmagick
16
- requirement: !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
19
- - - ! '>='
20
- - !ruby/object:Gem::Version
21
- version: '0'
22
- type: :runtime
23
- prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ! '>='
28
- - !ruby/object:Gem::Version
29
- version: '0'
12
+ date: 2012-11-23 00:00:00.000000000 Z
13
+ dependencies: []
30
14
  description: ''
31
15
  email:
32
16
  - office@michaelfranzl.com
@@ -40,6 +24,10 @@ files:
40
24
  - Rakefile
41
25
  - escper.gemspec
42
26
  - lib/escper.rb
27
+ - lib/escper/asciifier.rb
28
+ - lib/escper/codepages.yml
29
+ - lib/escper/image.rb
30
+ - lib/escper/printer.rb
43
31
  - lib/escper/test.png
44
32
  - lib/escper/version.rb
45
33
  homepage: http://michaelfranzl.com
@@ -62,7 +50,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
62
50
  version: '0'
63
51
  requirements: []
64
52
  rubyforge_project: escper
65
- rubygems_version: 1.8.24
53
+ rubygems_version: 1.8.11
66
54
  signing_key:
67
55
  specification_version: 3
68
56
  summary: Converts bitmaps to the ESC/POS receipt printer command