go-text / typesetting

High quality text shaping in pure Go.
Other
88 stars 11 forks source link

bitmap data is always the same for any font sizes #160

Closed hajimehoshi closed 2 months ago

hajimehoshi commented 2 months ago

Apparently, even if a font face embeds bitmap data for various sizes, embed bitmap glyphs don't change for any font sizes.

I used Sazanami-Gothic font that should embed various bitmap glyphs for various sizes.

I tested this program:

main.go:

package main

import (
    "bufio"
    "bytes"
    _ "embed"
    "flag"
    "image"
    "image/draw"
    "image/png"
    "os"

    "github.com/go-text/typesetting/font"
    "github.com/go-text/typesetting/language"
    "github.com/go-text/typesetting/opentype/api"
    "github.com/go-text/typesetting/shaping"
    "golang.org/x/image/math/fixed"
)

//go:embed sazanami-gothic.ttf
var sazanamiGothic []byte

type singleFontmap struct {
    face font.Face
}

func (s *singleFontmap) ResolveFace(r rune) font.Face {
    return s.face
}

func render(dst draw.Image, size int, y int, text string) {
    f, err := font.ParseTTF(bytes.NewReader(sazanamiGothic))
    if err != nil {
        panic(err)
    }
    script, err := language.ParseScript("jpan")
    if err != nil {
        panic(err)
    }
    str := []rune(text)
    input := shaping.Input{
        Text:     str,
        RunStart: 0,
        RunEnd:   len(str),
        Face:     f,
        Size:     fixed.I(size),
        Script:   script,
        Language: language.NewLanguage("jp"),
    }

    var segmenter shaping.Segmenter
    inputs := segmenter.Split(input, &singleFontmap{face: f})

    var x int
    for _, input := range inputs {
        out := (&shaping.HarfbuzzShaper{}).Shape(input)
        (shaping.Line{out}).AdjustBaselines()
        for _, g := range out.Glyphs {
            data, ok := f.GlyphData(g.GlyphID).(api.GlyphBitmap)
            if !ok {
                continue
            }
            if data.Format != api.BlackAndWhite {
                continue
            }
            img := image.NewAlpha(image.Rect(0, 0, data.Width, data.Height))
            for j := 0; j < data.Height; j++ {
                for i := 0; i < data.Width; i++ {
                    idx := j*data.Width + i
                    if data.Data[idx/8]&(1<<(7-idx%8)) != 0 {
                        img.Pix[j*img.Stride+i] = 0xff
                    }
                }
            }
            // The origin position calculation is wrong, but it's just a demo.
            r := dst.Bounds()
            r = r.Add(image.Pt(x, y))
            draw.Draw(dst, r, img, image.Point{}, draw.Over)
            x += size
        }
    }
}

func main() {
    flag.Parse()

    dst := image.NewRGBA(image.Rect(0, 0, 640, 480))
    draw.Draw(dst, dst.Bounds(), image.Black, image.Point{}, draw.Src)

    for i, s := range []int{8, 9, 10, 11, 12, 13, 14} {
        render(dst, s, i*20, "ABC")
    }

    f, err := os.Create("output.png")
    if err != nil {
        panic(err)
    }
    defer f.Close()

    out := bufio.NewWriter(f)
    defer out.Flush()

    if err := png.Encode(out, dst); err != nil {
        panic(err)
    }
}

go.mod:

module foo

go 1.22

require (
    github.com/go-text/typesetting v0.1.1
    golang.org/x/image v0.15.0
)

require golang.org/x/text v0.14.0 // indirect

The result was:

image

benoitkugler commented 2 months ago

Hi !

The bitmap selection is based on the XPpem and YPpem of the Face struct. We do not set these fields, so you have to do it yourself (essentially because it is tied to the Input.Size, which is user controlled). You may also want to have a look at Font.BitmapSizes, which list the actual sizes provided by the font file.

hajimehoshi commented 2 months ago

I see, I'll try!

hajimehoshi commented 2 months ago

I've succeeded to fetch bitmap data. Thank you!