fix wrong git ignore
This commit is contained in:
149
internal/pdf/contentstream/draw/bezier_curve.go
Normal file
149
internal/pdf/contentstream/draw/bezier_curve.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package draw
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"gitea.tecamino.com/paadi/pdfmerge/internal/pdf/model"
|
||||
)
|
||||
|
||||
// Cubic bezier curves are defined by:
|
||||
// R(t) = P0*(1-t)^3 + P1*3*t*(1-t)^2 + P2*3*t^2*(1-t) + P3*t^3
|
||||
// where P0 is the current point, P1, P2 control points and P3 the final point.
|
||||
type CubicBezierCurve struct {
|
||||
P0 Point // Starting point.
|
||||
P1 Point // Control point 1.
|
||||
P2 Point // Control point 2.
|
||||
P3 Point // Final point.
|
||||
}
|
||||
|
||||
func NewCubicBezierCurve(x0, y0, x1, y1, x2, y2, x3, y3 float64) CubicBezierCurve {
|
||||
curve := CubicBezierCurve{}
|
||||
curve.P0 = NewPoint(x0, y0)
|
||||
curve.P1 = NewPoint(x1, y1)
|
||||
curve.P2 = NewPoint(x2, y2)
|
||||
curve.P3 = NewPoint(x3, y3)
|
||||
return curve
|
||||
}
|
||||
|
||||
// Add X,Y offset to all points on a curve.
|
||||
func (curve CubicBezierCurve) AddOffsetXY(offX, offY float64) CubicBezierCurve {
|
||||
curve.P0.X += offX
|
||||
curve.P1.X += offX
|
||||
curve.P2.X += offX
|
||||
curve.P3.X += offX
|
||||
|
||||
curve.P0.Y += offY
|
||||
curve.P1.Y += offY
|
||||
curve.P2.Y += offY
|
||||
curve.P3.Y += offY
|
||||
|
||||
return curve
|
||||
}
|
||||
|
||||
func (curve CubicBezierCurve) GetBounds() model.PdfRectangle {
|
||||
minX := curve.P0.X
|
||||
maxX := curve.P0.X
|
||||
minY := curve.P0.Y
|
||||
maxY := curve.P0.Y
|
||||
|
||||
// 1000 points.
|
||||
for t := 0.0; t <= 1.0; t += 0.001 {
|
||||
Rx := curve.P0.X*math.Pow(1-t, 3) +
|
||||
curve.P1.X*3*t*math.Pow(1-t, 2) +
|
||||
curve.P2.X*3*math.Pow(t, 2)*(1-t) +
|
||||
curve.P3.X*math.Pow(t, 3)
|
||||
Ry := curve.P0.Y*math.Pow(1-t, 3) +
|
||||
curve.P1.Y*3*t*math.Pow(1-t, 2) +
|
||||
curve.P2.Y*3*math.Pow(t, 2)*(1-t) +
|
||||
curve.P3.Y*math.Pow(t, 3)
|
||||
|
||||
if Rx < minX {
|
||||
minX = Rx
|
||||
}
|
||||
if Rx > maxX {
|
||||
maxX = Rx
|
||||
}
|
||||
if Ry < minY {
|
||||
minY = Ry
|
||||
}
|
||||
if Ry > maxY {
|
||||
maxY = Ry
|
||||
}
|
||||
}
|
||||
|
||||
bounds := model.PdfRectangle{}
|
||||
bounds.Llx = minX
|
||||
bounds.Lly = minY
|
||||
bounds.Urx = maxX
|
||||
bounds.Ury = maxY
|
||||
return bounds
|
||||
}
|
||||
|
||||
type CubicBezierPath struct {
|
||||
Curves []CubicBezierCurve
|
||||
}
|
||||
|
||||
func NewCubicBezierPath() CubicBezierPath {
|
||||
bpath := CubicBezierPath{}
|
||||
bpath.Curves = []CubicBezierCurve{}
|
||||
return bpath
|
||||
}
|
||||
|
||||
func (this CubicBezierPath) AppendCurve(curve CubicBezierCurve) CubicBezierPath {
|
||||
this.Curves = append(this.Curves, curve)
|
||||
return this
|
||||
}
|
||||
|
||||
func (bpath CubicBezierPath) Copy() CubicBezierPath {
|
||||
bpathcopy := CubicBezierPath{}
|
||||
bpathcopy.Curves = []CubicBezierCurve{}
|
||||
for _, c := range bpath.Curves {
|
||||
bpathcopy.Curves = append(bpathcopy.Curves, c)
|
||||
}
|
||||
return bpathcopy
|
||||
}
|
||||
|
||||
func (bpath CubicBezierPath) Offset(offX, offY float64) CubicBezierPath {
|
||||
for i, c := range bpath.Curves {
|
||||
bpath.Curves[i] = c.AddOffsetXY(offX, offY)
|
||||
}
|
||||
return bpath
|
||||
}
|
||||
|
||||
func (bpath CubicBezierPath) GetBoundingBox() Rectangle {
|
||||
bbox := Rectangle{}
|
||||
|
||||
minX := 0.0
|
||||
maxX := 0.0
|
||||
minY := 0.0
|
||||
maxY := 0.0
|
||||
for idx, c := range bpath.Curves {
|
||||
curveBounds := c.GetBounds()
|
||||
if idx == 0 {
|
||||
minX = curveBounds.Llx
|
||||
maxX = curveBounds.Urx
|
||||
minY = curveBounds.Lly
|
||||
maxY = curveBounds.Ury
|
||||
continue
|
||||
}
|
||||
|
||||
if curveBounds.Llx < minX {
|
||||
minX = curveBounds.Llx
|
||||
}
|
||||
if curveBounds.Urx > maxX {
|
||||
maxX = curveBounds.Urx
|
||||
}
|
||||
if curveBounds.Lly < minY {
|
||||
minY = curveBounds.Lly
|
||||
}
|
||||
if curveBounds.Ury > maxY {
|
||||
maxY = curveBounds.Ury
|
||||
}
|
||||
}
|
||||
|
||||
bbox.X = minX
|
||||
bbox.Y = minY
|
||||
bbox.Width = maxX - minX
|
||||
bbox.Height = maxY - minY
|
||||
return bbox
|
||||
}
|
||||
98
internal/pdf/contentstream/draw/path.go
Normal file
98
internal/pdf/contentstream/draw/path.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package draw
|
||||
|
||||
// A path consists of straight line connections between each point defined in an array of points.
|
||||
type Path struct {
|
||||
Points []Point
|
||||
}
|
||||
|
||||
func NewPath() Path {
|
||||
path := Path{}
|
||||
path.Points = []Point{}
|
||||
return path
|
||||
}
|
||||
|
||||
func (this Path) AppendPoint(point Point) Path {
|
||||
this.Points = append(this.Points, point)
|
||||
return this
|
||||
}
|
||||
|
||||
func (this Path) RemovePoint(number int) Path {
|
||||
if number < 1 || number > len(this.Points) {
|
||||
return this
|
||||
}
|
||||
|
||||
idx := number - 1
|
||||
this.Points = append(this.Points[:idx], this.Points[idx+1:]...)
|
||||
return this
|
||||
}
|
||||
|
||||
func (this Path) Length() int {
|
||||
return len(this.Points)
|
||||
}
|
||||
|
||||
func (this Path) GetPointNumber(number int) Point {
|
||||
if number < 1 || number > len(this.Points) {
|
||||
return Point{}
|
||||
}
|
||||
return this.Points[number-1]
|
||||
}
|
||||
|
||||
func (path Path) Copy() Path {
|
||||
pathcopy := Path{}
|
||||
pathcopy.Points = []Point{}
|
||||
for _, p := range path.Points {
|
||||
pathcopy.Points = append(pathcopy.Points, p)
|
||||
}
|
||||
return pathcopy
|
||||
}
|
||||
|
||||
func (path Path) Offset(offX, offY float64) Path {
|
||||
for i, p := range path.Points {
|
||||
path.Points[i] = p.Add(offX, offY)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func (path Path) GetBoundingBox() BoundingBox {
|
||||
bbox := BoundingBox{}
|
||||
|
||||
minX := 0.0
|
||||
maxX := 0.0
|
||||
minY := 0.0
|
||||
maxY := 0.0
|
||||
for idx, p := range path.Points {
|
||||
if idx == 0 {
|
||||
minX = p.X
|
||||
maxX = p.X
|
||||
minY = p.Y
|
||||
maxY = p.Y
|
||||
continue
|
||||
}
|
||||
|
||||
if p.X < minX {
|
||||
minX = p.X
|
||||
}
|
||||
if p.X > maxX {
|
||||
maxX = p.X
|
||||
}
|
||||
if p.Y < minY {
|
||||
minY = p.Y
|
||||
}
|
||||
if p.Y > maxY {
|
||||
maxY = p.Y
|
||||
}
|
||||
}
|
||||
|
||||
bbox.X = minX
|
||||
bbox.Y = minY
|
||||
bbox.Width = maxX - minX
|
||||
bbox.Height = maxY - minY
|
||||
return bbox
|
||||
}
|
||||
|
||||
type BoundingBox struct {
|
||||
X float64
|
||||
Y float64
|
||||
Width float64
|
||||
Height float64
|
||||
}
|
||||
32
internal/pdf/contentstream/draw/point.go
Normal file
32
internal/pdf/contentstream/draw/point.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package draw
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Point struct {
|
||||
X float64
|
||||
Y float64
|
||||
}
|
||||
|
||||
func NewPoint(x, y float64) Point {
|
||||
point := Point{}
|
||||
point.X = x
|
||||
point.Y = y
|
||||
return point
|
||||
}
|
||||
|
||||
func (p Point) Add(dx, dy float64) Point {
|
||||
p.X += dx
|
||||
p.Y += dy
|
||||
return p
|
||||
}
|
||||
|
||||
// Add vector to a point.
|
||||
func (this Point) AddVector(v Vector) Point {
|
||||
this.X += v.Dx
|
||||
this.Y += v.Dy
|
||||
return this
|
||||
}
|
||||
|
||||
func (p Point) String() string {
|
||||
return fmt.Sprintf("(%.1f,%.1f)", p.X, p.Y)
|
||||
}
|
||||
353
internal/pdf/contentstream/draw/shapes.go
Normal file
353
internal/pdf/contentstream/draw/shapes.go
Normal file
@@ -0,0 +1,353 @@
|
||||
package draw
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
pdfcontent "gitea.tecamino.com/paadi/pdfmerge/internal/pdf/contentstream"
|
||||
pdfcore "gitea.tecamino.com/paadi/pdfmerge/internal/pdf/core"
|
||||
pdf "gitea.tecamino.com/paadi/pdfmerge/internal/pdf/model"
|
||||
)
|
||||
|
||||
type Circle struct {
|
||||
X float64
|
||||
Y float64
|
||||
Width float64
|
||||
Height float64
|
||||
FillEnabled bool // Show fill?
|
||||
FillColor *pdf.PdfColorDeviceRGB
|
||||
BorderEnabled bool // Show border?
|
||||
BorderWidth float64
|
||||
BorderColor *pdf.PdfColorDeviceRGB
|
||||
Opacity float64 // Alpha value (0-1).
|
||||
}
|
||||
|
||||
// Draw a circle. Can specify a graphics state (gsName) for setting opacity etc. Otherwise leave empty ("").
|
||||
// Returns the content stream as a byte array, the bounding box and an error on failure.
|
||||
func (c Circle) Draw(gsName string) ([]byte, *pdf.PdfRectangle, error) {
|
||||
xRad := c.Width / 2
|
||||
yRad := c.Height / 2
|
||||
if c.BorderEnabled {
|
||||
xRad -= c.BorderWidth / 2
|
||||
yRad -= c.BorderWidth / 2
|
||||
}
|
||||
|
||||
magic := 0.551784
|
||||
xMagic := xRad * magic
|
||||
yMagic := yRad * magic
|
||||
|
||||
bpath := NewCubicBezierPath()
|
||||
bpath = bpath.AppendCurve(NewCubicBezierCurve(-xRad, 0, -xRad, yMagic, -xMagic, yRad, 0, yRad))
|
||||
bpath = bpath.AppendCurve(NewCubicBezierCurve(0, yRad, xMagic, yRad, xRad, yMagic, xRad, 0))
|
||||
bpath = bpath.AppendCurve(NewCubicBezierCurve(xRad, 0, xRad, -yMagic, xMagic, -yRad, 0, -yRad))
|
||||
bpath = bpath.AppendCurve(NewCubicBezierCurve(0, -yRad, -xMagic, -yRad, -xRad, -yMagic, -xRad, 0))
|
||||
bpath = bpath.Offset(xRad, yRad)
|
||||
if c.BorderEnabled {
|
||||
bpath = bpath.Offset(c.BorderWidth/2, c.BorderWidth/2)
|
||||
}
|
||||
if c.X != 0 || c.Y != 0 {
|
||||
bpath = bpath.Offset(c.X, c.Y)
|
||||
}
|
||||
|
||||
creator := pdfcontent.NewContentCreator()
|
||||
|
||||
creator.Add_q()
|
||||
|
||||
if c.FillEnabled {
|
||||
creator.Add_rg(c.FillColor.R(), c.FillColor.G(), c.FillColor.B())
|
||||
}
|
||||
if c.BorderEnabled {
|
||||
creator.Add_RG(c.BorderColor.R(), c.BorderColor.G(), c.BorderColor.B())
|
||||
creator.Add_w(c.BorderWidth)
|
||||
}
|
||||
if len(gsName) > 1 {
|
||||
// If a graphics state is provided, use it. (Used for transparency settings here).
|
||||
creator.Add_gs(pdfcore.PdfObjectName(gsName))
|
||||
}
|
||||
|
||||
DrawBezierPathWithCreator(bpath, creator)
|
||||
creator.Add_h() // Close the path.
|
||||
|
||||
if c.FillEnabled && c.BorderEnabled {
|
||||
creator.Add_B() // fill and stroke.
|
||||
} else if c.FillEnabled {
|
||||
creator.Add_f() // Fill.
|
||||
} else if c.BorderEnabled {
|
||||
creator.Add_S() // Stroke.
|
||||
}
|
||||
creator.Add_Q()
|
||||
|
||||
// Get bounding box.
|
||||
pathBbox := bpath.GetBoundingBox()
|
||||
if c.BorderEnabled {
|
||||
// Account for stroke width.
|
||||
pathBbox.Height += c.BorderWidth
|
||||
pathBbox.Width += c.BorderWidth
|
||||
pathBbox.X -= c.BorderWidth / 2
|
||||
pathBbox.Y -= c.BorderWidth / 2
|
||||
}
|
||||
|
||||
// Bounding box - global coordinate system.
|
||||
bbox := &pdf.PdfRectangle{}
|
||||
bbox.Llx = pathBbox.X
|
||||
bbox.Lly = pathBbox.Y
|
||||
bbox.Urx = pathBbox.X + pathBbox.Width
|
||||
bbox.Ury = pathBbox.Y + pathBbox.Height
|
||||
|
||||
return creator.Bytes(), bbox, nil
|
||||
}
|
||||
|
||||
// A rectangle defined with a specified Width and Height and a lower left corner at (X,Y). The rectangle can
|
||||
// optionally have a border and a filling color.
|
||||
// The Width/Height includes the border (if any specified), i.e. is positioned inside.
|
||||
type Rectangle struct {
|
||||
X float64
|
||||
Y float64
|
||||
Width float64
|
||||
Height float64
|
||||
FillEnabled bool // Show fill?
|
||||
FillColor *pdf.PdfColorDeviceRGB
|
||||
BorderEnabled bool // Show border?
|
||||
BorderWidth float64
|
||||
BorderColor *pdf.PdfColorDeviceRGB
|
||||
Opacity float64 // Alpha value (0-1).
|
||||
}
|
||||
|
||||
// Draw the circle. Can specify a graphics state (gsName) for setting opacity etc. Otherwise leave empty ("").
|
||||
// Returns the content stream as a byte array, bounding box and an error on failure.
|
||||
func (rect Rectangle) Draw(gsName string) ([]byte, *pdf.PdfRectangle, error) {
|
||||
path := NewPath()
|
||||
|
||||
path = path.AppendPoint(NewPoint(0, 0))
|
||||
path = path.AppendPoint(NewPoint(0, rect.Height))
|
||||
path = path.AppendPoint(NewPoint(rect.Width, rect.Height))
|
||||
path = path.AppendPoint(NewPoint(rect.Width, 0))
|
||||
path = path.AppendPoint(NewPoint(0, 0))
|
||||
|
||||
if rect.X != 0 || rect.Y != 0 {
|
||||
path = path.Offset(rect.X, rect.Y)
|
||||
}
|
||||
|
||||
creator := pdfcontent.NewContentCreator()
|
||||
|
||||
creator.Add_q()
|
||||
if rect.FillEnabled {
|
||||
creator.Add_rg(rect.FillColor.R(), rect.FillColor.G(), rect.FillColor.B())
|
||||
}
|
||||
if rect.BorderEnabled {
|
||||
creator.Add_RG(rect.BorderColor.R(), rect.BorderColor.G(), rect.BorderColor.B())
|
||||
creator.Add_w(rect.BorderWidth)
|
||||
}
|
||||
if len(gsName) > 1 {
|
||||
// If a graphics state is provided, use it. (Used for transparency settings here).
|
||||
creator.Add_gs(pdfcore.PdfObjectName(gsName))
|
||||
}
|
||||
DrawPathWithCreator(path, creator)
|
||||
creator.Add_h() // Close the path.
|
||||
|
||||
if rect.FillEnabled && rect.BorderEnabled {
|
||||
creator.Add_B() // fill and stroke.
|
||||
} else if rect.FillEnabled {
|
||||
creator.Add_f() // Fill.
|
||||
} else if rect.BorderEnabled {
|
||||
creator.Add_S() // Stroke.
|
||||
}
|
||||
creator.Add_Q()
|
||||
|
||||
// Get bounding box.
|
||||
pathBbox := path.GetBoundingBox()
|
||||
|
||||
// Bounding box - global coordinate system.
|
||||
bbox := &pdf.PdfRectangle{}
|
||||
bbox.Llx = pathBbox.X
|
||||
bbox.Lly = pathBbox.Y
|
||||
bbox.Urx = pathBbox.X + pathBbox.Width
|
||||
bbox.Ury = pathBbox.Y + pathBbox.Height
|
||||
|
||||
return creator.Bytes(), bbox, nil
|
||||
}
|
||||
|
||||
// The currently supported line ending styles are None, Arrow (ClosedArrow) and Butt.
|
||||
type LineEndingStyle int
|
||||
|
||||
const (
|
||||
LineEndingStyleNone LineEndingStyle = 0
|
||||
LineEndingStyleArrow LineEndingStyle = 1
|
||||
LineEndingStyleButt LineEndingStyle = 2
|
||||
)
|
||||
|
||||
// Defines a line between point 1 (X1,Y1) and point 2 (X2,Y2). The line ending styles can be none (regular line),
|
||||
// or arrows at either end. The line also has a specified width, color and opacity.
|
||||
type Line struct {
|
||||
X1 float64
|
||||
Y1 float64
|
||||
X2 float64
|
||||
Y2 float64
|
||||
LineColor *pdf.PdfColorDeviceRGB
|
||||
Opacity float64 // Alpha value (0-1).
|
||||
LineWidth float64
|
||||
LineEndingStyle1 LineEndingStyle // Line ending style of point 1.
|
||||
LineEndingStyle2 LineEndingStyle // Line ending style of point 2.
|
||||
}
|
||||
|
||||
// Draw a line in PDF. Generates the content stream which can be used in page contents or appearance stream of annotation.
|
||||
// Returns the stream content, XForm bounding box (local), bounding box and an error if one occurred.
|
||||
func (line Line) Draw(gsName string) ([]byte, *pdf.PdfRectangle, error) {
|
||||
x1, x2 := line.X1, line.X2
|
||||
y1, y2 := line.Y1, line.Y2
|
||||
|
||||
dy := y2 - y1
|
||||
dx := x2 - x1
|
||||
theta := math.Atan2(dy, dx)
|
||||
|
||||
L := math.Sqrt(math.Pow(dx, 2.0) + math.Pow(dy, 2.0))
|
||||
w := line.LineWidth
|
||||
|
||||
pi := math.Pi
|
||||
|
||||
mul := 1.0
|
||||
if dx < 0 {
|
||||
mul *= -1.0
|
||||
}
|
||||
if dy < 0 {
|
||||
mul *= -1.0
|
||||
}
|
||||
|
||||
// Vs.
|
||||
VsX := mul * (-w / 2 * math.Cos(theta+pi/2))
|
||||
VsY := mul * (-w/2*math.Sin(theta+pi/2) + w*math.Sin(theta+pi/2))
|
||||
|
||||
// V1.
|
||||
V1X := VsX + w/2*math.Cos(theta+pi/2)
|
||||
V1Y := VsY + w/2*math.Sin(theta+pi/2)
|
||||
|
||||
// P2.
|
||||
V2X := VsX + w/2*math.Cos(theta+pi/2) + L*math.Cos(theta)
|
||||
V2Y := VsY + w/2*math.Sin(theta+pi/2) + L*math.Sin(theta)
|
||||
|
||||
// P3.
|
||||
V3X := VsX + w/2*math.Cos(theta+pi/2) + L*math.Cos(theta) + w*math.Cos(theta-pi/2)
|
||||
V3Y := VsY + w/2*math.Sin(theta+pi/2) + L*math.Sin(theta) + w*math.Sin(theta-pi/2)
|
||||
|
||||
// P4.
|
||||
V4X := VsX + w/2*math.Cos(theta-pi/2)
|
||||
V4Y := VsY + w/2*math.Sin(theta-pi/2)
|
||||
|
||||
path := NewPath()
|
||||
path = path.AppendPoint(NewPoint(V1X, V1Y))
|
||||
path = path.AppendPoint(NewPoint(V2X, V2Y))
|
||||
path = path.AppendPoint(NewPoint(V3X, V3Y))
|
||||
path = path.AppendPoint(NewPoint(V4X, V4Y))
|
||||
|
||||
lineEnding1 := line.LineEndingStyle1
|
||||
lineEnding2 := line.LineEndingStyle2
|
||||
|
||||
// TODO: Allow custom height/widths.
|
||||
arrowHeight := 3 * w
|
||||
arrowWidth := 3 * w
|
||||
arrowExtruding := (arrowWidth - w) / 2
|
||||
|
||||
if lineEnding2 == LineEndingStyleArrow {
|
||||
// Convert P2, P3
|
||||
p2 := path.GetPointNumber(2)
|
||||
|
||||
va1 := NewVectorPolar(arrowHeight, theta+pi)
|
||||
pa1 := p2.AddVector(va1)
|
||||
|
||||
bVec := NewVectorPolar(arrowWidth/2, theta+pi/2)
|
||||
aVec := NewVectorPolar(arrowHeight, theta)
|
||||
|
||||
va2 := NewVectorPolar(arrowExtruding, theta+pi/2)
|
||||
pa2 := pa1.AddVector(va2)
|
||||
|
||||
va3 := aVec.Add(bVec.Flip())
|
||||
pa3 := pa2.AddVector(va3)
|
||||
|
||||
va4 := bVec.Scale(2).Flip().Add(va3.Flip())
|
||||
pa4 := pa3.AddVector(va4)
|
||||
|
||||
pa5 := pa1.AddVector(NewVectorPolar(w, theta-pi/2))
|
||||
|
||||
newpath := NewPath()
|
||||
newpath = newpath.AppendPoint(path.GetPointNumber(1))
|
||||
newpath = newpath.AppendPoint(pa1)
|
||||
newpath = newpath.AppendPoint(pa2)
|
||||
newpath = newpath.AppendPoint(pa3)
|
||||
newpath = newpath.AppendPoint(pa4)
|
||||
newpath = newpath.AppendPoint(pa5)
|
||||
newpath = newpath.AppendPoint(path.GetPointNumber(4))
|
||||
|
||||
path = newpath
|
||||
}
|
||||
if lineEnding1 == LineEndingStyleArrow {
|
||||
// Get the first and last points.
|
||||
p1 := path.GetPointNumber(1)
|
||||
pn := path.GetPointNumber(path.Length())
|
||||
|
||||
// First three points on arrow.
|
||||
v1 := NewVectorPolar(w/2, theta+pi+pi/2)
|
||||
pa1 := p1.AddVector(v1)
|
||||
|
||||
v2 := NewVectorPolar(arrowHeight, theta).Add(NewVectorPolar(arrowWidth/2, theta+pi/2))
|
||||
pa2 := pa1.AddVector(v2)
|
||||
|
||||
v3 := NewVectorPolar(arrowExtruding, theta-pi/2)
|
||||
pa3 := pa2.AddVector(v3)
|
||||
|
||||
// Last three points
|
||||
v5 := NewVectorPolar(arrowHeight, theta)
|
||||
pa5 := pn.AddVector(v5)
|
||||
|
||||
v6 := NewVectorPolar(arrowExtruding, theta+pi+pi/2)
|
||||
pa6 := pa5.AddVector(v6)
|
||||
|
||||
pa7 := pa1
|
||||
|
||||
newpath := NewPath()
|
||||
newpath = newpath.AppendPoint(pa1)
|
||||
newpath = newpath.AppendPoint(pa2)
|
||||
newpath = newpath.AppendPoint(pa3)
|
||||
for _, p := range path.Points[1 : len(path.Points)-1] {
|
||||
newpath = newpath.AppendPoint(p)
|
||||
}
|
||||
newpath = newpath.AppendPoint(pa5)
|
||||
newpath = newpath.AppendPoint(pa6)
|
||||
newpath = newpath.AppendPoint(pa7)
|
||||
|
||||
path = newpath
|
||||
}
|
||||
|
||||
creator := pdfcontent.NewContentCreator()
|
||||
|
||||
// Draw line with arrow
|
||||
creator.
|
||||
Add_q().
|
||||
Add_rg(line.LineColor.R(), line.LineColor.G(), line.LineColor.B())
|
||||
if len(gsName) > 1 {
|
||||
// If a graphics state is provided, use it. (Used for transparency settings here).
|
||||
creator.Add_gs(pdfcore.PdfObjectName(gsName))
|
||||
}
|
||||
|
||||
path = path.Offset(line.X1, line.Y1)
|
||||
|
||||
pathBbox := path.GetBoundingBox()
|
||||
|
||||
DrawPathWithCreator(path, creator)
|
||||
creator.Add_f().
|
||||
//creator.Add_S().
|
||||
Add_Q()
|
||||
|
||||
/*
|
||||
// Offsets (needed for placement of annotations bbox).
|
||||
offX := x1 - VsX
|
||||
offY := y1 - VsY
|
||||
*/
|
||||
|
||||
// Bounding box - global coordinate system.
|
||||
bbox := &pdf.PdfRectangle{}
|
||||
bbox.Llx = pathBbox.X
|
||||
bbox.Lly = pathBbox.Y
|
||||
bbox.Urx = pathBbox.X + pathBbox.Width
|
||||
bbox.Ury = pathBbox.Y + pathBbox.Height
|
||||
|
||||
return creator.Bytes(), bbox, nil
|
||||
}
|
||||
28
internal/pdf/contentstream/draw/utils.go
Normal file
28
internal/pdf/contentstream/draw/utils.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package draw
|
||||
|
||||
import (
|
||||
pdfcontent "gitea.tecamino.com/paadi/pdfmerge/internal/pdf/contentstream"
|
||||
)
|
||||
|
||||
// Make the path with the content creator.
|
||||
// Adds the PDF commands to draw the path to the creator instance.
|
||||
func DrawPathWithCreator(path Path, creator *pdfcontent.ContentCreator) {
|
||||
for idx, p := range path.Points {
|
||||
if idx == 0 {
|
||||
creator.Add_m(p.X, p.Y)
|
||||
} else {
|
||||
creator.Add_l(p.X, p.Y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make the bezier path with the content creator.
|
||||
// Adds the PDF commands to draw the path to the creator instance.
|
||||
func DrawBezierPathWithCreator(bpath CubicBezierPath, creator *pdfcontent.ContentCreator) {
|
||||
for idx, c := range bpath.Curves {
|
||||
if idx == 0 {
|
||||
creator.Add_m(c.P0.X, c.P0.Y)
|
||||
}
|
||||
creator.Add_c(c.P1.X, c.P1.Y, c.P2.X, c.P2.Y, c.P3.X, c.P3.Y)
|
||||
}
|
||||
}
|
||||
80
internal/pdf/contentstream/draw/vector.go
Normal file
80
internal/pdf/contentstream/draw/vector.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package draw
|
||||
|
||||
import "math"
|
||||
|
||||
type Vector struct {
|
||||
Dx float64
|
||||
Dy float64
|
||||
}
|
||||
|
||||
func NewVector(dx, dy float64) Vector {
|
||||
v := Vector{}
|
||||
v.Dx = dx
|
||||
v.Dy = dy
|
||||
return v
|
||||
}
|
||||
|
||||
func NewVectorBetween(a Point, b Point) Vector {
|
||||
v := Vector{}
|
||||
v.Dx = b.X - a.X
|
||||
v.Dy = b.Y - a.Y
|
||||
return v
|
||||
}
|
||||
|
||||
func NewVectorPolar(length float64, theta float64) Vector {
|
||||
v := Vector{}
|
||||
|
||||
v.Dx = length * math.Cos(theta)
|
||||
v.Dy = length * math.Sin(theta)
|
||||
return v
|
||||
}
|
||||
|
||||
func (v Vector) Add(other Vector) Vector {
|
||||
v.Dx += other.Dx
|
||||
v.Dy += other.Dy
|
||||
return v
|
||||
}
|
||||
|
||||
func (v Vector) Rotate(phi float64) Vector {
|
||||
mag := v.Magnitude()
|
||||
angle := v.GetPolarAngle()
|
||||
|
||||
return NewVectorPolar(mag, angle+phi)
|
||||
}
|
||||
|
||||
// Change the sign of the vector: -vector.
|
||||
func (this Vector) Flip() Vector {
|
||||
mag := this.Magnitude()
|
||||
theta := this.GetPolarAngle()
|
||||
|
||||
this.Dx = mag * math.Cos(theta+math.Pi)
|
||||
this.Dy = mag * math.Sin(theta+math.Pi)
|
||||
return this
|
||||
}
|
||||
|
||||
func (v Vector) FlipY() Vector {
|
||||
v.Dy = -v.Dy
|
||||
return v
|
||||
}
|
||||
|
||||
func (v Vector) FlipX() Vector {
|
||||
v.Dx = -v.Dx
|
||||
return v
|
||||
}
|
||||
|
||||
func (this Vector) Scale(factor float64) Vector {
|
||||
mag := this.Magnitude()
|
||||
theta := this.GetPolarAngle()
|
||||
|
||||
this.Dx = factor * mag * math.Cos(theta)
|
||||
this.Dy = factor * mag * math.Sin(theta)
|
||||
return this
|
||||
}
|
||||
|
||||
func (this Vector) Magnitude() float64 {
|
||||
return math.Sqrt(math.Pow(this.Dx, 2.0) + math.Pow(this.Dy, 2.0))
|
||||
}
|
||||
|
||||
func (this Vector) GetPolarAngle() float64 {
|
||||
return math.Atan2(this.Dy, this.Dx)
|
||||
}
|
||||
Reference in New Issue
Block a user