mirror of
https://github.com/unidoc/unipdf.git
synced 2025-05-01 22:17:29 +08:00
135 lines
3.6 KiB
Go
135 lines
3.6 KiB
Go
/*
|
||
* This file is subject to the terms and conditions defined in
|
||
* file 'LICENSE.md', which is part of this source code package.
|
||
*/
|
||
|
||
package contentstream
|
||
|
||
import (
|
||
"fmt"
|
||
"math"
|
||
|
||
"github.com/unidoc/unidoc/common"
|
||
)
|
||
|
||
// Matrix is a linear transform matrix in homogenous coordinates.
|
||
// PDF coordinate transforms are always affine so we only need 6 of these. See newMatrix.
|
||
type Matrix [9]float64
|
||
|
||
// IdentityMatrix returns the identity transform.
|
||
func IdentityMatrix() Matrix {
|
||
return NewMatrix(1, 0, 0, 1, 0, 0)
|
||
}
|
||
|
||
// TranslationMatrix returns a matrix that translates by `tx`, `ty`.
|
||
func TranslationMatrix(tx, ty float64) Matrix {
|
||
return NewMatrix(1, 0, 0, 1, tx, ty)
|
||
}
|
||
|
||
// NewMatrix returns an affine transform matrix laid out in homogenous coordinates as
|
||
// a b 0
|
||
// c d 0
|
||
// tx ty 1
|
||
func NewMatrix(a, b, c, d, tx, ty float64) Matrix {
|
||
m := Matrix{
|
||
a, b, 0,
|
||
c, d, 0,
|
||
tx, ty, 1,
|
||
}
|
||
m.fixup()
|
||
return m
|
||
}
|
||
|
||
// String returns a string describing `m`.
|
||
func (m Matrix) String() string {
|
||
a, b, c, d, tx, ty := m[0], m[1], m[3], m[4], m[6], m[7]
|
||
return fmt.Sprintf("[%.4f,%.4f,%.4f,%.4f:%.4f,%.4f]", a, b, c, d, tx, ty)
|
||
}
|
||
|
||
// Set sets `m` to affine transform a,b,c,d,tx,ty.
|
||
func (m *Matrix) Set(a, b, c, d, tx, ty float64) {
|
||
m[0], m[1] = a, b
|
||
m[3], m[4] = c, d
|
||
m[6], m[7] = tx, ty
|
||
m.fixup()
|
||
}
|
||
|
||
// Concat sets `m` to `m` × `b`.
|
||
// `b` needs to be created by newMatrix. i.e. It must be an affine transform.
|
||
// m00 m01 0 b00 b01 0 m00*b00 + m01*b01 m00*b10 + m01*b11 0
|
||
// m10 m11 0 × b10 b11 0 = m10*b00 + m11*b01 m10*b10 + m11*b11 0
|
||
// m20 m21 1 b20 b21 1 m20*b00 + m21*b10 + b20 m20*b01 + m21*b11 + b21 1
|
||
func (m *Matrix) Concat(b Matrix) {
|
||
*m = Matrix{
|
||
m[0]*b[0] + m[1]*b[3], m[0]*b[1] + m[1]*b[4], 0,
|
||
m[3]*b[0] + m[4]*b[3], m[3]*b[1] + m[4]*b[4], 0,
|
||
m[6]*b[0] + m[7]*b[3] + b[6], m[6]*b[1] + m[7]*b[4] + b[7], 1,
|
||
}
|
||
m.fixup()
|
||
}
|
||
|
||
// Mult returns `m` × `b`.
|
||
func (m Matrix) Mult(b Matrix) Matrix {
|
||
m.Concat(b)
|
||
return m
|
||
}
|
||
|
||
// Translate appends a translation of `dx`,`dy` to `m`.
|
||
// m.Translate(dx, dy) is equivalent to m.Concat(NewMatrix(1, 0, 0, 1, dx, dy))
|
||
func (m *Matrix) Translate(dx, dy float64) {
|
||
m[6] += dx
|
||
m[7] += dy
|
||
m.fixup()
|
||
}
|
||
|
||
// Translation returns the translation part of `m`.
|
||
func (m *Matrix) Translation() (float64, float64) {
|
||
return m[6], m[7]
|
||
}
|
||
|
||
// Transform returns coordinates `x`,`y` transformed by `m`.
|
||
func (m *Matrix) Transform(x, y float64) (float64, float64) {
|
||
xp := x*m[0] + y*m[1] + m[6]
|
||
yp := x*m[3] + y*m[4] + m[7]
|
||
return xp, yp
|
||
}
|
||
|
||
// ScalingFactorX returns the X scaling of the affine transform.
|
||
func (m *Matrix) ScalingFactorX() float64 {
|
||
return math.Hypot(m[0], m[1])
|
||
}
|
||
|
||
// ScalingFactorY returns the Y scaling of the affine transform.
|
||
func (m *Matrix) ScalingFactorY() float64 {
|
||
return math.Hypot(m[3], m[4])
|
||
}
|
||
|
||
// Angle returns the angle of the affine transform in `m` in degrees.
|
||
func (m *Matrix) Angle() float64 {
|
||
theta := math.Atan2(-m[1], m[0])
|
||
if theta < 0.0 {
|
||
theta += 2 * math.Pi
|
||
}
|
||
return theta / math.Pi * 180.0
|
||
|
||
}
|
||
|
||
// fixup forces `m` to have reasonable values. It is a guard against crazy values in corrupt PDF
|
||
// files.
|
||
// Currently it clamps elements to [-maxAbsNumber, -maxAbsNumber] to avoid floating point exceptions.
|
||
func (m *Matrix) fixup() {
|
||
for i, x := range m {
|
||
if x > maxAbsNumber {
|
||
common.Log.Debug("FIXUP: %d -> %d", x, maxAbsNumber)
|
||
m[i] = maxAbsNumber
|
||
} else if x < -maxAbsNumber {
|
||
common.Log.Debug("FIXUP: %d -> %d", x, -maxAbsNumber)
|
||
m[i] = -maxAbsNumber
|
||
}
|
||
}
|
||
}
|
||
|
||
// largest numbers needed in PDF transforms. Is this correct?
|
||
// TODO(gunnsth): Practical value? Need some reasoning.
|
||
const maxAbsNumber = 1e9
|