pdf-reader 1.2.0 → 1.3.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.
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