mirror of
https://github.com/unidoc/unipdf.git
synced 2025-04-26 13:48:55 +08:00
139 lines
4.6 KiB
Go
139 lines
4.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 annotator
|
|
|
|
import (
|
|
"github.com/unidoc/unipdf/v3/common"
|
|
"github.com/unidoc/unipdf/v3/contentstream/draw"
|
|
pdfcore "github.com/unidoc/unipdf/v3/core"
|
|
pdf "github.com/unidoc/unipdf/v3/model"
|
|
)
|
|
|
|
// LineAnnotationDef 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 LineAnnotationDef struct {
|
|
X1 float64
|
|
Y1 float64
|
|
X2 float64
|
|
Y2 float64
|
|
LineColor *pdf.PdfColorDeviceRGB
|
|
Opacity float64 // Alpha value (0-1).
|
|
LineWidth float64
|
|
LineEndingStyle1 draw.LineEndingStyle // Line ending style of point 1.
|
|
LineEndingStyle2 draw.LineEndingStyle // Line ending style of point 2.
|
|
}
|
|
|
|
// CreateLineAnnotation creates a line annotation object that can be added to page PDF annotations.
|
|
func CreateLineAnnotation(lineDef LineAnnotationDef) (*pdf.PdfAnnotation, error) {
|
|
// Line annotation.
|
|
lineAnnotation := pdf.NewPdfAnnotationLine()
|
|
|
|
// Line endpoint locations.
|
|
lineAnnotation.L = pdfcore.MakeArrayFromFloats([]float64{lineDef.X1, lineDef.Y1, lineDef.X2, lineDef.Y2})
|
|
|
|
// Line endings.
|
|
le1 := pdfcore.MakeName("None")
|
|
if lineDef.LineEndingStyle1 == draw.LineEndingStyleArrow {
|
|
le1 = pdfcore.MakeName("ClosedArrow")
|
|
}
|
|
le2 := pdfcore.MakeName("None")
|
|
if lineDef.LineEndingStyle2 == draw.LineEndingStyleArrow {
|
|
le2 = pdfcore.MakeName("ClosedArrow")
|
|
}
|
|
lineAnnotation.LE = pdfcore.MakeArray(le1, le2)
|
|
|
|
// Opacity.
|
|
if lineDef.Opacity < 1.0 {
|
|
lineAnnotation.CA = pdfcore.MakeFloat(lineDef.Opacity)
|
|
}
|
|
|
|
r, g, b := lineDef.LineColor.R(), lineDef.LineColor.G(), lineDef.LineColor.B()
|
|
lineAnnotation.IC = pdfcore.MakeArrayFromFloats([]float64{r, g, b}) // fill color of line endings, rgb 0-1.
|
|
lineAnnotation.C = pdfcore.MakeArrayFromFloats([]float64{r, g, b}) // line color, rgb 0-1.
|
|
bs := pdf.NewBorderStyle()
|
|
bs.SetBorderWidth(lineDef.LineWidth) // Line width: 3 points.
|
|
lineAnnotation.BS = bs.ToPdfObject()
|
|
|
|
// Make the appearance stream (for uniform appearance).
|
|
apDict, bbox, err := makeLineAnnotationAppearanceStream(lineDef)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
lineAnnotation.AP = apDict
|
|
|
|
// The rect specifies the location and dimensions of the annotation. Technically if the annotation could not
|
|
// be displayed if it goes outside these bounds, although rarely enforced.
|
|
lineAnnotation.Rect = pdfcore.MakeArrayFromFloats([]float64{bbox.Llx, bbox.Lly, bbox.Urx, bbox.Ury})
|
|
|
|
return lineAnnotation.PdfAnnotation, nil
|
|
}
|
|
|
|
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.MakeDict()
|
|
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"
|
|
}
|
|
|
|
content, localBbox, globalBbox, err := drawPdfLine(lineDef, gsName)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
err = form.SetContentStream(content, nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Local bounding box for the XObject Form.
|
|
form.BBox = localBbox.ToPdfObject()
|
|
|
|
apDict := pdfcore.MakeDict()
|
|
apDict.Set("N", form.ToPdfObject())
|
|
|
|
return apDict, globalBbox, nil
|
|
}
|
|
|
|
func drawPdfLine(lineDef LineAnnotationDef, gsName string) ([]byte, *pdf.PdfRectangle, *pdf.PdfRectangle, error) {
|
|
// The annotation is drawn locally in a relative coordinate system with 0,0 as the origin rather than an offset.
|
|
line := draw.Line{
|
|
X1: 0,
|
|
Y1: 0,
|
|
X2: lineDef.X2 - lineDef.X1,
|
|
Y2: lineDef.Y2 - lineDef.Y1,
|
|
LineColor: lineDef.LineColor,
|
|
Opacity: lineDef.Opacity,
|
|
LineWidth: lineDef.LineWidth,
|
|
LineEndingStyle1: lineDef.LineEndingStyle1,
|
|
LineEndingStyle2: lineDef.LineEndingStyle2,
|
|
}
|
|
|
|
content, localBbox, err := line.Draw(gsName)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
// Bounding box - global page coordinate system (with offset).
|
|
globalBbox := &pdf.PdfRectangle{}
|
|
globalBbox.Llx = lineDef.X1 + localBbox.Llx
|
|
globalBbox.Lly = lineDef.Y1 + localBbox.Lly
|
|
globalBbox.Urx = lineDef.X1 + localBbox.Urx
|
|
globalBbox.Ury = lineDef.Y1 + localBbox.Ury
|
|
|
|
return content, localBbox, globalBbox, nil
|
|
}
|