diff --git a/.gitignore b/.gitignore index 99e384c..f32752a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ coverage.txt go.sum *.png -*.pbf \ No newline at end of file +*.pbf + +./testdata/NotoSansCJKsc-Regular.ttf \ No newline at end of file diff --git a/README.md b/README.md index f92d2e4..2e680fc 100644 --- a/README.md +++ b/README.md @@ -7,4 +7,4 @@ Generates Signed Distance Field glyphsets from OpenType fonts -Inspired by https://github.com/mapbox/tiny-sdf +Inspired by diff --git a/sdf_builder.go b/sdf_builder.go index 6b3a175..177224a 100644 --- a/sdf_builder.go +++ b/sdf_builder.go @@ -41,11 +41,12 @@ type SDFBuilder struct { Font *truetype.Font Face font.Face SDFBuilderOpt + dotStartY int } func (b *SDFBuilder) Init() { if b.FontSize == 0 { - b.FontSize = 24 + b.FontSize = 64 } if b.Buffer == 0 { b.Buffer = 3 @@ -55,6 +56,15 @@ func (b *SDFBuilder) Init() { Size: b.FontSize, Hinting: font.HintingFull, }) + + metrics := b.Face.Metrics() + + // https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyph_metrics_2x.png + + fontDesignedHeight := metrics.Ascent.Floor() + metrics.Descent.Floor() + fixed := int(math.Round(float64(metrics.Height.Floor()-fontDesignedHeight)/2)) + 1 + + b.dotStartY = metrics.Height.Floor() + metrics.Descent.Floor() + fixed } func (b *SDFBuilder) Glyphs(min int, max int) *Glyphs { @@ -89,22 +99,24 @@ func (b *SDFBuilder) Glyph(x rune) *Glyph { return nil } - bounds, mask, maskp, advance, ok := b.Face.Glyph(fixed.P(0, 0), x) + bounds, mask, maskp, advance, ok := b.Face.Glyph(fixed.P(0, b.dotStartY), x) if !ok { return nil } - // https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyph_metrics_2x.png - - metrics := b.Face.Metrics() size := bounds.Size() - fixedAscent := metrics.Height.Floor() + metrics.Descent.Floor() - - id := uint32(x) width := uint32(size.X) height := uint32(size.Y) - top := -int32(bounds.Min.Y + fixedAscent) + + if width == 0 || height == 0 { + return nil + } + + buffer := int(b.Buffer) + id := uint32(x) + + top := -int32(bounds.Min.Y) left := int32(bounds.Min.X) a := uint32(advance.Floor()) @@ -117,13 +129,10 @@ func (b *SDFBuilder) Glyph(x rune) *Glyph { Advance: &a, } - buffer := int(b.Buffer) - w := int(*g.Width) + buffer*2 h := int(*g.Height) + buffer*2 dst := image.NewRGBA(image.Rect(0, 0, w, h)) - draw.DrawMask(dst, dst.Bounds(), &image.Uniform{image.Black}, image.ZP, mask, maskp.Sub(image.Pt(buffer, buffer)), draw.Over) g.Bitmap = CalcSDF(dst, 8, 0.25) diff --git a/sdf_builder_test.go b/sdf_builder_test.go index 4e2e6f3..aa2a473 100644 --- a/sdf_builder_test.go +++ b/sdf_builder_test.go @@ -11,36 +11,47 @@ import ( "github.com/stretchr/testify/require" ) -func TestSDFBuilder(t *testing.T) { - ttf, err := ioutil.ReadFile("./testdata/Lato-Regular.ttf") - require.NoError(t, err) +func builderFor(fontFamily string) *SDFBuilder { + ttf, err := ioutil.ReadFile("./testdata/" + fontFamily + ".ttf") + if err != nil { + panic(err) + } font, err := truetype.Parse(ttf) - require.NoError(t, err) - - builder := NewSDFBuilder(font, SDFBuilderOpt{FontSize: 24, Buffer: 3}) - - t.Run("#Glyph", func(t *testing.T) { - for i := 0; i < 255; i++ { - g := builder.Glyph(rune(i)) - if g != nil { - fmt.Printf("%s %d\n", string(*g.Id), *g.Top) - img := DrawGlyph(g, true) - SavePNG(fmt.Sprintf("./testdata/Lato/%d.png", i), img) - } + + if err != nil { + panic(err) + } + + return NewSDFBuilder(font, SDFBuilderOpt{FontSize: 24, Buffer: 3}) +} + +func TestSDFBuilder_Glyph(t *testing.T) { + builder := builderFor("NotoSans-Regular") + + for i := 0; i < 255; i++ { + g := builder.Glyph(rune(i)) + if g != nil { + fmt.Printf("%s %d\n", string(*g.Id), *g.Top) + img := DrawGlyph(g, true) + SavePNG(fmt.Sprintf("./testdata/NotoSans/%d.png", i), img) } - }) + } +} +func TestSDFBuilder(t *testing.T) { t.Run("#Glyphs", func(t *testing.T) { + builder := builderFor("NotoSans-Regular") + for _, rng := range [][]int{ {0, 255}, - //{20224, 20479}, - //{22784, 23039}, + {20224, 20479}, + {22784, 23039}, } { s := builder.Glyphs(rng[0], rng[1]) bytes, err := proto.Marshal(s) require.NoError(t, err) - ioutil.WriteFile(fmt.Sprintf("./testdata/Lato/%d-%d.pbf", rng[0], rng[1]), bytes, os.ModePerm) + ioutil.WriteFile(fmt.Sprintf("./testdata/NotoSans/%d-%d.pbf", rng[0], rng[1]), bytes, os.ModePerm) } }) } diff --git a/testdata/Lato-Regular.ttf b/testdata/Lato-Regular.ttf deleted file mode 100644 index 0f3d0f8..0000000 Binary files a/testdata/Lato-Regular.ttf and /dev/null differ diff --git a/testdata/NotoSans-Regular.ttf b/testdata/NotoSans-Regular.ttf new file mode 100644 index 0000000..9dd1019 Binary files /dev/null and b/testdata/NotoSans-Regular.ttf differ diff --git a/testdata/Lato/.gitkeep b/testdata/NotoSans/.gitkeep similarity index 100% rename from testdata/Lato/.gitkeep rename to testdata/NotoSans/.gitkeep diff --git a/testdata/index.html b/testdata/index.html index ccee216..9f0b5fe 100644 --- a/testdata/index.html +++ b/testdata/index.html @@ -18,12 +18,29 @@ bottom: 0; width: 100%; } + + #text { + position: absolute; + top: calc(50% + 7px); /* desent of Noto Sans is 7 */ + left: 50%; + line-height: 1; + font-size: 96px; + opacity: 0.5; + transform: translate3d(-50%, -100%, 0); + font-family: 'Noto Sans', sans-self; + white-space: nowrap; + }
+
+ 1你2好@gMah好il +