├── AUTHORS ├── CONTRIBUTORS ├── LICENSE ├── README ├── cmd └── print-glyph-points │ └── main.c ├── example ├── capjoin │ └── main.go ├── drawer │ └── main.go ├── freetype │ └── main.go ├── gamma │ └── main.go ├── genbasicfont │ └── main.go ├── raster │ └── main.go ├── round │ └── main.go └── truetype │ └── main.go ├── freetype.go ├── freetype_test.go ├── licenses ├── ftl.txt └── gpl.txt ├── raster ├── geom.go ├── paint.go ├── raster.go └── stroke.go ├── testdata ├── COPYING ├── README ├── luximr.ttf ├── luximr.ttx ├── luxirr.ttf ├── luxirr.ttx ├── luxisr-12pt-sans-hinting.txt ├── luxisr-12pt-with-hinting.txt ├── luxisr.ttf ├── luxisr.ttx └── make-other-hinting-txts.sh └── truetype ├── face.go ├── face_test.go ├── glyph.go ├── hint.go ├── hint_test.go ├── opcodes.go ├── truetype.go └── truetype_test.go /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of Freetype-Go authors for copyright purposes. 2 | # This file is distinct from the CONTRIBUTORS files. 3 | # See the latter for an explanation. 4 | # 5 | # Freetype-Go is derived from Freetype, which is written in C. The latter 6 | # is copyright 1996-2010 David Turner, Robert Wilhelm, and Werner Lemberg. 7 | 8 | # Names should be added to this file as 9 | # Name or Organization 10 | # The email address is not required for organizations. 11 | 12 | # Please keep the list sorted. 13 | 14 | Google Inc. 15 | Jeff R. Allen 16 | Maksim Kochkin 17 | Michael Fogleman 18 | Rémy Oudompheng 19 | Roger Peppe 20 | Steven Edwards 21 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # This is the official list of people who can contribute 2 | # (and typically have contributed) code to the Freetype-Go repository. 3 | # The AUTHORS file lists the copyright holders; this file 4 | # lists people. For example, Google employees are listed here 5 | # but not in AUTHORS, because Google holds the copyright. 6 | # 7 | # The submission process automatically checks to make sure 8 | # that people submitting code are listed in this file (by email address). 9 | # 10 | # Names should be added to this file only after verifying that 11 | # the individual or the individual's organization has agreed to 12 | # the appropriate Contributor License Agreement, found here: 13 | # 14 | # http://code.google.com/legal/individual-cla-v1.0.html 15 | # http://code.google.com/legal/corporate-cla-v1.0.html 16 | # 17 | # The agreement for individuals can be filled out on the web. 18 | # 19 | # When adding J Random Contributor's name to this file, 20 | # either J's name or J's organization's name should be 21 | # added to the AUTHORS file, depending on whether the 22 | # individual or corporate CLA was used. 23 | 24 | # Names should be added to this file like so: 25 | # Name 26 | 27 | # Please keep the list sorted. 28 | 29 | Andrew Gerrand 30 | Jeff R. Allen 31 | Maksim Kochkin 32 | Michael Fogleman 33 | Nigel Tao 34 | Rémy Oudompheng 35 | Rob Pike 36 | Roger Peppe 37 | Russ Cox 38 | Steven Edwards 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Use of the Freetype-Go software is subject to your choice of exactly one of 2 | the following two licenses: 3 | * The FreeType License, which is similar to the original BSD license with 4 | an advertising clause, or 5 | * The GNU General Public License (GPL), version 2 or later. 6 | 7 | The text of these licenses are available in the licenses/ftl.txt and the 8 | licenses/gpl.txt files respectively. They are also available at 9 | http://freetype.sourceforge.net/license.html 10 | 11 | The Luxi fonts in the testdata directory are licensed separately. See the 12 | testdata/COPYING file for details. 13 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | The Freetype font rasterizer in the Go programming language. 2 | 3 | To download and install from source: 4 | $ go get github.com/golang/freetype 5 | 6 | It is an incomplete port: 7 | * It only supports TrueType fonts, and not Type 1 fonts nor bitmap fonts. 8 | * It only supports the Unicode encoding. 9 | 10 | There are also some implementation differences: 11 | * It uses a 26.6 fixed point co-ordinate system everywhere internally, 12 | as opposed to the original Freetype's mix of 26.6 (or 10.6 for 16-bit 13 | systems) in some places, and 24.8 in the "smooth" rasterizer. 14 | 15 | Freetype-Go is derived from Freetype, which is written in C. Freetype is 16 | copyright 1996-2010 David Turner, Robert Wilhelm, and Werner Lemberg. 17 | Freetype-Go is copyright The Freetype-Go Authors, who are listed in the 18 | AUTHORS file. 19 | 20 | Unless otherwise noted, the Freetype-Go source files are distributed 21 | under the BSD-style license found in the LICENSE file. 22 | -------------------------------------------------------------------------------- /cmd/print-glyph-points/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | gcc main.c -I/usr/include/freetype2 -lfreetype && ./a.out 12 ../../testdata/luxisr.ttf with_hinting 3 | */ 4 | 5 | #include 6 | #include 7 | #include FT_FREETYPE_H 8 | 9 | void usage(char** argv) { 10 | fprintf(stderr, "usage: %s font_size font_file [with_hinting|sans_hinting]\n", argv[0]); 11 | } 12 | 13 | int main(int argc, char** argv) { 14 | FT_Error error; 15 | FT_Library library; 16 | FT_Face face; 17 | FT_Glyph_Metrics* m; 18 | FT_Outline* o; 19 | FT_Int major, minor, patch; 20 | int i, j, font_size, no_hinting; 21 | 22 | if (argc != 4) { 23 | usage(argv); 24 | return 1; 25 | } 26 | font_size = atoi(argv[1]); 27 | if (font_size <= 0) { 28 | fprintf(stderr, "invalid font_size\n"); 29 | usage(argv); 30 | return 1; 31 | } 32 | if (!strcmp(argv[3], "with_hinting")) { 33 | no_hinting = 0; 34 | } else if (!strcmp(argv[3], "sans_hinting")) { 35 | no_hinting = 1; 36 | } else { 37 | fprintf(stderr, "neither \"with_hinting\" nor \"sans_hinting\"\n"); 38 | usage(argv); 39 | return 1; 40 | }; 41 | error = FT_Init_FreeType(&library); 42 | if (error) { 43 | fprintf(stderr, "FT_Init_FreeType: error #%d\n", error); 44 | return 1; 45 | } 46 | FT_Library_Version(library, &major, &minor, &patch); 47 | printf("freetype version %d.%d.%d\n", major, minor, patch); 48 | error = FT_New_Face(library, argv[2], 0, &face); 49 | if (error) { 50 | fprintf(stderr, "FT_New_Face: error #%d\n", error); 51 | return 1; 52 | } 53 | error = FT_Set_Char_Size(face, 0, font_size*64, 0, 0); 54 | if (error) { 55 | fprintf(stderr, "FT_Set_Char_Size: error #%d\n", error); 56 | return 1; 57 | } 58 | for (i = 0; i < face->num_glyphs; i++) { 59 | error = FT_Load_Glyph(face, i, no_hinting ? FT_LOAD_NO_HINTING : FT_LOAD_DEFAULT); 60 | if (error) { 61 | fprintf(stderr, "FT_Load_Glyph: glyph %d: error #%d\n", i, error); 62 | return 1; 63 | } 64 | if (face->glyph->format != FT_GLYPH_FORMAT_OUTLINE) { 65 | fprintf(stderr, "glyph format for glyph %d is not FT_GLYPH_FORMAT_OUTLINE\n", i); 66 | return 1; 67 | } 68 | m = &face->glyph->metrics; 69 | /* Print what Go calls the AdvanceWidth, and then: XMin, YMin, XMax, YMax. */ 70 | printf("%ld %ld %ld %ld %ld;", 71 | m->horiAdvance, 72 | m->horiBearingX, 73 | m->horiBearingY - m->height, 74 | m->horiBearingX + m->width, 75 | m->horiBearingY); 76 | /* Print the glyph points. */ 77 | o = &face->glyph->outline; 78 | for (j = 0; j < o->n_points; j++) { 79 | if (j != 0) { 80 | printf(", "); 81 | } 82 | printf("%ld %ld %d", o->points[j].x, o->points[j].y, o->tags[j] & 0x01); 83 | } 84 | printf("\n"); 85 | } 86 | return 0; 87 | } 88 | -------------------------------------------------------------------------------- /example/capjoin/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | // +build example 7 | // 8 | // This build tag means that "go install github.com/golang/freetype/..." 9 | // doesn't install this example program. Use "go run main.go" to run it or "go 10 | // install -tags=example" to install it. 11 | 12 | package main 13 | 14 | import ( 15 | "bufio" 16 | "fmt" 17 | "image" 18 | "image/color" 19 | "image/draw" 20 | "image/png" 21 | "log" 22 | "os" 23 | 24 | "github.com/golang/freetype/raster" 25 | "golang.org/x/image/math/fixed" 26 | ) 27 | 28 | func main() { 29 | const ( 30 | w = 400 31 | h = 400 32 | ) 33 | r := raster.NewRasterizer(w, h) 34 | r.UseNonZeroWinding = true 35 | 36 | cjs := []struct { 37 | c raster.Capper 38 | j raster.Joiner 39 | }{ 40 | {raster.RoundCapper, raster.RoundJoiner}, 41 | {raster.ButtCapper, raster.BevelJoiner}, 42 | {raster.SquareCapper, raster.BevelJoiner}, 43 | } 44 | 45 | for i, cj := range cjs { 46 | var path raster.Path 47 | path.Start(fixed.P(30+100*i, 30+120*i)) 48 | path.Add1(fixed.P(180+100*i, 80+120*i)) 49 | path.Add1(fixed.P(50+100*i, 130+120*i)) 50 | raster.Stroke(r, path, fixed.I(20), cj.c, cj.j) 51 | } 52 | 53 | rgba := image.NewRGBA(image.Rect(0, 0, w, h)) 54 | draw.Draw(rgba, rgba.Bounds(), image.Black, image.Point{}, draw.Src) 55 | p := raster.NewRGBAPainter(rgba) 56 | p.SetColor(color.RGBA{0x7f, 0x7f, 0x7f, 0xff}) 57 | r.Rasterize(p) 58 | 59 | white := color.RGBA{0xff, 0xff, 0xff, 0xff} 60 | for i := range cjs { 61 | rgba.SetRGBA(30+100*i, 30+120*i, white) 62 | rgba.SetRGBA(180+100*i, 80+120*i, white) 63 | rgba.SetRGBA(50+100*i, 130+120*i, white) 64 | } 65 | 66 | // Save that RGBA image to disk. 67 | outFile, err := os.Create("out.png") 68 | if err != nil { 69 | log.Println(err) 70 | os.Exit(1) 71 | } 72 | defer outFile.Close() 73 | b := bufio.NewWriter(outFile) 74 | err = png.Encode(b, rgba) 75 | if err != nil { 76 | log.Println(err) 77 | os.Exit(1) 78 | } 79 | err = b.Flush() 80 | if err != nil { 81 | log.Println(err) 82 | os.Exit(1) 83 | } 84 | fmt.Println("Wrote out.png OK.") 85 | } 86 | -------------------------------------------------------------------------------- /example/drawer/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | // +build example 7 | // 8 | // This build tag means that "go install github.com/golang/freetype/..." 9 | // doesn't install this example program. Use "go run main.go" to run it or "go 10 | // install -tags=example" to install it. 11 | 12 | package main 13 | 14 | import ( 15 | "bufio" 16 | "flag" 17 | "fmt" 18 | "image" 19 | "image/color" 20 | "image/draw" 21 | "image/png" 22 | "io/ioutil" 23 | "log" 24 | "math" 25 | "os" 26 | 27 | "github.com/golang/freetype/truetype" 28 | "golang.org/x/image/font" 29 | "golang.org/x/image/math/fixed" 30 | ) 31 | 32 | var ( 33 | dpi = flag.Float64("dpi", 72, "screen resolution in Dots Per Inch") 34 | fontfile = flag.String("fontfile", "../../testdata/luxisr.ttf", "filename of the ttf font") 35 | hinting = flag.String("hinting", "none", "none | full") 36 | size = flag.Float64("size", 12, "font size in points") 37 | spacing = flag.Float64("spacing", 1.5, "line spacing (e.g. 2 means double spaced)") 38 | wonb = flag.Bool("whiteonblack", false, "white text on a black background") 39 | ) 40 | 41 | const title = "Jabberwocky" 42 | 43 | var text = []string{ 44 | "’Twas brillig, and the slithy toves", 45 | "Did gyre and gimble in the wabe;", 46 | "All mimsy were the borogoves,", 47 | "And the mome raths outgrabe.", 48 | "", 49 | "“Beware the Jabberwock, my son!", 50 | "The jaws that bite, the claws that catch!", 51 | "Beware the Jubjub bird, and shun", 52 | "The frumious Bandersnatch!”", 53 | "", 54 | "He took his vorpal sword in hand:", 55 | "Long time the manxome foe he sought—", 56 | "So rested he by the Tumtum tree,", 57 | "And stood awhile in thought.", 58 | "", 59 | "And as in uffish thought he stood,", 60 | "The Jabberwock, with eyes of flame,", 61 | "Came whiffling through the tulgey wood,", 62 | "And burbled as it came!", 63 | "", 64 | "One, two! One, two! and through and through", 65 | "The vorpal blade went snicker-snack!", 66 | "He left it dead, and with its head", 67 | "He went galumphing back.", 68 | "", 69 | "“And hast thou slain the Jabberwock?", 70 | "Come to my arms, my beamish boy!", 71 | "O frabjous day! Callooh! Callay!”", 72 | "He chortled in his joy.", 73 | "", 74 | "’Twas brillig, and the slithy toves", 75 | "Did gyre and gimble in the wabe;", 76 | "All mimsy were the borogoves,", 77 | "And the mome raths outgrabe.", 78 | } 79 | 80 | func main() { 81 | flag.Parse() 82 | 83 | // Read the font data. 84 | fontBytes, err := ioutil.ReadFile(*fontfile) 85 | if err != nil { 86 | log.Println(err) 87 | return 88 | } 89 | f, err := truetype.Parse(fontBytes) 90 | if err != nil { 91 | log.Println(err) 92 | return 93 | } 94 | 95 | // Draw the background and the guidelines. 96 | fg, bg := image.Black, image.White 97 | ruler := color.RGBA{0xdd, 0xdd, 0xdd, 0xff} 98 | if *wonb { 99 | fg, bg = image.White, image.Black 100 | ruler = color.RGBA{0x22, 0x22, 0x22, 0xff} 101 | } 102 | const imgW, imgH = 640, 480 103 | rgba := image.NewRGBA(image.Rect(0, 0, imgW, imgH)) 104 | draw.Draw(rgba, rgba.Bounds(), bg, image.ZP, draw.Src) 105 | for i := 0; i < 200; i++ { 106 | rgba.Set(10, 10+i, ruler) 107 | rgba.Set(10+i, 10, ruler) 108 | } 109 | 110 | // Draw the text. 111 | h := font.HintingNone 112 | switch *hinting { 113 | case "full": 114 | h = font.HintingFull 115 | } 116 | d := &font.Drawer{ 117 | Dst: rgba, 118 | Src: fg, 119 | Face: truetype.NewFace(f, &truetype.Options{ 120 | Size: *size, 121 | DPI: *dpi, 122 | Hinting: h, 123 | }), 124 | } 125 | y := 10 + int(math.Ceil(*size**dpi/72)) 126 | dy := int(math.Ceil(*size * *spacing * *dpi / 72)) 127 | d.Dot = fixed.Point26_6{ 128 | X: (fixed.I(imgW) - d.MeasureString(title)) / 2, 129 | Y: fixed.I(y), 130 | } 131 | d.DrawString(title) 132 | y += dy 133 | for _, s := range text { 134 | d.Dot = fixed.P(10, y) 135 | d.DrawString(s) 136 | y += dy 137 | } 138 | 139 | // Save that RGBA image to disk. 140 | outFile, err := os.Create("out.png") 141 | if err != nil { 142 | log.Println(err) 143 | os.Exit(1) 144 | } 145 | defer outFile.Close() 146 | b := bufio.NewWriter(outFile) 147 | err = png.Encode(b, rgba) 148 | if err != nil { 149 | log.Println(err) 150 | os.Exit(1) 151 | } 152 | err = b.Flush() 153 | if err != nil { 154 | log.Println(err) 155 | os.Exit(1) 156 | } 157 | fmt.Println("Wrote out.png OK.") 158 | } 159 | -------------------------------------------------------------------------------- /example/freetype/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | // +build example 7 | // 8 | // This build tag means that "go install github.com/golang/freetype/..." 9 | // doesn't install this example program. Use "go run main.go" to run it or "go 10 | // install -tags=example" to install it. 11 | 12 | package main 13 | 14 | import ( 15 | "bufio" 16 | "flag" 17 | "fmt" 18 | "image" 19 | "image/color" 20 | "image/draw" 21 | "image/png" 22 | "io/ioutil" 23 | "log" 24 | "os" 25 | 26 | "github.com/golang/freetype" 27 | "golang.org/x/image/font" 28 | ) 29 | 30 | var ( 31 | dpi = flag.Float64("dpi", 72, "screen resolution in Dots Per Inch") 32 | fontfile = flag.String("fontfile", "../../testdata/luxisr.ttf", "filename of the ttf font") 33 | hinting = flag.String("hinting", "none", "none | full") 34 | size = flag.Float64("size", 12, "font size in points") 35 | spacing = flag.Float64("spacing", 1.5, "line spacing (e.g. 2 means double spaced)") 36 | wonb = flag.Bool("whiteonblack", false, "white text on a black background") 37 | ) 38 | 39 | var text = []string{ 40 | "’Twas brillig, and the slithy toves", 41 | "Did gyre and gimble in the wabe;", 42 | "All mimsy were the borogoves,", 43 | "And the mome raths outgrabe.", 44 | "", 45 | "“Beware the Jabberwock, my son!", 46 | "The jaws that bite, the claws that catch!", 47 | "Beware the Jubjub bird, and shun", 48 | "The frumious Bandersnatch!”", 49 | "", 50 | "He took his vorpal sword in hand:", 51 | "Long time the manxome foe he sought—", 52 | "So rested he by the Tumtum tree,", 53 | "And stood awhile in thought.", 54 | "", 55 | "And as in uffish thought he stood,", 56 | "The Jabberwock, with eyes of flame,", 57 | "Came whiffling through the tulgey wood,", 58 | "And burbled as it came!", 59 | "", 60 | "One, two! One, two! and through and through", 61 | "The vorpal blade went snicker-snack!", 62 | "He left it dead, and with its head", 63 | "He went galumphing back.", 64 | "", 65 | "“And hast thou slain the Jabberwock?", 66 | "Come to my arms, my beamish boy!", 67 | "O frabjous day! Callooh! Callay!”", 68 | "He chortled in his joy.", 69 | "", 70 | "’Twas brillig, and the slithy toves", 71 | "Did gyre and gimble in the wabe;", 72 | "All mimsy were the borogoves,", 73 | "And the mome raths outgrabe.", 74 | } 75 | 76 | func main() { 77 | flag.Parse() 78 | 79 | // Read the font data. 80 | fontBytes, err := ioutil.ReadFile(*fontfile) 81 | if err != nil { 82 | log.Println(err) 83 | return 84 | } 85 | f, err := freetype.ParseFont(fontBytes) 86 | if err != nil { 87 | log.Println(err) 88 | return 89 | } 90 | 91 | // Initialize the context. 92 | fg, bg := image.Black, image.White 93 | ruler := color.RGBA{0xdd, 0xdd, 0xdd, 0xff} 94 | if *wonb { 95 | fg, bg = image.White, image.Black 96 | ruler = color.RGBA{0x22, 0x22, 0x22, 0xff} 97 | } 98 | rgba := image.NewRGBA(image.Rect(0, 0, 640, 480)) 99 | draw.Draw(rgba, rgba.Bounds(), bg, image.ZP, draw.Src) 100 | c := freetype.NewContext() 101 | c.SetDPI(*dpi) 102 | c.SetFont(f) 103 | c.SetFontSize(*size) 104 | c.SetClip(rgba.Bounds()) 105 | c.SetDst(rgba) 106 | c.SetSrc(fg) 107 | switch *hinting { 108 | default: 109 | c.SetHinting(font.HintingNone) 110 | case "full": 111 | c.SetHinting(font.HintingFull) 112 | } 113 | 114 | // Draw the guidelines. 115 | for i := 0; i < 200; i++ { 116 | rgba.Set(10, 10+i, ruler) 117 | rgba.Set(10+i, 10, ruler) 118 | } 119 | 120 | // Draw the text. 121 | pt := freetype.Pt(10, 10+int(c.PointToFixed(*size)>>6)) 122 | for _, s := range text { 123 | _, err = c.DrawString(s, pt) 124 | if err != nil { 125 | log.Println(err) 126 | return 127 | } 128 | pt.Y += c.PointToFixed(*size * *spacing) 129 | } 130 | 131 | // Save that RGBA image to disk. 132 | outFile, err := os.Create("out.png") 133 | if err != nil { 134 | log.Println(err) 135 | os.Exit(1) 136 | } 137 | defer outFile.Close() 138 | b := bufio.NewWriter(outFile) 139 | err = png.Encode(b, rgba) 140 | if err != nil { 141 | log.Println(err) 142 | os.Exit(1) 143 | } 144 | err = b.Flush() 145 | if err != nil { 146 | log.Println(err) 147 | os.Exit(1) 148 | } 149 | fmt.Println("Wrote out.png OK.") 150 | } 151 | -------------------------------------------------------------------------------- /example/gamma/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | // +build example 7 | // 8 | // This build tag means that "go install github.com/golang/freetype/..." 9 | // doesn't install this example program. Use "go run main.go" to run it or "go 10 | // install -tags=example" to install it. 11 | 12 | package main 13 | 14 | import ( 15 | "bufio" 16 | "fmt" 17 | "image" 18 | "image/draw" 19 | "image/png" 20 | "log" 21 | "os" 22 | 23 | "github.com/golang/freetype/raster" 24 | "golang.org/x/image/math/fixed" 25 | ) 26 | 27 | func p(x, y int) fixed.Point26_6 { 28 | return fixed.Point26_6{ 29 | X: fixed.Int26_6(x * 64), 30 | Y: fixed.Int26_6(y * 64), 31 | } 32 | } 33 | 34 | func main() { 35 | // Draw a rounded corner that is one pixel wide. 36 | r := raster.NewRasterizer(50, 50) 37 | r.Start(p(5, 5)) 38 | r.Add1(p(5, 25)) 39 | r.Add2(p(5, 45), p(25, 45)) 40 | r.Add1(p(45, 45)) 41 | r.Add1(p(45, 44)) 42 | r.Add1(p(26, 44)) 43 | r.Add2(p(6, 44), p(6, 24)) 44 | r.Add1(p(6, 5)) 45 | r.Add1(p(5, 5)) 46 | 47 | // Rasterize that curve multiple times at different gammas. 48 | const ( 49 | w = 600 50 | h = 200 51 | ) 52 | rgba := image.NewRGBA(image.Rect(0, 0, w, h)) 53 | draw.Draw(rgba, image.Rect(0, 0, w, h/2), image.Black, image.ZP, draw.Src) 54 | draw.Draw(rgba, image.Rect(0, h/2, w, h), image.White, image.ZP, draw.Src) 55 | mask := image.NewAlpha(image.Rect(0, 0, 50, 50)) 56 | painter := raster.NewAlphaSrcPainter(mask) 57 | gammas := []float64{1.0 / 10.0, 1.0 / 3.0, 1.0 / 2.0, 2.0 / 3.0, 4.0 / 5.0, 1.0, 5.0 / 4.0, 3.0 / 2.0, 2.0, 3.0, 10.0} 58 | for i, g := range gammas { 59 | draw.Draw(mask, mask.Bounds(), image.Transparent, image.ZP, draw.Src) 60 | r.Rasterize(raster.NewGammaCorrectionPainter(painter, g)) 61 | x, y := 50*i+25, 25 62 | draw.DrawMask(rgba, image.Rect(x, y, x+50, y+50), image.White, image.ZP, mask, image.ZP, draw.Over) 63 | y += 100 64 | draw.DrawMask(rgba, image.Rect(x, y, x+50, y+50), image.Black, image.ZP, mask, image.ZP, draw.Over) 65 | } 66 | 67 | // Save that RGBA image to disk. 68 | outFile, err := os.Create("out.png") 69 | if err != nil { 70 | log.Println(err) 71 | os.Exit(1) 72 | } 73 | defer outFile.Close() 74 | b := bufio.NewWriter(outFile) 75 | err = png.Encode(b, rgba) 76 | if err != nil { 77 | log.Println(err) 78 | os.Exit(1) 79 | } 80 | err = b.Flush() 81 | if err != nil { 82 | log.Println(err) 83 | os.Exit(1) 84 | } 85 | fmt.Println("Wrote out.png OK.") 86 | } 87 | -------------------------------------------------------------------------------- /example/genbasicfont/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | // +build example 7 | // 8 | // This build tag means that "go install github.com/golang/freetype/..." 9 | // doesn't install this example program. Use "go run main.go" to run it or "go 10 | // install -tags=example" to install it. 11 | 12 | // Program genbasicfont generates Go source code that imports 13 | // golang.org/x/image/font/basicfont to provide a fixed width font face. 14 | package main 15 | 16 | import ( 17 | "bytes" 18 | "flag" 19 | "fmt" 20 | "go/format" 21 | "image" 22 | "image/draw" 23 | "io/ioutil" 24 | "log" 25 | "net/http" 26 | "strings" 27 | "unicode" 28 | 29 | "github.com/golang/freetype/truetype" 30 | "golang.org/x/image/font" 31 | "golang.org/x/image/math/fixed" 32 | ) 33 | 34 | var ( 35 | fontfile = flag.String("fontfile", "../../testdata/luxisr.ttf", "filename or URL of the TTF font") 36 | hinting = flag.String("hinting", "none", "none, vertical or full") 37 | pkg = flag.String("pkg", "example", "the package name for the generated code") 38 | size = flag.Float64("size", 12, "the number of pixels in 1 em") 39 | vr = flag.String("var", "example", "the variable name for the generated code") 40 | ) 41 | 42 | func loadFontFile() ([]byte, error) { 43 | if strings.HasPrefix(*fontfile, "http://") || strings.HasPrefix(*fontfile, "https://") { 44 | resp, err := http.Get(*fontfile) 45 | if err != nil { 46 | return nil, err 47 | } 48 | defer resp.Body.Close() 49 | return ioutil.ReadAll(resp.Body) 50 | } 51 | return ioutil.ReadFile(*fontfile) 52 | } 53 | 54 | func parseHinting(h string) font.Hinting { 55 | switch h { 56 | case "full": 57 | return font.HintingFull 58 | case "vertical": 59 | log.Fatal("TODO: have package truetype implement vertical hinting") 60 | return font.HintingVertical 61 | } 62 | return font.HintingNone 63 | } 64 | 65 | func privateUseArea(r rune) bool { 66 | return 0xe000 <= r && r <= 0xf8ff || 67 | 0xf0000 <= r && r <= 0xffffd || 68 | 0x100000 <= r && r <= 0x10fffd 69 | } 70 | 71 | func loadRanges(f *truetype.Font) (ret [][2]rune) { 72 | rr := [2]rune{-1, -1} 73 | for r := rune(0); r <= unicode.MaxRune; r++ { 74 | if privateUseArea(r) { 75 | continue 76 | } 77 | if f.Index(r) == 0 { 78 | continue 79 | } 80 | if rr[1] == r { 81 | rr[1] = r + 1 82 | continue 83 | } 84 | if rr[0] != -1 { 85 | ret = append(ret, rr) 86 | } 87 | rr = [2]rune{r, r + 1} 88 | } 89 | if rr[0] != -1 { 90 | ret = append(ret, rr) 91 | } 92 | return ret 93 | } 94 | 95 | func emptyCol(m *image.Gray, r image.Rectangle, x int) bool { 96 | for y := r.Min.Y; y < r.Max.Y; y++ { 97 | if m.GrayAt(x, y).Y > 0 { 98 | return false 99 | } 100 | } 101 | return true 102 | } 103 | 104 | func emptyRow(m *image.Gray, r image.Rectangle, y int) bool { 105 | for x := r.Min.X; x < r.Max.X; x++ { 106 | if m.GrayAt(x, y).Y > 0 { 107 | return false 108 | } 109 | } 110 | return true 111 | } 112 | 113 | func tightBounds(m *image.Gray) (r image.Rectangle) { 114 | r = m.Bounds() 115 | for ; r.Min.Y < r.Max.Y && emptyRow(m, r, r.Min.Y+0); r.Min.Y++ { 116 | } 117 | for ; r.Min.Y < r.Max.Y && emptyRow(m, r, r.Max.Y-1); r.Max.Y-- { 118 | } 119 | for ; r.Min.X < r.Max.X && emptyCol(m, r, r.Min.X+0); r.Min.X++ { 120 | } 121 | for ; r.Min.X < r.Max.X && emptyCol(m, r, r.Max.X-1); r.Max.X-- { 122 | } 123 | return r 124 | } 125 | 126 | func printPix(ranges [][2]rune, glyphs map[rune]*image.Gray, b image.Rectangle) []byte { 127 | buf := new(bytes.Buffer) 128 | for _, rr := range ranges { 129 | for r := rr[0]; r < rr[1]; r++ { 130 | m := glyphs[r] 131 | fmt.Fprintf(buf, "// U+%08x '%c'\n", r, r) 132 | for y := b.Min.Y; y < b.Max.Y; y++ { 133 | for x := b.Min.X; x < b.Max.X; x++ { 134 | fmt.Fprintf(buf, "%#02x, ", m.GrayAt(x, y).Y) 135 | } 136 | fmt.Fprintln(buf) 137 | } 138 | fmt.Fprintln(buf) 139 | } 140 | } 141 | return buf.Bytes() 142 | } 143 | 144 | func printRanges(ranges [][2]rune) []byte { 145 | buf := new(bytes.Buffer) 146 | offset := 0 147 | for _, rr := range ranges { 148 | fmt.Fprintf(buf, "{'\\U%08x', '\\U%08x', %d},\n", rr[0], rr[1], offset) 149 | offset += int(rr[1] - rr[0]) 150 | } 151 | return buf.Bytes() 152 | } 153 | 154 | func main() { 155 | flag.Parse() 156 | b, err := loadFontFile() 157 | if err != nil { 158 | log.Fatal(err) 159 | } 160 | f, err := truetype.Parse(b) 161 | if err != nil { 162 | log.Fatal(err) 163 | } 164 | face := truetype.NewFace(f, &truetype.Options{ 165 | Size: *size, 166 | Hinting: parseHinting(*hinting), 167 | }) 168 | defer face.Close() 169 | 170 | fBounds := f.Bounds(fixed.Int26_6(*size * 64)) 171 | iBounds := image.Rect( 172 | +fBounds.Min.X.Floor(), 173 | -fBounds.Max.Y.Ceil(), 174 | +fBounds.Max.X.Ceil(), 175 | -fBounds.Min.Y.Floor(), 176 | ) 177 | 178 | tBounds := image.Rectangle{} 179 | glyphs := map[rune]*image.Gray{} 180 | advance := fixed.Int26_6(-1) 181 | 182 | ranges := loadRanges(f) 183 | for _, rr := range ranges { 184 | for r := rr[0]; r < rr[1]; r++ { 185 | dr, mask, maskp, adv, ok := face.Glyph(fixed.Point26_6{}, r) 186 | if !ok { 187 | log.Fatalf("could not load glyph for %U", r) 188 | } 189 | if advance < 0 { 190 | advance = adv 191 | } else if advance != adv { 192 | log.Fatalf("advance was not constant: got %v and %v", advance, adv) 193 | } 194 | dst := image.NewGray(iBounds) 195 | draw.DrawMask(dst, dr, image.White, image.Point{}, mask, maskp, draw.Src) 196 | glyphs[r] = dst 197 | tBounds = tBounds.Union(tightBounds(dst)) 198 | } 199 | } 200 | 201 | // height is the glyph image height, not the inter-line spacing. 202 | width, height := tBounds.Dx(), tBounds.Dy() 203 | 204 | buf := new(bytes.Buffer) 205 | fmt.Fprintf(buf, "// generated by go generate; DO NOT EDIT.\n\npackage %s\n\n", *pkg) 206 | fmt.Fprintf(buf, "import (\n\"image\"\n\n\"golang.org/x/image/font/basicfont\"\n)\n\n") 207 | fmt.Fprintf(buf, "// %s contains %d %d×%d glyphs in %d Pix bytes.\n", 208 | *vr, len(glyphs), width, height, len(glyphs)*width*height) 209 | fmt.Fprintf(buf, `var %s = basicfont.Face{ 210 | Advance: %d, 211 | Width: %d, 212 | Height: %d, 213 | Ascent: %d, 214 | Descent: %d, 215 | Left: %d, 216 | Mask: &image.Alpha{ 217 | Stride: %d, 218 | Rect: image.Rectangle{Max: image.Point{%d, %d*%d}}, 219 | Pix: []byte{ 220 | %s 221 | }, 222 | }, 223 | Ranges: []basicfont.Range{ 224 | %s 225 | }, 226 | }`, *vr, advance.Ceil(), width, face.Metrics().Height.Ceil(), -tBounds.Min.Y, +tBounds.Max.Y, tBounds.Min.X, 227 | width, width, len(glyphs), height, 228 | printPix(ranges, glyphs, tBounds), printRanges(ranges)) 229 | 230 | fmted, err := format.Source(buf.Bytes()) 231 | if err != nil { 232 | log.Fatalf("format.Source: %v", err) 233 | } 234 | if err := ioutil.WriteFile(*vr+".go", fmted, 0644); err != nil { 235 | log.Fatalf("ioutil.WriteFile: %v", err) 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /example/raster/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | // +build example 7 | // 8 | // This build tag means that "go install github.com/golang/freetype/..." 9 | // doesn't install this example program. Use "go run main.go" to run it or "go 10 | // install -tags=example" to install it. 11 | 12 | package main 13 | 14 | import ( 15 | "bufio" 16 | "fmt" 17 | "image" 18 | "image/color" 19 | "image/draw" 20 | "image/png" 21 | "log" 22 | "os" 23 | 24 | "github.com/golang/freetype/raster" 25 | "golang.org/x/image/math/fixed" 26 | ) 27 | 28 | type node struct { 29 | x, y, degree int 30 | } 31 | 32 | // These contours "outside" and "inside" are from the 'A' glyph from the Droid 33 | // Serif Regular font. 34 | 35 | var outside = []node{ 36 | node{414, 489, 1}, 37 | node{336, 274, 2}, 38 | node{327, 250, 0}, 39 | node{322, 226, 2}, 40 | node{317, 203, 0}, 41 | node{317, 186, 2}, 42 | node{317, 134, 0}, 43 | node{350, 110, 2}, 44 | node{384, 86, 0}, 45 | node{453, 86, 1}, 46 | node{500, 86, 1}, 47 | node{500, 0, 1}, 48 | node{0, 0, 1}, 49 | node{0, 86, 1}, 50 | node{39, 86, 2}, 51 | node{69, 86, 0}, 52 | node{90, 92, 2}, 53 | node{111, 99, 0}, 54 | node{128, 117, 2}, 55 | node{145, 135, 0}, 56 | node{160, 166, 2}, 57 | node{176, 197, 0}, 58 | node{195, 246, 1}, 59 | node{649, 1462, 1}, 60 | node{809, 1462, 1}, 61 | node{1272, 195, 2}, 62 | node{1284, 163, 0}, 63 | node{1296, 142, 2}, 64 | node{1309, 121, 0}, 65 | node{1326, 108, 2}, 66 | node{1343, 96, 0}, 67 | node{1365, 91, 2}, 68 | node{1387, 86, 0}, 69 | node{1417, 86, 1}, 70 | node{1444, 86, 1}, 71 | node{1444, 0, 1}, 72 | node{881, 0, 1}, 73 | node{881, 86, 1}, 74 | node{928, 86, 2}, 75 | node{1051, 86, 0}, 76 | node{1051, 184, 2}, 77 | node{1051, 201, 0}, 78 | node{1046, 219, 2}, 79 | node{1042, 237, 0}, 80 | node{1034, 260, 1}, 81 | node{952, 489, 1}, 82 | node{414, 489, -1}, 83 | } 84 | 85 | var inside = []node{ 86 | node{686, 1274, 1}, 87 | node{453, 592, 1}, 88 | node{915, 592, 1}, 89 | node{686, 1274, -1}, 90 | } 91 | 92 | func p(n node) fixed.Point26_6 { 93 | x, y := 20+n.x/4, 380-n.y/4 94 | return fixed.Point26_6{ 95 | X: fixed.Int26_6(x << 6), 96 | Y: fixed.Int26_6(y << 6), 97 | } 98 | } 99 | 100 | func contour(r *raster.Rasterizer, ns []node) { 101 | if len(ns) == 0 { 102 | return 103 | } 104 | i := 0 105 | r.Start(p(ns[i])) 106 | for { 107 | switch ns[i].degree { 108 | case -1: 109 | // -1 signifies end-of-contour. 110 | return 111 | case 1: 112 | i += 1 113 | r.Add1(p(ns[i])) 114 | case 2: 115 | i += 2 116 | r.Add2(p(ns[i-1]), p(ns[i])) 117 | default: 118 | panic("bad degree") 119 | } 120 | } 121 | } 122 | 123 | func showNodes(m *image.RGBA, ns []node) { 124 | for _, n := range ns { 125 | p := p(n) 126 | x, y := int(p.X)/64, int(p.Y)/64 127 | if !(image.Point{x, y}).In(m.Bounds()) { 128 | continue 129 | } 130 | var c color.Color 131 | switch n.degree { 132 | case 0: 133 | c = color.RGBA{0, 255, 255, 255} 134 | case 1: 135 | c = color.RGBA{255, 0, 0, 255} 136 | case 2: 137 | c = color.RGBA{255, 0, 0, 255} 138 | } 139 | if c != nil { 140 | m.Set(x, y, c) 141 | } 142 | } 143 | } 144 | 145 | func main() { 146 | // Rasterize the contours to a mask image. 147 | const ( 148 | w = 400 149 | h = 400 150 | ) 151 | r := raster.NewRasterizer(w, h) 152 | contour(r, outside) 153 | contour(r, inside) 154 | mask := image.NewAlpha(image.Rect(0, 0, w, h)) 155 | p := raster.NewAlphaSrcPainter(mask) 156 | r.Rasterize(p) 157 | 158 | // Draw the mask image (in gray) onto an RGBA image. 159 | rgba := image.NewRGBA(image.Rect(0, 0, w, h)) 160 | gray := image.NewUniform(color.Alpha{0x1f}) 161 | draw.Draw(rgba, rgba.Bounds(), image.Black, image.ZP, draw.Src) 162 | draw.DrawMask(rgba, rgba.Bounds(), gray, image.ZP, mask, image.ZP, draw.Over) 163 | showNodes(rgba, outside) 164 | showNodes(rgba, inside) 165 | 166 | // Save that RGBA image to disk. 167 | outFile, err := os.Create("out.png") 168 | if err != nil { 169 | log.Println(err) 170 | os.Exit(1) 171 | } 172 | defer outFile.Close() 173 | b := bufio.NewWriter(outFile) 174 | err = png.Encode(b, rgba) 175 | if err != nil { 176 | log.Println(err) 177 | os.Exit(1) 178 | } 179 | err = b.Flush() 180 | if err != nil { 181 | log.Println(err) 182 | os.Exit(1) 183 | } 184 | fmt.Println("Wrote out.png OK.") 185 | } 186 | -------------------------------------------------------------------------------- /example/round/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | // +build example 7 | // 8 | // This build tag means that "go install github.com/golang/freetype/..." 9 | // doesn't install this example program. Use "go run main.go" to run it or "go 10 | // install -tags=example" to install it. 11 | 12 | // This program visualizes the quadratic approximation to the circle, used to 13 | // implement round joins when stroking paths. The approximation is used in the 14 | // stroking code for arcs between 0 and 45 degrees, but is visualized here 15 | // between 0 and 90 degrees. The discrepancy between the approximation and the 16 | // true circle is clearly visible at angles above 65 degrees. 17 | package main 18 | 19 | import ( 20 | "bufio" 21 | "fmt" 22 | "image" 23 | "image/color" 24 | "image/draw" 25 | "image/png" 26 | "log" 27 | "math" 28 | "os" 29 | 30 | "github.com/golang/freetype/raster" 31 | "golang.org/x/image/math/fixed" 32 | ) 33 | 34 | // pDot returns the dot product p·q. 35 | func pDot(p, q fixed.Point26_6) fixed.Int52_12 { 36 | px, py := int64(p.X), int64(p.Y) 37 | qx, qy := int64(q.X), int64(q.Y) 38 | return fixed.Int52_12(px*qx + py*qy) 39 | } 40 | 41 | func main() { 42 | const ( 43 | n = 17 44 | r = 64 * 80 45 | ) 46 | s := fixed.Int26_6(r * math.Sqrt(2) / 2) 47 | t := fixed.Int26_6(r * math.Tan(math.Pi/8)) 48 | 49 | m := image.NewRGBA(image.Rect(0, 0, 800, 600)) 50 | draw.Draw(m, m.Bounds(), image.NewUniform(color.RGBA{63, 63, 63, 255}), image.ZP, draw.Src) 51 | mp := raster.NewRGBAPainter(m) 52 | mp.SetColor(image.Black) 53 | z := raster.NewRasterizer(800, 600) 54 | 55 | for i := 0; i < n; i++ { 56 | cx := fixed.Int26_6(6400 + 12800*(i%4)) 57 | cy := fixed.Int26_6(640 + 8000*(i/4)) 58 | c := fixed.Point26_6{X: cx, Y: cy} 59 | theta := math.Pi * (0.5 + 0.5*float64(i)/(n-1)) 60 | dx := fixed.Int26_6(r * math.Cos(theta)) 61 | dy := fixed.Int26_6(r * math.Sin(theta)) 62 | d := fixed.Point26_6{X: dx, Y: dy} 63 | // Draw a quarter-circle approximated by two quadratic segments, 64 | // with each segment spanning 45 degrees. 65 | z.Start(c) 66 | z.Add1(c.Add(fixed.Point26_6{X: r, Y: 0})) 67 | z.Add2(c.Add(fixed.Point26_6{X: r, Y: t}), c.Add(fixed.Point26_6{X: s, Y: s})) 68 | z.Add2(c.Add(fixed.Point26_6{X: t, Y: r}), c.Add(fixed.Point26_6{X: 0, Y: r})) 69 | // Add another quadratic segment whose angle ranges between 0 and 90 70 | // degrees. For an explanation of the magic constants 128, 150, 181 and 71 | // 256, read the comments in the freetype/raster package. 72 | dot := 256 * pDot(d, fixed.Point26_6{X: 0, Y: r}) / (r * r) 73 | multiple := fixed.Int26_6(150-(150-128)*(dot-181)/(256-181)) >> 2 74 | z.Add2(c.Add(fixed.Point26_6{X: dx, Y: r + dy}.Mul(multiple)), c.Add(d)) 75 | // Close the curve. 76 | z.Add1(c) 77 | } 78 | z.Rasterize(mp) 79 | 80 | for i := 0; i < n; i++ { 81 | cx := fixed.Int26_6(6400 + 12800*(i%4)) 82 | cy := fixed.Int26_6(640 + 8000*(i/4)) 83 | for j := 0; j < n; j++ { 84 | theta := math.Pi * float64(j) / (n - 1) 85 | dx := fixed.Int26_6(r * math.Cos(theta)) 86 | dy := fixed.Int26_6(r * math.Sin(theta)) 87 | m.Set(int((cx+dx)/64), int((cy+dy)/64), color.RGBA{255, 255, 0, 255}) 88 | } 89 | } 90 | 91 | // Save that RGBA image to disk. 92 | outFile, err := os.Create("out.png") 93 | if err != nil { 94 | log.Println(err) 95 | os.Exit(1) 96 | } 97 | defer outFile.Close() 98 | b := bufio.NewWriter(outFile) 99 | err = png.Encode(b, m) 100 | if err != nil { 101 | log.Println(err) 102 | os.Exit(1) 103 | } 104 | err = b.Flush() 105 | if err != nil { 106 | log.Println(err) 107 | os.Exit(1) 108 | } 109 | fmt.Println("Wrote out.png OK.") 110 | } 111 | -------------------------------------------------------------------------------- /example/truetype/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | // +build example 7 | // 8 | // This build tag means that "go install github.com/golang/freetype/..." 9 | // doesn't install this example program. Use "go run main.go" to run it or "go 10 | // install -tags=example" to install it. 11 | 12 | package main 13 | 14 | import ( 15 | "flag" 16 | "fmt" 17 | "io/ioutil" 18 | "log" 19 | 20 | "github.com/golang/freetype/truetype" 21 | "golang.org/x/image/font" 22 | "golang.org/x/image/math/fixed" 23 | ) 24 | 25 | var fontfile = flag.String("fontfile", "../../testdata/luxisr.ttf", "filename of the ttf font") 26 | 27 | func printBounds(b fixed.Rectangle26_6) { 28 | fmt.Printf("Min.X:%d Min.Y:%d Max.X:%d Max.Y:%d\n", b.Min.X, b.Min.Y, b.Max.X, b.Max.Y) 29 | } 30 | 31 | func printGlyph(g *truetype.GlyphBuf) { 32 | printBounds(g.Bounds) 33 | fmt.Print("Points:\n---\n") 34 | e := 0 35 | for i, p := range g.Points { 36 | fmt.Printf("%4d, %4d", p.X, p.Y) 37 | if p.Flags&0x01 != 0 { 38 | fmt.Print(" on\n") 39 | } else { 40 | fmt.Print(" off\n") 41 | } 42 | if i+1 == int(g.Ends[e]) { 43 | fmt.Print("---\n") 44 | e++ 45 | } 46 | } 47 | } 48 | 49 | func main() { 50 | flag.Parse() 51 | fmt.Printf("Loading fontfile %q\n", *fontfile) 52 | b, err := ioutil.ReadFile(*fontfile) 53 | if err != nil { 54 | log.Println(err) 55 | return 56 | } 57 | f, err := truetype.Parse(b) 58 | if err != nil { 59 | log.Println(err) 60 | return 61 | } 62 | fupe := fixed.Int26_6(f.FUnitsPerEm()) 63 | printBounds(f.Bounds(fupe)) 64 | fmt.Printf("FUnitsPerEm:%d\n\n", fupe) 65 | 66 | c0, c1 := 'A', 'V' 67 | 68 | i0 := f.Index(c0) 69 | hm := f.HMetric(fupe, i0) 70 | g := &truetype.GlyphBuf{} 71 | err = g.Load(f, fupe, i0, font.HintingNone) 72 | if err != nil { 73 | log.Println(err) 74 | return 75 | } 76 | fmt.Printf("'%c' glyph\n", c0) 77 | fmt.Printf("AdvanceWidth:%d LeftSideBearing:%d\n", hm.AdvanceWidth, hm.LeftSideBearing) 78 | printGlyph(g) 79 | i1 := f.Index(c1) 80 | fmt.Printf("\n'%c', '%c' Kern:%d\n", c0, c1, f.Kern(fupe, i0, i1)) 81 | 82 | fmt.Printf("\nThe numbers above are in FUnits.\n" + 83 | "The numbers below are in 26.6 fixed point pixels, at 12pt and 72dpi.\n\n") 84 | a := truetype.NewFace(f, &truetype.Options{ 85 | Size: 12, 86 | DPI: 72, 87 | }) 88 | fmt.Printf("%#v\n", a.Metrics()) 89 | } 90 | -------------------------------------------------------------------------------- /freetype.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | // The freetype package provides a convenient API to draw text onto an image. 7 | // Use the freetype/raster and freetype/truetype packages for lower level 8 | // control over rasterization and TrueType parsing. 9 | package freetype // import "github.com/golang/freetype" 10 | 11 | import ( 12 | "errors" 13 | "image" 14 | "image/draw" 15 | 16 | "github.com/golang/freetype/raster" 17 | "github.com/golang/freetype/truetype" 18 | "golang.org/x/image/font" 19 | "golang.org/x/image/math/fixed" 20 | ) 21 | 22 | // These constants determine the size of the glyph cache. The cache is keyed 23 | // primarily by the glyph index modulo nGlyphs, and secondarily by sub-pixel 24 | // position for the mask image. Sub-pixel positions are quantized to 25 | // nXFractions possible values in both the x and y directions. 26 | const ( 27 | nGlyphs = 256 28 | nXFractions = 4 29 | nYFractions = 1 30 | ) 31 | 32 | // An entry in the glyph cache is keyed explicitly by the glyph index and 33 | // implicitly by the quantized x and y fractional offset. It maps to a mask 34 | // image and an offset. 35 | type cacheEntry struct { 36 | valid bool 37 | glyph truetype.Index 38 | advanceWidth fixed.Int26_6 39 | mask *image.Alpha 40 | offset image.Point 41 | } 42 | 43 | // ParseFont just calls the Parse function from the freetype/truetype package. 44 | // It is provided here so that code that imports this package doesn't need 45 | // to also include the freetype/truetype package. 46 | func ParseFont(b []byte) (*truetype.Font, error) { 47 | return truetype.Parse(b) 48 | } 49 | 50 | // Pt converts from a co-ordinate pair measured in pixels to a fixed.Point26_6 51 | // co-ordinate pair measured in fixed.Int26_6 units. 52 | func Pt(x, y int) fixed.Point26_6 { 53 | return fixed.Point26_6{ 54 | X: fixed.Int26_6(x << 6), 55 | Y: fixed.Int26_6(y << 6), 56 | } 57 | } 58 | 59 | // A Context holds the state for drawing text in a given font and size. 60 | type Context struct { 61 | r *raster.Rasterizer 62 | f *truetype.Font 63 | glyphBuf truetype.GlyphBuf 64 | // clip is the clip rectangle for drawing. 65 | clip image.Rectangle 66 | // dst and src are the destination and source images for drawing. 67 | dst draw.Image 68 | src image.Image 69 | // fontSize and dpi are used to calculate scale. scale is the number of 70 | // 26.6 fixed point units in 1 em. hinting is the hinting policy. 71 | fontSize, dpi float64 72 | scale fixed.Int26_6 73 | hinting font.Hinting 74 | // cache is the glyph cache. 75 | cache [nGlyphs * nXFractions * nYFractions]cacheEntry 76 | } 77 | 78 | // PointToFixed converts the given number of points (as in "a 12 point font") 79 | // into a 26.6 fixed point number of pixels. 80 | func (c *Context) PointToFixed(x float64) fixed.Int26_6 { 81 | return fixed.Int26_6(x * float64(c.dpi) * (64.0 / 72.0)) 82 | } 83 | 84 | // drawContour draws the given closed contour with the given offset. 85 | func (c *Context) drawContour(ps []truetype.Point, dx, dy fixed.Int26_6) { 86 | if len(ps) == 0 { 87 | return 88 | } 89 | 90 | // The low bit of each point's Flags value is whether the point is on the 91 | // curve. Truetype fonts only have quadratic Bézier curves, not cubics. 92 | // Thus, two consecutive off-curve points imply an on-curve point in the 93 | // middle of those two. 94 | // 95 | // See http://chanae.walon.org/pub/ttf/ttf_glyphs.htm for more details. 96 | 97 | // ps[0] is a truetype.Point measured in FUnits and positive Y going 98 | // upwards. start is the same thing measured in fixed point units and 99 | // positive Y going downwards, and offset by (dx, dy). 100 | start := fixed.Point26_6{ 101 | X: dx + ps[0].X, 102 | Y: dy - ps[0].Y, 103 | } 104 | others := []truetype.Point(nil) 105 | if ps[0].Flags&0x01 != 0 { 106 | others = ps[1:] 107 | } else { 108 | last := fixed.Point26_6{ 109 | X: dx + ps[len(ps)-1].X, 110 | Y: dy - ps[len(ps)-1].Y, 111 | } 112 | if ps[len(ps)-1].Flags&0x01 != 0 { 113 | start = last 114 | others = ps[:len(ps)-1] 115 | } else { 116 | start = fixed.Point26_6{ 117 | X: (start.X + last.X) / 2, 118 | Y: (start.Y + last.Y) / 2, 119 | } 120 | others = ps 121 | } 122 | } 123 | c.r.Start(start) 124 | q0, on0 := start, true 125 | for _, p := range others { 126 | q := fixed.Point26_6{ 127 | X: dx + p.X, 128 | Y: dy - p.Y, 129 | } 130 | on := p.Flags&0x01 != 0 131 | if on { 132 | if on0 { 133 | c.r.Add1(q) 134 | } else { 135 | c.r.Add2(q0, q) 136 | } 137 | } else { 138 | if on0 { 139 | // No-op. 140 | } else { 141 | mid := fixed.Point26_6{ 142 | X: (q0.X + q.X) / 2, 143 | Y: (q0.Y + q.Y) / 2, 144 | } 145 | c.r.Add2(q0, mid) 146 | } 147 | } 148 | q0, on0 = q, on 149 | } 150 | // Close the curve. 151 | if on0 { 152 | c.r.Add1(start) 153 | } else { 154 | c.r.Add2(q0, start) 155 | } 156 | } 157 | 158 | // rasterize returns the advance width, glyph mask and integer-pixel offset 159 | // to render the given glyph at the given sub-pixel offsets. 160 | // The 26.6 fixed point arguments fx and fy must be in the range [0, 1). 161 | func (c *Context) rasterize(glyph truetype.Index, fx, fy fixed.Int26_6) ( 162 | fixed.Int26_6, *image.Alpha, image.Point, error) { 163 | 164 | if err := c.glyphBuf.Load(c.f, c.scale, glyph, c.hinting); err != nil { 165 | return 0, nil, image.Point{}, err 166 | } 167 | // Calculate the integer-pixel bounds for the glyph. 168 | xmin := int(fx+c.glyphBuf.Bounds.Min.X) >> 6 169 | ymin := int(fy-c.glyphBuf.Bounds.Max.Y) >> 6 170 | xmax := int(fx+c.glyphBuf.Bounds.Max.X+0x3f) >> 6 171 | ymax := int(fy-c.glyphBuf.Bounds.Min.Y+0x3f) >> 6 172 | if xmin > xmax || ymin > ymax { 173 | return 0, nil, image.Point{}, errors.New("freetype: negative sized glyph") 174 | } 175 | // A TrueType's glyph's nodes can have negative co-ordinates, but the 176 | // rasterizer clips anything left of x=0 or above y=0. xmin and ymin are 177 | // the pixel offsets, based on the font's FUnit metrics, that let a 178 | // negative co-ordinate in TrueType space be non-negative in rasterizer 179 | // space. xmin and ymin are typically <= 0. 180 | fx -= fixed.Int26_6(xmin << 6) 181 | fy -= fixed.Int26_6(ymin << 6) 182 | // Rasterize the glyph's vectors. 183 | c.r.Clear() 184 | e0 := 0 185 | for _, e1 := range c.glyphBuf.Ends { 186 | c.drawContour(c.glyphBuf.Points[e0:e1], fx, fy) 187 | e0 = e1 188 | } 189 | a := image.NewAlpha(image.Rect(0, 0, xmax-xmin, ymax-ymin)) 190 | c.r.Rasterize(raster.NewAlphaSrcPainter(a)) 191 | return c.glyphBuf.AdvanceWidth, a, image.Point{xmin, ymin}, nil 192 | } 193 | 194 | // glyph returns the advance width, glyph mask and integer-pixel offset to 195 | // render the given glyph at the given sub-pixel point. It is a cache for the 196 | // rasterize method. Unlike rasterize, p's co-ordinates do not have to be in 197 | // the range [0, 1). 198 | func (c *Context) glyph(glyph truetype.Index, p fixed.Point26_6) ( 199 | fixed.Int26_6, *image.Alpha, image.Point, error) { 200 | 201 | // Split p.X and p.Y into their integer and fractional parts. 202 | ix, fx := int(p.X>>6), p.X&0x3f 203 | iy, fy := int(p.Y>>6), p.Y&0x3f 204 | // Calculate the index t into the cache array. 205 | tg := int(glyph) % nGlyphs 206 | tx := int(fx) / (64 / nXFractions) 207 | ty := int(fy) / (64 / nYFractions) 208 | t := ((tg*nXFractions)+tx)*nYFractions + ty 209 | // Check for a cache hit. 210 | if e := c.cache[t]; e.valid && e.glyph == glyph { 211 | return e.advanceWidth, e.mask, e.offset.Add(image.Point{ix, iy}), nil 212 | } 213 | // Rasterize the glyph and put the result into the cache. 214 | advanceWidth, mask, offset, err := c.rasterize(glyph, fx, fy) 215 | if err != nil { 216 | return 0, nil, image.Point{}, err 217 | } 218 | c.cache[t] = cacheEntry{true, glyph, advanceWidth, mask, offset} 219 | return advanceWidth, mask, offset.Add(image.Point{ix, iy}), nil 220 | } 221 | 222 | // DrawString draws s at p and returns p advanced by the text extent. The text 223 | // is placed so that the left edge of the em square of the first character of s 224 | // and the baseline intersect at p. The majority of the affected pixels will be 225 | // above and to the right of the point, but some may be below or to the left. 226 | // For example, drawing a string that starts with a 'J' in an italic font may 227 | // affect pixels below and left of the point. 228 | // 229 | // p is a fixed.Point26_6 and can therefore represent sub-pixel positions. 230 | func (c *Context) DrawString(s string, p fixed.Point26_6) (fixed.Point26_6, error) { 231 | if c.f == nil { 232 | return fixed.Point26_6{}, errors.New("freetype: DrawText called with a nil font") 233 | } 234 | prev, hasPrev := truetype.Index(0), false 235 | for _, rune := range s { 236 | index := c.f.Index(rune) 237 | if hasPrev { 238 | kern := c.f.Kern(c.scale, prev, index) 239 | if c.hinting != font.HintingNone { 240 | kern = (kern + 32) &^ 63 241 | } 242 | p.X += kern 243 | } 244 | advanceWidth, mask, offset, err := c.glyph(index, p) 245 | if err != nil { 246 | return fixed.Point26_6{}, err 247 | } 248 | p.X += advanceWidth 249 | glyphRect := mask.Bounds().Add(offset) 250 | dr := c.clip.Intersect(glyphRect) 251 | if !dr.Empty() { 252 | mp := image.Point{0, dr.Min.Y - glyphRect.Min.Y} 253 | draw.DrawMask(c.dst, dr, c.src, image.ZP, mask, mp, draw.Over) 254 | } 255 | prev, hasPrev = index, true 256 | } 257 | return p, nil 258 | } 259 | 260 | // recalc recalculates scale and bounds values from the font size, screen 261 | // resolution and font metrics, and invalidates the glyph cache. 262 | func (c *Context) recalc() { 263 | c.scale = fixed.Int26_6(c.fontSize * c.dpi * (64.0 / 72.0)) 264 | if c.f == nil { 265 | c.r.SetBounds(0, 0) 266 | } else { 267 | // Set the rasterizer's bounds to be big enough to handle the largest glyph. 268 | b := c.f.Bounds(c.scale) 269 | xmin := +int(b.Min.X) >> 6 270 | ymin := -int(b.Max.Y) >> 6 271 | xmax := +int(b.Max.X+63) >> 6 272 | ymax := -int(b.Min.Y-63) >> 6 273 | c.r.SetBounds(xmax-xmin, ymax-ymin) 274 | } 275 | for i := range c.cache { 276 | c.cache[i] = cacheEntry{} 277 | } 278 | } 279 | 280 | // SetDPI sets the screen resolution in dots per inch. 281 | func (c *Context) SetDPI(dpi float64) { 282 | if c.dpi == dpi { 283 | return 284 | } 285 | c.dpi = dpi 286 | c.recalc() 287 | } 288 | 289 | // SetFont sets the font used to draw text. 290 | func (c *Context) SetFont(f *truetype.Font) { 291 | if c.f == f { 292 | return 293 | } 294 | c.f = f 295 | c.recalc() 296 | } 297 | 298 | // SetFontSize sets the font size in points (as in "a 12 point font"). 299 | func (c *Context) SetFontSize(fontSize float64) { 300 | if c.fontSize == fontSize { 301 | return 302 | } 303 | c.fontSize = fontSize 304 | c.recalc() 305 | } 306 | 307 | // SetHinting sets the hinting policy. 308 | func (c *Context) SetHinting(hinting font.Hinting) { 309 | c.hinting = hinting 310 | for i := range c.cache { 311 | c.cache[i] = cacheEntry{} 312 | } 313 | } 314 | 315 | // SetDst sets the destination image for draw operations. 316 | func (c *Context) SetDst(dst draw.Image) { 317 | c.dst = dst 318 | } 319 | 320 | // SetSrc sets the source image for draw operations. This is typically an 321 | // image.Uniform. 322 | func (c *Context) SetSrc(src image.Image) { 323 | c.src = src 324 | } 325 | 326 | // SetClip sets the clip rectangle for drawing. 327 | func (c *Context) SetClip(clip image.Rectangle) { 328 | c.clip = clip 329 | } 330 | 331 | // TODO(nigeltao): implement Context.SetGamma. 332 | 333 | // NewContext creates a new Context. 334 | func NewContext() *Context { 335 | return &Context{ 336 | r: raster.NewRasterizer(0, 0), 337 | fontSize: 12, 338 | dpi: 72, 339 | scale: 12 << 6, 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /freetype_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | package freetype 7 | 8 | import ( 9 | "image" 10 | "image/draw" 11 | "io/ioutil" 12 | "runtime" 13 | "strings" 14 | "testing" 15 | ) 16 | 17 | func BenchmarkDrawString(b *testing.B) { 18 | data, err := ioutil.ReadFile("licenses/gpl.txt") 19 | if err != nil { 20 | b.Fatal(err) 21 | } 22 | lines := strings.Split(string(data), "\n") 23 | 24 | data, err = ioutil.ReadFile("testdata/luxisr.ttf") 25 | if err != nil { 26 | b.Fatal(err) 27 | } 28 | f, err := ParseFont(data) 29 | if err != nil { 30 | b.Fatal(err) 31 | } 32 | 33 | dst := image.NewRGBA(image.Rect(0, 0, 800, 600)) 34 | draw.Draw(dst, dst.Bounds(), image.White, image.ZP, draw.Src) 35 | 36 | c := NewContext() 37 | c.SetDst(dst) 38 | c.SetClip(dst.Bounds()) 39 | c.SetSrc(image.Black) 40 | c.SetFont(f) 41 | 42 | var ms runtime.MemStats 43 | runtime.ReadMemStats(&ms) 44 | mallocs := ms.Mallocs 45 | 46 | b.ResetTimer() 47 | for i := 0; i < b.N; i++ { 48 | for j, line := range lines { 49 | _, err := c.DrawString(line, Pt(0, (j*16)%600)) 50 | if err != nil { 51 | b.Fatal(err) 52 | } 53 | } 54 | } 55 | b.StopTimer() 56 | runtime.ReadMemStats(&ms) 57 | mallocs = ms.Mallocs - mallocs 58 | b.Logf("%d iterations, %d mallocs per iteration\n", b.N, int(mallocs)/b.N) 59 | } 60 | -------------------------------------------------------------------------------- /licenses/ftl.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang/freetype/e2365dfdc4a05e4b8299a783240d4a7d5a65d4e4/licenses/ftl.txt -------------------------------------------------------------------------------- /licenses/gpl.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /raster/geom.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | package raster 7 | 8 | import ( 9 | "fmt" 10 | "math" 11 | 12 | "golang.org/x/image/math/fixed" 13 | ) 14 | 15 | // maxAbs returns the maximum of abs(a) and abs(b). 16 | func maxAbs(a, b fixed.Int26_6) fixed.Int26_6 { 17 | if a < 0 { 18 | a = -a 19 | } 20 | if b < 0 { 21 | b = -b 22 | } 23 | if a < b { 24 | return b 25 | } 26 | return a 27 | } 28 | 29 | // pNeg returns the vector -p, or equivalently p rotated by 180 degrees. 30 | func pNeg(p fixed.Point26_6) fixed.Point26_6 { 31 | return fixed.Point26_6{-p.X, -p.Y} 32 | } 33 | 34 | // pDot returns the dot product p·q. 35 | func pDot(p fixed.Point26_6, q fixed.Point26_6) fixed.Int52_12 { 36 | px, py := int64(p.X), int64(p.Y) 37 | qx, qy := int64(q.X), int64(q.Y) 38 | return fixed.Int52_12(px*qx + py*qy) 39 | } 40 | 41 | // pLen returns the length of the vector p. 42 | func pLen(p fixed.Point26_6) fixed.Int26_6 { 43 | // TODO(nigeltao): use fixed point math. 44 | x := float64(p.X) 45 | y := float64(p.Y) 46 | return fixed.Int26_6(math.Sqrt(x*x + y*y)) 47 | } 48 | 49 | // pNorm returns the vector p normalized to the given length, or zero if p is 50 | // degenerate. 51 | func pNorm(p fixed.Point26_6, length fixed.Int26_6) fixed.Point26_6 { 52 | d := pLen(p) 53 | if d == 0 { 54 | return fixed.Point26_6{} 55 | } 56 | s, t := int64(length), int64(d) 57 | x := int64(p.X) * s / t 58 | y := int64(p.Y) * s / t 59 | return fixed.Point26_6{fixed.Int26_6(x), fixed.Int26_6(y)} 60 | } 61 | 62 | // pRot45CW returns the vector p rotated clockwise by 45 degrees. 63 | // 64 | // Note that the Y-axis grows downwards, so {1, 0}.Rot45CW is {1/√2, 1/√2}. 65 | func pRot45CW(p fixed.Point26_6) fixed.Point26_6 { 66 | // 181/256 is approximately 1/√2, or sin(π/4). 67 | px, py := int64(p.X), int64(p.Y) 68 | qx := (+px - py) * 181 / 256 69 | qy := (+px + py) * 181 / 256 70 | return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)} 71 | } 72 | 73 | // pRot90CW returns the vector p rotated clockwise by 90 degrees. 74 | // 75 | // Note that the Y-axis grows downwards, so {1, 0}.Rot90CW is {0, 1}. 76 | func pRot90CW(p fixed.Point26_6) fixed.Point26_6 { 77 | return fixed.Point26_6{-p.Y, p.X} 78 | } 79 | 80 | // pRot135CW returns the vector p rotated clockwise by 135 degrees. 81 | // 82 | // Note that the Y-axis grows downwards, so {1, 0}.Rot135CW is {-1/√2, 1/√2}. 83 | func pRot135CW(p fixed.Point26_6) fixed.Point26_6 { 84 | // 181/256 is approximately 1/√2, or sin(π/4). 85 | px, py := int64(p.X), int64(p.Y) 86 | qx := (-px - py) * 181 / 256 87 | qy := (+px - py) * 181 / 256 88 | return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)} 89 | } 90 | 91 | // pRot45CCW returns the vector p rotated counter-clockwise by 45 degrees. 92 | // 93 | // Note that the Y-axis grows downwards, so {1, 0}.Rot45CCW is {1/√2, -1/√2}. 94 | func pRot45CCW(p fixed.Point26_6) fixed.Point26_6 { 95 | // 181/256 is approximately 1/√2, or sin(π/4). 96 | px, py := int64(p.X), int64(p.Y) 97 | qx := (+px + py) * 181 / 256 98 | qy := (-px + py) * 181 / 256 99 | return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)} 100 | } 101 | 102 | // pRot90CCW returns the vector p rotated counter-clockwise by 90 degrees. 103 | // 104 | // Note that the Y-axis grows downwards, so {1, 0}.Rot90CCW is {0, -1}. 105 | func pRot90CCW(p fixed.Point26_6) fixed.Point26_6 { 106 | return fixed.Point26_6{p.Y, -p.X} 107 | } 108 | 109 | // pRot135CCW returns the vector p rotated counter-clockwise by 135 degrees. 110 | // 111 | // Note that the Y-axis grows downwards, so {1, 0}.Rot135CCW is {-1/√2, -1/√2}. 112 | func pRot135CCW(p fixed.Point26_6) fixed.Point26_6 { 113 | // 181/256 is approximately 1/√2, or sin(π/4). 114 | px, py := int64(p.X), int64(p.Y) 115 | qx := (-px + py) * 181 / 256 116 | qy := (-px - py) * 181 / 256 117 | return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)} 118 | } 119 | 120 | // An Adder accumulates points on a curve. 121 | type Adder interface { 122 | // Start starts a new curve at the given point. 123 | Start(a fixed.Point26_6) 124 | // Add1 adds a linear segment to the current curve. 125 | Add1(b fixed.Point26_6) 126 | // Add2 adds a quadratic segment to the current curve. 127 | Add2(b, c fixed.Point26_6) 128 | // Add3 adds a cubic segment to the current curve. 129 | Add3(b, c, d fixed.Point26_6) 130 | } 131 | 132 | // A Path is a sequence of curves, and a curve is a start point followed by a 133 | // sequence of linear, quadratic or cubic segments. 134 | type Path []fixed.Int26_6 135 | 136 | // String returns a human-readable representation of a Path. 137 | func (p Path) String() string { 138 | s := "" 139 | for i := 0; i < len(p); { 140 | if i != 0 { 141 | s += " " 142 | } 143 | switch p[i] { 144 | case 0: 145 | s += "S0" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+3])) 146 | i += 4 147 | case 1: 148 | s += "A1" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+3])) 149 | i += 4 150 | case 2: 151 | s += "A2" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+5])) 152 | i += 6 153 | case 3: 154 | s += "A3" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+7])) 155 | i += 8 156 | default: 157 | panic("freetype/raster: bad path") 158 | } 159 | } 160 | return s 161 | } 162 | 163 | // Clear cancels any previous calls to p.Start or p.AddXxx. 164 | func (p *Path) Clear() { 165 | *p = (*p)[:0] 166 | } 167 | 168 | // Start starts a new curve at the given point. 169 | func (p *Path) Start(a fixed.Point26_6) { 170 | *p = append(*p, 0, a.X, a.Y, 0) 171 | } 172 | 173 | // Add1 adds a linear segment to the current curve. 174 | func (p *Path) Add1(b fixed.Point26_6) { 175 | *p = append(*p, 1, b.X, b.Y, 1) 176 | } 177 | 178 | // Add2 adds a quadratic segment to the current curve. 179 | func (p *Path) Add2(b, c fixed.Point26_6) { 180 | *p = append(*p, 2, b.X, b.Y, c.X, c.Y, 2) 181 | } 182 | 183 | // Add3 adds a cubic segment to the current curve. 184 | func (p *Path) Add3(b, c, d fixed.Point26_6) { 185 | *p = append(*p, 3, b.X, b.Y, c.X, c.Y, d.X, d.Y, 3) 186 | } 187 | 188 | // AddPath adds the Path q to p. 189 | func (p *Path) AddPath(q Path) { 190 | *p = append(*p, q...) 191 | } 192 | 193 | // AddStroke adds a stroked Path. 194 | func (p *Path) AddStroke(q Path, width fixed.Int26_6, cr Capper, jr Joiner) { 195 | Stroke(p, q, width, cr, jr) 196 | } 197 | 198 | // firstPoint returns the first point in a non-empty Path. 199 | func (p Path) firstPoint() fixed.Point26_6 { 200 | return fixed.Point26_6{p[1], p[2]} 201 | } 202 | 203 | // lastPoint returns the last point in a non-empty Path. 204 | func (p Path) lastPoint() fixed.Point26_6 { 205 | return fixed.Point26_6{p[len(p)-3], p[len(p)-2]} 206 | } 207 | 208 | // addPathReversed adds q reversed to p. 209 | // For example, if q consists of a linear segment from A to B followed by a 210 | // quadratic segment from B to C to D, then the values of q looks like: 211 | // index: 01234567890123 212 | // value: 0AA01BB12CCDD2 213 | // So, when adding q backwards to p, we want to Add2(C, B) followed by Add1(A). 214 | func addPathReversed(p Adder, q Path) { 215 | if len(q) == 0 { 216 | return 217 | } 218 | i := len(q) - 1 219 | for { 220 | switch q[i] { 221 | case 0: 222 | return 223 | case 1: 224 | i -= 4 225 | p.Add1( 226 | fixed.Point26_6{q[i-2], q[i-1]}, 227 | ) 228 | case 2: 229 | i -= 6 230 | p.Add2( 231 | fixed.Point26_6{q[i+2], q[i+3]}, 232 | fixed.Point26_6{q[i-2], q[i-1]}, 233 | ) 234 | case 3: 235 | i -= 8 236 | p.Add3( 237 | fixed.Point26_6{q[i+4], q[i+5]}, 238 | fixed.Point26_6{q[i+2], q[i+3]}, 239 | fixed.Point26_6{q[i-2], q[i-1]}, 240 | ) 241 | default: 242 | panic("freetype/raster: bad path") 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /raster/paint.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | package raster 7 | 8 | import ( 9 | "image" 10 | "image/color" 11 | "image/draw" 12 | "math" 13 | ) 14 | 15 | // A Span is a horizontal segment of pixels with constant alpha. X0 is an 16 | // inclusive bound and X1 is exclusive, the same as for slices. A fully opaque 17 | // Span has Alpha == 0xffff. 18 | type Span struct { 19 | Y, X0, X1 int 20 | Alpha uint32 21 | } 22 | 23 | // A Painter knows how to paint a batch of Spans. Rasterization may involve 24 | // Painting multiple batches, and done will be true for the final batch. The 25 | // Spans' Y values are monotonically increasing during a rasterization. Paint 26 | // may use all of ss as scratch space during the call. 27 | type Painter interface { 28 | Paint(ss []Span, done bool) 29 | } 30 | 31 | // The PainterFunc type adapts an ordinary function to the Painter interface. 32 | type PainterFunc func(ss []Span, done bool) 33 | 34 | // Paint just delegates the call to f. 35 | func (f PainterFunc) Paint(ss []Span, done bool) { f(ss, done) } 36 | 37 | // An AlphaOverPainter is a Painter that paints Spans onto a *image.Alpha using 38 | // the Over Porter-Duff composition operator. 39 | type AlphaOverPainter struct { 40 | Image *image.Alpha 41 | } 42 | 43 | // Paint satisfies the Painter interface. 44 | func (r AlphaOverPainter) Paint(ss []Span, done bool) { 45 | b := r.Image.Bounds() 46 | for _, s := range ss { 47 | if s.Y < b.Min.Y { 48 | continue 49 | } 50 | if s.Y >= b.Max.Y { 51 | return 52 | } 53 | if s.X0 < b.Min.X { 54 | s.X0 = b.Min.X 55 | } 56 | if s.X1 > b.Max.X { 57 | s.X1 = b.Max.X 58 | } 59 | if s.X0 >= s.X1 { 60 | continue 61 | } 62 | base := (s.Y-r.Image.Rect.Min.Y)*r.Image.Stride - r.Image.Rect.Min.X 63 | p := r.Image.Pix[base+s.X0 : base+s.X1] 64 | a := int(s.Alpha >> 8) 65 | for i, c := range p { 66 | v := int(c) 67 | p[i] = uint8((v*255 + (255-v)*a) / 255) 68 | } 69 | } 70 | } 71 | 72 | // NewAlphaOverPainter creates a new AlphaOverPainter for the given image. 73 | func NewAlphaOverPainter(m *image.Alpha) AlphaOverPainter { 74 | return AlphaOverPainter{m} 75 | } 76 | 77 | // An AlphaSrcPainter is a Painter that paints Spans onto a *image.Alpha using 78 | // the Src Porter-Duff composition operator. 79 | type AlphaSrcPainter struct { 80 | Image *image.Alpha 81 | } 82 | 83 | // Paint satisfies the Painter interface. 84 | func (r AlphaSrcPainter) Paint(ss []Span, done bool) { 85 | b := r.Image.Bounds() 86 | for _, s := range ss { 87 | if s.Y < b.Min.Y { 88 | continue 89 | } 90 | if s.Y >= b.Max.Y { 91 | return 92 | } 93 | if s.X0 < b.Min.X { 94 | s.X0 = b.Min.X 95 | } 96 | if s.X1 > b.Max.X { 97 | s.X1 = b.Max.X 98 | } 99 | if s.X0 >= s.X1 { 100 | continue 101 | } 102 | base := (s.Y-r.Image.Rect.Min.Y)*r.Image.Stride - r.Image.Rect.Min.X 103 | p := r.Image.Pix[base+s.X0 : base+s.X1] 104 | color := uint8(s.Alpha >> 8) 105 | for i := range p { 106 | p[i] = color 107 | } 108 | } 109 | } 110 | 111 | // NewAlphaSrcPainter creates a new AlphaSrcPainter for the given image. 112 | func NewAlphaSrcPainter(m *image.Alpha) AlphaSrcPainter { 113 | return AlphaSrcPainter{m} 114 | } 115 | 116 | // An RGBAPainter is a Painter that paints Spans onto a *image.RGBA. 117 | type RGBAPainter struct { 118 | // Image is the image to compose onto. 119 | Image *image.RGBA 120 | // Op is the Porter-Duff composition operator. 121 | Op draw.Op 122 | // cr, cg, cb and ca are the 16-bit color to paint the spans. 123 | cr, cg, cb, ca uint32 124 | } 125 | 126 | // Paint satisfies the Painter interface. 127 | func (r *RGBAPainter) Paint(ss []Span, done bool) { 128 | b := r.Image.Bounds() 129 | for _, s := range ss { 130 | if s.Y < b.Min.Y { 131 | continue 132 | } 133 | if s.Y >= b.Max.Y { 134 | return 135 | } 136 | if s.X0 < b.Min.X { 137 | s.X0 = b.Min.X 138 | } 139 | if s.X1 > b.Max.X { 140 | s.X1 = b.Max.X 141 | } 142 | if s.X0 >= s.X1 { 143 | continue 144 | } 145 | // This code mimics drawGlyphOver in $GOROOT/src/image/draw/draw.go. 146 | ma := s.Alpha 147 | const m = 1<<16 - 1 148 | i0 := (s.Y-r.Image.Rect.Min.Y)*r.Image.Stride + (s.X0-r.Image.Rect.Min.X)*4 149 | i1 := i0 + (s.X1-s.X0)*4 150 | if r.Op == draw.Over { 151 | for i := i0; i < i1; i += 4 { 152 | dr := uint32(r.Image.Pix[i+0]) 153 | dg := uint32(r.Image.Pix[i+1]) 154 | db := uint32(r.Image.Pix[i+2]) 155 | da := uint32(r.Image.Pix[i+3]) 156 | a := (m - (r.ca * ma / m)) * 0x101 157 | r.Image.Pix[i+0] = uint8((dr*a + r.cr*ma) / m >> 8) 158 | r.Image.Pix[i+1] = uint8((dg*a + r.cg*ma) / m >> 8) 159 | r.Image.Pix[i+2] = uint8((db*a + r.cb*ma) / m >> 8) 160 | r.Image.Pix[i+3] = uint8((da*a + r.ca*ma) / m >> 8) 161 | } 162 | } else { 163 | for i := i0; i < i1; i += 4 { 164 | r.Image.Pix[i+0] = uint8(r.cr * ma / m >> 8) 165 | r.Image.Pix[i+1] = uint8(r.cg * ma / m >> 8) 166 | r.Image.Pix[i+2] = uint8(r.cb * ma / m >> 8) 167 | r.Image.Pix[i+3] = uint8(r.ca * ma / m >> 8) 168 | } 169 | } 170 | } 171 | } 172 | 173 | // SetColor sets the color to paint the spans. 174 | func (r *RGBAPainter) SetColor(c color.Color) { 175 | r.cr, r.cg, r.cb, r.ca = c.RGBA() 176 | } 177 | 178 | // NewRGBAPainter creates a new RGBAPainter for the given image. 179 | func NewRGBAPainter(m *image.RGBA) *RGBAPainter { 180 | return &RGBAPainter{Image: m} 181 | } 182 | 183 | // A MonochromePainter wraps another Painter, quantizing each Span's alpha to 184 | // be either fully opaque or fully transparent. 185 | type MonochromePainter struct { 186 | Painter Painter 187 | y, x0, x1 int 188 | } 189 | 190 | // Paint delegates to the wrapped Painter after quantizing each Span's alpha 191 | // value and merging adjacent fully opaque Spans. 192 | func (m *MonochromePainter) Paint(ss []Span, done bool) { 193 | // We compact the ss slice, discarding any Spans whose alpha quantizes to zero. 194 | j := 0 195 | for _, s := range ss { 196 | if s.Alpha >= 0x8000 { 197 | if m.y == s.Y && m.x1 == s.X0 { 198 | m.x1 = s.X1 199 | } else { 200 | ss[j] = Span{m.y, m.x0, m.x1, 1<<16 - 1} 201 | j++ 202 | m.y, m.x0, m.x1 = s.Y, s.X0, s.X1 203 | } 204 | } 205 | } 206 | if done { 207 | // Flush the accumulated Span. 208 | finalSpan := Span{m.y, m.x0, m.x1, 1<<16 - 1} 209 | if j < len(ss) { 210 | ss[j] = finalSpan 211 | j++ 212 | m.Painter.Paint(ss[:j], true) 213 | } else if j == len(ss) { 214 | m.Painter.Paint(ss, false) 215 | if cap(ss) > 0 { 216 | ss = ss[:1] 217 | } else { 218 | ss = make([]Span, 1) 219 | } 220 | ss[0] = finalSpan 221 | m.Painter.Paint(ss, true) 222 | } else { 223 | panic("unreachable") 224 | } 225 | // Reset the accumulator, so that this Painter can be re-used. 226 | m.y, m.x0, m.x1 = 0, 0, 0 227 | } else { 228 | m.Painter.Paint(ss[:j], false) 229 | } 230 | } 231 | 232 | // NewMonochromePainter creates a new MonochromePainter that wraps the given 233 | // Painter. 234 | func NewMonochromePainter(p Painter) *MonochromePainter { 235 | return &MonochromePainter{Painter: p} 236 | } 237 | 238 | // A GammaCorrectionPainter wraps another Painter, performing gamma-correction 239 | // on each Span's alpha value. 240 | type GammaCorrectionPainter struct { 241 | // Painter is the wrapped Painter. 242 | Painter Painter 243 | // a is the precomputed alpha values for linear interpolation, with fully 244 | // opaque == 0xffff. 245 | a [256]uint16 246 | // gammaIsOne is whether gamma correction is a no-op. 247 | gammaIsOne bool 248 | } 249 | 250 | // Paint delegates to the wrapped Painter after performing gamma-correction on 251 | // each Span. 252 | func (g *GammaCorrectionPainter) Paint(ss []Span, done bool) { 253 | if !g.gammaIsOne { 254 | const n = 0x101 255 | for i, s := range ss { 256 | if s.Alpha == 0 || s.Alpha == 0xffff { 257 | continue 258 | } 259 | p, q := s.Alpha/n, s.Alpha%n 260 | // The resultant alpha is a linear interpolation of g.a[p] and g.a[p+1]. 261 | a := uint32(g.a[p])*(n-q) + uint32(g.a[p+1])*q 262 | ss[i].Alpha = (a + n/2) / n 263 | } 264 | } 265 | g.Painter.Paint(ss, done) 266 | } 267 | 268 | // SetGamma sets the gamma value. 269 | func (g *GammaCorrectionPainter) SetGamma(gamma float64) { 270 | g.gammaIsOne = gamma == 1 271 | if g.gammaIsOne { 272 | return 273 | } 274 | for i := 0; i < 256; i++ { 275 | a := float64(i) / 0xff 276 | a = math.Pow(a, gamma) 277 | g.a[i] = uint16(0xffff * a) 278 | } 279 | } 280 | 281 | // NewGammaCorrectionPainter creates a new GammaCorrectionPainter that wraps 282 | // the given Painter. 283 | func NewGammaCorrectionPainter(p Painter, gamma float64) *GammaCorrectionPainter { 284 | g := &GammaCorrectionPainter{Painter: p} 285 | g.SetGamma(gamma) 286 | return g 287 | } 288 | -------------------------------------------------------------------------------- /raster/raster.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | // Package raster provides an anti-aliasing 2-D rasterizer. 7 | // 8 | // It is part of the larger Freetype suite of font-related packages, but the 9 | // raster package is not specific to font rasterization, and can be used 10 | // standalone without any other Freetype package. 11 | // 12 | // Rasterization is done by the same area/coverage accumulation algorithm as 13 | // the Freetype "smooth" module, and the Anti-Grain Geometry library. A 14 | // description of the area/coverage algorithm is at 15 | // http://projects.tuxee.net/cl-vectors/section-the-cl-aa-algorithm 16 | package raster // import "github.com/golang/freetype/raster" 17 | 18 | import ( 19 | "strconv" 20 | 21 | "golang.org/x/image/math/fixed" 22 | ) 23 | 24 | // A cell is part of a linked list (for a given yi co-ordinate) of accumulated 25 | // area/coverage for the pixel at (xi, yi). 26 | type cell struct { 27 | xi int 28 | area, cover int 29 | next int 30 | } 31 | 32 | type Rasterizer struct { 33 | // If false, the default behavior is to use the even-odd winding fill 34 | // rule during Rasterize. 35 | UseNonZeroWinding bool 36 | // An offset (in pixels) to the painted spans. 37 | Dx, Dy int 38 | 39 | // The width of the Rasterizer. The height is implicit in len(cellIndex). 40 | width int 41 | // splitScaleN is the scaling factor used to determine how many times 42 | // to decompose a quadratic or cubic segment into a linear approximation. 43 | splitScale2, splitScale3 int 44 | 45 | // The current pen position. 46 | a fixed.Point26_6 47 | // The current cell and its area/coverage being accumulated. 48 | xi, yi int 49 | area, cover int 50 | 51 | // Saved cells. 52 | cell []cell 53 | // Linked list of cells, one per row. 54 | cellIndex []int 55 | // Buffers. 56 | cellBuf [256]cell 57 | cellIndexBuf [64]int 58 | spanBuf [64]Span 59 | } 60 | 61 | // findCell returns the index in r.cell for the cell corresponding to 62 | // (r.xi, r.yi). The cell is created if necessary. 63 | func (r *Rasterizer) findCell() int { 64 | if r.yi < 0 || r.yi >= len(r.cellIndex) { 65 | return -1 66 | } 67 | xi := r.xi 68 | if xi < 0 { 69 | xi = -1 70 | } else if xi > r.width { 71 | xi = r.width 72 | } 73 | i, prev := r.cellIndex[r.yi], -1 74 | for i != -1 && r.cell[i].xi <= xi { 75 | if r.cell[i].xi == xi { 76 | return i 77 | } 78 | i, prev = r.cell[i].next, i 79 | } 80 | c := len(r.cell) 81 | if c == cap(r.cell) { 82 | buf := make([]cell, c, 4*c) 83 | copy(buf, r.cell) 84 | r.cell = buf[0 : c+1] 85 | } else { 86 | r.cell = r.cell[0 : c+1] 87 | } 88 | r.cell[c] = cell{xi, 0, 0, i} 89 | if prev == -1 { 90 | r.cellIndex[r.yi] = c 91 | } else { 92 | r.cell[prev].next = c 93 | } 94 | return c 95 | } 96 | 97 | // saveCell saves any accumulated r.area/r.cover for (r.xi, r.yi). 98 | func (r *Rasterizer) saveCell() { 99 | if r.area != 0 || r.cover != 0 { 100 | i := r.findCell() 101 | if i != -1 { 102 | r.cell[i].area += r.area 103 | r.cell[i].cover += r.cover 104 | } 105 | r.area = 0 106 | r.cover = 0 107 | } 108 | } 109 | 110 | // setCell sets the (xi, yi) cell that r is accumulating area/coverage for. 111 | func (r *Rasterizer) setCell(xi, yi int) { 112 | if r.xi != xi || r.yi != yi { 113 | r.saveCell() 114 | r.xi, r.yi = xi, yi 115 | } 116 | } 117 | 118 | // scan accumulates area/coverage for the yi'th scanline, going from 119 | // x0 to x1 in the horizontal direction (in 26.6 fixed point co-ordinates) 120 | // and from y0f to y1f fractional vertical units within that scanline. 121 | func (r *Rasterizer) scan(yi int, x0, y0f, x1, y1f fixed.Int26_6) { 122 | // Break the 26.6 fixed point X co-ordinates into integral and fractional parts. 123 | x0i := int(x0) / 64 124 | x0f := x0 - fixed.Int26_6(64*x0i) 125 | x1i := int(x1) / 64 126 | x1f := x1 - fixed.Int26_6(64*x1i) 127 | 128 | // A perfectly horizontal scan. 129 | if y0f == y1f { 130 | r.setCell(x1i, yi) 131 | return 132 | } 133 | dx, dy := x1-x0, y1f-y0f 134 | // A single cell scan. 135 | if x0i == x1i { 136 | r.area += int((x0f + x1f) * dy) 137 | r.cover += int(dy) 138 | return 139 | } 140 | // There are at least two cells. Apart from the first and last cells, 141 | // all intermediate cells go through the full width of the cell, 142 | // or 64 units in 26.6 fixed point format. 143 | var ( 144 | p, q, edge0, edge1 fixed.Int26_6 145 | xiDelta int 146 | ) 147 | if dx > 0 { 148 | p, q = (64-x0f)*dy, dx 149 | edge0, edge1, xiDelta = 0, 64, 1 150 | } else { 151 | p, q = x0f*dy, -dx 152 | edge0, edge1, xiDelta = 64, 0, -1 153 | } 154 | yDelta, yRem := p/q, p%q 155 | if yRem < 0 { 156 | yDelta -= 1 157 | yRem += q 158 | } 159 | // Do the first cell. 160 | xi, y := x0i, y0f 161 | r.area += int((x0f + edge1) * yDelta) 162 | r.cover += int(yDelta) 163 | xi, y = xi+xiDelta, y+yDelta 164 | r.setCell(xi, yi) 165 | if xi != x1i { 166 | // Do all the intermediate cells. 167 | p = 64 * (y1f - y + yDelta) 168 | fullDelta, fullRem := p/q, p%q 169 | if fullRem < 0 { 170 | fullDelta -= 1 171 | fullRem += q 172 | } 173 | yRem -= q 174 | for xi != x1i { 175 | yDelta = fullDelta 176 | yRem += fullRem 177 | if yRem >= 0 { 178 | yDelta += 1 179 | yRem -= q 180 | } 181 | r.area += int(64 * yDelta) 182 | r.cover += int(yDelta) 183 | xi, y = xi+xiDelta, y+yDelta 184 | r.setCell(xi, yi) 185 | } 186 | } 187 | // Do the last cell. 188 | yDelta = y1f - y 189 | r.area += int((edge0 + x1f) * yDelta) 190 | r.cover += int(yDelta) 191 | } 192 | 193 | // Start starts a new curve at the given point. 194 | func (r *Rasterizer) Start(a fixed.Point26_6) { 195 | r.setCell(int(a.X/64), int(a.Y/64)) 196 | r.a = a 197 | } 198 | 199 | // Add1 adds a linear segment to the current curve. 200 | func (r *Rasterizer) Add1(b fixed.Point26_6) { 201 | x0, y0 := r.a.X, r.a.Y 202 | x1, y1 := b.X, b.Y 203 | dx, dy := x1-x0, y1-y0 204 | // Break the 26.6 fixed point Y co-ordinates into integral and fractional 205 | // parts. 206 | y0i := int(y0) / 64 207 | y0f := y0 - fixed.Int26_6(64*y0i) 208 | y1i := int(y1) / 64 209 | y1f := y1 - fixed.Int26_6(64*y1i) 210 | 211 | if y0i == y1i { 212 | // There is only one scanline. 213 | r.scan(y0i, x0, y0f, x1, y1f) 214 | 215 | } else if dx == 0 { 216 | // This is a vertical line segment. We avoid calling r.scan and instead 217 | // manipulate r.area and r.cover directly. 218 | var ( 219 | edge0, edge1 fixed.Int26_6 220 | yiDelta int 221 | ) 222 | if dy > 0 { 223 | edge0, edge1, yiDelta = 0, 64, 1 224 | } else { 225 | edge0, edge1, yiDelta = 64, 0, -1 226 | } 227 | x0i, yi := int(x0)/64, y0i 228 | x0fTimes2 := (int(x0) - (64 * x0i)) * 2 229 | // Do the first pixel. 230 | dcover := int(edge1 - y0f) 231 | darea := int(x0fTimes2 * dcover) 232 | r.area += darea 233 | r.cover += dcover 234 | yi += yiDelta 235 | r.setCell(x0i, yi) 236 | // Do all the intermediate pixels. 237 | dcover = int(edge1 - edge0) 238 | darea = int(x0fTimes2 * dcover) 239 | for yi != y1i { 240 | r.area += darea 241 | r.cover += dcover 242 | yi += yiDelta 243 | r.setCell(x0i, yi) 244 | } 245 | // Do the last pixel. 246 | dcover = int(y1f - edge0) 247 | darea = int(x0fTimes2 * dcover) 248 | r.area += darea 249 | r.cover += dcover 250 | 251 | } else { 252 | // There are at least two scanlines. Apart from the first and last 253 | // scanlines, all intermediate scanlines go through the full height of 254 | // the row, or 64 units in 26.6 fixed point format. 255 | var ( 256 | p, q, edge0, edge1 fixed.Int26_6 257 | yiDelta int 258 | ) 259 | if dy > 0 { 260 | p, q = (64-y0f)*dx, dy 261 | edge0, edge1, yiDelta = 0, 64, 1 262 | } else { 263 | p, q = y0f*dx, -dy 264 | edge0, edge1, yiDelta = 64, 0, -1 265 | } 266 | xDelta, xRem := p/q, p%q 267 | if xRem < 0 { 268 | xDelta -= 1 269 | xRem += q 270 | } 271 | // Do the first scanline. 272 | x, yi := x0, y0i 273 | r.scan(yi, x, y0f, x+xDelta, edge1) 274 | x, yi = x+xDelta, yi+yiDelta 275 | r.setCell(int(x)/64, yi) 276 | if yi != y1i { 277 | // Do all the intermediate scanlines. 278 | p = 64 * dx 279 | fullDelta, fullRem := p/q, p%q 280 | if fullRem < 0 { 281 | fullDelta -= 1 282 | fullRem += q 283 | } 284 | xRem -= q 285 | for yi != y1i { 286 | xDelta = fullDelta 287 | xRem += fullRem 288 | if xRem >= 0 { 289 | xDelta += 1 290 | xRem -= q 291 | } 292 | r.scan(yi, x, edge0, x+xDelta, edge1) 293 | x, yi = x+xDelta, yi+yiDelta 294 | r.setCell(int(x)/64, yi) 295 | } 296 | } 297 | // Do the last scanline. 298 | r.scan(yi, x, edge0, x1, y1f) 299 | } 300 | // The next lineTo starts from b. 301 | r.a = b 302 | } 303 | 304 | // Add2 adds a quadratic segment to the current curve. 305 | func (r *Rasterizer) Add2(b, c fixed.Point26_6) { 306 | // Calculate nSplit (the number of recursive decompositions) based on how 307 | // 'curvy' it is. Specifically, how much the middle point b deviates from 308 | // (a+c)/2. 309 | dev := maxAbs(r.a.X-2*b.X+c.X, r.a.Y-2*b.Y+c.Y) / fixed.Int26_6(r.splitScale2) 310 | nsplit := 0 311 | for dev > 0 { 312 | dev /= 4 313 | nsplit++ 314 | } 315 | // dev is 32-bit, and nsplit++ every time we shift off 2 bits, so maxNsplit 316 | // is 16. 317 | const maxNsplit = 16 318 | if nsplit > maxNsplit { 319 | panic("freetype/raster: Add2 nsplit too large: " + strconv.Itoa(nsplit)) 320 | } 321 | // Recursively decompose the curve nSplit levels deep. 322 | var ( 323 | pStack [2*maxNsplit + 3]fixed.Point26_6 324 | sStack [maxNsplit + 1]int 325 | i int 326 | ) 327 | sStack[0] = nsplit 328 | pStack[0] = c 329 | pStack[1] = b 330 | pStack[2] = r.a 331 | for i >= 0 { 332 | s := sStack[i] 333 | p := pStack[2*i:] 334 | if s > 0 { 335 | // Split the quadratic curve p[:3] into an equivalent set of two 336 | // shorter curves: p[:3] and p[2:5]. The new p[4] is the old p[2], 337 | // and p[0] is unchanged. 338 | mx := p[1].X 339 | p[4].X = p[2].X 340 | p[3].X = (p[4].X + mx) / 2 341 | p[1].X = (p[0].X + mx) / 2 342 | p[2].X = (p[1].X + p[3].X) / 2 343 | my := p[1].Y 344 | p[4].Y = p[2].Y 345 | p[3].Y = (p[4].Y + my) / 2 346 | p[1].Y = (p[0].Y + my) / 2 347 | p[2].Y = (p[1].Y + p[3].Y) / 2 348 | // The two shorter curves have one less split to do. 349 | sStack[i] = s - 1 350 | sStack[i+1] = s - 1 351 | i++ 352 | } else { 353 | // Replace the level-0 quadratic with a two-linear-piece 354 | // approximation. 355 | midx := (p[0].X + 2*p[1].X + p[2].X) / 4 356 | midy := (p[0].Y + 2*p[1].Y + p[2].Y) / 4 357 | r.Add1(fixed.Point26_6{midx, midy}) 358 | r.Add1(p[0]) 359 | i-- 360 | } 361 | } 362 | } 363 | 364 | // Add3 adds a cubic segment to the current curve. 365 | func (r *Rasterizer) Add3(b, c, d fixed.Point26_6) { 366 | // Calculate nSplit (the number of recursive decompositions) based on how 367 | // 'curvy' it is. 368 | dev2 := maxAbs(r.a.X-3*(b.X+c.X)+d.X, r.a.Y-3*(b.Y+c.Y)+d.Y) / fixed.Int26_6(r.splitScale2) 369 | dev3 := maxAbs(r.a.X-2*b.X+d.X, r.a.Y-2*b.Y+d.Y) / fixed.Int26_6(r.splitScale3) 370 | nsplit := 0 371 | for dev2 > 0 || dev3 > 0 { 372 | dev2 /= 8 373 | dev3 /= 4 374 | nsplit++ 375 | } 376 | // devN is 32-bit, and nsplit++ every time we shift off 2 bits, so 377 | // maxNsplit is 16. 378 | const maxNsplit = 16 379 | if nsplit > maxNsplit { 380 | panic("freetype/raster: Add3 nsplit too large: " + strconv.Itoa(nsplit)) 381 | } 382 | // Recursively decompose the curve nSplit levels deep. 383 | var ( 384 | pStack [3*maxNsplit + 4]fixed.Point26_6 385 | sStack [maxNsplit + 1]int 386 | i int 387 | ) 388 | sStack[0] = nsplit 389 | pStack[0] = d 390 | pStack[1] = c 391 | pStack[2] = b 392 | pStack[3] = r.a 393 | for i >= 0 { 394 | s := sStack[i] 395 | p := pStack[3*i:] 396 | if s > 0 { 397 | // Split the cubic curve p[:4] into an equivalent set of two 398 | // shorter curves: p[:4] and p[3:7]. The new p[6] is the old p[3], 399 | // and p[0] is unchanged. 400 | m01x := (p[0].X + p[1].X) / 2 401 | m12x := (p[1].X + p[2].X) / 2 402 | m23x := (p[2].X + p[3].X) / 2 403 | p[6].X = p[3].X 404 | p[5].X = m23x 405 | p[1].X = m01x 406 | p[2].X = (m01x + m12x) / 2 407 | p[4].X = (m12x + m23x) / 2 408 | p[3].X = (p[2].X + p[4].X) / 2 409 | m01y := (p[0].Y + p[1].Y) / 2 410 | m12y := (p[1].Y + p[2].Y) / 2 411 | m23y := (p[2].Y + p[3].Y) / 2 412 | p[6].Y = p[3].Y 413 | p[5].Y = m23y 414 | p[1].Y = m01y 415 | p[2].Y = (m01y + m12y) / 2 416 | p[4].Y = (m12y + m23y) / 2 417 | p[3].Y = (p[2].Y + p[4].Y) / 2 418 | // The two shorter curves have one less split to do. 419 | sStack[i] = s - 1 420 | sStack[i+1] = s - 1 421 | i++ 422 | } else { 423 | // Replace the level-0 cubic with a two-linear-piece approximation. 424 | midx := (p[0].X + 3*(p[1].X+p[2].X) + p[3].X) / 8 425 | midy := (p[0].Y + 3*(p[1].Y+p[2].Y) + p[3].Y) / 8 426 | r.Add1(fixed.Point26_6{midx, midy}) 427 | r.Add1(p[0]) 428 | i-- 429 | } 430 | } 431 | } 432 | 433 | // AddPath adds the given Path. 434 | func (r *Rasterizer) AddPath(p Path) { 435 | for i := 0; i < len(p); { 436 | switch p[i] { 437 | case 0: 438 | r.Start( 439 | fixed.Point26_6{p[i+1], p[i+2]}, 440 | ) 441 | i += 4 442 | case 1: 443 | r.Add1( 444 | fixed.Point26_6{p[i+1], p[i+2]}, 445 | ) 446 | i += 4 447 | case 2: 448 | r.Add2( 449 | fixed.Point26_6{p[i+1], p[i+2]}, 450 | fixed.Point26_6{p[i+3], p[i+4]}, 451 | ) 452 | i += 6 453 | case 3: 454 | r.Add3( 455 | fixed.Point26_6{p[i+1], p[i+2]}, 456 | fixed.Point26_6{p[i+3], p[i+4]}, 457 | fixed.Point26_6{p[i+5], p[i+6]}, 458 | ) 459 | i += 8 460 | default: 461 | panic("freetype/raster: bad path") 462 | } 463 | } 464 | } 465 | 466 | // AddStroke adds a stroked Path. 467 | func (r *Rasterizer) AddStroke(q Path, width fixed.Int26_6, cr Capper, jr Joiner) { 468 | Stroke(r, q, width, cr, jr) 469 | } 470 | 471 | // areaToAlpha converts an area value to a uint32 alpha value. A completely 472 | // filled pixel corresponds to an area of 64*64*2, and an alpha of 0xffff. The 473 | // conversion of area values greater than this depends on the winding rule: 474 | // even-odd or non-zero. 475 | func (r *Rasterizer) areaToAlpha(area int) uint32 { 476 | // The C Freetype implementation (version 2.3.12) does "alpha := area>>1" 477 | // without the +1. Round-to-nearest gives a more symmetric result than 478 | // round-down. The C implementation also returns 8-bit alpha, not 16-bit 479 | // alpha. 480 | a := (area + 1) >> 1 481 | if a < 0 { 482 | a = -a 483 | } 484 | alpha := uint32(a) 485 | if r.UseNonZeroWinding { 486 | if alpha > 0x0fff { 487 | alpha = 0x0fff 488 | } 489 | } else { 490 | alpha &= 0x1fff 491 | if alpha > 0x1000 { 492 | alpha = 0x2000 - alpha 493 | } else if alpha == 0x1000 { 494 | alpha = 0x0fff 495 | } 496 | } 497 | // alpha is now in the range [0x0000, 0x0fff]. Convert that 12-bit alpha to 498 | // 16-bit alpha. 499 | return alpha<<4 | alpha>>8 500 | } 501 | 502 | // Rasterize converts r's accumulated curves into Spans for p. The Spans passed 503 | // to p are non-overlapping, and sorted by Y and then X. They all have non-zero 504 | // width (and 0 <= X0 < X1 <= r.width) and non-zero A, except for the final 505 | // Span, which has Y, X0, X1 and A all equal to zero. 506 | func (r *Rasterizer) Rasterize(p Painter) { 507 | r.saveCell() 508 | s := 0 509 | for yi := 0; yi < len(r.cellIndex); yi++ { 510 | xi, cover := 0, 0 511 | for c := r.cellIndex[yi]; c != -1; c = r.cell[c].next { 512 | if cover != 0 && r.cell[c].xi > xi { 513 | alpha := r.areaToAlpha(cover * 64 * 2) 514 | if alpha != 0 { 515 | xi0, xi1 := xi, r.cell[c].xi 516 | if xi0 < 0 { 517 | xi0 = 0 518 | } 519 | if xi1 >= r.width { 520 | xi1 = r.width 521 | } 522 | if xi0 < xi1 { 523 | r.spanBuf[s] = Span{yi + r.Dy, xi0 + r.Dx, xi1 + r.Dx, alpha} 524 | s++ 525 | } 526 | } 527 | } 528 | cover += r.cell[c].cover 529 | alpha := r.areaToAlpha(cover*64*2 - r.cell[c].area) 530 | xi = r.cell[c].xi + 1 531 | if alpha != 0 { 532 | xi0, xi1 := r.cell[c].xi, xi 533 | if xi0 < 0 { 534 | xi0 = 0 535 | } 536 | if xi1 >= r.width { 537 | xi1 = r.width 538 | } 539 | if xi0 < xi1 { 540 | r.spanBuf[s] = Span{yi + r.Dy, xi0 + r.Dx, xi1 + r.Dx, alpha} 541 | s++ 542 | } 543 | } 544 | if s > len(r.spanBuf)-2 { 545 | p.Paint(r.spanBuf[:s], false) 546 | s = 0 547 | } 548 | } 549 | } 550 | p.Paint(r.spanBuf[:s], true) 551 | } 552 | 553 | // Clear cancels any previous calls to r.Start or r.AddXxx. 554 | func (r *Rasterizer) Clear() { 555 | r.a = fixed.Point26_6{} 556 | r.xi = 0 557 | r.yi = 0 558 | r.area = 0 559 | r.cover = 0 560 | r.cell = r.cell[:0] 561 | for i := 0; i < len(r.cellIndex); i++ { 562 | r.cellIndex[i] = -1 563 | } 564 | } 565 | 566 | // SetBounds sets the maximum width and height of the rasterized image and 567 | // calls Clear. The width and height are in pixels, not fixed.Int26_6 units. 568 | func (r *Rasterizer) SetBounds(width, height int) { 569 | if width < 0 { 570 | width = 0 571 | } 572 | if height < 0 { 573 | height = 0 574 | } 575 | // Use the same ssN heuristic as the C Freetype (version 2.4.0) 576 | // implementation. 577 | ss2, ss3 := 32, 16 578 | if width > 24 || height > 24 { 579 | ss2, ss3 = 2*ss2, 2*ss3 580 | if width > 120 || height > 120 { 581 | ss2, ss3 = 2*ss2, 2*ss3 582 | } 583 | } 584 | r.width = width 585 | r.splitScale2 = ss2 586 | r.splitScale3 = ss3 587 | r.cell = r.cellBuf[:0] 588 | if height > len(r.cellIndexBuf) { 589 | r.cellIndex = make([]int, height) 590 | } else { 591 | r.cellIndex = r.cellIndexBuf[:height] 592 | } 593 | r.Clear() 594 | } 595 | 596 | // NewRasterizer creates a new Rasterizer with the given bounds. 597 | func NewRasterizer(width, height int) *Rasterizer { 598 | r := new(Rasterizer) 599 | r.SetBounds(width, height) 600 | return r 601 | } 602 | -------------------------------------------------------------------------------- /raster/stroke.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | package raster 7 | 8 | import ( 9 | "golang.org/x/image/math/fixed" 10 | ) 11 | 12 | // Two points are considered practically equal if the square of the distance 13 | // between them is less than one quarter (i.e. 1024 / 4096). 14 | const epsilon = fixed.Int52_12(1024) 15 | 16 | // A Capper signifies how to begin or end a stroked path. 17 | type Capper interface { 18 | // Cap adds a cap to p given a pivot point and the normal vector of a 19 | // terminal segment. The normal's length is half of the stroke width. 20 | Cap(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) 21 | } 22 | 23 | // The CapperFunc type adapts an ordinary function to be a Capper. 24 | type CapperFunc func(Adder, fixed.Int26_6, fixed.Point26_6, fixed.Point26_6) 25 | 26 | func (f CapperFunc) Cap(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) { 27 | f(p, halfWidth, pivot, n1) 28 | } 29 | 30 | // A Joiner signifies how to join interior nodes of a stroked path. 31 | type Joiner interface { 32 | // Join adds a join to the two sides of a stroked path given a pivot 33 | // point and the normal vectors of the trailing and leading segments. 34 | // Both normals have length equal to half of the stroke width. 35 | Join(lhs, rhs Adder, halfWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) 36 | } 37 | 38 | // The JoinerFunc type adapts an ordinary function to be a Joiner. 39 | type JoinerFunc func(lhs, rhs Adder, halfWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) 40 | 41 | func (f JoinerFunc) Join(lhs, rhs Adder, halfWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) { 42 | f(lhs, rhs, halfWidth, pivot, n0, n1) 43 | } 44 | 45 | // RoundCapper adds round caps to a stroked path. 46 | var RoundCapper Capper = CapperFunc(roundCapper) 47 | 48 | func roundCapper(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) { 49 | // The cubic Bézier approximation to a circle involves the magic number 50 | // (√2 - 1) * 4/3, which is approximately 35/64. 51 | const k = 35 52 | e := pRot90CCW(n1) 53 | side := pivot.Add(e) 54 | start, end := pivot.Sub(n1), pivot.Add(n1) 55 | d, e := n1.Mul(k), e.Mul(k) 56 | p.Add3(start.Add(e), side.Sub(d), side) 57 | p.Add3(side.Add(d), end.Add(e), end) 58 | } 59 | 60 | // ButtCapper adds butt caps to a stroked path. 61 | var ButtCapper Capper = CapperFunc(buttCapper) 62 | 63 | func buttCapper(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) { 64 | p.Add1(pivot.Add(n1)) 65 | } 66 | 67 | // SquareCapper adds square caps to a stroked path. 68 | var SquareCapper Capper = CapperFunc(squareCapper) 69 | 70 | func squareCapper(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) { 71 | e := pRot90CCW(n1) 72 | side := pivot.Add(e) 73 | p.Add1(side.Sub(n1)) 74 | p.Add1(side.Add(n1)) 75 | p.Add1(pivot.Add(n1)) 76 | } 77 | 78 | // RoundJoiner adds round joins to a stroked path. 79 | var RoundJoiner Joiner = JoinerFunc(roundJoiner) 80 | 81 | func roundJoiner(lhs, rhs Adder, haflWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) { 82 | dot := pDot(pRot90CW(n0), n1) 83 | if dot >= 0 { 84 | addArc(lhs, pivot, n0, n1) 85 | rhs.Add1(pivot.Sub(n1)) 86 | } else { 87 | lhs.Add1(pivot.Add(n1)) 88 | addArc(rhs, pivot, pNeg(n0), pNeg(n1)) 89 | } 90 | } 91 | 92 | // BevelJoiner adds bevel joins to a stroked path. 93 | var BevelJoiner Joiner = JoinerFunc(bevelJoiner) 94 | 95 | func bevelJoiner(lhs, rhs Adder, haflWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) { 96 | lhs.Add1(pivot.Add(n1)) 97 | rhs.Add1(pivot.Sub(n1)) 98 | } 99 | 100 | // addArc adds a circular arc from pivot+n0 to pivot+n1 to p. The shorter of 101 | // the two possible arcs is taken, i.e. the one spanning <= 180 degrees. The 102 | // two vectors n0 and n1 must be of equal length. 103 | func addArc(p Adder, pivot, n0, n1 fixed.Point26_6) { 104 | // r2 is the square of the length of n0. 105 | r2 := pDot(n0, n0) 106 | if r2 < epsilon { 107 | // The arc radius is so small that we collapse to a straight line. 108 | p.Add1(pivot.Add(n1)) 109 | return 110 | } 111 | // We approximate the arc by 0, 1, 2 or 3 45-degree quadratic segments plus 112 | // a final quadratic segment from s to n1. Each 45-degree segment has 113 | // control points {1, 0}, {1, tan(π/8)} and {1/√2, 1/√2} suitably scaled, 114 | // rotated and translated. tan(π/8) is approximately 27/64. 115 | const tpo8 = 27 116 | var s fixed.Point26_6 117 | // We determine which octant the angle between n0 and n1 is in via three 118 | // dot products. m0, m1 and m2 are n0 rotated clockwise by 45, 90 and 135 119 | // degrees. 120 | m0 := pRot45CW(n0) 121 | m1 := pRot90CW(n0) 122 | m2 := pRot90CW(m0) 123 | if pDot(m1, n1) >= 0 { 124 | if pDot(n0, n1) >= 0 { 125 | if pDot(m2, n1) <= 0 { 126 | // n1 is between 0 and 45 degrees clockwise of n0. 127 | s = n0 128 | } else { 129 | // n1 is between 45 and 90 degrees clockwise of n0. 130 | p.Add2(pivot.Add(n0).Add(m1.Mul(tpo8)), pivot.Add(m0)) 131 | s = m0 132 | } 133 | } else { 134 | pm1, n0t := pivot.Add(m1), n0.Mul(tpo8) 135 | p.Add2(pivot.Add(n0).Add(m1.Mul(tpo8)), pivot.Add(m0)) 136 | p.Add2(pm1.Add(n0t), pm1) 137 | if pDot(m0, n1) >= 0 { 138 | // n1 is between 90 and 135 degrees clockwise of n0. 139 | s = m1 140 | } else { 141 | // n1 is between 135 and 180 degrees clockwise of n0. 142 | p.Add2(pm1.Sub(n0t), pivot.Add(m2)) 143 | s = m2 144 | } 145 | } 146 | } else { 147 | if pDot(n0, n1) >= 0 { 148 | if pDot(m0, n1) >= 0 { 149 | // n1 is between 0 and 45 degrees counter-clockwise of n0. 150 | s = n0 151 | } else { 152 | // n1 is between 45 and 90 degrees counter-clockwise of n0. 153 | p.Add2(pivot.Add(n0).Sub(m1.Mul(tpo8)), pivot.Sub(m2)) 154 | s = pNeg(m2) 155 | } 156 | } else { 157 | pm1, n0t := pivot.Sub(m1), n0.Mul(tpo8) 158 | p.Add2(pivot.Add(n0).Sub(m1.Mul(tpo8)), pivot.Sub(m2)) 159 | p.Add2(pm1.Add(n0t), pm1) 160 | if pDot(m2, n1) <= 0 { 161 | // n1 is between 90 and 135 degrees counter-clockwise of n0. 162 | s = pNeg(m1) 163 | } else { 164 | // n1 is between 135 and 180 degrees counter-clockwise of n0. 165 | p.Add2(pm1.Sub(n0t), pivot.Sub(m0)) 166 | s = pNeg(m0) 167 | } 168 | } 169 | } 170 | // The final quadratic segment has two endpoints s and n1 and the middle 171 | // control point is a multiple of s.Add(n1), i.e. it is on the angle 172 | // bisector of those two points. The multiple ranges between 128/256 and 173 | // 150/256 as the angle between s and n1 ranges between 0 and 45 degrees. 174 | // 175 | // When the angle is 0 degrees (i.e. s and n1 are coincident) then 176 | // s.Add(n1) is twice s and so the middle control point of the degenerate 177 | // quadratic segment should be half s.Add(n1), and half = 128/256. 178 | // 179 | // When the angle is 45 degrees then 150/256 is the ratio of the lengths of 180 | // the two vectors {1, tan(π/8)} and {1 + 1/√2, 1/√2}. 181 | // 182 | // d is the normalized dot product between s and n1. Since the angle ranges 183 | // between 0 and 45 degrees then d ranges between 256/256 and 181/256. 184 | d := 256 * pDot(s, n1) / r2 185 | multiple := fixed.Int26_6(150-(150-128)*(d-181)/(256-181)) >> 2 186 | p.Add2(pivot.Add(s.Add(n1).Mul(multiple)), pivot.Add(n1)) 187 | } 188 | 189 | // midpoint returns the midpoint of two Points. 190 | func midpoint(a, b fixed.Point26_6) fixed.Point26_6 { 191 | return fixed.Point26_6{(a.X + b.X) / 2, (a.Y + b.Y) / 2} 192 | } 193 | 194 | // angleGreaterThan45 returns whether the angle between two vectors is more 195 | // than 45 degrees. 196 | func angleGreaterThan45(v0, v1 fixed.Point26_6) bool { 197 | v := pRot45CCW(v0) 198 | return pDot(v, v1) < 0 || pDot(pRot90CW(v), v1) < 0 199 | } 200 | 201 | // interpolate returns the point (1-t)*a + t*b. 202 | func interpolate(a, b fixed.Point26_6, t fixed.Int52_12) fixed.Point26_6 { 203 | s := 1<<12 - t 204 | x := s*fixed.Int52_12(a.X) + t*fixed.Int52_12(b.X) 205 | y := s*fixed.Int52_12(a.Y) + t*fixed.Int52_12(b.Y) 206 | return fixed.Point26_6{fixed.Int26_6(x >> 12), fixed.Int26_6(y >> 12)} 207 | } 208 | 209 | // curviest2 returns the value of t for which the quadratic parametric curve 210 | // (1-t)²*a + 2*t*(1-t).b + t²*c has maximum curvature. 211 | // 212 | // The curvature of the parametric curve f(t) = (x(t), y(t)) is 213 | // |x′y″-y′x″| / (x′²+y′²)^(3/2). 214 | // 215 | // Let d = b-a and e = c-2*b+a, so that f′(t) = 2*d+2*e*t and f″(t) = 2*e. 216 | // The curvature's numerator is (2*dx+2*ex*t)*(2*ey)-(2*dy+2*ey*t)*(2*ex), 217 | // which simplifies to 4*dx*ey-4*dy*ex, which is constant with respect to t. 218 | // 219 | // Thus, curvature is extreme where the denominator is extreme, i.e. where 220 | // (x′²+y′²) is extreme. The first order condition is that 221 | // 2*x′*x″+2*y′*y″ = 0, or (dx+ex*t)*ex + (dy+ey*t)*ey = 0. 222 | // Solving for t gives t = -(dx*ex+dy*ey) / (ex*ex+ey*ey). 223 | func curviest2(a, b, c fixed.Point26_6) fixed.Int52_12 { 224 | dx := int64(b.X - a.X) 225 | dy := int64(b.Y - a.Y) 226 | ex := int64(c.X - 2*b.X + a.X) 227 | ey := int64(c.Y - 2*b.Y + a.Y) 228 | if ex == 0 && ey == 0 { 229 | return 2048 230 | } 231 | return fixed.Int52_12(-4096 * (dx*ex + dy*ey) / (ex*ex + ey*ey)) 232 | } 233 | 234 | // A stroker holds state for stroking a path. 235 | type stroker struct { 236 | // p is the destination that records the stroked path. 237 | p Adder 238 | // u is the half-width of the stroke. 239 | u fixed.Int26_6 240 | // cr and jr specify how to end and connect path segments. 241 | cr Capper 242 | jr Joiner 243 | // r is the reverse path. Stroking a path involves constructing two 244 | // parallel paths 2*u apart. The first path is added immediately to p, 245 | // the second path is accumulated in r and eventually added in reverse. 246 | r Path 247 | // a is the most recent segment point. anorm is the segment normal of 248 | // length u at that point. 249 | a, anorm fixed.Point26_6 250 | } 251 | 252 | // addNonCurvy2 adds a quadratic segment to the stroker, where the segment 253 | // defined by (k.a, b, c) achieves maximum curvature at either k.a or c. 254 | func (k *stroker) addNonCurvy2(b, c fixed.Point26_6) { 255 | // We repeatedly divide the segment at its middle until it is straight 256 | // enough to approximate the stroke by just translating the control points. 257 | // ds and ps are stacks of depths and points. t is the top of the stack. 258 | const maxDepth = 5 259 | var ( 260 | ds [maxDepth + 1]int 261 | ps [2*maxDepth + 3]fixed.Point26_6 262 | t int 263 | ) 264 | // Initially the ps stack has one quadratic segment of depth zero. 265 | ds[0] = 0 266 | ps[2] = k.a 267 | ps[1] = b 268 | ps[0] = c 269 | anorm := k.anorm 270 | var cnorm fixed.Point26_6 271 | 272 | for { 273 | depth := ds[t] 274 | a := ps[2*t+2] 275 | b := ps[2*t+1] 276 | c := ps[2*t+0] 277 | ab := b.Sub(a) 278 | bc := c.Sub(b) 279 | abIsSmall := pDot(ab, ab) < fixed.Int52_12(1<<12) 280 | bcIsSmall := pDot(bc, bc) < fixed.Int52_12(1<<12) 281 | if abIsSmall && bcIsSmall { 282 | // Approximate the segment by a circular arc. 283 | cnorm = pRot90CCW(pNorm(bc, k.u)) 284 | mac := midpoint(a, c) 285 | addArc(k.p, mac, anorm, cnorm) 286 | addArc(&k.r, mac, pNeg(anorm), pNeg(cnorm)) 287 | } else if depth < maxDepth && angleGreaterThan45(ab, bc) { 288 | // Divide the segment in two and push both halves on the stack. 289 | mab := midpoint(a, b) 290 | mbc := midpoint(b, c) 291 | t++ 292 | ds[t+0] = depth + 1 293 | ds[t-1] = depth + 1 294 | ps[2*t+2] = a 295 | ps[2*t+1] = mab 296 | ps[2*t+0] = midpoint(mab, mbc) 297 | ps[2*t-1] = mbc 298 | continue 299 | } else { 300 | // Translate the control points. 301 | bnorm := pRot90CCW(pNorm(c.Sub(a), k.u)) 302 | cnorm = pRot90CCW(pNorm(bc, k.u)) 303 | k.p.Add2(b.Add(bnorm), c.Add(cnorm)) 304 | k.r.Add2(b.Sub(bnorm), c.Sub(cnorm)) 305 | } 306 | if t == 0 { 307 | k.a, k.anorm = c, cnorm 308 | return 309 | } 310 | t-- 311 | anorm = cnorm 312 | } 313 | panic("unreachable") 314 | } 315 | 316 | // Add1 adds a linear segment to the stroker. 317 | func (k *stroker) Add1(b fixed.Point26_6) { 318 | bnorm := pRot90CCW(pNorm(b.Sub(k.a), k.u)) 319 | if len(k.r) == 0 { 320 | k.p.Start(k.a.Add(bnorm)) 321 | k.r.Start(k.a.Sub(bnorm)) 322 | } else { 323 | k.jr.Join(k.p, &k.r, k.u, k.a, k.anorm, bnorm) 324 | } 325 | k.p.Add1(b.Add(bnorm)) 326 | k.r.Add1(b.Sub(bnorm)) 327 | k.a, k.anorm = b, bnorm 328 | } 329 | 330 | // Add2 adds a quadratic segment to the stroker. 331 | func (k *stroker) Add2(b, c fixed.Point26_6) { 332 | ab := b.Sub(k.a) 333 | bc := c.Sub(b) 334 | abnorm := pRot90CCW(pNorm(ab, k.u)) 335 | if len(k.r) == 0 { 336 | k.p.Start(k.a.Add(abnorm)) 337 | k.r.Start(k.a.Sub(abnorm)) 338 | } else { 339 | k.jr.Join(k.p, &k.r, k.u, k.a, k.anorm, abnorm) 340 | } 341 | 342 | // Approximate nearly-degenerate quadratics by linear segments. 343 | abIsSmall := pDot(ab, ab) < epsilon 344 | bcIsSmall := pDot(bc, bc) < epsilon 345 | if abIsSmall || bcIsSmall { 346 | acnorm := pRot90CCW(pNorm(c.Sub(k.a), k.u)) 347 | k.p.Add1(c.Add(acnorm)) 348 | k.r.Add1(c.Sub(acnorm)) 349 | k.a, k.anorm = c, acnorm 350 | return 351 | } 352 | 353 | // The quadratic segment (k.a, b, c) has a point of maximum curvature. 354 | // If this occurs at an end point, we process the segment as a whole. 355 | t := curviest2(k.a, b, c) 356 | if t <= 0 || 4096 <= t { 357 | k.addNonCurvy2(b, c) 358 | return 359 | } 360 | 361 | // Otherwise, we perform a de Casteljau decomposition at the point of 362 | // maximum curvature and process the two straighter parts. 363 | mab := interpolate(k.a, b, t) 364 | mbc := interpolate(b, c, t) 365 | mabc := interpolate(mab, mbc, t) 366 | 367 | // If the vectors ab and bc are close to being in opposite directions, 368 | // then the decomposition can become unstable, so we approximate the 369 | // quadratic segment by two linear segments joined by an arc. 370 | bcnorm := pRot90CCW(pNorm(bc, k.u)) 371 | if pDot(abnorm, bcnorm) < -fixed.Int52_12(k.u)*fixed.Int52_12(k.u)*2047/2048 { 372 | pArc := pDot(abnorm, bc) < 0 373 | 374 | k.p.Add1(mabc.Add(abnorm)) 375 | if pArc { 376 | z := pRot90CW(abnorm) 377 | addArc(k.p, mabc, abnorm, z) 378 | addArc(k.p, mabc, z, bcnorm) 379 | } 380 | k.p.Add1(mabc.Add(bcnorm)) 381 | k.p.Add1(c.Add(bcnorm)) 382 | 383 | k.r.Add1(mabc.Sub(abnorm)) 384 | if !pArc { 385 | z := pRot90CW(abnorm) 386 | addArc(&k.r, mabc, pNeg(abnorm), z) 387 | addArc(&k.r, mabc, z, pNeg(bcnorm)) 388 | } 389 | k.r.Add1(mabc.Sub(bcnorm)) 390 | k.r.Add1(c.Sub(bcnorm)) 391 | 392 | k.a, k.anorm = c, bcnorm 393 | return 394 | } 395 | 396 | // Process the decomposed parts. 397 | k.addNonCurvy2(mab, mabc) 398 | k.addNonCurvy2(mbc, c) 399 | } 400 | 401 | // Add3 adds a cubic segment to the stroker. 402 | func (k *stroker) Add3(b, c, d fixed.Point26_6) { 403 | panic("freetype/raster: stroke unimplemented for cubic segments") 404 | } 405 | 406 | // stroke adds the stroked Path q to p, where q consists of exactly one curve. 407 | func (k *stroker) stroke(q Path) { 408 | // Stroking is implemented by deriving two paths each k.u apart from q. 409 | // The left-hand-side path is added immediately to k.p; the right-hand-side 410 | // path is accumulated in k.r. Once we've finished adding the LHS to k.p, 411 | // we add the RHS in reverse order. 412 | k.r = make(Path, 0, len(q)) 413 | k.a = fixed.Point26_6{q[1], q[2]} 414 | for i := 4; i < len(q); { 415 | switch q[i] { 416 | case 1: 417 | k.Add1( 418 | fixed.Point26_6{q[i+1], q[i+2]}, 419 | ) 420 | i += 4 421 | case 2: 422 | k.Add2( 423 | fixed.Point26_6{q[i+1], q[i+2]}, 424 | fixed.Point26_6{q[i+3], q[i+4]}, 425 | ) 426 | i += 6 427 | case 3: 428 | k.Add3( 429 | fixed.Point26_6{q[i+1], q[i+2]}, 430 | fixed.Point26_6{q[i+3], q[i+4]}, 431 | fixed.Point26_6{q[i+5], q[i+6]}, 432 | ) 433 | i += 8 434 | default: 435 | panic("freetype/raster: bad path") 436 | } 437 | } 438 | if len(k.r) == 0 { 439 | return 440 | } 441 | // TODO(nigeltao): if q is a closed curve then we should join the first and 442 | // last segments instead of capping them. 443 | k.cr.Cap(k.p, k.u, q.lastPoint(), pNeg(k.anorm)) 444 | addPathReversed(k.p, k.r) 445 | pivot := q.firstPoint() 446 | k.cr.Cap(k.p, k.u, pivot, pivot.Sub(fixed.Point26_6{k.r[1], k.r[2]})) 447 | } 448 | 449 | // Stroke adds q stroked with the given width to p. The result is typically 450 | // self-intersecting and should be rasterized with UseNonZeroWinding. 451 | // cr and jr may be nil, which defaults to a RoundCapper or RoundJoiner. 452 | func Stroke(p Adder, q Path, width fixed.Int26_6, cr Capper, jr Joiner) { 453 | if len(q) == 0 { 454 | return 455 | } 456 | if cr == nil { 457 | cr = RoundCapper 458 | } 459 | if jr == nil { 460 | jr = RoundJoiner 461 | } 462 | if q[0] != 0 { 463 | panic("freetype/raster: bad path") 464 | } 465 | s := stroker{p: p, u: width / 2, cr: cr, jr: jr} 466 | i := 0 467 | for j := 4; j < len(q); { 468 | switch q[j] { 469 | case 0: 470 | s.stroke(q[i:j]) 471 | i, j = j, j+4 472 | case 1: 473 | j += 4 474 | case 2: 475 | j += 6 476 | case 3: 477 | j += 8 478 | default: 479 | panic("freetype/raster: bad path") 480 | } 481 | } 482 | s.stroke(q[i:]) 483 | } 484 | -------------------------------------------------------------------------------- /testdata/COPYING: -------------------------------------------------------------------------------- 1 | Luxi fonts copyright (c) 2001 by Bigelow & Holmes Inc. Luxi font 2 | instruction code copyright (c) 2001 by URW++ GmbH. All Rights 3 | Reserved. Luxi is a registered trademark of Bigelow & Holmes Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of these Fonts and associated documentation files (the "Font 7 | Software"), to deal in the Font Software, including without 8 | limitation the rights to use, copy, merge, publish, distribute, 9 | sublicense, and/or sell copies of the Font Software, and to permit 10 | persons to whom the Font Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright and trademark notices and this permission notice 14 | shall be included in all copies of one or more of the Font Software. 15 | 16 | The Font Software may not be modified, altered, or added to, and in 17 | particular the designs of glyphs or characters in the Fonts may not 18 | be modified nor may additional glyphs or characters be added to the 19 | Fonts. This License becomes null and void when the Fonts or Font 20 | Software have been modified. 21 | 22 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 24 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 25 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL 26 | BIGELOW & HOLMES INC. OR URW++ GMBH. BE LIABLE FOR ANY CLAIM, DAMAGES 27 | OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, 28 | INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF 29 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR 30 | INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT 31 | SOFTWARE. 32 | 33 | Except as contained in this notice, the names of Bigelow & Holmes 34 | Inc. and URW++ GmbH. shall not be used in advertising or otherwise to 35 | promote the sale, use or other dealings in this Font Software without 36 | prior written authorization from Bigelow & Holmes Inc. and URW++ GmbH. 37 | 38 | For further information, contact: 39 | 40 | info@urwpp.de 41 | or 42 | design@bigelowandholmes.com 43 | -------------------------------------------------------------------------------- /testdata/README: -------------------------------------------------------------------------------- 1 | The luxi*.ttf and COPYING files in this directory were copied from the X.org 2 | project, specifically 3 | http://xorg.freedesktop.org/releases/individual/font/font-bh-ttf-1.0.0.tar.bz2 4 | 5 | There are three Luxi fonts: sans (s), serif (r) and monospaced (m). For example, 6 | luxisr.ttf is Luxi Sans. The 'r' here means regular, as opposed to bold. 7 | 8 | The *.ttx files in this directory were generated from the *.ttf files 9 | by the ttx command-line tool. 10 | http://www.letterror.com/code/ttx/index.html 11 | 12 | The *-hinting.txt files in this directory were generated from the *.ttf files 13 | by the ../cmd/print-glyph-points command-line tool. 14 | -------------------------------------------------------------------------------- /testdata/luximr.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang/freetype/e2365dfdc4a05e4b8299a783240d4a7d5a65d4e4/testdata/luximr.ttf -------------------------------------------------------------------------------- /testdata/luxirr.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang/freetype/e2365dfdc4a05e4b8299a783240d4a7d5a65d4e4/testdata/luxirr.ttf -------------------------------------------------------------------------------- /testdata/luxisr.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang/freetype/e2365dfdc4a05e4b8299a783240d4a7d5a65d4e4/testdata/luxisr.ttf -------------------------------------------------------------------------------- /testdata/make-other-hinting-txts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # This script creates the optional x-*-hinting.txt files from fonts that are 4 | # not checked in for copyright or file size reasons. 5 | # 6 | # Run it from this directory (testdata). 7 | # 8 | # It has only been tested on an Ubuntu 14.04 system. 9 | 10 | set -e 11 | 12 | : ${FONTDIR:=/usr/share/fonts/truetype} 13 | 14 | ln -sf $FONTDIR/droid/DroidSansJapanese.ttf x-droid-sans-japanese.ttf 15 | ln -sf $FONTDIR/msttcorefonts/Arial_Bold.ttf x-arial-bold.ttf 16 | ln -sf $FONTDIR/msttcorefonts/Times_New_Roman.ttf x-times-new-roman.ttf 17 | ln -sf $FONTDIR/ttf-dejavu/DejaVuSans-Oblique.ttf x-deja-vu-sans-oblique.ttf 18 | 19 | ${CC:=gcc} ../cmd/print-glyph-points/main.c $(pkg-config --cflags --libs freetype2) -o print-glyph-points 20 | 21 | # Uncomment these lines to also recreate the luxisr-*-hinting.txt files. 22 | # ./print-glyph-points 12 luxisr.ttf sans_hinting > luxisr-12pt-sans-hinting.txt 23 | # ./print-glyph-points 12 luxisr.ttf with_hinting > luxisr-12pt-with-hinting.txt 24 | 25 | ./print-glyph-points 9 x-droid-sans-japanese.ttf sans_hinting > x-droid-sans-japanese-9pt-sans-hinting.txt 26 | ./print-glyph-points 9 x-droid-sans-japanese.ttf with_hinting > x-droid-sans-japanese-9pt-with-hinting.txt 27 | ./print-glyph-points 11 x-arial-bold.ttf sans_hinting > x-arial-bold-11pt-sans-hinting.txt 28 | ./print-glyph-points 11 x-arial-bold.ttf with_hinting > x-arial-bold-11pt-with-hinting.txt 29 | ./print-glyph-points 13 x-times-new-roman.ttf sans_hinting > x-times-new-roman-13pt-sans-hinting.txt 30 | ./print-glyph-points 13 x-times-new-roman.ttf with_hinting > x-times-new-roman-13pt-with-hinting.txt 31 | ./print-glyph-points 17 x-deja-vu-sans-oblique.ttf sans_hinting > x-deja-vu-sans-oblique-17pt-sans-hinting.txt 32 | ./print-glyph-points 17 x-deja-vu-sans-oblique.ttf with_hinting > x-deja-vu-sans-oblique-17pt-with-hinting.txt 33 | 34 | rm print-glyph-points 35 | -------------------------------------------------------------------------------- /truetype/face.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | package truetype 7 | 8 | import ( 9 | "image" 10 | "math" 11 | 12 | "github.com/golang/freetype/raster" 13 | "golang.org/x/image/font" 14 | "golang.org/x/image/math/fixed" 15 | ) 16 | 17 | func powerOf2(i int) bool { 18 | return i != 0 && (i&(i-1)) == 0 19 | } 20 | 21 | // Options are optional arguments to NewFace. 22 | type Options struct { 23 | // Size is the font size in points, as in "a 10 point font size". 24 | // 25 | // A zero value means to use a 12 point font size. 26 | Size float64 27 | 28 | // DPI is the dots-per-inch resolution. 29 | // 30 | // A zero value means to use 72 DPI. 31 | DPI float64 32 | 33 | // Hinting is how to quantize the glyph nodes. 34 | // 35 | // A zero value means to use no hinting. 36 | Hinting font.Hinting 37 | 38 | // GlyphCacheEntries is the number of entries in the glyph mask image 39 | // cache. 40 | // 41 | // If non-zero, it must be a power of 2. 42 | // 43 | // A zero value means to use 512 entries. 44 | GlyphCacheEntries int 45 | 46 | // SubPixelsX is the number of sub-pixel locations a glyph's dot is 47 | // quantized to, in the horizontal direction. For example, a value of 8 48 | // means that the dot is quantized to 1/8th of a pixel. This quantization 49 | // only affects the glyph mask image, not its bounding box or advance 50 | // width. A higher value gives a more faithful glyph image, but reduces the 51 | // effectiveness of the glyph cache. 52 | // 53 | // If non-zero, it must be a power of 2, and be between 1 and 64 inclusive. 54 | // 55 | // A zero value means to use 4 sub-pixel locations. 56 | SubPixelsX int 57 | 58 | // SubPixelsY is the number of sub-pixel locations a glyph's dot is 59 | // quantized to, in the vertical direction. For example, a value of 8 60 | // means that the dot is quantized to 1/8th of a pixel. This quantization 61 | // only affects the glyph mask image, not its bounding box or advance 62 | // width. A higher value gives a more faithful glyph image, but reduces the 63 | // effectiveness of the glyph cache. 64 | // 65 | // If non-zero, it must be a power of 2, and be between 1 and 64 inclusive. 66 | // 67 | // A zero value means to use 1 sub-pixel location. 68 | SubPixelsY int 69 | } 70 | 71 | func (o *Options) size() float64 { 72 | if o != nil && o.Size > 0 { 73 | return o.Size 74 | } 75 | return 12 76 | } 77 | 78 | func (o *Options) dpi() float64 { 79 | if o != nil && o.DPI > 0 { 80 | return o.DPI 81 | } 82 | return 72 83 | } 84 | 85 | func (o *Options) hinting() font.Hinting { 86 | if o != nil { 87 | switch o.Hinting { 88 | case font.HintingVertical, font.HintingFull: 89 | // TODO: support vertical hinting. 90 | return font.HintingFull 91 | } 92 | } 93 | return font.HintingNone 94 | } 95 | 96 | func (o *Options) glyphCacheEntries() int { 97 | if o != nil && powerOf2(o.GlyphCacheEntries) { 98 | return o.GlyphCacheEntries 99 | } 100 | // 512 is 128 * 4 * 1, which lets us cache 128 glyphs at 4 * 1 subpixel 101 | // locations in the X and Y direction. 102 | return 512 103 | } 104 | 105 | func (o *Options) subPixelsX() (value uint32, halfQuantum, mask fixed.Int26_6) { 106 | if o != nil { 107 | switch o.SubPixelsX { 108 | case 1, 2, 4, 8, 16, 32, 64: 109 | return subPixels(o.SubPixelsX) 110 | } 111 | } 112 | // This default value of 4 isn't based on anything scientific, merely as 113 | // small a number as possible that looks almost as good as no quantization, 114 | // or returning subPixels(64). 115 | return subPixels(4) 116 | } 117 | 118 | func (o *Options) subPixelsY() (value uint32, halfQuantum, mask fixed.Int26_6) { 119 | if o != nil { 120 | switch o.SubPixelsX { 121 | case 1, 2, 4, 8, 16, 32, 64: 122 | return subPixels(o.SubPixelsX) 123 | } 124 | } 125 | // This default value of 1 isn't based on anything scientific, merely that 126 | // vertical sub-pixel glyph rendering is pretty rare. Baseline locations 127 | // can usually afford to snap to the pixel grid, so the vertical direction 128 | // doesn't have the deal with the horizontal's fractional advance widths. 129 | return subPixels(1) 130 | } 131 | 132 | // subPixels returns q and the bias and mask that leads to q quantized 133 | // sub-pixel locations per full pixel. 134 | // 135 | // For example, q == 4 leads to a bias of 8 and a mask of 0xfffffff0, or -16, 136 | // because we want to round fractions of fixed.Int26_6 as: 137 | // - 0 to 7 rounds to 0. 138 | // - 8 to 23 rounds to 16. 139 | // - 24 to 39 rounds to 32. 140 | // - 40 to 55 rounds to 48. 141 | // - 56 to 63 rounds to 64. 142 | // which means to add 8 and then bitwise-and with -16, in two's complement 143 | // representation. 144 | // 145 | // When q == 1, we want bias == 32 and mask == -64. 146 | // When q == 2, we want bias == 16 and mask == -32. 147 | // When q == 4, we want bias == 8 and mask == -16. 148 | // ... 149 | // When q == 64, we want bias == 0 and mask == -1. (The no-op case). 150 | // The pattern is clear. 151 | func subPixels(q int) (value uint32, bias, mask fixed.Int26_6) { 152 | return uint32(q), 32 / fixed.Int26_6(q), -64 / fixed.Int26_6(q) 153 | } 154 | 155 | // glyphCacheEntry caches the arguments and return values of rasterize. 156 | type glyphCacheEntry struct { 157 | key glyphCacheKey 158 | val glyphCacheVal 159 | } 160 | 161 | type glyphCacheKey struct { 162 | index Index 163 | fx, fy uint8 164 | } 165 | 166 | type glyphCacheVal struct { 167 | advanceWidth fixed.Int26_6 168 | offset image.Point 169 | gw int 170 | gh int 171 | } 172 | 173 | type indexCacheEntry struct { 174 | rune rune 175 | index Index 176 | } 177 | 178 | // NewFace returns a new font.Face for the given Font. 179 | func NewFace(f *Font, opts *Options) font.Face { 180 | a := &face{ 181 | f: f, 182 | hinting: opts.hinting(), 183 | scale: fixed.Int26_6(0.5 + (opts.size() * opts.dpi() * 64 / 72)), 184 | glyphCache: make([]glyphCacheEntry, opts.glyphCacheEntries()), 185 | } 186 | a.subPixelX, a.subPixelBiasX, a.subPixelMaskX = opts.subPixelsX() 187 | a.subPixelY, a.subPixelBiasY, a.subPixelMaskY = opts.subPixelsY() 188 | 189 | // Fill the cache with invalid entries. Valid glyph cache entries have fx 190 | // and fy in the range [0, 64). Valid index cache entries have rune >= 0. 191 | for i := range a.glyphCache { 192 | a.glyphCache[i].key.fy = 0xff 193 | } 194 | for i := range a.indexCache { 195 | a.indexCache[i].rune = -1 196 | } 197 | 198 | // Set the rasterizer's bounds to be big enough to handle the largest glyph. 199 | b := f.Bounds(a.scale) 200 | xmin := +int(b.Min.X) >> 6 201 | ymin := -int(b.Max.Y) >> 6 202 | xmax := +int(b.Max.X+63) >> 6 203 | ymax := -int(b.Min.Y-63) >> 6 204 | a.maxw = xmax - xmin 205 | a.maxh = ymax - ymin 206 | a.masks = image.NewAlpha(image.Rect(0, 0, a.maxw, a.maxh*len(a.glyphCache))) 207 | a.r.SetBounds(a.maxw, a.maxh) 208 | a.p = facePainter{a} 209 | 210 | return a 211 | } 212 | 213 | type face struct { 214 | f *Font 215 | hinting font.Hinting 216 | scale fixed.Int26_6 217 | subPixelX uint32 218 | subPixelBiasX fixed.Int26_6 219 | subPixelMaskX fixed.Int26_6 220 | subPixelY uint32 221 | subPixelBiasY fixed.Int26_6 222 | subPixelMaskY fixed.Int26_6 223 | masks *image.Alpha 224 | glyphCache []glyphCacheEntry 225 | r raster.Rasterizer 226 | p raster.Painter 227 | paintOffset int 228 | maxw int 229 | maxh int 230 | glyphBuf GlyphBuf 231 | indexCache [indexCacheLen]indexCacheEntry 232 | 233 | // TODO: clip rectangle? 234 | } 235 | 236 | const indexCacheLen = 256 237 | 238 | func (a *face) index(r rune) Index { 239 | const mask = indexCacheLen - 1 240 | c := &a.indexCache[r&mask] 241 | if c.rune == r { 242 | return c.index 243 | } 244 | i := a.f.Index(r) 245 | c.rune = r 246 | c.index = i 247 | return i 248 | } 249 | 250 | // Close satisfies the font.Face interface. 251 | func (a *face) Close() error { return nil } 252 | 253 | // Metrics satisfies the font.Face interface. 254 | func (a *face) Metrics() font.Metrics { 255 | scale := float64(a.scale) 256 | fupe := float64(a.f.FUnitsPerEm()) 257 | return font.Metrics{ 258 | Height: a.scale, 259 | Ascent: fixed.Int26_6(math.Ceil(scale * float64(+a.f.ascent) / fupe)), 260 | Descent: fixed.Int26_6(math.Ceil(scale * float64(-a.f.descent) / fupe)), 261 | } 262 | } 263 | 264 | // Kern satisfies the font.Face interface. 265 | func (a *face) Kern(r0, r1 rune) fixed.Int26_6 { 266 | i0 := a.index(r0) 267 | i1 := a.index(r1) 268 | kern := a.f.Kern(a.scale, i0, i1) 269 | if a.hinting != font.HintingNone { 270 | kern = (kern + 32) &^ 63 271 | } 272 | return kern 273 | } 274 | 275 | // Glyph satisfies the font.Face interface. 276 | func (a *face) Glyph(dot fixed.Point26_6, r rune) ( 277 | dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) { 278 | 279 | // Quantize to the sub-pixel granularity. 280 | dotX := (dot.X + a.subPixelBiasX) & a.subPixelMaskX 281 | dotY := (dot.Y + a.subPixelBiasY) & a.subPixelMaskY 282 | 283 | // Split the coordinates into their integer and fractional parts. 284 | ix, fx := int(dotX>>6), dotX&0x3f 285 | iy, fy := int(dotY>>6), dotY&0x3f 286 | 287 | index := a.index(r) 288 | cIndex := uint32(index) 289 | cIndex = cIndex*a.subPixelX - uint32(fx/a.subPixelMaskX) 290 | cIndex = cIndex*a.subPixelY - uint32(fy/a.subPixelMaskY) 291 | cIndex &= uint32(len(a.glyphCache) - 1) 292 | a.paintOffset = a.maxh * int(cIndex) 293 | k := glyphCacheKey{ 294 | index: index, 295 | fx: uint8(fx), 296 | fy: uint8(fy), 297 | } 298 | var v glyphCacheVal 299 | if a.glyphCache[cIndex].key != k { 300 | var ok bool 301 | v, ok = a.rasterize(index, fx, fy) 302 | if !ok { 303 | return image.Rectangle{}, nil, image.Point{}, 0, false 304 | } 305 | a.glyphCache[cIndex] = glyphCacheEntry{k, v} 306 | } else { 307 | v = a.glyphCache[cIndex].val 308 | } 309 | 310 | dr.Min = image.Point{ 311 | X: ix + v.offset.X, 312 | Y: iy + v.offset.Y, 313 | } 314 | dr.Max = image.Point{ 315 | X: dr.Min.X + v.gw, 316 | Y: dr.Min.Y + v.gh, 317 | } 318 | return dr, a.masks, image.Point{Y: a.paintOffset}, v.advanceWidth, true 319 | } 320 | 321 | func (a *face) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) { 322 | if err := a.glyphBuf.Load(a.f, a.scale, a.index(r), a.hinting); err != nil { 323 | return fixed.Rectangle26_6{}, 0, false 324 | } 325 | xmin := +a.glyphBuf.Bounds.Min.X 326 | ymin := -a.glyphBuf.Bounds.Max.Y 327 | xmax := +a.glyphBuf.Bounds.Max.X 328 | ymax := -a.glyphBuf.Bounds.Min.Y 329 | if xmin > xmax || ymin > ymax { 330 | return fixed.Rectangle26_6{}, 0, false 331 | } 332 | return fixed.Rectangle26_6{ 333 | Min: fixed.Point26_6{ 334 | X: xmin, 335 | Y: ymin, 336 | }, 337 | Max: fixed.Point26_6{ 338 | X: xmax, 339 | Y: ymax, 340 | }, 341 | }, a.glyphBuf.AdvanceWidth, true 342 | } 343 | 344 | func (a *face) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) { 345 | if err := a.glyphBuf.Load(a.f, a.scale, a.index(r), a.hinting); err != nil { 346 | return 0, false 347 | } 348 | return a.glyphBuf.AdvanceWidth, true 349 | } 350 | 351 | // rasterize returns the advance width, integer-pixel offset to render at, and 352 | // the width and height of the given glyph at the given sub-pixel offsets. 353 | // 354 | // The 26.6 fixed point arguments fx and fy must be in the range [0, 1). 355 | func (a *face) rasterize(index Index, fx, fy fixed.Int26_6) (v glyphCacheVal, ok bool) { 356 | if err := a.glyphBuf.Load(a.f, a.scale, index, a.hinting); err != nil { 357 | return glyphCacheVal{}, false 358 | } 359 | // Calculate the integer-pixel bounds for the glyph. 360 | xmin := int(fx+a.glyphBuf.Bounds.Min.X) >> 6 361 | ymin := int(fy-a.glyphBuf.Bounds.Max.Y) >> 6 362 | xmax := int(fx+a.glyphBuf.Bounds.Max.X+0x3f) >> 6 363 | ymax := int(fy-a.glyphBuf.Bounds.Min.Y+0x3f) >> 6 364 | if xmin > xmax || ymin > ymax { 365 | return glyphCacheVal{}, false 366 | } 367 | // A TrueType's glyph's nodes can have negative co-ordinates, but the 368 | // rasterizer clips anything left of x=0 or above y=0. xmin and ymin are 369 | // the pixel offsets, based on the font's FUnit metrics, that let a 370 | // negative co-ordinate in TrueType space be non-negative in rasterizer 371 | // space. xmin and ymin are typically <= 0. 372 | fx -= fixed.Int26_6(xmin << 6) 373 | fy -= fixed.Int26_6(ymin << 6) 374 | // Rasterize the glyph's vectors. 375 | a.r.Clear() 376 | pixOffset := a.paintOffset * a.maxw 377 | clear(a.masks.Pix[pixOffset : pixOffset+a.maxw*a.maxh]) 378 | e0 := 0 379 | for _, e1 := range a.glyphBuf.Ends { 380 | a.drawContour(a.glyphBuf.Points[e0:e1], fx, fy) 381 | e0 = e1 382 | } 383 | a.r.Rasterize(a.p) 384 | return glyphCacheVal{ 385 | a.glyphBuf.AdvanceWidth, 386 | image.Point{xmin, ymin}, 387 | xmax - xmin, 388 | ymax - ymin, 389 | }, true 390 | } 391 | 392 | func clear(pix []byte) { 393 | for i := range pix { 394 | pix[i] = 0 395 | } 396 | } 397 | 398 | // drawContour draws the given closed contour with the given offset. 399 | func (a *face) drawContour(ps []Point, dx, dy fixed.Int26_6) { 400 | if len(ps) == 0 { 401 | return 402 | } 403 | 404 | // The low bit of each point's Flags value is whether the point is on the 405 | // curve. Truetype fonts only have quadratic Bézier curves, not cubics. 406 | // Thus, two consecutive off-curve points imply an on-curve point in the 407 | // middle of those two. 408 | // 409 | // See http://chanae.walon.org/pub/ttf/ttf_glyphs.htm for more details. 410 | 411 | // ps[0] is a truetype.Point measured in FUnits and positive Y going 412 | // upwards. start is the same thing measured in fixed point units and 413 | // positive Y going downwards, and offset by (dx, dy). 414 | start := fixed.Point26_6{ 415 | X: dx + ps[0].X, 416 | Y: dy - ps[0].Y, 417 | } 418 | var others []Point 419 | if ps[0].Flags&0x01 != 0 { 420 | others = ps[1:] 421 | } else { 422 | last := fixed.Point26_6{ 423 | X: dx + ps[len(ps)-1].X, 424 | Y: dy - ps[len(ps)-1].Y, 425 | } 426 | if ps[len(ps)-1].Flags&0x01 != 0 { 427 | start = last 428 | others = ps[:len(ps)-1] 429 | } else { 430 | start = fixed.Point26_6{ 431 | X: (start.X + last.X) / 2, 432 | Y: (start.Y + last.Y) / 2, 433 | } 434 | others = ps 435 | } 436 | } 437 | a.r.Start(start) 438 | q0, on0 := start, true 439 | for _, p := range others { 440 | q := fixed.Point26_6{ 441 | X: dx + p.X, 442 | Y: dy - p.Y, 443 | } 444 | on := p.Flags&0x01 != 0 445 | if on { 446 | if on0 { 447 | a.r.Add1(q) 448 | } else { 449 | a.r.Add2(q0, q) 450 | } 451 | } else { 452 | if on0 { 453 | // No-op. 454 | } else { 455 | mid := fixed.Point26_6{ 456 | X: (q0.X + q.X) / 2, 457 | Y: (q0.Y + q.Y) / 2, 458 | } 459 | a.r.Add2(q0, mid) 460 | } 461 | } 462 | q0, on0 = q, on 463 | } 464 | // Close the curve. 465 | if on0 { 466 | a.r.Add1(start) 467 | } else { 468 | a.r.Add2(q0, start) 469 | } 470 | } 471 | 472 | // facePainter is like a raster.AlphaSrcPainter, with an additional Y offset 473 | // (face.paintOffset) to the painted spans. 474 | type facePainter struct { 475 | a *face 476 | } 477 | 478 | func (p facePainter) Paint(ss []raster.Span, done bool) { 479 | m := p.a.masks 480 | b := m.Bounds() 481 | b.Min.Y = p.a.paintOffset 482 | b.Max.Y = p.a.paintOffset + p.a.maxh 483 | for _, s := range ss { 484 | s.Y += p.a.paintOffset 485 | if s.Y < b.Min.Y { 486 | continue 487 | } 488 | if s.Y >= b.Max.Y { 489 | return 490 | } 491 | if s.X0 < b.Min.X { 492 | s.X0 = b.Min.X 493 | } 494 | if s.X1 > b.Max.X { 495 | s.X1 = b.Max.X 496 | } 497 | if s.X0 >= s.X1 { 498 | continue 499 | } 500 | base := (s.Y-m.Rect.Min.Y)*m.Stride - m.Rect.Min.X 501 | p := m.Pix[base+s.X0 : base+s.X1] 502 | color := uint8(s.Alpha >> 8) 503 | for i := range p { 504 | p[i] = color 505 | } 506 | } 507 | } 508 | -------------------------------------------------------------------------------- /truetype/face_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | package truetype 7 | 8 | import ( 9 | "image" 10 | "image/draw" 11 | "io/ioutil" 12 | "strings" 13 | "testing" 14 | 15 | "golang.org/x/image/font" 16 | "golang.org/x/image/math/fixed" 17 | ) 18 | 19 | func BenchmarkDrawString(b *testing.B) { 20 | data, err := ioutil.ReadFile("../licenses/gpl.txt") 21 | if err != nil { 22 | b.Fatal(err) 23 | } 24 | lines := strings.Split(string(data), "\n") 25 | data, err = ioutil.ReadFile("../testdata/luxisr.ttf") 26 | if err != nil { 27 | b.Fatal(err) 28 | } 29 | f, err := Parse(data) 30 | if err != nil { 31 | b.Fatal(err) 32 | } 33 | dst := image.NewRGBA(image.Rect(0, 0, 800, 600)) 34 | draw.Draw(dst, dst.Bounds(), image.White, image.ZP, draw.Src) 35 | d := &font.Drawer{ 36 | Dst: dst, 37 | Src: image.Black, 38 | Face: NewFace(f, nil), 39 | } 40 | b.ReportAllocs() 41 | b.ResetTimer() 42 | for i := 0; i < b.N; i++ { 43 | for j, line := range lines { 44 | d.Dot = fixed.P(0, (j*16)%600) 45 | d.DrawString(line) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /truetype/glyph.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | package truetype 7 | 8 | import ( 9 | "golang.org/x/image/font" 10 | "golang.org/x/image/math/fixed" 11 | ) 12 | 13 | // TODO: implement VerticalHinting. 14 | 15 | // A Point is a co-ordinate pair plus whether it is 'on' a contour or an 'off' 16 | // control point. 17 | type Point struct { 18 | X, Y fixed.Int26_6 19 | // The Flags' LSB means whether or not this Point is 'on' the contour. 20 | // Other bits are reserved for internal use. 21 | Flags uint32 22 | } 23 | 24 | // A GlyphBuf holds a glyph's contours. A GlyphBuf can be re-used to load a 25 | // series of glyphs from a Font. 26 | type GlyphBuf struct { 27 | // AdvanceWidth is the glyph's advance width. 28 | AdvanceWidth fixed.Int26_6 29 | // Bounds is the glyph's bounding box. 30 | Bounds fixed.Rectangle26_6 31 | // Points contains all Points from all contours of the glyph. If hinting 32 | // was used to load a glyph then Unhinted contains those Points before they 33 | // were hinted, and InFontUnits contains those Points before they were 34 | // hinted and scaled. 35 | Points, Unhinted, InFontUnits []Point 36 | // Ends is the point indexes of the end point of each contour. The length 37 | // of Ends is the number of contours in the glyph. The i'th contour 38 | // consists of points Points[Ends[i-1]:Ends[i]], where Ends[-1] is 39 | // interpreted to mean zero. 40 | Ends []int 41 | 42 | font *Font 43 | scale fixed.Int26_6 44 | hinting font.Hinting 45 | hinter hinter 46 | // phantomPoints are the co-ordinates of the synthetic phantom points 47 | // used for hinting and bounding box calculations. 48 | phantomPoints [4]Point 49 | // pp1x is the X co-ordinate of the first phantom point. The '1' is 50 | // using 1-based indexing; pp1x is almost always phantomPoints[0].X. 51 | // TODO: eliminate this and consistently use phantomPoints[0].X. 52 | pp1x fixed.Int26_6 53 | // metricsSet is whether the glyph's metrics have been set yet. For a 54 | // compound glyph, a sub-glyph may override the outer glyph's metrics. 55 | metricsSet bool 56 | // tmp is a scratch buffer. 57 | tmp []Point 58 | } 59 | 60 | // Flags for decoding a glyph's contours. These flags are documented at 61 | // http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html. 62 | const ( 63 | flagOnCurve = 1 << iota 64 | flagXShortVector 65 | flagYShortVector 66 | flagRepeat 67 | flagPositiveXShortVector 68 | flagPositiveYShortVector 69 | 70 | // The remaining flags are for internal use. 71 | flagTouchedX 72 | flagTouchedY 73 | ) 74 | 75 | // The same flag bits (0x10 and 0x20) are overloaded to have two meanings, 76 | // dependent on the value of the flag{X,Y}ShortVector bits. 77 | const ( 78 | flagThisXIsSame = flagPositiveXShortVector 79 | flagThisYIsSame = flagPositiveYShortVector 80 | ) 81 | 82 | // Load loads a glyph's contours from a Font, overwriting any previously loaded 83 | // contours for this GlyphBuf. scale is the number of 26.6 fixed point units in 84 | // 1 em, i is the glyph index, and h is the hinting policy. 85 | func (g *GlyphBuf) Load(f *Font, scale fixed.Int26_6, i Index, h font.Hinting) error { 86 | g.Points = g.Points[:0] 87 | g.Unhinted = g.Unhinted[:0] 88 | g.InFontUnits = g.InFontUnits[:0] 89 | g.Ends = g.Ends[:0] 90 | g.font = f 91 | g.hinting = h 92 | g.scale = scale 93 | g.pp1x = 0 94 | g.phantomPoints = [4]Point{} 95 | g.metricsSet = false 96 | 97 | if h != font.HintingNone { 98 | if err := g.hinter.init(f, scale); err != nil { 99 | return err 100 | } 101 | } 102 | if err := g.load(0, i, true); err != nil { 103 | return err 104 | } 105 | // TODO: this selection of either g.pp1x or g.phantomPoints[0].X isn't ideal, 106 | // and should be cleaned up once we have all the testScaling tests passing, 107 | // plus additional tests for Freetype-Go's bounding boxes matching C Freetype's. 108 | pp1x := g.pp1x 109 | if h != font.HintingNone { 110 | pp1x = g.phantomPoints[0].X 111 | } 112 | if pp1x != 0 { 113 | for i := range g.Points { 114 | g.Points[i].X -= pp1x 115 | } 116 | } 117 | 118 | advanceWidth := g.phantomPoints[1].X - g.phantomPoints[0].X 119 | if h != font.HintingNone { 120 | if len(f.hdmx) >= 8 { 121 | if n := u32(f.hdmx, 4); n > 3+uint32(i) { 122 | for hdmx := f.hdmx[8:]; uint32(len(hdmx)) >= n; hdmx = hdmx[n:] { 123 | if fixed.Int26_6(hdmx[0]) == scale>>6 { 124 | advanceWidth = fixed.Int26_6(hdmx[2+i]) << 6 125 | break 126 | } 127 | } 128 | } 129 | } 130 | advanceWidth = (advanceWidth + 32) &^ 63 131 | } 132 | g.AdvanceWidth = advanceWidth 133 | 134 | // Set g.Bounds to the 'control box', which is the bounding box of the 135 | // Bézier curves' control points. This is easier to calculate, no smaller 136 | // than and often equal to the tightest possible bounding box of the curves 137 | // themselves. This approach is what C Freetype does. We can't just scale 138 | // the nominal bounding box in the glyf data as the hinting process and 139 | // phantom point adjustment may move points outside of that box. 140 | if len(g.Points) == 0 { 141 | g.Bounds = fixed.Rectangle26_6{} 142 | } else { 143 | p := g.Points[0] 144 | g.Bounds.Min.X = p.X 145 | g.Bounds.Max.X = p.X 146 | g.Bounds.Min.Y = p.Y 147 | g.Bounds.Max.Y = p.Y 148 | for _, p := range g.Points[1:] { 149 | if g.Bounds.Min.X > p.X { 150 | g.Bounds.Min.X = p.X 151 | } else if g.Bounds.Max.X < p.X { 152 | g.Bounds.Max.X = p.X 153 | } 154 | if g.Bounds.Min.Y > p.Y { 155 | g.Bounds.Min.Y = p.Y 156 | } else if g.Bounds.Max.Y < p.Y { 157 | g.Bounds.Max.Y = p.Y 158 | } 159 | } 160 | // Snap the box to the grid, if hinting is on. 161 | if h != font.HintingNone { 162 | g.Bounds.Min.X &^= 63 163 | g.Bounds.Min.Y &^= 63 164 | g.Bounds.Max.X += 63 165 | g.Bounds.Max.X &^= 63 166 | g.Bounds.Max.Y += 63 167 | g.Bounds.Max.Y &^= 63 168 | } 169 | } 170 | return nil 171 | } 172 | 173 | func (g *GlyphBuf) load(recursion uint32, i Index, useMyMetrics bool) (err error) { 174 | // The recursion limit here is arbitrary, but defends against malformed glyphs. 175 | if recursion >= 32 { 176 | return UnsupportedError("excessive compound glyph recursion") 177 | } 178 | // Find the relevant slice of g.font.glyf. 179 | var g0, g1 uint32 180 | if g.font.locaOffsetFormat == locaOffsetFormatShort { 181 | g0 = 2 * uint32(u16(g.font.loca, 2*int(i))) 182 | g1 = 2 * uint32(u16(g.font.loca, 2*int(i)+2)) 183 | } else { 184 | g0 = u32(g.font.loca, 4*int(i)) 185 | g1 = u32(g.font.loca, 4*int(i)+4) 186 | } 187 | 188 | // Decode the contour count and nominal bounding box, from the first 189 | // 10 bytes of the glyf data. boundsYMin and boundsXMax, at offsets 4 190 | // and 6, are unused. 191 | glyf, ne, boundsXMin, boundsYMax := []byte(nil), 0, fixed.Int26_6(0), fixed.Int26_6(0) 192 | if g0+10 <= g1 { 193 | glyf = g.font.glyf[g0:g1] 194 | ne = int(int16(u16(glyf, 0))) 195 | boundsXMin = fixed.Int26_6(int16(u16(glyf, 2))) 196 | boundsYMax = fixed.Int26_6(int16(u16(glyf, 8))) 197 | } 198 | 199 | // Create the phantom points. 200 | uhm, pp1x := g.font.unscaledHMetric(i), fixed.Int26_6(0) 201 | uvm := g.font.unscaledVMetric(i, boundsYMax) 202 | g.phantomPoints = [4]Point{ 203 | {X: boundsXMin - uhm.LeftSideBearing}, 204 | {X: boundsXMin - uhm.LeftSideBearing + uhm.AdvanceWidth}, 205 | {X: uhm.AdvanceWidth / 2, Y: boundsYMax + uvm.TopSideBearing}, 206 | {X: uhm.AdvanceWidth / 2, Y: boundsYMax + uvm.TopSideBearing - uvm.AdvanceHeight}, 207 | } 208 | if len(glyf) == 0 { 209 | g.addPhantomsAndScale(len(g.Points), len(g.Points), true, true) 210 | copy(g.phantomPoints[:], g.Points[len(g.Points)-4:]) 211 | g.Points = g.Points[:len(g.Points)-4] 212 | // TODO: also trim g.InFontUnits and g.Unhinted? 213 | return nil 214 | } 215 | 216 | // Load and hint the contours. 217 | if ne < 0 { 218 | if ne != -1 { 219 | // http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html says that 220 | // "the values -2, -3, and so forth, are reserved for future use." 221 | return UnsupportedError("negative number of contours") 222 | } 223 | pp1x = g.font.scale(g.scale * (boundsXMin - uhm.LeftSideBearing)) 224 | if err := g.loadCompound(recursion, uhm, i, glyf, useMyMetrics); err != nil { 225 | return err 226 | } 227 | } else { 228 | np0, ne0 := len(g.Points), len(g.Ends) 229 | program := g.loadSimple(glyf, ne) 230 | g.addPhantomsAndScale(np0, np0, true, true) 231 | pp1x = g.Points[len(g.Points)-4].X 232 | if g.hinting != font.HintingNone { 233 | if len(program) != 0 { 234 | err := g.hinter.run( 235 | program, 236 | g.Points[np0:], 237 | g.Unhinted[np0:], 238 | g.InFontUnits[np0:], 239 | g.Ends[ne0:], 240 | ) 241 | if err != nil { 242 | return err 243 | } 244 | } 245 | // Drop the four phantom points. 246 | g.InFontUnits = g.InFontUnits[:len(g.InFontUnits)-4] 247 | g.Unhinted = g.Unhinted[:len(g.Unhinted)-4] 248 | } 249 | if useMyMetrics { 250 | copy(g.phantomPoints[:], g.Points[len(g.Points)-4:]) 251 | } 252 | g.Points = g.Points[:len(g.Points)-4] 253 | if np0 != 0 { 254 | // The hinting program expects the []Ends values to be indexed 255 | // relative to the inner glyph, not the outer glyph, so we delay 256 | // adding np0 until after the hinting program (if any) has run. 257 | for i := ne0; i < len(g.Ends); i++ { 258 | g.Ends[i] += np0 259 | } 260 | } 261 | } 262 | if useMyMetrics && !g.metricsSet { 263 | g.metricsSet = true 264 | g.pp1x = pp1x 265 | } 266 | return nil 267 | } 268 | 269 | // loadOffset is the initial offset for loadSimple and loadCompound. The first 270 | // 10 bytes are the number of contours and the bounding box. 271 | const loadOffset = 10 272 | 273 | func (g *GlyphBuf) loadSimple(glyf []byte, ne int) (program []byte) { 274 | offset := loadOffset 275 | for i := 0; i < ne; i++ { 276 | g.Ends = append(g.Ends, 1+int(u16(glyf, offset))) 277 | offset += 2 278 | } 279 | 280 | // Note the TrueType hinting instructions. 281 | instrLen := int(u16(glyf, offset)) 282 | offset += 2 283 | program = glyf[offset : offset+instrLen] 284 | offset += instrLen 285 | 286 | if ne == 0 { 287 | return program 288 | } 289 | 290 | np0 := len(g.Points) 291 | np1 := np0 + int(g.Ends[len(g.Ends)-1]) 292 | 293 | // Decode the flags. 294 | for i := np0; i < np1; { 295 | c := uint32(glyf[offset]) 296 | offset++ 297 | g.Points = append(g.Points, Point{Flags: c}) 298 | i++ 299 | if c&flagRepeat != 0 { 300 | count := glyf[offset] 301 | offset++ 302 | for ; count > 0; count-- { 303 | g.Points = append(g.Points, Point{Flags: c}) 304 | i++ 305 | } 306 | } 307 | } 308 | 309 | // Decode the co-ordinates. 310 | var x int16 311 | for i := np0; i < np1; i++ { 312 | f := g.Points[i].Flags 313 | if f&flagXShortVector != 0 { 314 | dx := int16(glyf[offset]) 315 | offset++ 316 | if f&flagPositiveXShortVector == 0 { 317 | x -= dx 318 | } else { 319 | x += dx 320 | } 321 | } else if f&flagThisXIsSame == 0 { 322 | x += int16(u16(glyf, offset)) 323 | offset += 2 324 | } 325 | g.Points[i].X = fixed.Int26_6(x) 326 | } 327 | var y int16 328 | for i := np0; i < np1; i++ { 329 | f := g.Points[i].Flags 330 | if f&flagYShortVector != 0 { 331 | dy := int16(glyf[offset]) 332 | offset++ 333 | if f&flagPositiveYShortVector == 0 { 334 | y -= dy 335 | } else { 336 | y += dy 337 | } 338 | } else if f&flagThisYIsSame == 0 { 339 | y += int16(u16(glyf, offset)) 340 | offset += 2 341 | } 342 | g.Points[i].Y = fixed.Int26_6(y) 343 | } 344 | 345 | return program 346 | } 347 | 348 | func (g *GlyphBuf) loadCompound(recursion uint32, uhm HMetric, i Index, 349 | glyf []byte, useMyMetrics bool) error { 350 | 351 | // Flags for decoding a compound glyph. These flags are documented at 352 | // http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html. 353 | const ( 354 | flagArg1And2AreWords = 1 << iota 355 | flagArgsAreXYValues 356 | flagRoundXYToGrid 357 | flagWeHaveAScale 358 | flagUnused 359 | flagMoreComponents 360 | flagWeHaveAnXAndYScale 361 | flagWeHaveATwoByTwo 362 | flagWeHaveInstructions 363 | flagUseMyMetrics 364 | flagOverlapCompound 365 | ) 366 | np0, ne0 := len(g.Points), len(g.Ends) 367 | offset := loadOffset 368 | for { 369 | flags := u16(glyf, offset) 370 | component := Index(u16(glyf, offset+2)) 371 | dx, dy, transform, hasTransform := fixed.Int26_6(0), fixed.Int26_6(0), [4]int16{}, false 372 | if flags&flagArg1And2AreWords != 0 { 373 | dx = fixed.Int26_6(int16(u16(glyf, offset+4))) 374 | dy = fixed.Int26_6(int16(u16(glyf, offset+6))) 375 | offset += 8 376 | } else { 377 | dx = fixed.Int26_6(int16(int8(glyf[offset+4]))) 378 | dy = fixed.Int26_6(int16(int8(glyf[offset+5]))) 379 | offset += 6 380 | } 381 | if flags&flagArgsAreXYValues == 0 { 382 | return UnsupportedError("compound glyph transform vector") 383 | } 384 | if flags&(flagWeHaveAScale|flagWeHaveAnXAndYScale|flagWeHaveATwoByTwo) != 0 { 385 | hasTransform = true 386 | switch { 387 | case flags&flagWeHaveAScale != 0: 388 | transform[0] = int16(u16(glyf, offset+0)) 389 | transform[3] = transform[0] 390 | offset += 2 391 | case flags&flagWeHaveAnXAndYScale != 0: 392 | transform[0] = int16(u16(glyf, offset+0)) 393 | transform[3] = int16(u16(glyf, offset+2)) 394 | offset += 4 395 | case flags&flagWeHaveATwoByTwo != 0: 396 | transform[0] = int16(u16(glyf, offset+0)) 397 | transform[1] = int16(u16(glyf, offset+2)) 398 | transform[2] = int16(u16(glyf, offset+4)) 399 | transform[3] = int16(u16(glyf, offset+6)) 400 | offset += 8 401 | } 402 | } 403 | savedPP := g.phantomPoints 404 | np0 := len(g.Points) 405 | componentUMM := useMyMetrics && (flags&flagUseMyMetrics != 0) 406 | if err := g.load(recursion+1, component, componentUMM); err != nil { 407 | return err 408 | } 409 | if flags&flagUseMyMetrics == 0 { 410 | g.phantomPoints = savedPP 411 | } 412 | if hasTransform { 413 | for j := np0; j < len(g.Points); j++ { 414 | p := &g.Points[j] 415 | newX := 0 + 416 | fixed.Int26_6((int64(p.X)*int64(transform[0])+1<<13)>>14) + 417 | fixed.Int26_6((int64(p.Y)*int64(transform[2])+1<<13)>>14) 418 | newY := 0 + 419 | fixed.Int26_6((int64(p.X)*int64(transform[1])+1<<13)>>14) + 420 | fixed.Int26_6((int64(p.Y)*int64(transform[3])+1<<13)>>14) 421 | p.X, p.Y = newX, newY 422 | } 423 | } 424 | dx = g.font.scale(g.scale * dx) 425 | dy = g.font.scale(g.scale * dy) 426 | if flags&flagRoundXYToGrid != 0 { 427 | dx = (dx + 32) &^ 63 428 | dy = (dy + 32) &^ 63 429 | } 430 | for j := np0; j < len(g.Points); j++ { 431 | p := &g.Points[j] 432 | p.X += dx 433 | p.Y += dy 434 | } 435 | // TODO: also adjust g.InFontUnits and g.Unhinted? 436 | if flags&flagMoreComponents == 0 { 437 | break 438 | } 439 | } 440 | 441 | instrLen := 0 442 | if g.hinting != font.HintingNone && offset+2 <= len(glyf) { 443 | instrLen = int(u16(glyf, offset)) 444 | offset += 2 445 | } 446 | 447 | g.addPhantomsAndScale(np0, len(g.Points), false, instrLen > 0) 448 | points, ends := g.Points[np0:], g.Ends[ne0:] 449 | g.Points = g.Points[:len(g.Points)-4] 450 | for j := range points { 451 | points[j].Flags &^= flagTouchedX | flagTouchedY 452 | } 453 | 454 | if instrLen == 0 { 455 | if !g.metricsSet { 456 | copy(g.phantomPoints[:], points[len(points)-4:]) 457 | } 458 | return nil 459 | } 460 | 461 | // Hint the compound glyph. 462 | program := glyf[offset : offset+instrLen] 463 | // Temporarily adjust the ends to be relative to this compound glyph. 464 | if np0 != 0 { 465 | for i := range ends { 466 | ends[i] -= np0 467 | } 468 | } 469 | // Hinting instructions of a composite glyph completely refer to the 470 | // (already) hinted subglyphs. 471 | g.tmp = append(g.tmp[:0], points...) 472 | if err := g.hinter.run(program, points, g.tmp, g.tmp, ends); err != nil { 473 | return err 474 | } 475 | if np0 != 0 { 476 | for i := range ends { 477 | ends[i] += np0 478 | } 479 | } 480 | if !g.metricsSet { 481 | copy(g.phantomPoints[:], points[len(points)-4:]) 482 | } 483 | return nil 484 | } 485 | 486 | func (g *GlyphBuf) addPhantomsAndScale(np0, np1 int, simple, adjust bool) { 487 | // Add the four phantom points. 488 | g.Points = append(g.Points, g.phantomPoints[:]...) 489 | // Scale the points. 490 | if simple && g.hinting != font.HintingNone { 491 | g.InFontUnits = append(g.InFontUnits, g.Points[np1:]...) 492 | } 493 | for i := np1; i < len(g.Points); i++ { 494 | p := &g.Points[i] 495 | p.X = g.font.scale(g.scale * p.X) 496 | p.Y = g.font.scale(g.scale * p.Y) 497 | } 498 | if g.hinting == font.HintingNone { 499 | return 500 | } 501 | // Round the 1st phantom point to the grid, shifting all other points equally. 502 | // Note that "all other points" starts from np0, not np1. 503 | // TODO: delete this adjustment and the np0/np1 distinction, when 504 | // we update the compatibility tests to C Freetype 2.5.3. 505 | // See http://git.savannah.gnu.org/cgit/freetype/freetype2.git/commit/?id=05c786d990390a7ca18e62962641dac740bacb06 506 | if adjust { 507 | pp1x := g.Points[len(g.Points)-4].X 508 | if dx := ((pp1x + 32) &^ 63) - pp1x; dx != 0 { 509 | for i := np0; i < len(g.Points); i++ { 510 | g.Points[i].X += dx 511 | } 512 | } 513 | } 514 | if simple { 515 | g.Unhinted = append(g.Unhinted, g.Points[np1:]...) 516 | } 517 | // Round the 2nd and 4th phantom point to the grid. 518 | p := &g.Points[len(g.Points)-3] 519 | p.X = (p.X + 32) &^ 63 520 | p = &g.Points[len(g.Points)-1] 521 | p.Y = (p.Y + 32) &^ 63 522 | } 523 | -------------------------------------------------------------------------------- /truetype/hint_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | package truetype 7 | 8 | import ( 9 | "reflect" 10 | "strings" 11 | "testing" 12 | 13 | "golang.org/x/image/math/fixed" 14 | ) 15 | 16 | func TestBytecode(t *testing.T) { 17 | testCases := []struct { 18 | desc string 19 | prog []byte 20 | want []int32 21 | errStr string 22 | }{ 23 | { 24 | "underflow", 25 | []byte{ 26 | opDUP, 27 | }, 28 | nil, 29 | "underflow", 30 | }, 31 | { 32 | "infinite loop", 33 | []byte{ 34 | opPUSHW000, // [-1] 35 | 0xff, 36 | 0xff, 37 | opDUP, // [-1, -1] 38 | opJMPR, // [-1] 39 | }, 40 | nil, 41 | "too many steps", 42 | }, 43 | { 44 | "unbalanced if/else", 45 | []byte{ 46 | opPUSHB000, // [0] 47 | 0, 48 | opIF, 49 | }, 50 | nil, 51 | "unbalanced", 52 | }, 53 | { 54 | "vector set/gets", 55 | []byte{ 56 | opSVTCA1, // [] 57 | opGPV, // [0x4000, 0] 58 | opSVTCA0, // [0x4000, 0] 59 | opGFV, // [0x4000, 0, 0, 0x4000] 60 | opNEG, // [0x4000, 0, 0, -0x4000] 61 | opSPVFS, // [0x4000, 0] 62 | opSFVTPV, // [0x4000, 0] 63 | opPUSHB000, // [0x4000, 0, 1] 64 | 1, 65 | opGFV, // [0x4000, 0, 1, 0, -0x4000] 66 | opPUSHB000, // [0x4000, 0, 1, 0, -0x4000, 2] 67 | 2, 68 | }, 69 | []int32{0x4000, 0, 1, 0, -0x4000, 2}, 70 | "", 71 | }, 72 | { 73 | "jumps", 74 | []byte{ 75 | opPUSHB001, // [10, 2] 76 | 10, 77 | 2, 78 | opJMPR, // [10] 79 | opDUP, // not executed 80 | opDUP, // [10, 10] 81 | opPUSHB010, // [10, 10, 20, 2, 1] 82 | 20, 83 | 2, 84 | 1, 85 | opJROT, // [10, 10, 20] 86 | opDUP, // not executed 87 | opDUP, // [10, 10, 20, 20] 88 | opPUSHB010, // [10, 10, 20, 20, 30, 2, 1] 89 | 30, 90 | 2, 91 | 1, 92 | opJROF, // [10, 10, 20, 20, 30] 93 | opDUP, // [10, 10, 20, 20, 30, 30] 94 | opDUP, // [10, 10, 20, 20, 30, 30, 30] 95 | }, 96 | []int32{10, 10, 20, 20, 30, 30, 30}, 97 | "", 98 | }, 99 | { 100 | "stack ops", 101 | []byte{ 102 | opPUSHB010, // [10, 20, 30] 103 | 10, 104 | 20, 105 | 30, 106 | opCLEAR, // [] 107 | opPUSHB010, // [40, 50, 60] 108 | 40, 109 | 50, 110 | 60, 111 | opSWAP, // [40, 60, 50] 112 | opDUP, // [40, 60, 50, 50] 113 | opDUP, // [40, 60, 50, 50, 50] 114 | opPOP, // [40, 60, 50, 50] 115 | opDEPTH, // [40, 60, 50, 50, 4] 116 | opCINDEX, // [40, 60, 50, 50, 40] 117 | opPUSHB000, // [40, 60, 50, 50, 40, 4] 118 | 4, 119 | opMINDEX, // [40, 50, 50, 40, 60] 120 | }, 121 | []int32{40, 50, 50, 40, 60}, 122 | "", 123 | }, 124 | { 125 | "push ops", 126 | []byte{ 127 | opPUSHB000, // [255] 128 | 255, 129 | opPUSHW001, // [255, -2, 253] 130 | 255, 131 | 254, 132 | 0, 133 | 253, 134 | opNPUSHB, // [1, -2, 253, 1, 2] 135 | 2, 136 | 1, 137 | 2, 138 | opNPUSHW, // [1, -2, 253, 1, 2, 0x0405, 0x0607, 0x0809] 139 | 3, 140 | 4, 141 | 5, 142 | 6, 143 | 7, 144 | 8, 145 | 9, 146 | }, 147 | []int32{255, -2, 253, 1, 2, 0x0405, 0x0607, 0x0809}, 148 | "", 149 | }, 150 | { 151 | "store ops", 152 | []byte{ 153 | opPUSHB011, // [1, 22, 3, 44] 154 | 1, 155 | 22, 156 | 3, 157 | 44, 158 | opWS, // [1, 22] 159 | opWS, // [] 160 | opPUSHB000, // [3] 161 | 3, 162 | opRS, // [44] 163 | }, 164 | []int32{44}, 165 | "", 166 | }, 167 | { 168 | "comparison ops", 169 | []byte{ 170 | opPUSHB001, // [10, 20] 171 | 10, 172 | 20, 173 | opLT, // [1] 174 | opPUSHB001, // [1, 10, 20] 175 | 10, 176 | 20, 177 | opLTEQ, // [1, 1] 178 | opPUSHB001, // [1, 1, 10, 20] 179 | 10, 180 | 20, 181 | opGT, // [1, 1, 0] 182 | opPUSHB001, // [1, 1, 0, 10, 20] 183 | 10, 184 | 20, 185 | opGTEQ, // [1, 1, 0, 0] 186 | opEQ, // [1, 1, 1] 187 | opNEQ, // [1, 0] 188 | }, 189 | []int32{1, 0}, 190 | "", 191 | }, 192 | { 193 | "odd/even", 194 | // Calculate odd(2+31/64), odd(2+32/64), even(2), even(1). 195 | []byte{ 196 | opPUSHB000, // [159] 197 | 159, 198 | opODD, // [0] 199 | opPUSHB000, // [0, 160] 200 | 160, 201 | opODD, // [0, 1] 202 | opPUSHB000, // [0, 1, 128] 203 | 128, 204 | opEVEN, // [0, 1, 1] 205 | opPUSHB000, // [0, 1, 1, 64] 206 | 64, 207 | opEVEN, // [0, 1, 1, 0] 208 | }, 209 | []int32{0, 1, 1, 0}, 210 | "", 211 | }, 212 | { 213 | "if true", 214 | []byte{ 215 | opPUSHB001, // [255, 1] 216 | 255, 217 | 1, 218 | opIF, 219 | opPUSHB000, // [255, 2] 220 | 2, 221 | opEIF, 222 | opPUSHB000, // [255, 2, 254] 223 | 254, 224 | }, 225 | []int32{255, 2, 254}, 226 | "", 227 | }, 228 | { 229 | "if false", 230 | []byte{ 231 | opPUSHB001, // [255, 0] 232 | 255, 233 | 0, 234 | opIF, 235 | opPUSHB000, // [255] 236 | 2, 237 | opEIF, 238 | opPUSHB000, // [255, 254] 239 | 254, 240 | }, 241 | []int32{255, 254}, 242 | "", 243 | }, 244 | { 245 | "if/else true", 246 | []byte{ 247 | opPUSHB000, // [1] 248 | 1, 249 | opIF, 250 | opPUSHB000, // [2] 251 | 2, 252 | opELSE, 253 | opPUSHB000, // not executed 254 | 3, 255 | opEIF, 256 | }, 257 | []int32{2}, 258 | "", 259 | }, 260 | { 261 | "if/else false", 262 | []byte{ 263 | opPUSHB000, // [0] 264 | 0, 265 | opIF, 266 | opPUSHB000, // not executed 267 | 2, 268 | opELSE, 269 | opPUSHB000, // [3] 270 | 3, 271 | opEIF, 272 | }, 273 | []int32{3}, 274 | "", 275 | }, 276 | { 277 | "if/else true if/else false", 278 | // 0x58 is the opcode for opIF. The literal 0x58s below are pushed data. 279 | []byte{ 280 | opPUSHB010, // [255, 0, 1] 281 | 255, 282 | 0, 283 | 1, 284 | opIF, 285 | opIF, 286 | opPUSHB001, // not executed 287 | 0x58, 288 | 0x58, 289 | opELSE, 290 | opPUSHW000, // [255, 0x5858] 291 | 0x58, 292 | 0x58, 293 | opEIF, 294 | opELSE, 295 | opIF, 296 | opNPUSHB, // not executed 297 | 3, 298 | 0x58, 299 | 0x58, 300 | 0x58, 301 | opELSE, 302 | opNPUSHW, // not executed 303 | 2, 304 | 0x58, 305 | 0x58, 306 | 0x58, 307 | 0x58, 308 | opEIF, 309 | opEIF, 310 | opPUSHB000, // [255, 0x5858, 254] 311 | 254, 312 | }, 313 | []int32{255, 0x5858, 254}, 314 | "", 315 | }, 316 | { 317 | "if/else false if/else true", 318 | // 0x58 is the opcode for opIF. The literal 0x58s below are pushed data. 319 | []byte{ 320 | opPUSHB010, // [255, 1, 0] 321 | 255, 322 | 1, 323 | 0, 324 | opIF, 325 | opIF, 326 | opPUSHB001, // not executed 327 | 0x58, 328 | 0x58, 329 | opELSE, 330 | opPUSHW000, // not executed 331 | 0x58, 332 | 0x58, 333 | opEIF, 334 | opELSE, 335 | opIF, 336 | opNPUSHB, // [255, 0x58, 0x58, 0x58] 337 | 3, 338 | 0x58, 339 | 0x58, 340 | 0x58, 341 | opELSE, 342 | opNPUSHW, // not executed 343 | 2, 344 | 0x58, 345 | 0x58, 346 | 0x58, 347 | 0x58, 348 | opEIF, 349 | opEIF, 350 | opPUSHB000, // [255, 0x58, 0x58, 0x58, 254] 351 | 254, 352 | }, 353 | []int32{255, 0x58, 0x58, 0x58, 254}, 354 | "", 355 | }, 356 | { 357 | "logical ops", 358 | []byte{ 359 | opPUSHB010, // [0, 10, 20] 360 | 0, 361 | 10, 362 | 20, 363 | opAND, // [0, 1] 364 | opOR, // [1] 365 | opNOT, // [0] 366 | }, 367 | []int32{0}, 368 | "", 369 | }, 370 | { 371 | "arithmetic ops", 372 | // Calculate abs((-(1 - (2*3)))/2 + 1/64). 373 | // The answer is 5/2 + 1/64 in ideal numbers, or 161 in 26.6 fixed point math. 374 | []byte{ 375 | opPUSHB010, // [64, 128, 192] 376 | 1 << 6, 377 | 2 << 6, 378 | 3 << 6, 379 | opMUL, // [64, 384] 380 | opSUB, // [-320] 381 | opNEG, // [320] 382 | opPUSHB000, // [320, 128] 383 | 2 << 6, 384 | opDIV, // [160] 385 | opPUSHB000, // [160, 1] 386 | 1, 387 | opADD, // [161] 388 | opABS, // [161] 389 | }, 390 | []int32{161}, 391 | "", 392 | }, 393 | { 394 | "floor, ceiling", 395 | []byte{ 396 | opPUSHB000, // [96] 397 | 96, 398 | opFLOOR, // [64] 399 | opPUSHB000, // [64, 96] 400 | 96, 401 | opCEILING, // [64, 128] 402 | }, 403 | []int32{64, 128}, 404 | "", 405 | }, 406 | { 407 | "rounding", 408 | // Round 1.40625 (which is 90/64) under various rounding policies. 409 | // See figure 20 of https://developer.apple.com/fonts/TTRefMan/RM02/Chap2.html#rounding 410 | []byte{ 411 | opROFF, // [] 412 | opPUSHB000, // [90] 413 | 90, 414 | opROUND00, // [90] 415 | opRTG, // [90] 416 | opPUSHB000, // [90, 90] 417 | 90, 418 | opROUND00, // [90, 64] 419 | opRTHG, // [90, 64] 420 | opPUSHB000, // [90, 64, 90] 421 | 90, 422 | opROUND00, // [90, 64, 96] 423 | opRDTG, // [90, 64, 96] 424 | opPUSHB000, // [90, 64, 96, 90] 425 | 90, 426 | opROUND00, // [90, 64, 96, 64] 427 | opRUTG, // [90, 64, 96, 64] 428 | opPUSHB000, // [90, 64, 96, 64, 90] 429 | 90, 430 | opROUND00, // [90, 64, 96, 64, 128] 431 | opRTDG, // [90, 64, 96, 64, 128] 432 | opPUSHB000, // [90, 64, 96, 64, 128, 90] 433 | 90, 434 | opROUND00, // [90, 64, 96, 64, 128, 96] 435 | }, 436 | []int32{90, 64, 96, 64, 128, 96}, 437 | "", 438 | }, 439 | { 440 | "super-rounding", 441 | // See figure 20 of https://developer.apple.com/fonts/TTRefMan/RM02/Chap2.html#rounding 442 | // and the sign preservation steps of the "Order of rounding operations" section. 443 | []byte{ 444 | opPUSHB000, // [0x58] 445 | 0x58, 446 | opSROUND, // [] 447 | opPUSHW000, // [-81] 448 | 0xff, 449 | 0xaf, 450 | opROUND00, // [-80] 451 | opPUSHW000, // [-80, -80] 452 | 0xff, 453 | 0xb0, 454 | opROUND00, // [-80, -80] 455 | opPUSHW000, // [-80, -80, -17] 456 | 0xff, 457 | 0xef, 458 | opROUND00, // [-80, -80, -16] 459 | opPUSHW000, // [-80, -80, -16, -16] 460 | 0xff, 461 | 0xf0, 462 | opROUND00, // [-80, -80, -16, -16] 463 | opPUSHB000, // [-80, -80, -16, -16, 0] 464 | 0, 465 | opROUND00, // [-80, -80, -16, -16, 16] 466 | opPUSHB000, // [-80, -80, -16, -16, 16, 16] 467 | 16, 468 | opROUND00, // [-80, -80, -16, -16, 16, 16] 469 | opPUSHB000, // [-80, -80, -16, -16, 16, 16, 47] 470 | 47, 471 | opROUND00, // [-80, -80, -16, -16, 16, 16, 16] 472 | opPUSHB000, // [-80, -80, -16, -16, 16, 16, 16, 48] 473 | 48, 474 | opROUND00, // [-80, -80, -16, -16, 16, 16, 16, 80] 475 | }, 476 | []int32{-80, -80, -16, -16, 16, 16, 16, 80}, 477 | "", 478 | }, 479 | { 480 | "roll", 481 | []byte{ 482 | opPUSHB010, // [1, 2, 3] 483 | 1, 484 | 2, 485 | 3, 486 | opROLL, // [2, 3, 1] 487 | }, 488 | []int32{2, 3, 1}, 489 | "", 490 | }, 491 | { 492 | "max/min", 493 | []byte{ 494 | opPUSHW001, // [-2, -3] 495 | 0xff, 496 | 0xfe, 497 | 0xff, 498 | 0xfd, 499 | opMAX, // [-2] 500 | opPUSHW001, // [-2, -4, -5] 501 | 0xff, 502 | 0xfc, 503 | 0xff, 504 | 0xfb, 505 | opMIN, // [-2, -5] 506 | }, 507 | []int32{-2, -5}, 508 | "", 509 | }, 510 | { 511 | "functions", 512 | []byte{ 513 | opPUSHB011, // [3, 7, 0, 3] 514 | 3, 515 | 7, 516 | 0, 517 | 3, 518 | 519 | opFDEF, // Function #3 (not called) 520 | opPUSHB000, 521 | 98, 522 | opENDF, 523 | 524 | opFDEF, // Function #0 525 | opDUP, 526 | opADD, 527 | opENDF, 528 | 529 | opFDEF, // Function #7 530 | opPUSHB001, 531 | 10, 532 | 0, 533 | opCALL, 534 | opDUP, 535 | opENDF, 536 | 537 | opFDEF, // Function #3 (again) 538 | opPUSHB000, 539 | 99, 540 | opENDF, 541 | 542 | opPUSHB001, // [2, 0] 543 | 2, 544 | 0, 545 | opCALL, // [4] 546 | opPUSHB000, // [4, 3] 547 | 3, 548 | opLOOPCALL, // [99, 99, 99, 99] 549 | opPUSHB000, // [99, 99, 99, 99, 7] 550 | 7, 551 | opCALL, // [99, 99, 99, 99, 20, 20] 552 | }, 553 | []int32{99, 99, 99, 99, 20, 20}, 554 | "", 555 | }, 556 | } 557 | 558 | for _, tc := range testCases { 559 | h := &hinter{} 560 | h.init(&Font{ 561 | maxStorage: 32, 562 | maxStackElements: 100, 563 | }, 768) 564 | err, errStr := h.run(tc.prog, nil, nil, nil, nil), "" 565 | if err != nil { 566 | errStr = err.Error() 567 | } 568 | if tc.errStr != "" { 569 | if errStr == "" { 570 | t.Errorf("%s: got no error, want %q", tc.desc, tc.errStr) 571 | } else if !strings.Contains(errStr, tc.errStr) { 572 | t.Errorf("%s: got error %q, want one containing %q", tc.desc, errStr, tc.errStr) 573 | } 574 | continue 575 | } 576 | if errStr != "" { 577 | t.Errorf("%s: got error %q, want none", tc.desc, errStr) 578 | continue 579 | } 580 | got := h.stack[:len(tc.want)] 581 | if !reflect.DeepEqual(got, tc.want) { 582 | t.Errorf("%s: got %v, want %v", tc.desc, got, tc.want) 583 | continue 584 | } 585 | } 586 | } 587 | 588 | // TestMove tests that the hinter.move method matches the output of the C 589 | // Freetype implementation. 590 | func TestMove(t *testing.T) { 591 | h, p := hinter{}, Point{} 592 | testCases := []struct { 593 | pvX, pvY, fvX, fvY f2dot14 594 | wantX, wantY fixed.Int26_6 595 | }{ 596 | {+0x4000, +0x0000, +0x4000, +0x0000, +1000, +0}, 597 | {+0x4000, +0x0000, -0x4000, +0x0000, +1000, +0}, 598 | {-0x4000, +0x0000, +0x4000, +0x0000, -1000, +0}, 599 | {-0x4000, +0x0000, -0x4000, +0x0000, -1000, +0}, 600 | {+0x0000, +0x4000, +0x0000, +0x4000, +0, +1000}, 601 | {+0x0000, +0x4000, +0x0000, -0x4000, +0, +1000}, 602 | {+0x4000, +0x0000, +0x2d41, +0x2d41, +1000, +1000}, 603 | {+0x4000, +0x0000, -0x2d41, +0x2d41, +1000, -1000}, 604 | {+0x4000, +0x0000, +0x2d41, -0x2d41, +1000, -1000}, 605 | {+0x4000, +0x0000, -0x2d41, -0x2d41, +1000, +1000}, 606 | {-0x4000, +0x0000, +0x2d41, +0x2d41, -1000, -1000}, 607 | {-0x4000, +0x0000, -0x2d41, +0x2d41, -1000, +1000}, 608 | {-0x4000, +0x0000, +0x2d41, -0x2d41, -1000, +1000}, 609 | {-0x4000, +0x0000, -0x2d41, -0x2d41, -1000, -1000}, 610 | {+0x376d, +0x2000, +0x2d41, +0x2d41, +732, +732}, 611 | {-0x376d, +0x2000, +0x2d41, +0x2d41, -2732, -2732}, 612 | {+0x376d, +0x2000, +0x2d41, -0x2d41, +2732, -2732}, 613 | {-0x376d, +0x2000, +0x2d41, -0x2d41, -732, +732}, 614 | {-0x376d, -0x2000, +0x2d41, +0x2d41, -732, -732}, 615 | {+0x376d, +0x2000, +0x4000, +0x0000, +1155, +0}, 616 | {+0x376d, +0x2000, +0x0000, +0x4000, +0, +2000}, 617 | } 618 | for _, tc := range testCases { 619 | p = Point{} 620 | h.gs.pv = [2]f2dot14{tc.pvX, tc.pvY} 621 | h.gs.fv = [2]f2dot14{tc.fvX, tc.fvY} 622 | h.move(&p, 1000, true) 623 | tx := p.Flags&flagTouchedX != 0 624 | ty := p.Flags&flagTouchedY != 0 625 | wantTX := tc.fvX != 0 626 | wantTY := tc.fvY != 0 627 | if p.X != tc.wantX || p.Y != tc.wantY || tx != wantTX || ty != wantTY { 628 | t.Errorf("pv=%v, fv=%v\ngot %d, %d, %t, %t\nwant %d, %d, %t, %t", 629 | h.gs.pv, h.gs.fv, p.X, p.Y, tx, ty, tc.wantX, tc.wantY, wantTX, wantTY) 630 | continue 631 | } 632 | 633 | // Check that p is aligned with the freedom vector. 634 | a := int64(p.X) * int64(tc.fvY) 635 | b := int64(p.Y) * int64(tc.fvX) 636 | if a != b { 637 | t.Errorf("pv=%v, fv=%v, p=%v not aligned with fv", h.gs.pv, h.gs.fv, p) 638 | continue 639 | } 640 | 641 | // Check that the projected p is 1000 away from the origin. 642 | dotProd := (int64(p.X)*int64(tc.pvX) + int64(p.Y)*int64(tc.pvY) + 1<<13) >> 14 643 | if dotProd != 1000 { 644 | t.Errorf("pv=%v, fv=%v, p=%v not 1000 from origin", h.gs.pv, h.gs.fv, p) 645 | continue 646 | } 647 | } 648 | } 649 | 650 | // TestNormalize tests that the normalize function matches the output of the C 651 | // Freetype implementation. 652 | func TestNormalize(t *testing.T) { 653 | testCases := [][2]f2dot14{ 654 | {-15895, 3974}, 655 | {-15543, 5181}, 656 | {-14654, 7327}, 657 | {-11585, 11585}, 658 | {0, 16384}, 659 | {11585, 11585}, 660 | {14654, 7327}, 661 | {15543, 5181}, 662 | {15895, 3974}, 663 | {16066, 3213}, 664 | {16161, 2694}, 665 | {16219, 2317}, 666 | {16257, 2032}, 667 | {16284, 1809}, 668 | } 669 | for i, want := range testCases { 670 | got := normalize(f2dot14(i)-4, 1) 671 | if got != want { 672 | t.Errorf("i=%d: got %v, want %v", i, got, want) 673 | } 674 | } 675 | } 676 | -------------------------------------------------------------------------------- /truetype/opcodes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | package truetype 7 | 8 | // The Truetype opcodes are summarized at 9 | // https://developer.apple.com/fonts/TTRefMan/RM07/appendixA.html 10 | 11 | const ( 12 | opSVTCA0 = 0x00 // Set freedom and projection Vectors To Coordinate Axis 13 | opSVTCA1 = 0x01 // . 14 | opSPVTCA0 = 0x02 // Set Projection Vector To Coordinate Axis 15 | opSPVTCA1 = 0x03 // . 16 | opSFVTCA0 = 0x04 // Set Freedom Vector to Coordinate Axis 17 | opSFVTCA1 = 0x05 // . 18 | opSPVTL0 = 0x06 // Set Projection Vector To Line 19 | opSPVTL1 = 0x07 // . 20 | opSFVTL0 = 0x08 // Set Freedom Vector To Line 21 | opSFVTL1 = 0x09 // . 22 | opSPVFS = 0x0a // Set Projection Vector From Stack 23 | opSFVFS = 0x0b // Set Freedom Vector From Stack 24 | opGPV = 0x0c // Get Projection Vector 25 | opGFV = 0x0d // Get Freedom Vector 26 | opSFVTPV = 0x0e // Set Freedom Vector To Projection Vector 27 | opISECT = 0x0f // moves point p to the InterSECTion of two lines 28 | opSRP0 = 0x10 // Set Reference Point 0 29 | opSRP1 = 0x11 // Set Reference Point 1 30 | opSRP2 = 0x12 // Set Reference Point 2 31 | opSZP0 = 0x13 // Set Zone Pointer 0 32 | opSZP1 = 0x14 // Set Zone Pointer 1 33 | opSZP2 = 0x15 // Set Zone Pointer 2 34 | opSZPS = 0x16 // Set Zone PointerS 35 | opSLOOP = 0x17 // Set LOOP variable 36 | opRTG = 0x18 // Round To Grid 37 | opRTHG = 0x19 // Round To Half Grid 38 | opSMD = 0x1a // Set Minimum Distance 39 | opELSE = 0x1b // ELSE clause 40 | opJMPR = 0x1c // JuMP Relative 41 | opSCVTCI = 0x1d // Set Control Value Table Cut-In 42 | opSSWCI = 0x1e // Set Single Width Cut-In 43 | opSSW = 0x1f // Set Single Width 44 | opDUP = 0x20 // DUPlicate top stack element 45 | opPOP = 0x21 // POP top stack element 46 | opCLEAR = 0x22 // CLEAR the stack 47 | opSWAP = 0x23 // SWAP the top two elements on the stack 48 | opDEPTH = 0x24 // DEPTH of the stack 49 | opCINDEX = 0x25 // Copy the INDEXed element to the top of the stack 50 | opMINDEX = 0x26 // Move the INDEXed element to the top of the stack 51 | opALIGNPTS = 0x27 // ALIGN PoinTS 52 | op_0x28 = 0x28 // deprecated 53 | opUTP = 0x29 // UnTouch Point 54 | opLOOPCALL = 0x2a // LOOP and CALL function 55 | opCALL = 0x2b // CALL function 56 | opFDEF = 0x2c // Function DEFinition 57 | opENDF = 0x2d // END Function definition 58 | opMDAP0 = 0x2e // Move Direct Absolute Point 59 | opMDAP1 = 0x2f // . 60 | opIUP0 = 0x30 // Interpolate Untouched Points through the outline 61 | opIUP1 = 0x31 // . 62 | opSHP0 = 0x32 // SHift Point using reference point 63 | opSHP1 = 0x33 // . 64 | opSHC0 = 0x34 // SHift Contour using reference point 65 | opSHC1 = 0x35 // . 66 | opSHZ0 = 0x36 // SHift Zone using reference point 67 | opSHZ1 = 0x37 // . 68 | opSHPIX = 0x38 // SHift point by a PIXel amount 69 | opIP = 0x39 // Interpolate Point 70 | opMSIRP0 = 0x3a // Move Stack Indirect Relative Point 71 | opMSIRP1 = 0x3b // . 72 | opALIGNRP = 0x3c // ALIGN to Reference Point 73 | opRTDG = 0x3d // Round To Double Grid 74 | opMIAP0 = 0x3e // Move Indirect Absolute Point 75 | opMIAP1 = 0x3f // . 76 | opNPUSHB = 0x40 // PUSH N Bytes 77 | opNPUSHW = 0x41 // PUSH N Words 78 | opWS = 0x42 // Write Store 79 | opRS = 0x43 // Read Store 80 | opWCVTP = 0x44 // Write Control Value Table in Pixel units 81 | opRCVT = 0x45 // Read Control Value Table entry 82 | opGC0 = 0x46 // Get Coordinate projected onto the projection vector 83 | opGC1 = 0x47 // . 84 | opSCFS = 0x48 // Sets Coordinate From the Stack using projection vector and freedom vector 85 | opMD0 = 0x49 // Measure Distance 86 | opMD1 = 0x4a // . 87 | opMPPEM = 0x4b // Measure Pixels Per EM 88 | opMPS = 0x4c // Measure Point Size 89 | opFLIPON = 0x4d // set the auto FLIP Boolean to ON 90 | opFLIPOFF = 0x4e // set the auto FLIP Boolean to OFF 91 | opDEBUG = 0x4f // DEBUG call 92 | opLT = 0x50 // Less Than 93 | opLTEQ = 0x51 // Less Than or EQual 94 | opGT = 0x52 // Greater Than 95 | opGTEQ = 0x53 // Greater Than or EQual 96 | opEQ = 0x54 // EQual 97 | opNEQ = 0x55 // Not EQual 98 | opODD = 0x56 // ODD 99 | opEVEN = 0x57 // EVEN 100 | opIF = 0x58 // IF test 101 | opEIF = 0x59 // End IF 102 | opAND = 0x5a // logical AND 103 | opOR = 0x5b // logical OR 104 | opNOT = 0x5c // logical NOT 105 | opDELTAP1 = 0x5d // DELTA exception P1 106 | opSDB = 0x5e // Set Delta Base in the graphics state 107 | opSDS = 0x5f // Set Delta Shift in the graphics state 108 | opADD = 0x60 // ADD 109 | opSUB = 0x61 // SUBtract 110 | opDIV = 0x62 // DIVide 111 | opMUL = 0x63 // MULtiply 112 | opABS = 0x64 // ABSolute value 113 | opNEG = 0x65 // NEGate 114 | opFLOOR = 0x66 // FLOOR 115 | opCEILING = 0x67 // CEILING 116 | opROUND00 = 0x68 // ROUND value 117 | opROUND01 = 0x69 // . 118 | opROUND10 = 0x6a // . 119 | opROUND11 = 0x6b // . 120 | opNROUND00 = 0x6c // No ROUNDing of value 121 | opNROUND01 = 0x6d // . 122 | opNROUND10 = 0x6e // . 123 | opNROUND11 = 0x6f // . 124 | opWCVTF = 0x70 // Write Control Value Table in Funits 125 | opDELTAP2 = 0x71 // DELTA exception P2 126 | opDELTAP3 = 0x72 // DELTA exception P3 127 | opDELTAC1 = 0x73 // DELTA exception C1 128 | opDELTAC2 = 0x74 // DELTA exception C2 129 | opDELTAC3 = 0x75 // DELTA exception C3 130 | opSROUND = 0x76 // Super ROUND 131 | opS45ROUND = 0x77 // Super ROUND 45 degrees 132 | opJROT = 0x78 // Jump Relative On True 133 | opJROF = 0x79 // Jump Relative On False 134 | opROFF = 0x7a // Round OFF 135 | op_0x7b = 0x7b // deprecated 136 | opRUTG = 0x7c // Round Up To Grid 137 | opRDTG = 0x7d // Round Down To Grid 138 | opSANGW = 0x7e // Set ANGle Weight 139 | opAA = 0x7f // Adjust Angle 140 | opFLIPPT = 0x80 // FLIP PoinT 141 | opFLIPRGON = 0x81 // FLIP RanGe ON 142 | opFLIPRGOFF = 0x82 // FLIP RanGe OFF 143 | op_0x83 = 0x83 // deprecated 144 | op_0x84 = 0x84 // deprecated 145 | opSCANCTRL = 0x85 // SCAN conversion ConTRoL 146 | opSDPVTL0 = 0x86 // Set Dual Projection Vector To Line 147 | opSDPVTL1 = 0x87 // . 148 | opGETINFO = 0x88 // GET INFOrmation 149 | opIDEF = 0x89 // Instruction DEFinition 150 | opROLL = 0x8a // ROLL the top three stack elements 151 | opMAX = 0x8b // MAXimum of top two stack elements 152 | opMIN = 0x8c // MINimum of top two stack elements 153 | opSCANTYPE = 0x8d // SCANTYPE 154 | opINSTCTRL = 0x8e // INSTRuction execution ConTRoL 155 | op_0x8f = 0x8f 156 | op_0x90 = 0x90 157 | op_0x91 = 0x91 158 | op_0x92 = 0x92 159 | op_0x93 = 0x93 160 | op_0x94 = 0x94 161 | op_0x95 = 0x95 162 | op_0x96 = 0x96 163 | op_0x97 = 0x97 164 | op_0x98 = 0x98 165 | op_0x99 = 0x99 166 | op_0x9a = 0x9a 167 | op_0x9b = 0x9b 168 | op_0x9c = 0x9c 169 | op_0x9d = 0x9d 170 | op_0x9e = 0x9e 171 | op_0x9f = 0x9f 172 | op_0xa0 = 0xa0 173 | op_0xa1 = 0xa1 174 | op_0xa2 = 0xa2 175 | op_0xa3 = 0xa3 176 | op_0xa4 = 0xa4 177 | op_0xa5 = 0xa5 178 | op_0xa6 = 0xa6 179 | op_0xa7 = 0xa7 180 | op_0xa8 = 0xa8 181 | op_0xa9 = 0xa9 182 | op_0xaa = 0xaa 183 | op_0xab = 0xab 184 | op_0xac = 0xac 185 | op_0xad = 0xad 186 | op_0xae = 0xae 187 | op_0xaf = 0xaf 188 | opPUSHB000 = 0xb0 // PUSH Bytes 189 | opPUSHB001 = 0xb1 // . 190 | opPUSHB010 = 0xb2 // . 191 | opPUSHB011 = 0xb3 // . 192 | opPUSHB100 = 0xb4 // . 193 | opPUSHB101 = 0xb5 // . 194 | opPUSHB110 = 0xb6 // . 195 | opPUSHB111 = 0xb7 // . 196 | opPUSHW000 = 0xb8 // PUSH Words 197 | opPUSHW001 = 0xb9 // . 198 | opPUSHW010 = 0xba // . 199 | opPUSHW011 = 0xbb // . 200 | opPUSHW100 = 0xbc // . 201 | opPUSHW101 = 0xbd // . 202 | opPUSHW110 = 0xbe // . 203 | opPUSHW111 = 0xbf // . 204 | opMDRP00000 = 0xc0 // Move Direct Relative Point 205 | opMDRP00001 = 0xc1 // . 206 | opMDRP00010 = 0xc2 // . 207 | opMDRP00011 = 0xc3 // . 208 | opMDRP00100 = 0xc4 // . 209 | opMDRP00101 = 0xc5 // . 210 | opMDRP00110 = 0xc6 // . 211 | opMDRP00111 = 0xc7 // . 212 | opMDRP01000 = 0xc8 // . 213 | opMDRP01001 = 0xc9 // . 214 | opMDRP01010 = 0xca // . 215 | opMDRP01011 = 0xcb // . 216 | opMDRP01100 = 0xcc // . 217 | opMDRP01101 = 0xcd // . 218 | opMDRP01110 = 0xce // . 219 | opMDRP01111 = 0xcf // . 220 | opMDRP10000 = 0xd0 // . 221 | opMDRP10001 = 0xd1 // . 222 | opMDRP10010 = 0xd2 // . 223 | opMDRP10011 = 0xd3 // . 224 | opMDRP10100 = 0xd4 // . 225 | opMDRP10101 = 0xd5 // . 226 | opMDRP10110 = 0xd6 // . 227 | opMDRP10111 = 0xd7 // . 228 | opMDRP11000 = 0xd8 // . 229 | opMDRP11001 = 0xd9 // . 230 | opMDRP11010 = 0xda // . 231 | opMDRP11011 = 0xdb // . 232 | opMDRP11100 = 0xdc // . 233 | opMDRP11101 = 0xdd // . 234 | opMDRP11110 = 0xde // . 235 | opMDRP11111 = 0xdf // . 236 | opMIRP00000 = 0xe0 // Move Indirect Relative Point 237 | opMIRP00001 = 0xe1 // . 238 | opMIRP00010 = 0xe2 // . 239 | opMIRP00011 = 0xe3 // . 240 | opMIRP00100 = 0xe4 // . 241 | opMIRP00101 = 0xe5 // . 242 | opMIRP00110 = 0xe6 // . 243 | opMIRP00111 = 0xe7 // . 244 | opMIRP01000 = 0xe8 // . 245 | opMIRP01001 = 0xe9 // . 246 | opMIRP01010 = 0xea // . 247 | opMIRP01011 = 0xeb // . 248 | opMIRP01100 = 0xec // . 249 | opMIRP01101 = 0xed // . 250 | opMIRP01110 = 0xee // . 251 | opMIRP01111 = 0xef // . 252 | opMIRP10000 = 0xf0 // . 253 | opMIRP10001 = 0xf1 // . 254 | opMIRP10010 = 0xf2 // . 255 | opMIRP10011 = 0xf3 // . 256 | opMIRP10100 = 0xf4 // . 257 | opMIRP10101 = 0xf5 // . 258 | opMIRP10110 = 0xf6 // . 259 | opMIRP10111 = 0xf7 // . 260 | opMIRP11000 = 0xf8 // . 261 | opMIRP11001 = 0xf9 // . 262 | opMIRP11010 = 0xfa // . 263 | opMIRP11011 = 0xfb // . 264 | opMIRP11100 = 0xfc // . 265 | opMIRP11101 = 0xfd // . 266 | opMIRP11110 = 0xfe // . 267 | opMIRP11111 = 0xff // . 268 | ) 269 | 270 | // popCount is the number of stack elements that each opcode pops. 271 | var popCount = [256]uint8{ 272 | // 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f 273 | 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 5, // 0x00 - 0x0f 274 | 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, // 0x10 - 0x1f 275 | 1, 1, 0, 2, 0, 1, 1, 2, 0, 1, 2, 1, 1, 0, 1, 1, // 0x20 - 0x2f 276 | 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 2, 2, 0, 0, 2, 2, // 0x30 - 0x3f 277 | 0, 0, 2, 1, 2, 1, 1, 1, 2, 2, 2, 0, 0, 0, 0, 0, // 0x40 - 0x4f 278 | 2, 2, 2, 2, 2, 2, 1, 1, 1, 0, 2, 2, 1, 1, 1, 1, // 0x50 - 0x5f 279 | 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6f 280 | 2, 1, 1, 1, 1, 1, 1, 1, 2, 2, 0, 0, 0, 0, 1, 1, // 0x70 - 0x7f 281 | 0, 2, 2, 0, 0, 1, 2, 2, 1, 1, 3, 2, 2, 1, 2, 0, // 0x80 - 0x8f 282 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90 - 0x9f 283 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xa0 - 0xaf 284 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xb0 - 0xbf 285 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xc0 - 0xcf 286 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xd0 - 0xdf 287 | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xe0 - 0xef 288 | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xf0 - 0xff 289 | } 290 | -------------------------------------------------------------------------------- /truetype/truetype.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | // Package truetype provides a parser for the TTF and TTC file formats. 7 | // Those formats are documented at http://developer.apple.com/fonts/TTRefMan/ 8 | // and http://www.microsoft.com/typography/otspec/ 9 | // 10 | // Some of a font's methods provide lengths or co-ordinates, e.g. bounds, font 11 | // metrics and control points. All these methods take a scale parameter, which 12 | // is the number of pixels in 1 em, expressed as a 26.6 fixed point value. For 13 | // example, if 1 em is 10 pixels then scale is fixed.I(10), which is equal to 14 | // fixed.Int26_6(10 << 6). 15 | // 16 | // To measure a TrueType font in ideal FUnit space, use scale equal to 17 | // font.FUnitsPerEm(). 18 | package truetype // import "github.com/golang/freetype/truetype" 19 | 20 | import ( 21 | "fmt" 22 | 23 | "golang.org/x/image/math/fixed" 24 | ) 25 | 26 | // An Index is a Font's index of a rune. 27 | type Index uint16 28 | 29 | // A NameID identifies a name table entry. 30 | // 31 | // See https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html 32 | type NameID uint16 33 | 34 | const ( 35 | NameIDCopyright NameID = 0 36 | NameIDFontFamily = 1 37 | NameIDFontSubfamily = 2 38 | NameIDUniqueSubfamilyID = 3 39 | NameIDFontFullName = 4 40 | NameIDNameTableVersion = 5 41 | NameIDPostscriptName = 6 42 | NameIDTrademarkNotice = 7 43 | NameIDManufacturerName = 8 44 | NameIDDesignerName = 9 45 | NameIDFontDescription = 10 46 | NameIDFontVendorURL = 11 47 | NameIDFontDesignerURL = 12 48 | NameIDFontLicense = 13 49 | NameIDFontLicenseURL = 14 50 | NameIDPreferredFamily = 16 51 | NameIDPreferredSubfamily = 17 52 | NameIDCompatibleName = 18 53 | NameIDSampleText = 19 54 | ) 55 | 56 | const ( 57 | // A 32-bit encoding consists of a most-significant 16-bit Platform ID and a 58 | // least-significant 16-bit Platform Specific ID. The magic numbers are 59 | // specified at https://www.microsoft.com/typography/otspec/name.htm 60 | unicodeEncodingBMPOnly = 0x00000003 // PID = 0 (Unicode), PSID = 3 (Unicode 2.0 BMP Only) 61 | unicodeEncodingFull = 0x00000004 // PID = 0 (Unicode), PSID = 4 (Unicode 2.0 Full Repertoire) 62 | microsoftSymbolEncoding = 0x00030000 // PID = 3 (Microsoft), PSID = 0 (Symbol) 63 | microsoftUCS2Encoding = 0x00030001 // PID = 3 (Microsoft), PSID = 1 (UCS-2) 64 | microsoftUCS4Encoding = 0x0003000a // PID = 3 (Microsoft), PSID = 10 (UCS-4) 65 | ) 66 | 67 | // An HMetric holds the horizontal metrics of a single glyph. 68 | type HMetric struct { 69 | AdvanceWidth, LeftSideBearing fixed.Int26_6 70 | } 71 | 72 | // A VMetric holds the vertical metrics of a single glyph. 73 | type VMetric struct { 74 | AdvanceHeight, TopSideBearing fixed.Int26_6 75 | } 76 | 77 | // A FormatError reports that the input is not a valid TrueType font. 78 | type FormatError string 79 | 80 | func (e FormatError) Error() string { 81 | return "freetype: invalid TrueType format: " + string(e) 82 | } 83 | 84 | // An UnsupportedError reports that the input uses a valid but unimplemented 85 | // TrueType feature. 86 | type UnsupportedError string 87 | 88 | func (e UnsupportedError) Error() string { 89 | return "freetype: unsupported TrueType feature: " + string(e) 90 | } 91 | 92 | // u32 returns the big-endian uint32 at b[i:]. 93 | func u32(b []byte, i int) uint32 { 94 | return uint32(b[i])<<24 | uint32(b[i+1])<<16 | uint32(b[i+2])<<8 | uint32(b[i+3]) 95 | } 96 | 97 | // u16 returns the big-endian uint16 at b[i:]. 98 | func u16(b []byte, i int) uint16 { 99 | return uint16(b[i])<<8 | uint16(b[i+1]) 100 | } 101 | 102 | // readTable returns a slice of the TTF data given by a table's directory entry. 103 | func readTable(ttf []byte, offsetLength []byte) ([]byte, error) { 104 | offset := int(u32(offsetLength, 0)) 105 | if offset < 0 { 106 | return nil, FormatError(fmt.Sprintf("offset too large: %d", uint32(offset))) 107 | } 108 | length := int(u32(offsetLength, 4)) 109 | if length < 0 { 110 | return nil, FormatError(fmt.Sprintf("length too large: %d", uint32(length))) 111 | } 112 | end := offset + length 113 | if end < 0 || end > len(ttf) { 114 | return nil, FormatError(fmt.Sprintf("offset + length too large: %d", uint32(offset)+uint32(length))) 115 | } 116 | return ttf[offset:end], nil 117 | } 118 | 119 | // parseSubtables returns the offset and platformID of the best subtable in 120 | // table, where best favors a Unicode cmap encoding, and failing that, a 121 | // Microsoft cmap encoding. offset is the offset of the first subtable in 122 | // table, and size is the size of each subtable. 123 | // 124 | // If pred is non-nil, then only subtables that satisfy that predicate will be 125 | // considered. 126 | func parseSubtables(table []byte, name string, offset, size int, pred func([]byte) bool) ( 127 | bestOffset int, bestPID uint32, retErr error) { 128 | 129 | if len(table) < 4 { 130 | return 0, 0, FormatError(name + " too short") 131 | } 132 | nSubtables := int(u16(table, 2)) 133 | if len(table) < size*nSubtables+offset { 134 | return 0, 0, FormatError(name + " too short") 135 | } 136 | ok := false 137 | for i := 0; i < nSubtables; i, offset = i+1, offset+size { 138 | if pred != nil && !pred(table[offset:]) { 139 | continue 140 | } 141 | // We read the 16-bit Platform ID and 16-bit Platform Specific ID as a single uint32. 142 | // All values are big-endian. 143 | pidPsid := u32(table, offset) 144 | // We prefer the Unicode cmap encoding. Failing to find that, we fall 145 | // back onto the Microsoft cmap encoding. 146 | if pidPsid == unicodeEncodingBMPOnly || pidPsid == unicodeEncodingFull { 147 | bestOffset, bestPID, ok = offset, pidPsid>>16, true 148 | break 149 | 150 | } else if pidPsid == microsoftSymbolEncoding || 151 | pidPsid == microsoftUCS2Encoding || 152 | pidPsid == microsoftUCS4Encoding { 153 | 154 | bestOffset, bestPID, ok = offset, pidPsid>>16, true 155 | // We don't break out of the for loop, so that Unicode can override Microsoft. 156 | } 157 | } 158 | if !ok { 159 | return 0, 0, UnsupportedError(name + " encoding") 160 | } 161 | return bestOffset, bestPID, nil 162 | } 163 | 164 | const ( 165 | locaOffsetFormatUnknown int = iota 166 | locaOffsetFormatShort 167 | locaOffsetFormatLong 168 | ) 169 | 170 | // A cm holds a parsed cmap entry. 171 | type cm struct { 172 | start, end, delta, offset uint32 173 | } 174 | 175 | // A Font represents a Truetype font. 176 | type Font struct { 177 | // Tables sliced from the TTF data. The different tables are documented 178 | // at http://developer.apple.com/fonts/TTRefMan/RM06/Chap6.html 179 | cmap, cvt, fpgm, glyf, hdmx, head, hhea, hmtx, kern, loca, maxp, name, os2, prep, vmtx []byte 180 | 181 | cmapIndexes []byte 182 | 183 | // Cached values derived from the raw ttf data. 184 | cm []cm 185 | locaOffsetFormat int 186 | nGlyph, nHMetric, nKern int 187 | fUnitsPerEm int32 188 | ascent int32 // In FUnits. 189 | descent int32 // In FUnits; typically negative. 190 | bounds fixed.Rectangle26_6 // In FUnits. 191 | // Values from the maxp section. 192 | maxTwilightPoints, maxStorage, maxFunctionDefs, maxStackElements uint16 193 | } 194 | 195 | func (f *Font) parseCmap() error { 196 | const ( 197 | cmapFormat4 = 4 198 | cmapFormat12 = 12 199 | languageIndependent = 0 200 | ) 201 | 202 | offset, _, err := parseSubtables(f.cmap, "cmap", 4, 8, nil) 203 | if err != nil { 204 | return err 205 | } 206 | offset = int(u32(f.cmap, offset+4)) 207 | if offset <= 0 || offset > len(f.cmap) { 208 | return FormatError("bad cmap offset") 209 | } 210 | 211 | cmapFormat := u16(f.cmap, offset) 212 | switch cmapFormat { 213 | case cmapFormat4: 214 | language := u16(f.cmap, offset+4) 215 | if language != languageIndependent { 216 | return UnsupportedError(fmt.Sprintf("language: %d", language)) 217 | } 218 | segCountX2 := int(u16(f.cmap, offset+6)) 219 | if segCountX2%2 == 1 { 220 | return FormatError(fmt.Sprintf("bad segCountX2: %d", segCountX2)) 221 | } 222 | segCount := segCountX2 / 2 223 | offset += 14 224 | f.cm = make([]cm, segCount) 225 | for i := 0; i < segCount; i++ { 226 | f.cm[i].end = uint32(u16(f.cmap, offset)) 227 | offset += 2 228 | } 229 | offset += 2 230 | for i := 0; i < segCount; i++ { 231 | f.cm[i].start = uint32(u16(f.cmap, offset)) 232 | offset += 2 233 | } 234 | for i := 0; i < segCount; i++ { 235 | f.cm[i].delta = uint32(u16(f.cmap, offset)) 236 | offset += 2 237 | } 238 | for i := 0; i < segCount; i++ { 239 | f.cm[i].offset = uint32(u16(f.cmap, offset)) 240 | offset += 2 241 | } 242 | f.cmapIndexes = f.cmap[offset:] 243 | return nil 244 | 245 | case cmapFormat12: 246 | if u16(f.cmap, offset+2) != 0 { 247 | return FormatError(fmt.Sprintf("cmap format: % x", f.cmap[offset:offset+4])) 248 | } 249 | length := u32(f.cmap, offset+4) 250 | language := u32(f.cmap, offset+8) 251 | if language != languageIndependent { 252 | return UnsupportedError(fmt.Sprintf("language: %d", language)) 253 | } 254 | nGroups := u32(f.cmap, offset+12) 255 | if length != 12*nGroups+16 { 256 | return FormatError("inconsistent cmap length") 257 | } 258 | offset += 16 259 | f.cm = make([]cm, nGroups) 260 | for i := uint32(0); i < nGroups; i++ { 261 | f.cm[i].start = u32(f.cmap, offset+0) 262 | f.cm[i].end = u32(f.cmap, offset+4) 263 | f.cm[i].delta = u32(f.cmap, offset+8) - f.cm[i].start 264 | offset += 12 265 | } 266 | return nil 267 | } 268 | return UnsupportedError(fmt.Sprintf("cmap format: %d", cmapFormat)) 269 | } 270 | 271 | func (f *Font) parseHead() error { 272 | if len(f.head) != 54 { 273 | return FormatError(fmt.Sprintf("bad head length: %d", len(f.head))) 274 | } 275 | f.fUnitsPerEm = int32(u16(f.head, 18)) 276 | f.bounds.Min.X = fixed.Int26_6(int16(u16(f.head, 36))) 277 | f.bounds.Min.Y = fixed.Int26_6(int16(u16(f.head, 38))) 278 | f.bounds.Max.X = fixed.Int26_6(int16(u16(f.head, 40))) 279 | f.bounds.Max.Y = fixed.Int26_6(int16(u16(f.head, 42))) 280 | switch i := u16(f.head, 50); i { 281 | case 0: 282 | f.locaOffsetFormat = locaOffsetFormatShort 283 | case 1: 284 | f.locaOffsetFormat = locaOffsetFormatLong 285 | default: 286 | return FormatError(fmt.Sprintf("bad indexToLocFormat: %d", i)) 287 | } 288 | return nil 289 | } 290 | 291 | func (f *Font) parseHhea() error { 292 | if len(f.hhea) != 36 { 293 | return FormatError(fmt.Sprintf("bad hhea length: %d", len(f.hhea))) 294 | } 295 | f.ascent = int32(int16(u16(f.hhea, 4))) 296 | f.descent = int32(int16(u16(f.hhea, 6))) 297 | f.nHMetric = int(u16(f.hhea, 34)) 298 | if 4*f.nHMetric+2*(f.nGlyph-f.nHMetric) != len(f.hmtx) { 299 | return FormatError(fmt.Sprintf("bad hmtx length: %d", len(f.hmtx))) 300 | } 301 | return nil 302 | } 303 | 304 | func (f *Font) parseKern() error { 305 | // Apple's TrueType documentation (http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html) says: 306 | // "Previous versions of the 'kern' table defined both the version and nTables fields in the header 307 | // as UInt16 values and not UInt32 values. Use of the older format on the Mac OS is discouraged 308 | // (although AAT can sense an old kerning table and still make correct use of it). Microsoft 309 | // Windows still uses the older format for the 'kern' table and will not recognize the newer one. 310 | // Fonts targeted for the Mac OS only should use the new format; fonts targeted for both the Mac OS 311 | // and Windows should use the old format." 312 | // Since we expect that almost all fonts aim to be Windows-compatible, we only parse the "older" format, 313 | // just like the C Freetype implementation. 314 | if len(f.kern) == 0 { 315 | if f.nKern != 0 { 316 | return FormatError("bad kern table length") 317 | } 318 | return nil 319 | } 320 | if len(f.kern) < 18 { 321 | return FormatError("kern data too short") 322 | } 323 | version, offset := u16(f.kern, 0), 2 324 | if version != 0 { 325 | return UnsupportedError(fmt.Sprintf("kern version: %d", version)) 326 | } 327 | 328 | n, offset := u16(f.kern, offset), offset+2 329 | if n == 0 { 330 | return UnsupportedError("kern nTables: 0") 331 | } 332 | // TODO: support multiple subtables. In practice, almost all .ttf files 333 | // have only one subtable, if they have a kern table at all. But it's not 334 | // impossible. Xolonium Regular (https://fontlibrary.org/en/font/xolonium) 335 | // has 3 subtables. Those subtables appear to be disjoint, rather than 336 | // being the same kerning pairs encoded in three different ways. 337 | // 338 | // For now, we'll use only the first subtable. 339 | 340 | offset += 2 // Skip the version. 341 | length, offset := int(u16(f.kern, offset)), offset+2 342 | coverage, offset := u16(f.kern, offset), offset+2 343 | if coverage != 0x0001 { 344 | // We only support horizontal kerning. 345 | return UnsupportedError(fmt.Sprintf("kern coverage: 0x%04x", coverage)) 346 | } 347 | f.nKern, offset = int(u16(f.kern, offset)), offset+2 348 | if 6*f.nKern != length-14 { 349 | return FormatError("bad kern table length") 350 | } 351 | return nil 352 | } 353 | 354 | func (f *Font) parseMaxp() error { 355 | if len(f.maxp) != 32 { 356 | return FormatError(fmt.Sprintf("bad maxp length: %d", len(f.maxp))) 357 | } 358 | f.nGlyph = int(u16(f.maxp, 4)) 359 | f.maxTwilightPoints = u16(f.maxp, 16) 360 | f.maxStorage = u16(f.maxp, 18) 361 | f.maxFunctionDefs = u16(f.maxp, 20) 362 | f.maxStackElements = u16(f.maxp, 24) 363 | return nil 364 | } 365 | 366 | // scale returns x divided by f.fUnitsPerEm, rounded to the nearest integer. 367 | func (f *Font) scale(x fixed.Int26_6) fixed.Int26_6 { 368 | if x >= 0 { 369 | x += fixed.Int26_6(f.fUnitsPerEm) / 2 370 | } else { 371 | x -= fixed.Int26_6(f.fUnitsPerEm) / 2 372 | } 373 | return x / fixed.Int26_6(f.fUnitsPerEm) 374 | } 375 | 376 | // Bounds returns the union of a Font's glyphs' bounds. 377 | func (f *Font) Bounds(scale fixed.Int26_6) fixed.Rectangle26_6 { 378 | b := f.bounds 379 | b.Min.X = f.scale(scale * b.Min.X) 380 | b.Min.Y = f.scale(scale * b.Min.Y) 381 | b.Max.X = f.scale(scale * b.Max.X) 382 | b.Max.Y = f.scale(scale * b.Max.Y) 383 | return b 384 | } 385 | 386 | // FUnitsPerEm returns the number of FUnits in a Font's em-square's side. 387 | func (f *Font) FUnitsPerEm() int32 { 388 | return f.fUnitsPerEm 389 | } 390 | 391 | // Index returns a Font's index for the given rune. 392 | func (f *Font) Index(x rune) Index { 393 | c := uint32(x) 394 | for i, j := 0, len(f.cm); i < j; { 395 | h := i + (j-i)/2 396 | cm := &f.cm[h] 397 | if c < cm.start { 398 | j = h 399 | } else if cm.end < c { 400 | i = h + 1 401 | } else if cm.offset == 0 { 402 | return Index(c + cm.delta) 403 | } else { 404 | offset := int(cm.offset) + 2*(h-len(f.cm)+int(c-cm.start)) 405 | return Index(u16(f.cmapIndexes, offset)) 406 | } 407 | } 408 | return 0 409 | } 410 | 411 | // Name returns the Font's name value for the given NameID. It returns "" if 412 | // there was an error, or if that name was not found. 413 | func (f *Font) Name(id NameID) string { 414 | x, platformID, err := parseSubtables(f.name, "name", 6, 12, func(b []byte) bool { 415 | return NameID(u16(b, 6)) == id 416 | }) 417 | if err != nil { 418 | return "" 419 | } 420 | offset, length := u16(f.name, 4)+u16(f.name, x+10), u16(f.name, x+8) 421 | // Return the ASCII value of the encoded string. 422 | // The string is encoded as UTF-16 on non-Apple platformIDs; Apple is platformID 1. 423 | src := f.name[offset : offset+length] 424 | var dst []byte 425 | if platformID != 1 { // UTF-16. 426 | if len(src)&1 != 0 { 427 | return "" 428 | } 429 | dst = make([]byte, len(src)/2) 430 | for i := range dst { 431 | dst[i] = printable(u16(src, 2*i)) 432 | } 433 | } else { // ASCII. 434 | dst = make([]byte, len(src)) 435 | for i, c := range src { 436 | dst[i] = printable(uint16(c)) 437 | } 438 | } 439 | return string(dst) 440 | } 441 | 442 | func printable(r uint16) byte { 443 | if 0x20 <= r && r < 0x7f { 444 | return byte(r) 445 | } 446 | return '?' 447 | } 448 | 449 | // unscaledHMetric returns the unscaled horizontal metrics for the glyph with 450 | // the given index. 451 | func (f *Font) unscaledHMetric(i Index) (h HMetric) { 452 | j := int(i) 453 | if j < 0 || f.nGlyph <= j { 454 | return HMetric{} 455 | } 456 | if j >= f.nHMetric { 457 | p := 4 * (f.nHMetric - 1) 458 | return HMetric{ 459 | AdvanceWidth: fixed.Int26_6(u16(f.hmtx, p)), 460 | LeftSideBearing: fixed.Int26_6(int16(u16(f.hmtx, p+2*(j-f.nHMetric)+4))), 461 | } 462 | } 463 | return HMetric{ 464 | AdvanceWidth: fixed.Int26_6(u16(f.hmtx, 4*j)), 465 | LeftSideBearing: fixed.Int26_6(int16(u16(f.hmtx, 4*j+2))), 466 | } 467 | } 468 | 469 | // HMetric returns the horizontal metrics for the glyph with the given index. 470 | func (f *Font) HMetric(scale fixed.Int26_6, i Index) HMetric { 471 | h := f.unscaledHMetric(i) 472 | h.AdvanceWidth = f.scale(scale * h.AdvanceWidth) 473 | h.LeftSideBearing = f.scale(scale * h.LeftSideBearing) 474 | return h 475 | } 476 | 477 | // unscaledVMetric returns the unscaled vertical metrics for the glyph with 478 | // the given index. yMax is the top of the glyph's bounding box. 479 | func (f *Font) unscaledVMetric(i Index, yMax fixed.Int26_6) (v VMetric) { 480 | j := int(i) 481 | if j < 0 || f.nGlyph <= j { 482 | return VMetric{} 483 | } 484 | if 4*j+4 <= len(f.vmtx) { 485 | return VMetric{ 486 | AdvanceHeight: fixed.Int26_6(u16(f.vmtx, 4*j)), 487 | TopSideBearing: fixed.Int26_6(int16(u16(f.vmtx, 4*j+2))), 488 | } 489 | } 490 | // The OS/2 table has grown over time. 491 | // https://developer.apple.com/fonts/TTRefMan/RM06/Chap6OS2.html 492 | // says that it was originally 68 bytes. Optional fields, including 493 | // the ascender and descender, are described at 494 | // http://www.microsoft.com/typography/otspec/os2.htm 495 | if len(f.os2) >= 72 { 496 | sTypoAscender := fixed.Int26_6(int16(u16(f.os2, 68))) 497 | sTypoDescender := fixed.Int26_6(int16(u16(f.os2, 70))) 498 | return VMetric{ 499 | AdvanceHeight: sTypoAscender - sTypoDescender, 500 | TopSideBearing: sTypoAscender - yMax, 501 | } 502 | } 503 | return VMetric{ 504 | AdvanceHeight: fixed.Int26_6(f.fUnitsPerEm), 505 | TopSideBearing: 0, 506 | } 507 | } 508 | 509 | // VMetric returns the vertical metrics for the glyph with the given index. 510 | func (f *Font) VMetric(scale fixed.Int26_6, i Index) VMetric { 511 | // TODO: should 0 be bounds.YMax? 512 | v := f.unscaledVMetric(i, 0) 513 | v.AdvanceHeight = f.scale(scale * v.AdvanceHeight) 514 | v.TopSideBearing = f.scale(scale * v.TopSideBearing) 515 | return v 516 | } 517 | 518 | // Kern returns the horizontal adjustment for the given glyph pair. A positive 519 | // kern means to move the glyphs further apart. 520 | func (f *Font) Kern(scale fixed.Int26_6, i0, i1 Index) fixed.Int26_6 { 521 | if f.nKern == 0 { 522 | return 0 523 | } 524 | g := uint32(i0)<<16 | uint32(i1) 525 | lo, hi := 0, f.nKern 526 | for lo < hi { 527 | i := (lo + hi) / 2 528 | ig := u32(f.kern, 18+6*i) 529 | if ig < g { 530 | lo = i + 1 531 | } else if ig > g { 532 | hi = i 533 | } else { 534 | return f.scale(scale * fixed.Int26_6(int16(u16(f.kern, 22+6*i)))) 535 | } 536 | } 537 | return 0 538 | } 539 | 540 | // Parse returns a new Font for the given TTF or TTC data. 541 | // 542 | // For TrueType Collections, the first font in the collection is parsed. 543 | func Parse(ttf []byte) (font *Font, err error) { 544 | return parse(ttf, 0) 545 | } 546 | 547 | func parse(ttf []byte, offset int) (font *Font, err error) { 548 | if len(ttf)-offset < 12 { 549 | err = FormatError("TTF data is too short") 550 | return 551 | } 552 | originalOffset := offset 553 | magic, offset := u32(ttf, offset), offset+4 554 | switch magic { 555 | case 0x00010000: 556 | // No-op. 557 | case 0x74746366: // "ttcf" as a big-endian uint32. 558 | if originalOffset != 0 { 559 | err = FormatError("recursive TTC") 560 | return 561 | } 562 | ttcVersion, offset := u32(ttf, offset), offset+4 563 | if ttcVersion != 0x00010000 && ttcVersion != 0x00020000 { 564 | err = FormatError("bad TTC version") 565 | return 566 | } 567 | numFonts, offset := int(u32(ttf, offset)), offset+4 568 | if numFonts <= 0 { 569 | err = FormatError("bad number of TTC fonts") 570 | return 571 | } 572 | if len(ttf[offset:])/4 < numFonts { 573 | err = FormatError("TTC offset table is too short") 574 | return 575 | } 576 | // TODO: provide an API to select which font in a TrueType collection to return, 577 | // not just the first one. This may require an API to parse a TTC's name tables, 578 | // so users of this package can select the font in a TTC by name. 579 | offset = int(u32(ttf, offset)) 580 | if offset <= 0 || offset > len(ttf) { 581 | err = FormatError("bad TTC offset") 582 | return 583 | } 584 | return parse(ttf, offset) 585 | default: 586 | err = FormatError("bad TTF version") 587 | return 588 | } 589 | n, offset := int(u16(ttf, offset)), offset+2 590 | offset += 6 // Skip the searchRange, entrySelector and rangeShift. 591 | if len(ttf) < 16*n+offset { 592 | err = FormatError("TTF data is too short") 593 | return 594 | } 595 | f := new(Font) 596 | // Assign the table slices. 597 | for i := 0; i < n; i++ { 598 | x := 16*i + offset 599 | switch string(ttf[x : x+4]) { 600 | case "cmap": 601 | f.cmap, err = readTable(ttf, ttf[x+8:x+16]) 602 | case "cvt ": 603 | f.cvt, err = readTable(ttf, ttf[x+8:x+16]) 604 | case "fpgm": 605 | f.fpgm, err = readTable(ttf, ttf[x+8:x+16]) 606 | case "glyf": 607 | f.glyf, err = readTable(ttf, ttf[x+8:x+16]) 608 | case "hdmx": 609 | f.hdmx, err = readTable(ttf, ttf[x+8:x+16]) 610 | case "head": 611 | f.head, err = readTable(ttf, ttf[x+8:x+16]) 612 | case "hhea": 613 | f.hhea, err = readTable(ttf, ttf[x+8:x+16]) 614 | case "hmtx": 615 | f.hmtx, err = readTable(ttf, ttf[x+8:x+16]) 616 | case "kern": 617 | f.kern, err = readTable(ttf, ttf[x+8:x+16]) 618 | case "loca": 619 | f.loca, err = readTable(ttf, ttf[x+8:x+16]) 620 | case "maxp": 621 | f.maxp, err = readTable(ttf, ttf[x+8:x+16]) 622 | case "name": 623 | f.name, err = readTable(ttf, ttf[x+8:x+16]) 624 | case "OS/2": 625 | f.os2, err = readTable(ttf, ttf[x+8:x+16]) 626 | case "prep": 627 | f.prep, err = readTable(ttf, ttf[x+8:x+16]) 628 | case "vmtx": 629 | f.vmtx, err = readTable(ttf, ttf[x+8:x+16]) 630 | } 631 | if err != nil { 632 | return 633 | } 634 | } 635 | // Parse and sanity-check the TTF data. 636 | if err = f.parseHead(); err != nil { 637 | return 638 | } 639 | if err = f.parseMaxp(); err != nil { 640 | return 641 | } 642 | if err = f.parseCmap(); err != nil { 643 | return 644 | } 645 | if err = f.parseKern(); err != nil { 646 | return 647 | } 648 | if err = f.parseHhea(); err != nil { 649 | return 650 | } 651 | font = f 652 | return 653 | } 654 | -------------------------------------------------------------------------------- /truetype/truetype_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | package truetype 7 | 8 | import ( 9 | "bufio" 10 | "fmt" 11 | "io" 12 | "io/ioutil" 13 | "os" 14 | "strconv" 15 | "strings" 16 | "testing" 17 | 18 | "golang.org/x/image/font" 19 | "golang.org/x/image/math/fixed" 20 | ) 21 | 22 | func parseTestdataFont(name string) (f *Font, testdataIsOptional bool, err error) { 23 | b, err := ioutil.ReadFile(fmt.Sprintf("../testdata/%s.ttf", name)) 24 | if err != nil { 25 | // The "x-foo" fonts are optional tests, as they are not checked 26 | // in for copyright or file size reasons. 27 | return nil, strings.HasPrefix(name, "x-"), fmt.Errorf("%s: ReadFile: %v", name, err) 28 | } 29 | f, err = Parse(b) 30 | if err != nil { 31 | return nil, true, fmt.Errorf("%s: Parse: %v", name, err) 32 | } 33 | return f, false, nil 34 | } 35 | 36 | func mkBounds(minX, minY, maxX, maxY fixed.Int26_6) fixed.Rectangle26_6 { 37 | return fixed.Rectangle26_6{ 38 | Min: fixed.Point26_6{ 39 | X: minX, 40 | Y: minY, 41 | }, 42 | Max: fixed.Point26_6{ 43 | X: maxX, 44 | Y: maxY, 45 | }, 46 | } 47 | } 48 | 49 | // TestParse tests that the luxisr.ttf metrics and glyphs are parsed correctly. 50 | // The numerical values can be manually verified by examining luxisr.ttx. 51 | func TestParse(t *testing.T) { 52 | f, _, err := parseTestdataFont("luxisr") 53 | if err != nil { 54 | t.Fatal(err) 55 | } 56 | if got, want := f.FUnitsPerEm(), int32(2048); got != want { 57 | t.Errorf("FUnitsPerEm: got %v, want %v", got, want) 58 | } 59 | fupe := fixed.Int26_6(f.FUnitsPerEm()) 60 | if got, want := f.Bounds(fupe), mkBounds(-441, -432, 2024, 2033); got != want { 61 | t.Errorf("Bounds: got %v, want %v", got, want) 62 | } 63 | 64 | i0 := f.Index('A') 65 | i1 := f.Index('V') 66 | if i0 != 36 || i1 != 57 { 67 | t.Fatalf("Index: i0, i1 = %d, %d, want 36, 57", i0, i1) 68 | } 69 | if got, want := f.HMetric(fupe, i0), (HMetric{1366, 19}); got != want { 70 | t.Errorf("HMetric: got %v, want %v", got, want) 71 | } 72 | if got, want := f.VMetric(fupe, i0), (VMetric{2465, 553}); got != want { 73 | t.Errorf("VMetric: got %v, want %v", got, want) 74 | } 75 | if got, want := f.Kern(fupe, i0, i1), fixed.Int26_6(-144); got != want { 76 | t.Errorf("Kern: got %v, want %v", got, want) 77 | } 78 | 79 | g := &GlyphBuf{} 80 | err = g.Load(f, fupe, i0, font.HintingNone) 81 | if err != nil { 82 | t.Fatalf("Load: %v", err) 83 | } 84 | g0 := &GlyphBuf{ 85 | Bounds: g.Bounds, 86 | Points: g.Points, 87 | Ends: g.Ends, 88 | } 89 | g1 := &GlyphBuf{ 90 | Bounds: mkBounds(19, 0, 1342, 1480), 91 | Points: []Point{ 92 | {19, 0, 51}, 93 | {581, 1480, 1}, 94 | {789, 1480, 51}, 95 | {1342, 0, 1}, 96 | {1116, 0, 35}, 97 | {962, 410, 3}, 98 | {368, 410, 33}, 99 | {214, 0, 3}, 100 | {428, 566, 19}, 101 | {904, 566, 33}, 102 | {667, 1200, 3}, 103 | }, 104 | Ends: []int{8, 11}, 105 | } 106 | if got, want := fmt.Sprint(g0), fmt.Sprint(g1); got != want { 107 | t.Errorf("GlyphBuf:\ngot %v\nwant %v", got, want) 108 | } 109 | } 110 | 111 | func TestIndex(t *testing.T) { 112 | testCases := map[string]map[rune]Index{ 113 | "luxisr": { 114 | ' ': 3, 115 | '!': 4, 116 | 'A': 36, 117 | 'V': 57, 118 | 'É': 101, 119 | 'fl': 193, 120 | '\u22c5': 385, 121 | '中': 0, 122 | }, 123 | 124 | // The x-etc test cases use those versions of the .ttf files provided 125 | // by Ubuntu 14.04. See testdata/make-other-hinting-txts.sh for details. 126 | 127 | "x-arial-bold": { 128 | ' ': 3, 129 | '+': 14, 130 | '0': 19, 131 | '_': 66, 132 | 'w': 90, 133 | '~': 97, 134 | 'Ä': 98, 135 | 'fl': 192, 136 | '½': 242, 137 | 'σ': 305, 138 | 'λ': 540, 139 | 'ỹ': 1275, 140 | '\u04e9': 1319, 141 | '中': 0, 142 | }, 143 | "x-deja-vu-sans-oblique": { 144 | ' ': 3, 145 | '*': 13, 146 | 'Œ': 276, 147 | 'ω': 861, 148 | '‡': 2571, 149 | '⊕': 3110, 150 | 'fl': 4728, 151 | '\ufb03': 4729, 152 | '\ufffd': 4813, 153 | // TODO: '\U0001f640': ???, 154 | '中': 0, 155 | }, 156 | "x-droid-sans-japanese": { 157 | ' ': 0, 158 | '\u3000': 3, 159 | '\u3041': 25, 160 | '\u30fe': 201, 161 | '\uff61': 202, 162 | '\uff67': 208, 163 | '\uff9e': 263, 164 | '\uff9f': 264, 165 | '\u4e00': 265, 166 | '\u557e': 1000, 167 | '\u61b6': 2024, 168 | '\u6ede': 3177, 169 | '\u7505': 3555, 170 | '\u81e3': 4602, 171 | '\u81e5': 4603, 172 | '\u81e7': 4604, 173 | '\u81e8': 4605, 174 | '\u81ea': 4606, 175 | '\u81ed': 4607, 176 | '\u81f3': 4608, 177 | '\u81f4': 4609, 178 | '\u91c7': 5796, 179 | '\u9fa0': 6620, 180 | '\u203e': 12584, 181 | }, 182 | "x-times-new-roman": { 183 | ' ': 3, 184 | ':': 29, 185 | 'fl': 192, 186 | 'Ŀ': 273, 187 | '♠': 388, 188 | 'Ŗ': 451, 189 | 'Σ': 520, 190 | '\u200D': 745, 191 | 'Ẽ': 1216, 192 | '\u04e9': 1319, 193 | '中': 0, 194 | }, 195 | } 196 | for name, wants := range testCases { 197 | f, testdataIsOptional, err := parseTestdataFont(name) 198 | if err != nil { 199 | if testdataIsOptional { 200 | t.Log(err) 201 | } else { 202 | t.Fatal(err) 203 | } 204 | continue 205 | } 206 | for r, want := range wants { 207 | if got := f.Index(r); got != want { 208 | t.Errorf("%s: Index of %q, aka %U: got %d, want %d", name, r, r, got, want) 209 | } 210 | } 211 | } 212 | } 213 | 214 | func TestName(t *testing.T) { 215 | testCases := map[string]string{ 216 | "luximr": "Luxi Mono", 217 | "luxirr": "Luxi Serif", 218 | "luxisr": "Luxi Sans", 219 | } 220 | 221 | for name, want := range testCases { 222 | f, testdataIsOptional, err := parseTestdataFont(name) 223 | if err != nil { 224 | if testdataIsOptional { 225 | t.Log(err) 226 | } else { 227 | t.Fatal(err) 228 | } 229 | continue 230 | } 231 | if got := f.Name(NameIDFontFamily); got != want { 232 | t.Errorf("%s: got %q, want %q", name, got, want) 233 | } 234 | } 235 | } 236 | 237 | type scalingTestData struct { 238 | advanceWidth fixed.Int26_6 239 | bounds fixed.Rectangle26_6 240 | points []Point 241 | } 242 | 243 | // scalingTestParse parses a line of points like 244 | // 213 -22 -111 236 555;-22 -111 1, 178 555 1, 236 555 1, 36 -111 1 245 | // The line will not have a trailing "\n". 246 | func scalingTestParse(line string) (ret scalingTestData) { 247 | next := func(s string) (string, fixed.Int26_6) { 248 | t, i := "", strings.Index(s, " ") 249 | if i != -1 { 250 | s, t = s[:i], s[i+1:] 251 | } 252 | x, _ := strconv.Atoi(s) 253 | return t, fixed.Int26_6(x) 254 | } 255 | 256 | i := strings.Index(line, ";") 257 | prefix, line := line[:i], line[i+1:] 258 | 259 | prefix, ret.advanceWidth = next(prefix) 260 | prefix, ret.bounds.Min.X = next(prefix) 261 | prefix, ret.bounds.Min.Y = next(prefix) 262 | prefix, ret.bounds.Max.X = next(prefix) 263 | prefix, ret.bounds.Max.Y = next(prefix) 264 | 265 | ret.points = make([]Point, 0, 1+strings.Count(line, ",")) 266 | for len(line) > 0 { 267 | s := line 268 | if i := strings.Index(line, ","); i != -1 { 269 | s, line = line[:i], line[i+1:] 270 | for len(line) > 0 && line[0] == ' ' { 271 | line = line[1:] 272 | } 273 | } else { 274 | line = "" 275 | } 276 | s, x := next(s) 277 | s, y := next(s) 278 | s, f := next(s) 279 | ret.points = append(ret.points, Point{X: x, Y: y, Flags: uint32(f)}) 280 | } 281 | return ret 282 | } 283 | 284 | // scalingTestEquals is equivalent to, but faster than, calling 285 | // reflect.DeepEqual(a, b), and also returns the index of the first non-equal 286 | // element. It also treats a nil []Point and an empty non-nil []Point as equal. 287 | // a and b must have equal length. 288 | func scalingTestEquals(a, b []Point) (index int, equals bool) { 289 | for i, p := range a { 290 | if p != b[i] { 291 | return i, false 292 | } 293 | } 294 | return 0, true 295 | } 296 | 297 | var scalingTestCases = []struct { 298 | name string 299 | size int 300 | }{ 301 | {"luxisr", 12}, 302 | {"x-arial-bold", 11}, 303 | {"x-deja-vu-sans-oblique", 17}, 304 | {"x-droid-sans-japanese", 9}, 305 | {"x-times-new-roman", 13}, 306 | } 307 | 308 | func testScaling(t *testing.T, h font.Hinting) { 309 | for _, tc := range scalingTestCases { 310 | f, testdataIsOptional, err := parseTestdataFont(tc.name) 311 | if err != nil { 312 | if testdataIsOptional { 313 | t.Log(err) 314 | } else { 315 | t.Error(err) 316 | } 317 | continue 318 | } 319 | hintingStr := "sans" 320 | if h != font.HintingNone { 321 | hintingStr = "with" 322 | } 323 | testFile, err := os.Open(fmt.Sprintf( 324 | "../testdata/%s-%dpt-%s-hinting.txt", tc.name, tc.size, hintingStr)) 325 | if err != nil { 326 | t.Errorf("%s: Open: %v", tc.name, err) 327 | continue 328 | } 329 | defer testFile.Close() 330 | 331 | wants := []scalingTestData{} 332 | scanner := bufio.NewScanner(testFile) 333 | if scanner.Scan() { 334 | major, minor, patch := 0, 0, 0 335 | _, err := fmt.Sscanf(scanner.Text(), "freetype version %d.%d.%d", &major, &minor, &patch) 336 | if err != nil { 337 | t.Errorf("%s: version information: %v", tc.name, err) 338 | } 339 | if (major < 2) || (major == 2 && minor < 5) || (major == 2 && minor == 5 && patch < 1) { 340 | t.Errorf("%s: need freetype version >= 2.5.1.\n"+ 341 | "Try setting LD_LIBRARY_PATH=/path/to/freetype_built_from_src/objs/.libs/\n"+ 342 | "and re-running testdata/make-other-hinting-txts.sh", 343 | tc.name) 344 | continue 345 | } 346 | } else { 347 | t.Errorf("%s: no version information", tc.name) 348 | continue 349 | } 350 | for scanner.Scan() { 351 | wants = append(wants, scalingTestParse(scanner.Text())) 352 | } 353 | if err := scanner.Err(); err != nil && err != io.EOF { 354 | t.Errorf("%s: Scanner: %v", tc.name, err) 355 | continue 356 | } 357 | 358 | glyphBuf := &GlyphBuf{} 359 | for i, want := range wants { 360 | if err = glyphBuf.Load(f, fixed.I(tc.size), Index(i), h); err != nil { 361 | t.Errorf("%s: glyph #%d: Load: %v", tc.name, i, err) 362 | continue 363 | } 364 | got := scalingTestData{ 365 | advanceWidth: glyphBuf.AdvanceWidth, 366 | bounds: glyphBuf.Bounds, 367 | points: glyphBuf.Points, 368 | } 369 | 370 | if got.advanceWidth != want.advanceWidth { 371 | t.Errorf("%s: glyph #%d advance width:\ngot %v\nwant %v", 372 | tc.name, i, got.advanceWidth, want.advanceWidth) 373 | continue 374 | } 375 | 376 | if got.bounds != want.bounds { 377 | t.Errorf("%s: glyph #%d bounds:\ngot %v\nwant %v", 378 | tc.name, i, got.bounds, want.bounds) 379 | continue 380 | } 381 | 382 | for i := range got.points { 383 | got.points[i].Flags &= 0x01 384 | } 385 | if len(got.points) != len(want.points) { 386 | t.Errorf("%s: glyph #%d:\ngot %v\nwant %v\ndifferent slice lengths: %d versus %d", 387 | tc.name, i, got.points, want.points, len(got.points), len(want.points)) 388 | continue 389 | } 390 | if j, equals := scalingTestEquals(got.points, want.points); !equals { 391 | t.Errorf("%s: glyph #%d:\ngot %v\nwant %v\nat index %d: %v versus %v", 392 | tc.name, i, got.points, want.points, j, got.points[j], want.points[j]) 393 | continue 394 | } 395 | } 396 | } 397 | } 398 | 399 | func TestScalingHintingNone(t *testing.T) { testScaling(t, font.HintingNone) } 400 | func TestScalingHintingFull(t *testing.T) { testScaling(t, font.HintingFull) } 401 | --------------------------------------------------------------------------------