Skip to content

Commit

Permalink
Added a basic acceptance test for the font generator
Browse files Browse the repository at this point in the history
  • Loading branch information
davesmith00000 committed Apr 22, 2024
1 parent c252d1c commit c398f88
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import indigoplugin.generators.AssetListing
import indigoplugin.generators.ConfigGen
import indigoplugin.generators.EmbedData
import indigoplugin.generators.EmbedAseprite
import indigoplugin.generators.FontGen

/** Assists with setting up source code generators for Indigo projects
*
Expand Down Expand Up @@ -273,6 +274,30 @@ final case class IndigoGenerators(fullyQualifiedPackageName: String, sources: Se
)
)

/** Used to generate a rendered font sheet and `FontInfo` instance based on a supplied font source file.
*
* @param moduleName
* The name for the Scala module, e.g. 'MyModule' would be `object MyModule {}`
* @param font
* The path to the font file, e.g. a TrueType *.ttf file
* @param fontOptions
* Parameters for the font, such as its identifier (font key), size, and anti-aliasing.
*/
def embedFont(
moduleName: String,
font: os.Path,
fontOptions: FontOptions
): IndigoGenerators =
this.copy(
sources = sources :+
FontGen.generate(
moduleName,
fullyQualifiedPackageName,
font,
fontOptions
)
)

/** Used to embed CSV (Comma Separated Value) data, usage: embedCSV.asEnum(moduleName, filePath), or
* embedCSV.asMap(moduleName, filePath)
*/
Expand Down
180 changes: 157 additions & 23 deletions indigo-plugin/indigo-plugin/src/indigoplugin/generators/FontGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ object FontGen {
def generate(
moduleName: String,
fullyQualifiedPackage: String,
fontFilePath: os.Path,
fontOptions: FontOptions
): os.Path => Seq[os.Path] = outDir => {

Expand All @@ -16,7 +17,7 @@ object FontGen {

val file = wd / s"$moduleName.scala"

val helper = FontAWTHelper.makeHelper(fontOptions.fontFilePath.toIO, fontOptions.fontSize)
val helper = FontAWTHelper.makeHelper(fontFilePath.toIO, fontOptions.fontSize)

// Process into laid out details
val charDetails =
Expand All @@ -27,7 +28,7 @@ object FontGen {
CharDetail(c, 0, 0, w, h, a)
},
helper.getBaselineOffset,
fontOptions.charactersPerLine
fontOptions.maxCharactersPerLine
)

// Write out FontInfo
Expand All @@ -43,7 +44,7 @@ object FontGen {
os.write.over(file, fontInfo)

// Write out font image
val outImageFileName = sanitiseName(fontOptions.fontKey, "png")
val outImageFileName = sanitiseName(fontOptions.fontKey, "png") + ".png"

helper.drawFontSheet((wd / outImageFileName).toIO, charDetails, sheetWidth, sheetHeight, fontOptions)

Expand Down Expand Up @@ -111,8 +112,10 @@ object FontGen {

s"""package $fullyQualifiedPackage
|
|import indigo.*
|
|// DO NOT EDIT: Generated by Indigo.
|object $moduleName:
|object $moduleName {
|
| val fontKey: FontKey = FontKey("$name")
|
Expand All @@ -128,6 +131,7 @@ object FontGen {
|$charString
| )
| )
|
|}
|""".stripMargin
}
Expand All @@ -146,25 +150,6 @@ object FontGen {

}

final case class CharDetail(char: Char, x: Int, y: Int, width: Int, height: Int, ascent: Int)

final case class FontOptions(
fontKey: String,
fontFilePath: os.Path,
fontSize: Int,
color: FontColor,
antiAlias: Boolean,
defaultChar: Char,
chars: List[Char],
charactersPerLine: Int
)

final case class FontColor(red: Int, green: Int, blue: Int) {

def toColor: java.awt.Color =
new java.awt.Color(red, green, blue)
}

object FontAWTHelper {

import java.awt._
Expand Down Expand Up @@ -243,3 +228,152 @@ object FontAWTHelper {
}

}

final case class CharDetail(char: Char, x: Int, y: Int, width: Int, height: Int, ascent: Int)

final case class FontOptions(
fontKey: String,
fontSize: Int,
chars: List[Char],
color: RGB,
defaultChar: Char,
antiAlias: Boolean,
maxCharactersPerLine: Int
) {

def withFontKey(newFontKey: String): FontOptions =
this.copy(fontKey = newFontKey)

def withFontSize(newFontSize: Int): FontOptions =
this.copy(fontSize = newFontSize)

def withChars(newChars: List[Char]): FontOptions =
this.copy(chars = newChars)

def withColor(newColor: RGB): FontOptions =
this.copy(color = newColor)

def withDefaultChar(newDefaultChar: Char): FontOptions =
this.copy(defaultChar = newDefaultChar)

def withAntiAlias(newAntiAlias: Boolean): FontOptions =
this.copy(antiAlias = newAntiAlias)
def useAntiAliasing: FontOptions =
withAntiAlias(true)
def noAntiAliasing: FontOptions =
withAntiAlias(false)

def withMaxCharactersPerLine(newMaxCharactersPerLine: Int): FontOptions =
this.copy(maxCharactersPerLine = newMaxCharactersPerLine)

}

object FontOptions {

def apply(fontKey: String, fontSize: Int, chars: List[Char]): FontOptions =
FontOptions(fontKey, fontSize, chars, RGB.White, chars.headOption.getOrElse(' '), false, 16)

}

final case class RGB(r: Double, g: Double, b: Double) {

def toColor: java.awt.Color =
new java.awt.Color((r * 255).toInt, (g * 255).toInt, (b * 255).toInt)

def +(other: RGB): RGB =
RGB.combine(this, other)

def withRed(newRed: Double): RGB =
this.copy(r = newRed)

def withGreen(newGreen: Double): RGB =
this.copy(g = newGreen)

def withBlue(newBlue: Double): RGB =
this.copy(b = newBlue)

def mix(other: RGB, amount: Double): RGB = {
val mix = Math.min(1.0, Math.max(0.0, amount))
RGB(
(r * (1.0 - mix)) + (other.r * mix),
(g * (1.0 - mix)) + (other.g * mix),
(b * (1.0 - mix)) + (other.b * mix)
)
}
def mix(other: RGB): RGB =
mix(other, 0.5)

}

object RGB {

val Red: RGB = RGB(1, 0, 0)
val Green: RGB = RGB(0, 1, 0)
val Blue: RGB = RGB(0, 0, 1)
val Yellow: RGB = RGB(1, 1, 0)
val Magenta: RGB = RGB(1, 0, 1)
val Cyan: RGB = RGB(0, 1, 1)
val White: RGB = RGB(1, 1, 1)
val Black: RGB = RGB(0, 0, 0)
val Coral: RGB = fromHexString("#FF7F50")
val Crimson: RGB = fromHexString("#DC143C")
val DarkBlue: RGB = fromHexString("#00008B")
val Indigo: RGB = fromHexString("#4B0082")
val Olive: RGB = fromHexString("#808000")
val Orange: RGB = fromHexString("#FFA500")
val Pink: RGB = fromHexString("#FFC0CB")
val Plum: RGB = fromHexString("#DDA0DD")
val Purple: RGB = fromHexString("#A020F0")
val Salmon: RGB = fromHexString("#FA8072")
val SeaGreen: RGB = fromHexString("#2E8B57")
val Silver: RGB = fromHexString("#C0C0C0")
val SlateGray: RGB = fromHexString("#708090")
val SteelBlue: RGB = fromHexString("#4682B4")
val Teal: RGB = fromHexString("#008080")
val Thistle: RGB = fromHexString("#D8BFD8")
val Tomato: RGB = fromHexString("#FF6347")

val Normal: RGB = White
val Zero: RGB = RGB(0, 0, 0)

def combine(a: RGB, b: RGB): RGB =
(a, b) match {
case (RGB.White, x) =>
x
case (x, RGB.White) =>
x
case (x, y) =>
RGB(x.r + y.r, x.g + y.g, x.b + y.b)
}

def fromHexString(hex: String): RGB =
hex match {
case h if h.startsWith("0x") && h.length == 8 =>
fromColorInts(
Integer.parseInt(hex.substring(2, 4), 16),
Integer.parseInt(hex.substring(4, 6), 16),
Integer.parseInt(hex.substring(6, 8), 16)
)

case h if h.startsWith("#") && h.length == 7 =>
fromColorInts(
Integer.parseInt(hex.substring(1, 3), 16),
Integer.parseInt(hex.substring(3, 5), 16),
Integer.parseInt(hex.substring(5, 7), 16)
)

case h if h.length == 6 =>
fromColorInts(
Integer.parseInt(hex.substring(0, 2), 16),
Integer.parseInt(hex.substring(2, 4), 16),
Integer.parseInt(hex.substring(4), 16)
)

case _ =>
RGB.White
}

def fromColorInts(r: Int, g: Int, b: Int): RGB =
RGB((1.0 / 255) * r, (1.0 / 255) * g, (1.0 / 255) * b)

}
3 changes: 3 additions & 0 deletions indigo-plugin/indigo-plugin/src/indigoplugin/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ package object indigoplugin {

type DataType = generators.DataType

type FontOptions = generators.FontOptions
type RGB = generators.RGB

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class GeneratorAcceptanceTests extends munit.FunSuite {
val sourceCSV = os.pwd / "test-assets" / "data" / "stats.csv"
val sourceMD = os.pwd / "test-assets" / "data" / "stats.md"
val sourceColours = os.pwd / "test-assets" / "data" / "colours.txt"
val sourceFontTTF = os.pwd / "test-files" / "VCR_OSD_MONO_1.001.ttf"

val targetDir = os.pwd / "out" / "indigo-plugin-generator-acceptance-test-output"

Expand All @@ -21,6 +22,30 @@ class GeneratorAcceptanceTests extends munit.FunSuite {
override def beforeAll(): Unit = cleanUp()
override def beforeEach(context: BeforeEach): Unit = cleanUp()

test("Can generate font bitmap and FontInfo from TTF file") {

val options: FontOptions =
FontOptions("my font", 16, List(' ', 'a', 'b', 'c'))
.withMaxCharactersPerLine(16)
.noAntiAliasing

val files =
IndigoGenerators("com.example.test")
.embedFont("MyFont", sourceFontTTF, options)
.toSourcePaths(targetDir)

files.toList match {
case fontInfo :: png :: Nil =>
assert(os.exists(fontInfo))
assert(os.exists(png))

case _ =>
fail(
s"Unexpected number of files generated, got ${files.length} files:\n${files.map(_.toString()).mkString("\n")}"
)
}
}

test("Can generate an enum from a CSV file") {

val files =
Expand Down
Binary file added indigo-plugin/test-files/VCR_OSD_MONO_1.001.ttf
Binary file not shown.

0 comments on commit c398f88

Please sign in to comment.