github.com/Kintar/etxt@v0.0.0-20221224033739-2fc69f000137/emask/rasterizer.go (about) 1 package emask 2 3 import "image" 4 5 import "golang.org/x/image/math/fixed" 6 import "golang.org/x/image/font/sfnt" 7 8 // Rasterizer is an interface for 2D vector graphics rasterization to an 9 // alpha mask. This interface is offered as an open alternative to the 10 // concrete [golang.org/x/image/vector.Rasterizer] type (as used by 11 // [golang.org/x/image/font/opentype]), allowing anyone to target it and 12 // use its own rasterizer for text rendering. 13 // 14 // Mask rasterizers can't be used concurrently and must tolerate 15 // coordinates out of bounds. 16 type Rasterizer interface { 17 // Rasterizes the given outline to an alpha mask. The outline must be 18 // drawn at the given fractional position (always positive coords between 19 // 0 and 0:63 (= 0.984375)). 20 // 21 // Notice that rasterizers might create masks bigger than Segments.Bounds() 22 // to account for their own special effects, but they still can't affect 23 // glyph bounds or advances (see esizer.Sizer for that). 24 Rasterize(sfnt.Segments, fixed.Point26_6) (*image.Alpha, error) 25 26 // The cache signature returns an uint64 that can be used with glyph 27 // caches in order to tell rasterizers apart. When using multiple 28 // mask rasterizers with a single cache, you normally want to make sure 29 // that their cache signatures are different. As a practical standard, 30 // implementers of mask rasterizers are encouraged to leave at least 31 // the 8 highest bits to be configurable by users through the 32 // UserCfgCacheSignature interface. 33 CacheSignature() uint64 34 35 // Sets the function to be called when the Rasterizer configuration 36 // or cache signature changes. This is a reserved function that only 37 // a Renderer should call internally in order to connect its cache 38 // handler to the rasterizer changes. 39 SetOnChangeFunc(func(Rasterizer)) 40 41 // If anyone needs the following methods, let me know and we 42 // can consider them... 43 //NotifyFontChange(*sfnt.Font) 44 //NotifySizeChange(fixed.Int26_6) 45 } 46 47 // See [Rasterizer]'s CacheSignature() documentation. 48 type UserCfgCacheSignature interface { 49 // Sets the highest byte of 'signature' to the given value, like: 50 // signature = (signature & 0x00FFFFFFFFFFFFFF) | (uint64(value) << 56) 51 SetHighByte(value uint8) 52 } 53 54 // Maybe I could export this, but it doesn't feel that relevant. 55 type vectorTracer interface { 56 // Move to the given coordinate. 57 MoveTo(fixed.Point26_6) 58 59 // Create a segment to the given coordinate. 60 LineTo(fixed.Point26_6) 61 62 // Conic Bézier curve (also called quadratic). The first parameter 63 // is the control coordinate, and the second one the final target. 64 QuadTo(fixed.Point26_6, fixed.Point26_6) 65 66 // Cubic Bézier curve. The first two parameters are the control 67 // coordinates, and the third one is the final target. 68 CubeTo(fixed.Point26_6, fixed.Point26_6, fixed.Point26_6) 69 } 70 71 // A low level method to rasterize glyph masks. 72 // 73 // Returned masks have their coordinates adjusted so the mask is drawn at 74 // dot origin (0, 0) + the given fractional position by default. To draw it at 75 // a specific dot with a matching fractional position, translate the mask by 76 // dot.X.Floor() and dot.Y.Floor(). If you don't want to adjust the fractional 77 // pixel position, you can call Rasterize with a zero-value fixed.Point26_6{}. 78 // 79 // The given drawing coordinate can be your current drawing dot, but as 80 // indicated above, only its fractional part will be considered. 81 // 82 // The image returned will be nil if the segments are empty or do 83 // not include any active lines or curves (e.g.: space glyphs). 84 func Rasterize(outline sfnt.Segments, rasterizer Rasterizer, dot fixed.Point26_6) (*image.Alpha, error) { 85 // return nil if the outline don't include lines or curves 86 somethingToDraw := false 87 for _, segment := range outline { 88 if segment.Op != sfnt.SegmentOpMoveTo { 89 somethingToDraw = true 90 break 91 } 92 } 93 if !somethingToDraw { 94 return nil, nil 95 } 96 97 // obtain the fractional part of the coordinate 98 // (always positive, between 0 and 0:63 [0.984375]) 99 fract := fixed.Point26_6{ 100 X: dot.X & 0x0000003F, 101 Y: dot.Y & 0x0000003F, 102 } 103 104 // rasterize the glyph outline 105 return rasterizer.Rasterize(outline, fract) 106 } 107 108 // Calls MoveTo(), LineTo(), QuadTo() and CubeTo() methods on the 109 // tracer, as corresponding, for each segment in the glyph outline. 110 // This could also be placed on emask.Rasterize, but I decided 111 // to keep it separate and pass the outline directly to the rasterizer 112 // instead, as some advanced use-cases might benefit from having the 113 // segments. Feel free to copy-paste if writing your own glyph mask 114 // rasterizer. 115 func processOutline(tracer vectorTracer, outline sfnt.Segments) { 116 for _, segment := range outline { 117 switch segment.Op { 118 case sfnt.SegmentOpMoveTo: 119 tracer.MoveTo(segment.Args[0]) 120 case sfnt.SegmentOpLineTo: 121 tracer.LineTo(segment.Args[0]) 122 case sfnt.SegmentOpQuadTo: 123 tracer.QuadTo(segment.Args[0], segment.Args[1]) 124 case sfnt.SegmentOpCubeTo: 125 tracer.CubeTo(segment.Args[0], segment.Args[1], segment.Args[2]) 126 default: 127 panic("unexpected segment.Op case") 128 } 129 } 130 }