tinygo-org / tinyfont

Text library for TinyGo displays
https://tinygo.org
BSD 3-Clause "New" or "Revised" License
50 stars 12 forks source link

The perfect bounding box problem #28

Closed rsjethani closed 1 year ago

rsjethani commented 3 years ago

While playing around with tingo+tinyfont on my adafruit clue, I wanted to create a bounding box(aka label in higher level general purpose gui programming) around a string so as to refresh the text by first re-filling the box with say black then writing string in say white. But while creating logic I noticed a few (possible) issues. To investigate further I created a small script which simply writes strings in a few selected fonts and also displays bounding box in 'red':

package main

import (
    "image/color"
    "machine"

    "tinygo.org/x/drivers/st7789"
    "tinygo.org/x/tinyfont"
    "tinygo.org/x/tinyfont/freemono"
    "tinygo.org/x/tinyfont/freesans"
    "tinygo.org/x/tinyfont/notosans"
    "tinygo.org/x/tinyfont/proggy"
)

func main() {
    machine.SPI1.Configure(machine.SPIConfig{
        Frequency: 8000000,
        SCK:       machine.TFT_SCK,
        SDO:       machine.TFT_SDO,
        SDI:       machine.TFT_SDO,
        Mode:      0,
    })

    display := st7789.New(machine.SPI1,
        machine.TFT_RESET,
        machine.TFT_DC,
        machine.TFT_CS,
        machine.TFT_LITE)

    display.Configure(st7789.Config{
        Width:        0,
        Height:       0,
        Rotation:     st7789.NO_ROTATION,
        RowOffset:    80,
        ColumnOffset: 0,
        FrameRate:    st7789.FRAMERATE_60,
        VSyncLines:   st7789.MAX_VSYNC_SCANLINES,
    })

    red := color.RGBA{100, 0, 0, 128}
    black := color.RGBA{0, 0, 0, 255}
    white := color.RGBA{255, 255, 255, 255}

    strings := map[string]*tinyfont.Font{
        "Notosans12pt":   &notosans.Notosans12pt,
        "TinySZ8pt7b":    &proggy.TinySZ8pt7b,
        "Regular12pt7bm": &freemono.Regular12pt7b,
        "Regular12pt7bs": &freesans.Regular12pt7b,
    }

    display.FillScreen(black)

    var y int16 = 10
    for str, font := range strings {
        y += int16(font.YAdvance) + 10
        _, w := tinyfont.LineWidth(font, str)
        display.FillRectangle(0, y-int16(font.YAdvance), int16(w), int16(font.YAdvance), red)
        tinyfont.WriteLine(&display, font, 0, y, str, white)
    }
}

See result in attached image

clue

Issues:

  1. Characters like p and g are sticking out of the bounding box:
  2. The bounding box for some fonts like the freesans.Regular12pt7b has lot of empty space above the characters.

Possible Reasons for issue 1:

  1. My logic of creating bounding box is wrong
  2. The Glyph values are not correct for characters like p and g

Possible Reasons for issue 2:

  1. My logic for creating bounding box is wrong.
  2. The YAdvance value for some fonts is way too high as compared to the largest YOffset in that font

PS1: I noticed that with some trial and error I can get a constant value to subtract from font.YAdvance to get prefect box with not too much extra space above the characters.

PS2: It is also possible that me as a newbie don't really understand how these fonts work and I might be trying to use them in a way they are not supposed to be used

sago35 commented 3 years ago

I don't understand font rendering very well. However, I think YAdvance is correct. YAdvance is supposed to be the amount to display the next line.

I think what's missing is the value of Bounding-Box.

The part that is displayed when you run the following code is the part that is close to the Bounding-Box. I think we need to add this Bounding-Box to the font side so that it can be displayed.

    var y int16 = 10
    for str, font := range strings {
        display.DrawFastHLine(0, 239, y, white)
        y += int16(font.YAdvance)
        display.DrawFastHLine(0, 239, y, white)

        _, w := tinyfont.LineWidth(font, str)
        display.FillRectangle(0, y-int16(font.YAdvance), int16(w), int16(font.YAdvance), red)
        tinyfont.WriteLine(display, font, 40, y, str, white)

        for i := 'a'; i <= 'z'; i++ {
            tinyfont.WriteLine(display, font, 5, y, fmt.Sprintf("%c", i), white)
        }
        for i := 'A'; i <= 'Z'; i++ {
            tinyfont.WriteLine(display, font, 5, y, fmt.Sprintf("%c", i), white)
        }

        y += 10
    }

image

sago35 commented 3 years ago

The program I wrote in the past handled the following. It is like preparing a large bmp and rewriting it.

However, I think it would be better to add a Bounding-Box to tinyfont.

https://github.com/sago35/tinygo-examples/blob/master/wioterminal/sample/main.go#L163-L164

deadprogram commented 3 years ago

Seems like a good idea!

sago35 commented 3 years ago

@rsjethani I defined a boundingbox as a test. I think it works fine.

image

Now I have written the following code.

    for str, font := range strings {
        y += int16(font.YAdvance) + 10
        _, w := tinyfont.LineWidth(font, str)
        display.FillRectangle(x, y-int16(font.YAdvance), int16(w), int16(font.YAdvance), red)
        display.DrawRectangle(x, y+int16(font.BBox[3]), int16(w), int16(font.BBox[1]), green) // !!! HERE !!!
        tinyfont.WriteLine(display, font, x, y, str, white)

    }
deadprogram commented 1 year ago

Closing as finished. Thanks everyone!