From 329700417413596cbf6ce5f8730f5b4d8a4728d4 Mon Sep 17 00:00:00 2001 From: Gunnsteinn Hall Date: Wed, 12 Apr 2017 12:31:05 +0000 Subject: [PATCH] Create rectangle annotation with appearance stream --- pdf/annotator/annotator.go | 64 ++++++++++++++++++++++-- pdf/annotator/rectangle.go | 99 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 pdf/annotator/rectangle.go diff --git a/pdf/annotator/annotator.go b/pdf/annotator/annotator.go index 83603f55..30e91684 100644 --- a/pdf/annotator/annotator.go +++ b/pdf/annotator/annotator.go @@ -5,6 +5,7 @@ import ( pdf "github.com/unidoc/unidoc/pdf/model" ) +// The currently supported line ending styles are None, Arrow (ClosedArrow) and Butt. type LineEndingStyle int const ( @@ -13,16 +14,18 @@ const ( 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 LineAnnotationDef struct { X1 float64 Y1 float64 X2 float64 Y2 float64 LineColor *pdf.PdfColorDeviceRGB - Opacity float64 + Opacity float64 // Alpha value (0-1). LineWidth float64 - LineEndingStyle1 LineEndingStyle - LineEndingStyle2 LineEndingStyle + LineEndingStyle1 LineEndingStyle // Line ending style of point 1. + LineEndingStyle2 LineEndingStyle // Line ending style of point 2. } // Creates a line annotation object that can be added to page PDF annotations. @@ -69,3 +72,58 @@ func CreateLineAnnotation(lineDef LineAnnotationDef) (*pdf.PdfAnnotation, error) return lineAnnotation.PdfAnnotation, 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). +type RectangleAnnotationDef 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). +} + +// Creates a rectangle annotation object that can be added to page PDF annotations. +func CreateRectangleAnnotation(rectDef RectangleAnnotationDef) (*pdf.PdfAnnotation, error) { + rectAnnotation := pdf.NewPdfAnnotationSquare() + + if rectDef.BorderEnabled { + r, g, b := rectDef.BorderColor.R(), rectDef.BorderColor.G(), rectDef.BorderColor.B() + rectAnnotation.C = pdfcore.MakeArrayFromFloats([]float64{r, g, b}) + bs := pdf.NewBorderStyle() + bs.SetBorderWidth(rectDef.BorderWidth) + rectAnnotation.BS = bs.ToPdfObject() + } + + if rectDef.FillEnabled { + r, g, b := rectDef.FillColor.R(), rectDef.FillColor.G(), rectDef.FillColor.B() + rectAnnotation.IC = pdfcore.MakeArrayFromFloats([]float64{r, g, b}) + } else { + rectAnnotation.IC = pdfcore.MakeArrayFromIntegers([]int{}) // No fill. + } + + if rectDef.Opacity < 1.0 { + rectAnnotation.CA = pdfcore.MakeFloat(rectDef.Opacity) + } + + // Make the appearance stream (for uniform appearance). + apDict, bbox, err := makeRectangleAnnotationAppearanceStream(rectDef) + if err != nil { + return nil, err + } + + rectAnnotation.AP = apDict + rectAnnotation.Rect = pdfcore.MakeArrayFromFloats([]float64{bbox.Llx, bbox.Lly, bbox.Urx, bbox.Ury}) + + return rectAnnotation.PdfAnnotation, nil + +} + +type CircleAnnotationDef struct { +} diff --git a/pdf/annotator/rectangle.go b/pdf/annotator/rectangle.go new file mode 100644 index 00000000..1f275f2e --- /dev/null +++ b/pdf/annotator/rectangle.go @@ -0,0 +1,99 @@ +package annotator + +import ( + "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" +) + +func makeRectangleAnnotationAppearanceStream(rectDef RectangleAnnotationDef) (*pdfcore.PdfObjectDictionary, *pdf.PdfRectangle, error) { + form := pdf.NewXObjectForm() + form.Resources = pdf.NewPdfPageResources() + + gsName := "" + if rectDef.Opacity < 1.0 { + // Create graphics state with right opacity. + gsState := &pdfcore.PdfObjectDictionary{} + gsState.Set("ca", pdfcore.MakeFloat(rectDef.Opacity)) + gsState.Set("CA", pdfcore.MakeFloat(rectDef.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 := drawPdfRectangle(rectDef, 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.PdfObjectDictionary{} + apDict.Set("N", form.ToPdfObject()) + + return apDict, globalBbox, nil +} + +func drawPdfRectangle(rectDef RectangleAnnotationDef, gsName string) ([]byte, *pdf.PdfRectangle, *pdf.PdfRectangle, error) { + path := draw.NewPath() + + path = path.AppendPoint(draw.NewPoint(0, 0)) + path = path.AppendPoint(draw.NewPoint(0, rectDef.Height)) + path = path.AppendPoint(draw.NewPoint(rectDef.Width, rectDef.Height)) + path = path.AppendPoint(draw.NewPoint(rectDef.Width, 0)) + path = path.AppendPoint(draw.NewPoint(0, 0)) + + creator := pdfcontent.NewContentCreator() + + creator.Add_q() + if rectDef.FillEnabled { + creator.Add_rg(rectDef.FillColor.R(), rectDef.FillColor.G(), rectDef.FillColor.B()) + } + if rectDef.BorderEnabled { + creator.Add_RG(rectDef.BorderColor.R(), rectDef.BorderColor.G(), rectDef.BorderColor.B()) + creator.Add_w(rectDef.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_B() // fill and stroke. + creator.Add_Q() + + // Offsets (needed for placement of annotations bbox). + offX := rectDef.X + offY := rectDef.Y + + // Get bounding box. + pathBbox := path.GetBoundingBox() + + // 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 +}