Open hajimehoshi opened 5 months ago
This is a remaining task from https://github.com/go-text/typesetting/pull/124#issuecomment-1856227000.
I used NotoSansJP-VF.otf from https://github.com/notofonts/noto-cjk/releases/tag/Sans2.004 ("All Variables OTF/OTC)".
package main import ( "bufio" "bytes" _ "embed" "flag" "image" "image/draw" "image/png" "os" "strings" "github.com/go-text/typesetting/di" "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" "golang.org/x/image/vector" ) //go:embed NotoSansJP-VF.otf var notoSansJP []byte type singleFontmap struct { face font.Face } func (s *singleFontmap) ResolveFace(r rune) font.Face { return s.face } func render(dst draw.Image, origX, origY float32, text string) { f, err := font.ParseTTF(bytes.NewReader(notoSansJP)) 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), Direction: di.DirectionTTB, Face: f, Size: fixed.I(32), Script: script, Language: language.NewLanguage("jp"), } var segmenter shaping.Segmenter inputs := segmenter.Split(input, &singleFontmap{face: f}) for _, input := range inputs { out := (&shaping.HarfbuzzShaper{}).Shape(input) (shaping.Line{out}).AdjustBaselines() for _, g := range out.Glyphs { data := f.GlyphData(g.GlyphID).(api.GlyphOutline) if out.Direction.IsSideways() { data.Sideways(fixed26_6ToFloat32(-g.YOffset) / fixed26_6ToFloat32(out.Size) * float32(f.Upem())) } segs := data.Segments scaledSegs := make([]api.Segment, len(segs)) scale := fixed26_6ToFloat32(out.Size) / float32(f.Upem()) for i, seg := range segs { scaledSegs[i] = seg for j := range seg.Args { scaledSegs[i].Args[j].X *= scale scaledSegs[i].Args[j].Y *= -scale } } drawSegments(dst, origX+fixed26_6ToFloat32(g.XOffset), origY+fixed26_6ToFloat32(-g.YOffset), scaledSegs) origX += fixed26_6ToFloat32(g.XAdvance) origY += fixed26_6ToFloat32(-g.YAdvance) } } } func fixed26_6ToFloat32(x fixed.Int26_6) float32 { return float32(x) / (1 << 6) } func drawSegments(dst draw.Image, origX, origY float32, segs []api.Segment) { if len(segs) == 0 { return } rast := vector.NewRasterizer(dst.Bounds().Max.X, dst.Bounds().Max.Y) for _, seg := range segs { switch seg.Op { case api.SegmentOpMoveTo: rast.MoveTo(seg.Args[0].X+origX, seg.Args[0].Y+origY) case api.SegmentOpLineTo: rast.LineTo(seg.Args[0].X+origX, seg.Args[0].Y+origY) case api.SegmentOpQuadTo: rast.QuadTo( seg.Args[0].X+origX, seg.Args[0].Y+origY, seg.Args[1].X+origX, seg.Args[1].Y+origY, ) case api.SegmentOpCubeTo: rast.CubeTo( seg.Args[0].X+origX, seg.Args[0].Y+origY, seg.Args[1].X+origX, seg.Args[1].Y+origY, seg.Args[2].X+origX, seg.Args[2].Y+origY, ) } } rast.ClosePath() rast.DrawOp = draw.Over rast.Draw(dst, dst.Bounds(), image.Opaque, image.Point{}) } func main() { flag.Parse() dst := image.NewRGBA(image.Rect(0, 0, 640, 480)) draw.Draw(dst, dst.Bounds(), image.Black, image.Point{}, draw.Src) text := "あgo-textあ\nあisあ\nあawesomeあ" for i, line := range strings.Split(text, "\n") { render(dst, 400-float32(i)*40, 100, line) } 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) } }
module foo go 1.21.6 require ( github.com/go-text/typesetting v0.1.1-0.20231231232151-8d81c02dc157 golang.org/x/image v0.15.0 ) require golang.org/x/text v0.14.0 // indirect
The output is:
The baselines for "go-text", "is", and "awesome" are different for each line.
The expected result is what Chrome browser does:
<style> body { font-size: 32px; font-family: sans-serif; writing-mode: vertical-rl; } </style> <p>あgo-textあ<br> あisあ<br> あawersomeあ</p>
Thanks!
This is a remaining task from https://github.com/go-text/typesetting/pull/124#issuecomment-1856227000.
I used NotoSansJP-VF.otf from https://github.com/notofonts/noto-cjk/releases/tag/Sans2.004 ("All Variables OTF/OTC)".
The output is:
The baselines for "go-text", "is", and "awesome" are different for each line.
The expected result is what Chrome browser does:
Thanks!