client_for_poslynx 0.2.4 → 0.2.5

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.
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