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  }