github.com/andybalholm/giopdf@v0.0.0-20220317170119-aad9a095ad48/path.go (about)

     1  package giopdf
     2  
     3  import (
     4  	"gioui.org/f32"
     5  	"gioui.org/op"
     6  	"gioui.org/op/clip"
     7  )
     8  
     9  // A PathElement represents a segement of a path.
    10  type PathElement struct {
    11  	// Op is the PDF path construction operator corresponding to this
    12  	// PathElement. Possible values are:
    13  	//
    14  	//     m moveto
    15  	//     l lineto
    16  	//     c curveto
    17  	//     h closepath
    18  	Op byte
    19  
    20  	// CP1 and CP2 are the control points for a bezier curve segment ('c').
    21  	CP1, CP2 f32.Point
    22  	End      f32.Point
    23  }
    24  
    25  type PathBuilder struct {
    26  	Path         []PathElement
    27  	currentPoint f32.Point
    28  	lastMoveTo   f32.Point
    29  }
    30  
    31  // MoveTo starts a new subpath at (x, y).
    32  func (p *PathBuilder) MoveTo(x, y float32) {
    33  	pt := f32.Point{x, y}
    34  	p.currentPoint = pt
    35  	p.lastMoveTo = pt
    36  
    37  	if len(p.Path) > 0 && p.Path[len(p.Path)-1].Op == 'm' {
    38  		p.Path[len(p.Path)-1].End = pt
    39  		return
    40  	}
    41  
    42  	p.Path = append(p.Path, PathElement{Op: 'm', End: pt})
    43  }
    44  
    45  // LineTo adds a line segment to the path, ending at (x, y).
    46  func (p *PathBuilder) LineTo(x, y float32) {
    47  	pt := f32.Point{x, y}
    48  	p.currentPoint = pt
    49  	p.Path = append(p.Path, PathElement{Op: 'l', End: pt})
    50  }
    51  
    52  // CurveTo adds a cubic Bezier curve to the path. It ends at (x3, y3) and has
    53  // (x1, y1) and (x2, y2) for control points.
    54  func (p *PathBuilder) CurveTo(x1, y1, x2, y2, x3, y3 float32) {
    55  	e := PathElement{
    56  		Op:  'c',
    57  		CP1: f32.Point{x1, y1},
    58  		CP2: f32.Point{x2, y2},
    59  		End: f32.Point{x3, y3},
    60  	}
    61  	p.currentPoint = e.End
    62  	p.Path = append(p.Path, e)
    63  }
    64  
    65  // CurveV adds a cubic Bezier curve to the path. It ends at (x3, y3) and has
    66  // the current point and (x2, y2) for control points.
    67  func (p *PathBuilder) CurveV(x2, y2, x3, y3 float32) {
    68  	e := PathElement{
    69  		Op:  'c',
    70  		CP1: p.currentPoint,
    71  		CP2: f32.Point{x2, y2},
    72  		End: f32.Point{x3, y3},
    73  	}
    74  	p.currentPoint = e.End
    75  	p.Path = append(p.Path, e)
    76  }
    77  
    78  // CurveY adds a cubic Bezier curve to the path. It ends at (x3, y3) and has
    79  // (x1, y1) and (x3, y3) for control points.
    80  func (p *PathBuilder) CurveY(x1, y1, x3, y3 float32) {
    81  	e := PathElement{
    82  		Op:  'c',
    83  		CP1: f32.Point{x1, y1},
    84  		CP2: f32.Point{x3, y3},
    85  		End: f32.Point{x3, y3},
    86  	}
    87  	p.currentPoint = e.End
    88  	p.Path = append(p.Path, e)
    89  }
    90  
    91  // QuadraticCurveTo adds a quadratic Bezier curve to the path. It ends at
    92  // (x2, y2) and has (x1, y1) for its control point.
    93  func (p *PathBuilder) QuadraticCurveTo(x1, y1, x2, y2 float32) {
    94  	// Convert the quadratic curve to a cubic one.
    95  	c1x := p.currentPoint.X + (x1-p.currentPoint.X)*2/3
    96  	c1y := p.currentPoint.Y + (y1-p.currentPoint.Y)*2/3
    97  	c2x := x2 + (x1-x2)*2/3
    98  	c2y := y2 + (y1-y2)*2/3
    99  	p.CurveTo(c1x, c1y, c2x, c2y, x2, y2)
   100  }
   101  
   102  // ClosePath closes the path, ensuring that it ends at the same point where it
   103  // began.
   104  func (p *PathBuilder) ClosePath() {
   105  	if len(p.Path) == 0 || p.Path[len(p.Path)-1].Op == 'h' {
   106  		return
   107  	}
   108  	p.Path = append(p.Path, PathElement{Op: 'h'})
   109  	p.currentPoint = p.lastMoveTo
   110  }
   111  
   112  // Rectangle creates a new subpath containing a rectangle of the specified
   113  // dimensions.
   114  func (p *PathBuilder) Rectangle(x, y, width, height float32) {
   115  	p.MoveTo(x, y)
   116  	p.LineTo(x+width, y)
   117  	p.LineTo(x+width, y+height)
   118  	p.LineTo(x, y+height)
   119  	p.ClosePath()
   120  }
   121  
   122  func toPathSpec(ops *op.Ops, p []PathElement, alwaysClose bool) clip.PathSpec {
   123  	var path clip.Path
   124  	path.Begin(ops)
   125  	closed := true
   126  
   127  	for _, e := range p {
   128  		switch e.Op {
   129  		case 'm':
   130  			if alwaysClose && !closed {
   131  				path.Close()
   132  			}
   133  			path.MoveTo(e.End)
   134  			closed = false
   135  
   136  		case 'l':
   137  			path.LineTo(e.End)
   138  			closed = false
   139  
   140  		case 'c':
   141  			path.CubeTo(e.CP1, e.CP2, e.End)
   142  			closed = false
   143  
   144  		case 'h':
   145  			path.Close()
   146  			closed = true
   147  		}
   148  	}
   149  
   150  	if alwaysClose && !closed {
   151  		path.Close()
   152  	}
   153  
   154  	return path.End()
   155  }
   156  
   157  func transformPath(p []PathElement, matrix f32.Affine2D) []PathElement {
   158  	result := make([]PathElement, len(p))
   159  	for i, e := range p {
   160  		result[i] = PathElement{
   161  			Op:  e.Op,
   162  			CP1: matrix.Transform(e.CP1),
   163  			CP2: matrix.Transform(e.CP2),
   164  			End: matrix.Transform(e.End),
   165  		}
   166  	}
   167  	return result
   168  }