client_for_poslynx 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGELOG +6 -0
  3. data/README.md +3 -0
  4. data/foo.svg +20 -0
  5. data/lib/client_for_poslynx/bit_sequence.rb +139 -0
  6. data/lib/client_for_poslynx/data/requests/can_visit.rb +1 -0
  7. data/lib/client_for_poslynx/data/requests/credit_card_sale.rb +1 -0
  8. data/lib/client_for_poslynx/data/requests/pin_pad_get_signature.rb +17 -0
  9. data/lib/client_for_poslynx/data/requests.rb +1 -0
  10. data/lib/client_for_poslynx/data/responses/credit_card_sale.rb +1 -0
  11. data/lib/client_for_poslynx/data/responses/pin_pad_get_signature.rb +18 -0
  12. data/lib/client_for_poslynx/data/responses.rb +1 -0
  13. data/lib/client_for_poslynx/experimental.rb +16 -0
  14. data/lib/client_for_poslynx/has_client_console_support.rb +7 -1
  15. data/lib/client_for_poslynx/message_handling/xml_extractor.rb +3 -2
  16. data/lib/client_for_poslynx/signature_image/draw.rb +43 -0
  17. data/lib/client_for_poslynx/signature_image/move.rb +43 -0
  18. data/lib/client_for_poslynx/signature_image/to_svg_converter.rb +99 -0
  19. data/lib/client_for_poslynx/signature_image.rb +97 -0
  20. data/lib/client_for_poslynx/version.rb +1 -1
  21. data/spec/client_for_poslynx/bit_sequence_spec.rb +147 -0
  22. data/spec/client_for_poslynx/data/requests/credit_card_sale_spec.rb +15 -11
  23. data/spec/client_for_poslynx/data/requests/pin_pad_get_signature_spec.rb +72 -0
  24. data/spec/client_for_poslynx/data/responses/credit_card_sale_spec.rb +4 -0
  25. data/spec/client_for_poslynx/data/responses/pin_pad_display_message_spec.rb +14 -18
  26. data/spec/client_for_poslynx/signature_image/to_svg_converter_spec.rb +114 -0
  27. data/spec/client_for_poslynx/signature_image_spec.rb +91 -0
  28. data/spec/spec_helper.rb +1 -0
  29. metadata +19 -2
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- Y2M2MDJlODNlYWUyOTQ4MGY2MWU5MjAxNzhjMzY3ZTYzYTY4YTZhMg==
4
+ ZmJmMWFiZTY5MDAyYWRiMDM4ZGUxY2ZhYzJhZjEwNmJmMDc3ODU3NQ==
5
5
  data.tar.gz: !binary |-
6
- MmRiYjM3YzcwN2MwNzM1NjdiNjlkNGUyNmU3NTViMDU3Mjg3MGNhOQ==
6
+ NmZkZTU4MGVlOGVjY2VkMzgxNjU2MjdhM2E3MDFhOWUyNmQyOWY1Yg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- OTIwNDhmN2I4MzIyOTFkYmI5MzM2NzBhNTgzNDg1OGMxNDdlZjM3MzcyMjI1
10
- ODVmZTk5YjNlMDVjMmMxNTNlMGNkZGM0NGNiM2RkNGRkMWIzYzY5YmM0NjNl
11
- MjkyOTQzNWMyMWExZDBlOWU1YTRlYTc5YjQ3NjJiMDVjNzVkNGU=
9
+ YTliZjg2OGE3MjIwYjMwNzFiZmI5ZjVhZGVkMDRmZWE2ZmVkM2E0ZWQwZjk5
10
+ NjQ4NzUxNzg5OGI0NGZhZGVjNGY3YjI0ZGNiYTZkNDAxMDVlNDE3ZjY0Y2U0
11
+ ZDkxOThmODRmMWYwMTI5ZjRkMWVlM2YwZGVjNzgxMThiYTliNWY=
12
12
  data.tar.gz: !binary |-
13
- MzNiYmNmMDRhYmJjNmZjODc4MjEwNzhhMzNiZDk5ODA4MTc5Mjg1ODNjY2Fl
14
- YWE5MDJiM2NkNmQwM2IwM2VmMzQ3NGVkZTAyZTg3OWExYWNiZTViOGEzMjQx
15
- YjMzNjhhNWVkN2FkNjdjZDIwNWM2MDVlMWMwNjFkZmMxMDIxNjQ=
13
+ YmEwNTQ1NzZlM2RhNjc1YzNlZTg0ZDk2NDhhMmQ1OWMxODZhMjY2YWZkYjkw
14
+ ZDg3NWJiZTJkOWZiMDY2ODY5MTkyODZlMTlmMTlhNmVlODNlNzcwY2UzMTFl
15
+ NDRmZjlhNGViZWZhNjk2ZjJlZjM4OWE1YzNjZmUxMjkxNmZmY2Q=
data/CHANGELOG CHANGED
@@ -23,3 +23,9 @@ Version 0.2.3
23
23
  Version 0.2.4
24
24
  - Fix bug: Fake terminal script crashes during user interaction
25
25
  for a debit sale request without a specified cash back amount.
26
+
27
+ Version 0.2.4
28
+ - Introduce experimental signature image parsing and conversion
29
+ to SVG.
30
+ - Fix bug: New format of POSLynx response message XML was causing
31
+ client to hang.
data/README.md CHANGED
@@ -45,6 +45,9 @@ lib/client_for_poslynx/has_client_colsole_support.rb file
45
45
  provides a good example of how to use the facilities that this
46
46
  gem provides.
47
47
 
48
+ Some releases may also include experimental features that must
49
+ be separately loaded by requiring "client_for_poslynx/experimental'.
50
+
48
51
  ## Known Limitations
49
52
 
50
53
  * Only a subset of the possible messages and elements is supported.
data/foo.svg ADDED
@@ -0,0 +1,20 @@
1
+ <svg version="1.1"
2
+ width="67.17mm" height="13.434mm"
3
+ viewBox="0 0 640 128"
4
+ preserveAspectRatio='none'
5
+ xmlns="http://www.w3.org/2000/svg"
6
+ >
7
+
8
+ <path d='M 40,10 l 0,30 0,30 -5,5 -3,1 -3,-1 -5,-5
9
+ M 24,10 l 30,0
10
+ M 65,40 l 10,0 6,4 4,6 0,15 -4,6 -6,4 -10,0 -6,-4 -4,-6 0,-15, 4,-6
11
+ M 93,40 l 14,28 5,10
12
+ M 130,40 l -15,30 -15,30 M 160,50 l 0,0'
13
+ stroke='black'
14
+ stroke-width='1mm'
15
+ stroke-linejoin='round'
16
+ stroke-linecap='square'
17
+ fill='none'
18
+ />
19
+
20
+ </svg>
@@ -0,0 +1,139 @@
1
+ # coding: binary
2
+
3
+ module ClientForPoslynx
4
+ class BitSequence
5
+ class TooManyBitsLong < StandardError ; end
6
+ class NumberOutOfBounds < StandardError ; end
7
+
8
+ class << self
9
+ private :new
10
+
11
+ def from_packed_bits(packed_string)
12
+ digits_string = packed_string.unpack('B*').first
13
+ from_bit_digits( digits_string )
14
+ end
15
+
16
+ alias ^ from_packed_bits
17
+
18
+ def new_empty
19
+ from_bit_digits('')
20
+ end
21
+
22
+ def from_bit_digits(digits_string)
23
+ new( digits_string )
24
+ end
25
+
26
+ alias / from_bit_digits
27
+
28
+ def from_unsigned(value, seq_length)
29
+ if seq_length > 64
30
+ raise TooManyBitsLong, "Can't build a representation more than 64 bits long from an unsigned number"
31
+ end
32
+ if value < 0
33
+ raise NumberOutOfBounds, "Can't build an unsigned representation of a negative number"
34
+ end
35
+ if value >= 2 ** seq_length
36
+ raise NumberOutOfBounds, "The largest value representable in #{seq_length} bits is less than #{value}"
37
+ end
38
+ packed_bits = [ value ].pack('Q>')
39
+ bit_seq = from_packed_bits( packed_bits )
40
+ bit_seq.shift( 64 - seq_length )
41
+ bit_seq
42
+ end
43
+
44
+ def from_sign_and_magnitude_of(value, seq_length)
45
+ magnitude = value.abs
46
+ magnitude_seq = from_unsigned( magnitude, seq_length - 1 )
47
+ sign_bit = from_bit_digits( value < 0 ? '1' : '0' )
48
+ magnitude_seq.unshift( sign_bit )
49
+ end
50
+
51
+ def from_uuencoded(uuencoded)
52
+ packed_bits = uuencoded.unpack('u').first
53
+ from_packed_bits( packed_bits )
54
+ end
55
+ end
56
+
57
+ def initialize(digits_string)
58
+ @digits_string = digits_string
59
+ end
60
+
61
+ def inspect
62
+ "#<#{self.class.name}: #{pretty_digits}>"
63
+ end
64
+
65
+ def pretty_digits
66
+ nibbles = digits_string.scan(/.{1,4}/)
67
+ bytes = nibbles.each_slice(2).map{ |n| n.join(' ') }
68
+ bytes.join(' ')
69
+ end
70
+
71
+ def ==(other)
72
+ return false unless other.respond_to?( :to_bit_digits )
73
+ digits_string == other.to_bit_digits
74
+ end
75
+
76
+ def to_packed_bits
77
+ [ digits_string ].pack('B*')
78
+ end
79
+
80
+ def to_bit_digits
81
+ digits_string
82
+ end
83
+
84
+ def uuencode
85
+ [ to_packed_bits ].pack('u')
86
+ end
87
+
88
+ def length
89
+ digits_string.length
90
+ end
91
+
92
+ def first_bit_digit
93
+ digits_string[0..0]
94
+ end
95
+
96
+ def push(sequence)
97
+ digits_string << sequence.to_bit_digits
98
+ self
99
+ end
100
+
101
+ alias << push
102
+
103
+ def shift(bit_count)
104
+ digits_taken = digits_string[0...bit_count]
105
+ digits_string[0...bit_count] = ''
106
+ self.class.from_bit_digits( digits_taken )
107
+ end
108
+
109
+ def unshift( bit_sequence )
110
+ digits_string[0...0] = bit_sequence.to_bit_digits
111
+ self
112
+ end
113
+
114
+ def as_unsigned
115
+ if length > 64
116
+ raise TooManyBitsLong,
117
+ "Cannot coerce sequence longer than 64 bits to unsigned number"
118
+ end
119
+ little_endian_digits = digits_string.reverse + '0' * 64
120
+ packed_little_endian = [ little_endian_digits ].pack('b*')
121
+ packed_little_endian.unpack('Q<').first
122
+ end
123
+
124
+ def as_sign_and_magnitude
125
+ if length > 65
126
+ raise TooManyBitsLong,
127
+ "Cannot coerce sequence longer than 65 bits to unsigned number"
128
+ end
129
+ magnitude_seq = self.class.from_bit_digits( digits_string[1..-1] )
130
+ magnitude = magnitude_seq.as_unsigned
131
+ first_bit_digit == '1' ? - magnitude : magnitude
132
+ end
133
+
134
+ private
135
+
136
+ attr_reader :digits_string
137
+
138
+ end
139
+ end
@@ -11,6 +11,7 @@ module ClientForPoslynx
11
11
  def visit_PinPadInitialize(visitee) ; visit_general visitee ; end
12
12
  def visit_PinPadDisplayMessage(visitee) ; visit_general visitee ; end
13
13
  def visit_PinPadDisplaySpecifiedForm(visitee) ; visit_general visitee ; end
14
+ def visit_PinPadGetSignature(visitee) ; visit_general visitee ; end
14
15
 
15
16
  def visit_general(visitee) ; end
16
17
 
@@ -20,6 +20,7 @@ module ClientForPoslynx
20
20
  attr_element_mapping attribute: :track_1, element: 'Track1'
21
21
  attr_element_mapping attribute: :card_number, element: 'CardNumber'
22
22
  attr_element_mapping attribute: :expiry_date, element: 'ExpiryDate'
23
+ attr_element_mapping attribute: :capture_signature, element: 'ReqPPSigCapture'
23
24
 
24
25
  end
25
26
 
@@ -0,0 +1,17 @@
1
+ # coding: utf-8
2
+
3
+ require_relative 'abstract_request'
4
+
5
+ module ClientForPoslynx
6
+ module Data
7
+ module Requests
8
+
9
+ class PinPadGetSignature < AbstractRequest
10
+
11
+ defining_property_value attribute: :command, element: 'Command', value: 'PPGETSIGNATURE'
12
+
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -5,6 +5,7 @@ require_relative 'requests/debit_card_sale'
5
5
  require_relative 'requests/pin_pad_initialize'
6
6
  require_relative 'requests/pin_pad_display_message'
7
7
  require_relative 'requests/pin_pad_display_specified_form'
8
+ require_relative 'requests/pin_pad_get_signature'
8
9
  require_relative 'requests/can_visit'
9
10
 
10
11
  module ClientForPoslynx
@@ -23,6 +23,7 @@ module ClientForPoslynx
23
23
  attr_element_mapping attribute: :transaction_date, element: 'TransactionDate'
24
24
  attr_element_mapping attribute: :transaction_time, element: 'TransactionTime'
25
25
  attr_element_mapping attribute: :input_method, element: 'InputMethod'
26
+ attr_element_mapping attribute: :signature, element: 'Signature'
26
27
  attr_element_mapping attribute: :receipt, element: 'Receipt', numbered_lines: 'Receipt%d'
27
28
  attr_element_mapping attribute: :customer_receipt, element: 'ReceiptCustomer', numbered_lines: 'Receipt%d'
28
29
 
@@ -0,0 +1,18 @@
1
+ # coding: utf-8
2
+
3
+ require_relative 'abstract_response'
4
+
5
+ module ClientForPoslynx
6
+ module Data
7
+ module Responses
8
+
9
+ class PinPadGetSignature < AbstractResponse
10
+
11
+ defining_property_value attribute: :command, element: 'Command', value: 'PPGETSIGNATURE'
12
+ attr_element_mapping attribute: :signature, element: 'Signature'
13
+
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -5,6 +5,7 @@ require_relative 'responses/debit_card_sale'
5
5
  require_relative 'responses/pin_pad_initialize'
6
6
  require_relative 'responses/pin_pad_display_message'
7
7
  require_relative 'responses/pin_pad_display_specified_form'
8
+ require_relative 'responses/pin_pad_get_signature'
8
9
 
9
10
  module ClientForPoslynx
10
11
  module Data
@@ -0,0 +1,16 @@
1
+ # coding: utf-8
2
+
3
+ # Require this file to load experimental features that
4
+ # are not yet expected to be fully usable.
5
+
6
+ # Bit sequence is actually in pretty good shape, but it's
7
+ # here because it is only used by SignatureImage.
8
+ require "client_for_poslynx/bit_sequence"
9
+
10
+ # SignatureImage currently only supports the legacy
11
+ # Hypercom/Equinox signature data format, which is useful
12
+ # to approximately 0% of POSLynx users. It has also not
13
+ # been tested with real data produced by a payment terminal,
14
+ # so it is possible that some understandings about the
15
+ # format are incorrect.
16
+ require "client_for_poslynx/signature_image"
@@ -22,7 +22,7 @@ module ClientForPoslynx
22
22
  conn = TCPSocket.new( config.host, config.port )
23
23
  conn.puts request.xml_serialize
24
24
  response = get_response_from( conn )
25
- conn.close unless conn.eof?
25
+ conn.close unless conn.closed?
26
26
  response
27
27
  end
28
28
 
@@ -81,6 +81,12 @@ module ClientForPoslynx
81
81
  }
82
82
  end
83
83
 
84
+ def example_pin_pad_get_signature
85
+ ClientForPoslynx::Data::Requests::PinPadGetSignature.new.tap { |req|
86
+ assign_common_example_request_attrs_to req
87
+ }
88
+ end
89
+
84
90
  private
85
91
 
86
92
  def get_response_from( connection )
@@ -23,11 +23,12 @@ module ClientForPoslynx
23
23
  root_name = nil
24
24
  while true
25
25
  line = stream.gets
26
+ puts line
26
27
  message << line
27
- if (! root_name) && line =~ /^<([A-Za-z_][^\s>]*)[ >]/
28
+ if (! root_name) && line =~ /^(?:<\?.+?\?>)?<([A-Za-z_][^\s>]*)[ >]/
28
29
  root_name = $1
29
30
  end
30
- break if root_name && line =~ /<\/#{root_name}\s*>$/
31
+ break if root_name && line =~ /<\/#{root_name}\s*>\s*$/
31
32
  end
32
33
  message
33
34
  end
@@ -0,0 +1,43 @@
1
+ # coding: utf-8
2
+
3
+ module ClientForPoslynx
4
+ class SignatureImage
5
+
6
+ class Draw
7
+ DELTA_BITS_LONG = 6
8
+ BIT_SEQUENCE_LENGTH = 1 + DELTA_BITS_LONG * 2
9
+
10
+ def self.first_in_bit_sequence(bit_seq)
11
+ bit_seq.first_bit_digit == '0' &&
12
+ bit_seq.length >= BIT_SEQUENCE_LENGTH
13
+ end
14
+
15
+ def self.parse_from_bit_sequence!(bit_seq)
16
+ bit_seq.shift 1
17
+ dx_bit_seq = bit_seq.shift( DELTA_BITS_LONG )
18
+ dy_bit_seq = bit_seq.shift( DELTA_BITS_LONG )
19
+ new( dx_bit_seq.as_sign_and_magnitude, dy_bit_seq.as_sign_and_magnitude )
20
+ end
21
+
22
+ attr_reader :dx, :dy
23
+
24
+ def initialize(dx, dy)
25
+ @dx = dx
26
+ @dy = dy
27
+ end
28
+
29
+ def ==(other)
30
+ return false unless self.class === other
31
+ return dx == other.dx && dy == other.dy
32
+ end
33
+
34
+ def to_bit_sequence
35
+ bit_seq = ClientForPoslynx::BitSequence / '0'
36
+ bit_seq << ClientForPoslynx::BitSequence.from_sign_and_magnitude_of( dx, DELTA_BITS_LONG )
37
+ bit_seq << ClientForPoslynx::BitSequence.from_sign_and_magnitude_of( dy, DELTA_BITS_LONG )
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,43 @@
1
+ # coding: utf-8
2
+
3
+ module ClientForPoslynx
4
+ class SignatureImage
5
+
6
+ class Move
7
+ X_BITS_LONG = 10
8
+ Y_BITS_LONG = 7
9
+ BIT_SEQUENCE_LENGTH = 1 + X_BITS_LONG + Y_BITS_LONG
10
+
11
+ def self.first_in_bit_sequence(bit_seq)
12
+ bit_seq.first_bit_digit == '1' &&
13
+ bit_seq.length >= BIT_SEQUENCE_LENGTH
14
+ end
15
+
16
+ def self.parse_from_bit_sequence!(bit_seq)
17
+ bit_seq.shift 1
18
+ x_bit_seq = bit_seq.shift( X_BITS_LONG )
19
+ y_bit_seq = bit_seq.shift( Y_BITS_LONG )
20
+ new( x_bit_seq.as_unsigned, y_bit_seq.as_unsigned )
21
+ end
22
+
23
+ attr_reader :x, :y
24
+
25
+ def initialize(x,y)
26
+ @x = x
27
+ @y = y
28
+ end
29
+
30
+ def ==(other)
31
+ return false unless self.class === other
32
+ return x == other.x && y == other.y
33
+ end
34
+
35
+ def to_bit_sequence
36
+ bit_seq = ClientForPoslynx::BitSequence / '1'
37
+ bit_seq << ClientForPoslynx::BitSequence.from_unsigned( x, X_BITS_LONG )
38
+ bit_seq << ClientForPoslynx::BitSequence.from_unsigned( y, Y_BITS_LONG )
39
+ end
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,99 @@
1
+ # coding: utf-8
2
+
3
+ module ClientForPoslynx
4
+ class SignatureImage
5
+
6
+ class ToSvgConverter
7
+
8
+ class << self
9
+
10
+ private :new
11
+
12
+ def convert( signature_image )
13
+ new( signature_image ).call
14
+ end
15
+
16
+ end
17
+
18
+ attr_accessor :signature_image
19
+ private :signature_image=
20
+
21
+ def initialize( signature_image )
22
+ self.signature_image = signature_image
23
+ end
24
+
25
+ def call
26
+ apply_document_characteristics
27
+
28
+ path_el = svg_document.create_element('path')
29
+ svg_element.add_child path_el
30
+
31
+ apply_path_characteristics_to path_el
32
+
33
+ path_el['d'] = path_data
34
+
35
+ svg_document.to_xml
36
+ end
37
+
38
+ private
39
+
40
+ def apply_document_characteristics
41
+ svg_element['width' ] = '67.17mm'
42
+ svg_element['height' ] = '13.434mm'
43
+ svg_element['viewBox' ] = '0 0 640 128'
44
+ svg_element['preserveAspectRatio'] = 'none'
45
+ end
46
+
47
+ def svg_element
48
+ svg_document.root
49
+ end
50
+
51
+ def svg_document
52
+ @svg_document ||= begin
53
+ doc = Nokogiri::XML::Document.new
54
+ root_el = doc.root = doc.create_element('svg')
55
+ root_el.default_namespace = 'http://www.w3.org/2000/svg'
56
+ root_el['version'] = '1.1'
57
+ doc
58
+ end
59
+ end
60
+
61
+ def apply_path_characteristics_to(path_el)
62
+ apply_stroke_styling_to path_el
63
+ path_el['fill'] = 'none'
64
+ end
65
+
66
+ def apply_stroke_styling_to(path_el)
67
+ path_el['stroke' ] = 'black'
68
+ path_el['stroke-width' ] = '1mm'
69
+ path_el['stroke-linejoin'] = 'round'
70
+ # Square line cap improves visibility of zero-length
71
+ # lines (dots).
72
+ path_el['stroke-linecap' ] = 'square'
73
+ end
74
+
75
+ def path_data
76
+ data = ''
77
+ signature_image.shape_step_groups.each do |shape_steps|
78
+ data << abs_moveto_instruction_for_move_step( shape_steps.first )
79
+ data << rel_lineto_instruction_for_draw_steps( shape_steps[1..-1] )
80
+ end
81
+ data
82
+ end
83
+
84
+ def abs_moveto_instruction_for_move_step(step)
85
+ "M #{step.x},#{step.y} "
86
+ end
87
+
88
+ def rel_lineto_instruction_for_draw_steps(steps)
89
+ return 'l 0,0 ' if steps.empty?
90
+
91
+ steps.reduce( 'l ' ) { |instruction, step|
92
+ instruction << "#{step.dx},#{step.dy} "
93
+ }
94
+ end
95
+
96
+ end
97
+
98
+ end
99
+ end
@@ -0,0 +1,97 @@
1
+ # coding: utf-8
2
+
3
+ require_relative 'signature_image/move'
4
+ require_relative 'signature_image/draw'
5
+ require_relative 'signature_image/to_svg_converter'
6
+
7
+ module ClientForPoslynx
8
+
9
+ class SignatureImage
10
+
11
+ class << self
12
+
13
+ def deserialize(serialized_data)
14
+ packed_binary = uudecode( serialized_data )
15
+ bit_seq = ClientForPoslynx::BitSequence.from_packed_bits( packed_binary )
16
+
17
+ sig_image = new
18
+ step_types = [ SignatureImage::Move, SignatureImage::Draw ]
19
+ while true
20
+ step_type = step_types.detect{ |st| st.first_in_bit_sequence( bit_seq ) }
21
+ break if step_type.nil?
22
+ step = step_type.parse_from_bit_sequence!( bit_seq )
23
+ sig_image << step
24
+ end
25
+
26
+ sig_image
27
+ end
28
+
29
+ def uudecode(uuencoded)
30
+ uuencoded.unpack('u').first
31
+ end
32
+
33
+ end
34
+
35
+ def ==(other)
36
+ return false unless self.class === other
37
+ sequence == other.sequence
38
+ end
39
+
40
+ def move(*args)
41
+ sequence << Move.new(*args)
42
+ end
43
+
44
+ def draw(*args)
45
+ sequence << Draw.new(*args)
46
+ end
47
+
48
+ def <<(step)
49
+ sequence << step
50
+ end
51
+
52
+ def each_step
53
+ sequence.each do |step| ; yield step ; end
54
+ end
55
+
56
+ def shape_step_groups
57
+ groups = []
58
+ group = []
59
+ sequence.each do |step|
60
+ if SignatureImage::Move === step
61
+ group = []
62
+ groups << group
63
+ end
64
+ group << step
65
+ end
66
+ groups
67
+ end
68
+
69
+ def serialize_legacy
70
+ unless Move === sequence.first
71
+ raise 'Must have an initial move-type step in order to serialize'
72
+ end
73
+ bit_seq = ClientForPoslynx::BitSequence.new_empty
74
+ sequence.each do |step|
75
+ bit_seq << step.to_bit_sequence
76
+ end
77
+ bit_seq.uuencode
78
+ end
79
+
80
+ protected
81
+
82
+ def sequence
83
+ @sequence ||= []
84
+ end
85
+
86
+ private
87
+
88
+ def pack_binary_digits(digits_string)
89
+ [ digits_string ].pack('B*')
90
+ end
91
+
92
+ def uuencode_packed_bits(packed_bits_string)
93
+ [ packed_bits_string ].pack('u')
94
+ end
95
+ end
96
+
97
+ end
@@ -1,5 +1,5 @@
1
1
  # coding: utf-8
2
2
 
3
3
  module ClientForPoslynx
4
- VERSION = '0.2.4'
4
+ VERSION = '0.2.5'
5
5
  end