2017-04-09 16:49:15 +00:00
|
|
|
package annotator
|
|
|
|
|
|
|
|
import (
|
|
|
|
"math"
|
|
|
|
|
|
|
|
"github.com/unidoc/unidoc/common"
|
|
|
|
pdfcontent "github.com/unidoc/unidoc/pdf/contentstream"
|
|
|
|
"github.com/unidoc/unidoc/pdf/contentstream/draw"
|
|
|
|
pdfcore "github.com/unidoc/unidoc/pdf/core"
|
|
|
|
pdf "github.com/unidoc/unidoc/pdf/model"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Make the path with the content creator.
|
|
|
|
func drawPathWithCreator(path draw.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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeLineAnnotationAppearanceStream(lineDef LineAnnotationDef) (*pdfcore.PdfObjectDictionary, *pdf.PdfRectangle, error) {
|
|
|
|
form := pdf.NewXObjectForm()
|
|
|
|
form.Resources = pdf.NewPdfPageResources()
|
|
|
|
|
|
|
|
gsName := ""
|
|
|
|
if lineDef.Opacity < 1.0 {
|
|
|
|
// Create graphics state with right opacity.
|
|
|
|
gsState := &pdfcore.PdfObjectDictionary{}
|
|
|
|
gsState.Set("ca", pdfcore.MakeFloat(lineDef.Opacity))
|
|
|
|
err := form.Resources.AddExtGState("gs1", gsState)
|
|
|
|
if err != nil {
|
|
|
|
common.Log.Debug("Unable to add extgstate gs1")
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
gsName = "gs1"
|
|
|
|
}
|
|
|
|
|
2017-04-10 01:04:00 +00:00
|
|
|
content, localBbox, globalBbox, err := drawPdfLine(lineDef, gsName)
|
2017-04-09 16:49:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = form.SetContentStream(content, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2017-04-10 01:04:00 +00:00
|
|
|
// Local bounding box for the XObject Form.
|
|
|
|
form.BBox = localBbox.ToPdfObject()
|
2017-04-09 16:49:15 +00:00
|
|
|
|
|
|
|
apDict := &pdfcore.PdfObjectDictionary{}
|
|
|
|
apDict.Set("N", form.ToPdfObject())
|
|
|
|
|
2017-04-10 01:04:00 +00:00
|
|
|
return apDict, globalBbox, nil
|
2017-04-09 16:49:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Draw a line in PDF. Generates the content stream which can be used in page contents or appearance stream of annotation.
|
2017-04-10 01:04:00 +00:00
|
|
|
// Returns the stream content, XForm bounding box (local), Rect bounding box (global/page) and an error if one occurred.
|
|
|
|
func drawPdfLine(lineDef LineAnnotationDef, gsName string) ([]byte, *pdf.PdfRectangle, *pdf.PdfRectangle, error) {
|
2017-04-09 16:49:15 +00:00
|
|
|
x1, x2 := lineDef.X1, lineDef.X2
|
|
|
|
y1, y2 := lineDef.Y1, lineDef.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 := lineDef.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 := draw.NewPath()
|
|
|
|
path = path.AppendPoint(draw.NewPoint(V1X, V1Y))
|
|
|
|
path = path.AppendPoint(draw.NewPoint(V2X, V2Y))
|
|
|
|
path = path.AppendPoint(draw.NewPoint(V3X, V3Y))
|
|
|
|
path = path.AppendPoint(draw.NewPoint(V4X, V4Y))
|
|
|
|
|
|
|
|
lineEnding1 := lineDef.LineEndingStyle1
|
|
|
|
lineEnding2 := lineDef.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 := draw.NewVectorPolar(arrowHeight, theta+pi)
|
|
|
|
pa1 := p2.AddVector(va1)
|
|
|
|
|
|
|
|
bVec := draw.NewVectorPolar(arrowWidth/2, theta+pi/2)
|
|
|
|
aVec := draw.NewVectorPolar(arrowHeight, theta)
|
|
|
|
|
|
|
|
va2 := draw.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(draw.NewVectorPolar(w, theta-pi/2))
|
|
|
|
|
|
|
|
newpath := draw.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 := draw.NewVectorPolar(w/2, theta+pi+pi/2)
|
|
|
|
pa1 := p1.AddVector(v1)
|
|
|
|
|
|
|
|
v2 := draw.NewVectorPolar(arrowHeight, theta).Add(draw.NewVectorPolar(arrowWidth/2, theta+pi/2))
|
|
|
|
pa2 := pa1.AddVector(v2)
|
|
|
|
|
|
|
|
v3 := draw.NewVectorPolar(arrowExtruding, theta-pi/2)
|
|
|
|
pa3 := pa2.AddVector(v3)
|
|
|
|
|
|
|
|
// Last three points
|
|
|
|
v5 := draw.NewVectorPolar(arrowHeight, theta)
|
|
|
|
pa5 := pn.AddVector(v5)
|
|
|
|
|
|
|
|
v6 := draw.NewVectorPolar(arrowExtruding, theta+pi+pi/2)
|
|
|
|
pa6 := pa5.AddVector(v6)
|
|
|
|
|
|
|
|
pa7 := pa1
|
|
|
|
|
|
|
|
newpath := draw.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
|
|
|
|
}
|
|
|
|
|
|
|
|
pathBbox := path.GetBoundingBox()
|
2017-04-10 01:04:00 +00:00
|
|
|
|
2017-04-09 16:49:15 +00:00
|
|
|
creator := pdfcontent.NewContentCreator()
|
|
|
|
|
|
|
|
// Draw line with arrow
|
|
|
|
creator.
|
|
|
|
Add_q().
|
|
|
|
Add_rg(lineDef.LineColor.R(), lineDef.LineColor.G(), lineDef.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))
|
|
|
|
}
|
|
|
|
drawPathWithCreator(path, creator)
|
|
|
|
creator.Add_f().
|
|
|
|
//creator.Add_S().
|
|
|
|
Add_Q()
|
|
|
|
|
2017-04-10 01:04:00 +00:00
|
|
|
// Offsets (needed for placement of annotations bbox).
|
|
|
|
offX := x1 - VsX
|
|
|
|
offY := y1 - VsY
|
|
|
|
|
|
|
|
// Bounding box - local coordinate system (without offset).
|
|
|
|
localBbox := &pdf.PdfRectangle{}
|
|
|
|
localBbox.Llx = pathBbox.X
|
|
|
|
localBbox.Lly = pathBbox.Y
|
|
|
|
localBbox.Urx = pathBbox.X + pathBbox.Width
|
|
|
|
localBbox.Ury = pathBbox.Y + pathBbox.Height
|
|
|
|
|
|
|
|
// Bounding box - global page coordinate system (with offset).
|
|
|
|
globalBbox := &pdf.PdfRectangle{}
|
|
|
|
globalBbox.Llx = offX + pathBbox.X
|
|
|
|
globalBbox.Lly = offY + pathBbox.Y
|
|
|
|
globalBbox.Urx = offX + pathBbox.X + pathBbox.Width
|
|
|
|
globalBbox.Ury = offY + pathBbox.Y + pathBbox.Height
|
|
|
|
|
|
|
|
return creator.Bytes(), localBbox, globalBbox, nil
|
2017-04-09 16:49:15 +00:00
|
|
|
}
|