github.com/unidoc/unidoc@v2.2.0+incompatible/pdf/annotator/line.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 "github.com/unidoc/unidoc/pdf/contentstream/draw" 11 pdfcore "github.com/unidoc/unidoc/pdf/core" 12 pdf "github.com/unidoc/unidoc/pdf/model" 13 ) 14 15 // Defines a line between point 1 (X1,Y1) and point 2 (X2,Y2). The line ending styles can be none (regular line), 16 // or arrows at either end. The line also has a specified width, color and opacity. 17 type LineAnnotationDef struct { 18 X1 float64 19 Y1 float64 20 X2 float64 21 Y2 float64 22 LineColor *pdf.PdfColorDeviceRGB 23 Opacity float64 // Alpha value (0-1). 24 LineWidth float64 25 LineEndingStyle1 draw.LineEndingStyle // Line ending style of point 1. 26 LineEndingStyle2 draw.LineEndingStyle // Line ending style of point 2. 27 } 28 29 // Creates a line annotation object that can be added to page PDF annotations. 30 func CreateLineAnnotation(lineDef LineAnnotationDef) (*pdf.PdfAnnotation, error) { 31 // Line annotation. 32 lineAnnotation := pdf.NewPdfAnnotationLine() 33 34 // Line endpoint locations. 35 lineAnnotation.L = pdfcore.MakeArrayFromFloats([]float64{lineDef.X1, lineDef.Y1, lineDef.X2, lineDef.Y2}) 36 37 // Line endings. 38 le1 := pdfcore.MakeName("None") 39 if lineDef.LineEndingStyle1 == draw.LineEndingStyleArrow { 40 le1 = pdfcore.MakeName("ClosedArrow") 41 } 42 le2 := pdfcore.MakeName("None") 43 if lineDef.LineEndingStyle2 == draw.LineEndingStyleArrow { 44 le2 = pdfcore.MakeName("ClosedArrow") 45 } 46 lineAnnotation.LE = pdfcore.MakeArray(le1, le2) 47 48 // Opacity. 49 if lineDef.Opacity < 1.0 { 50 lineAnnotation.CA = pdfcore.MakeFloat(lineDef.Opacity) 51 } 52 53 r, g, b := lineDef.LineColor.R(), lineDef.LineColor.G(), lineDef.LineColor.B() 54 lineAnnotation.IC = pdfcore.MakeArrayFromFloats([]float64{r, g, b}) // fill color of line endings, rgb 0-1. 55 lineAnnotation.C = pdfcore.MakeArrayFromFloats([]float64{r, g, b}) // line color, rgb 0-1. 56 bs := pdf.NewBorderStyle() 57 bs.SetBorderWidth(lineDef.LineWidth) // Line width: 3 points. 58 lineAnnotation.BS = bs.ToPdfObject() 59 60 // Make the appearance stream (for uniform appearance). 61 apDict, bbox, err := makeLineAnnotationAppearanceStream(lineDef) 62 if err != nil { 63 return nil, err 64 } 65 lineAnnotation.AP = apDict 66 67 // The rect specifies the location and dimensions of the annotation. Technically if the annotation could not 68 // be displayed if it goes outside these bounds, although rarely enforced. 69 lineAnnotation.Rect = pdfcore.MakeArrayFromFloats([]float64{bbox.Llx, bbox.Lly, bbox.Urx, bbox.Ury}) 70 71 return lineAnnotation.PdfAnnotation, nil 72 } 73 74 func makeLineAnnotationAppearanceStream(lineDef LineAnnotationDef) (*pdfcore.PdfObjectDictionary, *pdf.PdfRectangle, error) { 75 form := pdf.NewXObjectForm() 76 form.Resources = pdf.NewPdfPageResources() 77 78 gsName := "" 79 if lineDef.Opacity < 1.0 { 80 // Create graphics state with right opacity. 81 gsState := pdfcore.MakeDict() 82 gsState.Set("ca", pdfcore.MakeFloat(lineDef.Opacity)) 83 err := form.Resources.AddExtGState("gs1", gsState) 84 if err != nil { 85 common.Log.Debug("Unable to add extgstate gs1") 86 return nil, nil, err 87 } 88 89 gsName = "gs1" 90 } 91 92 content, localBbox, globalBbox, err := drawPdfLine(lineDef, gsName) 93 if err != nil { 94 return nil, nil, err 95 } 96 97 err = form.SetContentStream(content, nil) 98 if err != nil { 99 return nil, nil, err 100 } 101 102 // Local bounding box for the XObject Form. 103 form.BBox = localBbox.ToPdfObject() 104 105 apDict := pdfcore.MakeDict() 106 apDict.Set("N", form.ToPdfObject()) 107 108 return apDict, globalBbox, nil 109 } 110 111 func drawPdfLine(lineDef LineAnnotationDef, gsName string) ([]byte, *pdf.PdfRectangle, *pdf.PdfRectangle, error) { 112 // The annotation is drawn locally in a relative coordinate system with 0,0 as the origin rather than an offset. 113 line := draw.Line{ 114 X1: 0, 115 Y1: 0, 116 X2: lineDef.X2 - lineDef.X1, 117 Y2: lineDef.Y2 - lineDef.Y1, 118 LineColor: lineDef.LineColor, 119 Opacity: lineDef.Opacity, 120 LineWidth: lineDef.LineWidth, 121 LineEndingStyle1: lineDef.LineEndingStyle1, 122 LineEndingStyle2: lineDef.LineEndingStyle2, 123 } 124 125 content, localBbox, err := line.Draw(gsName) 126 if err != nil { 127 return nil, nil, nil, err 128 } 129 130 // Bounding box - global page coordinate system (with offset). 131 globalBbox := &pdf.PdfRectangle{} 132 globalBbox.Llx = lineDef.X1 + localBbox.Llx 133 globalBbox.Lly = lineDef.Y1 + localBbox.Lly 134 globalBbox.Urx = lineDef.X1 + localBbox.Urx 135 globalBbox.Ury = lineDef.Y1 + localBbox.Ury 136 137 return content, localBbox, globalBbox, nil 138 }