github.com/Kintar/etxt@v0.0.0-20221224033739-2fc69f000137/emask/outliner.go (about) 1 package emask 2 3 import "math" 4 5 type outliner struct { 6 x float64 7 y float64 8 thickness float64 // can't be modified throughout an outline 9 marginFactor uint8 10 Buffer buffer 11 CurveSegmenter curveSegmenter 12 13 segments [5]outlineSegment // the 0 and 1 are kept for closing 14 openSegmentCount int 15 } 16 17 // Sets the thickness of the outliner. Thickness can only be 18 // modified while not drawing. This means it can only be changed 19 // after MoveTo, ClosePath, CutPath or after initialization but 20 // before any LineTo, QuadTo or CubeTo commands are issued. 21 // 22 // This method will panic if any of the previous conditions are 23 // violated or if the passed thickness is zero, negative or bigger 24 // than 1024. 25 // 26 // The thickness will be quantized to a multiple of 1/1024 and 27 // the quantized value will be returned. 28 func (self *outliner) SetThickness(thickness float64) float64 { 29 if self.openSegmentCount > 0 { 30 panic("can't change thickness while drawing") 31 } 32 if thickness <= 0 { 33 panic("thickness <= 0 not allowed") 34 } 35 if thickness > 1024 { 36 panic("thickness > 1024 not allowed") 37 } 38 self.thickness = math.Round(thickness*1024) / 1024 39 if self.thickness == 0 { 40 self.thickness = 1 / 1024 41 } 42 return self.thickness 43 } 44 45 // TODO: probably needs to be quantized too, but I don't think everything 46 // 47 // will fit in the uint64 anyway. say 10 bits thickness, 10 bits 48 // margin factor, 8 bits signature, then curve segmenter needs 49 // 40 bits... so, 68 bits already... and thickness needs actually 50 // more like 20 bits. say 20 thick, 20 curve, 8 sig, 10 margin, 51 // 8 curve splits... that's still 66 bits. margin in 8 bits would 52 // be impossible I think. 18 for curve and 18 for thickness may be 53 // possible, but really tricky. Well, I can do it in 0.5 parts, up to 54 // 128. or 0.1 parts up to 25.6. that's not insane. to be seen if 55 // curve quantization in 20 bits is enough... 56 func (self *outliner) SetMarginFactor(factor float64) { 57 if factor < 1.0 { 58 panic("outliner margin factor must be >= 1.0") 59 } 60 if factor > 16.0 { 61 panic("outliner margin factor must be <= 16.0") 62 } 63 self.marginFactor = uint8(math.Round((factor - 1.0) * 16)) 64 } 65 66 func (self *outliner) MaxMargin() float64 { 67 return self.thickness * (float64(self.marginFactor) + 1.0) / 16 68 } 69 70 // Moves the current position to the given coordinates. 71 func (self *outliner) MoveTo(x, y float64) { 72 if self.openSegmentCount > 0 { 73 self.CutPath() // cut previous path if not closed yet 74 } 75 self.x = x 76 self.y = y 77 } 78 79 // Creates a straight line from the current position to the given 80 // target with the current thickness and moves the current position 81 // to the new one. 82 func (self *outliner) LineTo(x, y float64) { 83 if self.x == x && self.y == y { 84 return 85 } 86 defer func() { self.x, self.y = x, y }() 87 88 // compute new line ax + by + c = 0 coefficients 89 dx := x - self.x 90 dy := y - self.y 91 c := dx*self.y - dy*self.x 92 a, b, c := toLinearFormABC(self.x, self.y, x, y) 93 94 // if the new line goes in the same direction as the 95 // previous one, do not add it as a new line 96 if self.openSegmentCount > 0 { 97 prevSegment := &self.segments[self.openSegmentCount-1] 98 xdiv := prevSegment.a*b - a*prevSegment.b 99 if xdiv <= 0.00001 && xdiv >= -0.00001 { 100 prevSegment.fx = x 101 prevSegment.fy = y 102 103 start := self.segments[0] // check if closing outline 104 if start.ox == x && start.oy == y { 105 self.ClosePath() 106 } 107 return 108 } 109 } 110 111 // find parallels at the given distance that will delimit the new segment 112 c1, c2 := parallelsAtDist(a, b, c, self.thickness/2) 113 114 // create the segment 115 self.segments[self.openSegmentCount] = outlineSegment{ 116 ox: self.x, oy: self.y, fx: x, fy: y, 117 a: a, b: b, c1: c1, c2: c2, 118 } 119 self.openSegmentCount += 1 120 switch self.openSegmentCount { 121 case 3: // fill segment 1 122 self.segments[1].Fill(&self.Buffer, &self.segments[0], &self.segments[2]) 123 case 4: // fill segment 2 124 self.segments[2].Fill(&self.Buffer, &self.segments[1], &self.segments[3]) 125 case 5: // fill one segment and remove another old one 126 self.segments[3].Fill(&self.Buffer, &self.segments[2], &self.segments[4]) 127 self.segments[2] = self.segments[3] 128 self.segments[3] = self.segments[4] 129 self.openSegmentCount = 4 130 } 131 132 // see if we are closing the outline 133 if self.openSegmentCount > 1 { 134 start := self.segments[0] 135 if start.ox == x && start.oy == y { 136 self.ClosePath() 137 } 138 } 139 } 140 141 // Creates a boundary from the current position to the given target 142 // as a quadratic Bézier curve through the given control point and 143 // moves the current position to the new one. 144 func (self *outliner) QuadTo(ctrlX, ctrlY, fx, fy float64) { 145 self.CurveSegmenter.TraceQuad(self.LineTo, self.x, self.y, ctrlX, ctrlY, fx, fy) 146 } 147 148 // Creates a boundary from the current position to the given target 149 // as a cubic Bézier curve through the given control points and 150 // moves the current position to the new one. 151 func (self *outliner) CubeTo(cx1, cy1, cx2, cy2, fx, fy float64) { 152 self.CurveSegmenter.TraceCube(self.LineTo, self.x, self.y, cx1, cy1, cx2, cy2, fx, fy) 153 } 154 155 // Closes a path without tying back to the starting point. 156 func (self *outliner) CutPath() { 157 switch self.openSegmentCount { 158 case 0: 159 return // superfluous call 160 case 1: // cut both head and tail 161 self.segments[0].Cut(&self.Buffer) 162 default: // cut start tail, cut end head 163 sc := self.openSegmentCount 164 self.segments[0].CutTail(&self.Buffer, &self.segments[1]) 165 self.segments[sc-1].CutHead(&self.Buffer, &self.segments[sc-2]) 166 } 167 self.openSegmentCount = 0 168 } 169 170 // Closes a path tying back to the starting point (if possible). 171 func (self *outliner) ClosePath() { 172 sc := self.openSegmentCount 173 if sc <= 2 { 174 self.CutPath() 175 } else { 176 self.segments[0].Fill(&self.Buffer, &self.segments[sc-1], &self.segments[1]) 177 self.segments[sc-1].Fill(&self.Buffer, &self.segments[sc-2], &self.segments[0]) 178 } 179 self.openSegmentCount = 0 180 }