1
0
mirror of https://github.com/taigrr/gopher-os synced 2025-01-18 04:43:13 -08:00

Define FontSetter interface, the Font type and helper methods

The helper methods provide support for selecting the best-fit font for
the console dimensions and for looking up a font by name.
This commit is contained in:
Achilleas Anagnostopoulos 2017-07-09 07:54:33 +01:00
parent 6195f3fc3b
commit 952d0bf4a5
3 changed files with 168 additions and 1 deletions

View File

@ -1,6 +1,9 @@
package console
import "image/color"
import (
"gopheros/device/video/console/font"
"image/color"
)
// ScrollDir defines a scroll direction.
type ScrollDir uint8
@ -43,3 +46,11 @@ type Device interface {
// supported colors should be a no-op.
SetPaletteColor(uint8, color.RGBA)
}
// FontSetter is an interface implemented by console devices that
// support loadable bitmap fonts.
//
// SetFont selects a bitmap font to be used by the console.
type FontSetter interface {
SetFont(*font.Font)
}

View File

@ -0,0 +1,97 @@
package font
var (
// The list of available fonts.
availableFonts []*Font
)
// Font describes a bitmap font that can be used by a console device.
type Font struct {
// The name of the font
Name string
// The width of each glyph in pixels.
GlyphWidth uint32
// The height of each glyph in pixels.
GlyphHeight uint32
// The recommended console resolution for this font.
RecommendedWidth uint32
RecommendedHeight uint32
// Font priority (lower is better). When auto-detecting a font to use, the font with
// the lowest priority will be preferred
Priority uint32
// The number of bytes describing a row in a glyph.
BytesPerRow uint32
// The font bitmap. Each character consists of BytesPerRow * Height
// bytes where each bit indicates whether a pixel should be set to the
// foreground or the background color.
Data []byte
}
// FindByName looks up a font instance by name. If the font is not found then
// the function returns nil.
func FindByName(name string) *Font {
for _, f := range availableFonts {
if f.Name == name {
return f
}
}
return nil
}
// BestFit returns the best font from the available font list given the
// specified console dimensions. If multiple fonts match the dimension criteria
// then their priority attribute is used to select one.
//
// The algorithm for selecting the best font is the following:
// For each font:
// - calculate the sum of abs differences between the font recommended dimension
// and the console dimensions.
// - if the font score is lower than the current best font's score then the
// font becomes the new best font.
// - if the font score is equal to the current best font's score then the
// font with the lowest priority becomes the new best font.
func BestFit(consoleWidth, consoleHeight uint32) *Font {
var (
best *Font
bestDelta uint32
absDeltaW, absDeltaH, absDelta uint32
)
for _, f := range availableFonts {
if f.RecommendedWidth > consoleWidth {
absDeltaW = f.RecommendedWidth - consoleWidth
} else {
absDeltaW = consoleWidth - f.RecommendedWidth
}
if f.RecommendedHeight > consoleHeight {
absDeltaH = f.RecommendedHeight - consoleHeight
} else {
absDeltaH = consoleHeight - f.RecommendedHeight
}
absDelta = absDeltaW + absDeltaH
if best == nil {
best = f
bestDelta = absDelta
continue
}
if best.Priority < f.Priority || absDelta > bestDelta {
continue
}
best = f
bestDelta = absDelta
}
return best
}

View File

@ -0,0 +1,59 @@
package font
import "testing"
func TestFindByName(t *testing.T) {
defer func(origList []*Font) {
availableFonts = origList
}(availableFonts)
availableFonts = []*Font{
&Font{Name: "foo"},
&Font{Name: "bar"},
}
exp := availableFonts[1]
if got := FindByName("bar"); got != exp {
t.Fatalf("expected to get font: %v; got %v", exp, got)
}
if got := FindByName("not-existing-font"); got != nil {
t.Fatalf("expected to get nil for a font that does not exist; got %v", got)
}
}
func TestBestFit(t *testing.T) {
defer func(origList []*Font) {
availableFonts = origList
}(availableFonts)
availableFonts = []*Font{
&Font{Name: "retina1", RecommendedWidth: 2560, RecommendedHeight: 1600, Priority: 2},
&Font{Name: "retina2", RecommendedWidth: 2560, RecommendedHeight: 1600, Priority: 1},
&Font{Name: "default", RecommendedWidth: 800, RecommendedHeight: 600, Priority: 0},
&Font{Name: "standard", RecommendedWidth: 1024, RecommendedHeight: 768, Priority: 0},
}
specs := []struct {
consW, consH uint32
expName string
}{
{320, 200, "default"},
{800, 600, "default"},
{1024, 768, "standard"},
{3000, 3000, "retina2"},
{2500, 600, "retina2"},
}
for specIndex, spec := range specs {
got := BestFit(spec.consW, spec.consH)
if got == nil {
t.Errorf("[spec %d] unable to find a font", specIndex)
continue
}
if got.Name != spec.expName {
t.Errorf("[spec %d] expected to get font %q; got %q", specIndex, spec.expName, got.Name)
}
}
}