class PDF::Reader::TransformationMatrix

caused by allocating too many unnecessary objects.
only 6 numbers. This is important to save CPU time, memory and GC pressure
Because the final column never changes, we can represent each matrix using
[ e f 1 ]
[ c d 0 ]
[ a b 0 ]
something like this:
co-ordinate systems in PDF files are specified using a 3x3 matrix that looks

def faster_multiply!(a2,b2,c2, d2,e2,f2)


[ e f 1 ] [ e f 1 ]
[ c d 0 ] x [ c d 0 ]
[ a b 0 ] [ a b 0 ]

Like this:

of the optimised methods are applicable.
in the final column are fixed. This is the fallback method for when none
A general solution for multiplying two matrices when we know all values
def faster_multiply!(a2,b2,c2, d2,e2,f2)
  newa = (@a * a2) + (@b * c2)
  newb = (@a * b2) + (@b * d2)
  newc = (@c * a2) + (@d * c2)
  newd = (@c * b2) + (@d * d2)
  newe = (@e * a2) + (@f * c2) + e2
  newf = (@e * b2) + (@f * d2) + f2
  @a, @b, @c, @d, @e, @f = newa, newb, newc, newd, newe, newf
end

def horizontal_displacement_multiply!(e2)


[ 5 6 1 ] [ e2 0 1 ]
[ 3 4 0 ] x [ 0 1 0 ]
[ 1 2 0 ] [ 1 0 0 ]

Like this:

a simple horizontal displacement.
Optimised method for when the second matrix in the calculation is
def horizontal_displacement_multiply!(e2)
  @e = @e + e2
end

def horizontal_displacement_multiply_reversed!(a2,b2,c2,d2,e2,f2)


[ 5 0 1 ] [ 5 6 1 ]
[ 0 1 0 ] x [ 3 4 0 ]
[ 1 0 0 ] [ 1 2 0 ]

Like this:

a simple horizontal displacement.
Optimised method for when the first matrix in the calculation is
def horizontal_displacement_multiply_reversed!(a2,b2,c2,d2,e2,f2)
  newa = a2
  newb = b2
  newc = c2
  newd = d2
  newe = (@e * a2) + e2
  newf = (@e * b2) + f2
  @a, @b, @c, @d, @e, @f = newa, newb, newc, newd, newe, newf
end

def initialize(a, b, c, d, e, f)

def initialize(a, b, c, d, e, f)
  @a, @b, @c, @d, @e, @f = a, b, c, d, e, f
end

def inspect

def inspect
  "#{a}, #{b}, 0,\n#{c}, #{d}, #{0},\n#{e}, #{f}, 1"
end

def multiply!(a,b=nil,c=nil, d=nil,e=nil,f=nil)


writing systems
displacement to speed up processing documents that use vertical
TODO: it might be worth adding an optimised path for vertical

that allocate fewer objects
NOTE: The if statements in this method are ordered to prefer optimisations

NOTE: see Section 8.3.3, PDF 32000-1:2008, pp 119

the PDF spec to ensure you're multiplying things correctly.
NOTE: When multiplying matrices, ordering matters. Double check

so this is a worthwhile optimisation
we don't need too. Matrices are multiplied ALL THE FREAKING TIME
WARNING: This mutates the current matrix to avoid allocating memory when

in a PDF transformation matrix.
the second matrix is represented by the 6 scalar values that are changeable

multiply this matrix with another.
def multiply!(a,b=nil,c=nil, d=nil,e=nil,f=nil)
  if a == 1 && b == 0 && c == 0 && d == 1 && e == 0 && f == 0
    # the identity matrix, no effect
    self
  elsif @a == 1 && @b == 0 && @c == 0 && @d == 1 && @e == 0 && @f == 0
    # I'm the identity matrix, so just copy values across
    @a = a
    @b = b
    @c = c
    @d = d
    @e = e
    @f = f
  elsif a == 1 && b == 0 && c == 0 && d == 1 && f == 0
    # the other matrix is a horizontal displacement
    horizontal_displacement_multiply!(e)
  elsif @a == 1 && @b == 0 && @c == 0 && @d == 1 && @f == 0
    # I'm a horizontal displacement
    horizontal_displacement_multiply_reversed!(a,b,c,d,e,f)
  elsif @a != 1 && @b == 0 && @c == 0 && @d != 1 && @e == 0 && @f == 0
    # I'm a xy scale
    xy_scaling_multiply_reversed!(a,b,c,d,e,f)
  elsif a != 1 && b == 0 && c == 0 && d != 1 && e == 0 && f == 0
    # the other matrix is an xy scale
    xy_scaling_multiply!(a,b,c,d,e,f)
  else
    faster_multiply!(a,b,c, d,e,f)
  end
  self
end

def regular_multiply!(a2,b2,c2,d2,e2,f2)


[ e f 1 ] [ e f 1 ]
[ c d 0 ] x [ c d 0 ]
[ a b 0 ] [ a b 0 ]

Like this:

active code paths, but is here for reference. Use faster_multiply instead.
but slower due to excessive object allocations. It's not actually used in any
A general solution to multiplying two 3x3 matrixes. This is correct in all cases,
def regular_multiply!(a2,b2,c2,d2,e2,f2)
  newa = (@a * a2) + (@b * c2) + (0 * e2)
  newb = (@a * b2) + (@b * d2) + (0 * f2)
  newc = (@c * a2) + (@d * c2) + (0 * e2)
  newd = (@c * b2) + (@d * d2) + (0 * f2)
  newe = (@e * a2) + (@f * c2) + (1 * e2)
  newf = (@e * b2) + (@f * d2) + (1 * f2)
  @a, @b, @c, @d, @e, @f = newa, newb, newc, newd, newe, newf
end

def to_a

def to_a
  [@a,@b,0,
   @c,@d,0,
   @e,@f,1]
end

def xy_scaling_multiply!(a2,b2,c2,d2,e2,f2)


[ 5 6 1 ] [ 0 0 1 ]
[ 3 4 0 ] x [ 0 5 0 ]
[ 1 2 0 ] [ 5 0 0 ]

Like this:

an X and Y scale
Optimised method for when the second matrix in the calculation is
def xy_scaling_multiply!(a2,b2,c2,d2,e2,f2)
  newa = @a * a2
  newb = @b * d2
  newc = @c * a2
  newd = @d * d2
  newe = @e * a2
  newf = @f * d2
  @a, @b, @c, @d, @e, @f = newa, newb, newc, newd, newe, newf
end

def xy_scaling_multiply_reversed!(a2,b2,c2,d2,e2,f2)


[ 0 0 1 ] [ 5 6 1 ]
[ 0 5 0 ] x [ 3 4 0 ]
[ 5 0 0 ] [ 1 2 0 ]

Like this:

an X and Y scale
Optimised method for when the first matrix in the calculation is
def xy_scaling_multiply_reversed!(a2,b2,c2,d2,e2,f2)
  newa = @a * a2
  newb = @a * b2
  newc = @d * c2
  newd = @d * d2
  newe = e2
  newf = f2
  @a, @b, @c, @d, @e, @f = newa, newb, newc, newd, newe, newf
end