github.com/unidoc/unidoc@v2.2.0+incompatible/pdf/annotator/rectangle.go (about) 1 /* 2 * This file is subject to the terms and conditions defined in 3 * file 'LICENSE.md', which is part of this source code package. 4 */ 5 6 package annotator 7 8 import ( 9 "github.com/unidoc/unidoc/common" 10 11 "github.com/unidoc/unidoc/pdf/contentstream/draw" 12 pdfcore "github.com/unidoc/unidoc/pdf/core" 13 pdf "github.com/unidoc/unidoc/pdf/model" 14 ) 15 16 // A rectangle defined with a specified Width and Height and a lower left corner at (X,Y). The rectangle can 17 // optionally have a border and a filling color. 18 // The Width/Height includes the border (if any specified). 19 type RectangleAnnotationDef struct { 20 X float64 21 Y float64 22 Width float64 23 Height float64 24 FillEnabled bool // Show fill? 25 FillColor *pdf.PdfColorDeviceRGB 26 BorderEnabled bool // Show border? 27 BorderWidth float64 28 BorderColor *pdf.PdfColorDeviceRGB 29 Opacity float64 // Alpha value (0-1). 30 } 31 32 // Creates a rectangle annotation object that can be added to page PDF annotations. 33 func CreateRectangleAnnotation(rectDef RectangleAnnotationDef) (*pdf.PdfAnnotation, error) { 34 rectAnnotation := pdf.NewPdfAnnotationSquare() 35 36 if rectDef.BorderEnabled { 37 r, g, b := rectDef.BorderColor.R(), rectDef.BorderColor.G(), rectDef.BorderColor.B() 38 rectAnnotation.C = pdfcore.MakeArrayFromFloats([]float64{r, g, b}) 39 bs := pdf.NewBorderStyle() 40 bs.SetBorderWidth(rectDef.BorderWidth) 41 rectAnnotation.BS = bs.ToPdfObject() 42 } 43 44 if rectDef.FillEnabled { 45 r, g, b := rectDef.FillColor.R(), rectDef.FillColor.G(), rectDef.FillColor.B() 46 rectAnnotation.IC = pdfcore.MakeArrayFromFloats([]float64{r, g, b}) 47 } else { 48 rectAnnotation.IC = pdfcore.MakeArrayFromIntegers([]int{}) // No fill. 49 } 50 51 if rectDef.Opacity < 1.0 { 52 rectAnnotation.CA = pdfcore.MakeFloat(rectDef.Opacity) 53 } 54 55 // Make the appearance stream (for uniform appearance). 56 apDict, bbox, err := makeRectangleAnnotationAppearanceStream(rectDef) 57 if err != nil { 58 return nil, err 59 } 60 61 rectAnnotation.AP = apDict 62 rectAnnotation.Rect = pdfcore.MakeArrayFromFloats([]float64{bbox.Llx, bbox.Lly, bbox.Urx, bbox.Ury}) 63 64 return rectAnnotation.PdfAnnotation, nil 65 66 } 67 68 func makeRectangleAnnotationAppearanceStream(rectDef RectangleAnnotationDef) (*pdfcore.PdfObjectDictionary, *pdf.PdfRectangle, error) { 69 form := pdf.NewXObjectForm() 70 form.Resources = pdf.NewPdfPageResources() 71 72 gsName := "" 73 if rectDef.Opacity < 1.0 { 74 // Create graphics state with right opacity. 75 gsState := pdfcore.MakeDict() 76 gsState.Set("ca", pdfcore.MakeFloat(rectDef.Opacity)) 77 gsState.Set("CA", pdfcore.MakeFloat(rectDef.Opacity)) 78 err := form.Resources.AddExtGState("gs1", gsState) 79 if err != nil { 80 common.Log.Debug("Unable to add extgstate gs1") 81 return nil, nil, err 82 } 83 84 gsName = "gs1" 85 } 86 87 content, localBbox, globalBbox, err := drawPdfRectangle(rectDef, gsName) 88 if err != nil { 89 return nil, nil, err 90 } 91 92 err = form.SetContentStream(content, nil) 93 if err != nil { 94 return nil, nil, err 95 } 96 97 // Local bounding box for the XObject Form. 98 form.BBox = localBbox.ToPdfObject() 99 100 apDict := pdfcore.MakeDict() 101 apDict.Set("N", form.ToPdfObject()) 102 103 return apDict, globalBbox, nil 104 } 105 106 func drawPdfRectangle(rectDef RectangleAnnotationDef, gsName string) ([]byte, *pdf.PdfRectangle, *pdf.PdfRectangle, error) { 107 // The annotation is drawn locally in a relative coordinate system with 0,0 as the origin rather than an offset. 108 rect := draw.Rectangle{ 109 X: 0, 110 Y: 0, 111 Width: rectDef.Width, 112 Height: rectDef.Height, 113 FillEnabled: rectDef.FillEnabled, 114 FillColor: rectDef.FillColor, 115 BorderEnabled: rectDef.BorderEnabled, 116 BorderWidth: 2 * rectDef.BorderWidth, 117 BorderColor: rectDef.BorderColor, 118 Opacity: rectDef.Opacity, 119 } 120 121 content, localBbox, err := rect.Draw(gsName) 122 if err != nil { 123 return nil, nil, nil, err 124 } 125 126 // Bounding box - global page coordinate system (with offset). 127 globalBbox := &pdf.PdfRectangle{} 128 globalBbox.Llx = rectDef.X + localBbox.Llx 129 globalBbox.Lly = rectDef.Y + localBbox.Lly 130 globalBbox.Urx = rectDef.X + localBbox.Urx 131 globalBbox.Ury = rectDef.Y + localBbox.Ury 132 133 return content, localBbox, globalBbox, nil 134 }