Open
Description
This is a remaining task from #124 (comment).
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!
Metadata
Metadata
Assignees
Labels
No labels