...

Source file src/gonum.org/v1/plot/text/text.go

Documentation: gonum.org/v1/plot/text

     1  // Copyright ©2021 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package text
     6  
     7  import (
     8  	"image/color"
     9  	"math"
    10  
    11  	"gonum.org/v1/plot/font"
    12  	"gonum.org/v1/plot/vg"
    13  )
    14  
    15  // Handler parses, formats and renders text.
    16  type Handler interface {
    17  	// Cache returns the cache of fonts used by the text handler.
    18  	Cache() *font.Cache
    19  
    20  	// Extents returns the Extents of a font.
    21  	Extents(fnt font.Font) font.Extents
    22  
    23  	// Lines splits a given block of text into separate lines.
    24  	Lines(txt string) []string
    25  
    26  	// Box returns the bounding box of the given non-multiline text where:
    27  	//  - width is the horizontal space from the origin.
    28  	//  - height is the vertical space above the baseline.
    29  	//  - depth is the vertical space below the baseline, a positive number.
    30  	Box(txt string, fnt font.Font) (width, height, depth vg.Length)
    31  
    32  	// Draw renders the given text with the provided style and position
    33  	// on the canvas.
    34  	Draw(c vg.Canvas, txt string, sty Style, pt vg.Point)
    35  }
    36  
    37  // XAlignment specifies text alignment in the X direction. Three preset
    38  // options are available, but an arbitrary alignment
    39  // can also be specified using XAlignment(desired number).
    40  type XAlignment float64
    41  
    42  const (
    43  	// XLeft aligns the left edge of the text with the specified location.
    44  	XLeft XAlignment = 0
    45  	// XCenter aligns the horizontal center of the text with the specified location.
    46  	XCenter XAlignment = -0.5
    47  	// XRight aligns the right edge of the text with the specified location.
    48  	XRight XAlignment = -1
    49  )
    50  
    51  // YAlignment specifies text alignment in the Y direction. Three preset
    52  // options are available, but an arbitrary alignment
    53  // can also be specified using YAlignment(desired number).
    54  type YAlignment float64
    55  
    56  const (
    57  	// YTop aligns the top of of the text with the specified location.
    58  	YTop YAlignment = -1
    59  	// YCenter aligns the vertical center of the text with the specified location.
    60  	YCenter YAlignment = -0.5
    61  	// YBottom aligns the bottom of the text with the specified location.
    62  	YBottom YAlignment = 0
    63  )
    64  
    65  // Position specifies the text position.
    66  const (
    67  	PosLeft   = -1
    68  	PosBottom = -1
    69  	PosCenter = 0
    70  	PosTop    = +1
    71  	PosRight  = +1
    72  )
    73  
    74  // Style describes what text will look like.
    75  type Style struct {
    76  	// Color is the text color.
    77  	Color color.Color
    78  
    79  	// Font is the font description.
    80  	Font font.Font
    81  
    82  	// Rotation is the text rotation in radians, performed around the axis
    83  	// defined by XAlign and YAlign.
    84  	Rotation float64
    85  
    86  	// XAlign and YAlign specify the alignment of the text.
    87  	XAlign XAlignment
    88  	YAlign YAlignment
    89  
    90  	// Handler parses and formats text according to a given
    91  	// dialect (Markdown, LaTeX, plain, ...)
    92  	// The default is a plain text handler.
    93  	Handler Handler
    94  }
    95  
    96  // FontExtents returns the extents of this Style's font.
    97  func (s Style) FontExtents() font.Extents {
    98  	return s.Handler.Extents(s.Font)
    99  }
   100  
   101  // Width returns the width of lines of text
   102  // when using the given font before any text rotation is applied.
   103  func (s Style) Width(txt string) (max vg.Length) {
   104  	w, _ := s.box(txt)
   105  	return w
   106  }
   107  
   108  // Height returns the height of the text when using
   109  // the given font before any text rotation is applied.
   110  func (s Style) Height(txt string) vg.Length {
   111  	_, h := s.box(txt)
   112  	return h
   113  }
   114  
   115  // box returns the bounding box of a possibly multi-line text.
   116  func (s Style) box(txt string) (w, h vg.Length) {
   117  	var (
   118  		lines   = s.Handler.Lines(txt)
   119  		e       = s.FontExtents()
   120  		linegap = (e.Height - e.Ascent - e.Descent)
   121  	)
   122  	for i, line := range lines {
   123  		ww, hh, dd := s.Handler.Box(line, s.Font)
   124  		if ww > w {
   125  			w = ww
   126  		}
   127  		h += hh + dd
   128  		if i > 0 {
   129  			h += linegap
   130  		}
   131  	}
   132  
   133  	return w, h
   134  }
   135  
   136  // Rectangle returns a rectangle giving the bounds of
   137  // this text assuming that it is drawn at (0, 0).
   138  func (s Style) Rectangle(txt string) vg.Rectangle {
   139  	e := s.Handler.Extents(s.Font)
   140  	w, h := s.box(txt)
   141  	desc := vg.Length(e.Height - e.Ascent) // descent + linegap
   142  	xoff := vg.Length(s.XAlign) * w
   143  	yoff := vg.Length(s.YAlign)*h - desc
   144  
   145  	// lower left corner
   146  	p1 := rotatePoint(s.Rotation, vg.Point{X: xoff, Y: yoff})
   147  	// upper left corner
   148  	p2 := rotatePoint(s.Rotation, vg.Point{X: xoff, Y: h + yoff})
   149  	// lower right corner
   150  	p3 := rotatePoint(s.Rotation, vg.Point{X: w + xoff, Y: yoff})
   151  	// upper right corner
   152  	p4 := rotatePoint(s.Rotation, vg.Point{X: w + xoff, Y: h + yoff})
   153  
   154  	return vg.Rectangle{
   155  		Max: vg.Point{
   156  			X: max(p1.X, p2.X, p3.X, p4.X),
   157  			Y: max(p1.Y, p2.Y, p3.Y, p4.Y),
   158  		},
   159  		Min: vg.Point{
   160  			X: min(p1.X, p2.X, p3.X, p4.X),
   161  			Y: min(p1.Y, p2.Y, p3.Y, p4.Y),
   162  		},
   163  	}
   164  }
   165  
   166  // rotatePoint applies rotation theta (in radians) about the origin to point p.
   167  func rotatePoint(theta float64, p vg.Point) vg.Point {
   168  	if theta == 0 {
   169  		return p
   170  	}
   171  	x := float64(p.X)
   172  	y := float64(p.Y)
   173  
   174  	sin, cos := math.Sincos(theta)
   175  
   176  	return vg.Point{
   177  		X: vg.Length(x*cos - y*sin),
   178  		Y: vg.Length(y*cos + x*sin),
   179  	}
   180  }
   181  
   182  func max(d ...vg.Length) vg.Length {
   183  	o := vg.Length(math.Inf(-1))
   184  	for _, dd := range d {
   185  		if dd > o {
   186  			o = dd
   187  		}
   188  	}
   189  	return o
   190  }
   191  
   192  func min(d ...vg.Length) vg.Length {
   193  	o := vg.Length(math.Inf(1))
   194  	for _, dd := range d {
   195  		if dd < o {
   196  			o = dd
   197  		}
   198  	}
   199  	return o
   200  }
   201  

View as plain text