# -*- encoding: utf-8; frozen_string_literal: true -*- # #-- # This file is part of HexaPDF. # # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby # Copyright (C) 2014-2023 Thomas Leitner # # HexaPDF is free software: you can redistribute it and/or modify it # under the terms of the GNU Affero General Public License version 3 as # published by the Free Software Foundation with the addition of the # following permission added to Section 15 as permitted in Section 7(a): # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON # INFRINGEMENT OF THIRD PARTY RIGHTS. # # HexaPDF is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public # License for more details. # # You should have received a copy of the GNU Affero General Public License # along with HexaPDF. If not, see . # # The interactive user interfaces in modified source and object code # versions of HexaPDF must display Appropriate Legal Notices, as required # under Section 5 of the GNU Affero General Public License version 3. # # In accordance with Section 7(b) of the GNU Affero General Public # License, a covered work must retain the producer line in every PDF that # is created or manipulated using HexaPDF. # # If the GNU Affero General Public License doesn't fit your need, # commercial licenses are available at . #++ require 'hexapdf/utils/math_helpers' module HexaPDF module Content # A TransformationMatrix is a matrix used in PDF graphics operations to specify the # relationship between different coordinate systems. # # All matrix operations modify the matrix in place. So if the original matrix should be # preserved, duplicate it before the operation. # # It is important to note that the matrix transforms from the new coordinate system to the # untransformed coordinate system. This means that after the transformation all coordinates # are specified in the new, transformed coordinate system and to get the untransformed # coordinates the matrix needs to be applied. # # Although all operations are done in 2D space the transformation matrix is a 3x3 matrix # because homogeneous coordinates are used. This, however, also means that only six entries # are actually used that are named like in the following graphic: # # a b 0 # c d 0 # e f 1 # # Here is a simple transformation matrix to translate all coordinates by 5 units horizontally # and 10 units vertically: # # 1 0 0 # 0 1 0 # 5 10 1 # # Details and some examples can be found in the PDF reference. # # See: PDF1.7 s8.3 class TransformationMatrix include HexaPDF::Utils::MathHelpers # The value at the position (1,1) in the matrix. attr_reader :a # The value at the position (1,2) in the matrix. attr_reader :b # The value at the position (2,1) in the matrix. attr_reader :c # The value at the position (2,2) in the matrix. attr_reader :d # The value at the position (3,1) in the matrix. attr_reader :e # The value at the position (3,2) in the matrix. attr_reader :f # Initializes the transformation matrix with the given values. def initialize(a = 1, b = 0, c = 0, d = 1, e = 0, f = 0) @a = a @b = b @c = c @d = d @e = e @f = f end # Returns the untransformed coordinates of the given point. def evaluate(x, y) [@a * x + @c * y + @e, @b * x + @d * y + @f] end # Translates this matrix by +x+ units horizontally and +y+ units vertically and returns it. # # This is equal to premultiply(1, 0, 0, 1, x, y). def translate(x, y) @e = x * @a + y * @c + @e @f = x * @b + y * @d + @f self end # Scales this matrix by +sx+ units horizontally and +y+ units vertically and returns it. # # This is equal to premultiply(sx, 0, 0, sy, 0, 0). def scale(sx, sy) @a = sx * @a @b = sx * @b @c = sy * @c @d = sy * @d self end # Rotates this matrix by an angle of +q+ degrees and returns it. # # This equal to premultiply(cos(rad(q)), sin(rad(q)), -sin(rad(q)), cos(rad(q)), x, y). def rotate(q) cq = Math.cos(deg_to_rad(q)) sq = Math.sin(deg_to_rad(q)) premultiply(cq, sq, -sq, cq, 0, 0) end # Skews this matrix by an angle of +a+ degrees for the x axis and by an angle of +b+ degrees # for the y axis and returns it. # # This is equal to premultiply(1, tan(rad(a)), tan(rad(b)), 1, x, y). def skew(a, b) premultiply(1, Math.tan(deg_to_rad(a)), Math.tan(deg_to_rad(b)), 1, 0, 0) end # Transforms this matrix by premultiplying it with the given one (ie. given*this) and # returns it. def premultiply(a, b, c, d, e, f) a1 = a * @a + b * @c b1 = a * @b + b * @d c1 = c * @a + d * @c d1 = c * @b + d * @d @e = e * @a + f * @c + @e @f = e * @b + f * @d + @f @a = a1 @b = b1 @c = c1 @d = d1 self end # Returns +true+ if the other object is a transformation matrix with the same values. def ==(other) (other.kind_of?(self.class) && @a == other.a && @b == other.b && @c == other.c && @d == other.d && @e == other.e && @f == other.f) end # Creates an array [a, b, c, d, e, f] from the transformation matrix. def to_a [@a, @b, @c, @d, @e, @f] end end end end