github.com/laof/lite-speed-test@v0.0.0-20230930011949-1f39b7037845/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  }