Moved Matrix code to model/matrix.go

This commit is contained in:
Peter Williams 2018-11-28 22:29:35 +11:00
parent ad83b1c948
commit da8544e68b
4 changed files with 172 additions and 161 deletions

View File

@ -7,8 +7,6 @@ package contentstream
import (
"errors"
"fmt"
"math"
"github.com/unidoc/unidoc/common"
"github.com/unidoc/unidoc/pdf/core"
@ -22,7 +20,7 @@ type GraphicsState struct {
ColorspaceNonStroking model.PdfColorspace
ColorStroking model.PdfColor
ColorNonStroking model.PdfColor
CTM Matrix
CTM model.Matrix
}
type GraphicStateStack []GraphicsState
@ -208,7 +206,7 @@ func (proc *ContentStreamProcessor) Process(resources *model.PdfPageResources) e
proc.graphicsState.ColorspaceNonStroking = model.NewPdfColorspaceDeviceGray()
proc.graphicsState.ColorStroking = model.NewPdfColorDeviceGray(0)
proc.graphicsState.ColorNonStroking = model.NewPdfColorDeviceGray(0)
proc.graphicsState.CTM = IdentityMatrix()
proc.graphicsState.CTM = model.IdentityMatrix()
for _, op := range proc.operations {
var err error
@ -568,149 +566,8 @@ func (proc *ContentStreamProcessor) handleCommand_cm(op *ContentStreamOperation,
if err != nil {
return err
}
m := NewMatrix(f[0], f[1], f[2], f[3], f[4], f[5])
m := model.NewMatrix(f[0], f[1], f[2], f[3], f[4], f[5])
proc.graphicsState.CTM.Concat(m)
return nil
}
// 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]
}
// Translation returns the translation part of `m`.
func (m *Matrix) ScalingX() float64 {
return math.Hypot(m[0], m[1])
}
// 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 X scaling of the affine transform.
func (m *Matrix) ScalingFactorX() float64 {
return math.Sqrt(m[0]*m[0] + m[1]*m[1])
}
// ScalingFactorY returns X scaling of the affine transform.
func (m *Matrix) ScalingFactorY() float64 {
return math.Sqrt(m[3]*m[3] + m[4]*m[4])
}
// Angle returns the angle of the affine transform.
// For simplicity, we assume the transform is a multiple of 90 degrees.
func (m *Matrix) Angle() int {
a, b, c, d := m[0], m[1], m[3], m[4]
// We are returning θ for
// a b cos θ -sin θ
// c d = sin θ cos θ
if a > 0 && d > 0 {
// 1 0
// 0 1
return 0
} else if b < 0 && c > 0 {
// 0 1
// -1 0
return 90
} else if a < 0 && d < 0 {
// -1 0
// 0 -1
return 180
} else if b > 0 && c < 0 {
// 0 -1
// 1 0
return 270
}
common.Log.Debug("ERROR: Angle not a mulitple of 90°. m=%s", m)
return 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 {
m[i] = maxAbsNumber
} else if x < -maxAbsNumber {
m[i] = -maxAbsNumber
}
}
}
// largest numbers needed in PDF transforms. Is this correct?
const maxAbsNumber = 1e9

View File

@ -13,7 +13,7 @@ import (
"fmt"
"github.com/unidoc/unidoc/common"
"github.com/unidoc/unidoc/pdf/contentstream"
"github.com/unidoc/unidoc/pdf/model"
)
// Point defines a point in Cartesian coordinates
@ -34,7 +34,7 @@ func (p *Point) Set(x, y float64) {
// Transform transforms `p` by the affine transformation a, b, c, d, tx, ty.
func (p *Point) Transform(a, b, c, d, tx, ty float64) {
m := contentstream.NewMatrix(a, b, c, d, tx, ty)
m := model.NewMatrix(a, b, c, d, tx, ty)
p.transformByMatrix(m)
}
@ -61,7 +61,7 @@ func (p Point) Rotate(theta int) Point {
}
// transformByMatrix transforms `p` by the affine transformation `m`.
func (p *Point) transformByMatrix(m contentstream.Matrix) {
func (p *Point) transformByMatrix(m model.Matrix) {
p.X, p.Y = m.Transform(p.X, p.Y)
}

View File

@ -314,7 +314,7 @@ func (to *textObject) nextLine() {
// in `f` (page 250).
func (to *textObject) setTextMatrix(f []float64) {
a, b, c, d, tx, ty := f[0], f[1], f[2], f[3], f[4], f[5]
to.Tm = contentstream.NewMatrix(a, b, c, d, tx, ty)
to.Tm = model.NewMatrix(a, b, c, d, tx, ty)
to.Tlm = to.Tm
}
@ -570,9 +570,9 @@ type textObject struct {
gs contentstream.GraphicsState
fontStack *fontStacker
State *textState
Tm contentstream.Matrix // Text matrix. For the character pointer.
Tlm contentstream.Matrix // Text line matrix. For the start of line pointer.
Texts []XYText // Text gets written here.
Tm model.Matrix // Text matrix. For the character pointer.
Tlm model.Matrix // Text line matrix. For the start of line pointer.
Texts []XYText // Text gets written here.
}
// newTextState returns a default textState.
@ -591,8 +591,8 @@ func newTextObject(e *Extractor, gs contentstream.GraphicsState, state *textStat
gs: gs,
fontStack: fontStack,
State: state,
Tm: contentstream.IdentityMatrix(),
Tlm: contentstream.IdentityMatrix(),
Tm: model.IdentityMatrix(),
Tlm: model.IdentityMatrix(),
}
}
@ -620,7 +620,7 @@ func (to *textObject) renderText(data []byte) error {
spaceWidth := spaceMetrics.Wx * glyphTextRatio
common.Log.Trace("spaceWidth=%.2f text=%q font=%s fontSize=%.1f", spaceWidth, runes, font, tfs)
stateMatrix := contentstream.NewMatrix(
stateMatrix := model.NewMatrix(
tfs*th, 0,
0, tfs,
0, state.Trise)
@ -692,14 +692,14 @@ func (to *textObject) renderText(data []byte) error {
const glyphTextRatio = 1.0 / 1000.0
// translation returns the translation part of `m`.
func translation(m contentstream.Matrix) Point {
func translation(m model.Matrix) Point {
tx, ty := m.Translation()
return Point{tx, ty}
}
// translationMatrix returns a matrix that translates by `p`.
func translationMatrix(p Point) contentstream.Matrix {
return contentstream.TranslationMatrix(p.X, p.Y)
func translationMatrix(p Point) model.Matrix {
return model.TranslationMatrix(p.X, p.Y)
}
// moveTo moves the start of line pointer by `tx`,`ty` and sets the text pointer to the
@ -707,7 +707,7 @@ func translationMatrix(p Point) contentstream.Matrix {
// Move to the start of the next line, offset from the start of the current line by (tx, ty).
// `tx` and `ty` are in unscaled text space units.
func (to *textObject) moveTo(tx, ty float64) {
to.Tlm = contentstream.NewMatrix(1, 0, 0, 1, tx, ty).Mult(to.Tlm)
to.Tlm = model.NewMatrix(1, 0, 0, 1, tx, ty).Mult(to.Tlm)
to.Tm = to.Tlm
}
@ -726,7 +726,7 @@ type XYText struct {
// newXYText returns an XYText for text `text` rendered with text rendering matrix `trm` and end
// of character device coordinates `end`. `spaceWidth` is our best guess at the width of a space in
// the font the text is rendered in device coordinates.
func (to *textObject) newXYText(text string, trm contentstream.Matrix, end Point,
func (to *textObject) newXYText(text string, trm model.Matrix, end Point,
height, spaceWidth float64) XYText {
to.e.textCount++
theta := trm.Angle()

154
pdf/model/matrix.go Normal file
View File

@ -0,0 +1,154 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package model
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]
}
// Translation returns the translation part of `m`.
func (m *Matrix) ScalingX() float64 {
return math.Hypot(m[0], m[1])
}
// 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 X scaling of the affine transform.
func (m *Matrix) ScalingFactorX() float64 {
return math.Sqrt(m[0]*m[0] + m[1]*m[1])
}
// ScalingFactorY returns X scaling of the affine transform.
func (m *Matrix) ScalingFactorY() float64 {
return math.Sqrt(m[3]*m[3] + m[4]*m[4])
}
// Angle returns the angle of the affine transform.
// For simplicity, we assume the transform is a multiple of 90 degrees.
func (m *Matrix) Angle() int {
a, b, c, d := m[0], m[1], m[3], m[4]
// We are returning θ for
// a b cos θ -sin θ
// c d = sin θ cos θ
if a > 0 && d > 0 {
// 1 0
// 0 1
return 0
} else if b < 0 && c > 0 {
// 0 1
// -1 0
return 90
} else if a < 0 && d < 0 {
// -1 0
// 0 -1
return 180
} else if b > 0 && c < 0 {
// 0 -1
// 1 0
return 270
}
common.Log.Debug("ERROR: Angle not a mulitple of 90°. m=%s", m)
return 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 {
m[i] = maxAbsNumber
} else if x < -maxAbsNumber {
m[i] = -maxAbsNumber
}
}
}
// largest numbers needed in PDF transforms. Is this correct?
const maxAbsNumber = 1e9