github.com/Kintar/etxt@v0.0.0-20221224033739-2fc69f000137/examples/ebiten/faux_styles/main.go (about)

     1  package main
     2  
     3  import "os"
     4  import "log"
     5  import "fmt"
     6  import "math"
     7  import "image/color"
     8  
     9  import "github.com/hajimehoshi/ebiten/v2"
    10  import "github.com/Kintar/etxt"
    11  import "github.com/Kintar/etxt/emask"
    12  import "github.com/Kintar/etxt/esizer"
    13  
    14  const MainText = "The lazy programmer jumps\nover the brown codebase."
    15  
    16  type Game struct {
    17  	fauxRenderer *etxt.Renderer
    18  	helpRenderer *etxt.Renderer
    19  	italicAngle  float64 // native italic angle for the font
    20  
    21  	skewFactor   float64 // [-1, 1]
    22  	extraWidth   float64 // [0, 10]
    23  	sinceLastKey int
    24  	quantized    bool
    25  
    26  	usingCustomSizer bool
    27  	fauxSizer        esizer.Sizer
    28  	defaultSizer     esizer.Sizer
    29  }
    30  
    31  func (self *Game) Layout(w int, h int) (int, int) { return w, h }
    32  func (self *Game) Update() error {
    33  	self.sinceLastKey += 1
    34  
    35  	// left/right to modify skew (oblique)
    36  	if ebiten.IsKeyPressed(ebiten.KeyRight) {
    37  		if self.applyArrowSkewChange(+1) {
    38  			return nil
    39  		}
    40  	} else if ebiten.IsKeyPressed(ebiten.KeyLeft) {
    41  		if self.applyArrowSkewChange(-1) {
    42  			return nil
    43  		}
    44  	}
    45  
    46  	// up/down to modify width (bold)
    47  	if ebiten.IsKeyPressed(ebiten.KeyUp) {
    48  		if self.applyArrowBoldChange(+1) {
    49  			return nil
    50  		}
    51  	} else if ebiten.IsKeyPressed(ebiten.KeyDown) {
    52  		if self.applyArrowBoldChange(-1) {
    53  			return nil
    54  		}
    55  	}
    56  
    57  	if ebiten.IsKeyPressed(ebiten.KeyS) {
    58  		if self.sinceLastKey > 20 {
    59  			self.sinceLastKey = 0
    60  			if self.usingCustomSizer {
    61  				self.fauxRenderer.SetSizer(self.defaultSizer)
    62  			} else {
    63  				self.fauxRenderer.SetSizer(self.fauxSizer)
    64  			}
    65  			self.usingCustomSizer = !self.usingCustomSizer
    66  		}
    67  	}
    68  
    69  	// unitalicize
    70  	hasAngle := (self.italicAngle != 0) && (!math.IsNaN(self.italicAngle))
    71  	if hasAngle && ebiten.IsKeyPressed(ebiten.KeyU) {
    72  		if self.sinceLastKey > 20 {
    73  			// NOTE: I've tried with a few google fonts... and the angles are
    74  			//       not very reliable or accurate. I don't know what the heck
    75  			//       do they do to measure angles. Maybe they don't even measure
    76  			//       them, but if they are wrong by only 2 degrees consider
    77  			//       yourself lucky...
    78  			newSkew := self.italicAngle / 45.0
    79  			if newSkew != self.skewFactor {
    80  				self.skewFactor = newSkew
    81  				self.refreshSkew()
    82  				self.sinceLastKey = 0
    83  			}
    84  		}
    85  	}
    86  
    87  	// reset key (resets bold and oblique)
    88  	if ebiten.IsKeyPressed(ebiten.KeyR) {
    89  		if self.sinceLastKey > 20 {
    90  			self.sinceLastKey = 0
    91  			fauxRast := self.fauxRenderer.GetRasterizer()
    92  			fauxRast.(*emask.FauxRasterizer).SetSkewFactor(0)
    93  			fauxRast.(*emask.FauxRasterizer).SetExtraWidth(0)
    94  			self.extraWidth = 0
    95  			self.skewFactor = 0
    96  			return nil
    97  		}
    98  	}
    99  
   100  	// quantization switch
   101  	if ebiten.IsKeyPressed(ebiten.KeyQ) {
   102  		if self.sinceLastKey > 20 {
   103  			self.sinceLastKey = 0
   104  			if self.quantized {
   105  				self.fauxRenderer.SetQuantizerStep(1, 64)
   106  			} else {
   107  				self.fauxRenderer.SetQuantizerStep(64, 64)
   108  			}
   109  			self.quantized = !self.quantized
   110  			return nil
   111  		}
   112  	}
   113  
   114  	return nil
   115  }
   116  
   117  // logic to modify the skewFactor (for oblique text)
   118  func (self *Game) applyArrowSkewChange(sign int) bool {
   119  	if self.sinceLastKey < 10 {
   120  		return false
   121  	}
   122  
   123  	var skewAbsChange float64
   124  	if ebiten.IsKeyPressed(ebiten.KeyShift) {
   125  		skewAbsChange = 0.01
   126  	} else {
   127  		skewAbsChange = 0.03
   128  	}
   129  
   130  	var newSkew float64
   131  	if sign >= 0 {
   132  		newSkew = self.skewFactor + skewAbsChange
   133  		if newSkew > 1.0 {
   134  			newSkew = 1.0
   135  		}
   136  	} else {
   137  		newSkew = self.skewFactor - skewAbsChange
   138  		if newSkew < -1.0 {
   139  			newSkew = -1.0
   140  		}
   141  	}
   142  
   143  	if newSkew == self.skewFactor {
   144  		return false
   145  	}
   146  	self.skewFactor = newSkew
   147  	self.refreshSkew()
   148  	self.sinceLastKey = 0
   149  	return true
   150  }
   151  
   152  // logic to modify the extraWidth (for faux-bold text)
   153  func (self *Game) applyArrowBoldChange(sign int) bool {
   154  	if self.sinceLastKey < 20 {
   155  		return false
   156  	}
   157  
   158  	var boldAbsChange float64
   159  	if ebiten.IsKeyPressed(ebiten.KeyShift) {
   160  		boldAbsChange = 0.2
   161  	} else {
   162  		boldAbsChange = 0.5
   163  	}
   164  
   165  	var newBold float64
   166  	if sign >= 0 {
   167  		newBold = self.extraWidth + boldAbsChange
   168  		if newBold > 10.0 {
   169  			newBold = 10.0
   170  		}
   171  	} else {
   172  		newBold = self.extraWidth - boldAbsChange
   173  		if newBold < 0.0 {
   174  			newBold = 0.0
   175  		}
   176  	}
   177  
   178  	if newBold == self.extraWidth {
   179  		return false
   180  	}
   181  	self.extraWidth = newBold
   182  	self.refreshBold()
   183  	self.sinceLastKey = 0
   184  	return true
   185  }
   186  
   187  // Updates the rasterizer's skew factor.
   188  func (self *Game) refreshSkew() {
   189  	fauxRast := self.fauxRenderer.GetRasterizer()
   190  	fauxRast.(*emask.FauxRasterizer).SetSkewFactor(self.skewFactor)
   191  }
   192  
   193  // Updates the rasterizer's extraWidth.
   194  func (self *Game) refreshBold() {
   195  	fauxRast := self.fauxRenderer.GetRasterizer()
   196  	fauxRast.(*emask.FauxRasterizer).SetExtraWidth(self.extraWidth)
   197  }
   198  
   199  func (self *Game) Draw(screen *ebiten.Image) {
   200  	// dark background
   201  	screen.Fill(color.RGBA{0, 0, 0, 255})
   202  
   203  	// draw text
   204  	w, h := screen.Size()
   205  	self.fauxRenderer.SetTarget(screen)
   206  	self.fauxRenderer.Draw(MainText, w/2, h/3)
   207  
   208  	// draw helper info
   209  	skewInfo := fmt.Sprintf("skew %.2f (%.2f degrees) [right/left]", self.skewFactor, -self.skewFactor*45)
   210  	y := h - h/3 - int(float64(self.helpRenderer.GetLineAdvance().Ceil())*3)
   211  	self.helpRenderer.SetTarget(screen)
   212  
   213  	self.helpRenderer.Draw(skewInfo, w/2, y)
   214  	y += self.helpRenderer.GetLineAdvance().Ceil()
   215  	if math.IsNaN(self.italicAngle) {
   216  		self.helpRenderer.Draw("original italic angle unknown", w/2, y)
   217  	} else {
   218  		var info string
   219  		if self.italicAngle == 0 {
   220  			info = fmt.Sprintf("original italic angle %.2f degrees", self.italicAngle)
   221  		} else {
   222  			info = fmt.Sprintf("orig. it. angle %.2f degrees [U unitalicize]", self.italicAngle)
   223  		}
   224  		self.helpRenderer.Draw(info, w/2, y)
   225  	}
   226  	y += self.helpRenderer.GetLineAdvance().Ceil()
   227  	boldInfo := fmt.Sprintf("bold +%.1fpx [up/down]", self.extraWidth)
   228  	self.helpRenderer.Draw(boldInfo, w/2, y)
   229  	y += self.helpRenderer.GetLineAdvance().Ceil()
   230  	if self.quantized {
   231  		self.helpRenderer.Draw("quantization ON [press Q]", w/2, y)
   232  	} else {
   233  		self.helpRenderer.Draw("quantization OFF [press Q]", w/2, y)
   234  	}
   235  	y += self.helpRenderer.GetLineAdvance().Ceil()
   236  	if self.usingCustomSizer {
   237  		self.helpRenderer.Draw("faux sizer ON [press S]", w/2, y)
   238  	} else {
   239  		self.helpRenderer.Draw("faux sizer OFF [press S]", w/2, y)
   240  	}
   241  	y += self.helpRenderer.GetLineAdvance().Ceil()
   242  	self.helpRenderer.Draw("[press R to Reset]", w/2, y)
   243  }
   244  
   245  func main() {
   246  	// get font path
   247  	if len(os.Args) != 2 {
   248  		msg := "Usage: expects one argument with the path to the font to be used\n"
   249  		fmt.Fprint(os.Stderr, msg)
   250  		os.Exit(1)
   251  	}
   252  
   253  	// parse font
   254  	font, fontName, err := etxt.ParseFontFrom(os.Args[1])
   255  	if err != nil {
   256  		log.Fatal(err)
   257  	}
   258  	fmt.Printf("Font loaded: %s\n", fontName)
   259  
   260  	// create cache
   261  	cache := etxt.NewDefaultCache(1024 * 1024) // 1MB cache
   262  
   263  	// create and configure renderer
   264  	fauxRast := emask.FauxRasterizer{}
   265  	renderer := etxt.NewRenderer(&fauxRast)
   266  	defaultSizer := renderer.GetSizer()
   267  	customSizer := &esizer.AdvancePadSizer{}
   268  	renderer.SetCacheHandler(cache.NewHandler())
   269  	renderer.SetSizePx(36)
   270  	renderer.SetFont(font)
   271  	renderer.SetAlign(etxt.YCenter, etxt.XCenter)
   272  	renderer.SetColor(color.RGBA{255, 255, 255, 255})
   273  
   274  	// link custom sizer to fauxRast
   275  	fauxRast.SetAuxOnChangeFunc(func(*emask.FauxRasterizer) {
   276  		const SpFactor = 0.5 // between 0.5 and 1.0 is ok
   277  		customSizer.SetPaddingFloat(SpFactor * fauxRast.GetExtraWidth())
   278  	})
   279  
   280  	// create helper renderer for other text
   281  	helpRend := etxt.NewStdRenderer()
   282  	helpRend.SetCacheHandler(cache.NewHandler())
   283  	helpRend.SetSizePx(16)
   284  	helpRend.SetQuantizerStep(1, 64)
   285  	helpRend.SetFont(font)
   286  	helpRend.SetAlign(etxt.YCenter, etxt.XCenter)
   287  	helpRend.SetColor(color.RGBA{255, 255, 255, 150})
   288  
   289  	// get original italic angle information
   290  	postTable := font.PostTable()
   291  	italicAngle := math.NaN()
   292  	if postTable != nil {
   293  		italicAngle = postTable.ItalicAngle
   294  	}
   295  
   296  	// run the game
   297  	ebiten.SetWindowTitle("etxt/examples/ebiten/faux_styles")
   298  	ebiten.SetWindowSize(640, 480)
   299  	err = ebiten.RunGame(&Game{
   300  		fauxRenderer: renderer,
   301  		helpRenderer: helpRend,
   302  		quantized:    true,
   303  		fauxSizer:    customSizer,
   304  		defaultSizer: defaultSizer,
   305  		italicAngle:  italicAngle,
   306  	})
   307  	if err != nil {
   308  		log.Fatal(err)
   309  	}
   310  }