2023-11-12 09:47:31 +01:00
|
|
|
// Copyright 2023 The Ebitengine Authors
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package text
|
|
|
|
|
|
|
|
import (
|
|
|
|
"image"
|
|
|
|
"image/draw"
|
|
|
|
"math"
|
|
|
|
|
|
|
|
"github.com/go-text/typesetting/opentype/api"
|
|
|
|
"golang.org/x/image/math/fixed"
|
2023-11-19 18:50:27 +01:00
|
|
|
gvector "golang.org/x/image/vector"
|
2023-11-12 09:47:31 +01:00
|
|
|
|
|
|
|
"github.com/hajimehoshi/ebiten/v2"
|
2023-11-19 18:50:27 +01:00
|
|
|
"github.com/hajimehoshi/ebiten/v2/vector"
|
2023-11-12 09:47:31 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
func segmentsToBounds(segs []api.Segment) fixed.Rectangle26_6 {
|
|
|
|
if len(segs) == 0 {
|
|
|
|
return fixed.Rectangle26_6{}
|
|
|
|
}
|
|
|
|
|
|
|
|
minX := float32(math.Inf(1))
|
|
|
|
minY := float32(math.Inf(1))
|
|
|
|
maxX := float32(math.Inf(-1))
|
|
|
|
maxY := float32(math.Inf(-1))
|
|
|
|
|
|
|
|
for _, seg := range segs {
|
|
|
|
n := 1
|
|
|
|
switch seg.Op {
|
|
|
|
case api.SegmentOpQuadTo:
|
|
|
|
n = 2
|
|
|
|
case api.SegmentOpCubeTo:
|
|
|
|
n = 3
|
|
|
|
}
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
x := seg.Args[i].X
|
|
|
|
y := seg.Args[i].Y
|
|
|
|
if minX > x {
|
|
|
|
minX = x
|
|
|
|
}
|
|
|
|
if minY > y {
|
|
|
|
minY = y
|
|
|
|
}
|
|
|
|
if maxX < x {
|
|
|
|
maxX = x
|
|
|
|
}
|
|
|
|
if maxY < y {
|
|
|
|
maxY = y
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return fixed.Rectangle26_6{
|
|
|
|
Min: fixed.Point26_6{
|
|
|
|
X: float32ToFixed26_6(minX),
|
|
|
|
Y: float32ToFixed26_6(minY),
|
|
|
|
},
|
|
|
|
Max: fixed.Point26_6{
|
|
|
|
X: float32ToFixed26_6(maxX),
|
|
|
|
Y: float32ToFixed26_6(maxY),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func segmentsToImage(segs []api.Segment, subpixelOffset fixed.Point26_6, glyphBounds fixed.Rectangle26_6) *ebiten.Image {
|
|
|
|
if len(segs) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
w, h := (glyphBounds.Max.X - glyphBounds.Min.X).Ceil(), (glyphBounds.Max.Y - glyphBounds.Min.Y).Ceil()
|
|
|
|
if w == 0 || h == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-11-24 17:24:52 +01:00
|
|
|
// Add always 1 to the size.
|
|
|
|
// In theory, it is possible to determine whether +1 is necessary or not, but the calculation is pretty complicated.
|
|
|
|
w++
|
|
|
|
h++
|
2023-11-12 09:47:31 +01:00
|
|
|
|
|
|
|
biasX := fixed26_6ToFloat32(-glyphBounds.Min.X + subpixelOffset.X)
|
|
|
|
biasY := fixed26_6ToFloat32(-glyphBounds.Min.Y + subpixelOffset.Y)
|
|
|
|
|
2023-11-19 18:50:27 +01:00
|
|
|
rast := gvector.NewRasterizer(w, h)
|
2023-11-12 09:47:31 +01:00
|
|
|
rast.DrawOp = draw.Src
|
|
|
|
for _, seg := range segs {
|
|
|
|
switch seg.Op {
|
|
|
|
case api.SegmentOpMoveTo:
|
|
|
|
rast.MoveTo(seg.Args[0].X+biasX, seg.Args[0].Y+biasY)
|
|
|
|
case api.SegmentOpLineTo:
|
|
|
|
rast.LineTo(seg.Args[0].X+biasX, seg.Args[0].Y+biasY)
|
|
|
|
case api.SegmentOpQuadTo:
|
|
|
|
rast.QuadTo(
|
|
|
|
seg.Args[0].X+biasX, seg.Args[0].Y+biasY,
|
|
|
|
seg.Args[1].X+biasX, seg.Args[1].Y+biasY,
|
|
|
|
)
|
|
|
|
case api.SegmentOpCubeTo:
|
|
|
|
rast.CubeTo(
|
|
|
|
seg.Args[0].X+biasX, seg.Args[0].Y+biasY,
|
|
|
|
seg.Args[1].X+biasX, seg.Args[1].Y+biasY,
|
|
|
|
seg.Args[2].X+biasX, seg.Args[2].Y+biasY,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-12 06:43:26 +01:00
|
|
|
// Explicit closing is necessary especially for some OpenType fonts like
|
|
|
|
// NotoSansJP-VF.otf in https://github.com/notofonts/noto-cjk/releases/tag/Sans2.004.
|
|
|
|
// See also https://github.com/go-text/typesetting/issues/122.
|
|
|
|
rast.ClosePath()
|
|
|
|
|
2023-11-17 05:07:43 +01:00
|
|
|
dst := image.NewRGBA(image.Rect(0, 0, w, h))
|
2023-11-12 09:47:31 +01:00
|
|
|
rast.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
|
|
|
|
return ebiten.NewImageFromImage(dst)
|
|
|
|
}
|
2023-11-19 18:50:27 +01:00
|
|
|
|
|
|
|
func appendVectorPathFromSegments(path *vector.Path, segs []api.Segment, x, y float32) {
|
|
|
|
for _, seg := range segs {
|
|
|
|
switch seg.Op {
|
|
|
|
case api.SegmentOpMoveTo:
|
|
|
|
path.MoveTo(seg.Args[0].X+x, seg.Args[0].Y+y)
|
|
|
|
case api.SegmentOpLineTo:
|
|
|
|
path.LineTo(seg.Args[0].X+x, seg.Args[0].Y+y)
|
|
|
|
case api.SegmentOpQuadTo:
|
|
|
|
path.QuadTo(
|
|
|
|
seg.Args[0].X+x, seg.Args[0].Y+y,
|
|
|
|
seg.Args[1].X+x, seg.Args[1].Y+y,
|
|
|
|
)
|
|
|
|
case api.SegmentOpCubeTo:
|
|
|
|
path.CubicTo(
|
|
|
|
seg.Args[0].X+x, seg.Args[0].Y+y,
|
|
|
|
seg.Args[1].X+x, seg.Args[1].Y+y,
|
|
|
|
seg.Args[2].X+x, seg.Args[2].Y+y,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
path.Close()
|
|
|
|
}
|