github.com/unidoc/unidoc@v2.2.0+incompatible/pdf/annotator/circle.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 type CircleAnnotationDef struct { 17 X float64 18 Y float64 19 Width float64 20 Height float64 21 FillEnabled bool // Show fill? 22 FillColor *pdf.PdfColorDeviceRGB 23 BorderEnabled bool // Show border? 24 BorderWidth float64 25 BorderColor *pdf.PdfColorDeviceRGB 26 Opacity float64 // Alpha value (0-1). 27 } 28 29 // Creates a circle/ellipse annotation object with appearance stream that can be added to page PDF annotations. 30 func CreateCircleAnnotation(circDef CircleAnnotationDef) (*pdf.PdfAnnotation, error) { 31 circAnnotation := pdf.NewPdfAnnotationCircle() 32 33 if circDef.BorderEnabled { 34 r, g, b := circDef.BorderColor.R(), circDef.BorderColor.G(), circDef.BorderColor.B() 35 circAnnotation.C = pdfcore.MakeArrayFromFloats([]float64{r, g, b}) 36 bs := pdf.NewBorderStyle() 37 bs.SetBorderWidth(circDef.BorderWidth) 38 circAnnotation.BS = bs.ToPdfObject() 39 } 40 41 if circDef.FillEnabled { 42 r, g, b := circDef.FillColor.R(), circDef.FillColor.G(), circDef.FillColor.B() 43 circAnnotation.IC = pdfcore.MakeArrayFromFloats([]float64{r, g, b}) 44 } else { 45 circAnnotation.IC = pdfcore.MakeArrayFromIntegers([]int{}) // No fill. 46 } 47 48 if circDef.Opacity < 1.0 { 49 circAnnotation.CA = pdfcore.MakeFloat(circDef.Opacity) 50 } 51 52 // Make the appearance stream (for uniform appearance). 53 apDict, bbox, err := makeCircleAnnotationAppearanceStream(circDef) 54 if err != nil { 55 return nil, err 56 } 57 58 circAnnotation.AP = apDict 59 circAnnotation.Rect = pdfcore.MakeArrayFromFloats([]float64{bbox.Llx, bbox.Lly, bbox.Urx, bbox.Ury}) 60 61 return circAnnotation.PdfAnnotation, nil 62 63 } 64 65 func makeCircleAnnotationAppearanceStream(circDef CircleAnnotationDef) (*pdfcore.PdfObjectDictionary, *pdf.PdfRectangle, error) { 66 form := pdf.NewXObjectForm() 67 form.Resources = pdf.NewPdfPageResources() 68 69 gsName := "" 70 if circDef.Opacity < 1.0 { 71 // Create graphics state with right opacity. 72 gsState := pdfcore.MakeDict() 73 gsState.Set("ca", pdfcore.MakeFloat(circDef.Opacity)) 74 gsState.Set("CA", pdfcore.MakeFloat(circDef.Opacity)) 75 err := form.Resources.AddExtGState("gs1", gsState) 76 if err != nil { 77 common.Log.Debug("Unable to add extgstate gs1") 78 return nil, nil, err 79 } 80 81 gsName = "gs1" 82 } 83 84 content, localBbox, globalBbox, err := drawPdfCircle(circDef, gsName) 85 if err != nil { 86 return nil, nil, err 87 } 88 89 err = form.SetContentStream(content, nil) 90 if err != nil { 91 return nil, nil, err 92 } 93 94 // Local bounding box for the XObject Form. 95 form.BBox = localBbox.ToPdfObject() 96 97 apDict := pdfcore.MakeDict() 98 apDict.Set("N", form.ToPdfObject()) 99 100 return apDict, globalBbox, nil 101 } 102 103 func drawPdfCircle(circDef CircleAnnotationDef, gsName string) ([]byte, *pdf.PdfRectangle, *pdf.PdfRectangle, error) { 104 // The annotation is drawn locally in a relative coordinate system with 0,0 as the origin rather than an offset. 105 circle := draw.Circle{ 106 X: circDef.X, 107 Y: circDef.Y, 108 Width: circDef.Width, 109 Height: circDef.Height, 110 FillEnabled: circDef.FillEnabled, 111 FillColor: circDef.FillColor, 112 BorderEnabled: circDef.BorderEnabled, 113 BorderWidth: circDef.BorderWidth, 114 BorderColor: circDef.BorderColor, 115 Opacity: circDef.Opacity, 116 } 117 118 content, localBbox, err := circle.Draw(gsName) 119 if err != nil { 120 return nil, nil, nil, err 121 } 122 123 // Bounding box - global page coordinate system (with offset). 124 globalBbox := &pdf.PdfRectangle{} 125 globalBbox.Llx = circDef.X + localBbox.Llx 126 globalBbox.Lly = circDef.Y + localBbox.Lly 127 globalBbox.Urx = circDef.X + localBbox.Urx 128 globalBbox.Ury = circDef.Y + localBbox.Ury 129 130 return content, localBbox, globalBbox, nil 131 }