github.com/unidoc/unidoc@v2.2.0+incompatible/pdf/contentstream/draw/shapes.go (about) 1 package draw 2 3 import ( 4 "math" 5 6 pdfcontent "github.com/unidoc/unidoc/pdf/contentstream" 7 pdfcore "github.com/unidoc/unidoc/pdf/core" 8 pdf "github.com/unidoc/unidoc/pdf/model" 9 ) 10 11 type Circle struct { 12 X float64 13 Y float64 14 Width float64 15 Height float64 16 FillEnabled bool // Show fill? 17 FillColor *pdf.PdfColorDeviceRGB 18 BorderEnabled bool // Show border? 19 BorderWidth float64 20 BorderColor *pdf.PdfColorDeviceRGB 21 Opacity float64 // Alpha value (0-1). 22 } 23 24 // Draw a circle. Can specify a graphics state (gsName) for setting opacity etc. Otherwise leave empty (""). 25 // Returns the content stream as a byte array, the bounding box and an error on failure. 26 func (c Circle) Draw(gsName string) ([]byte, *pdf.PdfRectangle, error) { 27 xRad := c.Width / 2 28 yRad := c.Height / 2 29 if c.BorderEnabled { 30 xRad -= c.BorderWidth / 2 31 yRad -= c.BorderWidth / 2 32 } 33 34 magic := 0.551784 35 xMagic := xRad * magic 36 yMagic := yRad * magic 37 38 bpath := NewCubicBezierPath() 39 bpath = bpath.AppendCurve(NewCubicBezierCurve(-xRad, 0, -xRad, yMagic, -xMagic, yRad, 0, yRad)) 40 bpath = bpath.AppendCurve(NewCubicBezierCurve(0, yRad, xMagic, yRad, xRad, yMagic, xRad, 0)) 41 bpath = bpath.AppendCurve(NewCubicBezierCurve(xRad, 0, xRad, -yMagic, xMagic, -yRad, 0, -yRad)) 42 bpath = bpath.AppendCurve(NewCubicBezierCurve(0, -yRad, -xMagic, -yRad, -xRad, -yMagic, -xRad, 0)) 43 bpath = bpath.Offset(xRad, yRad) 44 if c.BorderEnabled { 45 bpath = bpath.Offset(c.BorderWidth/2, c.BorderWidth/2) 46 } 47 if c.X != 0 || c.Y != 0 { 48 bpath = bpath.Offset(c.X, c.Y) 49 } 50 51 creator := pdfcontent.NewContentCreator() 52 53 creator.Add_q() 54 55 if c.FillEnabled { 56 creator.Add_rg(c.FillColor.R(), c.FillColor.G(), c.FillColor.B()) 57 } 58 if c.BorderEnabled { 59 creator.Add_RG(c.BorderColor.R(), c.BorderColor.G(), c.BorderColor.B()) 60 creator.Add_w(c.BorderWidth) 61 } 62 if len(gsName) > 1 { 63 // If a graphics state is provided, use it. (Used for transparency settings here). 64 creator.Add_gs(pdfcore.PdfObjectName(gsName)) 65 } 66 67 DrawBezierPathWithCreator(bpath, creator) 68 creator.Add_h() // Close the path. 69 70 if c.FillEnabled && c.BorderEnabled { 71 creator.Add_B() // fill and stroke. 72 } else if c.FillEnabled { 73 creator.Add_f() // Fill. 74 } else if c.BorderEnabled { 75 creator.Add_S() // Stroke. 76 } 77 creator.Add_Q() 78 79 // Get bounding box. 80 pathBbox := bpath.GetBoundingBox() 81 if c.BorderEnabled { 82 // Account for stroke width. 83 pathBbox.Height += c.BorderWidth 84 pathBbox.Width += c.BorderWidth 85 pathBbox.X -= c.BorderWidth / 2 86 pathBbox.Y -= c.BorderWidth / 2 87 } 88 89 // Bounding box - global coordinate system. 90 bbox := &pdf.PdfRectangle{} 91 bbox.Llx = pathBbox.X 92 bbox.Lly = pathBbox.Y 93 bbox.Urx = pathBbox.X + pathBbox.Width 94 bbox.Ury = pathBbox.Y + pathBbox.Height 95 96 return creator.Bytes(), bbox, nil 97 } 98 99 // A rectangle defined with a specified Width and Height and a lower left corner at (X,Y). The rectangle can 100 // optionally have a border and a filling color. 101 // The Width/Height includes the border (if any specified), i.e. is positioned inside. 102 type Rectangle struct { 103 X float64 104 Y float64 105 Width float64 106 Height float64 107 FillEnabled bool // Show fill? 108 FillColor *pdf.PdfColorDeviceRGB 109 BorderEnabled bool // Show border? 110 BorderWidth float64 111 BorderColor *pdf.PdfColorDeviceRGB 112 Opacity float64 // Alpha value (0-1). 113 } 114 115 // Draw the circle. Can specify a graphics state (gsName) for setting opacity etc. Otherwise leave empty (""). 116 // Returns the content stream as a byte array, bounding box and an error on failure. 117 func (rect Rectangle) Draw(gsName string) ([]byte, *pdf.PdfRectangle, error) { 118 path := NewPath() 119 120 path = path.AppendPoint(NewPoint(0, 0)) 121 path = path.AppendPoint(NewPoint(0, rect.Height)) 122 path = path.AppendPoint(NewPoint(rect.Width, rect.Height)) 123 path = path.AppendPoint(NewPoint(rect.Width, 0)) 124 path = path.AppendPoint(NewPoint(0, 0)) 125 126 if rect.X != 0 || rect.Y != 0 { 127 path = path.Offset(rect.X, rect.Y) 128 } 129 130 creator := pdfcontent.NewContentCreator() 131 132 creator.Add_q() 133 if rect.FillEnabled { 134 creator.Add_rg(rect.FillColor.R(), rect.FillColor.G(), rect.FillColor.B()) 135 } 136 if rect.BorderEnabled { 137 creator.Add_RG(rect.BorderColor.R(), rect.BorderColor.G(), rect.BorderColor.B()) 138 creator.Add_w(rect.BorderWidth) 139 } 140 if len(gsName) > 1 { 141 // If a graphics state is provided, use it. (Used for transparency settings here). 142 creator.Add_gs(pdfcore.PdfObjectName(gsName)) 143 } 144 DrawPathWithCreator(path, creator) 145 creator.Add_h() // Close the path. 146 147 if rect.FillEnabled && rect.BorderEnabled { 148 creator.Add_B() // fill and stroke. 149 } else if rect.FillEnabled { 150 creator.Add_f() // Fill. 151 } else if rect.BorderEnabled { 152 creator.Add_S() // Stroke. 153 } 154 creator.Add_Q() 155 156 // Get bounding box. 157 pathBbox := path.GetBoundingBox() 158 159 // Bounding box - global coordinate system. 160 bbox := &pdf.PdfRectangle{} 161 bbox.Llx = pathBbox.X 162 bbox.Lly = pathBbox.Y 163 bbox.Urx = pathBbox.X + pathBbox.Width 164 bbox.Ury = pathBbox.Y + pathBbox.Height 165 166 return creator.Bytes(), bbox, nil 167 } 168 169 // The currently supported line ending styles are None, Arrow (ClosedArrow) and Butt. 170 type LineEndingStyle int 171 172 const ( 173 LineEndingStyleNone LineEndingStyle = 0 174 LineEndingStyleArrow LineEndingStyle = 1 175 LineEndingStyleButt LineEndingStyle = 2 176 ) 177 178 // Defines a line between point 1 (X1,Y1) and point 2 (X2,Y2). The line ending styles can be none (regular line), 179 // or arrows at either end. The line also has a specified width, color and opacity. 180 type Line struct { 181 X1 float64 182 Y1 float64 183 X2 float64 184 Y2 float64 185 LineColor *pdf.PdfColorDeviceRGB 186 Opacity float64 // Alpha value (0-1). 187 LineWidth float64 188 LineEndingStyle1 LineEndingStyle // Line ending style of point 1. 189 LineEndingStyle2 LineEndingStyle // Line ending style of point 2. 190 } 191 192 // Draw a line in PDF. Generates the content stream which can be used in page contents or appearance stream of annotation. 193 // Returns the stream content, XForm bounding box (local), bounding box and an error if one occurred. 194 func (line Line) Draw(gsName string) ([]byte, *pdf.PdfRectangle, error) { 195 x1, x2 := line.X1, line.X2 196 y1, y2 := line.Y1, line.Y2 197 198 dy := y2 - y1 199 dx := x2 - x1 200 theta := math.Atan2(dy, dx) 201 202 L := math.Sqrt(math.Pow(dx, 2.0) + math.Pow(dy, 2.0)) 203 w := line.LineWidth 204 205 pi := math.Pi 206 207 mul := 1.0 208 if dx < 0 { 209 mul *= -1.0 210 } 211 if dy < 0 { 212 mul *= -1.0 213 } 214 215 // Vs. 216 VsX := mul * (-w / 2 * math.Cos(theta+pi/2)) 217 VsY := mul * (-w/2*math.Sin(theta+pi/2) + w*math.Sin(theta+pi/2)) 218 219 // V1. 220 V1X := VsX + w/2*math.Cos(theta+pi/2) 221 V1Y := VsY + w/2*math.Sin(theta+pi/2) 222 223 // P2. 224 V2X := VsX + w/2*math.Cos(theta+pi/2) + L*math.Cos(theta) 225 V2Y := VsY + w/2*math.Sin(theta+pi/2) + L*math.Sin(theta) 226 227 // P3. 228 V3X := VsX + w/2*math.Cos(theta+pi/2) + L*math.Cos(theta) + w*math.Cos(theta-pi/2) 229 V3Y := VsY + w/2*math.Sin(theta+pi/2) + L*math.Sin(theta) + w*math.Sin(theta-pi/2) 230 231 // P4. 232 V4X := VsX + w/2*math.Cos(theta-pi/2) 233 V4Y := VsY + w/2*math.Sin(theta-pi/2) 234 235 path := NewPath() 236 path = path.AppendPoint(NewPoint(V1X, V1Y)) 237 path = path.AppendPoint(NewPoint(V2X, V2Y)) 238 path = path.AppendPoint(NewPoint(V3X, V3Y)) 239 path = path.AppendPoint(NewPoint(V4X, V4Y)) 240 241 lineEnding1 := line.LineEndingStyle1 242 lineEnding2 := line.LineEndingStyle2 243 244 // TODO: Allow custom height/widths. 245 arrowHeight := 3 * w 246 arrowWidth := 3 * w 247 arrowExtruding := (arrowWidth - w) / 2 248 249 if lineEnding2 == LineEndingStyleArrow { 250 // Convert P2, P3 251 p2 := path.GetPointNumber(2) 252 253 va1 := NewVectorPolar(arrowHeight, theta+pi) 254 pa1 := p2.AddVector(va1) 255 256 bVec := NewVectorPolar(arrowWidth/2, theta+pi/2) 257 aVec := NewVectorPolar(arrowHeight, theta) 258 259 va2 := NewVectorPolar(arrowExtruding, theta+pi/2) 260 pa2 := pa1.AddVector(va2) 261 262 va3 := aVec.Add(bVec.Flip()) 263 pa3 := pa2.AddVector(va3) 264 265 va4 := bVec.Scale(2).Flip().Add(va3.Flip()) 266 pa4 := pa3.AddVector(va4) 267 268 pa5 := pa1.AddVector(NewVectorPolar(w, theta-pi/2)) 269 270 newpath := NewPath() 271 newpath = newpath.AppendPoint(path.GetPointNumber(1)) 272 newpath = newpath.AppendPoint(pa1) 273 newpath = newpath.AppendPoint(pa2) 274 newpath = newpath.AppendPoint(pa3) 275 newpath = newpath.AppendPoint(pa4) 276 newpath = newpath.AppendPoint(pa5) 277 newpath = newpath.AppendPoint(path.GetPointNumber(4)) 278 279 path = newpath 280 } 281 if lineEnding1 == LineEndingStyleArrow { 282 // Get the first and last points. 283 p1 := path.GetPointNumber(1) 284 pn := path.GetPointNumber(path.Length()) 285 286 // First three points on arrow. 287 v1 := NewVectorPolar(w/2, theta+pi+pi/2) 288 pa1 := p1.AddVector(v1) 289 290 v2 := NewVectorPolar(arrowHeight, theta).Add(NewVectorPolar(arrowWidth/2, theta+pi/2)) 291 pa2 := pa1.AddVector(v2) 292 293 v3 := NewVectorPolar(arrowExtruding, theta-pi/2) 294 pa3 := pa2.AddVector(v3) 295 296 // Last three points 297 v5 := NewVectorPolar(arrowHeight, theta) 298 pa5 := pn.AddVector(v5) 299 300 v6 := NewVectorPolar(arrowExtruding, theta+pi+pi/2) 301 pa6 := pa5.AddVector(v6) 302 303 pa7 := pa1 304 305 newpath := NewPath() 306 newpath = newpath.AppendPoint(pa1) 307 newpath = newpath.AppendPoint(pa2) 308 newpath = newpath.AppendPoint(pa3) 309 for _, p := range path.Points[1 : len(path.Points)-1] { 310 newpath = newpath.AppendPoint(p) 311 } 312 newpath = newpath.AppendPoint(pa5) 313 newpath = newpath.AppendPoint(pa6) 314 newpath = newpath.AppendPoint(pa7) 315 316 path = newpath 317 } 318 319 creator := pdfcontent.NewContentCreator() 320 321 // Draw line with arrow 322 creator. 323 Add_q(). 324 Add_rg(line.LineColor.R(), line.LineColor.G(), line.LineColor.B()) 325 if len(gsName) > 1 { 326 // If a graphics state is provided, use it. (Used for transparency settings here). 327 creator.Add_gs(pdfcore.PdfObjectName(gsName)) 328 } 329 330 path = path.Offset(line.X1, line.Y1) 331 332 pathBbox := path.GetBoundingBox() 333 334 DrawPathWithCreator(path, creator) 335 creator.Add_f(). 336 //creator.Add_S(). 337 Add_Q() 338 339 /* 340 // Offsets (needed for placement of annotations bbox). 341 offX := x1 - VsX 342 offY := y1 - VsY 343 */ 344 345 // Bounding box - global coordinate system. 346 bbox := &pdf.PdfRectangle{} 347 bbox.Llx = pathBbox.X 348 bbox.Lly = pathBbox.Y 349 bbox.Urx = pathBbox.X + pathBbox.Width 350 bbox.Ury = pathBbox.Y + pathBbox.Height 351 352 return creator.Bytes(), bbox, nil 353 }