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 }