This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
# coding: utf-8
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PDF::Reader
|
||||
# co-ordinate systems in PDF files are specified using a 3x3 matrix that looks
|
||||
# something like this:
|
||||
#
|
||||
# [ a b 0 ]
|
||||
# [ c d 0 ]
|
||||
# [ e f 1 ]
|
||||
#
|
||||
# Because the final column never changes, we can represent each matrix using
|
||||
# only 6 numbers. This is important to save CPU time, memory and GC pressure
|
||||
# caused by allocating too many unnecessary objects.
|
||||
class TransformationMatrix
|
||||
attr_reader :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
|
||||
"#{a}, #{b}, 0,\n#{c}, #{d}, #{0},\n#{e}, #{f}, 1"
|
||||
end
|
||||
|
||||
def to_a
|
||||
[@a,@b,0,
|
||||
@c,@d,0,
|
||||
@e,@f,1]
|
||||
end
|
||||
|
||||
# multiply this matrix with another.
|
||||
#
|
||||
# the second matrix is represented by the 6 scalar values that are changeable
|
||||
# in a PDF transformation matrix.
|
||||
#
|
||||
# WARNING: This mutates the current matrix to avoid allocating memory when
|
||||
# we don't need too. Matrices are multiplied ALL THE FREAKING TIME
|
||||
# so this is a worthwhile optimisation
|
||||
#
|
||||
# NOTE: When multiplying matrices, ordering matters. Double check
|
||||
# the PDF spec to ensure you're multiplying things correctly.
|
||||
#
|
||||
# NOTE: see Section 8.3.3, PDF 32000-1:2008, pp 119
|
||||
#
|
||||
# NOTE: The if statements in this method are ordered to prefer optimisations
|
||||
# that allocate fewer objects
|
||||
#
|
||||
# TODO: it might be worth adding an optimised path for vertical
|
||||
# displacement to speed up processing documents that use vertical
|
||||
# writing systems
|
||||
#
|
||||
def multiply!(a,b,c, d,e,f)
|
||||
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
|
||||
|
||||
# Optimised method for when the second matrix in the calculation is
|
||||
# a simple horizontal displacement.
|
||||
#
|
||||
# Like this:
|
||||
#
|
||||
# [ 1 2 0 ] [ 1 0 0 ]
|
||||
# [ 3 4 0 ] x [ 0 1 0 ]
|
||||
# [ 5 6 1 ] [ e2 0 1 ]
|
||||
#
|
||||
def horizontal_displacement_multiply!(e2)
|
||||
@e = @e + e2
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Optimised method for when the first matrix in the calculation is
|
||||
# a simple horizontal displacement.
|
||||
#
|
||||
# Like this:
|
||||
#
|
||||
# [ 1 0 0 ] [ 1 2 0 ]
|
||||
# [ 0 1 0 ] x [ 3 4 0 ]
|
||||
# [ 5 0 1 ] [ 5 6 1 ]
|
||||
#
|
||||
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
|
||||
|
||||
# Optimised method for when the second matrix in the calculation is
|
||||
# an X and Y scale
|
||||
#
|
||||
# Like this:
|
||||
#
|
||||
# [ 1 2 0 ] [ 5 0 0 ]
|
||||
# [ 3 4 0 ] x [ 0 5 0 ]
|
||||
# [ 5 6 1 ] [ 0 0 1 ]
|
||||
#
|
||||
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
|
||||
|
||||
# Optimised method for when the first matrix in the calculation is
|
||||
# an X and Y scale
|
||||
#
|
||||
# Like this:
|
||||
#
|
||||
# [ 5 0 0 ] [ 1 2 0 ]
|
||||
# [ 0 5 0 ] x [ 3 4 0 ]
|
||||
# [ 0 0 1 ] [ 5 6 1 ]
|
||||
#
|
||||
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
|
||||
|
||||
# A general solution to multiplying two 3x3 matrixes. This is correct in all cases,
|
||||
# but slower due to excessive object allocations. It's not actually used in any
|
||||
# active code paths, but is here for reference. Use faster_multiply instead.
|
||||
#
|
||||
# Like this:
|
||||
#
|
||||
# [ a b 0 ] [ a b 0 ]
|
||||
# [ c d 0 ] x [ c d 0 ]
|
||||
# [ e f 1 ] [ e f 1 ]
|
||||
#
|
||||
def regular_multiply!(a2,b2,c2,d2,e2,f2)
|
||||
newa = (@a * a2) + (@b * c2) + (e2 * 0)
|
||||
newb = (@a * b2) + (@b * d2) + (f2 * 0)
|
||||
newc = (@c * a2) + (@d * c2) + (e2 * 0)
|
||||
newd = (@c * b2) + (@d * d2) + (f2 * 0)
|
||||
newe = (@e * a2) + (@f * c2) + (e2 * 1)
|
||||
newf = (@e * b2) + (@f * d2) + (f2 * 1)
|
||||
@a, @b, @c, @d, @e, @f = newa, newb, newc, newd, newe, newf
|
||||
end
|
||||
|
||||
# A general solution for multiplying two matrices when we know all values
|
||||
# in the final column are fixed. This is the fallback method for when none
|
||||
# of the optimised methods are applicable.
|
||||
#
|
||||
# Like this:
|
||||
#
|
||||
# [ a b 0 ] [ a b 0 ]
|
||||
# [ c d 0 ] x [ c d 0 ]
|
||||
# [ e f 1 ] [ e f 1 ]
|
||||
#
|
||||
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
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user