pdf-reader 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/CHANGELOG +7 -1
  2. data/README.rdoc +1 -0
  3. data/Rakefile +23 -8
  4. data/lib/pdf-reader.rb +3 -1
  5. data/lib/pdf/hash.rb +5 -1
  6. data/lib/pdf/reader.rb +8 -1
  7. data/lib/pdf/reader/afm/Courier-Bold.afm +342 -0
  8. data/lib/pdf/reader/afm/Courier-BoldOblique.afm +342 -0
  9. data/lib/pdf/reader/afm/Courier-Oblique.afm +342 -0
  10. data/lib/pdf/reader/afm/Courier.afm +342 -0
  11. data/lib/pdf/reader/afm/Helvetica-Bold.afm +2827 -0
  12. data/lib/pdf/reader/afm/Helvetica-BoldOblique.afm +2827 -0
  13. data/lib/pdf/reader/afm/Helvetica-Oblique.afm +3051 -0
  14. data/lib/pdf/reader/afm/Helvetica.afm +3051 -0
  15. data/lib/pdf/reader/afm/Symbol.afm +213 -0
  16. data/lib/pdf/reader/afm/Times-Bold.afm +2588 -0
  17. data/lib/pdf/reader/afm/Times-BoldItalic.afm +2384 -0
  18. data/lib/pdf/reader/afm/Times-Italic.afm +2667 -0
  19. data/lib/pdf/reader/afm/Times-Roman.afm +2419 -0
  20. data/lib/pdf/reader/afm/ZapfDingbats.afm +225 -0
  21. data/lib/pdf/reader/buffer.rb +14 -6
  22. data/lib/pdf/reader/cid_widths.rb +61 -0
  23. data/lib/pdf/reader/cmap.rb +8 -2
  24. data/lib/pdf/reader/encoding.rb +52 -27
  25. data/lib/pdf/reader/error.rb +16 -1
  26. data/lib/pdf/reader/filter.rb +2 -0
  27. data/lib/pdf/reader/filter/ascii85.rb +3 -1
  28. data/lib/pdf/reader/filter/ascii_hex.rb +3 -1
  29. data/lib/pdf/reader/filter/depredict.rb +2 -0
  30. data/lib/pdf/reader/filter/flate.rb +3 -1
  31. data/lib/pdf/reader/filter/lzw.rb +1 -0
  32. data/lib/pdf/reader/filter/null.rb +1 -0
  33. data/lib/pdf/reader/filter/run_length.rb +2 -1
  34. data/lib/pdf/reader/font.rb +74 -18
  35. data/lib/pdf/reader/font_descriptor.rb +80 -0
  36. data/lib/pdf/reader/glyph_hash.rb +6 -0
  37. data/lib/pdf/reader/lzw.rb +1 -0
  38. data/lib/pdf/reader/object_cache.rb +1 -1
  39. data/lib/pdf/reader/object_hash.rb +1 -1
  40. data/lib/pdf/reader/page_layout.rb +125 -0
  41. data/lib/pdf/reader/page_state.rb +172 -69
  42. data/lib/pdf/reader/page_text_receiver.rb +50 -21
  43. data/lib/pdf/reader/pages_strategy.rb +17 -4
  44. data/lib/pdf/reader/parser.rb +25 -52
  45. data/lib/pdf/reader/print_receiver.rb +5 -0
  46. data/lib/pdf/reader/reference.rb +2 -0
  47. data/lib/pdf/reader/register_receiver.rb +1 -1
  48. data/lib/pdf/reader/standard_security_handler.rb +2 -0
  49. data/lib/pdf/reader/stream.rb +2 -0
  50. data/lib/pdf/reader/synchronized_cache.rb +32 -0
  51. data/lib/pdf/reader/text_receiver.rb +5 -4
  52. data/lib/pdf/reader/text_run.rb +80 -0
  53. data/lib/pdf/reader/token.rb +2 -0
  54. data/lib/pdf/reader/transformation_matrix.rb +194 -0
  55. data/lib/pdf/reader/width_calculator.rb +11 -0
  56. data/lib/pdf/reader/width_calculator/built_in.rb +50 -0
  57. data/lib/pdf/reader/width_calculator/composite.rb +27 -0
  58. data/lib/pdf/reader/width_calculator/true_type.rb +56 -0
  59. data/lib/pdf/reader/width_calculator/type_one_or_three.rb +32 -0
  60. data/lib/pdf/reader/width_calculator/type_zero.rb +24 -0
  61. data/lib/pdf/reader/xref.rb +9 -2
  62. metadata +119 -13
@@ -1,3 +1,5 @@
1
+ # coding: utf-8
2
+
1
3
  ################################################################################
2
4
  #
3
5
  # Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
@@ -56,9 +58,10 @@ class PDF::Reader
56
58
  # == Text Callbacks
57
59
  #
58
60
  # All text passed into these callbacks will be encoded as UTF-8. Depending on where (and when) the
59
- # PDF was generated, there's a good chance the text is NOT stored as UTF-8 internally so be careful
60
- # when doing a comparison on strings returned from PDF::Reader (when doing unit tests for example). The
61
- # string may not be byte-by-byte identical with the string that was originally written to the PDF.
61
+ # PDF was generated, there's a good chance the text is NOT stored as UTF-8 internally so be
62
+ # careful when doing a comparison on strings returned from PDF::Reader (when doing unit tests for
63
+ # example). The string may not be byte-by-byte identical with the string that was originally
64
+ # written to the PDF.
62
65
  #
63
66
  # - end_text_object
64
67
  # - move_to_start_of_next_line
@@ -267,6 +270,16 @@ class PDF::Reader
267
270
  end
268
271
  private
269
272
  ################################################################################
273
+ def params_to_utf8(params, font)
274
+ if params.is_a?(String)
275
+ font.to_utf8(params)
276
+ elsif params.is_a?(Array)
277
+ params.map { |i| params_to_utf8(i, font)}
278
+ else
279
+ params
280
+ end
281
+ end
282
+ ################################################################################
270
283
  # Walk over all pages in the PDF file, calling the appropriate callbacks for each page and all
271
284
  # its content
272
285
  def walk_pages (page)
@@ -363,7 +376,7 @@ class PDF::Reader
363
376
  if options[:raw_text]
364
377
  callback("#{OPERATORS[token]}_raw".to_sym, params)
365
378
  end
366
- params = fonts[current_font].to_utf8(params)
379
+ params = params_to_utf8(params, fonts[current_font])
367
380
  elsif token == "ID"
368
381
  # inline image data, first convert the current params into a more familiar hash
369
382
  map = {}
@@ -1,3 +1,5 @@
1
+ # coding: utf-8
2
+
1
3
  ################################################################################
2
4
  #
3
5
  # Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
@@ -74,8 +76,6 @@ class PDF::Reader
74
76
  STRATEGIES[token].call(self, token)
75
77
  elsif token.is_a? PDF::Reader::Reference
76
78
  token
77
- elsif token.is_a? Token
78
- token
79
79
  elsif operators.has_key? token
80
80
  Token.new(token)
81
81
  elsif token.respond_to?(:to_token)
@@ -100,6 +100,7 @@ class PDF::Reader
100
100
 
101
101
  obj = parse_token
102
102
  post_obj = parse_token
103
+
103
104
  if post_obj == "stream"
104
105
  stream(obj)
105
106
  else
@@ -171,58 +172,30 @@ class PDF::Reader
171
172
  return "" if str == ")"
172
173
  Error.assert_equal(parse_token, ")")
173
174
 
174
- ret = ""
175
- idx = 0
176
-
177
- while idx < str.size
178
- chr = str[idx,1]
179
- jump = 1
180
-
181
- if chr == "\\"
182
- jump = 2
183
- case str[idx+1, 1]
184
- when "" then jump = 1
185
- when "n" then chr = "\n"
186
- when "r" then chr = "\r"
187
- when "t" then chr = "\t"
188
- when "b" then chr = "\b"
189
- when "f" then chr = "\f"
190
- when "(" then chr = "("
191
- when ")" then chr = ")"
192
- when "\\" then chr = "\\"
193
- when "\n" then
194
- chr = ""
195
- jump = 2
196
- else
197
- if str[idx+1,3].match(/\d{3}/)
198
- jump = 4
199
- chr = str[idx+1,3].oct.chr
200
- elsif str[idx+1,2].match(/\d{2}/)
201
- jump = 3
202
- chr = ("0"+str[idx+1,2]).oct.chr
203
- elsif str[idx+1,1].match(/\d/)
204
- jump = 2
205
- chr = ("00"+str[idx+1,1]).oct.chr
206
- else
207
- jump = 1
208
- chr = ""
209
- end
210
-
211
- end
212
- elsif chr == "\r" && str[idx+1,1] == "\n"
213
- chr = "\n"
214
- jump = 2
215
- elsif chr == "\n" && str[idx+1,1] == "\r"
216
- chr = "\n"
217
- jump = 2
218
- elsif chr == "\r"
219
- chr = "\n"
220
- end
221
- ret << chr
222
- idx += jump
175
+ str.gsub!(/\\([nrtbf()\\\n]|\d{1,3})?|\r\n?|\n\r/m) do |match|
176
+ MAPPING[match] || ""
223
177
  end
224
- ret
178
+ str
225
179
  end
180
+
181
+ MAPPING = {
182
+ "\r" => "\n",
183
+ "\n\r" => "\n",
184
+ "\r\n" => "\n",
185
+ "\\n" => "\n",
186
+ "\\r" => "\r",
187
+ "\\t" => "\t",
188
+ "\\b" => "\b",
189
+ "\\f" => "\f",
190
+ "\\(" => "(",
191
+ "\\)" => ")",
192
+ "\\\\" => "\\",
193
+ "\\\n" => "",
194
+ }
195
+ 0.upto(9) { |n| MAPPING["\\00"+n.to_s] = ("00"+n.to_s).oct.chr }
196
+ 0.upto(99) { |n| MAPPING["\\0"+n.to_s] = ("0"+n.to_s).oct.chr }
197
+ 0.upto(377) { |n| MAPPING["\\"+n.to_s] = n.to_s.oct.chr }
198
+
226
199
  ################################################################################
227
200
  # Decodes the contents of a PDF Stream and returns it as a Ruby String.
228
201
  def stream (dict)
@@ -1,4 +1,9 @@
1
+ # coding: utf-8
2
+
1
3
  class PDF::Reader
4
+ # A simple receiver that prints all operaters and parameters in the content
5
+ # stream of a single page.
6
+ #
2
7
  class PrintReceiver
3
8
 
4
9
  attr_accessor :callbacks
@@ -1,3 +1,5 @@
1
+ # coding: utf-8
2
+
1
3
  ################################################################################
2
4
  #
3
5
  # Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
@@ -85,7 +85,7 @@ class PDF::Reader
85
85
  match = idx and break if count == 0
86
86
  end
87
87
 
88
- if match
88
+ if match
89
89
  return callbacks[match, methods.size]
90
90
  else
91
91
  return nil
@@ -1,3 +1,5 @@
1
+ # coding: utf-8
2
+
1
3
  ################################################################################
2
4
  #
3
5
  # Copyright (C) 2011 Evan J Brunner (ejbrun@appittome.com)
@@ -1,3 +1,5 @@
1
+ # coding: utf-8
2
+
1
3
  ################################################################################
2
4
  #
3
5
  # Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ # utilities.rb : General-purpose utility classes which don't fit anywhere else
4
+ #
5
+ # Copyright August 2012, Alex Dowad. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+ #
9
+ # This was originally written for the prawn gem.
10
+
11
+ require 'thread'
12
+
13
+ class PDF::Reader
14
+
15
+ # Throughout the pdf-reader codebase, repeated calculations which can benefit
16
+ # from caching are made In some cases, caching and reusing results can not
17
+ # only save CPU cycles but also greatly reduce memory requirements But at the
18
+ # same time, we don't want to throw away thread safety We have two
19
+ # interchangeable thread-safe cache implementations:
20
+ class SynchronizedCache
21
+ def initialize
22
+ @cache = {}
23
+ @mutex = Mutex.new
24
+ end
25
+ def [](key)
26
+ @mutex.synchronize { @cache[key] }
27
+ end
28
+ def []=(key,value)
29
+ @mutex.synchronize { @cache[key] = value }
30
+ end
31
+ end
32
+ end
@@ -1,3 +1,5 @@
1
+ # coding: utf-8
2
+
1
3
  ################################################################################
2
4
  #
3
5
  # Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
@@ -9,10 +11,10 @@
9
11
  # distribute, sublicense, and/or sell copies of the Software, and to
10
12
  # permit persons to whom the Software is furnished to do so, subject to
11
13
  # the following conditions:
12
- #
14
+ #
13
15
  # The above copyright notice and this permission notice shall be
14
16
  # included in all copies or substantial portions of the Software.
15
- #
17
+ #
16
18
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
19
  # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
20
  # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -161,7 +163,7 @@ class PDF::Reader
161
163
 
162
164
  x = (@tm[2,0]/TS_UNITS_PER_H_CHAR).to_i
163
165
  y = (ury - (@tm[2,1]/TS_UNITS_PER_V_CHAR)).to_i
164
-
166
+
165
167
  #puts "rendering '#{string}' to #{x}x#{y}"
166
168
 
167
169
  place = (@output[y] ||= (" " * urx.to_i))
@@ -255,7 +257,6 @@ class PDF::Reader
255
257
  @smallest_y_loc = key if key < @smallest_y_loc
256
258
  @location = key
257
259
  @line = @displacement[key]
258
- #puts "calculate_line_and_location: @location=#@location @line=#@line smallest_y_loc=#@smallest_y_loc"
259
260
  end
260
261
  ################################################################################
261
262
  end
@@ -0,0 +1,80 @@
1
+ # coding: utf-8
2
+
3
+ class PDF::Reader
4
+ # A value object that represents one or more consecutive characters on a page.
5
+ class TextRun
6
+ include Comparable
7
+
8
+ attr_reader :x, :y, :width, :font_size, :text
9
+
10
+ alias :to_s :text
11
+
12
+ def initialize(x, y, width, font_size, text)
13
+ @x = x
14
+ @y = y
15
+ @width = width
16
+ @font_size = font_size.floor
17
+ @text = text
18
+ end
19
+
20
+ # Allows collections of TextRun objects to be sorted. They will be sorted
21
+ # in order of their position on a cartesian plain - Top Left to Bottom Right
22
+ def <=>(other)
23
+ if x == other.x && y == other.y
24
+ 0
25
+ elsif y < other.y
26
+ 1
27
+ elsif y > other.y
28
+ -1
29
+ elsif x < other.x
30
+ -1
31
+ elsif x > other.x
32
+ 1
33
+ end
34
+ end
35
+
36
+ def endx
37
+ @endx ||= x + width
38
+ end
39
+
40
+ def mean_character_width
41
+ @width / character_count
42
+ end
43
+
44
+ def mergable?(other)
45
+ y.to_i == other.y.to_i && font_size == other.font_size && mergable_range.include?(other.x)
46
+ end
47
+
48
+ def +(other)
49
+ raise ArgumentError, "#{other} cannot be merged with this run" unless mergable?(other)
50
+
51
+ if (other.x - endx) <( font_size * 0.2)
52
+ TextRun.new(x, y, other.endx - x, font_size, text + other.text)
53
+ else
54
+ TextRun.new(x, y, other.endx - x, font_size, "#{text} #{other.text}")
55
+ end
56
+ end
57
+
58
+ def inspect
59
+ "#{text} w:#{width} f:#{font_size} @#{x},#{y}"
60
+ end
61
+
62
+ private
63
+
64
+ def mergable_range
65
+ @mergable_range ||= Range.new(endx - 3, endx + font_size)
66
+ end
67
+
68
+ def character_count
69
+ if @text.size == 1
70
+ 1.0
71
+ elsif @text.respond_to?(:bytesize)
72
+ # M17N aware VM
73
+ # so we can trust String#size to return a character count
74
+ @text.size.to_f
75
+ else
76
+ text.unpack("U*").size.to_f
77
+ end
78
+ end
79
+ end
80
+ end
@@ -1,3 +1,5 @@
1
+ # coding: utf-8
2
+
1
3
  ################################################################################
2
4
  #
3
5
  # Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
@@ -0,0 +1,194 @@
1
+ # coding: utf-8
2
+
3
+ class PDF::Reader
4
+ # co-ordinate systems in PDF files are specified using a 3x3 matrix that looks
5
+ # something like this:
6
+ #
7
+ # [ a b 0 ]
8
+ # [ c d 0 ]
9
+ # [ e f 1 ]
10
+ #
11
+ # Because the final column never changes, we can represent each matrix using
12
+ # only 6 numbers. This is important to save CPU time, memory and GC pressure
13
+ # caused by allocating too many unnecessary objects.
14
+ class TransformationMatrix
15
+ attr_reader :a, :b, :c, :d, :e, :f
16
+
17
+ def initialize(a, b, c, d, e, f)
18
+ @a, @b, @c, @d, @e, @f = a, b, c, d, e, f
19
+ end
20
+
21
+ def inspect
22
+ "#{a}, #{b}, 0,\n#{c}, #{d}, #{0},\n#{e}, #{f}, 1"
23
+ end
24
+
25
+ def to_a
26
+ [@a,@b,0,
27
+ @c,@d,0,
28
+ @e,@f,1]
29
+ end
30
+
31
+ # multiply this matrix with another.
32
+ #
33
+ # the second matrix is represented by the 6 scalar values that are changeable
34
+ # in a PDF transformation matrix.
35
+ #
36
+ # WARNING: This mutates the current matrix to avoid allocating memory when
37
+ # we don't need too. Matrices are multiplied ALL THE FREAKING TIME
38
+ # so this is a worthwhile optimisation
39
+ #
40
+ # NOTE: When multiplying matrices, ordering matters. Double check
41
+ # the PDF spec to ensure you're multiplying things correctly.
42
+ #
43
+ # NOTE: see Section 8.3.3, PDF 32000-1:2008, pp 119
44
+ #
45
+ # NOTE: The if statements in this method are ordered to prefer optimisations
46
+ # that allocate fewer objects
47
+ #
48
+ # TODO: it might be worth adding an optimised path for vertical
49
+ # displacement to speed up processing documents that use vertical
50
+ # writing systems
51
+ #
52
+ def multiply!(a,b=nil,c=nil, d=nil,e=nil,f=nil)
53
+ if a == 1 && b == 0 && c == 0 && d == 1 && e == 0 && f == 0
54
+ # the identity matrix, no effect
55
+ self
56
+ elsif @a == 1 && @b == 0 && @c == 0 && @d == 1 && @e == 0 && @f == 0
57
+ # I'm the identity matrix, so just copy values across
58
+ @a = a
59
+ @b = b
60
+ @c = c
61
+ @d = d
62
+ @e = e
63
+ @f = f
64
+ elsif a == 1 && b == 0 && c == 0 && d == 1 && f == 0
65
+ # the other matrix is a horizontal displacement
66
+ horizontal_displacement_multiply!(e)
67
+ elsif @a == 1 && @b == 0 && @c == 0 && @d == 1 && @f == 0
68
+ # I'm a horizontal displacement
69
+ horizontal_displacement_multiply_reversed!(a,b,c,d,e,f)
70
+ elsif @a != 1 && @b == 0 && @c == 0 && @d != 1 && @e == 0 && @f == 0
71
+ # I'm a xy scale
72
+ xy_scaling_multiply_reversed!(a,b,c,d,e,f)
73
+ elsif a != 1 && b == 0 && c == 0 && d != 1 && e == 0 && f == 0
74
+ # the other matrix is an xy scale
75
+ xy_scaling_multiply!(a,b,c,d,e,f)
76
+ else
77
+ faster_multiply!(a,b,c, d,e,f)
78
+ end
79
+ self
80
+ end
81
+
82
+ # Optimised method for when the second matrix in the calculation is
83
+ # a simple horizontal displacement.
84
+ #
85
+ # Like this:
86
+ #
87
+ # [ 1 2 0 ] [ 1 0 0 ]
88
+ # [ 3 4 0 ] x [ 0 1 0 ]
89
+ # [ 5 6 1 ] [ e2 0 1 ]
90
+ #
91
+ def horizontal_displacement_multiply!(e2)
92
+ @e = @e + e2
93
+ end
94
+
95
+ private
96
+
97
+ # Optimised method for when the first matrix in the calculation is
98
+ # a simple horizontal displacement.
99
+ #
100
+ # Like this:
101
+ #
102
+ # [ 1 0 0 ] [ 1 2 0 ]
103
+ # [ 0 1 0 ] x [ 3 4 0 ]
104
+ # [ 5 0 1 ] [ 5 6 1 ]
105
+ #
106
+ def horizontal_displacement_multiply_reversed!(a2,b2,c2,d2,e2,f2)
107
+ newa = a2
108
+ newb = b2
109
+ newc = c2
110
+ newd = d2
111
+ newe = (@e * a2) + e2
112
+ newf = (@e * b2) + f2
113
+ @a, @b, @c, @d, @e, @f = newa, newb, newc, newd, newe, newf
114
+ end
115
+
116
+ # Optimised method for when the second matrix in the calculation is
117
+ # an X and Y scale
118
+ #
119
+ # Like this:
120
+ #
121
+ # [ 1 2 0 ] [ 5 0 0 ]
122
+ # [ 3 4 0 ] x [ 0 5 0 ]
123
+ # [ 5 6 1 ] [ 0 0 1 ]
124
+ #
125
+ def xy_scaling_multiply!(a2,b2,c2,d2,e2,f2)
126
+ newa = @a * a2
127
+ newb = @b * d2
128
+ newc = @c * a2
129
+ newd = @d * d2
130
+ newe = @e * a2
131
+ newf = @f * d2
132
+ @a, @b, @c, @d, @e, @f = newa, newb, newc, newd, newe, newf
133
+ end
134
+
135
+ # Optimised method for when the first matrix in the calculation is
136
+ # an X and Y scale
137
+ #
138
+ # Like this:
139
+ #
140
+ # [ 5 0 0 ] [ 1 2 0 ]
141
+ # [ 0 5 0 ] x [ 3 4 0 ]
142
+ # [ 0 0 1 ] [ 5 6 1 ]
143
+ #
144
+ def xy_scaling_multiply_reversed!(a2,b2,c2,d2,e2,f2)
145
+ newa = @a * a2
146
+ newb = @a * b2
147
+ newc = @d * c2
148
+ newd = @d * d2
149
+ newe = e2
150
+ newf = f2
151
+ @a, @b, @c, @d, @e, @f = newa, newb, newc, newd, newe, newf
152
+ end
153
+
154
+ # A general solution to multiplying two 3x3 matrixes. This is correct in all cases,
155
+ # but slower due to excessive object allocations. It's not actually used in any
156
+ # active code paths, but is here for reference. Use faster_multiply instead.
157
+ #
158
+ # Like this:
159
+ #
160
+ # [ a b 0 ] [ a b 0 ]
161
+ # [ c d 0 ] x [ c d 0 ]
162
+ # [ e f 1 ] [ e f 1 ]
163
+ #
164
+ def regular_multiply!(a2,b2,c2,d2,e2,f2)
165
+ newa = (@a * a2) + (@b * c2) + (0 * e2)
166
+ newb = (@a * b2) + (@b * d2) + (0 * f2)
167
+ newc = (@c * a2) + (@d * c2) + (0 * e2)
168
+ newd = (@c * b2) + (@d * d2) + (0 * f2)
169
+ newe = (@e * a2) + (@f * c2) + (1 * e2)
170
+ newf = (@e * b2) + (@f * d2) + (1 * f2)
171
+ @a, @b, @c, @d, @e, @f = newa, newb, newc, newd, newe, newf
172
+ end
173
+
174
+ # A general solution for multiplying two matrices when we know all values
175
+ # in the final column are fixed. This is the fallback method for when none
176
+ # of the optimised methods are applicable.
177
+ #
178
+ # Like this:
179
+ #
180
+ # [ a b 0 ] [ a b 0 ]
181
+ # [ c d 0 ] x [ c d 0 ]
182
+ # [ e f 1 ] [ e f 1 ]
183
+ #
184
+ def faster_multiply!(a2,b2,c2, d2,e2,f2)
185
+ newa = (@a * a2) + (@b * c2)
186
+ newb = (@a * b2) + (@b * d2)
187
+ newc = (@c * a2) + (@d * c2)
188
+ newd = (@c * b2) + (@d * d2)
189
+ newe = (@e * a2) + (@f * c2) + e2
190
+ newf = (@e * b2) + (@f * d2) + f2
191
+ @a, @b, @c, @d, @e, @f = newa, newb, newc, newd, newe, newf
192
+ end
193
+ end
194
+ end