github.com/xxf098/lite-proxy@v0.15.1-0.20230422081941-12c69f323218/web/render/context.go (about) 1 package render 2 3 import ( 4 "errors" 5 "image" 6 "image/color" 7 "image/jpeg" 8 "image/png" 9 "io" 10 "math" 11 "strings" 12 13 "github.com/golang/freetype/raster" 14 "golang.org/x/image/draw" 15 "golang.org/x/image/font" 16 "golang.org/x/image/font/basicfont" 17 "golang.org/x/image/math/f64" 18 ) 19 20 type LineCap int 21 22 const ( 23 LineCapRound LineCap = iota 24 LineCapButt 25 LineCapSquare 26 ) 27 28 type LineJoin int 29 30 const ( 31 LineJoinRound LineJoin = iota 32 LineJoinBevel 33 ) 34 35 type FillRule int 36 37 const ( 38 FillRuleWinding FillRule = iota 39 FillRuleEvenOdd 40 ) 41 42 type Align int 43 44 const ( 45 AlignLeft Align = iota 46 AlignCenter 47 AlignRight 48 ) 49 50 var ( 51 defaultFillStyle = NewSolidPattern(color.White) 52 defaultStrokeStyle = NewSolidPattern(color.Black) 53 ) 54 55 type Context struct { 56 width int 57 height int 58 rasterizer *raster.Rasterizer 59 im *image.RGBA 60 mask *image.Alpha 61 color color.Color 62 fillPattern Pattern 63 strokePattern Pattern 64 strokePath raster.Path 65 fillPath raster.Path 66 start Point 67 current Point 68 hasCurrent bool 69 dashes []float64 70 dashOffset float64 71 lineWidth float64 72 lineCap LineCap 73 lineJoin LineJoin 74 fillRule FillRule 75 fontFace font.Face 76 fontHeight float64 77 matrix Matrix 78 stack []*Context 79 } 80 81 // NewContext creates a new image.RGBA with the specified width and height 82 // and prepares a context for rendering onto that image. 83 func NewContext(width, height int) *Context { 84 return NewContextForRGBA(image.NewRGBA(image.Rect(0, 0, width, height))) 85 } 86 87 // NewContextForImage copies the specified image into a new image.RGBA 88 // and prepares a context for rendering onto that image. 89 func NewContextForImage(im image.Image) *Context { 90 return NewContextForRGBA(imageToRGBA(im)) 91 } 92 93 // NewContextForRGBA prepares a context for rendering onto the specified image. 94 // No copy is made. 95 func NewContextForRGBA(im *image.RGBA) *Context { 96 w := im.Bounds().Size().X 97 h := im.Bounds().Size().Y 98 return &Context{ 99 width: w, 100 height: h, 101 rasterizer: raster.NewRasterizer(w, h), 102 im: im, 103 color: color.Transparent, 104 fillPattern: defaultFillStyle, 105 strokePattern: defaultStrokeStyle, 106 lineWidth: 1, 107 fillRule: FillRuleWinding, 108 fontFace: basicfont.Face7x13, 109 fontHeight: 13, 110 matrix: Identity(), 111 } 112 } 113 114 // GetCurrentPoint will return the current point and if there is a current point. 115 // The point will have been transformed by the context's transformation matrix. 116 func (dc *Context) GetCurrentPoint() (Point, bool) { 117 if dc.hasCurrent { 118 return dc.current, true 119 } 120 return Point{}, false 121 } 122 123 // Image returns the image that has been drawn by this context. 124 func (dc *Context) Image() image.Image { 125 return dc.im 126 } 127 128 // Width returns the width of the image in pixels. 129 func (dc *Context) Width() int { 130 return dc.width 131 } 132 133 // Height returns the height of the image in pixels. 134 func (dc *Context) Height() int { 135 return dc.height 136 } 137 138 // SavePNG encodes the image as a PNG and writes it to disk. 139 func (dc *Context) SavePNG(path string) error { 140 return SavePNG(path, dc.im) 141 } 142 143 // SaveJPG encodes the image as a JPG and writes it to disk. 144 func (dc *Context) SaveJPG(path string, quality int) error { 145 return SaveJPG(path, dc.im, quality) 146 } 147 148 // EncodePNG encodes the image as a PNG and writes it to the provided io.Writer. 149 func (dc *Context) EncodePNG(w io.Writer) error { 150 return png.Encode(w, dc.im) 151 } 152 153 // EncodeJPG encodes the image as a JPG and writes it to the provided io.Writer 154 // in JPEG 4:2:0 baseline format with the given options. 155 // Default parameters are used if a nil *jpeg.Options is passed. 156 func (dc *Context) EncodeJPG(w io.Writer, o *jpeg.Options) error { 157 return jpeg.Encode(w, dc.im, o) 158 } 159 160 // SetDash sets the current dash pattern to use. Call with zero arguments to 161 // disable dashes. The values specify the lengths of each dash, with 162 // alternating on and off lengths. 163 func (dc *Context) SetDash(dashes ...float64) { 164 dc.dashes = dashes 165 } 166 167 // SetDashOffset sets the initial offset into the dash pattern to use when 168 // stroking dashed paths. 169 func (dc *Context) SetDashOffset(offset float64) { 170 dc.dashOffset = offset 171 } 172 173 func (dc *Context) SetLineWidth(lineWidth float64) { 174 dc.lineWidth = lineWidth 175 } 176 177 func (dc *Context) SetLineCap(lineCap LineCap) { 178 dc.lineCap = lineCap 179 } 180 181 func (dc *Context) SetLineCapRound() { 182 dc.lineCap = LineCapRound 183 } 184 185 func (dc *Context) SetLineCapButt() { 186 dc.lineCap = LineCapButt 187 } 188 189 func (dc *Context) SetLineCapSquare() { 190 dc.lineCap = LineCapSquare 191 } 192 193 func (dc *Context) SetLineJoin(lineJoin LineJoin) { 194 dc.lineJoin = lineJoin 195 } 196 197 func (dc *Context) SetLineJoinRound() { 198 dc.lineJoin = LineJoinRound 199 } 200 201 func (dc *Context) SetLineJoinBevel() { 202 dc.lineJoin = LineJoinBevel 203 } 204 205 func (dc *Context) SetFillRule(fillRule FillRule) { 206 dc.fillRule = fillRule 207 } 208 209 func (dc *Context) SetFillRuleWinding() { 210 dc.fillRule = FillRuleWinding 211 } 212 213 func (dc *Context) SetFillRuleEvenOdd() { 214 dc.fillRule = FillRuleEvenOdd 215 } 216 217 // Color Setters 218 219 func (dc *Context) setFillAndStrokeColor(c color.Color) { 220 dc.color = c 221 dc.fillPattern = NewSolidPattern(c) 222 dc.strokePattern = NewSolidPattern(c) 223 } 224 225 // SetFillStyle sets current fill style 226 func (dc *Context) SetFillStyle(pattern Pattern) { 227 // if pattern is SolidPattern, also change dc.color(for dc.Clear, dc.drawString) 228 if fillStyle, ok := pattern.(*solidPattern); ok { 229 dc.color = fillStyle.color 230 } 231 dc.fillPattern = pattern 232 } 233 234 // SetStrokeStyle sets current stroke style 235 func (dc *Context) SetStrokeStyle(pattern Pattern) { 236 dc.strokePattern = pattern 237 } 238 239 // SetColor sets the current color(for both fill and stroke). 240 func (dc *Context) SetColor(c color.Color) { 241 dc.setFillAndStrokeColor(c) 242 } 243 244 // SetHexColor sets the current color using a hex string. The leading pound 245 // sign (#) is optional. Both 3- and 6-digit variations are supported. 8 digits 246 // may be provided to set the alpha value as well. 247 func (dc *Context) SetHexColor(x string) { 248 r, g, b, a := parseHexColor(x) 249 dc.SetRGBA255(r, g, b, a) 250 } 251 252 // SetRGBA255 sets the current color. r, g, b, a values should be between 0 and 253 // 255, inclusive. 254 func (dc *Context) SetRGBA255(r, g, b, a int) { 255 dc.color = color.NRGBA{uint8(r), uint8(g), uint8(b), uint8(a)} 256 dc.setFillAndStrokeColor(dc.color) 257 } 258 259 // SetRGB255 sets the current color. r, g, b values should be between 0 and 255, 260 // inclusive. Alpha will be set to 255 (fully opaque). 261 func (dc *Context) SetRGB255(r, g, b int) { 262 dc.SetRGBA255(r, g, b, 255) 263 } 264 265 // SetRGBA sets the current color. r, g, b, a values should be between 0 and 1, 266 // inclusive. 267 func (dc *Context) SetRGBA(r, g, b, a float64) { 268 dc.color = color.NRGBA{ 269 uint8(r * 255), 270 uint8(g * 255), 271 uint8(b * 255), 272 uint8(a * 255), 273 } 274 dc.setFillAndStrokeColor(dc.color) 275 } 276 277 // SetRGB sets the current color. r, g, b values should be between 0 and 1, 278 // inclusive. Alpha will be set to 1 (fully opaque). 279 func (dc *Context) SetRGB(r, g, b float64) { 280 dc.SetRGBA(r, g, b, 1) 281 } 282 283 // Path Manipulation 284 285 // MoveTo starts a new subpath within the current path starting at the 286 // specified point. 287 func (dc *Context) MoveTo(x, y float64) { 288 if dc.hasCurrent { 289 dc.fillPath.Add1(dc.start.Fixed()) 290 } 291 x, y = dc.TransformPoint(x, y) 292 p := Point{x, y} 293 dc.strokePath.Start(p.Fixed()) 294 dc.fillPath.Start(p.Fixed()) 295 dc.start = p 296 dc.current = p 297 dc.hasCurrent = true 298 } 299 300 // LineTo adds a line segment to the current path starting at the current 301 // point. If there is no current point, it is equivalent to MoveTo(x, y) 302 func (dc *Context) LineTo(x, y float64) { 303 if !dc.hasCurrent { 304 dc.MoveTo(x, y) 305 } else { 306 x, y = dc.TransformPoint(x, y) 307 p := Point{x, y} 308 dc.strokePath.Add1(p.Fixed()) 309 dc.fillPath.Add1(p.Fixed()) 310 dc.current = p 311 } 312 } 313 314 // QuadraticTo adds a quadratic bezier curve to the current path starting at 315 // the current point. If there is no current point, it first performs 316 // MoveTo(x1, y1) 317 func (dc *Context) QuadraticTo(x1, y1, x2, y2 float64) { 318 if !dc.hasCurrent { 319 dc.MoveTo(x1, y1) 320 } 321 x1, y1 = dc.TransformPoint(x1, y1) 322 x2, y2 = dc.TransformPoint(x2, y2) 323 p1 := Point{x1, y1} 324 p2 := Point{x2, y2} 325 dc.strokePath.Add2(p1.Fixed(), p2.Fixed()) 326 dc.fillPath.Add2(p1.Fixed(), p2.Fixed()) 327 dc.current = p2 328 } 329 330 // CubicTo adds a cubic bezier curve to the current path starting at the 331 // current point. If there is no current point, it first performs 332 // MoveTo(x1, y1). Because freetype/raster does not support cubic beziers, 333 // this is emulated with many small line segments. 334 func (dc *Context) CubicTo(x1, y1, x2, y2, x3, y3 float64) { 335 if !dc.hasCurrent { 336 dc.MoveTo(x1, y1) 337 } 338 x0, y0 := dc.current.X, dc.current.Y 339 x1, y1 = dc.TransformPoint(x1, y1) 340 x2, y2 = dc.TransformPoint(x2, y2) 341 x3, y3 = dc.TransformPoint(x3, y3) 342 points := CubicBezier(x0, y0, x1, y1, x2, y2, x3, y3) 343 previous := dc.current.Fixed() 344 for _, p := range points[1:] { 345 f := p.Fixed() 346 if f == previous { 347 // TODO: this fixes some rendering issues but not all 348 continue 349 } 350 previous = f 351 dc.strokePath.Add1(f) 352 dc.fillPath.Add1(f) 353 dc.current = p 354 } 355 } 356 357 // ClosePath adds a line segment from the current point to the beginning 358 // of the current subpath. If there is no current point, this is a no-op. 359 func (dc *Context) ClosePath() { 360 if dc.hasCurrent { 361 dc.strokePath.Add1(dc.start.Fixed()) 362 dc.fillPath.Add1(dc.start.Fixed()) 363 dc.current = dc.start 364 } 365 } 366 367 // ClearPath clears the current path. There is no current point after this 368 // operation. 369 func (dc *Context) ClearPath() { 370 dc.strokePath.Clear() 371 dc.fillPath.Clear() 372 dc.hasCurrent = false 373 } 374 375 // NewSubPath starts a new subpath within the current path. There is no current 376 // point after this operation. 377 func (dc *Context) NewSubPath() { 378 if dc.hasCurrent { 379 dc.fillPath.Add1(dc.start.Fixed()) 380 } 381 dc.hasCurrent = false 382 } 383 384 // Path Drawing 385 386 func (dc *Context) capper() raster.Capper { 387 switch dc.lineCap { 388 case LineCapButt: 389 return raster.ButtCapper 390 case LineCapRound: 391 return raster.RoundCapper 392 case LineCapSquare: 393 return raster.SquareCapper 394 } 395 return nil 396 } 397 398 func (dc *Context) joiner() raster.Joiner { 399 switch dc.lineJoin { 400 case LineJoinBevel: 401 return raster.BevelJoiner 402 case LineJoinRound: 403 return raster.RoundJoiner 404 } 405 return nil 406 } 407 408 func (dc *Context) stroke(painter raster.Painter) { 409 path := dc.strokePath 410 if len(dc.dashes) > 0 { 411 path = dashed(path, dc.dashes, dc.dashOffset) 412 } else { 413 // TODO: this is a temporary workaround to remove tiny segments 414 // that result in rendering issues 415 path = rasterPath(flattenPath(path)) 416 } 417 r := dc.rasterizer 418 r.UseNonZeroWinding = true 419 r.Clear() 420 r.AddStroke(path, fix(dc.lineWidth), dc.capper(), dc.joiner()) 421 r.Rasterize(painter) 422 } 423 424 func (dc *Context) fill(painter raster.Painter) { 425 path := dc.fillPath 426 if dc.hasCurrent { 427 path = make(raster.Path, len(dc.fillPath)) 428 copy(path, dc.fillPath) 429 path.Add1(dc.start.Fixed()) 430 } 431 r := dc.rasterizer 432 r.UseNonZeroWinding = dc.fillRule == FillRuleWinding 433 r.Clear() 434 r.AddPath(path) 435 r.Rasterize(painter) 436 } 437 438 // StrokePreserve strokes the current path with the current color, line width, 439 // line cap, line join and dash settings. The path is preserved after this 440 // operation. 441 func (dc *Context) StrokePreserve() { 442 var painter raster.Painter 443 if dc.mask == nil { 444 if pattern, ok := dc.strokePattern.(*solidPattern); ok { 445 // with a nil mask and a solid color pattern, we can be more efficient 446 // TODO: refactor so we don't have to do this type assertion stuff? 447 p := raster.NewRGBAPainter(dc.im) 448 p.SetColor(pattern.color) 449 painter = p 450 } 451 } 452 if painter == nil { 453 painter = newPatternPainter(dc.im, dc.mask, dc.strokePattern) 454 } 455 dc.stroke(painter) 456 } 457 458 // Stroke strokes the current path with the current color, line width, 459 // line cap, line join and dash settings. The path is cleared after this 460 // operation. 461 func (dc *Context) Stroke() { 462 dc.StrokePreserve() 463 dc.ClearPath() 464 } 465 466 // FillPreserve fills the current path with the current color. Open subpaths 467 // are implicity closed. The path is preserved after this operation. 468 func (dc *Context) FillPreserve() { 469 var painter raster.Painter 470 if dc.mask == nil { 471 if pattern, ok := dc.fillPattern.(*solidPattern); ok { 472 // with a nil mask and a solid color pattern, we can be more efficient 473 // TODO: refactor so we don't have to do this type assertion stuff? 474 p := raster.NewRGBAPainter(dc.im) 475 p.SetColor(pattern.color) 476 painter = p 477 } 478 } 479 if painter == nil { 480 painter = newPatternPainter(dc.im, dc.mask, dc.fillPattern) 481 } 482 dc.fill(painter) 483 } 484 485 // Fill fills the current path with the current color. Open subpaths 486 // are implicity closed. The path is cleared after this operation. 487 func (dc *Context) Fill() { 488 dc.FillPreserve() 489 dc.ClearPath() 490 } 491 492 // ClipPreserve updates the clipping region by intersecting the current 493 // clipping region with the current path as it would be filled by dc.Fill(). 494 // The path is preserved after this operation. 495 func (dc *Context) ClipPreserve() { 496 clip := image.NewAlpha(image.Rect(0, 0, dc.width, dc.height)) 497 painter := raster.NewAlphaOverPainter(clip) 498 dc.fill(painter) 499 if dc.mask == nil { 500 dc.mask = clip 501 } else { 502 mask := image.NewAlpha(image.Rect(0, 0, dc.width, dc.height)) 503 draw.DrawMask(mask, mask.Bounds(), clip, image.ZP, dc.mask, image.ZP, draw.Over) 504 dc.mask = mask 505 } 506 } 507 508 // SetMask allows you to directly set the *image.Alpha to be used as a clipping 509 // mask. It must be the same size as the context, else an error is returned 510 // and the mask is unchanged. 511 func (dc *Context) SetMask(mask *image.Alpha) error { 512 if mask.Bounds().Size() != dc.im.Bounds().Size() { 513 return errors.New("mask size must match context size") 514 } 515 dc.mask = mask 516 return nil 517 } 518 519 // AsMask returns an *image.Alpha representing the alpha channel of this 520 // context. This can be useful for advanced clipping operations where you first 521 // render the mask geometry and then use it as a mask. 522 func (dc *Context) AsMask() *image.Alpha { 523 mask := image.NewAlpha(dc.im.Bounds()) 524 draw.Draw(mask, dc.im.Bounds(), dc.im, image.ZP, draw.Src) 525 return mask 526 } 527 528 // InvertMask inverts the alpha values in the current clipping mask such that 529 // a fully transparent region becomes fully opaque and vice versa. 530 func (dc *Context) InvertMask() { 531 if dc.mask == nil { 532 dc.mask = image.NewAlpha(dc.im.Bounds()) 533 } else { 534 for i, a := range dc.mask.Pix { 535 dc.mask.Pix[i] = 255 - a 536 } 537 } 538 } 539 540 // Clip updates the clipping region by intersecting the current 541 // clipping region with the current path as it would be filled by dc.Fill(). 542 // The path is cleared after this operation. 543 func (dc *Context) Clip() { 544 dc.ClipPreserve() 545 dc.ClearPath() 546 } 547 548 // ResetClip clears the clipping region. 549 func (dc *Context) ResetClip() { 550 dc.mask = nil 551 } 552 553 // Convenient Drawing Functions 554 555 // Clear fills the entire image with the current color. 556 func (dc *Context) Clear() { 557 src := image.NewUniform(dc.color) 558 draw.Draw(dc.im, dc.im.Bounds(), src, image.ZP, draw.Src) 559 } 560 561 // SetPixel sets the color of the specified pixel using the current color. 562 func (dc *Context) SetPixel(x, y int) { 563 dc.im.Set(x, y, dc.color) 564 } 565 566 // DrawPoint is like DrawCircle but ensures that a circle of the specified 567 // size is drawn regardless of the current transformation matrix. The position 568 // is still transformed, but not the shape of the point. 569 func (dc *Context) DrawPoint(x, y, r float64) { 570 dc.Push() 571 tx, ty := dc.TransformPoint(x, y) 572 dc.Identity() 573 dc.DrawCircle(tx, ty, r) 574 dc.Pop() 575 } 576 577 func (dc *Context) DrawLine(x1, y1, x2, y2 float64) { 578 dc.MoveTo(x1, y1) 579 dc.LineTo(x2, y2) 580 } 581 582 func (dc *Context) DrawRectangle(x, y, w, h float64) { 583 dc.NewSubPath() 584 dc.MoveTo(x, y) 585 dc.LineTo(x+w, y) 586 dc.LineTo(x+w, y+h) 587 dc.LineTo(x, y+h) 588 dc.ClosePath() 589 } 590 591 func (dc *Context) DrawRoundedRectangle(x, y, w, h, r float64) { 592 x0, x1, x2, x3 := x, x+r, x+w-r, x+w 593 y0, y1, y2, y3 := y, y+r, y+h-r, y+h 594 dc.NewSubPath() 595 dc.MoveTo(x1, y0) 596 dc.LineTo(x2, y0) 597 dc.DrawArc(x2, y1, r, Radians(270), Radians(360)) 598 dc.LineTo(x3, y2) 599 dc.DrawArc(x2, y2, r, Radians(0), Radians(90)) 600 dc.LineTo(x1, y3) 601 dc.DrawArc(x1, y2, r, Radians(90), Radians(180)) 602 dc.LineTo(x0, y1) 603 dc.DrawArc(x1, y1, r, Radians(180), Radians(270)) 604 dc.ClosePath() 605 } 606 607 func (dc *Context) DrawEllipticalArc(x, y, rx, ry, angle1, angle2 float64) { 608 const n = 16 609 for i := 0; i < n; i++ { 610 p1 := float64(i+0) / n 611 p2 := float64(i+1) / n 612 a1 := angle1 + (angle2-angle1)*p1 613 a2 := angle1 + (angle2-angle1)*p2 614 x0 := x + rx*math.Cos(a1) 615 y0 := y + ry*math.Sin(a1) 616 x1 := x + rx*math.Cos((a1+a2)/2) 617 y1 := y + ry*math.Sin((a1+a2)/2) 618 x2 := x + rx*math.Cos(a2) 619 y2 := y + ry*math.Sin(a2) 620 cx := 2*x1 - x0/2 - x2/2 621 cy := 2*y1 - y0/2 - y2/2 622 if i == 0 { 623 if dc.hasCurrent { 624 dc.LineTo(x0, y0) 625 } else { 626 dc.MoveTo(x0, y0) 627 } 628 } 629 dc.QuadraticTo(cx, cy, x2, y2) 630 } 631 } 632 633 func (dc *Context) DrawEllipse(x, y, rx, ry float64) { 634 dc.NewSubPath() 635 dc.DrawEllipticalArc(x, y, rx, ry, 0, 2*math.Pi) 636 dc.ClosePath() 637 } 638 639 func (dc *Context) DrawArc(x, y, r, angle1, angle2 float64) { 640 dc.DrawEllipticalArc(x, y, r, r, angle1, angle2) 641 } 642 643 func (dc *Context) DrawCircle(x, y, r float64) { 644 dc.NewSubPath() 645 dc.DrawEllipticalArc(x, y, r, r, 0, 2*math.Pi) 646 dc.ClosePath() 647 } 648 649 func (dc *Context) DrawRegularPolygon(n int, x, y, r, rotation float64) { 650 angle := 2 * math.Pi / float64(n) 651 rotation -= math.Pi / 2 652 if n%2 == 0 { 653 rotation += angle / 2 654 } 655 dc.NewSubPath() 656 for i := 0; i < n; i++ { 657 a := rotation + angle*float64(i) 658 dc.LineTo(x+r*math.Cos(a), y+r*math.Sin(a)) 659 } 660 dc.ClosePath() 661 } 662 663 // DrawImage draws the specified image at the specified point. 664 func (dc *Context) DrawImage(im image.Image, x, y int) { 665 dc.DrawImageAnchored(im, x, y, 0, 0) 666 } 667 668 // DrawImageAnchored draws the specified image at the specified anchor point. 669 // The anchor point is x - w * ax, y - h * ay, where w, h is the size of the 670 // image. Use ax=0.5, ay=0.5 to center the image at the specified point. 671 func (dc *Context) DrawImageAnchored(im image.Image, x, y int, ax, ay float64) { 672 s := im.Bounds().Size() 673 x -= int(ax * float64(s.X)) 674 y -= int(ay * float64(s.Y)) 675 transformer := draw.BiLinear 676 fx, fy := float64(x), float64(y) 677 m := dc.matrix.Translate(fx, fy) 678 s2d := f64.Aff3{m.XX, m.XY, m.X0, m.YX, m.YY, m.Y0} 679 if dc.mask == nil { 680 transformer.Transform(dc.im, s2d, im, im.Bounds(), draw.Over, nil) 681 } else { 682 transformer.Transform(dc.im, s2d, im, im.Bounds(), draw.Over, &draw.Options{ 683 DstMask: dc.mask, 684 DstMaskP: image.ZP, 685 }) 686 } 687 } 688 689 // Text Functions 690 691 func (dc *Context) SetFontFace(fontFace font.Face) { 692 dc.fontFace = fontFace 693 dc.fontHeight = float64(fontFace.Metrics().Height) / 64 694 } 695 696 func (dc *Context) LoadFontFace(path string, points float64) error { 697 face, err := LoadFontFace(path, points) 698 if err == nil { 699 dc.fontFace = face 700 dc.fontHeight = points * 72 / 96 701 } 702 return err 703 } 704 705 func (dc *Context) FontHeight() float64 { 706 return dc.fontHeight 707 } 708 709 func (dc *Context) drawString(im *image.RGBA, s string, x, y float64) { 710 d := &font.Drawer{ 711 Dst: im, 712 Src: image.NewUniform(dc.color), 713 Face: dc.fontFace, 714 Dot: fixp(x, y), 715 } 716 // based on Drawer.DrawString() in golang.org/x/image/font/font.go 717 prevC := rune(-1) 718 for _, c := range s { 719 if prevC >= 0 { 720 d.Dot.X += d.Face.Kern(prevC, c) 721 } 722 dr, mask, maskp, advance, ok := d.Face.Glyph(d.Dot, c) 723 if !ok { 724 // TODO: is falling back on the U+FFFD glyph the responsibility of 725 // the Drawer or the Face? 726 // TODO: set prevC = '\ufffd'? 727 continue 728 } 729 sr := dr.Sub(dr.Min) 730 transformer := draw.BiLinear 731 fx, fy := float64(dr.Min.X), float64(dr.Min.Y) 732 m := dc.matrix.Translate(fx, fy) 733 s2d := f64.Aff3{m.XX, m.XY, m.X0, m.YX, m.YY, m.Y0} 734 transformer.Transform(d.Dst, s2d, d.Src, sr, draw.Over, &draw.Options{ 735 SrcMask: mask, 736 SrcMaskP: maskp, 737 }) 738 d.Dot.X += advance 739 prevC = c 740 } 741 } 742 743 // DrawString draws the specified text at the specified point. 744 func (dc *Context) DrawString(s string, x, y float64) { 745 dc.DrawStringAnchored(s, x, y, 0, 0) 746 } 747 748 // DrawStringAnchored draws the specified text at the specified anchor point. 749 // The anchor point is x - w * ax, y - h * ay, where w, h is the size of the 750 // text. Use ax=0.5, ay=0.5 to center the text at the specified point. 751 func (dc *Context) DrawStringAnchored(s string, x, y, ax, ay float64) { 752 w, h := dc.MeasureString(s) 753 x -= ax * w 754 y += ay * h 755 if dc.mask == nil { 756 dc.drawString(dc.im, s, x, y) 757 } else { 758 im := image.NewRGBA(image.Rect(0, 0, dc.width, dc.height)) 759 dc.drawString(im, s, x, y) 760 draw.DrawMask(dc.im, dc.im.Bounds(), im, image.ZP, dc.mask, image.ZP, draw.Over) 761 } 762 } 763 764 // DrawStringWrapped word-wraps the specified string to the given max width 765 // and then draws it at the specified anchor point using the given line 766 // spacing and text alignment. 767 func (dc *Context) DrawStringWrapped(s string, x, y, ax, ay, width, lineSpacing float64, align Align) { 768 lines := dc.WordWrap(s, width) 769 770 // sync h formula with MeasureMultilineString 771 h := float64(len(lines)) * dc.fontHeight * lineSpacing 772 h -= (lineSpacing - 1) * dc.fontHeight 773 774 x -= ax * width 775 y -= ay * h 776 switch align { 777 case AlignLeft: 778 ax = 0 779 case AlignCenter: 780 ax = 0.5 781 x += width / 2 782 case AlignRight: 783 ax = 1 784 x += width 785 } 786 ay = 1 787 for _, line := range lines { 788 dc.DrawStringAnchored(line, x, y, ax, ay) 789 y += dc.fontHeight * lineSpacing 790 } 791 } 792 793 func (dc *Context) MeasureMultilineString(s string, lineSpacing float64) (width, height float64) { 794 lines := strings.Split(s, "\n") 795 796 // sync h formula with DrawStringWrapped 797 height = float64(len(lines)) * dc.fontHeight * lineSpacing 798 height -= (lineSpacing - 1) * dc.fontHeight 799 800 d := &font.Drawer{ 801 Face: dc.fontFace, 802 } 803 804 // max width from lines 805 for _, line := range lines { 806 adv := d.MeasureString(line) 807 currentWidth := float64(adv >> 6) // from gg.Context.MeasureString 808 if currentWidth > width { 809 width = currentWidth 810 } 811 } 812 813 return width, height 814 } 815 816 // MeasureString returns the rendered width and height of the specified text 817 // given the current font face. 818 func (dc *Context) MeasureString(s string) (w, h float64) { 819 d := &font.Drawer{ 820 Face: dc.fontFace, 821 } 822 a := d.MeasureString(s) 823 return float64(a >> 6), dc.fontHeight 824 } 825 826 // WordWrap wraps the specified string to the given max width and current 827 // font face. 828 func (dc *Context) WordWrap(s string, w float64) []string { 829 return wordWrap(dc, s, w) 830 } 831 832 // Transformation Matrix Operations 833 834 // Identity resets the current transformation matrix to the identity matrix. 835 // This results in no translating, scaling, rotating, or shearing. 836 func (dc *Context) Identity() { 837 dc.matrix = Identity() 838 } 839 840 // Translate updates the current matrix with a translation. 841 func (dc *Context) Translate(x, y float64) { 842 dc.matrix = dc.matrix.Translate(x, y) 843 } 844 845 // Scale updates the current matrix with a scaling factor. 846 // Scaling occurs about the origin. 847 func (dc *Context) Scale(x, y float64) { 848 dc.matrix = dc.matrix.Scale(x, y) 849 } 850 851 // ScaleAbout updates the current matrix with a scaling factor. 852 // Scaling occurs about the specified point. 853 func (dc *Context) ScaleAbout(sx, sy, x, y float64) { 854 dc.Translate(x, y) 855 dc.Scale(sx, sy) 856 dc.Translate(-x, -y) 857 } 858 859 // Rotate updates the current matrix with a anticlockwise rotation. 860 // Rotation occurs about the origin. Angle is specified in radians. 861 func (dc *Context) Rotate(angle float64) { 862 dc.matrix = dc.matrix.Rotate(angle) 863 } 864 865 // RotateAbout updates the current matrix with a anticlockwise rotation. 866 // Rotation occurs about the specified point. Angle is specified in radians. 867 func (dc *Context) RotateAbout(angle, x, y float64) { 868 dc.Translate(x, y) 869 dc.Rotate(angle) 870 dc.Translate(-x, -y) 871 } 872 873 // Shear updates the current matrix with a shearing angle. 874 // Shearing occurs about the origin. 875 func (dc *Context) Shear(x, y float64) { 876 dc.matrix = dc.matrix.Shear(x, y) 877 } 878 879 // ShearAbout updates the current matrix with a shearing angle. 880 // Shearing occurs about the specified point. 881 func (dc *Context) ShearAbout(sx, sy, x, y float64) { 882 dc.Translate(x, y) 883 dc.Shear(sx, sy) 884 dc.Translate(-x, -y) 885 } 886 887 // TransformPoint multiplies the specified point by the current matrix, 888 // returning a transformed position. 889 func (dc *Context) TransformPoint(x, y float64) (tx, ty float64) { 890 return dc.matrix.TransformPoint(x, y) 891 } 892 893 // InvertY flips the Y axis so that Y grows from bottom to top and Y=0 is at 894 // the bottom of the image. 895 func (dc *Context) InvertY() { 896 dc.Translate(0, float64(dc.height)) 897 dc.Scale(1, -1) 898 } 899 900 // Stack 901 902 // Push saves the current state of the context for later retrieval. These 903 // can be nested. 904 func (dc *Context) Push() { 905 x := *dc 906 dc.stack = append(dc.stack, &x) 907 } 908 909 // Pop restores the last saved context state from the stack. 910 func (dc *Context) Pop() { 911 before := *dc 912 s := dc.stack 913 x, s := s[len(s)-1], s[:len(s)-1] 914 *dc = *x 915 dc.mask = before.mask 916 dc.strokePath = before.strokePath 917 dc.fillPath = before.fillPath 918 dc.start = before.start 919 dc.current = before.current 920 dc.hasCurrent = before.hasCurrent 921 }