├── .gitignore ├── .gitmodules ├── LICENSE.txt ├── README.rst ├── build.sh ├── color.go ├── constants.go ├── constants_internal.go ├── doc ├── Makefile ├── conf.py ├── futureplan.rst ├── index.rst ├── make.bat └── thanks.rst ├── fontstashmini ├── atlas.go ├── fontstash_mini.go └── truetype │ ├── baking.go │ ├── loading.go │ └── truetype.go ├── gl_backend.go ├── gl_struct.go ├── nanovgo.go ├── nanovgo_test.go ├── paint.go ├── perfgraph └── performancegraph.go ├── platform_mobile.go ├── platform_other.go ├── platform_webgl.go ├── sample ├── Roboto-Bold.ttf ├── Roboto-Light.ttf ├── Roboto-Regular.ttf ├── demo │ ├── bindata.go │ └── demo.go ├── entypo.ttf ├── images │ ├── image1.jpg │ ├── image10.jpg │ ├── image11.jpg │ ├── image12.jpg │ ├── image2.jpg │ ├── image3.jpg │ ├── image4.jpg │ ├── image5.jpg │ ├── image6.jpg │ ├── image7.jpg │ ├── image8.jpg │ └── image9.jpg ├── sample.go └── web.go ├── structs.go ├── transform.go └── util.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.iml 3 | *.swp 4 | doc/_build 5 | 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "gh-pages"] 2 | path = gh-pages 3 | url = git@github.com:shibukawa/nanovgo.git 4 | branch = gh-pages 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Yoshiki Shibukawa yoshiki@shibu.jp 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | NanoVGo 2 | ============= 3 | 4 | Pure golang implementation of `NanoVG `_. NanoVG is a vector graphics engine inspired by HTML5 Canvas API. 5 | 6 | `DEMO `_ 7 | 8 | API Reference 9 | --------------- 10 | 11 | See `GoDoc `_ 12 | 13 | Porting Memo 14 | -------------- 15 | 16 | * Root folder ``.go`` files 17 | 18 | Ported from NanoVG. 19 | 20 | * ``fontstashmini/fontstash_mini.go`` 21 | 22 | Ported from `fontstash `_. It includes only needed functions. 23 | 24 | * ``fontstashmini/truetype`` 25 | 26 | Copy from ``https://github.com/TheOnly92/fontstash.go`` (Public Domain) 27 | 28 | License 29 | ---------- 30 | 31 | zlib license 32 | 33 | Original (NanoVG) Author 34 | --------------------------- 35 | 36 | * `Mikko Mononen `_ 37 | 38 | Author 39 | --------------- 40 | 41 | * `Yoshiki Shibukawa `_ 42 | 43 | Contribution 44 | ---------------- 45 | 46 | * Moriyoshi Koizumi 47 | * @hnakamur2 48 | * @mattn_jp 49 | * @hagat 50 | * @h_doxas 51 | * FSX 52 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | pushd sample 4 | 5 | echo "generate for desktop" 6 | 7 | go build 8 | 9 | echo "generate for web" 10 | 11 | rm -rf assets 12 | mkdir assets 13 | cp -r images/* assets/ 14 | cp ./Roboto-Bold.ttf assets 15 | cp ./Roboto-Regular.ttf assets 16 | cp ./entypo.ttf assets 17 | go-bindata -tags "js" -pkg "demo" -o demo/bindata.go assets 18 | gopherjs build -o ./gh-pages/demo/sample.js 19 | 20 | popd 21 | -------------------------------------------------------------------------------- /color.go: -------------------------------------------------------------------------------- 1 | package nanovgo 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | // Color utils 8 | // 9 | // Colors in NanoVGo are stored as unsigned ints in ABGR format. 10 | type Color struct { 11 | R, G, B, A float32 12 | } 13 | 14 | // TransRGBA sets transparency of a color value. 15 | func (c Color) TransRGBA(a uint8) Color { 16 | c.A = float32(a) / 255.0 17 | return c 18 | } 19 | 20 | // TransRGBAf sets transparency of a color value. 21 | func (c Color) TransRGBAf(a float32) Color { 22 | c.A = a 23 | return c 24 | } 25 | 26 | // PreMultiply preset alpha to each color. 27 | func (c Color) PreMultiply() Color { 28 | c.R *= c.A 29 | c.G *= c.A 30 | c.B *= c.A 31 | return c 32 | } 33 | 34 | // List returns color value as array. 35 | func (c Color) List() []float32 { 36 | return []float32{c.R, c.G, c.B, c.A} 37 | } 38 | 39 | // Convert To HSLA 40 | func (c Color) HSLA() (h, s, l, a float32) { 41 | max := maxFs(c.R, c.G, c.B) 42 | min := minFs(c.R, c.G, c.B) 43 | 44 | l = (max + min) * 0.5 45 | 46 | if max == min { 47 | h = 0 48 | s = 0 49 | } else { 50 | if max == c.R { 51 | h = ((c.G - c.B) / (max - min)) * 1.0 / 6.0 52 | } else if max == c.G { 53 | h = ((c.B-c.R)/(max-min))*1.0/6.0 + 1.0/3.0 54 | } else { 55 | h = ((c.R-c.G)/(max-min))*1.0/6.0 + 2.0/3.0 56 | } 57 | h = float32(math.Mod(float64(h), 1.0)) 58 | if l <= 0.5 { 59 | s = (max - min) / (max + min) 60 | } else { 61 | s = (max - min) / (2.0 - max - min) 62 | } 63 | } 64 | a = c.A 65 | return 66 | } 67 | 68 | // Calc luminance value 69 | func (c Color) Luminance() float32 { 70 | return c.R*0.299 + c.G*0.587 + c.B*0.144 71 | } 72 | 73 | // Calc constraint color 74 | func (c Color) ContrastingColor() Color { 75 | if c.Luminance() < 0.5 { 76 | return MONO(255, 255) 77 | } 78 | return MONO(0, 255) 79 | } 80 | 81 | // RGB returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f). 82 | func RGB(r, g, b uint8) Color { 83 | return RGBA(r, g, b, 255) 84 | } 85 | 86 | // RGBf returns a color value from red, green, blue values. Alpha will be set to 1.0f. 87 | func RGBf(r, g, b float32) Color { 88 | return RGBAf(r, g, b, 1.0) 89 | } 90 | 91 | // RGBA returns a color value from red, green, blue and alpha values. 92 | func RGBA(r, g, b, a uint8) Color { 93 | return Color{ 94 | R: float32(r) / 255.0, 95 | G: float32(g) / 255.0, 96 | B: float32(b) / 255.0, 97 | A: float32(a) / 255.0, 98 | } 99 | } 100 | 101 | // RGBAf returns a color value from red, green, blue and alpha values. 102 | func RGBAf(r, g, b, a float32) Color { 103 | return Color{r, g, b, a} 104 | } 105 | 106 | // HSL returns color value specified by hue, saturation and lightness. 107 | // HSL values are all in range [0..1], alpha will be set to 255. 108 | func HSL(h, s, l float32) Color { 109 | return HSLA(h, s, l, 255) 110 | } 111 | 112 | // HSLA returns color value specified by hue, saturation and lightness and alpha. 113 | // HSL values are all in range [0..1], alpha in range [0..255] 114 | func HSLA(h, s, l float32, a uint8) Color { 115 | h = float32(math.Mod(float64(h), 1.0)) 116 | if h < 0.0 { 117 | h += 1.0 118 | } 119 | s = clampF(s, 0.0, 1.0) 120 | l = clampF(l, 0.0, 1.0) 121 | var m2 float32 122 | if l <= 0.5 { 123 | m2 = l * (1 + s) 124 | } else { 125 | m2 = l + s - l*s 126 | } 127 | m1 := 2*l - m2 128 | return Color{ 129 | R: clampF(hue(h+1.0/3.0, m1, m2), 0.0, 1.0), 130 | G: clampF(hue(h, m1, m2), 0.0, 1.0), 131 | B: clampF(hue(h-1.0/3.0, m1, m2), 0.0, 1.0), 132 | A: float32(a) / 255.0, 133 | } 134 | } 135 | 136 | // MONO returns color value specified by intensity value. 137 | func MONO(i, alpha uint8) Color { 138 | return RGBA(i, i, i, alpha) 139 | } 140 | 141 | // MONOf returns color value specified by intensity value. 142 | func MONOf(i, alpha float32) Color { 143 | return RGBAf(i, i, i, alpha) 144 | } 145 | 146 | // LerpRGBA linearly interpolates from color c0 to c1, and returns resulting color value. 147 | func LerpRGBA(c0, c1 Color, u float32) Color { 148 | u = clampF(u, 0.0, 1.0) 149 | oneMinus := 1 - u 150 | return Color{ 151 | R: c0.R*oneMinus + c1.R*u, 152 | G: c0.G*oneMinus + c1.G*u, 153 | B: c0.B*oneMinus + c1.B*u, 154 | A: c0.A*oneMinus + c1.A*u, 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /constants.go: -------------------------------------------------------------------------------- 1 | package nanovgo 2 | 3 | // CreateFlags is used when NewContext() to create NanoVGo context. 4 | type CreateFlags int 5 | 6 | const ( 7 | // AntiAlias sets NanoVGo to use AA 8 | AntiAlias CreateFlags = 1 << 0 9 | // StencilStrokes sets NanoVGo to use stencil buffer to draw strokes 10 | StencilStrokes CreateFlags = 1 << 1 11 | // Debug shows OpenGL errors to console 12 | Debug CreateFlags = 1 << 2 13 | ) 14 | 15 | const ( 16 | // Kappa90 is length proportional to radius of a cubic bezier handle for 90deg arcs.) 17 | Kappa90 float32 = 0.5522847493 18 | // PI of float32 19 | PI float32 = 3.14159265358979323846264338327 20 | ) 21 | 22 | // Direction is used with Context.Arc 23 | type Direction int 24 | 25 | const ( 26 | // CounterClockwise specify Arc curve direction 27 | CounterClockwise Direction = 1 28 | // Clockwise specify Arc curve direction 29 | Clockwise Direction = 2 30 | ) 31 | 32 | // LineCap is used for line cap and joint 33 | type LineCap int 34 | 35 | const ( 36 | // Butt is used for line cap (default value) 37 | Butt LineCap = iota 38 | // Round is used for line cap and joint 39 | Round 40 | // Square is used for line cap 41 | Square 42 | // Bevel is used for joint 43 | Bevel 44 | // Miter is used for joint (default value) 45 | Miter 46 | ) 47 | 48 | // Align is used for text location 49 | type Align int 50 | 51 | const ( 52 | // AlignLeft (default) is used for horizontal align. Align text horizontally to left. 53 | AlignLeft Align = 1 << 0 54 | // AlignCenter is used for horizontal align. Align text horizontally to center. 55 | AlignCenter Align = 1 << 1 56 | // AlignRight is used for horizontal align. Align text horizontally to right. 57 | AlignRight Align = 1 << 2 58 | // AlignTop is used for vertical align. Align text vertically to top. 59 | AlignTop Align = 1 << 3 60 | // AlignMiddle is used for vertical align. Align text vertically to middle. 61 | AlignMiddle Align = 1 << 4 62 | // AlignBottom is used for vertical align. Align text vertically to bottom. 63 | AlignBottom Align = 1 << 5 64 | // AlignBaseline (default) is used for vertical align. Align text vertically to baseline. 65 | AlignBaseline Align = 1 << 6 66 | ) 67 | 68 | // ImageFlags is used for setting image object 69 | type ImageFlags int 70 | 71 | const ( 72 | // ImageGenerateMipmaps generates mipmaps during creation of the image. 73 | ImageGenerateMipmaps ImageFlags = 1 << 0 74 | // ImageRepeatX repeats image in X direction. 75 | ImageRepeatX ImageFlags = 1 << 1 76 | // ImageRepeatY repeats image in X direction. 77 | ImageRepeatY ImageFlags = 1 << 2 78 | // ImageFlippy flips (inverses) image in Y direction when rendered. 79 | ImageFlippy ImageFlags = 1 << 3 80 | // ImagePreMultiplied specifies image data has premultiplied alpha. 81 | ImagePreMultiplied ImageFlags = 1 << 4 82 | ) 83 | 84 | // Winding is used for changing filling strategy 85 | type Winding int 86 | 87 | const ( 88 | // Solid fills internal hole 89 | Solid Winding = 1 90 | // Hole keeps internal hole 91 | Hole Winding = 2 92 | ) 93 | -------------------------------------------------------------------------------- /constants_internal.go: -------------------------------------------------------------------------------- 1 | package nanovgo 2 | 3 | const ( 4 | nvgInitFontImageSize = 512 5 | nvgMaxFontImageSize = 2048 6 | nvgMaxFontImages = 4 7 | 8 | nvgInitCommandsSize = 256 9 | nvgInitPointsSize = 128 10 | nvgInitPathsSize = 16 11 | nvgInitVertsSize = 256 12 | nvgMaxStates = 32 13 | ) 14 | 15 | type nvgCommands int 16 | 17 | const ( 18 | nvgMOVETO nvgCommands = iota 19 | nvgLINETO 20 | nvgBEZIERTO 21 | nvgCLOSE 22 | nvgWINDING 23 | ) 24 | 25 | type nvgPointFlags int 26 | 27 | const ( 28 | nvgPtCORNER nvgPointFlags = 0x01 29 | nvgPtLEFT nvgPointFlags = 0x02 30 | nvgPtBEVEL nvgPointFlags = 0x04 31 | nvgPrINNERBEVEL nvgPointFlags = 0x08 32 | ) 33 | 34 | type nvgTextureType int 35 | 36 | const ( 37 | nvgTextureALPHA nvgTextureType = 1 38 | nvgTextureRGBA nvgTextureType = 2 39 | ) 40 | 41 | type nvgCodePointSize int 42 | 43 | const ( 44 | nvgNEWLINE nvgCodePointSize = iota 45 | nvgSPACE 46 | nvgCHAR 47 | ) 48 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) ../gh-pages/ 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/NanoVGo.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/NanoVGo.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/NanoVGo" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/NanoVGo" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # NanoVGo documentation build configuration file, created by 4 | # sphinx-quickstart on Fri Nov 27 20:24:55 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | import shlex 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | #sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [] 33 | 34 | # Add any paths that contain templates here, relative to this directory. 35 | templates_path = ['_templates'] 36 | 37 | # The suffix(es) of source filenames. 38 | # You can specify multiple suffix as a list of string: 39 | # source_suffix = ['.rst', '.md'] 40 | source_suffix = '.rst' 41 | 42 | # The encoding of source files. 43 | #source_encoding = 'utf-8-sig' 44 | 45 | # The master toctree document. 46 | master_doc = 'index' 47 | 48 | # General information about the project. 49 | project = u'NanoVGo' 50 | copyright = u'2015, Yoshiki Shibukawa' 51 | author = u'Yoshiki Shibukawa' 52 | 53 | # The version info for the project you're documenting, acts as replacement for 54 | # |version| and |release|, also used in various other places throughout the 55 | # built documents. 56 | # 57 | # The short X.Y version. 58 | version = '1.0' 59 | # The full version, including alpha/beta/rc tags. 60 | release = '1.0' 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | # 65 | # This is also used if you do content translation via gettext catalogs. 66 | # Usually you set "language" from the command line for these cases. 67 | language = None 68 | 69 | # There are two options for replacing |today|: either, you set today to some 70 | # non-false value, then it is used: 71 | #today = '' 72 | # Else, today_fmt is used as the format for a strftime call. 73 | #today_fmt = '%B %d, %Y' 74 | 75 | # List of patterns, relative to source directory, that match files and 76 | # directories to ignore when looking for source files. 77 | exclude_patterns = ['_build'] 78 | 79 | # The reST default role (used for this markup: `text`) to use for all 80 | # documents. 81 | #default_role = None 82 | 83 | # If true, '()' will be appended to :func: etc. cross-reference text. 84 | #add_function_parentheses = True 85 | 86 | # If true, the current module name will be prepended to all description 87 | # unit titles (such as .. function::). 88 | #add_module_names = True 89 | 90 | # If true, sectionauthor and moduleauthor directives will be shown in the 91 | # output. They are ignored by default. 92 | #show_authors = False 93 | 94 | # The name of the Pygments (syntax highlighting) style to use. 95 | pygments_style = 'sphinx' 96 | 97 | # A list of ignored prefixes for module index sorting. 98 | #modindex_common_prefix = [] 99 | 100 | # If true, keep warnings as "system message" paragraphs in the built documents. 101 | #keep_warnings = False 102 | 103 | # If true, `todo` and `todoList` produce output, else they produce nothing. 104 | todo_include_todos = False 105 | 106 | 107 | # -- Options for HTML output ---------------------------------------------- 108 | 109 | # The theme to use for HTML and HTML Help pages. See the documentation for 110 | # a list of builtin themes. 111 | html_theme = 'alabaster' 112 | 113 | # Theme options are theme-specific and customize the look and feel of a theme 114 | # further. For a list of options available for each theme, see the 115 | # documentation. 116 | #html_theme_options = {} 117 | 118 | # Add any paths that contain custom themes here, relative to this directory. 119 | #html_theme_path = [] 120 | 121 | # The name for this set of Sphinx documents. If None, it defaults to 122 | # " v documentation". 123 | #html_title = None 124 | 125 | # A shorter title for the navigation bar. Default is the same as html_title. 126 | #html_short_title = None 127 | 128 | # The name of an image file (relative to this directory) to place at the top 129 | # of the sidebar. 130 | #html_logo = None 131 | 132 | # The name of an image file (within the static path) to use as favicon of the 133 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 134 | # pixels large. 135 | #html_favicon = None 136 | 137 | # Add any paths that contain custom static files (such as style sheets) here, 138 | # relative to this directory. They are copied after the builtin static files, 139 | # so a file named "default.css" will overwrite the builtin "default.css". 140 | html_static_path = ['_static'] 141 | 142 | # Add any extra paths that contain custom files (such as robots.txt or 143 | # .htaccess) here, relative to this directory. These files are copied 144 | # directly to the root of the documentation. 145 | #html_extra_path = [] 146 | 147 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 148 | # using the given strftime format. 149 | #html_last_updated_fmt = '%b %d, %Y' 150 | 151 | # If true, SmartyPants will be used to convert quotes and dashes to 152 | # typographically correct entities. 153 | #html_use_smartypants = True 154 | 155 | # Custom sidebar templates, maps document names to template names. 156 | #html_sidebars = {} 157 | 158 | # Additional templates that should be rendered to pages, maps page names to 159 | # template names. 160 | #html_additional_pages = {} 161 | 162 | # If false, no module index is generated. 163 | #html_domain_indices = True 164 | 165 | # If false, no index is generated. 166 | #html_use_index = True 167 | 168 | # If true, the index is split into individual pages for each letter. 169 | #html_split_index = False 170 | 171 | # If true, links to the reST sources are added to the pages. 172 | #html_show_sourcelink = True 173 | 174 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 175 | #html_show_sphinx = True 176 | 177 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 178 | #html_show_copyright = True 179 | 180 | # If true, an OpenSearch description file will be output, and all pages will 181 | # contain a tag referring to it. The value of this option must be the 182 | # base URL from which the finished HTML is served. 183 | #html_use_opensearch = '' 184 | 185 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 186 | #html_file_suffix = None 187 | 188 | # Language to be used for generating the HTML full-text search index. 189 | # Sphinx supports the following languages: 190 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 191 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 192 | #html_search_language = 'en' 193 | 194 | # A dictionary with options for the search language support, empty by default. 195 | # Now only 'ja' uses this config value 196 | #html_search_options = {'type': 'default'} 197 | 198 | # The name of a javascript file (relative to the configuration directory) that 199 | # implements a search results scorer. If empty, the default will be used. 200 | #html_search_scorer = 'scorer.js' 201 | 202 | # Output file base name for HTML help builder. 203 | htmlhelp_basename = 'NanoVGodoc' 204 | 205 | # -- Options for LaTeX output --------------------------------------------- 206 | 207 | latex_elements = { 208 | # The paper size ('letterpaper' or 'a4paper'). 209 | #'papersize': 'letterpaper', 210 | 211 | # The font size ('10pt', '11pt' or '12pt'). 212 | #'pointsize': '10pt', 213 | 214 | # Additional stuff for the LaTeX preamble. 215 | #'preamble': '', 216 | 217 | # Latex figure (float) alignment 218 | #'figure_align': 'htbp', 219 | } 220 | 221 | # Grouping the document tree into LaTeX files. List of tuples 222 | # (source start file, target name, title, 223 | # author, documentclass [howto, manual, or own class]). 224 | latex_documents = [ 225 | (master_doc, 'NanoVGo.tex', u'NanoVGo Documentation', 226 | u'Yoshiki Shibukawa', 'manual'), 227 | ] 228 | 229 | # The name of an image file (relative to this directory) to place at the top of 230 | # the title page. 231 | #latex_logo = None 232 | 233 | # For "manual" documents, if this is true, then toplevel headings are parts, 234 | # not chapters. 235 | #latex_use_parts = False 236 | 237 | # If true, show page references after internal links. 238 | #latex_show_pagerefs = False 239 | 240 | # If true, show URL addresses after external links. 241 | #latex_show_urls = False 242 | 243 | # Documents to append as an appendix to all manuals. 244 | #latex_appendices = [] 245 | 246 | # If false, no module index is generated. 247 | #latex_domain_indices = True 248 | 249 | 250 | # -- Options for manual page output --------------------------------------- 251 | 252 | # One entry per manual page. List of tuples 253 | # (source start file, name, description, authors, manual section). 254 | man_pages = [ 255 | (master_doc, 'nanovgo', u'NanoVGo Documentation', 256 | [author], 1) 257 | ] 258 | 259 | # If true, show URL addresses after external links. 260 | #man_show_urls = False 261 | 262 | 263 | # -- Options for Texinfo output ------------------------------------------- 264 | 265 | # Grouping the document tree into Texinfo files. List of tuples 266 | # (source start file, target name, title, author, 267 | # dir menu entry, description, category) 268 | texinfo_documents = [ 269 | (master_doc, 'NanoVGo', u'NanoVGo Documentation', 270 | author, 'NanoVGo', 'One line description of project.', 271 | 'Miscellaneous'), 272 | ] 273 | 274 | # Documents to append as an appendix to all manuals. 275 | #texinfo_appendices = [] 276 | 277 | # If false, no module index is generated. 278 | #texinfo_domain_indices = True 279 | 280 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 281 | #texinfo_show_urls = 'footnote' 282 | 283 | # If true, do not generate a @detailmenu in the "Top" node's menu. 284 | #texinfo_no_detailmenu = False 285 | -------------------------------------------------------------------------------- /doc/futureplan.rst: -------------------------------------------------------------------------------- 1 | Future Plan 2 | --------------- 3 | 4 | Now, it includes all features of `NanoVG `_. 5 | 6 | * Change TrueType font library from `TheOnly92 `_'s code to `pure go freetype `_ to support TTC file. 7 | * Add font fallback mechanism. 8 | * Add default font lists for common operating systems. 9 | * Add any path/render image caching system. 10 | * Auto antialias (if device pixel ratio is bigger than 1, turn off AA for performance) 11 | * Add backend for use mobile/gl package. 12 | * Use float64 instead of float32 on gopher.js (see `performance tips `_) 13 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. NanoVGo documentation master file, created by 2 | sphinx-quickstart on Fri Nov 27 20:24:55 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | NanoVGo 7 | =================================== 8 | 9 | .. image:: https://godoc.org/github.com/shibukawa/nanovgo?status.svg 10 | :target: https://godoc.org/github.com/shibukawa/nanovgo 11 | 12 | .. raw:: html 13 | 14 | 15 | 16 | `Full Screen `_ 17 | 18 | NanoVGo is a pure golang implementation of `NanoVG `_. NanoVG is a vector graphics engine inspired by HTML5 Canvas API. 19 | 20 | It is depend on cross platform `OpenGL/WebGL library `_. Sample code uses cross platform `glfw wrapper `_. I tested on Mac, Windows, and browsers. 21 | 22 | .. note:: 23 | 24 | To build on gopher.js, it needs `this fix `_ to enable stencil buffer feature now. 25 | 26 | .. toctree:: 27 | :maxdepth: 2 28 | 29 | futureplan 30 | thanks 31 | 32 | API Reference 33 | --------------- 34 | 35 | See `GoDoc `_ 36 | 37 | Author 38 | --------------- 39 | 40 | * `Yoshiki Shibukawa `_ 41 | 42 | License 43 | ---------- 44 | 45 | zlib license 46 | 47 | Indices and tables 48 | ================== 49 | 50 | * :ref:`genindex` 51 | * :ref:`modindex` 52 | * :ref:`search` 53 | 54 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 2> nul 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\NanoVGo.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\NanoVGo.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /doc/thanks.rst: -------------------------------------------------------------------------------- 1 | Thanks & Contributers 2 | -------------------------- 3 | 4 | Original author of nanovg. 5 | 6 | * `Mikko Mononen `_ 7 | 8 | My code is heavly depending on `goxjs `_ packages. 9 | 10 | The following guys sent me PR: 11 | 12 | * `FSX `_ 13 | * `Moriyoshi Koizumi `_ 14 | 15 | The following guys helped me on Twitter about OpenGL: 16 | 17 | * @hagat 18 | * @h_doxas 19 | 20 | The following guys helped me on Qiita to implement `zero copy slice type casting `_: 21 | 22 | * @hnakamur2 23 | * @mattn_jp 24 | -------------------------------------------------------------------------------- /fontstashmini/atlas.go: -------------------------------------------------------------------------------- 1 | package fontstashmini 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | type AtlasNode struct { 8 | x, y, width int16 9 | } 10 | 11 | type Atlas struct { 12 | width, height int 13 | nodes []AtlasNode 14 | } 15 | 16 | func newAtlas(width, height, nnode int) *Atlas { 17 | atlas := &Atlas{ 18 | width: width, 19 | height: height, 20 | nodes: make([]AtlasNode, 1, nnode), 21 | } 22 | atlas.nodes[0].x = 0 23 | atlas.nodes[0].y = 0 24 | atlas.nodes[0].width = int16(width) 25 | return atlas 26 | } 27 | 28 | func (atlas *Atlas) rectFits(i, w, h int) int { 29 | x := int(atlas.nodes[i].x) 30 | y := int(atlas.nodes[i].y) 31 | if x+w > atlas.width { 32 | return -1 33 | } 34 | spaceLeft := w 35 | for spaceLeft > 0 { 36 | if i == len(atlas.nodes) { 37 | return -1 38 | } 39 | y = fons__maxi(y, int(atlas.nodes[i].y)) 40 | if y+h > atlas.height { 41 | return -1 42 | } 43 | spaceLeft -= int(atlas.nodes[i].width) 44 | i++ 45 | } 46 | return y 47 | } 48 | 49 | func (atlas *Atlas) addSkylineLevel(idx, x, y, w, h int) { 50 | atlas.insertNode(idx, x, y+h, w) 51 | for i := idx + 1; i < len(atlas.nodes); i++ { 52 | if atlas.nodes[i].x < atlas.nodes[i-1].x+atlas.nodes[i-1].width { 53 | shrink := atlas.nodes[i-1].x + atlas.nodes[i-1].width - atlas.nodes[i].x 54 | atlas.nodes[i].x += shrink 55 | atlas.nodes[i].width -= shrink 56 | if atlas.nodes[i].width <= 0 { 57 | atlas.removeNode(i) 58 | i-- 59 | } else { 60 | break 61 | } 62 | } else { 63 | break 64 | } 65 | } 66 | 67 | for i := 0; i < len(atlas.nodes)-1; i++ { 68 | if atlas.nodes[i].y == atlas.nodes[i+1].y { 69 | atlas.nodes[i].width += atlas.nodes[i+1].width 70 | atlas.removeNode(i + 1) 71 | i-- 72 | } 73 | } 74 | } 75 | 76 | func (atlas *Atlas) insertNode(idx, x, y, w int) { 77 | node := AtlasNode{ 78 | x: int16(x), 79 | y: int16(y), 80 | width: int16(w), 81 | } 82 | atlas.nodes = append(atlas.nodes[:idx], append([]AtlasNode{node}, atlas.nodes[idx:]...)...) 83 | } 84 | 85 | func (atlas *Atlas) removeNode(idx int) { 86 | atlas.nodes = append(atlas.nodes[:idx], atlas.nodes[idx+1:]...) 87 | } 88 | 89 | func (atlas *Atlas) addRect(rw, rh int) (bestX, bestY int, err error) { 90 | bestH := atlas.height 91 | bestW := atlas.width 92 | bestI := -1 93 | bestX = -1 94 | bestY = -1 95 | for i, node := range atlas.nodes { 96 | y := atlas.rectFits(i, rw, rh) 97 | if y != -1 { 98 | if y+rh < bestH || ((y+rh == bestH) && (int(node.width) < bestW)) { 99 | bestI = i 100 | bestW = int(node.width) 101 | bestH = y + rh 102 | bestX = int(node.x) 103 | bestY = y 104 | } 105 | } 106 | } 107 | if bestI == -1 { 108 | err = errors.New("can't find space") 109 | return 110 | } 111 | // Perform the actual packing. 112 | atlas.addSkylineLevel(bestI, bestX, bestY, rw, rh) 113 | return 114 | } 115 | 116 | func (atlas *Atlas) reset(width, height int) { 117 | atlas.width = width 118 | atlas.height = height 119 | if len(atlas.nodes) != 1 { 120 | atlas.nodes = make([]AtlasNode, 1, cap(atlas.nodes)) 121 | atlas.nodes[0].x = 0 122 | atlas.nodes[0].y = 0 123 | atlas.nodes[0].width = int16(width) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /fontstashmini/fontstash_mini.go: -------------------------------------------------------------------------------- 1 | package fontstashmini 2 | 3 | import ( 4 | "github.com/shibukawa/nanovgo/fontstashmini/truetype" 5 | "io/ioutil" 6 | "math" 7 | ) 8 | 9 | const ( 10 | FONS_VERTEX_COUNT = 1024 11 | FONS_SCRATCH_BUF_SIZE = 16000 12 | FONS_INIT_FONTS = 4 13 | FONS_INIT_GLYPHS = 256 14 | FONS_INIT_ATLAS_NODES = 256 15 | INVALID = -1 16 | ) 17 | 18 | type FONSAlign int 19 | 20 | const ( 21 | // Horizontal Align 22 | ALIGN_LEFT FONSAlign = 1 << 0 // Default 23 | ALIGN_CENTER = 1 << 1 24 | ALIGN_RIGHT = 1 << 2 25 | // Vertical Align 26 | ALIGN_TOP = 1 << 3 27 | ALIGN_MIDDLE = 1 << 4 28 | ALIGN_BOTTOM = 1 << 5 29 | ALIGN_BASELINE = 1 << 6 // Default 30 | ) 31 | 32 | type Params struct { 33 | width, height int 34 | } 35 | 36 | type State struct { 37 | font int 38 | align FONSAlign 39 | size float32 40 | blur float32 41 | spacing float32 42 | } 43 | 44 | type GlyphKey struct { 45 | codePoint rune 46 | size, blur int16 47 | } 48 | 49 | type Glyph struct { 50 | codePoint rune 51 | Index int 52 | size, blur int16 53 | x0, y0, x1, y1 int16 54 | xAdv, xOff, yOff int16 55 | } 56 | 57 | type Font struct { 58 | font *truetype.FontInfo 59 | name string 60 | data []byte 61 | freeData uint8 62 | ascender float32 63 | descender float32 64 | lineh float32 65 | glyphs map[GlyphKey]*Glyph 66 | lut []int 67 | } 68 | 69 | type Quad struct { 70 | X0, Y0, S0, T0 float32 71 | X1, Y1, S1, T1 float32 72 | } 73 | 74 | type TextIterator struct { 75 | stash *FontStash 76 | font *Font 77 | 78 | X, Y, NextX, NextY, Scale, Spacing float32 79 | CodePoint rune 80 | Size, Blur int 81 | PrevGlyph *Glyph 82 | CurrentIndex int 83 | NextIndex int 84 | End int 85 | Runes []rune 86 | } 87 | 88 | type FontStash struct { 89 | params Params 90 | itw, ith float32 91 | textureData []byte 92 | dirtyRect [4]int 93 | fonts []*Font 94 | atlas *Atlas 95 | verts []float32 96 | tcoords []float32 97 | scratch []byte 98 | nscratch int 99 | state State 100 | } 101 | 102 | func New(width, height int) *FontStash { 103 | params := Params{ 104 | width: width, 105 | height: height, 106 | } 107 | stash := &FontStash{ 108 | params: params, 109 | atlas: newAtlas(params.width, params.height, FONS_INIT_ATLAS_NODES), 110 | fonts: make([]*Font, 0, 4), 111 | itw: 1.0 / float32(params.width), 112 | ith: 1.0 / float32(params.height), 113 | textureData: make([]byte, params.width*params.height), 114 | verts: make([]float32, 0, FONS_VERTEX_COUNT*2), 115 | tcoords: make([]float32, 0, FONS_VERTEX_COUNT*2), 116 | dirtyRect: [4]int{params.width, params.height, 0, 0}, 117 | state: State{ 118 | size: 12.0, 119 | font: 0, 120 | blur: 0.0, 121 | spacing: 0.0, 122 | align: ALIGN_LEFT | ALIGN_BASELINE, 123 | }, 124 | } 125 | stash.addWhiteRect(2, 2) 126 | 127 | return stash 128 | } 129 | 130 | func (stash *FontStash) AddFont(name, path string) int { 131 | data, err := ioutil.ReadFile(path) 132 | if err != nil { 133 | return INVALID 134 | } 135 | return stash.AddFontFromMemory(name, data, 1) 136 | } 137 | 138 | func (stash *FontStash) AddFontFromMemory(name string, data []byte, freeData uint8) int { 139 | fontInstance, err := truetype.InitFont(data, 0) 140 | if err != nil { 141 | return INVALID 142 | } 143 | ascent, descent, lineGap := fontInstance.GetFontVMetrics() 144 | fh := float32(ascent - descent) 145 | 146 | font := &Font{ 147 | glyphs: make(map[GlyphKey]*Glyph), 148 | name: name, 149 | data: data, 150 | freeData: freeData, 151 | font: fontInstance, 152 | ascender: float32(ascent) / fh, 153 | descender: float32(descent) / fh, 154 | lineh: (fh + float32(lineGap)) / fh, 155 | } 156 | stash.fonts = append(stash.fonts, font) 157 | return len(stash.fonts) - 1 158 | } 159 | 160 | func (stash *FontStash) GetFontByName(name string) int { 161 | for i, font := range stash.fonts { 162 | if font.name == name { 163 | return i 164 | } 165 | } 166 | return INVALID 167 | } 168 | 169 | func (stash *FontStash) SetSize(size float32) { 170 | stash.state.size = size 171 | } 172 | 173 | func (stash *FontStash) SetSpacing(spacing float32) { 174 | stash.state.spacing = spacing 175 | } 176 | 177 | func (stash *FontStash) SetBlur(blur float32) { 178 | stash.state.blur = blur 179 | } 180 | 181 | func (stash *FontStash) SetAlign(align FONSAlign) { 182 | stash.state.align = align 183 | } 184 | 185 | func (stash *FontStash) SetFont(font int) { 186 | stash.state.font = font 187 | } 188 | 189 | func (stash *FontStash) GetFontName() string { 190 | return stash.fonts[stash.state.font].name 191 | } 192 | 193 | func (stash *FontStash) VerticalMetrics() (float32, float32, float32) { 194 | state := stash.state 195 | if len(stash.fonts) < state.font+1 { 196 | return -1, -1, -1 197 | } 198 | font := stash.fonts[state.font] 199 | iSize := float32(int16(state.size * 10.0)) 200 | return font.ascender * iSize / 10.0, font.descender * iSize / 10.0, font.lineh * iSize / 10.0 201 | } 202 | 203 | func (stash *FontStash) LineBounds(y float32) (minY, maxY float32) { 204 | state := stash.state 205 | if len(stash.fonts) < state.font+1 { 206 | return -1, -1 207 | } 208 | font := stash.fonts[state.font] 209 | iSize := float32(int16(state.size * 10.0)) 210 | 211 | y += stash.getVerticalAlign(font, state.align, iSize) 212 | 213 | // FontStash mini support only ZERO_TOPLEFT 214 | minY = y - font.ascender*iSize/10.0 215 | maxY = minY + font.lineh*iSize/10.0 216 | return 217 | } 218 | 219 | func (stash *FontStash) ValidateTexture() []int { 220 | if stash.dirtyRect[0] < stash.dirtyRect[2] && stash.dirtyRect[1] < stash.dirtyRect[3] { 221 | dirty := make([]int, 4) 222 | copy(dirty[0:4], stash.dirtyRect[:]) 223 | stash.dirtyRect[0] = stash.params.width 224 | stash.dirtyRect[1] = stash.params.height 225 | stash.dirtyRect[2] = 0 226 | stash.dirtyRect[3] = 0 227 | return dirty 228 | } 229 | return nil 230 | } 231 | 232 | func (stash *FontStash) GetTextureData() ([]byte, int, int) { 233 | return stash.textureData, stash.params.width, stash.params.height 234 | } 235 | 236 | func (stash *FontStash) ResetAtlas(width, height int) { 237 | // Flush pending glyphs 238 | stash.flush() 239 | // Reset atlas 240 | stash.atlas.reset(width, height) 241 | // Clear texture data 242 | stash.textureData = make([]byte, width*height) 243 | // Reset dirty rect 244 | stash.dirtyRect[0] = width 245 | stash.dirtyRect[1] = height 246 | stash.dirtyRect[2] = 0 247 | stash.dirtyRect[3] = 0 248 | // reset cached glyphs 249 | for _, font := range stash.fonts { 250 | font.glyphs = make(map[GlyphKey]*Glyph) 251 | } 252 | stash.params.width = width 253 | stash.params.height = height 254 | stash.itw = 1.0 / float32(width) 255 | stash.ith = 1.0 / float32(height) 256 | // Add white rect at 0, 0 for debug drawing 257 | stash.addWhiteRect(2, 2) 258 | } 259 | 260 | func (stash *FontStash) TextBounds(x, y float32, str string) (float32, []float32) { 261 | return stash.TextBoundsOfRunes(x, y, []rune(str)) 262 | } 263 | 264 | func (stash *FontStash) TextBoundsOfRunes(x, y float32, runes []rune) (float32, []float32) { 265 | state := stash.state 266 | prevGlyphIndex := -1 267 | size := int(state.size * 10.0) 268 | blur := int(state.blur) 269 | 270 | if len(stash.fonts) < state.font+1 { 271 | return 0, nil 272 | } 273 | font := stash.fonts[state.font] 274 | 275 | scale := font.getPixelHeightScale(state.size) 276 | y += stash.getVerticalAlign(font, state.align, float32(size)) 277 | 278 | minX := x 279 | maxX := x 280 | minY := y 281 | maxY := y 282 | startX := x 283 | 284 | for _, codePoint := range runes { 285 | glyph := stash.getGlyph(font, codePoint, size, blur) 286 | if glyph != nil { 287 | var quad Quad 288 | quad, x, y = stash.getQuad(font, prevGlyphIndex, glyph, scale, state.spacing, x, y) 289 | if quad.X0 < minX { 290 | minX = quad.X0 291 | } 292 | if quad.X1 > maxX { 293 | maxX = quad.X1 294 | } 295 | if quad.Y0 < minY { 296 | minY = quad.Y0 297 | } 298 | if quad.Y1 > maxY { 299 | maxY = quad.Y1 300 | } 301 | prevGlyphIndex = glyph.Index 302 | } else { 303 | prevGlyphIndex = -1 304 | } 305 | } 306 | 307 | advance := x - startX 308 | 309 | if (state.align & ALIGN_LEFT) != 0 { 310 | // do nothing 311 | } else if (state.align & ALIGN_RIGHT) != 0 { 312 | minX -= advance 313 | maxX -= advance 314 | } else if (state.align & ALIGN_CENTER) != 0 { 315 | minX -= advance * 0.5 316 | maxX -= advance * 0.5 317 | } 318 | bounds := []float32{minX, minY, maxX, maxY} 319 | return advance, bounds 320 | } 321 | 322 | func (stash *FontStash) TextIter(x, y float32, str string) *TextIterator { 323 | return stash.TextIterForRunes(x, y, []rune(str)) 324 | } 325 | 326 | func (stash *FontStash) TextIterForRunes(x, y float32, runes []rune) *TextIterator { 327 | state := stash.state 328 | if len(stash.fonts) < state.font+1 { 329 | return nil 330 | } 331 | font := stash.fonts[state.font] 332 | if (state.align & ALIGN_LEFT) != 0 { 333 | // do nothing 334 | } else if (state.align & ALIGN_RIGHT) != 0 { 335 | width, _ := stash.TextBoundsOfRunes(x, y, runes) 336 | x -= width 337 | } else if (state.align & ALIGN_CENTER) != 0 { 338 | width, _ := stash.TextBoundsOfRunes(x, y, runes) 339 | x -= width * 0.5 340 | } 341 | y += stash.getVerticalAlign(font, state.align, state.size * 10.0) 342 | iter := &TextIterator{ 343 | stash: stash, 344 | font: font, 345 | X: x, 346 | Y: y, 347 | NextX: x, 348 | NextY: y, 349 | Spacing: state.spacing, 350 | Size: int(state.size * 10.0), 351 | Blur: int(state.blur), 352 | Scale: font.getPixelHeightScale(state.size), 353 | CurrentIndex: 0, 354 | NextIndex: 0, 355 | End: len(runes), 356 | CodePoint: 0, 357 | PrevGlyph: nil, 358 | Runes: runes, 359 | } 360 | return iter 361 | } 362 | 363 | func (iter *TextIterator) Next() (quad Quad, ok bool) { 364 | iter.CurrentIndex = iter.NextIndex 365 | if iter.CurrentIndex == iter.End { 366 | return Quad{}, false 367 | } 368 | current := iter.NextIndex 369 | stash := iter.stash 370 | font := iter.font 371 | 372 | iter.CodePoint = iter.Runes[current] 373 | current++ 374 | iter.X = iter.NextX 375 | iter.Y = iter.NextY 376 | glyph := stash.getGlyph(font, iter.CodePoint, iter.Size, iter.Blur) 377 | prevGlyphIndex := -1 378 | if iter.PrevGlyph != nil { 379 | prevGlyphIndex = iter.PrevGlyph.Index 380 | } 381 | if glyph != nil { 382 | quad, iter.NextX, iter.NextY = iter.stash.getQuad(font, prevGlyphIndex, glyph, iter.Scale, iter.Spacing, iter.NextX, iter.NextY) 383 | } 384 | iter.PrevGlyph = glyph 385 | iter.NextIndex = current 386 | return quad, true 387 | } 388 | 389 | func (stash *FontStash) flush() { 390 | // Flush texture 391 | stash.ValidateTexture() 392 | // Flush triangles 393 | if len(stash.verts) > 0 { 394 | stash.verts = make([]float32, 0, FONS_VERTEX_COUNT*2) 395 | } 396 | } 397 | 398 | func (stash *FontStash) addWhiteRect(w, h int) { 399 | gx, gy, err := stash.atlas.addRect(w, h) 400 | if err != nil { 401 | return 402 | } 403 | gr := gx + w 404 | gb := gy + h 405 | 406 | for y := gy; y < gb; y++ { 407 | for x := gx; x < gr; x++ { 408 | stash.textureData[x+y*stash.params.width] = 0xff 409 | } 410 | } 411 | 412 | stash.dirtyRect[0] = fons__mini(stash.dirtyRect[0], gx) 413 | stash.dirtyRect[1] = fons__mini(stash.dirtyRect[1], gy) 414 | stash.dirtyRect[2] = fons__maxi(stash.dirtyRect[2], gr) 415 | stash.dirtyRect[3] = fons__maxi(stash.dirtyRect[3], gb) 416 | } 417 | 418 | func (stash *FontStash) getVerticalAlign(font *Font, align FONSAlign, iSize float32) float32 { 419 | // FontStash mini support only ZERO_TOPLEFT 420 | if (align & ALIGN_BASELINE) != 0 { 421 | return 0.0 422 | } else if (align & ALIGN_TOP) != 0 { 423 | return font.ascender * iSize / 10.0 424 | } else if (align & ALIGN_MIDDLE) != 0 { 425 | return (font.ascender + font.descender) / 2.0 * iSize / 10.0 426 | } else if (align & ALIGN_BOTTOM) != 0 { 427 | return font.descender * iSize / 10.0 428 | } 429 | return 0.0 430 | } 431 | 432 | func (stash *FontStash) getGlyph(font *Font, codePoint rune, size, blur int) *Glyph { 433 | if size < 0 { 434 | return nil 435 | } 436 | if blur > 20 { 437 | blur = 20 438 | } 439 | pad := blur + 2 440 | glyphKey := GlyphKey{ 441 | codePoint: codePoint, 442 | size: int16(size), 443 | blur: int16(blur), 444 | } 445 | glyph, ok := font.glyphs[glyphKey] 446 | if ok { 447 | return glyph 448 | } 449 | scale := font.getPixelHeightScale(float32(size) / 10.0) 450 | index := font.getGlyphIndex(codePoint) 451 | advance, _, x0, y0, x1, y1 := font.buildGlyphBitmap(index, scale) 452 | gw := x1 - x0 + pad*2 453 | gh := y1 - y0 + pad*2 454 | gx, gy, err := stash.atlas.addRect(gw, gh) 455 | if err != nil { 456 | return nil 457 | } 458 | gr := gx + gw 459 | gb := gy + gh 460 | width := stash.params.width 461 | glyph = &Glyph{ 462 | codePoint: codePoint, 463 | Index: index, 464 | size: int16(size), 465 | blur: int16(blur), 466 | x0: int16(gx), 467 | y0: int16(gy), 468 | x1: int16(gr), 469 | y1: int16(gb), 470 | xAdv: int16(scale * float32(advance) * 10.0), 471 | xOff: int16(x0 - pad), 472 | yOff: int16(y0 - pad), 473 | } 474 | font.glyphs[glyphKey] = glyph 475 | // Rasterize 476 | font.renderGlyphBitmap(stash.textureData, gx+pad, gy+pad, x1-x0, y1-y0, width, scale, scale, index) 477 | // Make sure there is one pixel empty border 478 | for y := gy; y < gb; y++ { 479 | stash.textureData[gx+y*width] = 0 480 | stash.textureData[gr-1+y*width] = 0 481 | } 482 | for x := gx; x < gr; x++ { 483 | stash.textureData[x+gy*width] = 0 484 | stash.textureData[x+(gb-1)*width] = 0 485 | } 486 | if blur > 0 { 487 | stash.nscratch = 0 488 | stash.blur(gx, gy, gw, gh, blur) 489 | } 490 | 491 | stash.dirtyRect[0] = fons__mini(stash.dirtyRect[0], gx) 492 | stash.dirtyRect[1] = fons__mini(stash.dirtyRect[1], gy) 493 | stash.dirtyRect[2] = fons__maxi(stash.dirtyRect[2], gr) 494 | stash.dirtyRect[3] = fons__maxi(stash.dirtyRect[3], gb) 495 | 496 | return glyph 497 | } 498 | 499 | func (stash *FontStash) getQuad(font *Font, prevGlyphIndex int, glyph *Glyph, scale, spacing float32, originalX, originalY float32) (quad Quad, x, y float32) { 500 | x = originalX 501 | y = originalY 502 | if prevGlyphIndex != -1 { 503 | adv := float32(font.getGlyphKernAdvance(prevGlyphIndex, glyph.Index)) * scale 504 | x += float32(int(adv + spacing + 0.5)) 505 | } 506 | xOff := float32(int(glyph.xOff + 1)) 507 | yOff := float32(int(glyph.yOff + 1)) 508 | x0 := float32(int(glyph.x0 + 1)) 509 | y0 := float32(int(glyph.y0 + 1)) 510 | x1 := float32(int(glyph.x1 - 1)) 511 | y1 := float32(int(glyph.y1 - 1)) 512 | // only support FONS_ZERO_TOPLEFT 513 | rx := float32(int(x + xOff)) 514 | ry := float32(int(y + yOff)) 515 | 516 | quad = Quad{ 517 | X0: rx, 518 | Y0: ry, 519 | X1: rx + x1 - x0, 520 | Y1: ry + y1 - y0, 521 | S0: x0 * stash.itw, 522 | T0: y0 * stash.ith, 523 | S1: x1 * stash.itw, 524 | T1: y1 * stash.ith, 525 | } 526 | x += float32(int(float32(glyph.xAdv)/10.0 + 0.5)) 527 | return 528 | } 529 | 530 | const ( 531 | APREC = 16 532 | ZPREC = 7 533 | ) 534 | 535 | func (stash *FontStash) blurCols(x0, y0, w, h, alpha int) { 536 | b := y0 + h 537 | r := x0 + w 538 | texture := stash.textureData 539 | textureWidth := stash.params.width 540 | for y := y0; y < b; y++ { 541 | z := 0 // force zero border 542 | yOffset := y * textureWidth 543 | for x := 1 + x0; x < r; x++ { 544 | offset := x + yOffset 545 | z += (alpha * ((int(texture[offset]) << ZPREC) - z)) >> APREC 546 | texture[offset] = byte(z >> ZPREC) 547 | } 548 | texture[r-1+yOffset] = 0 // force zero border 549 | z = 0 550 | for x := r - 2; x >= x0; x-- { 551 | offset := x + yOffset 552 | z += (alpha * ((int(texture[offset]) << ZPREC) - z)) >> APREC 553 | texture[offset] = byte(z >> ZPREC) 554 | } 555 | texture[x0+yOffset] = 0 556 | } 557 | } 558 | 559 | func (stash *FontStash) blurRows(x0, y0, w, h, alpha int) { 560 | b := y0 + h 561 | r := x0 + w 562 | texture := stash.textureData 563 | textureWidth := stash.params.width 564 | for x := x0; x < r; x++ { 565 | z := 0 // force zero border 566 | for y := 1 + y0; y < b; y++ { 567 | offset := x + y*textureWidth 568 | z += (alpha * ((int(texture[offset]) << ZPREC) - z)) >> APREC 569 | texture[offset] = byte(z >> ZPREC) 570 | } 571 | texture[x+(b-1)*textureWidth] = 0 // force zero border 572 | z = 0 573 | for y := b - 2; y >= y0; y-- { 574 | offset := x + y*textureWidth 575 | z += (alpha * ((int(texture[offset]) << ZPREC) - z)) >> APREC 576 | texture[offset] = byte(z >> ZPREC) 577 | } 578 | texture[x+y0*textureWidth] = 0 579 | } 580 | } 581 | 582 | func (stash *FontStash) blur(x, y, width, height, blur int) { 583 | sigma := float64(blur) * 0.57735 // 1 / sqrt(3) 584 | alpha := int(float64(1< b { 593 | return a 594 | } 595 | return b 596 | } 597 | 598 | func fons__mini(a, b int) int { 599 | if a < b { 600 | return a 601 | } 602 | return b 603 | } 604 | 605 | func (font *Font) getPixelHeightScale(size float32) float32 { 606 | return float32(font.font.ScaleForPixelHeight(float64(size))) 607 | } 608 | 609 | func (font *Font) getGlyphKernAdvance(glyph1, glyph2 int) int { 610 | return font.font.GetGlyphKernAdvance(glyph1, glyph2) 611 | } 612 | 613 | func (font *Font) getGlyphIndex(codePoint rune) int { 614 | return font.font.FindGlyphIndex(int(codePoint)) 615 | } 616 | 617 | func (font *Font) buildGlyphBitmap(index int, scale float32) (advance, lsb, x0, y0, x1, y1 int) { 618 | advance, lsb = font.font.GetGlyphHMetrics(index) 619 | x0, y0, x1, y1 = font.font.GetGlyphBitmapBoxSubpixel(index, float64(scale), float64(scale), 0, 0) 620 | return 621 | } 622 | 623 | func (font *Font) renderGlyphBitmap(data []byte, offsetX, offsetY, outWidth, outHeight, outStride int, scaleX, scaleY float32, index int) { 624 | font.font.MakeGlyphBitmapSubpixel(data[offsetY*outStride+offsetX:], outWidth, outHeight, outStride, float64(scaleX), float64(scaleY), 0, 0, index) 625 | } 626 | -------------------------------------------------------------------------------- /fontstashmini/truetype/baking.go: -------------------------------------------------------------------------------- 1 | package truetype 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "math" 7 | ) 8 | 9 | type BakedChar struct { 10 | x0, y0, x1, y1 uint16 // coordinates of bbox in bitmap 11 | xoff, yoff, xadvance float64 12 | } 13 | 14 | type AlignedQuad struct { 15 | X0, Y0, S0, T0 float32 // top-left 16 | X1, Y1, S1, T1 float32 // bottom-right 17 | } 18 | 19 | // Call GetBakedQuad with charIndex = 'character - firstChar', and it creates 20 | // the quad you need to draw and advances the current position. 21 | // 22 | // The coordinate system used assumes y increases downwards. 23 | // 24 | // Characters will extend both above and below the current position. 25 | func GetBakedQuad(chardata []*BakedChar, pw, ph, charIndex int, xpos, ypos float64, openglFillRule bool) (float64, *AlignedQuad) { 26 | q := &AlignedQuad{} 27 | d3dBias := float32(-0.5) 28 | if openglFillRule { 29 | d3dBias = 0 30 | } 31 | ipw := 1 / float32(pw) 32 | iph := 1 / float32(ph) 33 | b := chardata[charIndex] 34 | roundX := float32(math.Floor(xpos + b.xoff + 0.5)) 35 | roundY := float32(math.Floor(ypos + b.yoff + 0.5)) 36 | 37 | q.X0 = roundX + d3dBias 38 | q.Y0 = roundY + d3dBias 39 | q.X1 = roundX + float32(b.x1-b.x0) + d3dBias 40 | q.Y1 = roundY + float32(b.y1-b.y0) + d3dBias 41 | 42 | q.S0 = float32(b.x0) * ipw 43 | q.T0 = float32(b.y0) * iph 44 | q.S1 = float32(b.x1) * ipw 45 | q.T1 = float32(b.y1) * iph 46 | 47 | return xpos + b.xadvance, q 48 | } 49 | 50 | // offset is the font location (use offset=0 for plain .ttf), pixelHeight is the height of font in pixels. pixels is the bitmap to be filled in characters to bake. This uses a very crappy packing. 51 | func BakeFontBitmap(data []byte, offset int, pixelHeight float64, pixels []byte, pw, ph, firstChar, numChars int) (chardata []*BakedChar, err error, bottomY int, rtPixels []byte) { 52 | f, err := InitFont(data, offset) 53 | if err != nil { 54 | return 55 | } 56 | chardata = make([]*BakedChar, 96) 57 | // background of 0 around pixels 58 | copy(pixels, bytes.Repeat([]byte{0}, pw*ph)) 59 | x := 1 60 | y := 1 61 | bottomY = 1 62 | 63 | scale := f.ScaleForPixelHeight(pixelHeight) 64 | 65 | for i := 0; i < numChars; i++ { 66 | g := f.FindGlyphIndex(firstChar + i) 67 | advance, _ := f.GetGlyphHMetrics(g) 68 | x0, y0, x1, y1 := f.GetGlyphBitmapBox(g, scale, scale) 69 | gw := x1 - x0 70 | gh := y1 - y0 71 | if x+gw+1 >= pw { 72 | // advance to next row 73 | y = bottomY 74 | x = 1 75 | } 76 | if y+gh+1 >= ph { 77 | // check if it fits vertically AFTER potentially moving to next row 78 | err = errors.New("Doesn't fit") 79 | bottomY = -i 80 | return 81 | } 82 | if !(x+gw < pw) { 83 | err = errors.New("Error x+gw bottomY { 104 | bottomY = y + gh + 2 105 | } 106 | } 107 | rtPixels = pixels 108 | return 109 | } 110 | -------------------------------------------------------------------------------- /fontstashmini/truetype/loading.go: -------------------------------------------------------------------------------- 1 | package truetype 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // FontInfo is defined publically so you can declare on on the stack or as a 8 | // global or etc, but you should treat it as opaque. 9 | type FontInfo struct { 10 | data []byte // contains the .ttf file 11 | fontStart int // offset of start of font 12 | loca int // table location as offset from start of .ttf 13 | head int 14 | glyf int 15 | hhea int 16 | hmtx int 17 | kern int 18 | numGlyphs int // number of glyphs, needed for range checking 19 | indexMap int // a cmap mapping for our chosen character encoding 20 | indexToLocFormat int // format needed to map from glyph index to glyph 21 | } 22 | 23 | // Each .ttf/.ttc file may have more than one font. Each font has a sequential 24 | // index number starting from 0. Call this function to get the font offset for 25 | // a given index; it returns -1 if the index is out of range. A regular .ttf 26 | // file will only define one font and it always be at offset 0, so it will return 27 | // '0' for index 0, and -1 for all other indices. You can just skip this step 28 | // if you know it's that kind of font. 29 | func GetFontOffsetForIndex(data []byte, index int) int { 30 | if isFont(data) { 31 | if index == 0 { 32 | return 0 33 | } else { 34 | return -1 35 | } 36 | } 37 | 38 | // Check if it's a TTC 39 | if string(data[0:4]) == "ttcf" { 40 | if u32(data, 4) == 0x00010000 || u32(data, 4) == 0x00020000 { 41 | n := int(u32(data, 8)) 42 | if index >= n { 43 | return -1 44 | } 45 | return int(u32(data, 12+index*14)) 46 | } 47 | } 48 | return -1 49 | } 50 | 51 | // Given an offset into the file that defines a font, this function builds the 52 | // necessary cached info for the rest of the system. 53 | func InitFont(data []byte, offset int) (font *FontInfo, err error) { 54 | if len(data)-offset < 12 { 55 | err = errors.New("TTF data is too short") 56 | return 57 | } 58 | font = new(FontInfo) 59 | font.data = data 60 | font.fontStart = offset 61 | 62 | cmap := findTable(data, offset, "cmap") 63 | font.loca = findTable(data, offset, "loca") 64 | font.head = findTable(data, offset, "head") 65 | font.glyf = findTable(data, offset, "glyf") 66 | font.hhea = findTable(data, offset, "hhea") 67 | font.hmtx = findTable(data, offset, "hmtx") 68 | font.kern = findTable(data, offset, "kern") 69 | if cmap == 0 || font.loca == 0 || font.head == 0 || font.glyf == 0 || font.hhea == 0 || font.hmtx == 0 { 70 | err = errors.New("Required table not found") 71 | return 72 | } 73 | 74 | t := findTable(data, offset, "maxp") 75 | if t != 0 { 76 | font.numGlyphs = int(u16(data, t+4)) 77 | } else { 78 | font.numGlyphs = 0xfff 79 | } 80 | 81 | numTables := int(u16(data, cmap+2)) 82 | for i := 0; i < numTables; i++ { 83 | encodingRecord := cmap + 4 + 8*i 84 | switch int(u16(data, encodingRecord)) { 85 | case PLATFORM_ID_MICROSOFT: 86 | switch int(u16(data, encodingRecord+2)) { 87 | case MS_EID_UNICODE_FULL, MS_EID_UNICODE_BMP: 88 | font.indexMap = cmap + int(u32(data, encodingRecord+4)) 89 | } 90 | } 91 | } 92 | 93 | if font.indexMap == 0 { 94 | err = errors.New("Unknown cmap encoding table") 95 | return 96 | } 97 | 98 | font.indexToLocFormat = int(u16(data, font.head+50)) 99 | return 100 | } 101 | -------------------------------------------------------------------------------- /fontstashmini/truetype/truetype.go: -------------------------------------------------------------------------------- 1 | // This library processes TrueType files: 2 | // - parse files 3 | // - extract glyph metrics 4 | // - extract glyph shapes 5 | // - render glyphs to one-channel bitmaps with antialiasing (box filter) 6 | package truetype 7 | 8 | import ( 9 | "container/list" 10 | "math" 11 | "sort" 12 | ) 13 | 14 | const ( 15 | PLATFORM_ID_UNICODE int = iota 16 | PLATFORM_ID_MAC 17 | PLATFORM_ID_ISO 18 | PLATFORM_ID_MICROSOFT 19 | ) 20 | 21 | const ( 22 | MS_EID_SYMBOL int = 0 23 | MS_EID_UNICODE_BMP = 1 24 | MS_EID_SHIFTJIS = 2 25 | MS_EID_UNICODE_FULL = 10 26 | ) 27 | 28 | const ( 29 | vmove uint8 = iota + 1 30 | vline 31 | vcurve 32 | ) 33 | 34 | const ( 35 | tt_FIXSHIFT uint = 10 36 | tt_FIX = (1 << tt_FIXSHIFT) 37 | tt_FIXMASK = (tt_FIX - 1) 38 | ) 39 | 40 | type Vertex struct { 41 | X int 42 | Y int 43 | CX int 44 | CY int 45 | Type uint8 46 | Padding byte 47 | } 48 | 49 | func (font *FontInfo) ScaleForPixelHeight(height float64) float64 { 50 | fheight := float64(u16(font.data, font.hhea+4) - u16(font.data, font.hhea+6)) 51 | return height / fheight 52 | } 53 | 54 | func (font *FontInfo) GetGlyphBitmapBox(glyph int, scaleX, scaleY float64) (int, int, int, int) { 55 | return font.GetGlyphBitmapBoxSubpixel(glyph, scaleX, scaleY, 0, 0) 56 | } 57 | 58 | func (font *FontInfo) GetCodepointHMetrics(codepoint int) (int, int) { 59 | return font.GetGlyphHMetrics(font.FindGlyphIndex(codepoint)) 60 | } 61 | 62 | func (font *FontInfo) GetFontVMetrics() (int, int, int) { 63 | return int(int16(u16(font.data, font.hhea+4))), int(int16(u16(font.data, font.hhea+6))), int(int16(u16(font.data, font.hhea+8))) 64 | } 65 | 66 | func (font *FontInfo) GetGlyphHMetrics(glyphIndex int) (int, int) { 67 | numOfLongHorMetrics := int(u16(font.data, font.hhea+34)) 68 | if glyphIndex < numOfLongHorMetrics { 69 | return int(int16(u16(font.data, font.hmtx+4*glyphIndex))), int(int16(u16(font.data, font.hmtx+4*glyphIndex+2))) 70 | } 71 | return int(int16(u16(font.data, font.hmtx+4*(numOfLongHorMetrics-1)))), int(int16(u16(font.data, font.hmtx+4*numOfLongHorMetrics+2*(glyphIndex-numOfLongHorMetrics)))) 72 | } 73 | 74 | func (font *FontInfo) GetFontBoundingBox() (int, int, int, int) { 75 | return int(int16(u16(font.data, font.head+36))), 76 | int(int16(u16(font.data, font.head+38))), 77 | int(int16(u16(font.data, font.head+40))), 78 | int(int16(u16(font.data, font.head+42))) 79 | } 80 | 81 | func (font *FontInfo) GetCodepointBitmapBox(codepoint int, scaleX, scaleY float64) (int, int, int, int) { 82 | return font.GetCodepointBitmapBoxSubpixel(codepoint, scaleX, scaleY, 0, 0) 83 | } 84 | 85 | func (font *FontInfo) GetCodepointBitmapBoxSubpixel(codepoint int, scaleX, scaleY, shiftX, shiftY float64) (int, int, int, int) { 86 | return font.GetGlyphBitmapBoxSubpixel(font.FindGlyphIndex(codepoint), scaleX, scaleY, shiftX, shiftY) 87 | } 88 | 89 | func (font *FontInfo) GetCodepointBitmap(scaleX, scaleY float64, codePoint, xoff, yoff int) ([]byte, int, int) { 90 | return font.GetCodepointBitmapSubpixel(scaleX, scaleY, 0., 0., codePoint, xoff, yoff) 91 | } 92 | 93 | func (font *FontInfo) GetCodepointBitmapSubpixel(scaleX, scaleY, shiftX, shiftY float64, codePoint, xoff, yoff int) ([]byte, int, int) { 94 | return font.GetGlyphBitmapSubpixel(scaleX, scaleY, shiftX, shiftY, font.FindGlyphIndex(codePoint), xoff, yoff) 95 | } 96 | 97 | type Bitmap struct { 98 | W int 99 | H int 100 | Stride int 101 | Pixels []byte 102 | } 103 | 104 | func (font *FontInfo) GetGlyphBitmapSubpixel(scaleX, scaleY, shiftX, shiftY float64, glyph, xoff, yoff int) ([]byte, int, int) { 105 | var gbm Bitmap 106 | var width, height int 107 | vertices := font.GetGlyphShape(glyph) 108 | if scaleX == 0 { 109 | scaleX = scaleY 110 | } 111 | if scaleY == 0 { 112 | if scaleX == 0 { 113 | return nil, 0, 0 114 | } 115 | scaleY = scaleX 116 | } 117 | 118 | ix0, iy0, ix1, iy1 := font.GetGlyphBitmapBoxSubpixel(glyph, scaleX, scaleY, shiftX, shiftY) 119 | 120 | // now we get the size 121 | gbm.W = ix1 - ix0 122 | gbm.H = iy1 - iy0 123 | gbm.Pixels = nil 124 | 125 | width = gbm.W 126 | height = gbm.H 127 | xoff = ix0 128 | yoff = iy0 129 | 130 | if gbm.W != 0 && gbm.H != 0 { 131 | gbm.Pixels = make([]byte, gbm.W*gbm.H) 132 | gbm.Stride = gbm.W 133 | 134 | Rasterize(&gbm, 0.35, vertices, scaleX, scaleY, shiftX, shiftY, ix0, iy0, true) 135 | } 136 | 137 | return gbm.Pixels, width, height 138 | } 139 | 140 | type point struct { 141 | x float64 142 | y float64 143 | } 144 | 145 | func Rasterize(result *Bitmap, flatnessInPixels float64, vertices []Vertex, scaleX, scaleY, shiftX, shiftY float64, xOff, yOff int, invert bool) { 146 | var scale float64 147 | if scaleX > scaleY { 148 | scale = scaleY 149 | } else { 150 | scale = scaleX 151 | } 152 | windings, windingLengths, windingCount := FlattenCurves(vertices, flatnessInPixels/scale) 153 | if windings != nil { 154 | tt_rasterize(result, windings, windingLengths, windingCount, scaleX, scaleY, shiftX, shiftY, xOff, yOff, invert) 155 | } 156 | } 157 | 158 | func FlattenCurves(vertices []Vertex, objspaceFlatness float64) ([]point, []int, int) { 159 | var contourLengths []int 160 | points := []point{} 161 | 162 | objspaceFlatnessSquared := objspaceFlatness * objspaceFlatness 163 | n := 0 164 | start := 0 165 | 166 | for _, vertex := range vertices { 167 | if vertex.Type == vmove { 168 | n++ 169 | } 170 | } 171 | numContours := n 172 | 173 | if n == 0 { 174 | return nil, nil, 0 175 | } 176 | 177 | contourLengths = make([]int, n) 178 | 179 | var x, y float64 180 | n = -1 181 | for _, vertex := range vertices { 182 | switch vertex.Type { 183 | case vmove: 184 | if n >= 0 { 185 | contourLengths[n] = len(points) - start 186 | } 187 | n++ 188 | start = len(points) 189 | 190 | x = float64(vertex.X) 191 | y = float64(vertex.Y) 192 | points = append(points, point{x, y}) 193 | case vline: 194 | x = float64(vertex.X) 195 | y = float64(vertex.Y) 196 | points = append(points, point{x, y}) 197 | case vcurve: 198 | tesselateCurve(&points, x, y, float64(vertex.CX), float64(vertex.CY), float64(vertex.X), float64(vertex.Y), objspaceFlatnessSquared, 0) 199 | x = float64(vertex.X) 200 | y = float64(vertex.Y) 201 | } 202 | contourLengths[n] = len(points) - start 203 | } 204 | return points, contourLengths, numContours 205 | } 206 | 207 | // tesselate until threshold p is happy... @TODO warped to compensate for non-linear stretching 208 | func tesselateCurve(points *[]point, x0, y0, x1, y1, x2, y2, objspaceFlatnessSquared float64, n int) int { 209 | // midpoint 210 | mx := (x0 + 2*x1 + x2) / 4 211 | my := (y0 + 2*y1 + y2) / 4 212 | // versus directly drawn line 213 | dx := (x0+x2)/2 - mx 214 | dy := (y0+y2)/2 - my 215 | if n > 16 { 216 | return 1 217 | } 218 | if dx*dx+dy*dy > objspaceFlatnessSquared { // half-pixel error allowed... need to be smaller if AA 219 | tesselateCurve(points, x0, y0, (x0+x1)/2, (y0+y1)/2, mx, my, objspaceFlatnessSquared, n+1) 220 | tesselateCurve(points, mx, my, (x1+x2)/2, (y1+y2)/2, x2, y2, objspaceFlatnessSquared, n+1) 221 | } else { 222 | *points = append(*points, point{x2, y2}) 223 | } 224 | return 1 225 | } 226 | 227 | type Edge struct { 228 | x0 float64 229 | y0 float64 230 | x1 float64 231 | y1 float64 232 | invert bool 233 | } 234 | 235 | type Edges []Edge 236 | 237 | func (e Edges) Len() int { return len(e) } 238 | func (e Edges) Swap(i, j int) { e[i], e[j] = e[j], e[i] } 239 | func (e Edges) Less(i, j int) bool { 240 | return e[i].y0 < e[j].y0 241 | } 242 | 243 | func tt_rasterize(result *Bitmap, pts []point, wcount []int, windings int, scaleX, scaleY, shiftX, shiftY float64, offX, offY int, invert bool) { 244 | var yScaleInv float64 245 | if invert { 246 | yScaleInv = -scaleY 247 | } else { 248 | yScaleInv = scaleY 249 | } 250 | var vsubsample int 251 | if result.H < 8 { 252 | vsubsample = 15 253 | } else { 254 | vsubsample = 5 255 | } 256 | // vsubsample should divide 255 evenly; otherwise we won't reach full opacity 257 | 258 | // now we have to blow out the windings into explicit edge lists 259 | n := 0 260 | for i := 0; i < windings; i++ { 261 | n += wcount[i] 262 | } 263 | 264 | e := make([]Edge, n+1) 265 | n = 0 266 | 267 | m := 0 268 | for i := 0; i < windings; i++ { 269 | winding := wcount[i] 270 | p := pts[m:] 271 | m += winding 272 | j := winding - 1 273 | for k := 0; k < winding; k++ { 274 | a := k 275 | b := j 276 | // skip the edge if horizontal 277 | if p[j].y == p[k].y { 278 | j = k 279 | continue 280 | } 281 | // add edge from j to k to the list 282 | e[n].invert = false 283 | if invert { 284 | if p[j].y > p[k].y { 285 | e[n].invert = true 286 | a = j 287 | b = k 288 | } 289 | } else { 290 | if p[j].y < p[k].y { 291 | e[n].invert = true 292 | a = j 293 | b = k 294 | } 295 | } 296 | e[n].x0 = p[a].x*scaleX + shiftX 297 | e[n].y0 = p[a].y*yScaleInv*float64(vsubsample) + shiftY 298 | e[n].x1 = p[b].x*scaleX + shiftX 299 | e[n].y1 = p[b].y*yScaleInv*float64(vsubsample) + shiftY 300 | n++ 301 | j = k 302 | } 303 | } 304 | 305 | // now sort the edges by their highest point (should snap to integer, and then by x 306 | sort.Sort(Edges(e[:n])) 307 | 308 | // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule 309 | rasterizeSortedEdges(result, e, n, vsubsample, offX, offY) 310 | } 311 | 312 | type activeEdge struct { 313 | x int 314 | dx int 315 | ey float64 316 | valid int 317 | } 318 | 319 | func newActive(e *Edge, offX int, startPoint float64) *activeEdge { 320 | z := &activeEdge{} 321 | dxdy := (e.x1 - e.x0) / (e.y1 - e.y0) 322 | if dxdy < 0 { 323 | z.dx = -int(math.Floor(tt_FIX * -dxdy)) 324 | } else { 325 | z.dx = int(math.Floor(tt_FIX * dxdy)) 326 | } 327 | z.x = int(math.Floor(tt_FIX * (e.x0 + dxdy*(startPoint-e.y0)))) 328 | z.x -= offX * tt_FIX 329 | z.ey = e.y1 330 | if e.invert { 331 | z.valid = 1 332 | } else { 333 | z.valid = -1 334 | } 335 | return z 336 | } 337 | 338 | func rasterizeSortedEdges(result *Bitmap, e []Edge, n, vsubsample, offX, offY int) { 339 | var scanline []byte 340 | active := list.New() 341 | maxWeight := (255 / vsubsample) // weight per vertical scanline 342 | 343 | dataLength := 512 344 | if result.W > 512 { 345 | dataLength = result.W 346 | } 347 | 348 | y := offY * vsubsample 349 | e[n].y0 = float64(offY+result.H)*float64(vsubsample) + 1 350 | var j float64 351 | var i int 352 | 353 | for j < float64(result.H) { 354 | scanline = make([]byte, dataLength) 355 | for s := 0; s < vsubsample; s++ { 356 | // find center of pixel for this scanline 357 | scanY := float64(y) + 0.5 358 | 359 | // update all active edges; 360 | // remove all active edges that terminate before the center of this scanline 361 | var next *list.Element 362 | for step := active.Front(); step != nil; step = next { 363 | z := step 364 | if z.Value.(*activeEdge).ey <= scanY { 365 | next = z.Next() 366 | active.Remove(z) 367 | } else { 368 | z.Value.(*activeEdge).x += z.Value.(*activeEdge).dx 369 | next = z.Next() 370 | } 371 | } 372 | 373 | // resort the list if needed 374 | for { 375 | changed := false 376 | for step := active.Front(); step != nil && step.Next() != nil; step = step.Next() { 377 | if step.Value.(*activeEdge).x > step.Next().Value.(*activeEdge).x { 378 | active.MoveBefore(step.Next(), step) 379 | changed = true 380 | step = step.Prev() 381 | } 382 | } 383 | if !changed { 384 | break 385 | } 386 | } 387 | 388 | // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline 389 | for e[i].y0 <= scanY { 390 | if e[i].y1 > scanY { 391 | z := newActive(&e[i], offX, scanY) 392 | if active.Len() == 0 { 393 | active.PushBack(z) 394 | } else if z.x < active.Front().Value.(*activeEdge).x { 395 | active.PushFront(z) 396 | } else { 397 | p := active.Front() 398 | for p.Next() != nil && p.Next().Value.(*activeEdge).x < z.x { 399 | p = p.Next() 400 | } 401 | active.InsertAfter(z, p) 402 | } 403 | } 404 | i++ 405 | } 406 | 407 | // now process all active edges in XOR fashion 408 | if active.Len() > 0 { 409 | scanline = fillActiveEdges(scanline, result.W, active, maxWeight) 410 | } 411 | 412 | y++ 413 | } 414 | copy(result.Pixels[int(j)*result.Stride:], scanline[:result.W]) 415 | // result.Pixels = append(result.Pixels[:int(j)*result.Stride], scanline[:result.W]...) 416 | j++ 417 | } 418 | } 419 | 420 | // note: this routine clips fills that extend off the edges... ideally this 421 | // wouldn't happen, but it could happen if the truetype glyph bounding boxes 422 | // are wrong, or if the user supplies a too-small bitmap 423 | func fillActiveEdges(scanline []byte, length int, e *list.List, maxWeight int) []byte { 424 | // non-zero winding fill 425 | x0 := 0 426 | w := 0 427 | 428 | for p := e.Front(); p != nil; p = p.Next() { 429 | if w == 0 { 430 | // if we're currently at zero, we need to record the edge start point 431 | x0 = p.Value.(*activeEdge).x 432 | w += p.Value.(*activeEdge).valid 433 | } else { 434 | x1 := p.Value.(*activeEdge).x 435 | w += p.Value.(*activeEdge).valid 436 | // if we went to zero, we need to draw 437 | if w == 0 { 438 | i := (x0 >> tt_FIXSHIFT) 439 | j := (x1 >> tt_FIXSHIFT) 440 | 441 | if i < length && j >= 0 { 442 | if i == j { 443 | // x0, x1 are the same pixel, so compute combined coverage 444 | scanline[i] = scanline[i] + uint8((x1-x0)*maxWeight>>tt_FIXSHIFT) 445 | } else { 446 | if i >= 0 { // add antialiasing for x0 447 | scanline[i] = scanline[i] + uint8(((tt_FIX-(x0&tt_FIXMASK))*maxWeight)>>tt_FIXSHIFT) 448 | } else { 449 | i = -1 // clip 450 | } 451 | 452 | if j < length { // add antialiasing for x1 453 | scanline[j] = scanline[j] + uint8(((x1&tt_FIXMASK)*maxWeight)>>tt_FIXSHIFT) 454 | } else { 455 | j = length // clip 456 | } 457 | 458 | for i++; i < j; i++ { // fill pixels between x0 and x1 459 | scanline[i] = scanline[i] + uint8(maxWeight) 460 | } 461 | } 462 | } 463 | } 464 | } 465 | } 466 | return scanline 467 | } 468 | 469 | func (font *FontInfo) GetGlyphBitmapBoxSubpixel(glyph int, scaleX, scaleY, shiftX, shiftY float64) (ix0, iy0, ix1, iy1 int) { 470 | result, x0, y0, x1, y1 := font.GetGlyphBox(glyph) 471 | if !result { 472 | x0 = 0 473 | y0 = 0 474 | x1 = 0 475 | y1 = 0 476 | } 477 | ix0 = int(math.Floor(float64(x0)*scaleX + shiftX)) 478 | iy0 = -int(math.Ceil(float64(y1)*scaleY + shiftY)) 479 | ix1 = int(math.Ceil(float64(x1)*scaleX + shiftX)) 480 | iy1 = -int(math.Floor(float64(y0)*scaleY + shiftY)) 481 | return 482 | } 483 | 484 | func (font *FontInfo) GetGlyphBox(glyph int) (result bool, x0, y0, x1, y1 int) { 485 | g := font.GetGlyphOffset(glyph) 486 | if g < 0 { 487 | result = false 488 | return 489 | } 490 | 491 | x0 = int(int16(u16(font.data, g+2))) 492 | y0 = int(int16(u16(font.data, g+4))) 493 | x1 = int(int16(u16(font.data, g+6))) 494 | y1 = int(int16(u16(font.data, g+8))) 495 | result = true 496 | return 497 | } 498 | 499 | func (font *FontInfo) GetGlyphShape(glyphIndex int) []Vertex { 500 | data := font.data 501 | g := font.GetGlyphOffset(glyphIndex) 502 | if g < 0 { 503 | return nil 504 | } 505 | var vertices []Vertex 506 | 507 | numberOfContours := int(int16(u16(data, g))) 508 | numVertices := 0 509 | 510 | if numberOfContours > 0 { 511 | var flags uint8 512 | endPtsOfContours := g + 10 513 | ins := int(u16(data, g+10+numberOfContours*2)) 514 | points := g + 10 + numberOfContours*2 + 2 + ins 515 | 516 | n := 1 + int(u16(data[endPtsOfContours:], numberOfContours*2-2)) 517 | 518 | m := n + 2*numberOfContours 519 | vertices = make([]Vertex, m) 520 | 521 | nextMove := 0 522 | flagcount := 0 523 | 524 | // in first pass, we load uninterpreted data into the allocated array 525 | // above, shifted to the end of the array so we won't overwrite it when 526 | // we create our final data starting from the front 527 | 528 | off := m - n // starting offset for uninterpreted data, regardless of how m ends up being calculated 529 | 530 | // first load flags 531 | 532 | for i := 0; i < n; i++ { 533 | if flagcount == 0 { 534 | flags = uint8(data[points]) 535 | points++ 536 | if flags&8 != 0 { 537 | flagcount = int(data[points]) 538 | points++ 539 | } 540 | } else { 541 | flagcount-- 542 | } 543 | vertices[off+i].Type = flags 544 | } 545 | 546 | // now load x coordinates 547 | x := 0 548 | for i := 0; i < n; i++ { 549 | flags = vertices[off+i].Type 550 | if flags&2 != 0 { 551 | dx := int(data[points]) 552 | points++ 553 | // ??? 554 | if flags&16 != 0 { 555 | x += dx 556 | } else { 557 | x -= dx 558 | } 559 | } else { 560 | if flags&16 == 0 { 561 | x = x + int(int16(data[points])*256+int16(data[points+1])) 562 | points += 2 563 | } 564 | } 565 | vertices[off+i].X = x 566 | } 567 | 568 | // now load y coordinates 569 | y := 0 570 | for i := 0; i < n; i++ { 571 | flags = vertices[off+i].Type 572 | if flags&4 != 0 { 573 | dy := int(data[points]) 574 | points++ 575 | // ??? 576 | if flags&32 != 0 { 577 | y += dy 578 | } else { 579 | y -= dy 580 | } 581 | } else { 582 | if flags&32 == 0 { 583 | y = y + int(int16(data[points])*256+int16(data[points+1])) 584 | points += 2 585 | } 586 | } 587 | vertices[off+i].Y = y 588 | } 589 | 590 | // now convert them to our format 591 | numVertices = 0 592 | var sx, sy, cx, cy, scx, scy int 593 | var wasOff, startOff bool 594 | var j int 595 | for i := 0; i < n; i++ { 596 | flags = vertices[off+i].Type 597 | x = vertices[off+i].X 598 | y = vertices[off+i].Y 599 | 600 | if nextMove == i { 601 | if i != 0 { 602 | numVertices = closeShape(vertices, numVertices, wasOff, startOff, sx, sy, scx, scy, cx, cy) 603 | } 604 | 605 | // now start the new one 606 | startOff = flags&1 == 0 607 | if startOff { 608 | // if we start off with an off-curve point, then when we need to find a point on the curve 609 | // where we can start, and we need to save some state for when we wrap around. 610 | scx = x 611 | scy = y 612 | if vertices[off+i+1].Type&1 == 0 { 613 | // next point is also a curve point, so interpolate an on-point curve 614 | sx = (x + vertices[off+i+1].X) >> 1 615 | sy = (y + vertices[off+i+1].Y) >> 1 616 | } else { 617 | // otherwise just use the next point as our start point 618 | sx = vertices[off+i+1].X 619 | sy = vertices[off+i+1].Y 620 | i++ 621 | } 622 | } else { 623 | sx = x 624 | sy = y 625 | } 626 | vertices[numVertices] = Vertex{Type: vmove, X: sx, Y: sy, CX: 0, CY: 0} 627 | numVertices++ 628 | wasOff = false 629 | nextMove = 1 + int(u16(data[endPtsOfContours:], j*2)) 630 | j++ 631 | } else { 632 | if flags&1 == 0 { // if it's a curve 633 | if wasOff { // two off-curve control points in a row means interpolate an on-curve midpoint 634 | vertices[numVertices] = Vertex{Type: vcurve, X: (cx + x) >> 1, Y: (cy + y) >> 1, CX: cx, CY: cy} 635 | numVertices++ 636 | } 637 | cx = x 638 | cy = y 639 | wasOff = true 640 | } else { 641 | if wasOff { 642 | vertices[numVertices] = Vertex{Type: vcurve, X: x, Y: y, CX: cx, CY: cy} 643 | numVertices++ 644 | } else { 645 | vertices[numVertices] = Vertex{Type: vline, X: x, Y: y, CX: 0, CY: 0} 646 | numVertices++ 647 | } 648 | wasOff = false 649 | } 650 | } 651 | } 652 | numVertices = closeShape(vertices, numVertices, wasOff, startOff, sx, sy, scx, scy, cx, cy) 653 | } else if numberOfContours == -1 { 654 | // Compound shapes. 655 | more := true 656 | comp := g + 10 657 | numVertices = 0 658 | vertices = nil 659 | for more { 660 | var mtx = [6]float64{1, 0, 0, 1, 0, 0} 661 | 662 | flags := int(u16(data, comp)) 663 | comp += 2 664 | gidx := int(u16(data, comp)) 665 | comp += 2 666 | 667 | if flags&2 != 0 { // XY values 668 | if flags&1 != 0 { // shorts 669 | mtx[4] = float64(u16(data, comp)) 670 | comp += 2 671 | mtx[5] = float64(u16(data, comp)) 672 | comp += 2 673 | } else { 674 | mtx[4] = float64(data[comp]) 675 | comp++ 676 | mtx[5] = float64(data[comp]) 677 | comp++ 678 | } 679 | } else { 680 | // @TODO handle matching point 681 | panic("Handle matching point") 682 | } 683 | if flags&(1<<3) != 0 { // WE_HAVE_A_SCALE 684 | mtx[3] = float64(u16(data, comp)) / 16384. 685 | comp += 2 686 | mtx[0] = mtx[3] 687 | mtx[1] = 0 688 | mtx[2] = 0 689 | } else if flags&(1<<6) != 0 { // WE_HAVE_AN_X_AND_YSCALE 690 | mtx[0] = float64(u16(data, comp)) / 16384. 691 | comp += 2 692 | mtx[1] = 0 693 | mtx[2] = 0 694 | mtx[3] = float64(u16(data, comp)) / 16384. 695 | comp += 2 696 | } else if flags&(1<<7) != 0 { // WE_HAVE_A_TWO_BY_TWO 697 | mtx[0] = float64(u16(data, comp)) / 16384. 698 | comp += 2 699 | mtx[1] = float64(u16(data, comp)) / 16384. 700 | comp += 2 701 | mtx[2] = float64(u16(data, comp)) / 16384. 702 | comp += 2 703 | mtx[3] = float64(u16(data, comp)) / 16384. 704 | comp += 2 705 | } 706 | 707 | // Find transformation scales. 708 | m := math.Sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]) 709 | n := math.Sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]) 710 | 711 | // Get indexed glyph. 712 | compVerts := font.GetGlyphShape(gidx) 713 | compNumVerts := len(compVerts) 714 | if compNumVerts > 0 { 715 | // Transform vertices. 716 | for i := 0; i < compNumVerts; i++ { 717 | v := i 718 | x := compVerts[v].X 719 | y := compVerts[v].Y 720 | compVerts[v].X = int(m * (mtx[0]*float64(x) + mtx[2]*float64(y) + mtx[4])) 721 | compVerts[v].Y = int(n * (mtx[1]*float64(x) + mtx[3]*float64(y) + mtx[5])) 722 | x = compVerts[v].CX 723 | y = compVerts[v].CY 724 | compVerts[v].CX = int(m * (mtx[0]*float64(x) + mtx[2]*float64(y) + mtx[4])) 725 | compVerts[v].CY = int(n * (mtx[1]*float64(x) + mtx[3]*float64(y) + mtx[5])) 726 | } 727 | vertices = append(vertices, compVerts...) 728 | numVertices += compNumVerts 729 | } 730 | // More components? 731 | more = flags&(1<<5) != 0 732 | } 733 | } else if numberOfContours < 0 { 734 | // @TODO other compound variations? 735 | panic("Possibly other compound variations") 736 | } // numberOfContours == 0, do nothing 737 | return vertices[:numVertices] 738 | } 739 | 740 | func closeShape(vertices []Vertex, numVertices int, wasOff, startOff bool, sx, sy, scx, scy, cx, cy int) int { 741 | if startOff { 742 | if wasOff { 743 | vertices[numVertices] = Vertex{Type: vcurve, X: (cx + scx) >> 1, Y: (cy + scy) >> 1, CX: cx, CY: cy} 744 | numVertices++ 745 | } 746 | vertices[numVertices] = Vertex{Type: vcurve, X: sx, Y: sy, CX: scx, CY: scy} 747 | numVertices++ 748 | } else { 749 | if wasOff { 750 | vertices[numVertices] = Vertex{Type: vcurve, X: sx, Y: sy, CX: cx, CY: cy} 751 | numVertices++ 752 | } else { 753 | vertices[numVertices] = Vertex{Type: vline, X: sx, Y: sy, CX: 0, CY: 0} 754 | numVertices++ 755 | } 756 | } 757 | return numVertices 758 | } 759 | 760 | func (font *FontInfo) GetGlyphOffset(glyphIndex int) int { 761 | if glyphIndex >= font.numGlyphs { 762 | // Glyph index out of range 763 | return -1 764 | } 765 | if font.indexToLocFormat >= 2 { 766 | // Unknown index-glyph map format 767 | return -1 768 | } 769 | 770 | var g1, g2 int 771 | 772 | if font.indexToLocFormat == 0 { 773 | g1 = font.glyf + int(u16(font.data, font.loca+glyphIndex*2))*2 774 | g2 = font.glyf + int(u16(font.data, font.loca+glyphIndex*2+2))*2 775 | } else { 776 | g1 = font.glyf + int(u32(font.data, font.loca+glyphIndex*4)) 777 | g2 = font.glyf + int(u32(font.data, font.loca+glyphIndex*4+4)) 778 | } 779 | 780 | if g1 == g2 { 781 | // length is 0 782 | return -1 783 | } 784 | return g1 785 | } 786 | 787 | func (font *FontInfo) MakeCodepointBitmap(output []byte, outW, outH, outStride int, scaleX, scaleY float64, codepoint int) []byte { 788 | return font.MakeCodepointBitmapSubpixel(output, outW, outH, outStride, scaleX, scaleY, 0, 0, codepoint) 789 | } 790 | 791 | func (font *FontInfo) MakeCodepointBitmapSubpixel(output []byte, outW, outH, outStride int, scaleX, scaleY, shiftX, shiftY float64, codepoint int) []byte { 792 | return font.MakeGlyphBitmapSubpixel(output, outW, outH, outStride, scaleX, scaleY, shiftX, shiftY, font.FindGlyphIndex(codepoint)) 793 | } 794 | 795 | func (font *FontInfo) MakeGlyphBitmap(output []byte, outW, outH, outStride int, scaleX, scaleY float64, glyph int) []byte { 796 | return font.MakeGlyphBitmapSubpixel(output, outW, outH, outStride, scaleX, scaleY, 0, 0, glyph) 797 | } 798 | 799 | func (font *FontInfo) MakeGlyphBitmapSubpixel(output []byte, outW, outH, outStride int, scaleX, scaleY, shiftX, shiftY float64, glyph int) []byte { 800 | var gbm Bitmap 801 | vertices := font.GetGlyphShape(glyph) 802 | 803 | ix0, iy0, _, _ := font.GetGlyphBitmapBoxSubpixel(glyph, scaleX, scaleY, shiftX, shiftY) 804 | gbm.W = outW 805 | gbm.H = outH 806 | gbm.Stride = outStride 807 | 808 | if gbm.W > 0 && gbm.H > 0 { 809 | gbm.Pixels = output 810 | Rasterize(&gbm, 0.35, vertices, scaleX, scaleY, shiftX, shiftY, ix0, iy0, true) 811 | } 812 | return gbm.Pixels 813 | } 814 | 815 | func (font *FontInfo) GetCodepointKernAdvance(ch1, ch2 int) int { 816 | if font.kern == 0 { 817 | return 0 818 | } 819 | return font.GetGlyphKernAdvance(font.FindGlyphIndex(ch1), font.FindGlyphIndex(ch2)) 820 | } 821 | 822 | func (font *FontInfo) GetGlyphKernAdvance(glyph1, glyph2 int) int { 823 | data := font.kern 824 | 825 | // we only look at the first table. it must be 'horizontal' and format 0. 826 | if font.kern == 0 { 827 | return 0 828 | } 829 | if u16(font.data, data+2) < 1 { // number of tables, need at least 1 830 | return 0 831 | } 832 | if u16(font.data, data+8) != 1 { // horizontal flag must be set in format 833 | return 0 834 | } 835 | 836 | l := 0 837 | r := int(u16(font.data, data+10)) - 1 838 | needle := uint(glyph1)<<16 | uint(glyph2) 839 | for l <= r { 840 | m := (l + r) >> 1 841 | straw := uint(u32(font.data, data+18+(m*6))) // note: unaligned read 842 | if needle < straw { 843 | r = m - 1 844 | } else if needle > straw { 845 | l = m + 1 846 | } else { 847 | return int(int16(u16(font.data, data+22+(m*6)))) 848 | } 849 | } 850 | return 0 851 | } 852 | 853 | func (font *FontInfo) FindGlyphIndex(unicodeCodepoint int) int { 854 | data := font.data 855 | indexMap := font.indexMap 856 | 857 | format := int(u16(data, indexMap)) 858 | if format == 0 { // apple byte encoding 859 | numBytes := int(u16(data, indexMap+2)) 860 | if unicodeCodepoint < numBytes-6 { 861 | return int(data[indexMap+6+unicodeCodepoint]) 862 | } 863 | return 0 864 | } else if format == 6 { 865 | first := int(u16(data, indexMap+6)) 866 | count := int(u16(data, indexMap+8)) 867 | if unicodeCodepoint >= first && unicodeCodepoint < first+count { 868 | return int(u16(data, indexMap+10+(unicodeCodepoint-first)*2)) 869 | } 870 | return 0 871 | } else if format == 2 { 872 | panic("TODO: high-byte mapping for japanese/chinese/korean") 873 | return 0 874 | } else if format == 4 { 875 | segcount := int(u16(data, indexMap+6) >> 1) 876 | searchRange := int(u16(data, indexMap+8) >> 1) 877 | entrySelector := int(u16(data, indexMap+10)) 878 | rangeShift := int(u16(data, indexMap+12) >> 1) 879 | 880 | endCount := indexMap + 14 881 | search := endCount 882 | 883 | if unicodeCodepoint > 0xffff { 884 | return 0 885 | } 886 | 887 | if unicodeCodepoint >= int(u16(data, search+rangeShift*2)) { 888 | search += rangeShift * 2 889 | } 890 | 891 | search -= 2 892 | for entrySelector > 0 { 893 | searchRange >>= 1 894 | // start := int(u16(data, search+2+segcount*2+2)) 895 | // end := int(u16(data, search+2)) 896 | // start := int(u16(data, search+searchRange*2+segcount*2+2)) 897 | end := int(u16(data, search+searchRange*2)) 898 | if unicodeCodepoint > end { 899 | search += searchRange * 2 900 | } 901 | entrySelector-- 902 | } 903 | search += 2 904 | 905 | item := ((search - endCount) >> 1) 906 | 907 | if !(unicodeCodepoint <= int(u16(data, endCount+2*item))) { 908 | panic("unicode codepoint doesn't match") 909 | } 910 | start := int(u16(data, indexMap+14+segcount*2+2+2*item)) 911 | // end := int(u16(data, indexMap+14+2+2*item)) 912 | if unicodeCodepoint < start { 913 | return 0 914 | } 915 | 916 | offset := int(u16(data, indexMap+14+segcount*6+2+2*item)) 917 | if offset == 0 { 918 | return unicodeCodepoint + int(int16(u16(data, indexMap+14+segcount*4+2+2*item))) 919 | } 920 | return int(u16(data, offset+(unicodeCodepoint-start)*2+indexMap+14+segcount*6+2+2*item)) 921 | } else if format == 12 || format == 13 { 922 | ngroups := int(u32(data, indexMap+12)) 923 | low := 0 924 | high := ngroups 925 | for low < high { 926 | mid := low + ((high - low) >> 1) 927 | startChar := int(u32(data, indexMap+16+mid*12)) 928 | endChar := int(u32(data, indexMap+16+mid*12+4)) 929 | if unicodeCodepoint < startChar { 930 | high = mid 931 | } else if unicodeCodepoint > endChar { 932 | low = mid + 1 933 | } else { 934 | startGlyph := int(u32(data, indexMap+16+mid*12+8)) 935 | if format == 12 { 936 | return startGlyph + unicodeCodepoint - startChar 937 | } else { // format == 13 938 | return startGlyph 939 | } 940 | } 941 | } 942 | return 0 // not found 943 | } 944 | panic("Glyph not found!") 945 | return 0 946 | } 947 | 948 | func findTable(data []byte, offset int, tag string) int { 949 | numTables := int(u16(data, offset+4)) 950 | tableDir := offset + 12 951 | for i := 0; i < numTables; i++ { 952 | loc := tableDir + 16*i 953 | if string(data[loc:loc+4]) == tag { 954 | return int(u32(data, loc+8)) 955 | } 956 | } 957 | return 0 958 | } 959 | 960 | func u32(b []byte, i int) uint32 { 961 | return uint32(b[i])<<24 | uint32(b[i+1])<<16 | uint32(b[i+2])<<8 | uint32(b[i+3]) 962 | } 963 | 964 | // u16 returns the big-endian uint16 at b[i:]. 965 | func u16(b []byte, i int) uint16 { 966 | return uint16(b[i])<<8 | uint16(b[i+1]) 967 | } 968 | 969 | func isFont(data []byte) bool { 970 | if tag4(data, '1', 0, 0, 0) { 971 | return true 972 | } 973 | if string(data[0:4]) == "typ1" { 974 | return true 975 | } 976 | if string(data[0:4]) == "OTTO" { 977 | return true 978 | } 979 | if tag4(data, 0, 1, 0, 0) { 980 | return true 981 | } 982 | return false 983 | } 984 | 985 | func tag4(data []byte, c0, c1, c2, c3 byte) bool { 986 | return data[0] == c0 && data[1] == c1 && data[2] == c2 && data[3] == c3 987 | } 988 | -------------------------------------------------------------------------------- /gl_backend.go: -------------------------------------------------------------------------------- 1 | package nanovgo 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/goxjs/gl" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | glnvgLocVIEWSIZE = iota 12 | glnvgLocTEX 13 | glnvgLocFRAG 14 | glnvgMaxLOCS 15 | ) 16 | 17 | // NewContext makes new NanoVGo context that is entry point of this API 18 | func NewContext(flags CreateFlags) (*Context, error) { 19 | params := &glParams{ 20 | isEdgeAntiAlias: (flags & AntiAlias) != 0, 21 | context: &glContext{ 22 | flags: flags, 23 | }, 24 | } 25 | return createInternal(params) 26 | } 27 | 28 | type glShader struct { 29 | program gl.Program 30 | fragment gl.Shader 31 | vertex gl.Shader 32 | locations [glnvgMaxLOCS]gl.Uniform 33 | vertexAttrib gl.Attrib 34 | tcoordAttrib gl.Attrib 35 | } 36 | 37 | func (s *glShader) createShader(name, header, opts, vShader, fShader string) error { 38 | program := gl.CreateProgram() 39 | 40 | vertexShader := gl.CreateShader(gl.VERTEX_SHADER) 41 | gl.ShaderSource(vertexShader, strings.Join([]string{header, opts, vShader}, "\n")) 42 | gl.CompileShader(vertexShader) 43 | status := gl.Enum(gl.GetShaderi(vertexShader, gl.COMPILE_STATUS)) 44 | if status != gl.TRUE { 45 | return dumpShaderError(vertexShader, name, "vert") 46 | } 47 | 48 | fragmentShader := gl.CreateShader(gl.FRAGMENT_SHADER) 49 | gl.ShaderSource(fragmentShader, strings.Join([]string{header, opts, fShader}, "\n")) 50 | gl.CompileShader(fragmentShader) 51 | status = gl.Enum(gl.GetShaderi(fragmentShader, gl.COMPILE_STATUS)) 52 | if status != gl.TRUE { 53 | return dumpShaderError(fragmentShader, name, "vert") 54 | } 55 | 56 | gl.AttachShader(program, vertexShader) 57 | gl.AttachShader(program, fragmentShader) 58 | 59 | gl.LinkProgram(program) 60 | status = gl.Enum(gl.GetProgrami(program, gl.LINK_STATUS)) 61 | if status != gl.TRUE { 62 | return dumpProgramError(program, name) 63 | } 64 | 65 | s.vertexAttrib = gl.GetAttribLocation(program, "vertex") 66 | s.tcoordAttrib = gl.GetAttribLocation(program, "tcoord") 67 | 68 | s.program = program 69 | s.vertex = vertexShader 70 | s.fragment = fragmentShader 71 | 72 | return nil 73 | } 74 | 75 | func (s *glShader) deleteShader() { 76 | if s.program.Valid() { 77 | gl.DeleteProgram(s.program) 78 | } 79 | if s.vertex.Valid() { 80 | gl.DeleteShader(s.vertex) 81 | } 82 | if s.fragment.Valid() { 83 | gl.DeleteShader(s.fragment) 84 | } 85 | } 86 | 87 | func (s *glShader) getUniforms() { 88 | s.locations[glnvgLocVIEWSIZE] = gl.GetUniformLocation(s.program, "viewSize") 89 | s.locations[glnvgLocTEX] = gl.GetUniformLocation(s.program, "tex") 90 | s.locations[glnvgLocFRAG] = gl.GetUniformLocation(s.program, "frag") 91 | } 92 | 93 | const ( 94 | glnvgGLUniformArraySize = 11 95 | ) 96 | 97 | const ( 98 | // ImageNoDelete don't delete from memory when removing image 99 | ImageNoDelete ImageFlags = 1 << 16 100 | ) 101 | 102 | type glContext struct { 103 | shader glShader 104 | view [2]float32 105 | textures []*glTexture 106 | textureID int 107 | vertexBuffer gl.Buffer 108 | flags CreateFlags 109 | calls []glCall 110 | paths []glPath 111 | vertexes []float32 112 | uniforms []glFragUniforms 113 | 114 | stencilMask uint32 115 | stencilFunc gl.Enum 116 | stencilFuncRef int 117 | stencilFuncMask uint32 118 | } 119 | 120 | func (c *glContext) findTexture(id int) *glTexture { 121 | for _, texture := range c.textures { 122 | if texture.id == id { 123 | return texture 124 | } 125 | } 126 | return nil 127 | } 128 | 129 | func (c *glContext) deleteTexture(id int) error { 130 | tex := c.findTexture(id) 131 | if tex != nil && (tex.flags&ImageNoDelete) == 0 { 132 | gl.DeleteTexture(tex.tex) 133 | tex.id = 0 134 | return nil 135 | } 136 | return errors.New("can't find texture") 137 | } 138 | 139 | func (c *glContext) bindTexture(tex *gl.Texture) { 140 | if tex == nil { 141 | gl.BindTexture(gl.TEXTURE_2D, gl.Texture{}) 142 | } else { 143 | gl.BindTexture(gl.TEXTURE_2D, *tex) 144 | } 145 | } 146 | 147 | func (c *glContext) setStencilMask(mask uint32) { 148 | if c.stencilMask != mask { 149 | c.stencilMask = mask 150 | gl.StencilMask(mask) 151 | } 152 | } 153 | 154 | func (c *glContext) setStencilFunc(fun gl.Enum, ref int, mask uint32) { 155 | if c.stencilFunc != fun || c.stencilFuncRef != ref || c.stencilFuncMask != mask { 156 | c.stencilFunc = fun 157 | c.stencilFuncRef = ref 158 | c.stencilMask = mask 159 | gl.StencilFunc(fun, ref, mask) 160 | } 161 | } 162 | 163 | func (c *glContext) checkError(str string) { 164 | if c.flags&Debug == 0 { 165 | return 166 | } 167 | err := gl.GetError() 168 | if err != gl.NO_ERROR { 169 | dumpLog("Error %08x after %s\n", err, str) 170 | } 171 | } 172 | 173 | func (c *glContext) allocVertexMemory(size int) int { 174 | offset := len(c.vertexes) 175 | c.vertexes = append(c.vertexes, make([]float32, 4*size)...) 176 | return offset 177 | } 178 | 179 | func (c *glContext) allocFragUniforms(n int) ([]glFragUniforms, int) { 180 | ret := len(c.uniforms) 181 | c.uniforms = append(c.uniforms, make([]glFragUniforms, n)...) 182 | return c.uniforms[ret:], ret 183 | } 184 | 185 | func (c *glContext) allocPath(n int) ([]glPath, int) { 186 | ret := len(c.paths) 187 | c.paths = append(c.paths, make([]glPath, n)...) 188 | return c.paths[ret:], ret 189 | } 190 | 191 | func (c *glContext) allocTexture() *glTexture { 192 | var tex *glTexture 193 | for _, texture := range c.textures { 194 | if texture.id == 0 { 195 | tex = texture 196 | break 197 | } 198 | } 199 | if tex == nil { 200 | tex = &glTexture{} 201 | c.textures = append(c.textures, tex) 202 | } 203 | c.textureID++ 204 | tex.id = c.textureID 205 | return tex 206 | } 207 | 208 | func (c *glContext) convertPaint(frag *glFragUniforms, paint *Paint, scissor *nvgScissor, width, fringe, strokeThr float32) error { 209 | frag.setInnerColor(paint.innerColor.PreMultiply()) 210 | frag.setOuterColor(paint.outerColor.PreMultiply()) 211 | 212 | if scissor.extent[0] < -0.5 || scissor.extent[1] < -0.5 { 213 | frag.clearScissorMat() 214 | frag.setScissorExt(1.0, 1.0) 215 | frag.setScissorScale(1.0, 1.0) 216 | } else { 217 | xform := &scissor.xform 218 | frag.setScissorMat(xform.Inverse().ToMat3x4()) 219 | frag.setScissorExt(scissor.extent[0], scissor.extent[1]) 220 | scaleX := sqrtF(xform[0]*xform[0]+xform[2]*xform[2]) / fringe 221 | scaleY := sqrtF(xform[1]*xform[1]+xform[3]*xform[3]) / fringe 222 | frag.setScissorScale(scaleX, scaleY) 223 | } 224 | frag.setExtent(paint.extent) 225 | frag.setStrokeMult((width*0.5 + fringe*0.5) / fringe) 226 | frag.setStrokeThr(strokeThr) 227 | 228 | if paint.image != 0 { 229 | tex := c.findTexture(paint.image) 230 | if tex == nil { 231 | return errors.New("invalid texture in GLParams.convertPaint") 232 | } 233 | if tex.flags&ImageFlippy != 0 { 234 | frag.setPaintMat(ScaleMatrix(1.0, -1.0).Multiply(paint.xform).Inverse().ToMat3x4()) 235 | } else { 236 | frag.setPaintMat(paint.xform.Inverse().ToMat3x4()) 237 | } 238 | frag.setType(nsvgShaderFILLIMG) 239 | 240 | if tex.texType == nvgTextureRGBA { 241 | if tex.flags&ImagePreMultiplied != 0 { 242 | frag.setTexType(0) 243 | } else { 244 | frag.setTexType(1) 245 | } 246 | } else { 247 | frag.setTexType(2) 248 | } 249 | } else { 250 | frag.setType(nsvgShaderFILLGRAD) 251 | frag.setRadius(paint.radius) 252 | frag.setFeather(paint.feather) 253 | frag.setPaintMat(paint.xform.Inverse().ToMat3x4()) 254 | } 255 | 256 | return nil 257 | } 258 | 259 | func (c *glContext) setUniforms(uniformOffset, image int) { 260 | frag := c.uniforms[uniformOffset] 261 | gl.Uniform4fv(c.shader.locations[glnvgLocFRAG], frag[:]) 262 | 263 | if image != 0 { 264 | c.bindTexture(&c.findTexture(image).tex) 265 | checkError(c, "tex paint tex") 266 | } else { 267 | c.bindTexture(&gl.Texture{}) 268 | } 269 | } 270 | 271 | func (c *glContext) fill(call *glCall) { 272 | pathSentinel := call.pathOffset + call.pathCount 273 | 274 | // Draw shapes 275 | gl.Enable(gl.STENCIL_TEST) 276 | c.setStencilMask(0xff) 277 | c.setStencilFunc(gl.ALWAYS, 0x00, 0xff) 278 | gl.ColorMask(false, false, false, false) 279 | 280 | // set bindpoint for solid loc 281 | c.setUniforms(call.uniformOffset, 0) 282 | checkError(c, "fill simple") 283 | 284 | gl.StencilOpSeparate(gl.FRONT, gl.KEEP, gl.KEEP, gl.INCR_WRAP) 285 | gl.StencilOpSeparate(gl.BACK, gl.KEEP, gl.KEEP, gl.DECR_WRAP) 286 | 287 | gl.Disable(gl.CULL_FACE) 288 | for i := call.pathOffset; i < pathSentinel; i++ { 289 | path := &c.paths[i] 290 | gl.DrawArrays(gl.TRIANGLE_FAN, path.fillOffset, path.fillCount) 291 | } 292 | gl.Enable(gl.CULL_FACE) 293 | 294 | // Draw anti-aliased pixels 295 | gl.ColorMask(true, true, true, true) 296 | c.setUniforms(call.uniformOffset+1, call.image) 297 | 298 | if c.flags&AntiAlias != 0 { 299 | c.setStencilFunc(gl.EQUAL, 0x00, 0xff) 300 | gl.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP) 301 | // Draw fringes 302 | for i := call.pathOffset; i < pathSentinel; i++ { 303 | path := &c.paths[i] 304 | gl.DrawArrays(gl.TRIANGLE_STRIP, path.strokeOffset, path.strokeCount) 305 | } 306 | } 307 | 308 | // Draw fill 309 | c.setStencilFunc(gl.NOTEQUAL, 0x00, 0xff) 310 | gl.StencilOp(gl.ZERO, gl.ZERO, gl.ZERO) 311 | gl.DrawArrays(gl.TRIANGLES, call.triangleOffset, call.triangleCount) 312 | 313 | gl.Disable(gl.STENCIL_TEST) 314 | } 315 | 316 | func (c *glContext) convexFill(call *glCall) { 317 | paths := c.paths[call.pathOffset : call.pathOffset+call.pathCount] 318 | 319 | c.setUniforms(call.uniformOffset, call.image) 320 | checkError(c, "convex fill") 321 | 322 | for i := range paths { 323 | path := &paths[i] 324 | gl.DrawArrays(gl.TRIANGLE_FAN, path.fillOffset, path.fillCount) 325 | } 326 | 327 | if c.flags&AntiAlias != 0 { 328 | for i := range paths { 329 | path := &paths[i] 330 | gl.DrawArrays(gl.TRIANGLE_STRIP, path.strokeOffset, path.strokeCount) 331 | } 332 | } 333 | } 334 | 335 | func (c *glContext) stroke(call *glCall) { 336 | paths := c.paths[call.pathOffset : call.pathOffset+call.pathCount] 337 | 338 | if c.flags&StencilStrokes != 0 { 339 | gl.Enable(gl.STENCIL_TEST) 340 | c.setStencilMask(0xff) 341 | 342 | // Fill the stroke base without overlap 343 | c.setStencilFunc(gl.EQUAL, 0x00, 0xff) 344 | gl.StencilOp(gl.KEEP, gl.KEEP, gl.INCR) 345 | c.setUniforms(call.uniformOffset+1, call.image) 346 | checkError(c, "stroke fill 0") 347 | for i := range paths { 348 | path := &paths[i] 349 | gl.DrawArrays(gl.TRIANGLE_STRIP, path.strokeOffset, path.strokeCount) 350 | } 351 | 352 | // Draw anti-aliased pixels. 353 | c.setUniforms(call.uniformOffset, call.image) 354 | c.setStencilFunc(gl.EQUAL, 0x00, 0xff) 355 | gl.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP) 356 | for i := range paths { 357 | path := &paths[i] 358 | gl.DrawArrays(gl.TRIANGLE_STRIP, path.strokeOffset, path.strokeCount) 359 | } 360 | 361 | // Clear stencil buffer. 362 | gl.ColorMask(false, false, false, false) 363 | c.setStencilFunc(gl.ALWAYS, 0x00, 0xff) 364 | gl.StencilOp(gl.ZERO, gl.ZERO, gl.ZERO) 365 | checkError(c, "stroke fill 1") 366 | for i := range paths { 367 | path := &paths[i] 368 | gl.DrawArrays(gl.TRIANGLE_STRIP, path.strokeOffset, path.strokeCount) 369 | } 370 | gl.ColorMask(true, true, true, true) 371 | gl.Disable(gl.STENCIL_TEST) 372 | } else { 373 | c.setUniforms(call.uniformOffset, call.image) 374 | checkError(c, "stroke fill") 375 | for i := range paths { 376 | path := &paths[i] 377 | gl.DrawArrays(gl.TRIANGLE_STRIP, path.strokeOffset, path.strokeCount) 378 | } 379 | } 380 | } 381 | 382 | func (c *glContext) triangles(call *glCall) { 383 | c.setUniforms(call.uniformOffset, call.image) 384 | checkError(c, "triangles fill") 385 | gl.DrawArrays(gl.TRIANGLES, call.triangleOffset, call.triangleCount) 386 | } 387 | 388 | func (c *glContext) triangleStrip(call *glCall) { 389 | c.setUniforms(call.uniformOffset, call.image) 390 | checkError(c, "triangle strip fill") 391 | gl.DrawArrays(gl.TRIANGLE_STRIP, call.triangleOffset, call.triangleCount) 392 | } 393 | 394 | type glParams struct { 395 | isEdgeAntiAlias bool 396 | context *glContext 397 | } 398 | 399 | func (p *glParams) edgeAntiAlias() bool { 400 | return p.isEdgeAntiAlias 401 | } 402 | 403 | func (p *glParams) renderCreate() error { 404 | context := p.context 405 | //align := 4 406 | 407 | checkError(context, "init") 408 | 409 | if p.edgeAntiAlias() { 410 | err := context.shader.createShader("shader", shaderHeader, "#define EDGE_AA 1", fillVertexShader, fillFragmentShader) 411 | if err != nil { 412 | return err 413 | } 414 | } else { 415 | err := context.shader.createShader("shader", shaderHeader, "", fillVertexShader, fillFragmentShader) 416 | if err != nil { 417 | return err 418 | } 419 | } 420 | checkError(context, "init") 421 | context.shader.getUniforms() 422 | 423 | context.vertexBuffer = gl.CreateBuffer() 424 | context.vertexBuffer = gl.CreateBuffer() 425 | 426 | checkError(context, "create done") 427 | gl.Finish() 428 | return nil 429 | } 430 | 431 | func (p *glParams) renderCreateTexture(texType nvgTextureType, w, h int, flags ImageFlags, data []byte) int { 432 | if nearestPow2(w) != w || nearestPow2(h) != h { 433 | if (flags&ImageRepeatX) != 0 || (flags&ImageRepeatY) != 0 { 434 | dumpLog("Repeat X/Y is not supported for non power-of-two textures (%d x %d)\n", w, h) 435 | flags &= ^(ImageRepeatY | ImageRepeatX) 436 | } 437 | if (flags & ImageGenerateMipmaps) != 0 { 438 | dumpLog("Mip-maps is not support for non power-of-two textures (%d x %d)\n", w, h) 439 | flags &= ^ImageGenerateMipmaps 440 | } 441 | } 442 | tex := p.context.allocTexture() 443 | tex.tex = gl.CreateTexture() 444 | tex.width = w 445 | tex.height = h 446 | tex.texType = texType 447 | tex.flags = flags 448 | 449 | p.context.bindTexture(&tex.tex) 450 | gl.PixelStorei(gl.UNPACK_ALIGNMENT, 1) 451 | 452 | if texType == nvgTextureRGBA { 453 | data = prepareTextureBuffer(data, w, h, 4) 454 | gl.TexImage2D(gl.TEXTURE_2D, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, data) 455 | } else { 456 | data = prepareTextureBuffer(data, w, h, 1) 457 | gl.TexImage2D(gl.TEXTURE_2D, 0, w, h, gl.LUMINANCE, gl.UNSIGNED_BYTE, data) 458 | } 459 | 460 | if (flags & ImageGenerateMipmaps) != 0 { 461 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR) 462 | } else { 463 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) 464 | } 465 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) 466 | 467 | if (flags & ImageRepeatX) != 0 { 468 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT) 469 | } else { 470 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) 471 | } 472 | 473 | if (flags & ImageRepeatY) != 0 { 474 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT) 475 | } else { 476 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) 477 | } 478 | 479 | gl.PixelStorei(gl.UNPACK_ALIGNMENT, 4) 480 | 481 | if (flags & ImageGenerateMipmaps) != 0 { 482 | gl.GenerateMipmap(gl.TEXTURE_2D) 483 | } 484 | 485 | p.context.checkError("create tex") 486 | p.context.bindTexture(&gl.Texture{}) 487 | 488 | return tex.id 489 | } 490 | 491 | func (p *glParams) renderDeleteTexture(id int) error { 492 | tex := p.context.findTexture(id) 493 | if tex.tex.Valid() && (tex.flags&ImageNoDelete) == 0 { 494 | gl.DeleteTexture(tex.tex) 495 | tex.id = 0 496 | tex.tex = gl.Texture{} 497 | return nil 498 | } 499 | return errors.New("invalid texture in GLParams.deleteTexture") 500 | } 501 | 502 | func (p *glParams) renderUpdateTexture(image, x, y, w, h int, data []byte) error { 503 | tex := p.context.findTexture(image) 504 | if tex == nil { 505 | return errors.New("invalid texture in GLParams.updateTexture") 506 | } 507 | p.context.bindTexture(&tex.tex) 508 | gl.PixelStorei(gl.UNPACK_ALIGNMENT, 1) 509 | 510 | if tex.texType == nvgTextureRGBA { 511 | data = data[y*tex.width*4:] 512 | } else { 513 | data = data[y*tex.width:] 514 | } 515 | x = 0 516 | w = tex.width 517 | 518 | if tex.texType == nvgTextureRGBA { 519 | gl.TexSubImage2D(gl.TEXTURE_2D, 0, x, y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, data) 520 | } else { 521 | gl.TexSubImage2D(gl.TEXTURE_2D, 0, x, y, w, h, gl.LUMINANCE, gl.UNSIGNED_BYTE, data) 522 | } 523 | 524 | gl.PixelStorei(gl.UNPACK_ALIGNMENT, 4) 525 | 526 | p.context.bindTexture(nil) 527 | 528 | return nil 529 | } 530 | 531 | func (p *glParams) renderGetTextureSize(image int) (int, int, error) { 532 | tex := p.context.findTexture(image) 533 | if tex == nil { 534 | return -1, -1, errors.New("invalid texture in GLParams.getTextureSize") 535 | } 536 | return tex.width, tex.height, nil 537 | } 538 | 539 | func (p *glParams) renderViewport(width, height int) { 540 | p.context.view[0] = float32(width) 541 | p.context.view[1] = float32(height) 542 | } 543 | 544 | func (p *glParams) renderCancel() { 545 | c := p.context 546 | c.vertexes = c.vertexes[:0] 547 | c.paths = c.paths[:0] 548 | c.calls = c.calls[:0] 549 | c.uniforms = c.uniforms[:0] 550 | } 551 | 552 | func (p *glParams) renderFlush() { 553 | c := p.context 554 | 555 | if len(c.calls) > 0 { 556 | gl.UseProgram(c.shader.program) 557 | 558 | gl.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) 559 | gl.Enable(gl.CULL_FACE) 560 | gl.CullFace(gl.BACK) 561 | gl.FrontFace(gl.CCW) 562 | gl.Enable(gl.BLEND) 563 | gl.Disable(gl.DEPTH_TEST) 564 | gl.Disable(gl.SCISSOR_TEST) 565 | gl.ColorMask(true, true, true, true) 566 | gl.StencilMask(0xffffffff) 567 | gl.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP) 568 | gl.StencilFunc(gl.ALWAYS, 0, 0xffffffff) 569 | gl.ActiveTexture(gl.TEXTURE0) 570 | gl.BindTexture(gl.TEXTURE_2D, gl.Texture{}) 571 | c.stencilMask = 0xffffffff 572 | c.stencilFunc = gl.ALWAYS 573 | c.stencilFuncRef = 0 574 | c.stencilFuncMask = 0xffffffff 575 | b := castFloat32ToByte(c.vertexes) 576 | //dumpLog("vertex:", c.vertexes) 577 | // Upload vertex data 578 | gl.BindBuffer(gl.ARRAY_BUFFER, c.vertexBuffer) 579 | gl.BufferData(gl.ARRAY_BUFFER, b, gl.STREAM_DRAW) 580 | gl.EnableVertexAttribArray(c.shader.vertexAttrib) 581 | gl.EnableVertexAttribArray(c.shader.tcoordAttrib) 582 | gl.VertexAttribPointer(c.shader.vertexAttrib, 2, gl.FLOAT, false, 4*4, 0) 583 | gl.VertexAttribPointer(c.shader.tcoordAttrib, 2, gl.FLOAT, false, 4*4, 8) 584 | 585 | // Set view and texture just once per frame. 586 | gl.Uniform1i(c.shader.locations[glnvgLocTEX], 0) 587 | gl.Uniform2fv(c.shader.locations[glnvgLocVIEWSIZE], c.view[:]) 588 | 589 | for i := range c.calls { 590 | call := &c.calls[i] 591 | switch call.callType { 592 | case glnvgFILL: 593 | c.fill(call) 594 | case glnvgCONVEXFILL: 595 | c.convexFill(call) 596 | case glnvgSTROKE: 597 | c.stroke(call) 598 | case glnvgTRIANGLES: 599 | c.triangles(call) 600 | case glnvgTRIANGLESTRIP: 601 | c.triangleStrip(call) 602 | } 603 | } 604 | gl.DisableVertexAttribArray(c.shader.vertexAttrib) 605 | gl.DisableVertexAttribArray(c.shader.tcoordAttrib) 606 | gl.Disable(gl.CULL_FACE) 607 | gl.BindBuffer(gl.ARRAY_BUFFER, gl.Buffer{}) 608 | gl.UseProgram(gl.Program{}) 609 | c.bindTexture(nil) 610 | } 611 | c.vertexes = c.vertexes[:0] 612 | c.paths = c.paths[:0] 613 | c.calls = c.calls[:0] 614 | c.uniforms = c.uniforms[:0] 615 | } 616 | 617 | func (p *glParams) renderFill(paint *Paint, scissor *nvgScissor, fringe float32, bounds [4]float32, paths []nvgPath) { 618 | c := p.context 619 | var glPaths []glPath 620 | c.calls = append(c.calls, glCall{ 621 | pathCount: len(paths), 622 | image: paint.image, 623 | }) 624 | call := &c.calls[len(c.calls)-1] 625 | glPaths, call.pathOffset = c.allocPath(call.pathCount) 626 | 627 | if len(paths) == 0 && paths[0].convex { 628 | call.callType = glnvgCONVEXFILL 629 | } else { 630 | call.callType = glnvgFILL 631 | } 632 | 633 | // Allocate vertices for all the paths 634 | vertexOffset := c.allocVertexMemory(maxVertexCount(paths) + 6) 635 | for i := range paths { 636 | glPath := &glPaths[i] 637 | path := &paths[i] 638 | 639 | fillCount := len(path.fills) 640 | if fillCount > 0 { 641 | glPath.fillOffset = vertexOffset / 4 642 | glPath.fillCount = fillCount 643 | for j := 0; j < fillCount; j++ { 644 | vertex := &path.fills[j] 645 | c.vertexes[vertexOffset] = vertex.x 646 | c.vertexes[vertexOffset+1] = vertex.y 647 | c.vertexes[vertexOffset+2] = vertex.u 648 | c.vertexes[vertexOffset+3] = vertex.v 649 | vertexOffset += 4 650 | } 651 | } else { 652 | glPath.fillOffset = 0 653 | glPath.fillCount = 0 654 | } 655 | 656 | strokeCount := len(path.strokes) 657 | if strokeCount > 0 { 658 | glPath.strokeOffset = vertexOffset / 4 659 | glPath.strokeCount = strokeCount 660 | for j := 0; j < strokeCount; j++ { 661 | vertex := &path.strokes[j] 662 | c.vertexes[vertexOffset] = vertex.x 663 | c.vertexes[vertexOffset+1] = vertex.y 664 | c.vertexes[vertexOffset+2] = vertex.u 665 | c.vertexes[vertexOffset+3] = vertex.v 666 | vertexOffset += 4 667 | } 668 | } else { 669 | glPath.strokeOffset = 0 670 | glPath.strokeCount = 0 671 | } 672 | } 673 | 674 | // Quad 675 | call.triangleOffset = vertexOffset / 4 676 | call.triangleCount = 6 677 | 678 | c.vertexes[vertexOffset] = bounds[0] 679 | c.vertexes[vertexOffset+1] = bounds[3] 680 | c.vertexes[vertexOffset+2] = 0.5 681 | c.vertexes[vertexOffset+3] = 1.0 682 | vertexOffset += 4 683 | 684 | c.vertexes[vertexOffset] = bounds[2] 685 | c.vertexes[vertexOffset+1] = bounds[3] 686 | c.vertexes[vertexOffset+2] = 0.5 687 | c.vertexes[vertexOffset+3] = 1.0 688 | vertexOffset += 4 689 | 690 | c.vertexes[vertexOffset] = bounds[2] 691 | c.vertexes[vertexOffset+1] = bounds[1] 692 | c.vertexes[vertexOffset+2] = 0.5 693 | c.vertexes[vertexOffset+3] = 1.0 694 | vertexOffset += 4 695 | 696 | c.vertexes[vertexOffset] = bounds[0] 697 | c.vertexes[vertexOffset+1] = bounds[3] 698 | c.vertexes[vertexOffset+2] = 0.5 699 | c.vertexes[vertexOffset+3] = 1.0 700 | vertexOffset += 4 701 | 702 | c.vertexes[vertexOffset] = bounds[2] 703 | c.vertexes[vertexOffset+1] = bounds[1] 704 | c.vertexes[vertexOffset+2] = 0.5 705 | c.vertexes[vertexOffset+3] = 1.0 706 | vertexOffset += 4 707 | 708 | c.vertexes[vertexOffset] = bounds[0] 709 | c.vertexes[vertexOffset+1] = bounds[1] 710 | c.vertexes[vertexOffset+2] = 0.5 711 | c.vertexes[vertexOffset+3] = 1.0 712 | 713 | // Setup uniforms for draw calls 714 | var paintFrag *glFragUniforms 715 | if call.callType == glnvgFILL { 716 | var uniforms []glFragUniforms 717 | uniforms, call.uniformOffset = c.allocFragUniforms(2) 718 | // Simple shader for stencil 719 | u0 := &uniforms[0] 720 | u0.reset() 721 | u0.setStrokeThr(-1.0) 722 | u0.setType(nsvgShaderSIMPLE) 723 | paintFrag = &uniforms[1] 724 | } else { 725 | var frags []glFragUniforms 726 | frags, call.uniformOffset = c.allocFragUniforms(1) 727 | paintFrag = &frags[0] 728 | } 729 | // Fill shader 730 | paintFrag.reset() 731 | c.convertPaint(paintFrag, paint, scissor, fringe, fringe, -1.0) 732 | } 733 | 734 | func (p *glParams) renderStroke(paint *Paint, scissor *nvgScissor, fringe float32, strokeWidth float32, paths []nvgPath) { 735 | c := p.context 736 | var glPaths []glPath 737 | p.context.calls = append(c.calls, glCall{}) 738 | call := &c.calls[len(c.calls)-1] 739 | call.callType = glnvgSTROKE 740 | glPaths, call.pathOffset = c.allocPath(len(paths)) 741 | call.pathCount = len(paths) 742 | call.image = paint.image 743 | 744 | // Allocate vertices for all the paths 745 | vertexOffset := c.allocVertexMemory(maxVertexCount(paths)) 746 | 747 | for i := range paths { 748 | glPath := &glPaths[i] 749 | path := &paths[i] 750 | 751 | strokeCount := len(path.strokes) 752 | if strokeCount > 0 { 753 | glPath.strokeOffset = vertexOffset / 4 754 | glPath.strokeCount = strokeCount 755 | for j := 0; j < strokeCount; j++ { 756 | vertex := &path.strokes[j] 757 | c.vertexes[vertexOffset] = vertex.x 758 | c.vertexes[vertexOffset+1] = vertex.y 759 | c.vertexes[vertexOffset+2] = vertex.u 760 | c.vertexes[vertexOffset+3] = vertex.v 761 | vertexOffset += 4 762 | } 763 | } else { 764 | glPath.strokeOffset = 0 765 | glPath.strokeCount = 0 766 | } 767 | } 768 | 769 | // Fill shader 770 | if c.flags&StencilStrokes != 0 { 771 | var uniforms []glFragUniforms 772 | uniforms, call.uniformOffset = c.allocFragUniforms(2) 773 | u0 := &uniforms[0] 774 | u0.reset() 775 | c.convertPaint(u0, paint, scissor, strokeWidth, fringe, -1.0) 776 | u1 := &uniforms[1] 777 | u1.reset() 778 | c.convertPaint(u1, paint, scissor, strokeWidth, fringe, -1.0-0.5/266.0) 779 | } else { 780 | var frags []glFragUniforms 781 | frags, call.uniformOffset = c.allocFragUniforms(1) 782 | f0 := &frags[0] 783 | f0.reset() 784 | c.convertPaint(f0, paint, scissor, strokeWidth, fringe, -1.0) 785 | } 786 | } 787 | 788 | func (p *glParams) renderTriangles(paint *Paint, scissor *nvgScissor, vertexes []nvgVertex) { 789 | c := p.context 790 | 791 | vertexCount := len(vertexes) 792 | vertexOffset := c.allocVertexMemory(vertexCount) 793 | callIndex := len(c.calls) 794 | 795 | c.calls = append(c.calls, glCall{ 796 | callType: glnvgTRIANGLES, 797 | image: paint.image, 798 | triangleOffset: vertexOffset / 4, 799 | triangleCount: vertexCount, 800 | }) 801 | call := &c.calls[callIndex] 802 | 803 | for i := 0; i < vertexCount; i++ { 804 | vertex := &vertexes[i] 805 | c.vertexes[vertexOffset] = vertex.x 806 | c.vertexes[vertexOffset+1] = vertex.y 807 | c.vertexes[vertexOffset+2] = vertex.u 808 | c.vertexes[vertexOffset+3] = vertex.v 809 | vertexOffset += 4 810 | } 811 | 812 | // Fill shader 813 | var frags []glFragUniforms 814 | frags, call.uniformOffset = c.allocFragUniforms(1) 815 | f0 := &frags[0] 816 | f0.reset() 817 | c.convertPaint(f0, paint, scissor, 1.0, 1.0, -1.0) 818 | f0.setType(nsvgShaderIMG) 819 | } 820 | 821 | func (p *glParams) renderTriangleStrip(paint *Paint, scissor *nvgScissor, vertexes []nvgVertex) { 822 | c := p.context 823 | 824 | vertexCount := len(vertexes) 825 | vertexOffset := c.allocVertexMemory(vertexCount) 826 | callIndex := len(c.calls) 827 | 828 | c.calls = append(c.calls, glCall{ 829 | callType: glnvgTRIANGLESTRIP, 830 | image: paint.image, 831 | triangleOffset: vertexOffset / 4, 832 | triangleCount: vertexCount, 833 | }) 834 | call := &c.calls[callIndex] 835 | 836 | for i := 0; i < vertexCount; i++ { 837 | vertex := &vertexes[i] 838 | c.vertexes[vertexOffset] = vertex.x 839 | c.vertexes[vertexOffset+1] = vertex.y 840 | c.vertexes[vertexOffset+2] = vertex.u 841 | c.vertexes[vertexOffset+3] = vertex.v 842 | vertexOffset += 4 843 | } 844 | 845 | // Fill shader 846 | var frags []glFragUniforms 847 | frags, call.uniformOffset = c.allocFragUniforms(1) 848 | f0 := &frags[0] 849 | f0.reset() 850 | c.convertPaint(f0, paint, scissor, 1.0, 1.0, -1.0) 851 | f0.setType(nsvgShaderIMG) 852 | } 853 | 854 | func (p *glParams) renderDelete() { 855 | c := p.context 856 | c.shader.deleteShader() 857 | if c.vertexBuffer.Valid() { 858 | gl.DeleteBuffer(c.vertexBuffer) 859 | } 860 | for _, texture := range c.textures { 861 | if texture.tex.Valid() && (texture.flags&ImageNoDelete) == 0 { 862 | gl.DeleteTexture(texture.tex) 863 | } 864 | } 865 | p.context = nil 866 | } 867 | 868 | func dumpShaderError(shader gl.Shader, name, typeName string) error { 869 | str := gl.GetShaderInfoLog(shader) 870 | msg := fmt.Sprintf("Shader %s/%s error:\n%s\n", name, typeName, str) 871 | dumpLog(msg) 872 | return errors.New(msg) 873 | } 874 | 875 | func dumpProgramError(program gl.Program, name string) error { 876 | str := gl.GetProgramInfoLog(program) 877 | msg := fmt.Sprintf("Program %s error:\n%s\n", name, str) 878 | dumpLog(msg) 879 | return errors.New(msg) 880 | } 881 | 882 | func checkError(p *glContext, str string) { 883 | if p.flags&Debug == 0 { 884 | return 885 | } 886 | err := gl.GetError() 887 | if err != gl.NO_ERROR { 888 | dumpLog("Error %08x after %s\n", int(err), str) 889 | } 890 | } 891 | 892 | func maxVertexCount(paths []nvgPath) int { 893 | count := 0 894 | for i := range paths { 895 | path := &paths[i] 896 | count += len(path.fills) 897 | count += len(path.strokes) 898 | } 899 | return count 900 | } 901 | 902 | var fillVertexShader = ` 903 | #ifdef NANOVG_GL3 904 | uniform vec2 viewSize; 905 | in vec2 vertex; 906 | in vec2 tcoord; 907 | out vec2 ftcoord; 908 | out vec2 fpos; 909 | #else 910 | uniform vec2 viewSize; 911 | attribute vec2 vertex; 912 | attribute vec2 tcoord; 913 | varying vec2 ftcoord; 914 | varying vec2 fpos; 915 | #endif 916 | void main(void) { 917 | ftcoord = tcoord; 918 | fpos = vertex; 919 | gl_Position = vec4(2.0*vertex.x/viewSize.x - 1.0, 1.0 - 2.0*vertex.y/viewSize.y, 0, 1); 920 | }` 921 | 922 | var fillFragmentShader = ` 923 | #ifdef GL_ES 924 | #if defined(GL_FRAGMENT_PRECISION_HIGH) || defined(NANOVG_GL3) 925 | precision highp float; 926 | #else 927 | precision mediump float; 928 | #endif 929 | #endif 930 | #ifdef NANOVG_GL3 931 | #ifdef USE_UNIFORMBUFFER 932 | layout(std140) uniform frag { 933 | mat3 scissorMat; 934 | mat3 paintMat; 935 | vec4 innerCol; 936 | vec4 outerCol; 937 | vec2 scissorExt; 938 | vec2 scissorScale; 939 | vec2 extent; 940 | float radius; 941 | float feather; 942 | float strokeMult; 943 | float strokeThr; 944 | int texType; 945 | int type; 946 | }; 947 | #else 948 | // NANOVG_GL3 && !USE_UNIFORMBUF 949 | uniform vec4 frag[UNIFORMARRAY_SIZE]; 950 | #endif 951 | uniform sampler2D tex; 952 | in vec2 ftcoord; 953 | in vec2 fpos; 954 | out vec4 outColor; 955 | #else 956 | // !NANOVG_GL3 957 | uniform vec4 frag[UNIFORMARRAY_SIZE]; 958 | uniform sampler2D tex; 959 | varying vec2 ftcoord; 960 | varying vec2 fpos; 961 | #endif 962 | #ifndef USE_UNIFORMBUFFER 963 | #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz) 964 | #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz) 965 | #define innerCol frag[6] 966 | #define outerCol frag[7] 967 | #define scissorExt frag[8].xy 968 | #define scissorScale frag[8].zw 969 | #define extent frag[9].xy 970 | #define radius frag[9].z 971 | #define feather frag[9].w 972 | #define strokeMult frag[10].x 973 | #define strokeThr frag[10].y 974 | #define texType int(frag[10].z) 975 | #define type int(frag[10].w) 976 | #endif 977 | 978 | float sdroundrect(vec2 pt, vec2 ext, float rad) { 979 | vec2 ext2 = ext - vec2(rad,rad); 980 | vec2 d = abs(pt) - ext2; 981 | return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rad; 982 | } 983 | 984 | // Scissoring 985 | float scissorMask(vec2 p) { 986 | vec2 sc = (abs((scissorMat * vec3(p,1.0)).xy) - scissorExt); 987 | sc = vec2(0.5,0.5) - sc * scissorScale; 988 | return clamp(sc.x,0.0,1.0) * clamp(sc.y,0.0,1.0); 989 | } 990 | #ifdef EDGE_AA 991 | // Stroke - from [0..1] to clipped pyramid, where the slope is 1px. 992 | float strokeMask() { 993 | return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult) * min(1.0, ftcoord.y); 994 | } 995 | #endif 996 | 997 | void main(void) { 998 | vec4 result; 999 | float scissor = scissorMask(fpos); 1000 | #ifdef EDGE_AA 1001 | float strokeAlpha = strokeMask(); 1002 | #else 1003 | float strokeAlpha = 1.0; 1004 | #endif 1005 | if (type == 0) { // Gradient 1006 | // Calculate gradient color using box gradient 1007 | vec2 pt = (paintMat * vec3(fpos,1.0)).xy; 1008 | float d = clamp((sdroundrect(pt, extent, radius) + feather*0.5) / feather, 0.0, 1.0); 1009 | vec4 color = mix(innerCol,outerCol,d); 1010 | // Combine alpha 1011 | color *= strokeAlpha * scissor; 1012 | result = color; 1013 | } else if (type == 1) { // Image 1014 | // Calculate color fron texture 1015 | vec2 pt = (paintMat * vec3(fpos,1.0)).xy / extent; 1016 | #ifdef NANOVG_GL3 1017 | vec4 color = texture(tex, pt); 1018 | #else 1019 | vec4 color = texture2D(tex, pt); 1020 | #endif 1021 | if (texType == 1) color = vec4(color.xyz*color.w,color.w); 1022 | if (texType == 2) color = vec4(color.x); 1023 | // Apply color tint and alpha. 1024 | color *= innerCol; 1025 | // Combine alpha 1026 | color *= strokeAlpha * scissor; 1027 | result = color; 1028 | } else if (type == 2) { // Stencil fill 1029 | result = vec4(1,1,1,1); 1030 | } else if (type == 3) { // Textured tris 1031 | #ifdef NANOVG_GL3 1032 | vec4 color = texture(tex, ftcoord); 1033 | #else 1034 | vec4 color = texture2D(tex, ftcoord); 1035 | #endif 1036 | if (texType == 1) color = vec4(color.xyz*color.w,color.w); 1037 | if (texType == 2) color = vec4(color.x); 1038 | color *= scissor; 1039 | result = color * innerCol; 1040 | } 1041 | #ifdef EDGE_AA 1042 | if (strokeAlpha < strokeThr) discard; 1043 | #endif 1044 | #ifdef NANOVG_GL3 1045 | outColor = result; 1046 | #else 1047 | gl_FragColor = result; 1048 | #endif 1049 | }` 1050 | -------------------------------------------------------------------------------- /gl_struct.go: -------------------------------------------------------------------------------- 1 | package nanovgo 2 | 3 | import ( 4 | "github.com/goxjs/gl" 5 | ) 6 | 7 | const ( 8 | nsvgShaderFILLGRAD = iota 9 | nsvgShaderFILLIMG 10 | nsvgShaderSIMPLE 11 | nsvgShaderIMG 12 | ) 13 | 14 | type glnvgCallType int 15 | 16 | const ( 17 | glnvgNONE glnvgCallType = iota 18 | glnvgFILL 19 | glnvgCONVEXFILL 20 | glnvgSTROKE 21 | glnvgTRIANGLES 22 | glnvgTRIANGLESTRIP 23 | ) 24 | 25 | type glCall struct { 26 | callType glnvgCallType 27 | image int 28 | pathOffset int 29 | pathCount int 30 | triangleOffset int 31 | triangleCount int 32 | uniformOffset int 33 | } 34 | 35 | type glPath struct { 36 | fillOffset int 37 | fillCount int 38 | strokeOffset int 39 | strokeCount int 40 | } 41 | 42 | type glFragUniforms [44]float32 43 | 44 | func (u *glFragUniforms) reset() { 45 | for i := 0; i < 44; i++ { 46 | u[i] = 0 47 | } 48 | } 49 | 50 | func (u *glFragUniforms) setScissorMat(mat []float32) { 51 | copy(u[0:12], mat[0:12]) 52 | } 53 | 54 | func (u *glFragUniforms) clearScissorMat() { 55 | for i := 0; i < 12; i++ { 56 | u[i] = 0 57 | } 58 | } 59 | 60 | func (u *glFragUniforms) setPaintMat(mat []float32) { 61 | copy(u[12:24], mat[0:12]) 62 | } 63 | 64 | func (u *glFragUniforms) setInnerColor(color Color) { 65 | copy(u[24:28], color.List()) 66 | } 67 | 68 | func (u *glFragUniforms) setOuterColor(color Color) { 69 | copy(u[28:32], color.List()) 70 | } 71 | 72 | func (u *glFragUniforms) setScissorExt(a, b float32) { 73 | u[32] = a 74 | u[33] = b 75 | } 76 | 77 | func (u *glFragUniforms) setScissorScale(a, b float32) { 78 | u[34] = a 79 | u[35] = b 80 | } 81 | 82 | func (u *glFragUniforms) setExtent(ext [2]float32) { 83 | copy(u[36:38], ext[:]) 84 | } 85 | 86 | func (u *glFragUniforms) setRadius(radius float32) { 87 | u[38] = radius 88 | } 89 | 90 | func (u *glFragUniforms) setFeather(feather float32) { 91 | u[39] = feather 92 | } 93 | 94 | func (u *glFragUniforms) setStrokeMult(strokeMult float32) { 95 | u[40] = strokeMult 96 | } 97 | 98 | func (u *glFragUniforms) setStrokeThr(strokeThr float32) { 99 | u[41] = strokeThr 100 | } 101 | 102 | func (u *glFragUniforms) setTexType(texType float32) { 103 | u[42] = texType 104 | } 105 | 106 | func (u *glFragUniforms) setType(typeCode float32) { 107 | u[43] = typeCode 108 | } 109 | 110 | type glTexture struct { 111 | id int 112 | tex gl.Texture 113 | width, height int 114 | texType nvgTextureType 115 | flags ImageFlags 116 | } 117 | -------------------------------------------------------------------------------- /nanovgo_test.go: -------------------------------------------------------------------------------- 1 | package nanovgo 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestStateInit(t *testing.T) { 8 | c := Context{} 9 | c.Save() 10 | c.Reset() 11 | 12 | topState := c.getState() 13 | if topState.alpha != 1.0 { 14 | t.Errorf("initial alpha should be 1.0, but %f", topState.alpha) 15 | } 16 | } 17 | 18 | func TestStateSaveRestore(t *testing.T) { 19 | c := Context{} 20 | c.Save() 21 | c.Reset() 22 | 23 | topState := c.getState() 24 | topState.alpha = 0.5 25 | 26 | c.Save() 27 | 28 | nextState := c.getState() 29 | if nextState.alpha != 0.5 { 30 | t.Errorf("initial alpha should be same with parent's one, but %f", nextState.alpha) 31 | } 32 | nextState.alpha = 0.75 33 | 34 | c.Restore() 35 | 36 | topStateAgain := c.getState() 37 | if topStateAgain.alpha != 0.5 { 38 | t.Errorf("Restore() should set saved alpha, but %f", topStateAgain.alpha) 39 | } 40 | } 41 | 42 | func equal(a1, a2 TransformMatrix) bool { 43 | for i := 0; i < 6; i++ { 44 | if a1[i] != a2[i] { 45 | return false 46 | } 47 | } 48 | return true 49 | } 50 | 51 | func TestStateSaveRestore2(t *testing.T) { 52 | c := Context{} 53 | c.Save() 54 | c.Reset() 55 | 56 | topState := c.getState() 57 | topState.xform = TranslateMatrix(10, 5) 58 | 59 | c.Save() 60 | 61 | nextState := c.getState() 62 | if !equal(nextState.xform, TranslateMatrix(10, 5)) { 63 | t.Errorf("initial xform should be same with parent's one, but %v", nextState.xform) 64 | } 65 | nextState.xform = ScaleMatrix(20, 30) 66 | 67 | c.Restore() 68 | 69 | topStateAgain := c.getState() 70 | if !equal(topStateAgain.xform, TranslateMatrix(10, 5)) { 71 | t.Errorf("Restore() should set saved xform, but %v", topStateAgain.xform) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /paint.go: -------------------------------------------------------------------------------- 1 | package nanovgo 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | // Paint structure represent paint information including gradient and image painting. 8 | // Context.SetFillPaint() and Context.SetStrokePaint() accept this instance. 9 | type Paint struct { 10 | xform TransformMatrix 11 | extent [2]float32 12 | radius float32 13 | feather float32 14 | innerColor Color 15 | outerColor Color 16 | image int 17 | } 18 | 19 | func (p *Paint) setPaintColor(color Color) { 20 | p.xform = IdentityMatrix() 21 | p.extent[0] = 0.0 22 | p.extent[1] = 0.0 23 | p.radius = 0.0 24 | p.feather = 1.0 25 | p.innerColor = color 26 | p.outerColor = color 27 | p.image = 0 28 | } 29 | 30 | // LinearGradient creates and returns a linear gradient. Parameters (sx,sy)-(ex,ey) specify the start and end coordinates 31 | // of the linear gradient, icol specifies the start color and ocol the end color. 32 | // The gradient is transformed by the current transform when it is passed to Context.FillPaint() or Context.StrokePaint(). 33 | func LinearGradient(sx, sy, ex, ey float32, iColor, oColor Color) Paint { 34 | var large float32 = 1e5 35 | dx := ex - sx 36 | dy := ey - sy 37 | d := float32(math.Sqrt(float64(dx*dx + dy*dy))) 38 | if d > 0.0001 { 39 | dx /= d 40 | dy /= d 41 | } else { 42 | dx = 0.0 43 | dy = 1.0 44 | } 45 | 46 | return Paint{ 47 | xform: TransformMatrix{dy, -dx, dx, dy, sx - dx*large, sy - dy*large}, 48 | extent: [2]float32{large, large + d*0.5}, 49 | radius: 0.0, 50 | feather: maxF(1.0, d), 51 | innerColor: iColor, 52 | outerColor: oColor, 53 | } 54 | } 55 | 56 | // RadialGradient creates and returns a radial gradient. Parameters (cx,cy) specify the center, inr and outr specify 57 | // the inner and outer radius of the gradient, icol specifies the start color and ocol the end color. 58 | // The gradient is transformed by the current transform when it is passed to Context.FillPaint() or Context.StrokePaint(). 59 | func RadialGradient(cx, cy, inR, outR float32, iColor, oColor Color) Paint { 60 | r := (inR + outR) * 0.5 61 | f := outR - inR 62 | 63 | return Paint{ 64 | xform: TranslateMatrix(cx, cy), 65 | extent: [2]float32{r, r}, 66 | radius: 0.0, 67 | feather: maxF(1.0, f), 68 | innerColor: iColor, 69 | outerColor: oColor, 70 | } 71 | } 72 | 73 | // BoxGradient creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering 74 | // drop shadows or highlights for boxes. Parameters (x,y) define the top-left corner of the rectangle, 75 | // (w,h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry 76 | // the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient. 77 | // The gradient is transformed by the current transform when it is passed to Context.FillPaint() or Context.StrokePaint(). 78 | func BoxGradient(x, y, w, h, r, f float32, iColor, oColor Color) Paint { 79 | return Paint{ 80 | xform: TranslateMatrix(x+w*0.5, y+h*0.5), 81 | extent: [2]float32{w * 0.5, h * 0.5}, 82 | radius: r, 83 | feather: maxF(1.0, f), 84 | innerColor: iColor, 85 | outerColor: oColor, 86 | } 87 | } 88 | 89 | // ImagePattern creates and returns an image patter. Parameters (ox,oy) specify the left-top location of the image pattern, 90 | // (ex,ey) the size of one image, angle rotation around the top-left corner, image is handle to the image to render. 91 | // The gradient is transformed by the current transform when it is passed to Context.FillPaint() or Context.StrokePaint(). 92 | func ImagePattern(cx, cy, w, h, angle float32, img int, alpha float32) Paint { 93 | xform := RotateMatrix(angle) 94 | xform[4] = cx 95 | xform[5] = cy 96 | color := RGBAf(1, 1, 1, alpha) 97 | return Paint{ 98 | xform: xform, 99 | extent: [2]float32{w, h}, 100 | image: img, 101 | innerColor: color, 102 | outerColor: color, 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /perfgraph/performancegraph.go: -------------------------------------------------------------------------------- 1 | package perfgraph 2 | 3 | import ( 4 | "fmt" 5 | "github.com/shibukawa/nanovgo" 6 | "time" 7 | ) 8 | 9 | const ( 10 | nvgGraphHistoryCount = 100 11 | ) 12 | 13 | var backgroundColor = nanovgo.RGBA(0, 0, 0, 128) 14 | var graphColor = nanovgo.RGBA(255, 192, 0, 128) 15 | var titleTextColor = nanovgo.RGBA(255, 192, 0, 128) 16 | var fpsTextColor = nanovgo.RGBA(240, 240, 240, 255) 17 | var averageTextColor = nanovgo.RGBA(240, 240, 240, 160) 18 | var msTextColor = nanovgo.RGBA(240, 240, 240, 255) 19 | 20 | // PerfGraph shows FPS counter on NanoVGo application 21 | type PerfGraph struct { 22 | name string 23 | fontFace string 24 | values [nvgGraphHistoryCount]float32 25 | head int 26 | 27 | startTime time.Time 28 | lastUpdateTime time.Time 29 | } 30 | 31 | // NewPerfGraph creates PerfGraph instance 32 | func NewPerfGraph(name, fontFace string) *PerfGraph { 33 | return &PerfGraph{ 34 | name: name, 35 | fontFace: fontFace, 36 | startTime: time.Now(), 37 | lastUpdateTime: time.Now(), 38 | } 39 | } 40 | 41 | // UpdateGraph updates timer it is needed to show graph 42 | func (pg *PerfGraph) UpdateGraph() (timeFromStart, frameTime float32) { 43 | timeNow := time.Now() 44 | timeFromStart = float32(timeNow.Sub(pg.startTime)/time.Millisecond) * 0.001 45 | frameTime = float32(timeNow.Sub(pg.lastUpdateTime)/time.Millisecond) * 0.001 46 | pg.lastUpdateTime = timeNow 47 | 48 | pg.head = (pg.head + 1) % nvgGraphHistoryCount 49 | pg.values[pg.head] = frameTime 50 | return 51 | } 52 | 53 | // RenderGraph shows graph 54 | func (pg *PerfGraph) RenderGraph(ctx *nanovgo.Context, x, y float32) { 55 | avg := pg.GetGraphAverage() 56 | var w float32 = 200 57 | var h float32 = 35 58 | 59 | ctx.BeginPath() 60 | ctx.Rect(x, y, w, h) 61 | ctx.SetFillColor(backgroundColor) 62 | ctx.Fill() 63 | 64 | ctx.BeginPath() 65 | ctx.MoveTo(x, y+h) 66 | for i := 0; i < nvgGraphHistoryCount; i++ { 67 | v := float32(1.0) / float32(0.00001+pg.values[(pg.head+i)%nvgGraphHistoryCount]) 68 | if v > 80.0 { 69 | v = 80.0 70 | } 71 | vx := x + float32(i)/float32(nvgGraphHistoryCount-1)*w 72 | vy := y + h - ((v / 80.0) * h) 73 | ctx.LineTo(vx, vy) 74 | } 75 | ctx.LineTo(x+w, y+h) 76 | ctx.SetFillColor(graphColor) 77 | ctx.Fill() 78 | 79 | ctx.SetFontFace(pg.fontFace) 80 | 81 | if len(pg.name) > 0 { 82 | ctx.SetFontSize(14.0) 83 | ctx.SetTextAlign(nanovgo.AlignLeft | nanovgo.AlignTop) 84 | ctx.SetFillColor(titleTextColor) 85 | ctx.Text(x+3, y+1, pg.name) 86 | } 87 | 88 | ctx.SetFontSize(18.0) 89 | ctx.SetTextAlign(nanovgo.AlignRight | nanovgo.AlignTop) 90 | ctx.SetFillColor(fpsTextColor) 91 | ctx.Text(x+w-3, y+1, fmt.Sprintf("%.2f FPS", 1.0/avg)) 92 | 93 | ctx.SetFontSize(15.0) 94 | ctx.SetTextAlign(nanovgo.AlignRight | nanovgo.AlignBottom) 95 | ctx.SetFillColor(averageTextColor) 96 | ctx.Text(x+w-3, y+h+1, fmt.Sprintf("%.2f ms", avg*1000.0)) 97 | } 98 | 99 | // GetGraphAverage returns average value of graph. 100 | func (pg *PerfGraph) GetGraphAverage() float32 { 101 | var average float32 102 | for _, value := range pg.values { 103 | average += value 104 | } 105 | return average / float32(nvgGraphHistoryCount) 106 | } 107 | -------------------------------------------------------------------------------- /platform_mobile.go: -------------------------------------------------------------------------------- 1 | // +build darwin linux 2 | // +build arm arm64 3 | 4 | package nanovgo 5 | 6 | import ( 7 | "log" 8 | "unsafe" 9 | ) 10 | 11 | type Float float32 12 | 13 | var shaderHeader string = ` 14 | #version 100 15 | #define NANOVG_GL2 1 16 | #define UNIFORMARRAY_SIZE 11 17 | ` 18 | 19 | func prepareTextureBuffer(data []byte, w, h, bpp int) []byte { 20 | return data 21 | } 22 | 23 | func castFloat32ToByte(vertexes []float32) []byte { 24 | // Convert []float32 list to []byte without copy 25 | var b []byte 26 | b = (*(*[1 << 20]byte)(unsafe.Pointer(&vertexes[0])))[:len(vertexes)*4] 27 | return b 28 | } 29 | 30 | func dumpLog(values ...interface{}) { 31 | log.Println(values...) 32 | } 33 | -------------------------------------------------------------------------------- /platform_other.go: -------------------------------------------------------------------------------- 1 | // +build !arm !arm64 2 | // +build !js 3 | 4 | package nanovgo 5 | 6 | import ( 7 | "log" 8 | "unsafe" 9 | "encoding/binary" 10 | "math" 11 | ) 12 | 13 | type Float float32 14 | 15 | var shaderHeader = ` 16 | #define NANOVG_GL2 1 17 | #define UNIFORMARRAY_SIZE 11 18 | ` 19 | 20 | func prepareTextureBuffer(data []byte, w, h, bpp int) []byte { 21 | return data 22 | } 23 | 24 | func castFloat32ToByte(vertexes []float32) []byte { 25 | // Convert []float32 list to []byte without copy 26 | var b []byte 27 | if len(vertexes) > 65536 { 28 | b = make([]byte, len(vertexes)*4) 29 | for i, v := range vertexes { 30 | binary.LittleEndian.PutUint32(b[4*i:], math.Float32bits(v)) 31 | } 32 | } else { 33 | b = (*(*[1 << 20]byte)(unsafe.Pointer(&vertexes[0])))[:len(vertexes)*4] 34 | } 35 | return b 36 | } 37 | 38 | func dumpLog(values ...interface{}) { 39 | log.Println(values...) 40 | } 41 | -------------------------------------------------------------------------------- /platform_webgl.go: -------------------------------------------------------------------------------- 1 | // +build js 2 | 3 | package nanovgo 4 | 5 | import ( 6 | "encoding/binary" 7 | "honnef.co/go/js/console" 8 | "math" 9 | ) 10 | 11 | var shaderHeader string = ` 12 | #version 100 13 | #define NANOVG_GL2 1 14 | #define UNIFORMARRAY_SIZE 11 15 | ` 16 | 17 | func prepareTextureBuffer(data []byte, w, h, bpp int) []byte { 18 | // gl.TexImage2D on WebGL doesn't allow nil as input 19 | if data == nil { 20 | data = make([]byte, w*h*bpp) 21 | } else if len(data) < w*h*bpp { 22 | data = append(data, make([]byte, w*h*bpp-len(data))...) 23 | } 24 | return data 25 | } 26 | 27 | func castFloat32ToByte(vertexes []float32) []byte { 28 | b := make([]byte, len(vertexes)*4) 29 | for i, v := range vertexes { 30 | binary.LittleEndian.PutUint32(b[4*i:], math.Float32bits(v)) 31 | } 32 | return b 33 | } 34 | 35 | func dumpLog(values ...interface{}) { 36 | console.Log(values...) 37 | } 38 | -------------------------------------------------------------------------------- /sample/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/nanovgo/9141d09b3652b251464e87c714b6192b9a740ffc/sample/Roboto-Bold.ttf -------------------------------------------------------------------------------- /sample/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/nanovgo/9141d09b3652b251464e87c714b6192b9a740ffc/sample/Roboto-Light.ttf -------------------------------------------------------------------------------- /sample/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/nanovgo/9141d09b3652b251464e87c714b6192b9a740ffc/sample/Roboto-Regular.ttf -------------------------------------------------------------------------------- /sample/demo/demo.go: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import ( 4 | "github.com/shibukawa/nanovgo" 5 | "math" 6 | "strconv" 7 | ) 8 | 9 | const ( 10 | IconSEARCH = 0x1F50D 11 | IconCIRCLEDCROSS = 0x2716 12 | IconCHEVRONRIGHT = 0xE75E 13 | IconCHECK = 0x2713 14 | IconLOGIN = 0xE740 15 | IconTRASH = 0xE729 16 | ) 17 | 18 | // DemoData keeps font and image handlers 19 | type DemoData struct { 20 | FontNormal, FontBold, FontIcons int 21 | Images []int 22 | } 23 | 24 | func (d *DemoData) FreeData(ctx *nanovgo.Context) { 25 | for _, img := range d.Images { 26 | ctx.DeleteImage(img) 27 | } 28 | } 29 | 30 | func RenderDemo(ctx *nanovgo.Context, mx, my, width, height, t float32, blowup bool, data *DemoData) { 31 | drawEyes(ctx, width-250, 50, 150, 100, mx, my, t) 32 | drawParagraph(ctx, width-450, 50, 150, 100, mx, my) 33 | drawGraph(ctx, 0, height/2, width, height/2, t) 34 | drawColorWheel(ctx, width-300, height-300, 250.0, 250.0, t) 35 | 36 | // Line joints 37 | drawLines(ctx, 120, height-50, 600, 50, t) 38 | 39 | // Line widths 40 | drawWidths(ctx, 10, 50, 30) 41 | 42 | // Line caps 43 | drawCaps(ctx, 10, 300, 30) 44 | 45 | drawScissor(ctx, 50, height-80, t) 46 | 47 | ctx.Save() 48 | defer ctx.Restore() 49 | 50 | if blowup { 51 | ctx.Rotate(sinF(t*0.3) * 5.0 / 180.0 * nanovgo.PI) 52 | ctx.Scale(2.0, 2.0) 53 | } 54 | 55 | // Widgets 56 | drawWindow(ctx, "Widgets `n Stuff", 50, 50, 300, 400) 57 | var x float32 = 60.0 58 | var y float32 = 95.0 59 | drawSearchBox(ctx, "Search", x, y, 280, 25) 60 | y += 40 61 | drawDropDown(ctx, "Effects", x, y, 280, 28) 62 | popy := y + 14 63 | y += 45 64 | 65 | // Form 66 | drawLabel(ctx, "Login", x, y, 280, 20) 67 | y += 25 68 | drawEditBox(ctx, "Email", x, y, 280, 28) 69 | y += 35 70 | drawEditBox(ctx, "Password", x, y, 280, 28) 71 | y += 38 72 | drawCheckBox(ctx, "Remember me", x, y, 140, 28) 73 | drawButton(ctx, IconLOGIN, "Sign in", x+138, y, 140, 28, nanovgo.RGBA(0, 96, 128, 255)) 74 | y += 45 75 | 76 | // Slider 77 | drawLabel(ctx, "Diameter", x, y, 280, 20) 78 | y += 25 79 | drawEditBoxNum(ctx, "123.00", "px", x+180, y, 100, 28) 80 | drawSlider(ctx, 0.4, x, y, 170, 28) 81 | y += 55 82 | 83 | drawButton(ctx, IconTRASH, "Delete", x, y, 160, 28, nanovgo.RGBA(128, 16, 8, 255)) 84 | drawButton(ctx, 0, "Cancel", x+170, y, 110, 28, nanovgo.RGBA(0, 0, 0, 0)) 85 | 86 | // Thumbnails box 87 | drawThumbnails(ctx, 365, popy-30, 160, 300, data.Images, t) 88 | } 89 | 90 | func cosF(a float32) float32 { 91 | return float32(math.Cos(float64(a))) 92 | } 93 | func sinF(a float32) float32 { 94 | return float32(math.Sin(float64(a))) 95 | } 96 | func sqrtF(a float32) float32 { 97 | return float32(math.Sqrt(float64(a))) 98 | } 99 | func clampF(a, min, max float32) float32 { 100 | if a < min { 101 | return min 102 | } 103 | if a > max { 104 | return max 105 | } 106 | return a 107 | } 108 | func absF(a float32) float32 { 109 | if a > 0.0 { 110 | return a 111 | } 112 | return -a 113 | } 114 | func maxF(a, b float32) float32 { 115 | if a > b { 116 | return a 117 | } 118 | return b 119 | } 120 | 121 | func isBlack(col nanovgo.Color) bool { 122 | return col.R == 0.0 && col.G == 0.0 && col.B == 0.0 && col.A == 0.0 123 | } 124 | 125 | func cpToUTF8(cp int) string { 126 | return string([]rune{rune(cp)}) 127 | } 128 | 129 | func drawWindow(ctx *nanovgo.Context, title string, x, y, w, h float32) { 130 | var cornerRadius float32 = 3.0 131 | 132 | ctx.Save() 133 | defer ctx.Restore() 134 | // ctx.Reset(); 135 | 136 | // Window 137 | ctx.BeginPath() 138 | ctx.RoundedRect(x, y, w, h, cornerRadius) 139 | ctx.SetFillColor(nanovgo.RGBA(28, 30, 34, 192)) 140 | // ctx.FillColor(nanovgo.RGBA(0,0,0,128)); 141 | ctx.Fill() 142 | 143 | // Drop shadow 144 | shadowPaint := nanovgo.BoxGradient(x, y+2, w, h, cornerRadius*2, 10, nanovgo.RGBA(0, 0, 0, 128), nanovgo.RGBA(0, 0, 0, 0)) 145 | ctx.BeginPath() 146 | ctx.Rect(x-10, y-10, w+20, h+30) 147 | ctx.RoundedRect(x, y, w, h, cornerRadius) 148 | ctx.PathWinding(nanovgo.Hole) 149 | ctx.SetFillPaint(shadowPaint) 150 | ctx.Fill() 151 | 152 | // Header 153 | headerPaint := nanovgo.LinearGradient(x, y, x, y+15, nanovgo.RGBA(255, 255, 255, 8), nanovgo.RGBA(0, 0, 0, 16)) 154 | ctx.BeginPath() 155 | ctx.RoundedRect(x+1, y+1, w-2, 30, cornerRadius-1) 156 | ctx.SetFillPaint(headerPaint) 157 | ctx.Fill() 158 | ctx.BeginPath() 159 | ctx.MoveTo(x+0.5, y+0.5+30) 160 | ctx.LineTo(x+0.5+w-1, y+0.5+30) 161 | ctx.SetStrokeColor(nanovgo.RGBA(0, 0, 0, 32)) 162 | ctx.Stroke() 163 | 164 | ctx.SetFontSize(18.0) 165 | ctx.SetFontFace("sans-bold") 166 | ctx.SetTextAlign(nanovgo.AlignCenter | nanovgo.AlignMiddle) 167 | 168 | ctx.SetFontBlur(2) 169 | ctx.SetFillColor(nanovgo.RGBA(0, 0, 0, 128)) 170 | ctx.Text(x+w/2, y+16+1, title) 171 | 172 | ctx.SetFontBlur(0) 173 | ctx.SetFillColor(nanovgo.RGBA(220, 220, 220, 160)) 174 | ctx.Text(x+w/2, y+16, title) 175 | } 176 | 177 | func drawSearchBox(ctx *nanovgo.Context, text string, x, y, w, h float32) { 178 | cornerRadius := h/2 - 1 179 | 180 | // Edit 181 | bg := nanovgo.BoxGradient(x, y+1.5, w, h, h/2, 5, nanovgo.RGBA(0, 0, 0, 16), nanovgo.RGBA(0, 0, 0, 92)) 182 | ctx.BeginPath() 183 | ctx.RoundedRect(x, y, w, h, cornerRadius) 184 | ctx.SetFillPaint(bg) 185 | ctx.Fill() 186 | 187 | /* ctx.BeginPath(); 188 | ctx.RoundedRect(x+0.5f,y+0.5f, w-1,h-1, cornerRadius-0.5f); 189 | ctx.StrokeColor(ctx.RGBA(0,0,0,48)); 190 | ctx.Stroke();*/ 191 | 192 | ctx.SetFontSize(h * 1.3) 193 | ctx.SetFontFace("icons") 194 | ctx.SetFillColor(nanovgo.RGBA(255, 255, 255, 64)) 195 | ctx.SetTextAlign(nanovgo.AlignCenter | nanovgo.AlignMiddle) 196 | ctx.Text(x+h*0.55, y+h*0.55, cpToUTF8(IconSEARCH)) 197 | 198 | ctx.SetFontSize(20.0) 199 | ctx.SetFontFace("sans") 200 | ctx.SetFillColor(nanovgo.RGBA(255, 255, 255, 32)) 201 | 202 | ctx.SetTextAlign(nanovgo.AlignLeft | nanovgo.AlignMiddle) 203 | ctx.Text(x+h*1.05, y+h*0.5, text) 204 | 205 | ctx.SetFontSize(h * 1.3) 206 | ctx.SetFontFace("icons") 207 | ctx.SetFillColor(nanovgo.RGBA(255, 255, 255, 32)) 208 | ctx.SetTextAlign(nanovgo.AlignCenter | nanovgo.AlignMiddle) 209 | ctx.Text(x+w-h*0.55, y+h*0.55, cpToUTF8(IconCIRCLEDCROSS)) 210 | } 211 | 212 | func drawDropDown(ctx *nanovgo.Context, text string, x, y, w, h float32) { 213 | 214 | var cornerRadius float32 = 4.0 215 | 216 | bg := nanovgo.LinearGradient(x, y, x, y+h, nanovgo.RGBA(255, 255, 255, 16), nanovgo.RGBA(0, 0, 0, 16)) 217 | ctx.BeginPath() 218 | ctx.RoundedRect(x+1, y+1, w-2, h-2, cornerRadius-1) 219 | ctx.SetFillPaint(bg) 220 | ctx.Fill() 221 | 222 | ctx.BeginPath() 223 | ctx.RoundedRect(x+0.5, y+0.5, w-1, h-1, cornerRadius-0.5) 224 | ctx.SetStrokeColor(nanovgo.RGBA(0, 0, 0, 48)) 225 | ctx.Stroke() 226 | 227 | ctx.SetFontSize(20.0) 228 | ctx.SetFontFace("sans") 229 | ctx.SetFillColor(nanovgo.RGBA(255, 255, 255, 160)) 230 | ctx.SetTextAlign(nanovgo.AlignLeft | nanovgo.AlignMiddle) 231 | ctx.Text(x+h*0.3, y+h*0.5, text) 232 | 233 | ctx.SetFontSize(h * 1.3) 234 | ctx.SetFontFace("icons") 235 | ctx.SetFillColor(nanovgo.RGBA(255, 255, 255, 64)) 236 | ctx.SetTextAlign(nanovgo.AlignCenter | nanovgo.AlignMiddle) 237 | ctx.Text(x+w-h*0.5, y+h*0.5, cpToUTF8(IconCHEVRONRIGHT)) 238 | } 239 | 240 | func drawEditBoxBase(ctx *nanovgo.Context, x, y, w, h float32) { 241 | // Edit 242 | bg := nanovgo.BoxGradient(x+1, y+1+1.5, w-2, h-2, 3, 4, nanovgo.RGBA(255, 255, 255, 32), nanovgo.RGBA(32, 32, 32, 32)) 243 | ctx.BeginPath() 244 | ctx.RoundedRect(x+1, y+1, w-2, h-2, 4-1) 245 | ctx.SetFillPaint(bg) 246 | ctx.Fill() 247 | 248 | ctx.BeginPath() 249 | ctx.RoundedRect(x+0.5, y+0.5, w-1, h-1, 4-0.5) 250 | ctx.SetStrokeColor(nanovgo.RGBA(0, 0, 0, 48)) 251 | ctx.Stroke() 252 | } 253 | 254 | func drawEditBox(ctx *nanovgo.Context, text string, x, y, w, h float32) { 255 | 256 | drawEditBoxBase(ctx, x, y, w, h) 257 | 258 | ctx.SetFontSize(20.0) 259 | ctx.SetFontFace("sans") 260 | ctx.SetFillColor(nanovgo.RGBA(255, 255, 255, 64)) 261 | ctx.SetTextAlign(nanovgo.AlignLeft | nanovgo.AlignMiddle) 262 | ctx.Text(x+h*0.3, y+h*0.5, text) 263 | } 264 | 265 | func drawLabel(ctx *nanovgo.Context, text string, x, y, w, h float32) { 266 | 267 | ctx.SetFontSize(18.0) 268 | ctx.SetFontFace("sans") 269 | ctx.SetFillColor(nanovgo.RGBA(255, 255, 255, 128)) 270 | 271 | ctx.SetTextAlign(nanovgo.AlignLeft | nanovgo.AlignMiddle) 272 | ctx.Text(x, y+h*0.5, text) 273 | } 274 | 275 | func drawEditBoxNum(ctx *nanovgo.Context, text, units string, x, y, w, h float32) { 276 | drawEditBoxBase(ctx, x, y, w, h) 277 | 278 | uw, _ := ctx.TextBounds(0, 0, units) 279 | 280 | ctx.SetFontSize(18.0) 281 | ctx.SetFontFace("sans") 282 | ctx.SetFillColor(nanovgo.RGBA(255, 255, 255, 64)) 283 | ctx.SetTextAlign(nanovgo.AlignRight | nanovgo.AlignMiddle) 284 | ctx.Text(x+w-h*0.3, y+h*0.5, units) 285 | 286 | ctx.SetFontSize(20.0) 287 | ctx.SetFontFace("sans") 288 | ctx.SetFillColor(nanovgo.RGBA(255, 255, 255, 128)) 289 | ctx.SetTextAlign(nanovgo.AlignRight | nanovgo.AlignMiddle) 290 | ctx.Text(x+w-uw-h*0.5, y+h*0.5, text) 291 | } 292 | 293 | func drawCheckBox(ctx *nanovgo.Context, text string, x, y, w, h float32) { 294 | 295 | ctx.SetFontSize(18.0) 296 | ctx.SetFontFace("sans") 297 | ctx.SetFillColor(nanovgo.RGBA(255, 255, 255, 160)) 298 | 299 | ctx.SetTextAlign(nanovgo.AlignLeft | nanovgo.AlignMiddle) 300 | ctx.Text(x+28, y+h*0.5, text) 301 | 302 | bg := nanovgo.BoxGradient(x+1, y+float32(int(h*0.5))-9+1, 18, 18, 3, 3, nanovgo.RGBA(0, 0, 0, 32), nanovgo.RGBA(0, 0, 0, 92)) 303 | ctx.BeginPath() 304 | ctx.RoundedRect(x+1, y+float32(int(h*0.5))-9, 18, 18, 3) 305 | ctx.SetFillPaint(bg) 306 | ctx.Fill() 307 | 308 | ctx.SetFontSize(40) 309 | ctx.SetFontFace("icons") 310 | ctx.SetFillColor(nanovgo.RGBA(255, 255, 255, 128)) 311 | ctx.SetTextAlign(nanovgo.AlignCenter | nanovgo.AlignMiddle) 312 | ctx.Text(x+9+2, y+h*0.5, cpToUTF8(IconCHECK)) 313 | } 314 | 315 | func drawButton(ctx *nanovgo.Context, preicon int, text string, x, y, w, h float32, col nanovgo.Color) { 316 | var cornerRadius float32 = 4.0 317 | var iw float32 318 | 319 | var alpha uint8 320 | if isBlack(col) { 321 | alpha = 16 322 | } else { 323 | alpha = 32 324 | } 325 | bg := nanovgo.LinearGradient(x, y, x, y+h, nanovgo.RGBA(255, 255, 255, alpha), nanovgo.RGBA(0, 0, 0, alpha)) 326 | ctx.BeginPath() 327 | ctx.RoundedRect(x+1, y+1, w-2, h-2, cornerRadius-1) 328 | if !isBlack(col) { 329 | ctx.SetFillColor(col) 330 | ctx.Fill() 331 | } 332 | ctx.SetFillPaint(bg) 333 | ctx.Fill() 334 | 335 | ctx.BeginPath() 336 | ctx.RoundedRect(x+0.5, y+0.5, w-1, h-1, cornerRadius-0.5) 337 | ctx.SetStrokeColor(nanovgo.RGBA(0, 0, 0, 48)) 338 | ctx.Stroke() 339 | 340 | ctx.SetFontSize(20.0) 341 | ctx.SetFontFace("sans-bold") 342 | tw, _ := ctx.TextBounds(0, 0, text) 343 | if preicon != 0 { 344 | ctx.SetFontSize(h * 1.3) 345 | ctx.SetFontFace("icons") 346 | iw, _ = ctx.TextBounds(0, 0, cpToUTF8(preicon)) 347 | iw += h * 0.15 348 | 349 | ctx.SetFillColor(nanovgo.RGBA(255, 255, 255, 96)) 350 | ctx.SetTextAlign(nanovgo.AlignLeft | nanovgo.AlignMiddle) 351 | ctx.Text(x+w*0.5-tw*0.5-iw*0.75, y+h*0.5, cpToUTF8(preicon)) 352 | } 353 | 354 | ctx.SetFontSize(20.0) 355 | ctx.SetFontFace("sans-bold") 356 | ctx.SetTextAlign(nanovgo.AlignLeft | nanovgo.AlignMiddle) 357 | ctx.SetFillColor(nanovgo.RGBA(0, 0, 0, 160)) 358 | ctx.Text(x+w*0.5-tw*0.5+iw*0.25, y+h*0.5-1, text) 359 | ctx.SetFillColor(nanovgo.RGBA(255, 255, 255, 160)) 360 | ctx.Text(x+w*0.5-tw*0.5+iw*0.25, y+h*0.5, text) 361 | } 362 | 363 | func drawSlider(ctx *nanovgo.Context, pos, x, y, w, h float32) { 364 | cy := y + float32(int(h*0.5)) 365 | kr := float32(int(h * 0.25)) 366 | 367 | ctx.Save() 368 | defer ctx.Restore() 369 | // ctx.ClearState(vg); 370 | 371 | // Slot 372 | bg := nanovgo.BoxGradient(x, cy-2+1, w, 4, 2, 2, nanovgo.RGBA(0, 0, 0, 32), nanovgo.RGBA(0, 0, 0, 128)) 373 | ctx.BeginPath() 374 | ctx.RoundedRect(x, cy-2, w, 4, 2) 375 | ctx.SetFillPaint(bg) 376 | ctx.Fill() 377 | 378 | // Knob Shadow 379 | bg = nanovgo.RadialGradient(x+float32(int(pos*w)), cy+1, kr-3, kr+3, nanovgo.RGBA(0, 0, 0, 64), nanovgo.RGBA(0, 0, 0, 0)) 380 | ctx.BeginPath() 381 | ctx.Rect(x+float32(int(pos*w))-kr-5, cy-kr-5, kr*2+5+5, kr*2+5+5+3) 382 | ctx.Circle(x+float32(int(pos*w)), cy, kr) 383 | ctx.PathWinding(nanovgo.Hole) 384 | ctx.SetFillPaint(bg) 385 | ctx.Fill() 386 | 387 | // Knob 388 | knob := nanovgo.LinearGradient(x, cy-kr, x, cy+kr, nanovgo.RGBA(255, 255, 255, 16), nanovgo.RGBA(0, 0, 0, 16)) 389 | ctx.BeginPath() 390 | ctx.Circle(x+float32(int(pos*w)), cy, kr-1) 391 | ctx.SetFillColor(nanovgo.RGBA(40, 43, 48, 255)) 392 | ctx.Fill() 393 | ctx.SetFillPaint(knob) 394 | ctx.Fill() 395 | 396 | ctx.BeginPath() 397 | ctx.Circle(x+float32(int(pos*w)), cy, kr-0.5) 398 | ctx.SetStrokeColor(nanovgo.RGBA(0, 0, 0, 92)) 399 | ctx.Stroke() 400 | } 401 | 402 | func drawEyes(ctx *nanovgo.Context, x, y, w, h, mx, my, t float32) { 403 | ex := w * 0.23 404 | ey := h * 0.5 405 | lx := x + ex 406 | ly := y + ey 407 | rx := x + w - ex 408 | ry := y + ey 409 | var dx, dy, d, br float32 410 | if ex < ey { 411 | br = ex * 0.5 412 | } else { 413 | br = ey * 0.5 414 | } 415 | blink := float32(1.0 - math.Pow(float64(sinF(t*0.5)), 200)*0.8) 416 | 417 | bg1 := nanovgo.LinearGradient(x, y+h*0.5, x+w*0.1, y+h, nanovgo.RGBA(0, 0, 0, 32), nanovgo.RGBA(0, 0, 0, 16)) 418 | ctx.BeginPath() 419 | ctx.Ellipse(lx+3.0, ly+16.0, ex, ey) 420 | ctx.Ellipse(rx+3.0, ry+16.0, ex, ey) 421 | ctx.SetFillPaint(bg1) 422 | ctx.Fill() 423 | 424 | bg2 := nanovgo.LinearGradient(x, y+h*0.25, x+w*0.1, y+h, nanovgo.RGBA(220, 220, 220, 255), nanovgo.RGBA(128, 128, 128, 255)) 425 | ctx.BeginPath() 426 | ctx.Ellipse(lx, ly, ex, ey) 427 | ctx.Ellipse(rx, ry, ex, ey) 428 | ctx.SetFillPaint(bg2) 429 | ctx.Fill() 430 | 431 | dx = (mx - rx) / (ex * 10) 432 | dy = (my - ry) / (ey * 10) 433 | d = sqrtF(dx*dx + dy*dy) 434 | if d > 1.0 { 435 | dx /= d 436 | dy /= d 437 | } 438 | dx *= ex * 0.4 439 | dy *= ey * 0.5 440 | ctx.BeginPath() 441 | ctx.Ellipse(lx+dx, ly+dy+ey*0.25*(1.0-blink), br, br*blink) 442 | ctx.SetFillColor(nanovgo.RGBA(32, 32, 32, 255)) 443 | ctx.Fill() 444 | 445 | dx = (mx - rx) / (ex * 10) 446 | dy = (my - ry) / (ey * 10) 447 | d = sqrtF(dx*dx + dy*dy) 448 | if d > 1.0 { 449 | dx /= d 450 | dy /= d 451 | } 452 | dx *= ex * 0.4 453 | dy *= ey * 0.5 454 | ctx.BeginPath() 455 | ctx.Ellipse(rx+dx, ry+dy+ey*0.25*(1.0-blink), br, br*blink) 456 | ctx.SetFillColor(nanovgo.RGBA(32, 32, 32, 255)) 457 | ctx.Fill() 458 | dx = (mx - rx) / (ex * 10) 459 | dy = (my - ry) / (ey * 10) 460 | d = sqrtF(dx*dx + dy*dy) 461 | if d > 1.0 { 462 | dx /= d 463 | dy /= d 464 | } 465 | dx *= ex * 0.4 466 | dy *= ey * 0.5 467 | ctx.BeginPath() 468 | ctx.Ellipse(rx+dx, ry+dy+ey*0.25*(1.0-blink), br, br*blink) 469 | ctx.SetFillColor(nanovgo.RGBA(32, 32, 32, 255)) 470 | ctx.Fill() 471 | 472 | gloss1 := nanovgo.RadialGradient(lx-ex*0.25, ly-ey*0.5, ex*0.1, ex*0.75, nanovgo.RGBA(255, 255, 255, 128), nanovgo.RGBA(255, 255, 255, 0)) 473 | ctx.BeginPath() 474 | ctx.Ellipse(lx, ly, ex, ey) 475 | ctx.SetFillPaint(gloss1) 476 | ctx.Fill() 477 | 478 | gloss2 := nanovgo.RadialGradient(rx-ex*0.25, ry-ey*0.5, ex*0.1, ex*0.75, nanovgo.RGBA(255, 255, 255, 128), nanovgo.RGBA(255, 255, 255, 0)) 479 | ctx.BeginPath() 480 | ctx.Ellipse(rx, ry, ex, ey) 481 | ctx.SetFillPaint(gloss2) 482 | ctx.Fill() 483 | } 484 | 485 | func drawGraph(ctx *nanovgo.Context, x, y, w, h, t float32) { 486 | var sx, sy [6]float32 487 | dx := w / 5.0 488 | 489 | samples := []float32{ 490 | (1 + sinF(t*1.2345+cosF(t*0.33457)*0.44)) * 0.5, 491 | (1 + sinF(t*0.68363+cosF(t*1.3)*1.55)) * 0.5, 492 | (1 + sinF(t*1.1642+cosF(t*0.33457)*1.24)) * 0.5, 493 | (1 + sinF(t*0.56345+cosF(t*1.63)*0.14)) * 0.5, 494 | (1 + sinF(t*1.6245+cosF(t*0.254)*0.3)) * 0.5, 495 | (1 + sinF(t*0.345+cosF(t*0.03)*0.6)) * 0.5, 496 | } 497 | 498 | for i := 0; i < 6; i++ { 499 | sx[i] = x + float32(i)*dx 500 | sy[i] = y + h*samples[i]*0.8 501 | } 502 | 503 | // Graph background 504 | bg := nanovgo.LinearGradient(x, y, x, y+h, nanovgo.RGBA(0, 160, 192, 0), nanovgo.RGBA(0, 160, 192, 64)) 505 | ctx.BeginPath() 506 | ctx.MoveTo(sx[0], sy[0]) 507 | for i := 1; i < 6; i++ { 508 | ctx.BezierTo(sx[i-1]+dx*0.5, sy[i-1], sx[i]-dx*0.5, sy[i], sx[i], sy[i]) 509 | } 510 | ctx.LineTo(x+w, y+h) 511 | ctx.LineTo(x, y+h) 512 | ctx.SetFillPaint(bg) 513 | ctx.Fill() 514 | 515 | // Graph line 516 | ctx.BeginPath() 517 | ctx.MoveTo(sx[0], sy[0]+2) 518 | for i := 1; i < 6; i++ { 519 | ctx.BezierTo(sx[i-1]+dx*0.5, sy[i-1]+2, sx[i]-dx*0.5, sy[i]+2, sx[i], sy[i]+2) 520 | } 521 | ctx.SetStrokeColor(nanovgo.RGBA(0, 0, 0, 32)) 522 | ctx.SetStrokeWidth(3.0) 523 | ctx.Stroke() 524 | 525 | ctx.BeginPath() 526 | ctx.MoveTo(sx[0], sy[0]) 527 | for i := 1; i < 6; i++ { 528 | ctx.BezierTo(sx[i-1]+dx*0.5, sy[i-1], sx[i]-dx*0.5, sy[i], sx[i], sy[i]) 529 | } 530 | ctx.SetStrokeColor(nanovgo.RGBA(0, 160, 192, 255)) 531 | ctx.SetStrokeWidth(3.0) 532 | ctx.Stroke() 533 | 534 | // Graph sample pos 535 | for i := 0; i < 6; i++ { 536 | bg = nanovgo.RadialGradient(sx[i], sy[i]+2, 3.0, 8.0, nanovgo.RGBA(0, 0, 0, 32), nanovgo.RGBA(0, 0, 0, 0)) 537 | ctx.BeginPath() 538 | ctx.Rect(sx[i]-10, sy[i]-10+2, 20, 20) 539 | ctx.SetFillPaint(bg) 540 | ctx.Fill() 541 | } 542 | 543 | ctx.BeginPath() 544 | for i := 0; i < 6; i++ { 545 | ctx.Circle(sx[i], sy[i], 4.0) 546 | } 547 | ctx.SetFillColor(nanovgo.RGBA(0, 160, 192, 255)) 548 | ctx.Fill() 549 | ctx.BeginPath() 550 | for i := 0; i < 6; i++ { 551 | ctx.Circle(sx[i], sy[i], 2.0) 552 | } 553 | ctx.SetFillColor(nanovgo.RGBA(220, 220, 220, 255)) 554 | ctx.Fill() 555 | 556 | ctx.SetStrokeWidth(1.0) 557 | } 558 | 559 | func drawSpinner(ctx *nanovgo.Context, cx, cy, r, t float32) { 560 | a0 := 0.0 + t*6 561 | a1 := nanovgo.PI + t*6 562 | r0 := r 563 | r1 := r * 0.75 564 | 565 | ctx.Save() 566 | defer ctx.Restore() 567 | 568 | ctx.BeginPath() 569 | ctx.Arc(cx, cy, r0, a0, a1, nanovgo.Clockwise) 570 | ctx.Arc(cx, cy, r1, a1, a0, nanovgo.CounterClockwise) 571 | ctx.ClosePath() 572 | ax := cx + cosF(a0)*(r0+r1)*0.5 573 | ay := cy + sinF(a0)*(r0+r1)*0.5 574 | bx := cx + cosF(a1)*(r0+r1)*0.5 575 | by := cy + sinF(a1)*(r0+r1)*0.5 576 | paint := nanovgo.LinearGradient(ax, ay, bx, by, nanovgo.RGBA(0, 0, 0, 0), nanovgo.RGBA(0, 0, 0, 128)) 577 | ctx.SetFillPaint(paint) 578 | ctx.Fill() 579 | } 580 | 581 | func drawThumbnails(ctx *nanovgo.Context, x, y, w, h float32, images []int, t float32) { 582 | var cornerRadius float32 = 3.0 583 | 584 | var thumb float32 = 60.0 585 | var arry float32 = 30.5 586 | stackh := float32(len(images)/2)*(thumb+10) + 10 587 | u := (1 + cosF(t*0.5)) * 0.5 588 | u2 := (1 - cosF(t*0.2)) * 0.5 589 | 590 | ctx.Save() 591 | defer ctx.Restore() 592 | 593 | // Drop shadow 594 | shadowPaint := nanovgo.BoxGradient(x, y+4, w, h, cornerRadius*2, 20, nanovgo.RGBA(0, 0, 0, 128), nanovgo.RGBA(0, 0, 0, 0)) 595 | ctx.BeginPath() 596 | ctx.Rect(x-10, y-10, w+20, h+30) 597 | ctx.RoundedRect(x, y, w, h, cornerRadius) 598 | ctx.PathWinding(nanovgo.Hole) 599 | ctx.SetFillPaint(shadowPaint) 600 | ctx.Fill() 601 | 602 | // Window 603 | ctx.BeginPath() 604 | ctx.RoundedRect(x, y, w, h, cornerRadius) 605 | ctx.MoveTo(x-10, y+arry) 606 | ctx.LineTo(x+1, y+arry-11) 607 | ctx.LineTo(x+1, y+arry+11) 608 | ctx.SetFillColor(nanovgo.RGBA(200, 200, 200, 255)) 609 | ctx.Fill() 610 | 611 | ctx.Block(func() { 612 | ctx.Scissor(x, y, w, h) 613 | ctx.Translate(0, -(stackh-h)*u) 614 | 615 | dv := 1.0 / float32(len(images)-1) 616 | 617 | for i, imageID := range images { 618 | tx := x + 10.0 619 | ty := y + 10.0 620 | tx += float32(i%2) * (thumb + 10.0) 621 | ty += float32(i/2) * (thumb + 10.0) 622 | imgW, imgH, _ := ctx.ImageSize(imageID) 623 | var iw, ih, ix, iy float32 624 | if imgW < imgH { 625 | iw = thumb 626 | ih = iw * float32(imgH) / float32(imgW) 627 | ix = 0 628 | iy = -(ih - thumb) * 0.5 629 | } else { 630 | ih = thumb 631 | iw = ih * float32(imgW) / float32(imgH) 632 | ix = -(iw - thumb) * 0.5 633 | iy = 0 634 | } 635 | 636 | v := float32(i) * dv 637 | a := clampF((u2-v)/dv, 0, 1) 638 | 639 | if a < 1.0 { 640 | drawSpinner(ctx, tx+thumb/2, ty+thumb/2, thumb*0.25, t) 641 | } 642 | 643 | imgPaint := nanovgo.ImagePattern(tx+ix, ty+iy, iw, ih, 0.0/180.0*nanovgo.PI, imageID, a) 644 | ctx.BeginPath() 645 | ctx.RoundedRect(tx, ty, thumb, thumb, 5) 646 | ctx.SetFillPaint(imgPaint) 647 | ctx.Fill() 648 | 649 | shadowPaint := nanovgo.BoxGradient(tx-1, ty, thumb+2, thumb+2, 5, 3, nanovgo.RGBA(0, 0, 0, 128), nanovgo.RGBA(0, 0, 0, 0)) 650 | ctx.BeginPath() 651 | ctx.Rect(tx-5, ty-5, thumb+10, thumb+10) 652 | ctx.RoundedRect(tx, ty, thumb, thumb, 6) 653 | ctx.PathWinding(nanovgo.Hole) 654 | ctx.SetFillPaint(shadowPaint) 655 | ctx.Fill() 656 | 657 | ctx.BeginPath() 658 | ctx.RoundedRect(tx+0.5, ty+0.5, thumb-1, thumb-1, 4-0.5) 659 | ctx.SetStrokeWidth(1.0) 660 | ctx.SetStrokeColor(nanovgo.RGBA(255, 255, 255, 192)) 661 | ctx.Stroke() 662 | } 663 | }) 664 | 665 | // Hide fades 666 | fadePaint := nanovgo.LinearGradient(x, y, x, y+6, nanovgo.RGBA(200, 200, 200, 255), nanovgo.RGBA(200, 200, 200, 0)) 667 | ctx.BeginPath() 668 | ctx.Rect(x+4, y, w-8, 6) 669 | ctx.SetFillPaint(fadePaint) 670 | ctx.Fill() 671 | 672 | fadePaint = nanovgo.LinearGradient(x, y+h, x, y+h-6, nanovgo.RGBA(200, 200, 200, 255), nanovgo.RGBA(200, 200, 200, 0)) 673 | ctx.BeginPath() 674 | ctx.Rect(x+4, y+h-6, w-8, 6) 675 | ctx.SetFillPaint(fadePaint) 676 | ctx.Fill() 677 | 678 | // Scroll bar 679 | shadowPaint = nanovgo.BoxGradient(x+w-12+1, y+4+1, 8, h-8, 3, 4, nanovgo.RGBA(0, 0, 0, 32), nanovgo.RGBA(0, 0, 0, 92)) 680 | ctx.BeginPath() 681 | ctx.RoundedRect(x+w-12, y+4, 8, h-8, 3) 682 | ctx.SetFillPaint(shadowPaint) 683 | // ctx.FillColor(ctx.RGBA(255,0,0,128)); 684 | ctx.Fill() 685 | 686 | scrollH := (h / stackh) * (h - 8) 687 | shadowPaint = nanovgo.BoxGradient(x+w-12-1, y+4+(h-8-scrollH)*u-1, 8, scrollH, 3, 4, nanovgo.RGBA(220, 220, 220, 255), nanovgo.RGBA(128, 128, 128, 255)) 688 | ctx.BeginPath() 689 | ctx.RoundedRect(x+w-12+1, y+4+1+(h-8-scrollH)*u, 8-2, scrollH-2, 2) 690 | ctx.SetFillPaint(shadowPaint) 691 | // ctx.FillColor(ctx.RGBA(0,0,0,128)); 692 | ctx.Fill() 693 | } 694 | 695 | func drawColorWheel(ctx *nanovgo.Context, x, y, w, h, t float32) { 696 | var r0, r1, ax, ay, bx, by, aeps, r float32 697 | hue := sinF(t * 0.12) 698 | 699 | ctx.Save() 700 | defer ctx.Restore() 701 | /* ctx.BeginPath() 702 | ctx.Rect(x,y,w,h) 703 | ctx.FillColor(nanovgo.RGBA(255,0,0,128)) 704 | ctx.Fill()*/ 705 | 706 | cx := x + w*0.5 707 | cy := y + h*0.5 708 | if w < h { 709 | r1 = w*0.5 - 5.0 710 | } else { 711 | r1 = h*0.5 - 5.0 712 | } 713 | r0 = r1 - 20.0 714 | aeps = 0.5 / r1 // half a pixel arc length in radians (2pi cancels out). 715 | 716 | for i := 0; i < 6; i++ { 717 | a0 := float32(i)/6.0*nanovgo.PI*2.0 - aeps 718 | a1 := float32(i+1.0)/6.0*nanovgo.PI*2.0 + aeps 719 | ctx.BeginPath() 720 | ctx.Arc(cx, cy, r0, a0, a1, nanovgo.Clockwise) 721 | ctx.Arc(cx, cy, r1, a1, a0, nanovgo.CounterClockwise) 722 | ctx.ClosePath() 723 | ax = cx + cosF(a0)*(r0+r1)*0.5 724 | ay = cy + sinF(a0)*(r0+r1)*0.5 725 | bx = cx + cosF(a1)*(r0+r1)*0.5 726 | by = cy + sinF(a1)*(r0+r1)*0.5 727 | paint := nanovgo.LinearGradient(ax, ay, bx, by, nanovgo.HSLA(a0/(nanovgo.PI*2), 1.0, 0.55, 255), nanovgo.HSLA(a1/(nanovgo.PI*2), 1.0, 0.55, 255)) 728 | ctx.SetFillPaint(paint) 729 | ctx.Fill() 730 | } 731 | 732 | ctx.BeginPath() 733 | ctx.Circle(cx, cy, r0-0.5) 734 | ctx.Circle(cx, cy, r1+0.5) 735 | ctx.SetStrokeColor(nanovgo.RGBA(0, 0, 0, 64)) 736 | ctx.SetStrokeWidth(1.0) 737 | ctx.Stroke() 738 | 739 | // Selector 740 | ctx.Translate(cx, cy) 741 | ctx.Rotate(hue * nanovgo.PI * 2) 742 | 743 | // Marker on 744 | ctx.SetStrokeWidth(2.0) 745 | ctx.BeginPath() 746 | ctx.Rect(r0-1, -3, r1-r0+2, 6) 747 | ctx.SetStrokeColor(nanovgo.RGBA(255, 255, 255, 192)) 748 | ctx.Stroke() 749 | 750 | paint := nanovgo.BoxGradient(r0-3, -5, r1-r0+6, 10, 2, 4, nanovgo.RGBA(0, 0, 0, 128), nanovgo.RGBA(0, 0, 0, 0)) 751 | ctx.BeginPath() 752 | ctx.Rect(r0-2-10, -4-10, r1-r0+4+20, 8+20) 753 | ctx.Rect(r0-2, -4, r1-r0+4, 8) 754 | ctx.PathWinding(nanovgo.Hole) 755 | ctx.SetFillPaint(paint) 756 | ctx.Fill() 757 | 758 | // Center triangle 759 | r = r0 - 6 760 | ax = cosF(120.0/180.0*nanovgo.PI) * r 761 | ay = sinF(120.0/180.0*nanovgo.PI) * r 762 | bx = cosF(-120.0/180.0*nanovgo.PI) * r 763 | by = sinF(-120.0/180.0*nanovgo.PI) * r 764 | ctx.BeginPath() 765 | ctx.MoveTo(r, 0) 766 | ctx.LineTo(ax, ay) 767 | ctx.LineTo(bx, by) 768 | ctx.ClosePath() 769 | paint = nanovgo.LinearGradient(r, 0, ax, ay, nanovgo.HSLA(hue, 1.0, 0.5, 255), nanovgo.RGBA(255, 255, 255, 255)) 770 | ctx.SetFillPaint(paint) 771 | ctx.Fill() 772 | paint = nanovgo.LinearGradient((r+ax)*0.5, (0+ay)*0.5, bx, by, nanovgo.RGBA(0, 0, 0, 0), nanovgo.RGBA(0, 0, 0, 255)) 773 | ctx.SetFillPaint(paint) 774 | ctx.Fill() 775 | ctx.SetStrokeColor(nanovgo.RGBA(0, 0, 0, 64)) 776 | ctx.Stroke() 777 | 778 | // Select circle on triangle 779 | ax = cosF(120.0/180.0*nanovgo.PI) * r * 0.3 780 | ay = sinF(120.0/180.0*nanovgo.PI) * r * 0.4 781 | ctx.SetStrokeWidth(2.0) 782 | ctx.BeginPath() 783 | ctx.Circle(ax, ay, 5) 784 | ctx.SetStrokeColor(nanovgo.RGBA(255, 255, 255, 192)) 785 | ctx.Stroke() 786 | 787 | paint = nanovgo.RadialGradient(ax, ay, 7, 9, nanovgo.RGBA(0, 0, 0, 64), nanovgo.RGBA(0, 0, 0, 0)) 788 | ctx.BeginPath() 789 | ctx.Rect(ax-20, ay-20, 40, 40) 790 | ctx.Circle(ax, ay, 7) 791 | ctx.PathWinding(nanovgo.Hole) 792 | ctx.SetFillPaint(paint) 793 | ctx.Fill() 794 | } 795 | 796 | func drawLines(ctx *nanovgo.Context, x, y, w, h, t float32) { 797 | var pad float32 = 5.0 798 | s := w/9.0 - pad*2 799 | joins := []nanovgo.LineCap{nanovgo.Miter, nanovgo.Round, nanovgo.Bevel} 800 | caps := []nanovgo.LineCap{nanovgo.Butt, nanovgo.Round, nanovgo.Square} 801 | 802 | ctx.Save() 803 | defer ctx.Restore() 804 | 805 | pts := []float32{ 806 | -s*0.25 + cosF(t*0.3)*s*0.5, 807 | sinF(t*0.3) * s * 0.5, 808 | -s * 0.25, 809 | 0, 810 | s * 0.25, 811 | 0, 812 | s*0.25 + cosF(-t*0.3)*s*0.5, 813 | sinF(-t*0.3) * s * 0.5, 814 | } 815 | for i, cap := range caps { 816 | for j, join := range joins { 817 | fx := x + s*0.5 + float32(i*3+j)/9.0*w + pad 818 | fy := y - s*0.5 + pad 819 | 820 | ctx.SetLineCap(cap) 821 | ctx.SetLineJoin(join) 822 | 823 | ctx.SetStrokeWidth(s * 0.3) 824 | ctx.SetStrokeColor(nanovgo.RGBA(0, 0, 0, 160)) 825 | ctx.BeginPath() 826 | ctx.MoveTo(fx+pts[0], fy+pts[1]) 827 | ctx.LineTo(fx+pts[2], fy+pts[3]) 828 | ctx.LineTo(fx+pts[4], fy+pts[5]) 829 | ctx.LineTo(fx+pts[6], fy+pts[7]) 830 | ctx.Stroke() 831 | 832 | ctx.SetLineCap(nanovgo.Butt) 833 | ctx.SetLineJoin(nanovgo.Bevel) 834 | 835 | ctx.SetStrokeWidth(1.0) 836 | ctx.SetStrokeColor(nanovgo.RGBA(0, 192, 255, 255)) 837 | ctx.BeginPath() 838 | ctx.MoveTo(fx+pts[0], fy+pts[1]) 839 | ctx.LineTo(fx+pts[2], fy+pts[3]) 840 | ctx.LineTo(fx+pts[4], fy+pts[5]) 841 | ctx.LineTo(fx+pts[6], fy+pts[7]) 842 | ctx.Stroke() 843 | 844 | } 845 | } 846 | } 847 | 848 | func drawWidths(ctx *nanovgo.Context, x, y, width float32) { 849 | ctx.Save() 850 | defer ctx.Restore() 851 | 852 | ctx.SetStrokeColor(nanovgo.RGBA(0, 0, 0, 255)) 853 | for i := 0; i < 20; i++ { 854 | w := (float32(i) + 0.5) * 0.1 855 | ctx.SetStrokeWidth(w) 856 | ctx.BeginPath() 857 | ctx.MoveTo(x, y) 858 | ctx.LineTo(x+width, y+width*0.3) 859 | ctx.Stroke() 860 | y += 10 861 | } 862 | } 863 | 864 | func drawCaps(ctx *nanovgo.Context, x, y, width float32) { 865 | caps := []nanovgo.LineCap{nanovgo.Butt, nanovgo.Round, nanovgo.Square} 866 | var lineWidth float32 = 8.0 867 | 868 | ctx.Save() 869 | defer ctx.Restore() 870 | 871 | ctx.BeginPath() 872 | ctx.Rect(x-lineWidth/2, y, width+lineWidth, 40) 873 | ctx.SetFillColor(nanovgo.RGBA(255, 255, 255, 32)) 874 | ctx.Fill() 875 | 876 | ctx.BeginPath() 877 | ctx.Rect(x, y, width, 40) 878 | ctx.SetFillColor(nanovgo.RGBA(255, 255, 255, 32)) 879 | ctx.Fill() 880 | 881 | ctx.SetStrokeWidth(lineWidth) 882 | 883 | for i, cap := range caps { 884 | ctx.SetLineCap(cap) 885 | ctx.SetStrokeColor(nanovgo.RGBA(0, 0, 0, 255)) 886 | ctx.BeginPath() 887 | ctx.MoveTo(x, y+float32(i)*10+5) 888 | ctx.LineTo(x+width, y+float32(i)*10+5) 889 | ctx.Stroke() 890 | } 891 | } 892 | 893 | func drawParagraph(ctx *nanovgo.Context, x, y, width, height, mx, my float32) { 894 | text := "This is longer chunk of text.\n \n Would have used lorem ipsum but she was busy jumping over the lazy dog with the fox and all the men who came to the aid of the party." 895 | 896 | ctx.Save() 897 | defer ctx.Restore() 898 | 899 | ctx.SetFontSize(18.0) 900 | ctx.SetFontFace("sans") 901 | ctx.SetTextAlign(nanovgo.AlignLeft | nanovgo.AlignTop) 902 | _, _, lineh := ctx.TextMetrics() 903 | // The text break API can be used to fill a large buffer of rows, 904 | // or to iterate over the text just few lines (or just one) at a time. 905 | // The "next" variable of the last returned item tells where to continue. 906 | runes := []rune(text) 907 | 908 | var gx, gy float32 909 | var gutter int 910 | lnum := 0 911 | 912 | for _, row := range ctx.TextBreakLinesRune(runes, width) { 913 | hit := mx > x && mx < (x+width) && my >= y && my < (y+lineh) 914 | 915 | ctx.BeginPath() 916 | var alpha uint8 917 | if hit { 918 | alpha = 64 919 | } else { 920 | alpha = 16 921 | } 922 | ctx.SetFillColor(nanovgo.RGBA(255, 255, 255, alpha)) 923 | ctx.Rect(x, y, row.Width, lineh) 924 | ctx.Fill() 925 | 926 | ctx.SetFillColor(nanovgo.RGBA(255, 255, 255, 255)) 927 | ctx.TextRune(x, y, runes[row.StartIndex:row.EndIndex]) 928 | 929 | if hit { 930 | var caretX float32 931 | if mx < x+row.Width/2 { 932 | caretX = x 933 | } else { 934 | caretX = x + row.Width 935 | } 936 | px := x 937 | lineRune := runes[row.StartIndex:row.EndIndex] 938 | glyphs := ctx.TextGlyphPositionsRune(x, y, lineRune) 939 | for j, glyph := range glyphs { 940 | x0 := glyph.X 941 | var x1 float32 942 | if j+1 < len(glyphs) { 943 | x1 = glyphs[j+1].X 944 | } else { 945 | x1 = x + row.Width 946 | } 947 | gx = x0*0.3 + x1*0.7 948 | if mx >= px && mx < gx { 949 | caretX = glyph.X 950 | } 951 | px = gx 952 | } 953 | ctx.BeginPath() 954 | ctx.SetFillColor(nanovgo.RGBA(255, 192, 0, 255)) 955 | ctx.Rect(caretX, y, 1, lineh) 956 | ctx.Fill() 957 | 958 | gutter = lnum + 1 959 | gx = x - 10 960 | gy = y + lineh/2 961 | } 962 | lnum++ 963 | y += lineh 964 | } 965 | 966 | if gutter > 0 { 967 | txt := strconv.Itoa(gutter) 968 | 969 | ctx.SetFontSize(13.0) 970 | ctx.SetTextAlign(nanovgo.AlignRight | nanovgo.AlignMiddle) 971 | 972 | _, bounds := ctx.TextBounds(gx, gy, txt) 973 | 974 | ctx.BeginPath() 975 | ctx.SetFillColor(nanovgo.RGBA(255, 192, 0, 255)) 976 | ctx.RoundedRect( 977 | float32(int(bounds[0]-4)), 978 | float32(int(bounds[1]-2)), 979 | float32(int(bounds[2]-bounds[0])+8), 980 | float32(int(bounds[3]-bounds[1])+4), 981 | float32(int(bounds[3]-bounds[1])+4)/2.0-1.0) 982 | ctx.Fill() 983 | 984 | ctx.SetFillColor(nanovgo.RGBA(32, 32, 32, 255)) 985 | ctx.Text(gx, gy, txt) 986 | } 987 | 988 | y += 20.0 989 | 990 | ctx.SetFontSize(13.0) 991 | ctx.SetTextAlign(nanovgo.AlignLeft | nanovgo.AlignTop) 992 | ctx.SetTextLineHeight(1.2) 993 | 994 | bounds := ctx.TextBoxBounds(x, y, 150, "Hover your mouse over the text to see calculated caret position.") 995 | 996 | // Fade the tooltip out when close to it. 997 | gx = absF((mx - (bounds[0]+bounds[2])*0.5) / (bounds[0] - bounds[2])) 998 | gy = absF((my - (bounds[1]+bounds[3])*0.5) / (bounds[1] - bounds[3])) 999 | a := maxF(gx, gy) - 0.5 1000 | a = clampF(a, 0, 1) 1001 | ctx.SetGlobalAlpha(a) 1002 | 1003 | ctx.BeginPath() 1004 | ctx.SetFillColor(nanovgo.RGBA(220, 220, 220, 255)) 1005 | ctx.RoundedRect(bounds[0]-2, bounds[1]-2, float32(int(bounds[2]-bounds[0])+4), float32(int(bounds[3]-bounds[1])+4), 3) 1006 | px := float32(int((bounds[2] + bounds[0]) / 2)) 1007 | ctx.MoveTo(px, bounds[1]-10) 1008 | ctx.LineTo(px+7, bounds[1]+1) 1009 | ctx.LineTo(px-7, bounds[1]+1) 1010 | ctx.Fill() 1011 | 1012 | ctx.SetFillColor(nanovgo.RGBA(0, 0, 0, 220)) 1013 | ctx.TextBox(x, y, 150, "Hover your mouse over the text to see calculated caret position.") 1014 | } 1015 | 1016 | func drawScissor(ctx *nanovgo.Context, x, y, t float32) { 1017 | ctx.Save() 1018 | 1019 | // Draw first rect and set scissor to it's area. 1020 | ctx.Translate(x, y) 1021 | ctx.Rotate(nanovgo.DegToRad(5)) 1022 | ctx.BeginPath() 1023 | ctx.Rect(-20, -20, 60, 40) 1024 | ctx.SetFillColor(nanovgo.RGBA(255, 0, 0, 255)) 1025 | ctx.Fill() 1026 | ctx.Scissor(-20, -20, 60, 40) 1027 | 1028 | // Draw second rectangle with offset and rotation. 1029 | ctx.Translate(40, 0) 1030 | ctx.Rotate(t) 1031 | 1032 | // Draw the intended second rectangle without any scissoring. 1033 | ctx.Save() 1034 | ctx.ResetScissor() 1035 | ctx.BeginPath() 1036 | ctx.Rect(-20, -10, 60, 30) 1037 | ctx.SetFillColor(nanovgo.RGBA(255, 128, 0, 64)) 1038 | ctx.Fill() 1039 | ctx.Restore() 1040 | 1041 | // Draw second rectangle with combined scissoring. 1042 | ctx.IntersectScissor(-20, -10, 60, 30) 1043 | ctx.BeginPath() 1044 | ctx.Rect(-20, -10, 60, 30) 1045 | ctx.SetFillColor(nanovgo.RGBA(255, 128, 0, 255)) 1046 | ctx.Fill() 1047 | 1048 | ctx.Restore() 1049 | } 1050 | -------------------------------------------------------------------------------- /sample/entypo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/nanovgo/9141d09b3652b251464e87c714b6192b9a740ffc/sample/entypo.ttf -------------------------------------------------------------------------------- /sample/images/image1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/nanovgo/9141d09b3652b251464e87c714b6192b9a740ffc/sample/images/image1.jpg -------------------------------------------------------------------------------- /sample/images/image10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/nanovgo/9141d09b3652b251464e87c714b6192b9a740ffc/sample/images/image10.jpg -------------------------------------------------------------------------------- /sample/images/image11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/nanovgo/9141d09b3652b251464e87c714b6192b9a740ffc/sample/images/image11.jpg -------------------------------------------------------------------------------- /sample/images/image12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/nanovgo/9141d09b3652b251464e87c714b6192b9a740ffc/sample/images/image12.jpg -------------------------------------------------------------------------------- /sample/images/image2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/nanovgo/9141d09b3652b251464e87c714b6192b9a740ffc/sample/images/image2.jpg -------------------------------------------------------------------------------- /sample/images/image3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/nanovgo/9141d09b3652b251464e87c714b6192b9a740ffc/sample/images/image3.jpg -------------------------------------------------------------------------------- /sample/images/image4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/nanovgo/9141d09b3652b251464e87c714b6192b9a740ffc/sample/images/image4.jpg -------------------------------------------------------------------------------- /sample/images/image5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/nanovgo/9141d09b3652b251464e87c714b6192b9a740ffc/sample/images/image5.jpg -------------------------------------------------------------------------------- /sample/images/image6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/nanovgo/9141d09b3652b251464e87c714b6192b9a740ffc/sample/images/image6.jpg -------------------------------------------------------------------------------- /sample/images/image7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/nanovgo/9141d09b3652b251464e87c714b6192b9a740ffc/sample/images/image7.jpg -------------------------------------------------------------------------------- /sample/images/image8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/nanovgo/9141d09b3652b251464e87c714b6192b9a740ffc/sample/images/image8.jpg -------------------------------------------------------------------------------- /sample/images/image9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibukawa/nanovgo/9141d09b3652b251464e87c714b6192b9a740ffc/sample/images/image9.jpg -------------------------------------------------------------------------------- /sample/sample.go: -------------------------------------------------------------------------------- 1 | // +build !js 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "github.com/goxjs/gl" 8 | "github.com/goxjs/glfw" 9 | "github.com/shibukawa/nanovgo" 10 | "github.com/shibukawa/nanovgo/perfgraph" 11 | "github.com/shibukawa/nanovgo/sample/demo" 12 | "log" 13 | ) 14 | 15 | var blowup bool 16 | var premult bool 17 | 18 | func key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { 19 | if key == glfw.KeyEscape && action == glfw.Press { 20 | w.SetShouldClose(true) 21 | } else if key == glfw.KeySpace && action == glfw.Press { 22 | blowup = !blowup 23 | } else if key == glfw.KeyP && action == glfw.Press { 24 | premult = !premult 25 | } 26 | } 27 | 28 | func main() { 29 | err := glfw.Init(gl.ContextWatcher) 30 | if err != nil { 31 | panic(err) 32 | } 33 | defer glfw.Terminate() 34 | 35 | // demo MSAA 36 | glfw.WindowHint(glfw.Samples, 4) 37 | 38 | window, err := glfw.CreateWindow(1000, 600, "NanoVGo", nil, nil) 39 | if err != nil { 40 | panic(err) 41 | } 42 | window.SetKeyCallback(key) 43 | window.MakeContextCurrent() 44 | 45 | ctx, err := nanovgo.NewContext(0 /*nanovgo.AntiAlias | nanovgo.StencilStrokes | nanovgo.Debug*/) 46 | defer ctx.Delete() 47 | 48 | if err != nil { 49 | panic(err) 50 | } 51 | 52 | demoData := LoadDemo(ctx) 53 | 54 | glfw.SwapInterval(0) 55 | 56 | fps := perfgraph.NewPerfGraph("Frame Time", "sans") 57 | 58 | for !window.ShouldClose() { 59 | t, _ := fps.UpdateGraph() 60 | 61 | fbWidth, fbHeight := window.GetFramebufferSize() 62 | winWidth, winHeight := window.GetSize() 63 | mx, my := window.GetCursorPos() 64 | 65 | pixelRatio := float32(fbWidth) / float32(winWidth) 66 | gl.Viewport(0, 0, fbWidth, fbHeight) 67 | if premult { 68 | gl.ClearColor(0, 0, 0, 0) 69 | } else { 70 | gl.ClearColor(0.3, 0.3, 0.32, 1.0) 71 | } 72 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT) 73 | gl.Enable(gl.BLEND) 74 | gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) 75 | gl.Enable(gl.CULL_FACE) 76 | gl.Disable(gl.DEPTH_TEST) 77 | 78 | ctx.BeginFrame(winWidth, winHeight, pixelRatio) 79 | 80 | demo.RenderDemo(ctx, float32(mx), float32(my), float32(winWidth), float32(winHeight), t, blowup, demoData) 81 | fps.RenderGraph(ctx, 5, 5) 82 | 83 | ctx.EndFrame() 84 | 85 | gl.Enable(gl.DEPTH_TEST) 86 | window.SwapBuffers() 87 | glfw.PollEvents() 88 | } 89 | 90 | demoData.FreeData(ctx) 91 | } 92 | 93 | func LoadDemo(ctx *nanovgo.Context) *demo.DemoData { 94 | d := &demo.DemoData{} 95 | for i := 0; i < 12; i++ { 96 | path := fmt.Sprintf("images/image%d.jpg", i+1) 97 | d.Images = append(d.Images, ctx.CreateImage(path, 0)) 98 | if d.Images[i] == 0 { 99 | log.Fatalf("Could not load %s", path) 100 | } 101 | } 102 | 103 | d.FontIcons = ctx.CreateFont("icons", "entypo.ttf") 104 | if d.FontIcons == -1 { 105 | log.Fatalln("Could not add font icons.") 106 | } 107 | d.FontNormal = ctx.CreateFont("sans", "Roboto-Regular.ttf") 108 | if d.FontNormal == -1 { 109 | log.Fatalln("Could not add font italic.") 110 | } 111 | d.FontBold = ctx.CreateFont("sans-bold", "Roboto-Bold.ttf") 112 | if d.FontBold == -1 { 113 | log.Fatalln("Could not add font bold.") 114 | } 115 | return d 116 | } 117 | -------------------------------------------------------------------------------- /sample/web.go: -------------------------------------------------------------------------------- 1 | // +build js 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "github.com/goxjs/gl" 8 | "github.com/goxjs/glfw" 9 | "github.com/shibukawa/nanovgo" 10 | "github.com/shibukawa/nanovgo/perfgraph" 11 | "github.com/shibukawa/nanovgo/sample/demo" 12 | "io/ioutil" 13 | "log" 14 | "net/http" 15 | ) 16 | 17 | var blowup bool 18 | var premult bool 19 | 20 | func key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { 21 | if key == glfw.KeyEscape && action == glfw.Press { 22 | w.SetShouldClose(true) 23 | } else if key == glfw.KeySpace && action == glfw.Press { 24 | blowup = !blowup 25 | } else if key == glfw.KeyP && action == glfw.Press { 26 | premult = !premult 27 | } 28 | } 29 | 30 | func main() { 31 | err := glfw.Init(gl.ContextWatcher) 32 | if err != nil { 33 | panic(err) 34 | } 35 | defer glfw.Terminate() 36 | 37 | glfw.WindowHint(glfw.StencilBits, 1) 38 | glfw.WindowHint(glfw.Samples, 4) 39 | 40 | window, err := glfw.CreateWindow(1000*0.6, 600*0.6, "NanoVGo", nil, nil) 41 | if err != nil { 42 | panic(err) 43 | } 44 | window.SetKeyCallback(key) 45 | window.MakeContextCurrent() 46 | 47 | ctx, err := nanovgo.NewContext(0) 48 | defer ctx.Delete() 49 | 50 | if err != nil { 51 | panic(err) 52 | } 53 | 54 | demoData := LoadDemo(ctx) 55 | 56 | glfw.SwapInterval(0) 57 | 58 | fps := perfgraph.NewPerfGraph("Frame Time", "sans") 59 | 60 | for !window.ShouldClose() { 61 | t, _ := fps.UpdateGraph() 62 | 63 | fbWidth, fbHeight := window.GetFramebufferSize() 64 | winWidth, winHeight := window.GetSize() 65 | mx, my := window.GetCursorPos() 66 | 67 | pixelRatio := float32(fbWidth) / float32(winWidth) 68 | gl.Viewport(0, 0, fbWidth, fbHeight) 69 | if premult { 70 | gl.ClearColor(0, 0, 0, 0) 71 | } else { 72 | gl.ClearColor(0.3, 0.3, 0.32, 1.0) 73 | } 74 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT) 75 | gl.Enable(gl.BLEND) 76 | gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) 77 | gl.Enable(gl.CULL_FACE) 78 | gl.Disable(gl.DEPTH_TEST) 79 | 80 | ctx.BeginFrame(winWidth, winHeight, pixelRatio) 81 | 82 | demo.RenderDemo(ctx, float32(mx), float32(my), float32(winWidth), float32(winHeight), t, blowup, demoData) 83 | fps.RenderGraph(ctx, 5, 5) 84 | 85 | ctx.EndFrame() 86 | 87 | gl.Enable(gl.DEPTH_TEST) 88 | window.SwapBuffers() 89 | glfw.PollEvents() 90 | } 91 | 92 | demoData.FreeData(ctx) 93 | } 94 | 95 | func LoadDemo(ctx *nanovgo.Context) *demo.DemoData { 96 | d := &demo.DemoData{} 97 | for i := 0; i < 12; i++ { 98 | path := fmt.Sprintf("assets/image%d.jpg", i+1) 99 | d.Images = append(d.Images, ctx.CreateImageFromMemory(0, demo.MustAsset(path))) 100 | if d.Images[i] == 0 { 101 | log.Fatalf("Could not load %s", path) 102 | } 103 | } 104 | 105 | d.FontIcons = ctx.CreateFontFromMemory("icons", demo.MustAsset("assets/entypo.ttf"), 0) 106 | if d.FontIcons == -1 { 107 | log.Fatalln("Could not add font icons.") 108 | } 109 | d.FontNormal = ctx.CreateFontFromMemory("sans", demo.MustAsset("assets/Roboto-Regular.ttf"), 0) 110 | if d.FontNormal == -1 { 111 | log.Fatalln("Could not add font italic.") 112 | } 113 | d.FontBold = ctx.CreateFontFromMemory("sans-bold", demo.MustAsset("assets/Roboto-Bold.ttf"), 0) 114 | if d.FontBold == -1 { 115 | log.Fatalln("Could not add font bold.") 116 | } 117 | return d 118 | } 119 | 120 | func readFile(path string) ([]byte, error) { 121 | resp, err := http.Get("/nanovgo/" + path) 122 | if err != nil { 123 | log.Fatal(err) 124 | } 125 | log.Println("/nanovgo/" + path + ": " + resp.Status) 126 | defer resp.Body.Close() 127 | return ioutil.ReadAll(resp.Body) 128 | } 129 | -------------------------------------------------------------------------------- /structs.go: -------------------------------------------------------------------------------- 1 | package nanovgo 2 | 3 | import ( 4 | "github.com/shibukawa/nanovgo/fontstashmini" 5 | ) 6 | 7 | type nvgParams interface { 8 | edgeAntiAlias() bool 9 | renderCreate() error 10 | renderCreateTexture(texType nvgTextureType, w, h int, flags ImageFlags, data []byte) int 11 | renderDeleteTexture(image int) error 12 | renderUpdateTexture(image, x, y, w, h int, data []byte) error 13 | renderGetTextureSize(image int) (int, int, error) 14 | renderViewport(width, height int) 15 | renderCancel() 16 | renderFlush() 17 | renderFill(paint *Paint, scissor *nvgScissor, fringe float32, bounds [4]float32, paths []nvgPath) 18 | renderStroke(paint *Paint, scissor *nvgScissor, fringe float32, strokeWidth float32, paths []nvgPath) 19 | renderTriangles(paint *Paint, scissor *nvgScissor, vertexes []nvgVertex) 20 | renderTriangleStrip(paint *Paint, scissor *nvgScissor, vertexes []nvgVertex) 21 | renderDelete() 22 | } 23 | 24 | type nvgPoint struct { 25 | x, y float32 26 | dx, dy float32 27 | len float32 28 | dmx, dmy float32 29 | flags nvgPointFlags 30 | } 31 | 32 | type nvgVertex struct { 33 | x, y, u, v float32 34 | } 35 | 36 | func (vtx *nvgVertex) set(x, y, u, v float32) { 37 | vtx.x = x 38 | vtx.y = y 39 | vtx.u = u 40 | vtx.v = v 41 | } 42 | 43 | type nvgPath struct { 44 | first int 45 | count int 46 | closed bool 47 | nBevel int 48 | fills []nvgVertex 49 | strokes []nvgVertex 50 | winding Winding 51 | convex bool 52 | } 53 | 54 | type nvgScissor struct { 55 | xform TransformMatrix 56 | extent [2]float32 57 | } 58 | 59 | type nvgState struct { 60 | fill, stroke Paint 61 | strokeWidth float32 62 | miterLimit float32 63 | lineJoin LineCap 64 | lineCap LineCap 65 | alpha float32 66 | xform TransformMatrix 67 | scissor nvgScissor 68 | fontSize float32 69 | letterSpacing float32 70 | lineHeight float32 71 | fontBlur float32 72 | textAlign Align 73 | fontID int 74 | } 75 | 76 | func (s *nvgState) reset() { 77 | s.fill.setPaintColor(RGBA(255, 255, 255, 255)) 78 | s.stroke.setPaintColor(RGBA(0, 0, 0, 255)) 79 | s.strokeWidth = 1.0 80 | s.miterLimit = 10.0 81 | s.lineCap = Butt 82 | s.lineJoin = Miter 83 | s.alpha = 1.0 84 | s.xform = IdentityMatrix() 85 | s.scissor.xform = IdentityMatrix() 86 | s.scissor.xform[0] = 0.0 87 | s.scissor.xform[3] = 0.0 88 | s.scissor.extent[0] = -1.0 89 | s.scissor.extent[1] = -1.0 90 | 91 | s.fontSize = 16.0 92 | s.letterSpacing = 0.0 93 | s.lineHeight = 1.0 94 | s.fontBlur = 0.0 95 | s.textAlign = AlignLeft | AlignBaseline 96 | s.fontID = fontstashmini.INVALID 97 | } 98 | 99 | func (s *nvgState) getFontScale() float32 { 100 | return minF(quantize(s.xform.getAverageScale(), 0.01), 4.0) 101 | } 102 | 103 | type nvgPathCache struct { 104 | points []nvgPoint 105 | paths []nvgPath 106 | vertexes []nvgVertex 107 | bounds [4]float32 108 | } 109 | 110 | func (c *nvgPathCache) allocVertexes(n int) []nvgVertex { 111 | offset := len(c.vertexes) 112 | c.vertexes = append(c.vertexes, make([]nvgVertex, n)...) 113 | return c.vertexes[offset:] 114 | } 115 | 116 | func (c *nvgPathCache) clearPathCache() { 117 | c.points = c.points[:0] 118 | c.paths = c.paths[:0] 119 | c.vertexes = c.vertexes[:0] 120 | } 121 | 122 | func (c *nvgPathCache) lastPath() *nvgPath { 123 | if len(c.paths) > 0 { 124 | return &c.paths[len(c.paths)-1] 125 | } 126 | return nil 127 | } 128 | 129 | func (c *nvgPathCache) addPath() { 130 | c.paths = append(c.paths, nvgPath{first: len(c.points), winding: Solid}) 131 | } 132 | 133 | func (c *nvgPathCache) lastPoint() *nvgPoint { 134 | if len(c.points) > 0 { 135 | return &c.points[len(c.points)-1] 136 | } 137 | return nil 138 | } 139 | 140 | func (c *nvgPathCache) addPoint(x, y float32, flags nvgPointFlags, distTol float32) { 141 | path := c.lastPath() 142 | 143 | if path.count > 0 && len(c.points) > 0 { 144 | lastPoint := c.lastPoint() 145 | if ptEquals(lastPoint.x, lastPoint.y, x, y, distTol) { 146 | lastPoint.flags |= flags 147 | return 148 | } 149 | } 150 | 151 | c.points = append(c.points, nvgPoint{ 152 | x: x, 153 | y: y, 154 | dx: 0, 155 | dy: 0, 156 | len: 0, 157 | dmx: 0, 158 | dmy: 0, 159 | flags: flags, 160 | }) 161 | path.count++ 162 | } 163 | 164 | func (c *nvgPathCache) closePath() { 165 | path := c.lastPath() 166 | if path != nil { 167 | path.closed = true 168 | } 169 | } 170 | 171 | func (c *nvgPathCache) pathWinding(winding Winding) { 172 | path := c.lastPath() 173 | if path != nil { 174 | path.winding = winding 175 | } 176 | } 177 | 178 | func (c *nvgPathCache) tesselateBezier(x1, y1, x2, y2, x3, y3, x4, y4 float32, level int, flags nvgPointFlags, tessTol, distTol float32) { 179 | if level > 10 { 180 | return 181 | } 182 | dx := x4 - x1 183 | dy := y4 - y1 184 | d2 := absF(((x2-x4)*dy - (y2-y4)*dx)) 185 | d3 := absF(((x3-x4)*dy - (y3-y4)*dx)) 186 | 187 | if (d2+d3)*(d2+d3) < tessTol*(dx*dx+dy*dy) { 188 | c.addPoint(x4, y4, flags, distTol) 189 | return 190 | } 191 | 192 | x12 := (x1 + x2) * 0.5 193 | y12 := (y1 + y2) * 0.5 194 | x23 := (x2 + x3) * 0.5 195 | y23 := (y2 + y3) * 0.5 196 | x34 := (x3 + x4) * 0.5 197 | y34 := (y3 + y4) * 0.5 198 | x123 := (x12 + x23) * 0.5 199 | y123 := (y12 + y23) * 0.5 200 | x234 := (x23 + x34) * 0.5 201 | y234 := (y23 + y34) * 0.5 202 | x1234 := (x123 + x234) * 0.5 203 | y1234 := (y123 + y234) * 0.5 204 | c.tesselateBezier(x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0, tessTol, distTol) 205 | c.tesselateBezier(x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, flags, tessTol, distTol) 206 | } 207 | 208 | func (c *nvgPathCache) calculateJoins(w float32, lineJoin LineCap, miterLimit float32) { 209 | var iw float32 210 | if w > 0.0 { 211 | iw = 1.0 / w 212 | } 213 | // Calculate which joins needs extra vertices to append, and gather vertex count. 214 | for i := 0; i < len(c.paths); i++ { 215 | path := &c.paths[i] 216 | points := c.points[path.first:] 217 | p0 := &points[path.count-1] 218 | p1 := &points[0] 219 | nLeft := 0 220 | path.nBevel = 0 221 | p1Index := 0 222 | 223 | for j := 0; j < path.count; j++ { 224 | dlx0 := p0.dy 225 | dly0 := -p0.dx 226 | dlx1 := p1.dy 227 | dly1 := -p1.dx 228 | 229 | // Calculate extrusions 230 | p1.dmx = (dlx0 + dlx1) * 0.5 231 | p1.dmy = (dly0 + dly1) * 0.5 232 | dmr2 := p1.dmx*p1.dmx + p1.dmy*p1.dmy 233 | if dmr2 > 0.000001 { 234 | scale := minF(1.0/dmr2, 600.0) 235 | p1.dmx *= scale 236 | p1.dmy *= scale 237 | } 238 | 239 | // Clear flags, but keep the corner. 240 | if p1.flags&nvgPtCORNER != 0 { 241 | p1.flags = nvgPtCORNER 242 | } else { 243 | p1.flags = 0 244 | } 245 | 246 | // Keep track of left turns. 247 | cross := p1.dx*p0.dy - p0.dx*p1.dy 248 | if cross > 0.0 { 249 | nLeft++ 250 | p1.flags |= nvgPtLEFT 251 | } 252 | 253 | // Calculate if we should use bevel or miter for inner join. 254 | limit := maxF(1.0, minF(p0.len, p1.len)*iw) 255 | if dmr2*limit*limit < 1.0 { 256 | p1.flags |= nvgPrINNERBEVEL 257 | } 258 | 259 | // Check to see if the corner needs to be beveled. 260 | if p1.flags&nvgPtCORNER != 0 { 261 | if dmr2*miterLimit*miterLimit < 1.0 || lineJoin == Bevel || lineJoin == Round { 262 | p1.flags |= nvgPtBEVEL 263 | } 264 | } 265 | 266 | if p1.flags&(nvgPtBEVEL|nvgPrINNERBEVEL) != 0 { 267 | path.nBevel++ 268 | } 269 | 270 | p1Index++ 271 | p0 = p1 272 | if len(points) != p1Index { 273 | p1 = &points[p1Index] 274 | } 275 | } 276 | path.convex = (nLeft == path.count) 277 | } 278 | } 279 | 280 | func (c *nvgPathCache) expandStroke(w float32, lineCap, lineJoin LineCap, miterLimit, fringeWidth, tessTol float32) { 281 | aa := fringeWidth 282 | // Calculate divisions per half circle. 283 | nCap := curveDivs(w, PI, tessTol) 284 | c.calculateJoins(w, lineJoin, miterLimit) 285 | 286 | // Calculate max vertex usage. 287 | countVertex := 0 288 | for i := 0; i < len(c.paths); i++ { 289 | path := &c.paths[i] 290 | if lineJoin == Round { 291 | countVertex += (path.count + path.nBevel*(nCap+2) + 1) * 2 // plus one for loop 292 | } else { 293 | countVertex += (path.count + path.nBevel*5 + 1) * 2 // plus one for loop 294 | } 295 | if !path.closed { 296 | // space for caps 297 | if lineCap == Round { 298 | countVertex += (nCap*2 + 2) * 2 299 | } else { 300 | countVertex += (3 + 3) * 2 301 | } 302 | } 303 | } 304 | 305 | dst := c.allocVertexes(countVertex) 306 | 307 | for i := 0; i < len(c.paths); i++ { 308 | path := &c.paths[i] 309 | points := c.points[path.first:] 310 | 311 | path.fills = path.fills[:0] 312 | 313 | // Calculate fringe or stroke 314 | index := 0 315 | var p0, p1 *nvgPoint 316 | var s, e, p1Index int 317 | 318 | if path.closed { 319 | // Looping 320 | p0 = &points[path.count-1] 321 | p1 = &points[0] 322 | s = 0 323 | e = path.count 324 | p1Index = 0 325 | } else { 326 | // Add cap 327 | p0 = &points[0] 328 | p1 = &points[1] 329 | s = 1 330 | e = path.count - 1 331 | p1Index = 1 332 | 333 | dx := p1.x - p0.x 334 | dy := p1.y - p0.y 335 | _, dx, dy = normalize(dx, dy) 336 | switch lineCap { 337 | case Butt: 338 | index = buttCapStart(dst, index, p0, dx, dy, w, -aa*0.5, aa) 339 | case Square: 340 | index = buttCapStart(dst, index, p0, dx, dy, w, w-aa, aa) 341 | case Round: 342 | index = roundCapStart(dst, index, p0, dx, dy, w, nCap, aa) 343 | } 344 | } 345 | 346 | for j := s; j < e; j++ { 347 | if p1.flags&(nvgPtBEVEL|nvgPrINNERBEVEL) != 0 { 348 | if lineJoin == Round { 349 | index = roundJoin(dst, index, p0, p1, w, w, 0, 1, nCap, aa) 350 | } else { 351 | index = bevelJoin(dst, index, p0, p1, w, w, 0, 1, aa) 352 | } 353 | } else { 354 | (&dst[index]).set(p1.x+p1.dmx*w, p1.y+p1.dmy*w, 0, 1) 355 | (&dst[index+1]).set(p1.x-p1.dmx*w, p1.y-p1.dmy*w, 1, 1) 356 | index += 2 357 | } 358 | p1Index++ 359 | p0 = p1 360 | if len(points) != p1Index { 361 | p1 = &points[p1Index] 362 | } 363 | } 364 | 365 | if path.closed { 366 | (&dst[index]).set(dst[0].x, dst[0].y, 0, 1) 367 | (&dst[index+1]).set(dst[1].x, dst[1].y, 1, 1) 368 | index += 2 369 | } else { 370 | dx := p1.x - p0.x 371 | dy := p1.y - p0.y 372 | _, dx, dy = normalize(dx, dy) 373 | switch lineCap { 374 | case Butt: 375 | index = buttCapEnd(dst, index, p1, dx, dy, w, -aa*0.5, aa) 376 | case Square: 377 | index = buttCapEnd(dst, index, p1, dx, dy, w, w-aa, aa) 378 | case Round: 379 | index = roundCapEnd(dst, index, p1, dx, dy, w, nCap, aa) 380 | } 381 | } 382 | 383 | path.strokes = dst[0:index] 384 | dst = dst[index:] 385 | } 386 | } 387 | 388 | func (c *nvgPathCache) expandFill(w float32, lineJoin LineCap, miterLimit, fringeWidth float32) { 389 | aa := fringeWidth 390 | fringe := w > 0.0 391 | 392 | // Calculate max vertex usage. 393 | c.calculateJoins(w, lineJoin, miterLimit) 394 | countVertex := 0 395 | for i := 0; i < len(c.paths); i++ { 396 | path := &c.paths[i] 397 | countVertex += path.count + path.nBevel + 1 398 | if fringe { 399 | countVertex += (path.count + path.nBevel*5 + 1) * 2 // plus one for loop 400 | } 401 | } 402 | 403 | dst := c.allocVertexes(countVertex) 404 | 405 | convex := len(c.paths) == 1 && c.paths[0].convex 406 | 407 | for i := 0; i < len(c.paths); i++ { 408 | path := &c.paths[i] 409 | points := c.points[path.first:] 410 | 411 | // Calculate shape vertices. 412 | wOff := 0.5 * aa 413 | index := 0 414 | 415 | if fringe { 416 | p0 := &points[path.count-1] 417 | p1 := &points[0] 418 | p1Index := 0 419 | for j := 0; j < path.count; j++ { 420 | if p1.flags&nvgPtBEVEL != 0 { 421 | dlx0 := p0.dy 422 | dly0 := -p0.dx 423 | dlx1 := p1.dy 424 | dly1 := -p1.dx 425 | if p1.flags&nvgPtLEFT != 0 { 426 | lx := p1.x + p1.dmx*wOff 427 | ly := p1.y + p1.dmy*wOff 428 | (&dst[index]).set(lx, ly, 0.5, 1) 429 | index++ 430 | } else { 431 | lx0 := p1.x + dlx0*wOff 432 | ly0 := p1.y + dly0*wOff 433 | lx1 := p1.x + dlx1*wOff 434 | ly1 := p1.y + dly1*wOff 435 | (&dst[index]).set(lx0, ly0, 0.5, 1) 436 | (&dst[index+1]).set(lx1, ly1, 0.5, 1) 437 | index += 2 438 | } 439 | } else { 440 | lx := p1.x + p1.dmx*wOff 441 | ly := p1.y + p1.dmy*wOff 442 | (&dst[index]).set(lx, ly, 0.5, 1) 443 | index++ 444 | } 445 | 446 | p1Index++ 447 | p0 = p1 448 | if len(points) != p1Index { 449 | p1 = &points[p1Index] 450 | } 451 | } 452 | } else { 453 | for j := 0; j < path.count; j++ { 454 | point := &points[j] 455 | (&dst[index]).set(point.x, point.y, 0.5, 1) 456 | index++ 457 | } 458 | } 459 | path.fills = dst[0:index] 460 | dst = dst[index:] 461 | 462 | // Calculate fringe 463 | if fringe { 464 | lw := w + wOff 465 | rw := w - wOff 466 | var lu float32 467 | var ru float32 = 1.0 468 | 469 | // Create only half a fringe for convex shapes so that 470 | // the shape can be rendered without stenciling. 471 | if convex { 472 | lw = wOff // This should generate the same vertex as fill inset above. 473 | lu = 0.5 // Set outline fade at middle. 474 | } 475 | p0 := &points[path.count-1] 476 | p1 := &points[0] 477 | p1Index := 0 478 | index := 0 479 | 480 | // Looping 481 | for j := 0; j < path.count; j++ { 482 | if p1.flags&(nvgPtBEVEL|nvgPrINNERBEVEL) != 0 { 483 | index = bevelJoin(dst, index, p0, p1, lw, rw, lu, ru, fringeWidth) 484 | } else { 485 | (&dst[index]).set(p1.x+(p1.dmx*lw), p1.y+(p1.dmy*lw), lu, 1) 486 | (&dst[index+1]).set(p1.x+(p1.dmx*lw), p1.y+(p1.dmy*lw), lu, 1) 487 | index += 2 488 | } 489 | p1Index++ 490 | p0 = p1 491 | if len(points) != p1Index { 492 | p1 = &points[p1Index] 493 | } 494 | } 495 | // Loop it 496 | (&dst[index]).set(dst[0].x, dst[0].y, lu, 1) 497 | (&dst[index+1]).set(dst[1].x, dst[1].y, ru, 1) 498 | index += 2 499 | 500 | path.strokes = dst[0:index] 501 | dst = dst[index:] 502 | } else { 503 | path.strokes = path.strokes[:0] 504 | } 505 | } 506 | } 507 | 508 | // GlyphPosition keeps glyph location information 509 | type GlyphPosition struct { 510 | Index int // Position of the glyph in the input string. 511 | Runes []rune 512 | X float32 // The x-coordinate of the logical glyph position. 513 | MinX, MaxX float32 // The bounds of the glyph shape. 514 | } 515 | 516 | // TextRow keeps row geometry information 517 | type TextRow struct { 518 | Runes []rune // The input string. 519 | StartIndex int // Index to the input text where the row starts. 520 | EndIndex int // Index to the input text where the row ends (one past the last character). 521 | NextIndex int // Index to the beginning of the next row. 522 | Width float32 // Logical width of the row. 523 | MinX, MaxX float32 // Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending. 524 | } 525 | -------------------------------------------------------------------------------- /transform.go: -------------------------------------------------------------------------------- 1 | package nanovgo 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | // The following functions can be used to make calculations on 2x3 transformation matrices. 8 | 9 | // TransformMatrix is a 2x3 matrix is represented as float[6]. 10 | type TransformMatrix [6]float32 11 | 12 | // IdentityMatrix makes the transform to identity matrix. 13 | func IdentityMatrix() TransformMatrix { 14 | return TransformMatrix{1.0, 0.0, 0.0, 1.0, 0.0, 0.0} 15 | } 16 | 17 | // TranslateMatrix makes the transform to translation matrix matrix. 18 | func TranslateMatrix(tx, ty float32) TransformMatrix { 19 | return TransformMatrix{1.0, 0.0, 0.0, 1.0, tx, ty} 20 | } 21 | 22 | // ScaleMatrix makes the transform to scale matrix. 23 | func ScaleMatrix(sx, sy float32) TransformMatrix { 24 | return TransformMatrix{sx, 0.0, 0.0, sy, 0.0, 0.0} 25 | } 26 | 27 | // RotateMatrix makes the transform to rotate matrix. Angle is specified in radians. 28 | func RotateMatrix(a float32) TransformMatrix { 29 | sin, cos := math.Sincos(float64(a)) 30 | sinF := float32(sin) 31 | cosF := float32(cos) 32 | return TransformMatrix{cosF, sinF, -sinF, cosF, 0.0, 0.0} 33 | } 34 | 35 | // SkewXMatrix makes the transform to skew-x matrix. Angle is specified in radians. 36 | func SkewXMatrix(a float32) TransformMatrix { 37 | return TransformMatrix{1.0, 0.0, float32(math.Tan(float64(a))), 1.0, 0.0, 0.0} 38 | } 39 | 40 | // SkewYMatrix makes the transform to skew-y matrix. Angle is specified in radians. 41 | func SkewYMatrix(a float32) TransformMatrix { 42 | return TransformMatrix{1.0, float32(math.Tan(float64(a))), 0.0, 1.0, 0.0, 0.0} 43 | } 44 | 45 | // Multiply makes the transform to the result of multiplication of two transforms, of A = A*B. 46 | func (t TransformMatrix) Multiply(s TransformMatrix) TransformMatrix { 47 | t0 := t[0]*s[0] + t[1]*s[2] 48 | t2 := t[2]*s[0] + t[3]*s[2] 49 | t4 := t[4]*s[0] + t[5]*s[2] + s[4] 50 | t[1] = t[0]*s[1] + t[1]*s[3] 51 | t[3] = t[2]*s[1] + t[3]*s[3] 52 | t[5] = t[4]*s[1] + t[5]*s[3] + s[5] 53 | t[0] = t0 54 | t[2] = t2 55 | t[4] = t4 56 | return t 57 | } 58 | 59 | // PreMultiply makes the transform to the result of multiplication of two transforms, of A = B*A. 60 | func (t TransformMatrix) PreMultiply(s TransformMatrix) TransformMatrix { 61 | return s.Multiply(t) 62 | } 63 | 64 | // Inverse makes the destination to inverse of specified transform. 65 | // Returns 1 if the inverse could be calculated, else 0. 66 | func (t TransformMatrix) Inverse() TransformMatrix { 67 | t0 := float64(t[0]) 68 | t1 := float64(t[1]) 69 | t2 := float64(t[2]) 70 | t3 := float64(t[3]) 71 | det := t0*t3 - t2*t1 72 | if det > -1e-6 && det < 1e-6 { 73 | return IdentityMatrix() 74 | } 75 | t4 := float64(t[4]) 76 | t5 := float64(t[5]) 77 | invdet := 1.0 / det 78 | return TransformMatrix{ 79 | float32(t3 * invdet), 80 | float32(-t1 * invdet), 81 | float32(-t2 * invdet), 82 | float32(t0 * invdet), 83 | float32((t2*t5 - t3*t4) * invdet), 84 | float32((t1*t4 - t0*t5) * invdet), 85 | } 86 | } 87 | 88 | // TransformPoint transforms a point by given TransformMatrix. 89 | func (t TransformMatrix) TransformPoint(sx, sy float32) (dx, dy float32) { 90 | dx = sx*t[0] + sy*t[2] + t[4] 91 | dy = sx*t[1] + sy*t[3] + t[5] 92 | return 93 | } 94 | 95 | // ToMat3x4 makes 3x4 matrix. 96 | func (t TransformMatrix) ToMat3x4() []float32 { 97 | return []float32{ 98 | t[0], t[1], 0.0, 0.0, 99 | t[2], t[3], 0.0, 0.0, 100 | t[4], t[5], 1.0, 0.0, 101 | } 102 | } 103 | 104 | func (t TransformMatrix) getAverageScale() float32 { 105 | sx := math.Sqrt(float64(t[0]*t[0] + t[2]*t[2])) 106 | sy := math.Sqrt(float64(t[1]*t[1] + t[3]*t[3])) 107 | return float32((sx + sy) * 0.5) 108 | } 109 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package nanovgo 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | // DegToRad converts degree to radian. 8 | func DegToRad(deg float32) float32 { 9 | return deg / 180.0 * PI 10 | } 11 | 12 | // RadToDeg converts radian to degree. 13 | func RadToDeg(rad float32) float32 { 14 | return rad / PI * 180.0 15 | } 16 | 17 | func signF(a float32) float32 { 18 | if a > 0.0 { 19 | return 1.0 20 | } 21 | return -1.0 22 | } 23 | 24 | func clampF(a, min, max float32) float32 { 25 | if a < min { 26 | return min 27 | } 28 | if a > max { 29 | return max 30 | } 31 | return a 32 | } 33 | 34 | func clampI(a, min, max int) int { 35 | if a < min { 36 | return min 37 | } 38 | if a > max { 39 | return max 40 | } 41 | return a 42 | } 43 | 44 | func hue(h, m1, m2 float32) float32 { 45 | if h < 0.0 { 46 | h++ 47 | } else if h > 1 { 48 | h-- 49 | } 50 | if h < 1.0/6.0 { 51 | return m1 + (m2-m1)*h*6.0 52 | } else if h < 3.0/6.0 { 53 | return m2 54 | } else if h < 4.0/6.0 { 55 | return m1 + (m2-m1)*(2.0/3.0-h)*6.0 56 | } 57 | return m1 58 | } 59 | 60 | func minF(a, b float32) float32 { 61 | if a < b { 62 | return a 63 | } 64 | return b 65 | } 66 | 67 | func maxF(a, b float32) float32 { 68 | if a > b { 69 | return a 70 | } 71 | return b 72 | } 73 | 74 | func maxI(a, b int) int { 75 | if a > b { 76 | return a 77 | } 78 | return b 79 | } 80 | 81 | func maxFs(v float32, values ...float32) float32 { 82 | max := v 83 | for _, value := range values { 84 | if max < value { 85 | max = value 86 | } 87 | } 88 | return max 89 | } 90 | 91 | func minFs(v float32, values ...float32) float32 { 92 | min := v 93 | for _, value := range values { 94 | if min > value { 95 | min = value 96 | } 97 | } 98 | return min 99 | } 100 | 101 | func cross(dx0, dy0, dx1, dy1 float32) float32 { 102 | return dx1*dy0 - dx0*dy1 103 | } 104 | 105 | func absF(a float32) float32 { 106 | if a > 0.0 { 107 | return a 108 | } 109 | return -a 110 | } 111 | 112 | func sqrtF(a float32) float32 { 113 | return float32(math.Sqrt(float64(a))) 114 | } 115 | func atan2F(a, b float32) float32 { 116 | return float32(math.Atan2(float64(a), float64(b))) 117 | } 118 | 119 | func acosF(a float32) float32 { 120 | return float32(math.Acos(float64(a))) 121 | } 122 | 123 | func tanF(a float32) float32 { 124 | return float32(math.Tan(float64(a))) 125 | } 126 | 127 | func sinCosF(a float32) (float32, float32) { 128 | s, c := math.Sincos(float64(a)) 129 | return float32(s), float32(c) 130 | } 131 | 132 | func ceilF(a float32) int { 133 | return int(math.Ceil(float64(a))) 134 | } 135 | 136 | func normalize(x, y float32) (float32, float32, float32) { 137 | d := float32(math.Sqrt(float64(x*x + y*y))) 138 | if d > 1e-6 { 139 | id := 1.0 / d 140 | x *= id 141 | y *= id 142 | } 143 | return d, x, y 144 | } 145 | 146 | func intersectRects(ax, ay, aw, ah, bx, by, bw, bh float32) [4]float32 { 147 | minX := maxF(ax, bx) 148 | minY := maxF(ay, by) 149 | maxX := minF(ax+aw, bx+bw) 150 | maxY := minF(ay+ah, by+bh) 151 | return [4]float32{ 152 | minX, 153 | minY, 154 | maxF(0.0, maxX-minX), 155 | maxF(0.0, maxY-minY), 156 | } 157 | } 158 | 159 | func ptEquals(x1, y1, x2, y2, tol float32) bool { 160 | dx := x2 - x1 161 | dy := y2 - y1 162 | return dx*dx+dy*dy < tol*tol 163 | } 164 | 165 | func distPtSeg(x, y, px, py, qx, qy float32) float32 { 166 | pqx := qx - px 167 | pqy := qy - py 168 | dx := x - px 169 | dy := y - py 170 | d := pqx*pqx + pqy*pqy 171 | t := clampF(pqx*dx+pqy*dy, 0.0, 1.1) 172 | if d > 0 { 173 | t /= d 174 | } 175 | dx = px + t*pqx - x 176 | dy = py + t*pqy - y 177 | return dx*dx + dy*dy 178 | } 179 | 180 | func triArea2(ax, ay, bx, by, cx, cy float32) float32 { 181 | abX := bx - ax 182 | abY := by - ay 183 | acX := cx - ax 184 | acY := cy - ay 185 | return acX*abY - abX*acY 186 | } 187 | 188 | func polyArea(points []nvgPoint, npts int) float32 { 189 | var area float32 190 | a := &points[0] 191 | for i := 2; i < npts; i++ { 192 | b := &points[i-1] 193 | c := &points[i] 194 | area += triArea2(a.x, a.y, b.x, b.y, c.x, c.y) 195 | } 196 | return area * 0.5 197 | } 198 | 199 | func polyReverse(points []nvgPoint, npts int) { 200 | i := 0 201 | j := npts - 1 202 | for i < j { 203 | points[i], points[j] = points[j], points[i] 204 | i++ 205 | j-- 206 | } 207 | } 208 | 209 | func curveDivs(r, arc, tol float32) int { 210 | da := math.Acos(float64(r/(r+tol))) * 2.0 211 | return maxI(2, int(math.Ceil(float64(arc)/da))) 212 | } 213 | 214 | func chooseBevel(bevel bool, p0, p1 *nvgPoint, w float32) (x0, y0, x1, y1 float32) { 215 | if bevel { 216 | x0 = p1.x + p0.dy*w 217 | y0 = p1.y - p0.dx*w 218 | x1 = p1.x + p1.dy*w 219 | y1 = p1.y - p1.dx*w 220 | } else { 221 | x0 = p1.x + p1.dmx*w 222 | y0 = p1.y + p1.dmy*w 223 | x1 = p1.x + p1.dmx*w 224 | y1 = p1.y + p1.dmy*w 225 | } 226 | return 227 | } 228 | 229 | func roundJoin(dst []nvgVertex, index int, p0, p1 *nvgPoint, lw, rw, lu, ru float32, nCap int, fringe float32) int { 230 | dlx0 := p0.dy 231 | dly0 := -p0.dx 232 | dlx1 := p1.dy 233 | dly1 := -p1.dx 234 | isInnerBevel := p1.flags&nvgPrINNERBEVEL != 0 235 | if p1.flags&nvgPtLEFT != 0 { 236 | lx0, ly0, lx1, ly1 := chooseBevel(isInnerBevel, p0, p1, lw) 237 | a0 := atan2F(-dly0, -dlx0) 238 | a1 := atan2F(-dly1, -dlx1) 239 | if a1 > a0 { 240 | a1 -= PI * 2 241 | } 242 | (&dst[index]).set(lx0, ly0, lu, 1) 243 | (&dst[index+1]).set(p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1) 244 | index += 2 245 | n := clampI(ceilF(((a0-a1)/PI)*float32(nCap)), 2, nCap) 246 | for i := 0; i < n; i++ { 247 | u := float32(i) / float32(n-1) 248 | a := a0 + u*(a1-a0) 249 | s, c := sinCosF(a) 250 | rx := p1.x + c*rw 251 | ry := p1.y + s*rw 252 | (&dst[index]).set(p1.x, p1.y, 0.5, 1) 253 | (&dst[index+1]).set(rx, ry, ru, 1) 254 | index += 2 255 | } 256 | (&dst[index]).set(lx1, ly1, lu, 1) 257 | (&dst[index+1]).set(p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1) 258 | index += 2 259 | } else { 260 | rx0, ry0, rx1, ry1 := chooseBevel(isInnerBevel, p0, p1, -rw) 261 | a0 := atan2F(dly0, dlx0) 262 | a1 := atan2F(dly1, dlx1) 263 | if a1 < a0 { 264 | a1 += PI * 2 265 | } 266 | (&dst[index]).set(p1.x+dlx0*rw, p1.y+dly0*rw, lu, 1) 267 | (&dst[index+1]).set(rx0, ry0, ru, 1) 268 | index += 2 269 | n := clampI(ceilF(((a1-a0)/PI)*float32(nCap)), 2, nCap) 270 | for i := 0; i < n; i++ { 271 | u := float32(i) / float32(n-1) 272 | a := a0 + u*(a1-a0) 273 | s, c := sinCosF(a) 274 | lx := p1.x + c*lw 275 | ly := p1.y + s*lw 276 | (&dst[index]).set(lx, ly, lu, 1) 277 | (&dst[index+1]).set(p1.x, p1.y, 0.5, 1) 278 | index += 2 279 | } 280 | (&dst[index]).set(p1.x+dlx1*rw, p1.y+dly1*rw, lu, 1) 281 | (&dst[index+1]).set(rx1, ry1, ru, 1) 282 | index += 2 283 | } 284 | return index 285 | } 286 | 287 | func bevelJoin(dst []nvgVertex, index int, p0, p1 *nvgPoint, lw, rw, lu, ru, fringe float32) int { 288 | dlx0 := p0.dy 289 | dly0 := -p0.dx 290 | dlx1 := p1.dy 291 | dly1 := -p1.dx 292 | isInnerBevel := p1.flags&nvgPrINNERBEVEL != 0 293 | isBevel := p1.flags&nvgPtBEVEL != 0 294 | if p1.flags&nvgPtLEFT != 0 { 295 | lx0, ly0, lx1, ly1 := chooseBevel(isInnerBevel, p0, p1, lw) 296 | 297 | (&dst[index]).set(lx0, ly0, lu, 1) 298 | (&dst[index+1]).set(p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1) 299 | index += 2 300 | 301 | if isBevel { 302 | (&dst[index]).set(lx0, ly0, lu, 1) 303 | (&dst[index+1]).set(p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1) 304 | 305 | (&dst[index+2]).set(lx1, ly1, lu, 1) 306 | (&dst[index+3]).set(p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1) 307 | 308 | index += 4 309 | } else { 310 | rx0 := p1.x - p1.dmx*rw 311 | ry0 := p1.y - p1.dmy*rw 312 | 313 | (&dst[index]).set(p1.x, p1.y, 0.5, 1) 314 | (&dst[index+1]).set(p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1) 315 | 316 | (&dst[index+2]).set(rx0, ry0, ru, 1) 317 | (&dst[index+3]).set(rx0, ry0, ru, 1) 318 | 319 | (&dst[index+4]).set(p1.x, p1.y, 0.5, 1) 320 | (&dst[index+5]).set(p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1) 321 | 322 | index += 6 323 | } 324 | (&dst[index]).set(lx1, ly1, lu, 1) 325 | (&dst[index+1]).set(p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1) 326 | index += 2 327 | } else { 328 | rx0, ry0, rx1, ry1 := chooseBevel(isInnerBevel, p0, p1, -rw) 329 | 330 | (&dst[index]).set(p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1) 331 | (&dst[index+1]).set(rx0, ry0, ru, 1) 332 | index += 2 333 | 334 | if isBevel { 335 | (&dst[index]).set(p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1) 336 | (&dst[index+1]).set(rx0, ry0, ru, 1) 337 | 338 | (&dst[index+2]).set(p1.x+dlx1*rw, p1.y+dly1*rw, lu, 1) 339 | (&dst[index+3]).set(rx1, ry1, ru, 1) 340 | 341 | index += 4 342 | } else { 343 | lx0 := p1.x + p1.dmx*rw 344 | ly0 := p1.y + p1.dmy*rw 345 | 346 | (&dst[index]).set(p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1) 347 | (&dst[index+1]).set(p1.x, p1.y, 0.5, 1) 348 | 349 | (&dst[index+2]).set(lx0, ly0, lu, 1) 350 | (&dst[index+3]).set(lx0, ly0, lu, 1) 351 | 352 | (&dst[index+4]).set(p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1) 353 | (&dst[index+5]).set(p1.x, p1.y, 0.5, 1) 354 | 355 | index += 6 356 | } 357 | (&dst[index]).set(p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1) 358 | (&dst[index+1]).set(rx1, ry1, ru, 1) 359 | index += 2 360 | } 361 | return index 362 | } 363 | 364 | func buttCapStart(dst []nvgVertex, index int, p *nvgPoint, dx, dy, w, d, aa float32) int { 365 | px := p.x - dx*d 366 | py := p.y - dy*d 367 | dlx := dy 368 | dly := -dx 369 | (&dst[index]).set(px+dlx*w-dx*aa, py+dly*w-dy*aa, 0, 0) 370 | (&dst[index+1]).set(px-dlx*w-dx*aa, py-dly*w-dy*aa, 1, 0) 371 | (&dst[index+2]).set(px+dlx*w, py+dly*w, 0, 1) 372 | (&dst[index+3]).set(px-dlx*w, py-dly*w, 1, 1) 373 | return index + 4 374 | } 375 | 376 | func buttCapEnd(dst []nvgVertex, index int, p *nvgPoint, dx, dy, w, d, aa float32) int { 377 | px := p.x + dx*d 378 | py := p.y + dy*d 379 | dlx := dy 380 | dly := -dx 381 | (&dst[index]).set(px+dlx*w, py+dly*w, 0, 1) 382 | (&dst[index+1]).set(px-dlx*w, py-dly*w, 1, 1) 383 | (&dst[index+2]).set(px+dlx*w+dx*aa, py+dly*w+dy*aa, 0, 0) 384 | (&dst[index+3]).set(px-dlx*w+dx*aa, py-dly*w-dy*aa, 1, 0) 385 | return index + 4 386 | } 387 | 388 | func roundCapStart(dst []nvgVertex, index int, p *nvgPoint, dx, dy, w float32, nCap int, aa float32) int { 389 | px := p.x 390 | py := p.y 391 | dlx := dy 392 | dly := -dx 393 | for i := 0; i < nCap; i++ { 394 | a := float32(i) / float32(nCap-1) * PI 395 | s, c := sinCosF(a) 396 | ax := c * w 397 | ay := s * w 398 | (&dst[index]).set(px-dlx*ax-dx*ay, py-dly*ax-dy*ay, 0, 1) 399 | (&dst[index+1]).set(px, py, 0.5, 1) 400 | index += 2 401 | } 402 | (&dst[index]).set(px+dlx*w, py+dly*w, 0, 1) 403 | (&dst[index+1]).set(px-dlx*w, py-dly*w, 1, 1) 404 | return index + 2 405 | } 406 | 407 | func roundCapEnd(dst []nvgVertex, index int, p *nvgPoint, dx, dy, w float32, nCap int, aa float32) int { 408 | px := p.x 409 | py := p.y 410 | dlx := dy 411 | dly := -dx 412 | (&dst[index]).set(px+dlx*w, py+dly*w, 0, 1) 413 | (&dst[index+1]).set(px-dlx*w, py-dly*w, 1, 1) 414 | index += 2 415 | for i := 0; i < nCap; i++ { 416 | a := float32(i) / float32(nCap-1) * PI 417 | s, c := sinCosF(a) 418 | ax := c * w 419 | ay := s * w 420 | (&dst[index]).set(px, py, 0.5, 1) 421 | (&dst[index+1]).set(px-dlx*ax+dx*ay, py-dly*ax+dy*ay, 0, 1) 422 | index += 2 423 | } 424 | return index 425 | } 426 | 427 | func nearestPow2(num int) int { 428 | var n uint 429 | uNum := uint(num) 430 | if uNum > 0 { 431 | n = uNum - 1 432 | } else { 433 | n = 0 434 | } 435 | n |= n >> 1 436 | n |= n >> 2 437 | n |= n >> 4 438 | n |= n >> 8 439 | n |= n >> 16 440 | n++ 441 | return int(num) 442 | } 443 | 444 | func quantize(a, d float32) float32 { 445 | return float32(int(a/d+0.5)) * d 446 | } 447 | --------------------------------------------------------------------------------