nanovg (empty) → 0.1.0.0
raw patch · 15 files changed
+4823/−0 lines, 15 filesdep +GLFW-bdep +QuickCheckdep +basesetup-changed
Dependencies added: GLFW-b, QuickCheck, base, bytestring, containers, gl, hspec, inline-c, linear, monad-loops, nanovg, text, transformers, vector
Files
- LICENSE +13/−0
- README.md +15/−0
- Setup.hs +2/−0
- cbits/glew.c +12/−0
- cbits/nanovg_gl.c +5/−0
- cbits/nanovg_wrapper.c +76/−0
- example/Example.hs +608/−0
- nanovg.cabal +70/−0
- nanovg/src/nanovg.c +2755/−0
- src/NanoVG.hs +68/−0
- src/NanoVG/Internal.chs +840/−0
- test/Contexts.hs +16/−0
- test/NanoVGSpec.c +104/−0
- test/NanoVGSpec.hs +238/−0
- test/Spec.hs +1/−0
+ LICENSE view
@@ -0,0 +1,13 @@+Copyright (c) 2016 Moritz Kiefer++Permission to use, copy, modify, and/or distribute this software for any purpose+with or without fee is hereby granted, provided that the above copyright notice+and this permission notice appear in all copies.++THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF+THIS SOFTWARE.
+ README.md view
@@ -0,0 +1,15 @@+# NanoVG Haskell bindings++Currently only the GL3 backend is supported.++A large part of the example bundled with+[NanoVG](https://github.com/memononen/nanovg) is translated into+Haskell and bundled as `example00`.++Most of the bindings directly expose the corresponding+[NanoVG](https://github.com/memononen/nanovg) so look there for more+details on the usage.++There is also a [diagrams backend](https://github.com/cocreature/diagrams-nanovg) using these bindings.++Feel free to open issues if you have any ideas for improvements (or even better PRs :)).
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ cbits/glew.c view
@@ -0,0 +1,12 @@+#include <GL/glew.h>+#include <stdio.h>+#include "nanovg.h"+#include "math.h"++void initGlew() {+ glewExperimental = GL_TRUE;+ if(glewInit() != GLEW_OK) {+ printf("Could not init glew.\n");+ /* return -1; */+ }+}
+ cbits/nanovg_gl.c view
@@ -0,0 +1,5 @@+#define NANOVG_GL3_IMPLEMENTATION+// This is used to link the implementation+#include "GL/glew.h"+#include "nanovg.h"+#include "nanovg_gl.h"
+ cbits/nanovg_wrapper.c view
@@ -0,0 +1,76 @@+#include "nanovg_wrapper.h"++void nvgRGB_(unsigned char r, unsigned char g, unsigned char b, NVGcolor *out) {+ *out = nvgRGB(r, g, b);+}++void nvgRGBf_(float r, float g, float b, NVGcolor *out) {+ *out = nvgRGBf(r, g, b);+}++void nvgRGBA_(unsigned char r, unsigned char g, unsigned char b,+ unsigned char a, NVGcolor *out) {+ *out = nvgRGBA(r, g, b, a);+}++void nvgRGBAf_(float r, float g, float b, float a, NVGcolor *out) {+ *out = nvgRGBAf(r, g, b, a);+}++void nvgLerpRGBA_(NVGcolor* c0, NVGcolor* c1, float u, NVGcolor *out) {+ *out = nvgLerpRGBA(*c0, *c1, u);+}++void nvgTransRGBA_(NVGcolor* c0, unsigned char a, NVGcolor *out) {+ *out = nvgTransRGBA(*c0, a);+}++void nvgTransRGBAf_(NVGcolor* c0, float a, NVGcolor *out) {+ *out = nvgTransRGBAf(*c0, a);+}++void nvgHSL_(float h, float s, float l, NVGcolor *out) {+ *out = nvgHSL(h, s, l);+}++void nvgHSLA_(float h, float s, float l, unsigned char a, NVGcolor *out) {+ *out = nvgHSLA(h, s, l, a);+}++void nvgLinearGradient_(NVGcontext *ctx, float sx, float sy, float ex, float ey,+ NVGcolor* icol, NVGcolor* ocol, NVGpaint *out) {+ *out = nvgLinearGradient(ctx, sx, sy, ex, ey, *icol, *ocol);+}++void nvgBoxGradient_(NVGcontext *ctx, float x, float y, float w, float h,+ float r, float f, NVGcolor* icol, NVGcolor* ocol,+ NVGpaint *out) {+ *out = nvgBoxGradient(ctx, x, y, w, h, r, f, *icol, *ocol);+}++void nvgRadialGradient_(NVGcontext *ctx, float cx, float cy, float inr,+ float outr, NVGcolor* icol, NVGcolor* ocol,+ NVGpaint *out) {+ *out = nvgRadialGradient(ctx, cx, cy, inr, outr, *icol, *ocol);+}++void nvgImagePattern_(NVGcontext *ctx, float ox, float oy, float ex, float ey,+ float angle, int image, float alpha, NVGpaint *out) {+ *out = nvgImagePattern(ctx, ox, oy, ex, ey, angle, image, alpha);+}++// c2hs can theoretically generate those, but it’s too much Setup.hs mess before Cabal 1.24+void nvgStrokePaint_(NVGcontext *ctx, NVGpaint *paint) {+ return nvgStrokePaint(ctx, *paint);+}+void nvgStrokeColor_(NVGcontext *ctx, NVGcolor *color) {+ return nvgStrokeColor(ctx, *color);+}++void nvgFillPaint_(NVGcontext *ctx, NVGpaint *paint) {+ return nvgFillPaint(ctx, *paint);+}++void nvgFillColor_(NVGcontext *ctx, NVGcolor *color) {+ return nvgFillColor(ctx, *color);+}
+ example/Example.hs view
@@ -0,0 +1,608 @@+{-# LANGUAGE ForeignFunctionInterface #-}+{-# LANGUAGE OverloadedStrings #-}+module Main where++import Control.Monad+import Control.Monad.Loops+import Control.Monad.Trans.Maybe+import Data.Bits hiding (rotate)+import Data.IORef+import Data.Maybe+import Data.Monoid+import qualified Data.Set as S+import qualified Data.Text as T+import qualified Data.Vector as V+import Graphics.GL.Core32+import Graphics.UI.GLFW+import Linear.V4+import NanoVG as NVG+import NanoVG.Internal as Internal+import Prelude hiding (init)++import Foreign.C.Types+import Foreign.Ptr++foreign import ccall unsafe "initGlew"+ glewInit :: IO CInt++main :: IO ()+main =+ do e <- init+ when (not e) $ putStrLn "Failed to init GLFW"+ windowHint $ WindowHint'ContextVersionMajor 3+ windowHint $ WindowHint'ContextVersionMinor 2+ windowHint $ WindowHint'OpenGLForwardCompat True+ windowHint $ WindowHint'OpenGLProfile OpenGLProfile'Core+ windowHint $ WindowHint'OpenGLDebugContext True+ win <- createWindow 1000 600 "NanoVG" Nothing Nothing+ case win of+ Nothing -> putStrLn "Failed to create window" >> terminate+ Just w ->+ do makeContextCurrent win+ glewInit+ glGetError+ c@(Context c') <- createGL3 (S.fromList [Antialias,StencilStrokes,Debug])+ -- error handling? who needs that anyway+ Just demoData <- runMaybeT $ loadDemoData c+ swapInterval 0+ setTime 0+ whileM_ (not <$> windowShouldClose w) $+ do Just t <- getTime+ (mx,my) <- getCursorPos w+ (width,height) <- getWindowSize w+ (fbWidth,fbHeight) <- getFramebufferSize w+ let pxRatio = fromIntegral fbWidth / fromIntegral width+ glViewport 0 0 (fromIntegral fbWidth) (fromIntegral fbHeight)+ glClearColor 0.3 0.3 0.32 1.0+ glClear (GL_COLOR_BUFFER_BIT .|. GL_DEPTH_BUFFER_BIT .|. GL_STENCIL_BUFFER_BIT)+ beginFrame c (fromIntegral width) (fromIntegral height) pxRatio+ renderDemo c demoData mx my width height t+ endFrame c+ swapBuffers w+ pollEvents++renderDemo :: Context -> DemoData -> Double -> Double -> Int -> Int -> Double -> IO ()+renderDemo c demoData mx my w h t =+ do drawEyes c (fromIntegral w - 250) 50 150 100 (realToFrac mx) (realToFrac my) (realToFrac t)+ drawParagraph c (fromIntegral w - 450) 50 150 100 (realToFrac mx) (realToFrac my)+ drawGraph c 0 (fromIntegral h/2) (fromIntegral w) (fromIntegral h/2) (realToFrac t)+ drawColorwheel c (fromIntegral w - 300) (fromIntegral h - 300) 250 250 (realToFrac t)++ drawLines c 120 (fromIntegral h - 50) 600 50 (realToFrac t)++ drawWidths c 10 50 30++ drawCaps c 10 300 30++ drawScissor c 50 (fromIntegral h-80) (realToFrac t)++ save c+ let popy = 95 + 24+ drawThumbnails c 365 popy 160 300 (images demoData) (realToFrac t)++ restore c++drawThumbnails :: Context -> CFloat -> CFloat -> CFloat -> CFloat -> V.Vector Image -> CFloat -> IO ()+drawThumbnails vg x y w h images t =+ do let cornerRadius = 3+ thumb = 60+ arry = 30.5+ nimages = V.length images+ stackh = (fromIntegral nimages/2)*(thumb+10)+10+ u = (1+cos (t*0.5))*0.5+ u2 = (1-cos (t*0.2))*0.5+ dv = 1/(fromIntegral nimages - 1)+ save vg++ shadowPaint <- boxGradient vg x (y+4) w h (cornerRadius*2) 20 (rgba 0 0 0 128) (rgba 0 0 0 0)+ beginPath vg+ rect vg (x-10) (y-10) (w+20) (h+30)+ roundedRect vg x y w h cornerRadius+ pathWinding vg (fromIntegral $ fromEnum Hole)+ fillPaint vg shadowPaint+ fill vg++ beginPath vg+ roundedRect vg x y w h cornerRadius+ moveTo vg (x-10) (y+arry)+ lineTo vg (x+1) (y+arry-11)+ lineTo vg (x+1) (y+arry+11)+ fillColor vg (rgba 200 200 200 255)+ fill vg++ save vg+ scissor vg x y w h+ translate vg 0 (-(stackh-h)*u)++ flip V.imapM_ images $ \i image -> do+ let tx = x + 10 + fromIntegral (i `mod` 2) * (thumb + 10)+ ty = y + 10 + fromIntegral (i `div` 2) * (thumb + 10)+ v = fromIntegral i * dv+ a = clamp ((u2-v)/dv) 0 1+ drawImage iw ih ix iy = do+ imgPaint <- imagePattern vg (tx+ix) (ty+iy) iw ih (0/180*pi) image a+ beginPath vg+ roundedRect vg tx ty thumb thumb 5+ fillPaint vg imgPaint+ fill vg+ (imgw,imgh) <- imageSize vg image++ when (a < 1) $ drawSpinner vg (tx + thumb/2) (ty+thumb/2) (thumb*0.25) t++ if imgw < imgh+ then+ let iw = thumb+ ih = iw*fromIntegral imgh/fromIntegral imgw+ ix = 0+ iy = -(ih-thumb)*0.5+ in drawImage iw ih ix iy+ else+ let ih = thumb+ iw = ih * fromIntegral imgw/ fromIntegral imgh+ ix = -(iw-thumb)*0.5+ iy = 0+ in drawImage iw ih ix iy++ shadowPaint <- boxGradient vg (tx-1) ty (thumb+2) (thumb+2) 5 3 (rgba 0 0 0 128) (rgba 0 0 0 0)+ beginPath vg+ rect vg (tx-5) (ty-5) (thumb+10) (thumb+10)+ roundedRect vg tx ty thumb thumb 6+ pathWinding vg (fromIntegral $ fromEnum Hole)+ fillPaint vg shadowPaint+ fill vg++ beginPath vg+ roundedRect vg (tx+0.5) (ty+0.5) (thumb-1) (thumb-1) (4-0.5)+ strokeWidth vg 1+ strokeColor vg (rgba 255 255 255 192)+ stroke vg+++ restore vg++ restore vg++drawSpinner :: Context -> CFloat -> CFloat -> CFloat -> CFloat -> IO ()+drawSpinner vg cx cy r t =+ do let a0 = 0+t*6+ a1 = pi + t*6+ r0 = r+ r1 = r*0.75+ save vg++ beginPath vg+ arc vg cx cy r0 a0 a1 CW+ arc vg cx cy r1 a1 a0 CCW+ closePath vg+ let ax = cx+cos a0 * (r0+r1)*0.5+ ay = cy+sin a0 * (r0+r1)*0.5+ bx = cx+cos a1 * (r0+r1)*0.5+ by = cy+sin a1 * (r0+r1)*0.5+ paint <- linearGradient vg ax ay bx by (rgba 0 0 0 0) (rgba 0 0 0 128)+ fillPaint vg paint+ fill vg++ restore vg++drawEyes :: Context -> CFloat -> CFloat -> CFloat -> CFloat -> CFloat -> CFloat -> CFloat -> IO ()+drawEyes c@(Context c') x y w h mx my t = do+ bg <- linearGradient c x (y+h*0.5) (x+w*0.1) (y+h) (rgba 0 0 0 32) (rgba 0 0 0 16)+ beginPath c+ ellipse c (lx+3) (ly+16) ex ey+ ellipse c (rx+3) (ry+16) ex ey+ fillPaint c bg+ fill c++ bg <- linearGradient c x (y+h*0.25) (x+w*0.1) (y+h) (rgba 220 220 220 255) (rgba 128 128 128 255)+ beginPath c+ ellipse c lx ly ex ey+ ellipse c rx ry ex ey+ fillPaint c bg+ fill c+ + let dx' = (mx - rx) / (ex * 10)+ dy' = (my - ry) / (ey * 10)+ d = sqrt (dx'*dx'+dy'*dy')+ dx'' = if d > 1 then dx'/d else dx'+ dy'' = if d > 1 then dy'/d else dy'+ dx = dx'' * ex * 0.4+ dy = dy'' * ey * 0.5+ beginPath c+ ellipse c (lx+dx) (ly+dy+ey*0.25*(1-blink)) br (br*blink)+ fillColor c (rgba 32 32 32 255)+ fill c++ let dx'' = (mx - rx) / (ex * 10)+ dy'' = (my - ry) / (ey * 10)+ d = sqrt (dx'' * dx'' + dy'' * dy'')+ dx' = if d > 1 then dx'' / d else dx''+ dy' = if d > 1 then dy'' / d else dy''+ dx = dx' * ex * 0.4+ dy = dy' * ey * 0.5+ beginPath c+ ellipse c (rx+dx) (ry+dy+ey*0.25*(1-blink)) br (br*blink)+ fillColor c (rgba 32 32 32 255)+ fill c++ gloss <- radialGradient c (lx-ex*0.25) (ly-ey*0.5) (ex*0.1) (ex*0.75) (rgba 255 255 255 128) (rgba 255 255 255 0)+ beginPath c+ ellipse c lx ly ex ey+ fillPaint c gloss+ fill c++ gloss <- radialGradient c (rx-ex*0.25) (ry-ey*0.5) (ex*0.1) (ex*0.75) (rgba 255 255 255 128) (rgba 255 255 255 0)+ beginPath c+ ellipse c rx ry ex ey+ fillPaint c gloss+ fill c+ where ex = w * 0.23+ ey = h * 0.5+ lx = x + ex+ ly = y + ey+ rx = x + w - ex+ ry = y + ey+ br = 0.5 * min ex ey+ blink = 1 - ((sin (t*0.5))**200)*0.8++drawParagraph :: Context -> CFloat -> CFloat -> CFloat -> CFloat -> CFloat -> CFloat -> IO ()+drawParagraph c x y w h mx my =+ do save c+ fontSize c 18+ fontFace c "sans"+ textAlign c (S.fromList [AlignLeft,AlignTop])+ (_,_,lineh) <- textMetrics c+ gutter <- newIORef Nothing+ yEnd <- newIORef y+ NVG.textBreakLines c text w 3 $ \row i -> do+ let y' = y + fromIntegral i * lineh+ hit = mx > x && mx < (x+w) && my >= y' && my < (y' + lineh)+ writeIORef yEnd y'+ beginPath c+ fillColor c (rgba 255 255 255 (if hit then 64 else 16))+ rect c x y' (width row) lineh+ fill c++ fillColor c (rgba 255 255 255 255)+ Internal.text c x y' (start row) (end row)+ when hit $ do+ let caretxInit = if mx < x+ (width row) / 2 then x else x + width row+ ps = x+ glyphs <- NVG.textGlyphPositions c x y (start row) (end row) 100+ let leftBorders = V.map glyphX glyphs+ rightBorders = V.snoc (V.drop 1 leftBorders) (x + width row)+ rightPoints = V.zipWith (\x y -> 0.3*x+0.7*y) leftBorders rightBorders+ leftPoints = V.cons x (V.take (V.length glyphs - 1) rightPoints)+ caretx = maybe caretxInit (glyphX . (glyphs V.!)) $ V.findIndex (\(px,gx) -> mx >= px && mx < gx) $ V.zip leftPoints rightPoints+ beginPath c+ fillColor c (rgba 255 192 0 255)+ rect c caretx y' 1 lineh+ fill c+ -- realized too late that I probably should have used a fold+ writeIORef gutter (Just (i+1,x-10,y'+lineh/2))+ gutter' <- readIORef gutter+ forM_ gutter' $ \(gutter,gx,gy) -> do+ let txt = T.pack $ show gutter+ fontSize c 13+ textAlign c (S.fromList [AlignRight,AlignMiddle])+ (Bounds (V4 b0 b1 b2 b3)) <- textBounds c gx gy txt+ beginPath c+ fillColor c (rgba 255 192 0 255)+ roundedRect c (b0-4) (b1-2) ((b2-b0)+8) ((b3-b1)+4) (((b3-b1)+4)/2-1)+ fill c+ fillColor c (rgba 32 32 32 255)+ NVG.text c gx gy txt++ y' <- (\x -> x+20+lineh) <$> readIORef yEnd++ fontSize c 13+ textAlign c (S.fromList [AlignLeft, AlignTop])+ textLineHeight c 1.2++ (Bounds (V4 b0 b1 b2 b3)) <- textBoxBounds c x y' 150 helpText++ let gx = abs $ (mx - (b0+b2)*0.5) / (b0 - b2)+ gy = abs $ (my - (b1+b3)*0.5) / (b1 - b3)+ a = (\x -> clamp x 0 1) $ max gx gy - 0.5+ globalAlpha c a++ beginPath c+ fillColor c (rgba 220 220 220 255)+ roundedRect c (b0-2) (b1-2) ((b2-b0)+4) ((b3-b1)+4) 3+ let px = (b2+b0)/2+ moveTo c px (b1-10)+ lineTo c (px+7) (b1+1)+ lineTo c (px-7) (b1+1)+ fill c++ fillColor c (rgba 0 0 0 220)+ textBox c x y' 150 helpText+ + restore c+ where 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."+ helpText = "Hover your mouse over the text to see calculated caret position."++drawGraph :: Context -> CFloat -> CFloat -> CFloat -> CFloat -> CFloat -> IO ()+drawGraph c x y w h t =+ do bg <- linearGradient c x y x (y+h) (rgba 0 16 192 0) (rgba 0 160 192 64)+ beginPath c+ moveTo c (sx V.! 0) (sy V.! 0)+ forM_ [1..5] $ \i ->+ bezierTo c (sx V.! (i-1) + dx*0.5) (sy V.! (i-1)) (sx V.! i - dx*0.5) (sy V.! i) (sx V.! i) (sy V.! i)+ lineTo c (x+w) (y+h)+ lineTo c x (y+h)+ fillPaint c bg+ fill c++ beginPath c+ moveTo c (sx V.! 0) (sy V.! 0 + 2)+ forM_ [1..5] $ \i ->+ bezierTo c (sx V.! (i-1)+dx*0.5) (sy V.! (i-1)+2) (sx V.! i - dx*0.5) (sy V.! i + 2) (sx V.! i) (sy V.! i + 2)+ strokeColor c (rgba 0 0 0 32)+ strokeWidth c 3+ stroke c++ beginPath c+ moveTo c (sx V.! 0) (sy V.! 0)+ forM_ [1..5] $ \i ->+ bezierTo c (sx V.! (i-1)+dx*0.5) (sy V.! (i-1)) (sx V.! i - dx*0.5) (sy V.! i) (sx V.! i) (sy V.! i)+ strokeColor c (rgba 0 160 192 255)+ strokeWidth c 3+ stroke c++ V.forM_ (V.zip sx sy) $ \(x,y) ->+ do bg <- radialGradient c x (y+2) 3 8 (rgba 0 0 0 32) (rgba 0 0 0 0)+ beginPath c+ rect c (x-10) (y-10+2) 20 20+ fillPaint c bg+ fill c++ beginPath c+ V.forM_ (V.zip sx sy) $ \(x,y) -> circle c x y 4+ fillColor c (rgba 0 160 192 255)+ fill c+ beginPath c+ V.forM_ (V.zip sx sy) $ \(x,y) -> circle c x y 2+ fillColor c (rgba 220 220 220 255)+ fill c+ + strokeWidth c 1+ where samples :: V.Vector CFloat+ samples =+ V.fromList+ [(1 + sin (t * 1.2345 + cos (t * 0.33457) * 0.44)) * 0.5+ ,(1 + sin (t * 0.68363 + cos (t * 1.3) * 1.55)) * 0.5+ ,(1 + sin (t * 1.1642 + cos (t * 0.33457) * 1.24)) * 0.5+ ,(1 + sin (t * 0.56345 + cos (t * 1.63) * 0.14)) * 0.5+ ,(1 + sin (t * 1.6245 + cos (t * 0.254) * 0.3)) * 0.5+ ,(1 + sin (t * 0.345 + cos (t * 0.03) * 0.6)) * 0.5]+ dx = w / 5+ sx :: V.Vector CFloat+ sx =+ V.generate 6+ (\i -> x + fromIntegral i * dx)+ sy :: V.Vector CFloat+ sy = V.map (\s -> y + h * s * 0.8) samples++drawColorwheel :: Context -> CFloat -> CFloat -> CFloat -> CFloat -> CFloat -> IO ()+drawColorwheel c x y w h t =+ do save c+ forM_ [0..5] $ \i ->+ do let a0 = i / 6 * pi * 2 - aeps+ a1 = (i+1)/6*pi*2+aeps+ beginPath c+ arc c cx cy r0 a0 a1 CW+ arc c cx cy r1 a1 a0 CCW+ closePath c+ let ax = cx + cos a0 * (r0+r1)*0.5+ ay = cy+sin a0 * (r0+r1)*0.5+ bx = cx + cos a1 * (r0+r1) * 0.5+ by = cy + sin a1 * (r0 + r1) * 0.5+ paint <- linearGradient c ax ay bx by (hsla (a0/(2*pi)) 1 0.55 255) (hsla (a1/(2*pi)) 1 0.55 255)+ fillPaint c paint+ fill c+ beginPath c+ circle c cx cy (r0-0.5)+ circle c cx cy (r1+0.5)+ strokeColor c (rgba 0 0 0 64)+ strokeWidth c 1+ stroke c++ save c+ translate c cx cy+ rotate c (hue*pi*2)++ strokeWidth c 2+ beginPath c+ rect c (r0-1) (-3) (r1-r0+2) 6+ strokeColor c (rgba 255 255 255 192)+ stroke c++ paint <- boxGradient c (r0-3) (-5) (r1-r0+6) 10 2 4 (rgba 0 0 0 128) (rgba 0 0 0 0)+ beginPath c+ rect c (r0-2-10) (-4-10) (r1-r0+4+20) (8+20)+ rect c (r0-2) (-4) (r1-r0+4) 8+ pathWinding c (fromIntegral$fromEnum Hole)+ fillPaint c paint+ fill c++ let r = r0 - 6+ ax = cos (120/180*pi) * r+ ay = sin(120/180*pi) * r+ bx = cos (-120/180*pi) * r+ by = sin(-120/180*pi) * r+ beginPath c+ moveTo c r 0+ lineTo c ax ay+ lineTo c bx by+ closePath c+ paint <- linearGradient c r 0 ax ay (hsla hue 1 0.5 255) (rgba 255 255 255 255)+ fillPaint c paint+ fill c+ strokeColor c (rgba 0 0 0 64)+ stroke c++ let ax = cos (120/180*pi)*r*0.3+ ay = sin(120/180*pi)*r*0.4+ strokeWidth c 2+ beginPath c+ circle c ax ay 5+ strokeColor c (rgba 255 255 255 192)+ stroke c++ paint <- radialGradient c ax ay 7 9 (rgba 0 0 0 64) (rgba 0 0 0 0)+ beginPath c+ rect c (ax-20) (ay-20) 40 40+ circle c ax ay 7+ pathWinding c (fromIntegral$fromEnum Hole)+ fillPaint c paint+ fill c+ restore c+ restore c+ where hue = sin (t * 0.12)+ cx = x + w*0.5+ cy = y+h*0.5+ r1 = min w h * 0.5 - 5+ r0 = r1 - 20+ aeps = 0.5 / r1+ ++drawLines :: Context -> CFloat -> CFloat -> CFloat -> CFloat -> CFloat -> IO ()+drawLines c x y w h t =+ do save c+ forM_ [0..2] $ \i -> forM_ [0..2] $ \j ->+ do let fx = x +s*0.5+(fromIntegral i*3+fromIntegral j)/9*w+pad+ fy = y-s*0.5+pad+ lineCap c (caps V.! i)+ lineJoin c (joins V.! j)++ strokeWidth c (s*0.3)+ strokeColor c (rgba 0 0 0 160)+ beginPath c+ moveTo c (fx + pts V.! 0) (fy + pts V.! 1)+ moveTo c (fx + pts V.! 2) (fy + pts V.! 3)+ moveTo c (fx + pts V.! 4) (fy + pts V.! 5)+ moveTo c (fx + pts V.! 6) (fy + pts V.! 7)+ stroke c++ lineCap c Butt+ lineJoin c Bevel++ strokeWidth c 1+ strokeColor c (rgba 0 192 255 255)+ beginPath c+ moveTo c (fx + pts V.! 0) (fy + pts V.! 1)+ moveTo c (fx + pts V.! 2) (fy + pts V.! 3)+ moveTo c (fx + pts V.! 4) (fy + pts V.! 5)+ moveTo c (fx + pts V.! 6) (fy + pts V.! 7)+ stroke c+ restore c+ where pad = 0.5+ s = w / 9 - pad * 2+ joins = V.fromList [Miter,Round,Bevel]+ caps = V.fromList [Butt,Round,Square]+ pts =+ V.fromList+ [-s * 0.25 + cos (t * 0.3) * s * 0.5+ ,sin (t * 0.3) * s * 0.5+ ,-s * 0.25+ ,0+ ,s * 0.25+ ,0+ ,s * 0.25 + cos (-t * 0.3) * s * 0.5+ ,sin (-t * 0.3) * s * 0.5]++drawWidths :: Context -> CFloat -> CFloat -> CFloat -> IO ()+drawWidths c x y width =+ do save c+ strokeColor c (rgba 0 0 0 255)+ forM_ [0..19] $ \i ->+ do let w = (i+0.5)*0.1+ y' = y + (10*i)+ strokeWidth c w+ beginPath c+ moveTo c x y'+ lineTo c (x+width) (y'+width*0.3)+ stroke c+ restore c++data DemoData =+ DemoData {fontNormal :: Font+ ,fontBold :: Font+ ,fontIcons :: Font+ ,images :: V.Vector Image}++loadDemoData :: Context -> MaybeT IO DemoData+loadDemoData c = do icons <- MaybeT $ createFont c "icons" (FileName "nanovg/example/entypo.ttf")+ normal <- MaybeT $ createFont c "sans" (FileName "nanovg/example/Roboto-Regular.ttf")+ bold <- MaybeT $ createFont c "sans-bold" (FileName "nanovg/example/Roboto-Bold.ttf")+ images <- loadImages+ pure (DemoData icons normal bold images)+ where loadImages :: MaybeT IO (V.Vector Image)+ loadImages =+ V.generateM 12 $+ \i ->+ do let file = FileName $+ "nanovg/example/images/image" <> T.pack (show (i + 1)) <> ".jpg"+ MaybeT $ createImage c file 0++drawCaps :: Context -> CFloat -> CFloat -> CFloat -> IO ()+drawCaps c x y width =+ do save c++ beginPath c+ rect c (x-lineWidth/2) y (width+lineWidth) 40+ fillColor c (rgba 255 255 255 32)+ fill c++ beginPath c+ rect c x y width 40+ fillColor c (rgba 255 255 255 32)+ fill c++ strokeWidth c lineWidth+ forM_ (zip [0..] [Butt,Round,Square]) $ \(i,cap) ->+ do lineCap c cap+ strokeColor c (rgba 0 0 0 255)+ beginPath c+ moveTo c x (y+i*10+5)+ lineTo c (x+width) (y+i*10+5)+ stroke c+ restore c+ where lineWidth = 8++drawScissor :: Context -> CFloat -> CFloat -> CFloat -> IO ()+drawScissor c x y t =+ do save c++ translate c x y+ rotate c (degToRad 5)+ beginPath c+ rect c (-20) (-20) 60 40+ fillColor c (rgba 255 0 0 255)+ fill c+ scissor c (-20) (-20) 60 40++ translate c 40 0+ rotate c t++ save c+ resetScissor c+ beginPath c+ rect c (-20) (-10) 60 30+ fillColor c (rgba 255 128 0 64)+ fill c+ restore c++ intersectScissor c (-20) (-10) 60 30+ beginPath c+ rect c (-20) (-10) 60 30+ fillColor c (rgba 255 128 0 255)+ fill c++ restore c++clamp :: Ord a => a -> a -> a -> a+clamp a' low up+ | a' < low = low+ | a' > up = up+ | otherwise = a'
+ nanovg.cabal view
@@ -0,0 +1,70 @@+name: nanovg+version: 0.1.0.0+synopsis: Haskell bindings for nanovg+description: Raw bindings to the OpenGL vector graphics library NanoVG+homepage: https://github.com/cocreature/haskell-nanovg+license: ISC+license-file: LICENSE+author: Moritz Kiefer+maintainer: moritz.kiefer@purelyfunctional.org+copyright: 2016 Moritz Kiefer+category: Graphics+build-type: Simple+extra-source-files: README.md+cabal-version: >=1.10+++library+ exposed-modules: NanoVG+ NanoVG.Internal+ build-depends: base >=4.8 && <4.9+ , bytestring+ , containers+ , linear+ , text+ , vector+ hs-source-dirs: src+ default-language: Haskell2010+ include-dirs: nanovg/src+ cbits+ includes: nanovg.h+ nanovg_gl.h+ nanovg_wrapper.h+ c-sources: nanovg/src/nanovg.c+ cbits/nanovg_wrapper.c+ cbits/nanovg_gl.c+ cc-options: -DNDEBUG+ ghc-options: -Wall+ extra-libraries: GLU, GL, m, GLEW++executable example00+ hs-source-dirs: example+ main-is: Example.hs+ build-depends: base+ , containers+ , gl+ , GLFW-b+ , linear+ , monad-loops+ , nanovg+ , text+ , transformers+ , vector+ default-language: Haskell2010+ c-sources: cbits/glew.c++test-suite nanovg-test+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ other-modules: NanoVGSpec+ Contexts+ main-is: Spec.hs+ build-depends: base+ , containers+ , hspec+ , inline-c+ , linear+ , nanovg+ , QuickCheck+ default-language: Haskell2010+ c-sources: test/NanoVGSpec.c
+ nanovg/src/nanovg.c view
@@ -0,0 +1,2755 @@+//+// Copyright (c) 2013 Mikko Mononen memon@inside.org+//+// This software is provided 'as-is', without any express or implied+// warranty. In no event will the authors be held liable for any damages+// arising from the use of this software.+// Permission is granted to anyone to use this software for any purpose,+// including commercial applications, and to alter it and redistribute it+// freely, subject to the following restrictions:+// 1. The origin of this software must not be misrepresented; you must not+// claim that you wrote the original software. If you use this software+// in a product, an acknowledgment in the product documentation would be+// appreciated but is not required.+// 2. Altered source versions must be plainly marked as such, and must not be+// misrepresented as being the original software.+// 3. This notice may not be removed or altered from any source distribution.+//++#include <stdlib.h>+#include <stdio.h>+#include <math.h>+#include <memory.h>++#include "nanovg.h"+#define FONTSTASH_IMPLEMENTATION+#include "fontstash.h"+#define STB_IMAGE_IMPLEMENTATION+#include "stb_image.h"++#ifdef _MSC_VER+#pragma warning(disable: 4100) // unreferenced formal parameter+#pragma warning(disable: 4127) // conditional expression is constant+#pragma warning(disable: 4204) // nonstandard extension used : non-constant aggregate initializer+#pragma warning(disable: 4706) // assignment within conditional expression+#endif++#define NVG_INIT_FONTIMAGE_SIZE 512+#define NVG_MAX_FONTIMAGE_SIZE 2048+#define NVG_MAX_FONTIMAGES 4++#define NVG_INIT_COMMANDS_SIZE 256+#define NVG_INIT_POINTS_SIZE 128+#define NVG_INIT_PATHS_SIZE 16+#define NVG_INIT_VERTS_SIZE 256+#define NVG_MAX_STATES 32++#define NVG_KAPPA90 0.5522847493f // Length proportional to radius of a cubic bezier handle for 90deg arcs.++#define NVG_COUNTOF(arr) (sizeof(arr) / sizeof(0[arr]))+++enum NVGcommands {+ NVG_MOVETO = 0,+ NVG_LINETO = 1,+ NVG_BEZIERTO = 2,+ NVG_CLOSE = 3,+ NVG_WINDING = 4,+};++enum NVGpointFlags+{+ NVG_PT_CORNER = 0x01,+ NVG_PT_LEFT = 0x02,+ NVG_PT_BEVEL = 0x04,+ NVG_PR_INNERBEVEL = 0x08,+};++struct NVGstate {+ NVGpaint fill;+ NVGpaint stroke;+ float strokeWidth;+ float miterLimit;+ int lineJoin;+ int lineCap;+ float alpha;+ float xform[6];+ NVGscissor scissor;+ float fontSize;+ float letterSpacing;+ float lineHeight;+ float fontBlur;+ int textAlign;+ int fontId;+};+typedef struct NVGstate NVGstate;++struct NVGpoint {+ float x,y;+ float dx, dy;+ float len;+ float dmx, dmy;+ unsigned char flags;+};+typedef struct NVGpoint NVGpoint;++struct NVGpathCache {+ NVGpoint* points;+ int npoints;+ int cpoints;+ NVGpath* paths;+ int npaths;+ int cpaths;+ NVGvertex* verts;+ int nverts;+ int cverts;+ float bounds[4];+};+typedef struct NVGpathCache NVGpathCache;++struct NVGcontext {+ NVGparams params;+ float* commands;+ int ccommands;+ int ncommands;+ float commandx, commandy;+ NVGstate states[NVG_MAX_STATES];+ int nstates;+ NVGpathCache* cache;+ float tessTol;+ float distTol;+ float fringeWidth;+ float devicePxRatio;+ struct FONScontext* fs;+ int fontImages[NVG_MAX_FONTIMAGES];+ int fontImageIdx;+ int drawCallCount;+ int fillTriCount;+ int strokeTriCount;+ int textTriCount;+};++static float nvg__sqrtf(float a) { return sqrtf(a); }+static float nvg__modf(float a, float b) { return fmodf(a, b); }+static float nvg__sinf(float a) { return sinf(a); }+static float nvg__cosf(float a) { return cosf(a); }+static float nvg__tanf(float a) { return tanf(a); }+static float nvg__atan2f(float a,float b) { return atan2f(a, b); }+static float nvg__acosf(float a) { return acosf(a); }++static int nvg__mini(int a, int b) { return a < b ? a : b; }+static int nvg__maxi(int a, int b) { return a > b ? a : b; }+static int nvg__clampi(int a, int mn, int mx) { return a < mn ? mn : (a > mx ? mx : a); }+static float nvg__minf(float a, float b) { return a < b ? a : b; }+static float nvg__maxf(float a, float b) { return a > b ? a : b; }+static float nvg__absf(float a) { return a >= 0.0f ? a : -a; }+static float nvg__signf(float a) { return a >= 0.0f ? 1.0f : -1.0f; }+static float nvg__clampf(float a, float mn, float mx) { return a < mn ? mn : (a > mx ? mx : a); }+static float nvg__cross(float dx0, float dy0, float dx1, float dy1) { return dx1*dy0 - dx0*dy1; }++static float nvg__normalize(float *x, float* y)+{+ float d = nvg__sqrtf((*x)*(*x) + (*y)*(*y));+ if (d > 1e-6f) {+ float id = 1.0f / d;+ *x *= id;+ *y *= id;+ }+ return d;+}+++static void nvg__deletePathCache(NVGpathCache* c)+{+ if (c == NULL) return;+ if (c->points != NULL) free(c->points);+ if (c->paths != NULL) free(c->paths);+ if (c->verts != NULL) free(c->verts);+ free(c);+}++static NVGpathCache* nvg__allocPathCache(void)+{+ NVGpathCache* c = (NVGpathCache*)malloc(sizeof(NVGpathCache));+ if (c == NULL) goto error;+ memset(c, 0, sizeof(NVGpathCache));++ c->points = (NVGpoint*)malloc(sizeof(NVGpoint)*NVG_INIT_POINTS_SIZE);+ if (!c->points) goto error;+ c->npoints = 0;+ c->cpoints = NVG_INIT_POINTS_SIZE;++ c->paths = (NVGpath*)malloc(sizeof(NVGpath)*NVG_INIT_PATHS_SIZE);+ if (!c->paths) goto error;+ c->npaths = 0;+ c->cpaths = NVG_INIT_PATHS_SIZE;++ c->verts = (NVGvertex*)malloc(sizeof(NVGvertex)*NVG_INIT_VERTS_SIZE);+ if (!c->verts) goto error;+ c->nverts = 0;+ c->cverts = NVG_INIT_VERTS_SIZE;++ return c;+error:+ nvg__deletePathCache(c);+ return NULL;+}++static void nvg__setDevicePixelRatio(NVGcontext* ctx, float ratio)+{+ ctx->tessTol = 0.25f / ratio;+ ctx->distTol = 0.01f / ratio;+ ctx->fringeWidth = 1.0f / ratio;+ ctx->devicePxRatio = ratio;+}++NVGcontext* nvgCreateInternal(NVGparams* params)+{+ FONSparams fontParams;+ NVGcontext* ctx = (NVGcontext*)malloc(sizeof(NVGcontext));+ int i;+ if (ctx == NULL) goto error;+ memset(ctx, 0, sizeof(NVGcontext));++ ctx->params = *params;+ for (i = 0; i < NVG_MAX_FONTIMAGES; i++)+ ctx->fontImages[i] = 0;++ ctx->commands = (float*)malloc(sizeof(float)*NVG_INIT_COMMANDS_SIZE);+ if (!ctx->commands) goto error;+ ctx->ncommands = 0;+ ctx->ccommands = NVG_INIT_COMMANDS_SIZE;++ ctx->cache = nvg__allocPathCache();+ if (ctx->cache == NULL) goto error;++ nvgSave(ctx);+ nvgReset(ctx);++ nvg__setDevicePixelRatio(ctx, 1.0f);++ if (ctx->params.renderCreate(ctx->params.userPtr) == 0) goto error;++ // Init font rendering+ memset(&fontParams, 0, sizeof(fontParams));+ fontParams.width = NVG_INIT_FONTIMAGE_SIZE;+ fontParams.height = NVG_INIT_FONTIMAGE_SIZE;+ fontParams.flags = FONS_ZERO_TOPLEFT;+ fontParams.renderCreate = NULL;+ fontParams.renderUpdate = NULL;+ fontParams.renderDraw = NULL;+ fontParams.renderDelete = NULL;+ fontParams.userPtr = NULL;+ ctx->fs = fonsCreateInternal(&fontParams);+ if (ctx->fs == NULL) goto error;++ // Create font texture+ ctx->fontImages[0] = ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_ALPHA, fontParams.width, fontParams.height, 0, NULL);+ if (ctx->fontImages[0] == 0) goto error;+ ctx->fontImageIdx = 0;++ return ctx;++error:+ nvgDeleteInternal(ctx);+ return 0;+}++NVGparams* nvgInternalParams(NVGcontext* ctx)+{+ return &ctx->params;+}++void nvgDeleteInternal(NVGcontext* ctx)+{+ int i;+ if (ctx == NULL) return;+ if (ctx->commands != NULL) free(ctx->commands);+ if (ctx->cache != NULL) nvg__deletePathCache(ctx->cache);++ if (ctx->fs)+ fonsDeleteInternal(ctx->fs);++ for (i = 0; i < NVG_MAX_FONTIMAGES; i++) {+ if (ctx->fontImages[i] != 0) {+ nvgDeleteImage(ctx, ctx->fontImages[i]);+ ctx->fontImages[i] = 0;+ }+ }++ if (ctx->params.renderDelete != NULL)+ ctx->params.renderDelete(ctx->params.userPtr);++ free(ctx);+}++void nvgBeginFrame(NVGcontext* ctx, int windowWidth, int windowHeight, float devicePixelRatio)+{+/* printf("Tris: draws:%d fill:%d stroke:%d text:%d TOT:%d\n",+ ctx->drawCallCount, ctx->fillTriCount, ctx->strokeTriCount, ctx->textTriCount,+ ctx->fillTriCount+ctx->strokeTriCount+ctx->textTriCount);*/++ ctx->nstates = 0;+ nvgSave(ctx);+ nvgReset(ctx);++ nvg__setDevicePixelRatio(ctx, devicePixelRatio);++ ctx->params.renderViewport(ctx->params.userPtr, windowWidth, windowHeight);++ ctx->drawCallCount = 0;+ ctx->fillTriCount = 0;+ ctx->strokeTriCount = 0;+ ctx->textTriCount = 0;+}++void nvgCancelFrame(NVGcontext* ctx)+{+ ctx->params.renderCancel(ctx->params.userPtr);+}++void nvgEndFrame(NVGcontext* ctx)+{+ ctx->params.renderFlush(ctx->params.userPtr);+ if (ctx->fontImageIdx != 0) {+ int fontImage = ctx->fontImages[ctx->fontImageIdx];+ int i, j, iw, ih;+ // delete images that smaller than current one+ if (fontImage == 0)+ return;+ nvgImageSize(ctx, fontImage, &iw, &ih);+ for (i = j = 0; i < ctx->fontImageIdx; i++) {+ if (ctx->fontImages[i] != 0) {+ int nw, nh;+ nvgImageSize(ctx, ctx->fontImages[i], &nw, &nh);+ if (nw < iw || nh < ih)+ nvgDeleteImage(ctx, ctx->fontImages[i]);+ else+ ctx->fontImages[j++] = ctx->fontImages[i];+ }+ }+ // make current font image to first+ ctx->fontImages[j++] = ctx->fontImages[0];+ ctx->fontImages[0] = fontImage;+ ctx->fontImageIdx = 0;+ // clear all images after j+ for (i = j; i < NVG_MAX_FONTIMAGES; i++)+ ctx->fontImages[i] = 0;+ }+}++NVGcolor nvgRGB(unsigned char r, unsigned char g, unsigned char b)+{+ return nvgRGBA(r,g,b,255);+}++NVGcolor nvgRGBf(float r, float g, float b)+{+ return nvgRGBAf(r,g,b,1.0f);+}++NVGcolor nvgRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a)+{+ NVGcolor color;+ // Use longer initialization to suppress warning.+ color.r = r / 255.0f;+ color.g = g / 255.0f;+ color.b = b / 255.0f;+ color.a = a / 255.0f;+ return color;+}++NVGcolor nvgRGBAf(float r, float g, float b, float a)+{+ NVGcolor color;+ // Use longer initialization to suppress warning.+ color.r = r;+ color.g = g;+ color.b = b;+ color.a = a;+ return color;+}++NVGcolor nvgTransRGBA(NVGcolor c, unsigned char a)+{+ c.a = a / 255.0f;+ return c;+}++NVGcolor nvgTransRGBAf(NVGcolor c, float a)+{+ c.a = a;+ return c;+}++NVGcolor nvgLerpRGBA(NVGcolor c0, NVGcolor c1, float u)+{+ int i;+ float oneminu;+ NVGcolor cint;++ u = nvg__clampf(u, 0.0f, 1.0f);+ oneminu = 1.0f - u;+ for( i = 0; i <4; i++ )+ {+ cint.rgba[i] = c0.rgba[i] * oneminu + c1.rgba[i] * u;+ }++ return cint;+}++NVGcolor nvgHSL(float h, float s, float l)+{+ return nvgHSLA(h,s,l,255);+}++static float nvg__hue(float h, float m1, float m2)+{+ if (h < 0) h += 1;+ if (h > 1) h -= 1;+ if (h < 1.0f/6.0f)+ return m1 + (m2 - m1) * h * 6.0f;+ else if (h < 3.0f/6.0f)+ return m2;+ else if (h < 4.0f/6.0f)+ return m1 + (m2 - m1) * (2.0f/3.0f - h) * 6.0f;+ return m1;+}++NVGcolor nvgHSLA(float h, float s, float l, unsigned char a)+{+ float m1, m2;+ NVGcolor col;+ h = nvg__modf(h, 1.0f);+ if (h < 0.0f) h += 1.0f;+ s = nvg__clampf(s, 0.0f, 1.0f);+ l = nvg__clampf(l, 0.0f, 1.0f);+ m2 = l <= 0.5f ? (l * (1 + s)) : (l + s - l * s);+ m1 = 2 * l - m2;+ col.r = nvg__clampf(nvg__hue(h + 1.0f/3.0f, m1, m2), 0.0f, 1.0f);+ col.g = nvg__clampf(nvg__hue(h, m1, m2), 0.0f, 1.0f);+ col.b = nvg__clampf(nvg__hue(h - 1.0f/3.0f, m1, m2), 0.0f, 1.0f);+ col.a = a/255.0f;+ return col;+}+++static NVGstate* nvg__getState(NVGcontext* ctx)+{+ return &ctx->states[ctx->nstates-1];+}++void nvgTransformIdentity(float* t)+{+ t[0] = 1.0f; t[1] = 0.0f;+ t[2] = 0.0f; t[3] = 1.0f;+ t[4] = 0.0f; t[5] = 0.0f;+}++void nvgTransformTranslate(float* t, float tx, float ty)+{+ t[0] = 1.0f; t[1] = 0.0f;+ t[2] = 0.0f; t[3] = 1.0f;+ t[4] = tx; t[5] = ty;+}++void nvgTransformScale(float* t, float sx, float sy)+{+ t[0] = sx; t[1] = 0.0f;+ t[2] = 0.0f; t[3] = sy;+ t[4] = 0.0f; t[5] = 0.0f;+}++void nvgTransformRotate(float* t, float a)+{+ float cs = nvg__cosf(a), sn = nvg__sinf(a);+ t[0] = cs; t[1] = sn;+ t[2] = -sn; t[3] = cs;+ t[4] = 0.0f; t[5] = 0.0f;+}++void nvgTransformSkewX(float* t, float a)+{+ t[0] = 1.0f; t[1] = 0.0f;+ t[2] = nvg__tanf(a); t[3] = 1.0f;+ t[4] = 0.0f; t[5] = 0.0f;+}++void nvgTransformSkewY(float* t, float a)+{+ t[0] = 1.0f; t[1] = nvg__tanf(a);+ t[2] = 0.0f; t[3] = 1.0f;+ t[4] = 0.0f; t[5] = 0.0f;+}++void nvgTransformMultiply(float* t, const float* s)+{+ float t0 = t[0] * s[0] + t[1] * s[2];+ float t2 = t[2] * s[0] + t[3] * s[2];+ float t4 = t[4] * s[0] + t[5] * s[2] + s[4];+ t[1] = t[0] * s[1] + t[1] * s[3];+ t[3] = t[2] * s[1] + t[3] * s[3];+ t[5] = t[4] * s[1] + t[5] * s[3] + s[5];+ t[0] = t0;+ t[2] = t2;+ t[4] = t4;+}++void nvgTransformPremultiply(float* t, const float* s)+{+ float s2[6];+ memcpy(s2, s, sizeof(float)*6);+ nvgTransformMultiply(s2, t);+ memcpy(t, s2, sizeof(float)*6);+}++int nvgTransformInverse(float* inv, const float* t)+{+ double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1];+ if (det > -1e-6 && det < 1e-6) {+ nvgTransformIdentity(inv);+ return 0;+ }+ invdet = 1.0 / det;+ inv[0] = (float)(t[3] * invdet);+ inv[2] = (float)(-t[2] * invdet);+ inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet);+ inv[1] = (float)(-t[1] * invdet);+ inv[3] = (float)(t[0] * invdet);+ inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet);+ return 1;+}++void nvgTransformPoint(float* dx, float* dy, const float* t, float sx, float sy)+{+ *dx = sx*t[0] + sy*t[2] + t[4];+ *dy = sx*t[1] + sy*t[3] + t[5];+}++float nvgDegToRad(float deg)+{+ return deg / 180.0f * NVG_PI;+}++float nvgRadToDeg(float rad)+{+ return rad / NVG_PI * 180.0f;+}++static void nvg__setPaintColor(NVGpaint* p, NVGcolor color)+{+ memset(p, 0, sizeof(*p));+ nvgTransformIdentity(p->xform);+ p->radius = 0.0f;+ p->feather = 1.0f;+ p->innerColor = color;+ p->outerColor = color;+}+++// State handling+void nvgSave(NVGcontext* ctx)+{+ if (ctx->nstates >= NVG_MAX_STATES)+ return;+ if (ctx->nstates > 0)+ memcpy(&ctx->states[ctx->nstates], &ctx->states[ctx->nstates-1], sizeof(NVGstate));+ ctx->nstates++;+}++void nvgRestore(NVGcontext* ctx)+{+ if (ctx->nstates <= 1)+ return;+ ctx->nstates--;+}++void nvgReset(NVGcontext* ctx)+{+ NVGstate* state = nvg__getState(ctx);+ memset(state, 0, sizeof(*state));++ nvg__setPaintColor(&state->fill, nvgRGBA(255,255,255,255));+ nvg__setPaintColor(&state->stroke, nvgRGBA(0,0,0,255));+ state->strokeWidth = 1.0f;+ state->miterLimit = 10.0f;+ state->lineCap = NVG_BUTT;+ state->lineJoin = NVG_MITER;+ state->alpha = 1.0f;+ nvgTransformIdentity(state->xform);++ state->scissor.extent[0] = -1.0f;+ state->scissor.extent[1] = -1.0f;++ state->fontSize = 16.0f;+ state->letterSpacing = 0.0f;+ state->lineHeight = 1.0f;+ state->fontBlur = 0.0f;+ state->textAlign = NVG_ALIGN_LEFT | NVG_ALIGN_BASELINE;+ state->fontId = 0;+}++// State setting+void nvgStrokeWidth(NVGcontext* ctx, float width)+{+ NVGstate* state = nvg__getState(ctx);+ state->strokeWidth = width;+}++void nvgMiterLimit(NVGcontext* ctx, float limit)+{+ NVGstate* state = nvg__getState(ctx);+ state->miterLimit = limit;+}++void nvgLineCap(NVGcontext* ctx, int cap)+{+ NVGstate* state = nvg__getState(ctx);+ state->lineCap = cap;+}++void nvgLineJoin(NVGcontext* ctx, int join)+{+ NVGstate* state = nvg__getState(ctx);+ state->lineJoin = join;+}++void nvgGlobalAlpha(NVGcontext* ctx, float alpha)+{+ NVGstate* state = nvg__getState(ctx);+ state->alpha = alpha;+}++void nvgTransform(NVGcontext* ctx, float a, float b, float c, float d, float e, float f)+{+ NVGstate* state = nvg__getState(ctx);+ float t[6] = { a, b, c, d, e, f };+ nvgTransformPremultiply(state->xform, t);+}++void nvgResetTransform(NVGcontext* ctx)+{+ NVGstate* state = nvg__getState(ctx);+ nvgTransformIdentity(state->xform);+}++void nvgTranslate(NVGcontext* ctx, float x, float y)+{+ NVGstate* state = nvg__getState(ctx);+ float t[6];+ nvgTransformTranslate(t, x,y);+ nvgTransformPremultiply(state->xform, t);+}++void nvgRotate(NVGcontext* ctx, float angle)+{+ NVGstate* state = nvg__getState(ctx);+ float t[6];+ nvgTransformRotate(t, angle);+ nvgTransformPremultiply(state->xform, t);+}++void nvgSkewX(NVGcontext* ctx, float angle)+{+ NVGstate* state = nvg__getState(ctx);+ float t[6];+ nvgTransformSkewX(t, angle);+ nvgTransformPremultiply(state->xform, t);+}++void nvgSkewY(NVGcontext* ctx, float angle)+{+ NVGstate* state = nvg__getState(ctx);+ float t[6];+ nvgTransformSkewY(t, angle);+ nvgTransformPremultiply(state->xform, t);+}++void nvgScale(NVGcontext* ctx, float x, float y)+{+ NVGstate* state = nvg__getState(ctx);+ float t[6];+ nvgTransformScale(t, x,y);+ nvgTransformPremultiply(state->xform, t);+}++void nvgCurrentTransform(NVGcontext* ctx, float* xform)+{+ NVGstate* state = nvg__getState(ctx);+ if (xform == NULL) return;+ memcpy(xform, state->xform, sizeof(float)*6);+}++void nvgStrokeColor(NVGcontext* ctx, NVGcolor color)+{+ NVGstate* state = nvg__getState(ctx);+ nvg__setPaintColor(&state->stroke, color);+}++void nvgStrokePaint(NVGcontext* ctx, NVGpaint paint)+{+ NVGstate* state = nvg__getState(ctx);+ state->stroke = paint;+ nvgTransformMultiply(state->stroke.xform, state->xform);+}++void nvgFillColor(NVGcontext* ctx, NVGcolor color)+{+ NVGstate* state = nvg__getState(ctx);+ nvg__setPaintColor(&state->fill, color);+}++void nvgFillPaint(NVGcontext* ctx, NVGpaint paint)+{+ NVGstate* state = nvg__getState(ctx);+ state->fill = paint;+ nvgTransformMultiply(state->fill.xform, state->xform);+}++int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags)+{+ int w, h, n, image;+ unsigned char* img;+ stbi_set_unpremultiply_on_load(1);+ stbi_convert_iphone_png_to_rgb(1);+ img = stbi_load(filename, &w, &h, &n, 4);+ if (img == NULL) {+// printf("Failed to load %s - %s\n", filename, stbi_failure_reason());+ return 0;+ }+ image = nvgCreateImageRGBA(ctx, w, h, imageFlags, img);+ stbi_image_free(img);+ return image;+}++int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, unsigned char* data, int ndata)+{+ int w, h, n, image;+ unsigned char* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4);+ if (img == NULL) {+// printf("Failed to load %s - %s\n", filename, stbi_failure_reason());+ return 0;+ }+ image = nvgCreateImageRGBA(ctx, w, h, imageFlags, img);+ stbi_image_free(img);+ return image;+}++int nvgCreateImageRGBA(NVGcontext* ctx, int w, int h, int imageFlags, const unsigned char* data)+{+ return ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_RGBA, w, h, imageFlags, data);+}++void nvgUpdateImage(NVGcontext* ctx, int image, const unsigned char* data)+{+ int w, h;+ ctx->params.renderGetTextureSize(ctx->params.userPtr, image, &w, &h);+ ctx->params.renderUpdateTexture(ctx->params.userPtr, image, 0,0, w,h, data);+}++void nvgImageSize(NVGcontext* ctx, int image, int* w, int* h)+{+ ctx->params.renderGetTextureSize(ctx->params.userPtr, image, w, h);+}++void nvgDeleteImage(NVGcontext* ctx, int image)+{+ ctx->params.renderDeleteTexture(ctx->params.userPtr, image);+}++NVGpaint nvgLinearGradient(NVGcontext* ctx,+ float sx, float sy, float ex, float ey,+ NVGcolor icol, NVGcolor ocol)+{+ NVGpaint p;+ float dx, dy, d;+ const float large = 1e5;+ NVG_NOTUSED(ctx);+ memset(&p, 0, sizeof(p));++ // Calculate transform aligned to the line+ dx = ex - sx;+ dy = ey - sy;+ d = sqrtf(dx*dx + dy*dy);+ if (d > 0.0001f) {+ dx /= d;+ dy /= d;+ } else {+ dx = 0;+ dy = 1;+ }++ p.xform[0] = dy; p.xform[1] = -dx;+ p.xform[2] = dx; p.xform[3] = dy;+ p.xform[4] = sx - dx*large; p.xform[5] = sy - dy*large;++ p.extent[0] = large;+ p.extent[1] = large + d*0.5f;++ p.radius = 0.0f;++ p.feather = nvg__maxf(1.0f, d);++ p.innerColor = icol;+ p.outerColor = ocol;++ return p;+}++NVGpaint nvgRadialGradient(NVGcontext* ctx,+ float cx, float cy, float inr, float outr,+ NVGcolor icol, NVGcolor ocol)+{+ NVGpaint p;+ float r = (inr+outr)*0.5f;+ float f = (outr-inr);+ NVG_NOTUSED(ctx);+ memset(&p, 0, sizeof(p));++ nvgTransformIdentity(p.xform);+ p.xform[4] = cx;+ p.xform[5] = cy;++ p.extent[0] = r;+ p.extent[1] = r;++ p.radius = r;++ p.feather = nvg__maxf(1.0f, f);++ p.innerColor = icol;+ p.outerColor = ocol;++ return p;+}++NVGpaint nvgBoxGradient(NVGcontext* ctx,+ float x, float y, float w, float h, float r, float f,+ NVGcolor icol, NVGcolor ocol)+{+ NVGpaint p;+ NVG_NOTUSED(ctx);+ memset(&p, 0, sizeof(p));++ nvgTransformIdentity(p.xform);+ p.xform[4] = x+w*0.5f;+ p.xform[5] = y+h*0.5f;++ p.extent[0] = w*0.5f;+ p.extent[1] = h*0.5f;++ p.radius = r;++ p.feather = nvg__maxf(1.0f, f);++ p.innerColor = icol;+ p.outerColor = ocol;++ return p;+}+++NVGpaint nvgImagePattern(NVGcontext* ctx,+ float cx, float cy, float w, float h, float angle,+ int image, float alpha)+{+ NVGpaint p;+ NVG_NOTUSED(ctx);+ memset(&p, 0, sizeof(p));++ nvgTransformRotate(p.xform, angle);+ p.xform[4] = cx;+ p.xform[5] = cy;++ p.extent[0] = w;+ p.extent[1] = h;++ p.image = image;++ p.innerColor = p.outerColor = nvgRGBAf(1,1,1,alpha);++ return p;+}++// Scissoring+void nvgScissor(NVGcontext* ctx, float x, float y, float w, float h)+{+ NVGstate* state = nvg__getState(ctx);++ w = nvg__maxf(0.0f, w);+ h = nvg__maxf(0.0f, h);++ nvgTransformIdentity(state->scissor.xform);+ state->scissor.xform[4] = x+w*0.5f;+ state->scissor.xform[5] = y+h*0.5f;+ nvgTransformMultiply(state->scissor.xform, state->xform);++ state->scissor.extent[0] = w*0.5f;+ state->scissor.extent[1] = h*0.5f;+}++static void nvg__isectRects(float* dst,+ float ax, float ay, float aw, float ah,+ float bx, float by, float bw, float bh)+{+ float minx = nvg__maxf(ax, bx);+ float miny = nvg__maxf(ay, by);+ float maxx = nvg__minf(ax+aw, bx+bw);+ float maxy = nvg__minf(ay+ah, by+bh);+ dst[0] = minx;+ dst[1] = miny;+ dst[2] = nvg__maxf(0.0f, maxx - minx);+ dst[3] = nvg__maxf(0.0f, maxy - miny);+}++void nvgIntersectScissor(NVGcontext* ctx, float x, float y, float w, float h)+{+ NVGstate* state = nvg__getState(ctx);+ float pxform[6], invxorm[6];+ float rect[4];+ float ex, ey, tex, tey;++ // If no previous scissor has been set, set the scissor as current scissor.+ if (state->scissor.extent[0] < 0) {+ nvgScissor(ctx, x, y, w, h);+ return;+ }++ // Transform the current scissor rect into current transform space.+ // If there is difference in rotation, this will be approximation.+ memcpy(pxform, state->scissor.xform, sizeof(float)*6);+ ex = state->scissor.extent[0];+ ey = state->scissor.extent[1];+ nvgTransformInverse(invxorm, state->xform);+ nvgTransformMultiply(pxform, invxorm);+ tex = ex*nvg__absf(pxform[0]) + ey*nvg__absf(pxform[2]);+ tey = ex*nvg__absf(pxform[1]) + ey*nvg__absf(pxform[3]);++ // Intersect rects.+ nvg__isectRects(rect, pxform[4]-tex,pxform[5]-tey,tex*2,tey*2, x,y,w,h);++ nvgScissor(ctx, rect[0], rect[1], rect[2], rect[3]);+}++void nvgResetScissor(NVGcontext* ctx)+{+ NVGstate* state = nvg__getState(ctx);+ memset(state->scissor.xform, 0, sizeof(state->scissor.xform));+ state->scissor.extent[0] = -1.0f;+ state->scissor.extent[1] = -1.0f;+}++static int nvg__ptEquals(float x1, float y1, float x2, float y2, float tol)+{+ float dx = x2 - x1;+ float dy = y2 - y1;+ return dx*dx + dy*dy < tol*tol;+}++static float nvg__distPtSeg(float x, float y, float px, float py, float qx, float qy)+{+ float pqx, pqy, dx, dy, d, t;+ pqx = qx-px;+ pqy = qy-py;+ dx = x-px;+ dy = y-py;+ d = pqx*pqx + pqy*pqy;+ t = pqx*dx + pqy*dy;+ if (d > 0) t /= d;+ if (t < 0) t = 0;+ else if (t > 1) t = 1;+ dx = px + t*pqx - x;+ dy = py + t*pqy - y;+ return dx*dx + dy*dy;+}++static void nvg__appendCommands(NVGcontext* ctx, float* vals, int nvals)+{+ NVGstate* state = nvg__getState(ctx);+ int i;++ if (ctx->ncommands+nvals > ctx->ccommands) {+ float* commands;+ int ccommands = ctx->ncommands+nvals + ctx->ccommands/2;+ commands = (float*)realloc(ctx->commands, sizeof(float)*ccommands);+ if (commands == NULL) return;+ ctx->commands = commands;+ ctx->ccommands = ccommands;+ }++ if ((int)vals[0] != NVG_CLOSE && (int)vals[0] != NVG_WINDING) {+ ctx->commandx = vals[nvals-2];+ ctx->commandy = vals[nvals-1];+ }++ // transform commands+ i = 0;+ while (i < nvals) {+ int cmd = (int)vals[i];+ switch (cmd) {+ case NVG_MOVETO:+ nvgTransformPoint(&vals[i+1],&vals[i+2], state->xform, vals[i+1],vals[i+2]);+ i += 3;+ break;+ case NVG_LINETO:+ nvgTransformPoint(&vals[i+1],&vals[i+2], state->xform, vals[i+1],vals[i+2]);+ i += 3;+ break;+ case NVG_BEZIERTO:+ nvgTransformPoint(&vals[i+1],&vals[i+2], state->xform, vals[i+1],vals[i+2]);+ nvgTransformPoint(&vals[i+3],&vals[i+4], state->xform, vals[i+3],vals[i+4]);+ nvgTransformPoint(&vals[i+5],&vals[i+6], state->xform, vals[i+5],vals[i+6]);+ i += 7;+ break;+ case NVG_CLOSE:+ i++;+ break;+ case NVG_WINDING:+ i += 2;+ break;+ default:+ i++;+ }+ }++ memcpy(&ctx->commands[ctx->ncommands], vals, nvals*sizeof(float));++ ctx->ncommands += nvals;+}+++static void nvg__clearPathCache(NVGcontext* ctx)+{+ ctx->cache->npoints = 0;+ ctx->cache->npaths = 0;+}++static NVGpath* nvg__lastPath(NVGcontext* ctx)+{+ if (ctx->cache->npaths > 0)+ return &ctx->cache->paths[ctx->cache->npaths-1];+ return NULL;+}++static void nvg__addPath(NVGcontext* ctx)+{+ NVGpath* path;+ if (ctx->cache->npaths+1 > ctx->cache->cpaths) {+ NVGpath* paths;+ int cpaths = ctx->cache->npaths+1 + ctx->cache->cpaths/2;+ paths = (NVGpath*)realloc(ctx->cache->paths, sizeof(NVGpath)*cpaths);+ if (paths == NULL) return;+ ctx->cache->paths = paths;+ ctx->cache->cpaths = cpaths;+ }+ path = &ctx->cache->paths[ctx->cache->npaths];+ memset(path, 0, sizeof(*path));+ path->first = ctx->cache->npoints;+ path->winding = NVG_CCW;++ ctx->cache->npaths++;+}++static NVGpoint* nvg__lastPoint(NVGcontext* ctx)+{+ if (ctx->cache->npoints > 0)+ return &ctx->cache->points[ctx->cache->npoints-1];+ return NULL;+}++static void nvg__addPoint(NVGcontext* ctx, float x, float y, int flags)+{+ NVGpath* path = nvg__lastPath(ctx);+ NVGpoint* pt;+ if (path == NULL) return;++ if (path->count > 0 && ctx->cache->npoints > 0) {+ pt = nvg__lastPoint(ctx);+ if (nvg__ptEquals(pt->x,pt->y, x,y, ctx->distTol)) {+ pt->flags |= flags;+ return;+ }+ }++ if (ctx->cache->npoints+1 > ctx->cache->cpoints) {+ NVGpoint* points;+ int cpoints = ctx->cache->npoints+1 + ctx->cache->cpoints/2;+ points = (NVGpoint*)realloc(ctx->cache->points, sizeof(NVGpoint)*cpoints);+ if (points == NULL) return;+ ctx->cache->points = points;+ ctx->cache->cpoints = cpoints;+ }++ pt = &ctx->cache->points[ctx->cache->npoints];+ memset(pt, 0, sizeof(*pt));+ pt->x = x;+ pt->y = y;+ pt->flags = (unsigned char)flags;++ ctx->cache->npoints++;+ path->count++;+}++static void nvg__closePath(NVGcontext* ctx)+{+ NVGpath* path = nvg__lastPath(ctx);+ if (path == NULL) return;+ path->closed = 1;+}++static void nvg__pathWinding(NVGcontext* ctx, int winding)+{+ NVGpath* path = nvg__lastPath(ctx);+ if (path == NULL) return;+ path->winding = winding;+}++static float nvg__getAverageScale(float *t)+{+ float sx = sqrtf(t[0]*t[0] + t[2]*t[2]);+ float sy = sqrtf(t[1]*t[1] + t[3]*t[3]);+ return (sx + sy) * 0.5f;+}++static NVGvertex* nvg__allocTempVerts(NVGcontext* ctx, int nverts)+{+ if (nverts > ctx->cache->cverts) {+ NVGvertex* verts;+ int cverts = (nverts + 0xff) & ~0xff; // Round up to prevent allocations when things change just slightly.+ verts = (NVGvertex*)realloc(ctx->cache->verts, sizeof(NVGvertex)*cverts);+ if (verts == NULL) return NULL;+ ctx->cache->verts = verts;+ ctx->cache->cverts = cverts;+ }++ return ctx->cache->verts;+}++static float nvg__triarea2(float ax, float ay, float bx, float by, float cx, float cy)+{+ float abx = bx - ax;+ float aby = by - ay;+ float acx = cx - ax;+ float acy = cy - ay;+ return acx*aby - abx*acy;+}++static float nvg__polyArea(NVGpoint* pts, int npts)+{+ int i;+ float area = 0;+ for (i = 2; i < npts; i++) {+ NVGpoint* a = &pts[0];+ NVGpoint* b = &pts[i-1];+ NVGpoint* c = &pts[i];+ area += nvg__triarea2(a->x,a->y, b->x,b->y, c->x,c->y);+ }+ return area * 0.5f;+}++static void nvg__polyReverse(NVGpoint* pts, int npts)+{+ NVGpoint tmp;+ int i = 0, j = npts-1;+ while (i < j) {+ tmp = pts[i];+ pts[i] = pts[j];+ pts[j] = tmp;+ i++;+ j--;+ }+}+++static void nvg__vset(NVGvertex* vtx, float x, float y, float u, float v)+{+ vtx->x = x;+ vtx->y = y;+ vtx->u = u;+ vtx->v = v;+}++static void nvg__tesselateBezier(NVGcontext* ctx,+ float x1, float y1, float x2, float y2,+ float x3, float y3, float x4, float y4,+ int level, int type)+{+ float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234;+ float dx,dy,d2,d3;++ if (level > 10) return;++ x12 = (x1+x2)*0.5f;+ y12 = (y1+y2)*0.5f;+ x23 = (x2+x3)*0.5f;+ y23 = (y2+y3)*0.5f;+ x34 = (x3+x4)*0.5f;+ y34 = (y3+y4)*0.5f;+ x123 = (x12+x23)*0.5f;+ y123 = (y12+y23)*0.5f;++ dx = x4 - x1;+ dy = y4 - y1;+ d2 = nvg__absf(((x2 - x4) * dy - (y2 - y4) * dx));+ d3 = nvg__absf(((x3 - x4) * dy - (y3 - y4) * dx));++ if ((d2 + d3)*(d2 + d3) < ctx->tessTol * (dx*dx + dy*dy)) {+ nvg__addPoint(ctx, x4, y4, type);+ return;+ }++/* if (nvg__absf(x1+x3-x2-x2) + nvg__absf(y1+y3-y2-y2) + nvg__absf(x2+x4-x3-x3) + nvg__absf(y2+y4-y3-y3) < ctx->tessTol) {+ nvg__addPoint(ctx, x4, y4, type);+ return;+ }*/++ x234 = (x23+x34)*0.5f;+ y234 = (y23+y34)*0.5f;+ x1234 = (x123+x234)*0.5f;+ y1234 = (y123+y234)*0.5f;++ nvg__tesselateBezier(ctx, x1,y1, x12,y12, x123,y123, x1234,y1234, level+1, 0);+ nvg__tesselateBezier(ctx, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type);+}++static void nvg__flattenPaths(NVGcontext* ctx)+{+ NVGpathCache* cache = ctx->cache;+// NVGstate* state = nvg__getState(ctx);+ NVGpoint* last;+ NVGpoint* p0;+ NVGpoint* p1;+ NVGpoint* pts;+ NVGpath* path;+ int i, j;+ float* cp1;+ float* cp2;+ float* p;+ float area;++ if (cache->npaths > 0)+ return;++ // Flatten+ i = 0;+ while (i < ctx->ncommands) {+ int cmd = (int)ctx->commands[i];+ switch (cmd) {+ case NVG_MOVETO:+ nvg__addPath(ctx);+ p = &ctx->commands[i+1];+ nvg__addPoint(ctx, p[0], p[1], NVG_PT_CORNER);+ i += 3;+ break;+ case NVG_LINETO:+ p = &ctx->commands[i+1];+ nvg__addPoint(ctx, p[0], p[1], NVG_PT_CORNER);+ i += 3;+ break;+ case NVG_BEZIERTO:+ last = nvg__lastPoint(ctx);+ if (last != NULL) {+ cp1 = &ctx->commands[i+1];+ cp2 = &ctx->commands[i+3];+ p = &ctx->commands[i+5];+ nvg__tesselateBezier(ctx, last->x,last->y, cp1[0],cp1[1], cp2[0],cp2[1], p[0],p[1], 0, NVG_PT_CORNER);+ }+ i += 7;+ break;+ case NVG_CLOSE:+ nvg__closePath(ctx);+ i++;+ break;+ case NVG_WINDING:+ nvg__pathWinding(ctx, (int)ctx->commands[i+1]);+ i += 2;+ break;+ default:+ i++;+ }+ }++ cache->bounds[0] = cache->bounds[1] = 1e6f;+ cache->bounds[2] = cache->bounds[3] = -1e6f;++ // Calculate the direction and length of line segments.+ for (j = 0; j < cache->npaths; j++) {+ path = &cache->paths[j];+ pts = &cache->points[path->first];++ // If the first and last points are the same, remove the last, mark as closed path.+ p0 = &pts[path->count-1];+ p1 = &pts[0];+ if (nvg__ptEquals(p0->x,p0->y, p1->x,p1->y, ctx->distTol)) {+ path->count--;+ p0 = &pts[path->count-1];+ path->closed = 1;+ }++ // Enforce winding.+ if (path->count > 2) {+ area = nvg__polyArea(pts, path->count);+ if (path->winding == NVG_CCW && area < 0.0f)+ nvg__polyReverse(pts, path->count);+ if (path->winding == NVG_CW && area > 0.0f)+ nvg__polyReverse(pts, path->count);+ }++ for(i = 0; i < path->count; i++) {+ // Calculate segment direction and length+ p0->dx = p1->x - p0->x;+ p0->dy = p1->y - p0->y;+ p0->len = nvg__normalize(&p0->dx, &p0->dy);+ // Update bounds+ cache->bounds[0] = nvg__minf(cache->bounds[0], p0->x);+ cache->bounds[1] = nvg__minf(cache->bounds[1], p0->y);+ cache->bounds[2] = nvg__maxf(cache->bounds[2], p0->x);+ cache->bounds[3] = nvg__maxf(cache->bounds[3], p0->y);+ // Advance+ p0 = p1++;+ }+ }+}++static int nvg__curveDivs(float r, float arc, float tol)+{+ float da = acosf(r / (r + tol)) * 2.0f;+ return nvg__maxi(2, (int)ceilf(arc / da));+}++static void nvg__chooseBevel(int bevel, NVGpoint* p0, NVGpoint* p1, float w,+ float* x0, float* y0, float* x1, float* y1)+{+ if (bevel) {+ *x0 = p1->x + p0->dy * w;+ *y0 = p1->y - p0->dx * w;+ *x1 = p1->x + p1->dy * w;+ *y1 = p1->y - p1->dx * w;+ } else {+ *x0 = p1->x + p1->dmx * w;+ *y0 = p1->y + p1->dmy * w;+ *x1 = p1->x + p1->dmx * w;+ *y1 = p1->y + p1->dmy * w;+ }+}++static NVGvertex* nvg__roundJoin(NVGvertex* dst, NVGpoint* p0, NVGpoint* p1,+ float lw, float rw, float lu, float ru, int ncap, float fringe)+{+ int i, n;+ float dlx0 = p0->dy;+ float dly0 = -p0->dx;+ float dlx1 = p1->dy;+ float dly1 = -p1->dx;+ NVG_NOTUSED(fringe);++ if (p1->flags & NVG_PT_LEFT) {+ float lx0,ly0,lx1,ly1,a0,a1;+ nvg__chooseBevel(p1->flags & NVG_PR_INNERBEVEL, p0, p1, lw, &lx0,&ly0, &lx1,&ly1);+ a0 = atan2f(-dly0, -dlx0);+ a1 = atan2f(-dly1, -dlx1);+ if (a1 > a0) a1 -= NVG_PI*2;++ nvg__vset(dst, lx0, ly0, lu,1); dst++;+ nvg__vset(dst, p1->x - dlx0*rw, p1->y - dly0*rw, ru,1); dst++;++ n = nvg__clampi((int)ceilf(((a0 - a1) / NVG_PI) * ncap), 2, ncap);+ for (i = 0; i < n; i++) {+ float u = i/(float)(n-1);+ float a = a0 + u*(a1-a0);+ float rx = p1->x + cosf(a) * rw;+ float ry = p1->y + sinf(a) * rw;+ nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++;+ nvg__vset(dst, rx, ry, ru,1); dst++;+ }++ nvg__vset(dst, lx1, ly1, lu,1); dst++;+ nvg__vset(dst, p1->x - dlx1*rw, p1->y - dly1*rw, ru,1); dst++;++ } else {+ float rx0,ry0,rx1,ry1,a0,a1;+ nvg__chooseBevel(p1->flags & NVG_PR_INNERBEVEL, p0, p1, -rw, &rx0,&ry0, &rx1,&ry1);+ a0 = atan2f(dly0, dlx0);+ a1 = atan2f(dly1, dlx1);+ if (a1 < a0) a1 += NVG_PI*2;++ nvg__vset(dst, p1->x + dlx0*rw, p1->y + dly0*rw, lu,1); dst++;+ nvg__vset(dst, rx0, ry0, ru,1); dst++;++ n = nvg__clampi((int)ceilf(((a1 - a0) / NVG_PI) * ncap), 2, ncap);+ for (i = 0; i < n; i++) {+ float u = i/(float)(n-1);+ float a = a0 + u*(a1-a0);+ float lx = p1->x + cosf(a) * lw;+ float ly = p1->y + sinf(a) * lw;+ nvg__vset(dst, lx, ly, lu,1); dst++;+ nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++;+ }++ nvg__vset(dst, p1->x + dlx1*rw, p1->y + dly1*rw, lu,1); dst++;+ nvg__vset(dst, rx1, ry1, ru,1); dst++;++ }+ return dst;+}++static NVGvertex* nvg__bevelJoin(NVGvertex* dst, NVGpoint* p0, NVGpoint* p1,+ float lw, float rw, float lu, float ru, float fringe)+{+ float rx0,ry0,rx1,ry1;+ float lx0,ly0,lx1,ly1;+ float dlx0 = p0->dy;+ float dly0 = -p0->dx;+ float dlx1 = p1->dy;+ float dly1 = -p1->dx;+ NVG_NOTUSED(fringe);++ if (p1->flags & NVG_PT_LEFT) {+ nvg__chooseBevel(p1->flags & NVG_PR_INNERBEVEL, p0, p1, lw, &lx0,&ly0, &lx1,&ly1);++ nvg__vset(dst, lx0, ly0, lu,1); dst++;+ nvg__vset(dst, p1->x - dlx0*rw, p1->y - dly0*rw, ru,1); dst++;++ if (p1->flags & NVG_PT_BEVEL) {+ nvg__vset(dst, lx0, ly0, lu,1); dst++;+ nvg__vset(dst, p1->x - dlx0*rw, p1->y - dly0*rw, ru,1); dst++;++ nvg__vset(dst, lx1, ly1, lu,1); dst++;+ nvg__vset(dst, p1->x - dlx1*rw, p1->y - dly1*rw, ru,1); dst++;+ } else {+ rx0 = p1->x - p1->dmx * rw;+ ry0 = p1->y - p1->dmy * rw;++ nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++;+ nvg__vset(dst, p1->x - dlx0*rw, p1->y - dly0*rw, ru,1); dst++;++ nvg__vset(dst, rx0, ry0, ru,1); dst++;+ nvg__vset(dst, rx0, ry0, ru,1); dst++;++ nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++;+ nvg__vset(dst, p1->x - dlx1*rw, p1->y - dly1*rw, ru,1); dst++;+ }++ nvg__vset(dst, lx1, ly1, lu,1); dst++;+ nvg__vset(dst, p1->x - dlx1*rw, p1->y - dly1*rw, ru,1); dst++;++ } else {+ nvg__chooseBevel(p1->flags & NVG_PR_INNERBEVEL, p0, p1, -rw, &rx0,&ry0, &rx1,&ry1);++ nvg__vset(dst, p1->x + dlx0*lw, p1->y + dly0*lw, lu,1); dst++;+ nvg__vset(dst, rx0, ry0, ru,1); dst++;++ if (p1->flags & NVG_PT_BEVEL) {+ nvg__vset(dst, p1->x + dlx0*lw, p1->y + dly0*lw, lu,1); dst++;+ nvg__vset(dst, rx0, ry0, ru,1); dst++;++ nvg__vset(dst, p1->x + dlx1*lw, p1->y + dly1*lw, lu,1); dst++;+ nvg__vset(dst, rx1, ry1, ru,1); dst++;+ } else {+ lx0 = p1->x + p1->dmx * lw;+ ly0 = p1->y + p1->dmy * lw;++ nvg__vset(dst, p1->x + dlx0*lw, p1->y + dly0*lw, lu,1); dst++;+ nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++;++ nvg__vset(dst, lx0, ly0, lu,1); dst++;+ nvg__vset(dst, lx0, ly0, lu,1); dst++;++ nvg__vset(dst, p1->x + dlx1*lw, p1->y + dly1*lw, lu,1); dst++;+ nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++;+ }++ nvg__vset(dst, p1->x + dlx1*lw, p1->y + dly1*lw, lu,1); dst++;+ nvg__vset(dst, rx1, ry1, ru,1); dst++;+ }++ return dst;+}++static NVGvertex* nvg__buttCapStart(NVGvertex* dst, NVGpoint* p,+ float dx, float dy, float w, float d, float aa)+{+ float px = p->x - dx*d;+ float py = p->y - dy*d;+ float dlx = dy;+ float dly = -dx;+ nvg__vset(dst, px + dlx*w - dx*aa, py + dly*w - dy*aa, 0,0); dst++;+ nvg__vset(dst, px - dlx*w - dx*aa, py - dly*w - dy*aa, 1,0); dst++;+ nvg__vset(dst, px + dlx*w, py + dly*w, 0,1); dst++;+ nvg__vset(dst, px - dlx*w, py - dly*w, 1,1); dst++;+ return dst;+}++static NVGvertex* nvg__buttCapEnd(NVGvertex* dst, NVGpoint* p,+ float dx, float dy, float w, float d, float aa)+{+ float px = p->x + dx*d;+ float py = p->y + dy*d;+ float dlx = dy;+ float dly = -dx;+ nvg__vset(dst, px + dlx*w, py + dly*w, 0,1); dst++;+ nvg__vset(dst, px - dlx*w, py - dly*w, 1,1); dst++;+ nvg__vset(dst, px + dlx*w + dx*aa, py + dly*w + dy*aa, 0,0); dst++;+ nvg__vset(dst, px - dlx*w + dx*aa, py - dly*w + dy*aa, 1,0); dst++;+ return dst;+}+++static NVGvertex* nvg__roundCapStart(NVGvertex* dst, NVGpoint* p,+ float dx, float dy, float w, int ncap, float aa)+{+ int i;+ float px = p->x;+ float py = p->y;+ float dlx = dy;+ float dly = -dx;+ NVG_NOTUSED(aa);+ for (i = 0; i < ncap; i++) {+ float a = i/(float)(ncap-1)*NVG_PI;+ float ax = cosf(a) * w, ay = sinf(a) * w;+ nvg__vset(dst, px - dlx*ax - dx*ay, py - dly*ax - dy*ay, 0,1); dst++;+ nvg__vset(dst, px, py, 0.5f,1); dst++;+ }+ nvg__vset(dst, px + dlx*w, py + dly*w, 0,1); dst++;+ nvg__vset(dst, px - dlx*w, py - dly*w, 1,1); dst++;+ return dst;+}++static NVGvertex* nvg__roundCapEnd(NVGvertex* dst, NVGpoint* p,+ float dx, float dy, float w, int ncap, float aa)+{+ int i;+ float px = p->x;+ float py = p->y;+ float dlx = dy;+ float dly = -dx;+ NVG_NOTUSED(aa);+ nvg__vset(dst, px + dlx*w, py + dly*w, 0,1); dst++;+ nvg__vset(dst, px - dlx*w, py - dly*w, 1,1); dst++;+ for (i = 0; i < ncap; i++) {+ float a = i/(float)(ncap-1)*NVG_PI;+ float ax = cosf(a) * w, ay = sinf(a) * w;+ nvg__vset(dst, px, py, 0.5f,1); dst++;+ nvg__vset(dst, px - dlx*ax + dx*ay, py - dly*ax + dy*ay, 0,1); dst++;+ }+ return dst;+}+++static void nvg__calculateJoins(NVGcontext* ctx, float w, int lineJoin, float miterLimit)+{+ NVGpathCache* cache = ctx->cache;+ int i, j;+ float iw = 0.0f;++ if (w > 0.0f) iw = 1.0f / w;++ // Calculate which joins needs extra vertices to append, and gather vertex count.+ for (i = 0; i < cache->npaths; i++) {+ NVGpath* path = &cache->paths[i];+ NVGpoint* pts = &cache->points[path->first];+ NVGpoint* p0 = &pts[path->count-1];+ NVGpoint* p1 = &pts[0];+ int nleft = 0;++ path->nbevel = 0;++ for (j = 0; j < path->count; j++) {+ float dlx0, dly0, dlx1, dly1, dmr2, cross, limit;+ dlx0 = p0->dy;+ dly0 = -p0->dx;+ dlx1 = p1->dy;+ dly1 = -p1->dx;+ // Calculate extrusions+ p1->dmx = (dlx0 + dlx1) * 0.5f;+ p1->dmy = (dly0 + dly1) * 0.5f;+ dmr2 = p1->dmx*p1->dmx + p1->dmy*p1->dmy;+ if (dmr2 > 0.000001f) {+ float scale = 1.0f / dmr2;+ if (scale > 600.0f) {+ scale = 600.0f;+ }+ p1->dmx *= scale;+ p1->dmy *= scale;+ }++ // Clear flags, but keep the corner.+ p1->flags = (p1->flags & NVG_PT_CORNER) ? NVG_PT_CORNER : 0;++ // Keep track of left turns.+ cross = p1->dx * p0->dy - p0->dx * p1->dy;+ if (cross > 0.0f) {+ nleft++;+ p1->flags |= NVG_PT_LEFT;+ }++ // Calculate if we should use bevel or miter for inner join.+ limit = nvg__maxf(1.01f, nvg__minf(p0->len, p1->len) * iw);+ if ((dmr2 * limit*limit) < 1.0f)+ p1->flags |= NVG_PR_INNERBEVEL;++ // Check to see if the corner needs to be beveled.+ if (p1->flags & NVG_PT_CORNER) {+ if ((dmr2 * miterLimit*miterLimit) < 1.0f || lineJoin == NVG_BEVEL || lineJoin == NVG_ROUND) {+ p1->flags |= NVG_PT_BEVEL;+ }+ }++ if ((p1->flags & (NVG_PT_BEVEL | NVG_PR_INNERBEVEL)) != 0)+ path->nbevel++;++ p0 = p1++;+ }++ path->convex = (nleft == path->count) ? 1 : 0;+ }+}+++static int nvg__expandStroke(NVGcontext* ctx, float w, int lineCap, int lineJoin, float miterLimit)+{+ NVGpathCache* cache = ctx->cache;+ NVGvertex* verts;+ NVGvertex* dst;+ int cverts, i, j;+ float aa = ctx->fringeWidth;+ int ncap = nvg__curveDivs(w, NVG_PI, ctx->tessTol); // Calculate divisions per half circle.++ nvg__calculateJoins(ctx, w, lineJoin, miterLimit);++ // Calculate max vertex usage.+ cverts = 0;+ for (i = 0; i < cache->npaths; i++) {+ NVGpath* path = &cache->paths[i];+ int loop = (path->closed == 0) ? 0 : 1;+ if (lineJoin == NVG_ROUND)+ cverts += (path->count + path->nbevel*(ncap+2) + 1) * 2; // plus one for loop+ else+ cverts += (path->count + path->nbevel*5 + 1) * 2; // plus one for loop+ if (loop == 0) {+ // space for caps+ if (lineCap == NVG_ROUND) {+ cverts += (ncap*2 + 2)*2;+ } else {+ cverts += (3+3)*2;+ }+ }+ }++ verts = nvg__allocTempVerts(ctx, cverts);+ if (verts == NULL) return 0;++ for (i = 0; i < cache->npaths; i++) {+ NVGpath* path = &cache->paths[i];+ NVGpoint* pts = &cache->points[path->first];+ NVGpoint* p0;+ NVGpoint* p1;+ int s, e, loop;+ float dx, dy;++ path->fill = 0;+ path->nfill = 0;++ // Calculate fringe or stroke+ loop = (path->closed == 0) ? 0 : 1;+ dst = verts;+ path->stroke = dst;++ if (loop) {+ // Looping+ p0 = &pts[path->count-1];+ p1 = &pts[0];+ s = 0;+ e = path->count;+ } else {+ // Add cap+ p0 = &pts[0];+ p1 = &pts[1];+ s = 1;+ e = path->count-1;+ }++ if (loop == 0) {+ // Add cap+ dx = p1->x - p0->x;+ dy = p1->y - p0->y;+ nvg__normalize(&dx, &dy);+ if (lineCap == NVG_BUTT)+ dst = nvg__buttCapStart(dst, p0, dx, dy, w, -aa*0.5f, aa);+ else if (lineCap == NVG_BUTT || lineCap == NVG_SQUARE)+ dst = nvg__buttCapStart(dst, p0, dx, dy, w, w-aa, aa);+ else if (lineCap == NVG_ROUND)+ dst = nvg__roundCapStart(dst, p0, dx, dy, w, ncap, aa);+ }++ for (j = s; j < e; ++j) {+ if ((p1->flags & (NVG_PT_BEVEL | NVG_PR_INNERBEVEL)) != 0) {+ if (lineJoin == NVG_ROUND) {+ dst = nvg__roundJoin(dst, p0, p1, w, w, 0, 1, ncap, aa);+ } else {+ dst = nvg__bevelJoin(dst, p0, p1, w, w, 0, 1, aa);+ }+ } else {+ nvg__vset(dst, p1->x + (p1->dmx * w), p1->y + (p1->dmy * w), 0,1); dst++;+ nvg__vset(dst, p1->x - (p1->dmx * w), p1->y - (p1->dmy * w), 1,1); dst++;+ }+ p0 = p1++;+ }++ if (loop) {+ // Loop it+ nvg__vset(dst, verts[0].x, verts[0].y, 0,1); dst++;+ nvg__vset(dst, verts[1].x, verts[1].y, 1,1); dst++;+ } else {+ // Add cap+ dx = p1->x - p0->x;+ dy = p1->y - p0->y;+ nvg__normalize(&dx, &dy);+ if (lineCap == NVG_BUTT)+ dst = nvg__buttCapEnd(dst, p1, dx, dy, w, -aa*0.5f, aa);+ else if (lineCap == NVG_BUTT || lineCap == NVG_SQUARE)+ dst = nvg__buttCapEnd(dst, p1, dx, dy, w, w-aa, aa);+ else if (lineCap == NVG_ROUND)+ dst = nvg__roundCapEnd(dst, p1, dx, dy, w, ncap, aa);+ }++ path->nstroke = (int)(dst - verts);++ verts = dst;+ }++ return 1;+}++static int nvg__expandFill(NVGcontext* ctx, float w, int lineJoin, float miterLimit)+{+ NVGpathCache* cache = ctx->cache;+ NVGvertex* verts;+ NVGvertex* dst;+ int cverts, convex, i, j;+ float aa = ctx->fringeWidth;+ int fringe = w > 0.0f;++ nvg__calculateJoins(ctx, w, lineJoin, miterLimit);++ // Calculate max vertex usage.+ cverts = 0;+ for (i = 0; i < cache->npaths; i++) {+ NVGpath* path = &cache->paths[i];+ cverts += path->count + path->nbevel + 1;+ if (fringe)+ cverts += (path->count + path->nbevel*5 + 1) * 2; // plus one for loop+ }++ verts = nvg__allocTempVerts(ctx, cverts);+ if (verts == NULL) return 0;++ convex = cache->npaths == 1 && cache->paths[0].convex;++ for (i = 0; i < cache->npaths; i++) {+ NVGpath* path = &cache->paths[i];+ NVGpoint* pts = &cache->points[path->first];+ NVGpoint* p0;+ NVGpoint* p1;+ float rw, lw, woff;+ float ru, lu;++ // Calculate shape vertices.+ woff = 0.5f*aa;+ dst = verts;+ path->fill = dst;++ if (fringe) {+ // Looping+ p0 = &pts[path->count-1];+ p1 = &pts[0];+ for (j = 0; j < path->count; ++j) {+ if (p1->flags & NVG_PT_BEVEL) {+ float dlx0 = p0->dy;+ float dly0 = -p0->dx;+ float dlx1 = p1->dy;+ float dly1 = -p1->dx;+ if (p1->flags & NVG_PT_LEFT) {+ float lx = p1->x + p1->dmx * woff;+ float ly = p1->y + p1->dmy * woff;+ nvg__vset(dst, lx, ly, 0.5f,1); dst++;+ } else {+ float lx0 = p1->x + dlx0 * woff;+ float ly0 = p1->y + dly0 * woff;+ float lx1 = p1->x + dlx1 * woff;+ float ly1 = p1->y + dly1 * woff;+ nvg__vset(dst, lx0, ly0, 0.5f,1); dst++;+ nvg__vset(dst, lx1, ly1, 0.5f,1); dst++;+ }+ } else {+ nvg__vset(dst, p1->x + (p1->dmx * woff), p1->y + (p1->dmy * woff), 0.5f,1); dst++;+ }+ p0 = p1++;+ }+ } else {+ for (j = 0; j < path->count; ++j) {+ nvg__vset(dst, pts[j].x, pts[j].y, 0.5f,1);+ dst++;+ }+ }++ path->nfill = (int)(dst - verts);+ verts = dst;++ // Calculate fringe+ if (fringe) {+ lw = w + woff;+ rw = w - woff;+ lu = 0;+ ru = 1;+ dst = verts;+ path->stroke = dst;++ // Create only half a fringe for convex shapes so that+ // the shape can be rendered without stenciling.+ if (convex) {+ lw = woff; // This should generate the same vertex as fill inset above.+ lu = 0.5f; // Set outline fade at middle.+ }++ // Looping+ p0 = &pts[path->count-1];+ p1 = &pts[0];++ for (j = 0; j < path->count; ++j) {+ if ((p1->flags & (NVG_PT_BEVEL | NVG_PR_INNERBEVEL)) != 0) {+ dst = nvg__bevelJoin(dst, p0, p1, lw, rw, lu, ru, ctx->fringeWidth);+ } else {+ nvg__vset(dst, p1->x + (p1->dmx * lw), p1->y + (p1->dmy * lw), lu,1); dst++;+ nvg__vset(dst, p1->x - (p1->dmx * rw), p1->y - (p1->dmy * rw), ru,1); dst++;+ }+ p0 = p1++;+ }++ // Loop it+ nvg__vset(dst, verts[0].x, verts[0].y, lu,1); dst++;+ nvg__vset(dst, verts[1].x, verts[1].y, ru,1); dst++;++ path->nstroke = (int)(dst - verts);+ verts = dst;+ } else {+ path->stroke = NULL;+ path->nstroke = 0;+ }+ }++ return 1;+}+++// Draw+void nvgBeginPath(NVGcontext* ctx)+{+ ctx->ncommands = 0;+ nvg__clearPathCache(ctx);+}++void nvgMoveTo(NVGcontext* ctx, float x, float y)+{+ float vals[] = { NVG_MOVETO, x, y };+ nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals));+}++void nvgLineTo(NVGcontext* ctx, float x, float y)+{+ float vals[] = { NVG_LINETO, x, y };+ nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals));+}++void nvgBezierTo(NVGcontext* ctx, float c1x, float c1y, float c2x, float c2y, float x, float y)+{+ float vals[] = { NVG_BEZIERTO, c1x, c1y, c2x, c2y, x, y };+ nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals));+}++void nvgQuadTo(NVGcontext* ctx, float cx, float cy, float x, float y)+{+ float x0 = ctx->commandx;+ float y0 = ctx->commandy;+ float vals[] = { NVG_BEZIERTO,+ x0 + 2.0f/3.0f*(cx - x0), y0 + 2.0f/3.0f*(cy - y0),+ x + 2.0f/3.0f*(cx - x), y + 2.0f/3.0f*(cy - y),+ x, y };+ nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals));+}++void nvgArcTo(NVGcontext* ctx, float x1, float y1, float x2, float y2, float radius)+{+ float x0 = ctx->commandx;+ float y0 = ctx->commandy;+ float dx0,dy0, dx1,dy1, a, d, cx,cy, a0,a1;+ int dir;++ if (ctx->ncommands == 0) {+ return;+ }++ // Handle degenerate cases.+ if (nvg__ptEquals(x0,y0, x1,y1, ctx->distTol) ||+ nvg__ptEquals(x1,y1, x2,y2, ctx->distTol) ||+ nvg__distPtSeg(x1,y1, x0,y0, x2,y2) < ctx->distTol*ctx->distTol ||+ radius < ctx->distTol) {+ nvgLineTo(ctx, x1,y1);+ return;+ }++ // Calculate tangential circle to lines (x0,y0)-(x1,y1) and (x1,y1)-(x2,y2).+ dx0 = x0-x1;+ dy0 = y0-y1;+ dx1 = x2-x1;+ dy1 = y2-y1;+ nvg__normalize(&dx0,&dy0);+ nvg__normalize(&dx1,&dy1);+ a = nvg__acosf(dx0*dx1 + dy0*dy1);+ d = radius / nvg__tanf(a/2.0f);++// printf("a=%f° d=%f\n", a/NVG_PI*180.0f, d);++ if (d > 10000.0f) {+ nvgLineTo(ctx, x1,y1);+ return;+ }++ if (nvg__cross(dx0,dy0, dx1,dy1) > 0.0f) {+ cx = x1 + dx0*d + dy0*radius;+ cy = y1 + dy0*d + -dx0*radius;+ a0 = nvg__atan2f(dx0, -dy0);+ a1 = nvg__atan2f(-dx1, dy1);+ dir = NVG_CW;+// printf("CW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f);+ } else {+ cx = x1 + dx0*d + -dy0*radius;+ cy = y1 + dy0*d + dx0*radius;+ a0 = nvg__atan2f(-dx0, dy0);+ a1 = nvg__atan2f(dx1, -dy1);+ dir = NVG_CCW;+// printf("CCW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f);+ }++ nvgArc(ctx, cx, cy, radius, a0, a1, dir);+}++void nvgClosePath(NVGcontext* ctx)+{+ float vals[] = { NVG_CLOSE };+ nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals));+}++void nvgPathWinding(NVGcontext* ctx, int dir)+{+ float vals[] = { NVG_WINDING, (float)dir };+ nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals));+}++void nvgArc(NVGcontext* ctx, float cx, float cy, float r, float a0, float a1, int dir)+{+ float a = 0, da = 0, hda = 0, kappa = 0;+ float dx = 0, dy = 0, x = 0, y = 0, tanx = 0, tany = 0;+ float px = 0, py = 0, ptanx = 0, ptany = 0;+ float vals[3 + 5*7 + 100];+ int i, ndivs, nvals;+ int move = ctx->ncommands > 0 ? NVG_LINETO : NVG_MOVETO;++ // Clamp angles+ da = a1 - a0;+ if (dir == NVG_CW) {+ if (nvg__absf(da) >= NVG_PI*2) {+ da = NVG_PI*2;+ } else {+ while (da < 0.0f) da += NVG_PI*2;+ }+ } else {+ if (nvg__absf(da) >= NVG_PI*2) {+ da = -NVG_PI*2;+ } else {+ while (da > 0.0f) da -= NVG_PI*2;+ }+ }++ // Split arc into max 90 degree segments.+ ndivs = nvg__maxi(1, nvg__mini((int)(nvg__absf(da) / (NVG_PI*0.5f) + 0.5f), 5));+ hda = (da / (float)ndivs) / 2.0f;+ kappa = nvg__absf(4.0f / 3.0f * (1.0f - nvg__cosf(hda)) / nvg__sinf(hda));++ if (dir == NVG_CCW)+ kappa = -kappa;++ nvals = 0;+ for (i = 0; i <= ndivs; i++) {+ a = a0 + da * (i/(float)ndivs);+ dx = nvg__cosf(a);+ dy = nvg__sinf(a);+ x = cx + dx*r;+ y = cy + dy*r;+ tanx = -dy*r*kappa;+ tany = dx*r*kappa;++ if (i == 0) {+ vals[nvals++] = (float)move;+ vals[nvals++] = x;+ vals[nvals++] = y;+ } else {+ vals[nvals++] = NVG_BEZIERTO;+ vals[nvals++] = px+ptanx;+ vals[nvals++] = py+ptany;+ vals[nvals++] = x-tanx;+ vals[nvals++] = y-tany;+ vals[nvals++] = x;+ vals[nvals++] = y;+ }+ px = x;+ py = y;+ ptanx = tanx;+ ptany = tany;+ }++ nvg__appendCommands(ctx, vals, nvals);+}++void nvgRect(NVGcontext* ctx, float x, float y, float w, float h)+{+ float vals[] = {+ NVG_MOVETO, x,y,+ NVG_LINETO, x,y+h,+ NVG_LINETO, x+w,y+h,+ NVG_LINETO, x+w,y,+ NVG_CLOSE+ };+ nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals));+}++void nvgRoundedRect(NVGcontext* ctx, float x, float y, float w, float h, float r)+{+ if (r < 0.1f) {+ nvgRect(ctx, x,y,w,h);+ return;+ }+ else {+ float rx = nvg__minf(r, nvg__absf(w)*0.5f) * nvg__signf(w), ry = nvg__minf(r, nvg__absf(h)*0.5f) * nvg__signf(h);+ float vals[] = {+ NVG_MOVETO, x, y+ry,+ NVG_LINETO, x, y+h-ry,+ NVG_BEZIERTO, x, y+h-ry*(1-NVG_KAPPA90), x+rx*(1-NVG_KAPPA90), y+h, x+rx, y+h,+ NVG_LINETO, x+w-rx, y+h,+ NVG_BEZIERTO, x+w-rx*(1-NVG_KAPPA90), y+h, x+w, y+h-ry*(1-NVG_KAPPA90), x+w, y+h-ry,+ NVG_LINETO, x+w, y+ry,+ NVG_BEZIERTO, x+w, y+ry*(1-NVG_KAPPA90), x+w-rx*(1-NVG_KAPPA90), y, x+w-rx, y,+ NVG_LINETO, x+rx, y,+ NVG_BEZIERTO, x+rx*(1-NVG_KAPPA90), y, x, y+ry*(1-NVG_KAPPA90), x, y+ry,+ NVG_CLOSE+ };+ nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals));+ }+}++void nvgEllipse(NVGcontext* ctx, float cx, float cy, float rx, float ry)+{+ float vals[] = {+ NVG_MOVETO, cx-rx, cy,+ NVG_BEZIERTO, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry,+ NVG_BEZIERTO, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy,+ NVG_BEZIERTO, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry,+ NVG_BEZIERTO, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy,+ NVG_CLOSE+ };+ nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals));+}++void nvgCircle(NVGcontext* ctx, float cx, float cy, float r)+{+ nvgEllipse(ctx, cx,cy, r,r);+}++void nvgDebugDumpPathCache(NVGcontext* ctx)+{+ const NVGpath* path;+ int i, j;++ printf("Dumping %d cached paths\n", ctx->cache->npaths);+ for (i = 0; i < ctx->cache->npaths; i++) {+ path = &ctx->cache->paths[i];+ printf(" - Path %d\n", i);+ if (path->nfill) {+ printf(" - fill: %d\n", path->nfill);+ for (j = 0; j < path->nfill; j++)+ printf("%f\t%f\n", path->fill[j].x, path->fill[j].y);+ }+ if (path->nstroke) {+ printf(" - stroke: %d\n", path->nstroke);+ for (j = 0; j < path->nstroke; j++)+ printf("%f\t%f\n", path->stroke[j].x, path->stroke[j].y);+ }+ }+}++void nvgFill(NVGcontext* ctx)+{+ NVGstate* state = nvg__getState(ctx);+ const NVGpath* path;+ NVGpaint fillPaint = state->fill;+ int i;++ nvg__flattenPaths(ctx);+ if (ctx->params.edgeAntiAlias)+ nvg__expandFill(ctx, ctx->fringeWidth, NVG_MITER, 2.4f);+ else+ nvg__expandFill(ctx, 0.0f, NVG_MITER, 2.4f);++ // Apply global alpha+ fillPaint.innerColor.a *= state->alpha;+ fillPaint.outerColor.a *= state->alpha;++ ctx->params.renderFill(ctx->params.userPtr, &fillPaint, &state->scissor, ctx->fringeWidth,+ ctx->cache->bounds, ctx->cache->paths, ctx->cache->npaths);++ // Count triangles+ for (i = 0; i < ctx->cache->npaths; i++) {+ path = &ctx->cache->paths[i];+ ctx->fillTriCount += path->nfill-2;+ ctx->fillTriCount += path->nstroke-2;+ ctx->drawCallCount += 2;+ }+}++void nvgStroke(NVGcontext* ctx)+{+ NVGstate* state = nvg__getState(ctx);+ float scale = nvg__getAverageScale(state->xform);+ float strokeWidth = nvg__clampf(state->strokeWidth * scale, 0.0f, 200.0f);+ NVGpaint strokePaint = state->stroke;+ const NVGpath* path;+ int i;++ if (strokeWidth < ctx->fringeWidth) {+ // If the stroke width is less than pixel size, use alpha to emulate coverage.+ // Since coverage is area, scale by alpha*alpha.+ float alpha = nvg__clampf(strokeWidth / ctx->fringeWidth, 0.0f, 1.0f);+ strokePaint.innerColor.a *= alpha*alpha;+ strokePaint.outerColor.a *= alpha*alpha;+ strokeWidth = ctx->fringeWidth;+ }++ // Apply global alpha+ strokePaint.innerColor.a *= state->alpha;+ strokePaint.outerColor.a *= state->alpha;++ nvg__flattenPaths(ctx);++ if (ctx->params.edgeAntiAlias)+ nvg__expandStroke(ctx, strokeWidth*0.5f + ctx->fringeWidth*0.5f, state->lineCap, state->lineJoin, state->miterLimit);+ else+ nvg__expandStroke(ctx, strokeWidth*0.5f, state->lineCap, state->lineJoin, state->miterLimit);++ ctx->params.renderStroke(ctx->params.userPtr, &strokePaint, &state->scissor, ctx->fringeWidth,+ strokeWidth, ctx->cache->paths, ctx->cache->npaths);++ // Count triangles+ for (i = 0; i < ctx->cache->npaths; i++) {+ path = &ctx->cache->paths[i];+ ctx->strokeTriCount += path->nstroke-2;+ ctx->drawCallCount++;+ }+}++// Add fonts+int nvgCreateFont(NVGcontext* ctx, const char* name, const char* path)+{+ return fonsAddFont(ctx->fs, name, path);+}++int nvgCreateFontMem(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData)+{+ return fonsAddFontMem(ctx->fs, name, data, ndata, freeData);+}++int nvgFindFont(NVGcontext* ctx, const char* name)+{+ if (name == NULL) return -1;+ return fonsGetFontByName(ctx->fs, name);+}++// State setting+void nvgFontSize(NVGcontext* ctx, float size)+{+ NVGstate* state = nvg__getState(ctx);+ state->fontSize = size;+}++void nvgFontBlur(NVGcontext* ctx, float blur)+{+ NVGstate* state = nvg__getState(ctx);+ state->fontBlur = blur;+}++void nvgTextLetterSpacing(NVGcontext* ctx, float spacing)+{+ NVGstate* state = nvg__getState(ctx);+ state->letterSpacing = spacing;+}++void nvgTextLineHeight(NVGcontext* ctx, float lineHeight)+{+ NVGstate* state = nvg__getState(ctx);+ state->lineHeight = lineHeight;+}++void nvgTextAlign(NVGcontext* ctx, int align)+{+ NVGstate* state = nvg__getState(ctx);+ state->textAlign = align;+}++void nvgFontFaceId(NVGcontext* ctx, int font)+{+ NVGstate* state = nvg__getState(ctx);+ state->fontId = font;+}++void nvgFontFace(NVGcontext* ctx, const char* font)+{+ NVGstate* state = nvg__getState(ctx);+ state->fontId = fonsGetFontByName(ctx->fs, font);+}++static float nvg__quantize(float a, float d)+{+ return ((int)(a / d + 0.5f)) * d;+}++static float nvg__getFontScale(NVGstate* state)+{+ return nvg__minf(nvg__quantize(nvg__getAverageScale(state->xform), 0.01f), 4.0f);+}++static void nvg__flushTextTexture(NVGcontext* ctx)+{+ int dirty[4];++ if (fonsValidateTexture(ctx->fs, dirty)) {+ int fontImage = ctx->fontImages[ctx->fontImageIdx];+ // Update texture+ if (fontImage != 0) {+ int iw, ih;+ const unsigned char* data = fonsGetTextureData(ctx->fs, &iw, &ih);+ int x = dirty[0];+ int y = dirty[1];+ int w = dirty[2] - dirty[0];+ int h = dirty[3] - dirty[1];+ ctx->params.renderUpdateTexture(ctx->params.userPtr, fontImage, x,y, w,h, data);+ }+ }+}++static int nvg__allocTextAtlas(NVGcontext* ctx)+{+ int iw, ih;+ nvg__flushTextTexture(ctx);+ if (ctx->fontImageIdx >= NVG_MAX_FONTIMAGES-1)+ return 0;+ // if next fontImage already have a texture+ if (ctx->fontImages[ctx->fontImageIdx+1] != 0)+ nvgImageSize(ctx, ctx->fontImages[ctx->fontImageIdx+1], &iw, &ih);+ else { // calculate the new font image size and create it.+ nvgImageSize(ctx, ctx->fontImages[ctx->fontImageIdx], &iw, &ih);+ if (iw > ih)+ ih *= 2;+ else+ iw *= 2;+ if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE)+ iw = ih = NVG_MAX_FONTIMAGE_SIZE;+ ctx->fontImages[ctx->fontImageIdx+1] = ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_ALPHA, iw, ih, 0, NULL);+ }+ ++ctx->fontImageIdx;+ fonsResetAtlas(ctx->fs, iw, ih);+ return 1;+}++static void nvg__renderText(NVGcontext* ctx, NVGvertex* verts, int nverts)+{+ NVGstate* state = nvg__getState(ctx);+ NVGpaint paint = state->fill;++ // Render triangles.+ paint.image = ctx->fontImages[ctx->fontImageIdx];++ // Apply global alpha+ paint.innerColor.a *= state->alpha;+ paint.outerColor.a *= state->alpha;++ ctx->params.renderTriangles(ctx->params.userPtr, &paint, &state->scissor, verts, nverts);++ ctx->drawCallCount++;+ ctx->textTriCount += nverts/3;+}++float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char* end)+{+ NVGstate* state = nvg__getState(ctx);+ FONStextIter iter, prevIter;+ FONSquad q;+ NVGvertex* verts;+ float scale = nvg__getFontScale(state) * ctx->devicePxRatio;+ float invscale = 1.0f / scale;+ int cverts = 0;+ int nverts = 0;++ if (end == NULL)+ end = string + strlen(string);++ if (state->fontId == FONS_INVALID) return x;++ fonsSetSize(ctx->fs, state->fontSize*scale);+ fonsSetSpacing(ctx->fs, state->letterSpacing*scale);+ fonsSetBlur(ctx->fs, state->fontBlur*scale);+ fonsSetAlign(ctx->fs, state->textAlign);+ fonsSetFont(ctx->fs, state->fontId);++ cverts = nvg__maxi(2, (int)(end - string)) * 6; // conservative estimate.+ verts = nvg__allocTempVerts(ctx, cverts);+ if (verts == NULL) return x;++ fonsTextIterInit(ctx->fs, &iter, x*scale, y*scale, string, end);+ prevIter = iter;+ while (fonsTextIterNext(ctx->fs, &iter, &q)) {+ float c[4*2];+ if (iter.prevGlyphIndex == -1) { // can not retrieve glyph?+ if (!nvg__allocTextAtlas(ctx))+ break; // no memory :(+ if (nverts != 0) {+ nvg__renderText(ctx, verts, nverts);+ nverts = 0;+ }+ iter = prevIter;+ fonsTextIterNext(ctx->fs, &iter, &q); // try again+ if (iter.prevGlyphIndex == -1) // still can not find glyph?+ break;+ }+ prevIter = iter;+ // Transform corners.+ nvgTransformPoint(&c[0],&c[1], state->xform, q.x0*invscale, q.y0*invscale);+ nvgTransformPoint(&c[2],&c[3], state->xform, q.x1*invscale, q.y0*invscale);+ nvgTransformPoint(&c[4],&c[5], state->xform, q.x1*invscale, q.y1*invscale);+ nvgTransformPoint(&c[6],&c[7], state->xform, q.x0*invscale, q.y1*invscale);+ // Create triangles+ if (nverts+6 <= cverts) {+ nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); nverts++;+ nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); nverts++;+ nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); nverts++;+ nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); nverts++;+ nvg__vset(&verts[nverts], c[6], c[7], q.s0, q.t1); nverts++;+ nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); nverts++;+ }+ }++ // TODO: add back-end bit to do this just once per frame.+ nvg__flushTextTexture(ctx);++ nvg__renderText(ctx, verts, nverts);++ return iter.x;+}++void nvgTextBox(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end)+{+ NVGstate* state = nvg__getState(ctx);+ NVGtextRow rows[2];+ int nrows = 0, i;+ int oldAlign = state->textAlign;+ int haling = state->textAlign & (NVG_ALIGN_LEFT | NVG_ALIGN_CENTER | NVG_ALIGN_RIGHT);+ int valign = state->textAlign & (NVG_ALIGN_TOP | NVG_ALIGN_MIDDLE | NVG_ALIGN_BOTTOM | NVG_ALIGN_BASELINE);+ float lineh = 0;++ if (state->fontId == FONS_INVALID) return;++ nvgTextMetrics(ctx, NULL, NULL, &lineh);++ state->textAlign = NVG_ALIGN_LEFT | valign;++ while ((nrows = nvgTextBreakLines(ctx, string, end, breakRowWidth, rows, 2))) {+ for (i = 0; i < nrows; i++) {+ NVGtextRow* row = &rows[i];+ if (haling & NVG_ALIGN_LEFT)+ nvgText(ctx, x, y, row->start, row->end);+ else if (haling & NVG_ALIGN_CENTER)+ nvgText(ctx, x + breakRowWidth*0.5f - row->width*0.5f, y, row->start, row->end);+ else if (haling & NVG_ALIGN_RIGHT)+ nvgText(ctx, x + breakRowWidth - row->width, y, row->start, row->end);+ y += lineh * state->lineHeight;+ }+ string = rows[nrows-1].next;+ }++ state->textAlign = oldAlign;+}++int nvgTextGlyphPositions(NVGcontext* ctx, float x, float y, const char* string, const char* end, NVGglyphPosition* positions, int maxPositions)+{+ NVGstate* state = nvg__getState(ctx);+ float scale = nvg__getFontScale(state) * ctx->devicePxRatio;+ float invscale = 1.0f / scale;+ FONStextIter iter, prevIter;+ FONSquad q;+ int npos = 0;++ if (state->fontId == FONS_INVALID) return 0;++ if (end == NULL)+ end = string + strlen(string);++ if (string == end)+ return 0;++ fonsSetSize(ctx->fs, state->fontSize*scale);+ fonsSetSpacing(ctx->fs, state->letterSpacing*scale);+ fonsSetBlur(ctx->fs, state->fontBlur*scale);+ fonsSetAlign(ctx->fs, state->textAlign);+ fonsSetFont(ctx->fs, state->fontId);++ fonsTextIterInit(ctx->fs, &iter, x*scale, y*scale, string, end);+ prevIter = iter;+ while (fonsTextIterNext(ctx->fs, &iter, &q)) {+ if (iter.prevGlyphIndex < 0 && nvg__allocTextAtlas(ctx)) { // can not retrieve glyph?+ iter = prevIter;+ fonsTextIterNext(ctx->fs, &iter, &q); // try again+ }+ prevIter = iter;+ positions[npos].str = iter.str;+ positions[npos].x = iter.x * invscale;+ positions[npos].minx = nvg__minf(iter.x, q.x0) * invscale;+ positions[npos].maxx = nvg__maxf(iter.nextx, q.x1) * invscale;+ npos++;+ if (npos >= maxPositions)+ break;+ }++ return npos;+}++enum NVGcodepointType {+ NVG_SPACE,+ NVG_NEWLINE,+ NVG_CHAR,+};++int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, float breakRowWidth, NVGtextRow* rows, int maxRows)+{+ NVGstate* state = nvg__getState(ctx);+ float scale = nvg__getFontScale(state) * ctx->devicePxRatio;+ float invscale = 1.0f / scale;+ FONStextIter iter, prevIter;+ FONSquad q;+ int nrows = 0;+ float rowStartX = 0;+ float rowWidth = 0;+ float rowMinX = 0;+ float rowMaxX = 0;+ const char* rowStart = NULL;+ const char* rowEnd = NULL;+ const char* wordStart = NULL;+ float wordStartX = 0;+ float wordMinX = 0;+ const char* breakEnd = NULL;+ float breakWidth = 0;+ float breakMaxX = 0;+ int type = NVG_SPACE, ptype = NVG_SPACE;+ unsigned int pcodepoint = 0;++ if (maxRows == 0) return 0;+ if (state->fontId == FONS_INVALID) return 0;++ if (end == NULL)+ end = string + strlen(string);++ if (string == end) return 0;++ fonsSetSize(ctx->fs, state->fontSize*scale);+ fonsSetSpacing(ctx->fs, state->letterSpacing*scale);+ fonsSetBlur(ctx->fs, state->fontBlur*scale);+ fonsSetAlign(ctx->fs, state->textAlign);+ fonsSetFont(ctx->fs, state->fontId);++ breakRowWidth *= scale;++ fonsTextIterInit(ctx->fs, &iter, 0, 0, string, end);+ prevIter = iter;+ while (fonsTextIterNext(ctx->fs, &iter, &q)) {+ if (iter.prevGlyphIndex < 0 && nvg__allocTextAtlas(ctx)) { // can not retrieve glyph?+ iter = prevIter;+ fonsTextIterNext(ctx->fs, &iter, &q); // try again+ }+ prevIter = iter;+ switch (iter.codepoint) {+ case 9: // \t+ case 11: // \v+ case 12: // \f+ case 32: // space+ case 0x00a0: // NBSP+ type = NVG_SPACE;+ break;+ case 10: // \n+ type = pcodepoint == 13 ? NVG_SPACE : NVG_NEWLINE;+ break;+ case 13: // \r+ type = pcodepoint == 10 ? NVG_SPACE : NVG_NEWLINE;+ break;+ case 0x0085: // NEL+ type = NVG_NEWLINE;+ break;+ default:+ type = NVG_CHAR;+ break;+ }++ if (type == NVG_NEWLINE) {+ // Always handle new lines.+ rows[nrows].start = rowStart != NULL ? rowStart : iter.str;+ rows[nrows].end = rowEnd != NULL ? rowEnd : iter.str;+ rows[nrows].width = rowWidth * invscale;+ rows[nrows].minx = rowMinX * invscale;+ rows[nrows].maxx = rowMaxX * invscale;+ rows[nrows].next = iter.next;+ nrows++;+ if (nrows >= maxRows)+ return nrows;+ // Set null break point+ breakEnd = rowStart;+ breakWidth = 0.0;+ breakMaxX = 0.0;+ // Indicate to skip the white space at the beginning of the row.+ rowStart = NULL;+ rowEnd = NULL;+ rowWidth = 0;+ rowMinX = rowMaxX = 0;+ } else {+ if (rowStart == NULL) {+ // Skip white space until the beginning of the line+ if (type == NVG_CHAR) {+ // The current char is the row so far+ rowStartX = iter.x;+ rowStart = iter.str;+ rowEnd = iter.next;+ rowWidth = iter.nextx - rowStartX; // q.x1 - rowStartX;+ rowMinX = q.x0 - rowStartX;+ rowMaxX = q.x1 - rowStartX;+ wordStart = iter.str;+ wordStartX = iter.x;+ wordMinX = q.x0 - rowStartX;+ // Set null break point+ breakEnd = rowStart;+ breakWidth = 0.0;+ breakMaxX = 0.0;+ }+ } else {+ float nextWidth = iter.nextx - rowStartX;++ // track last non-white space character+ if (type == NVG_CHAR) {+ rowEnd = iter.next;+ rowWidth = iter.nextx - rowStartX;+ rowMaxX = q.x1 - rowStartX;+ }+ // track last end of a word+ if (ptype == NVG_CHAR && type == NVG_SPACE) {+ breakEnd = iter.str;+ breakWidth = rowWidth;+ breakMaxX = rowMaxX;+ }+ // track last beginning of a word+ if (ptype == NVG_SPACE && type == NVG_CHAR) {+ wordStart = iter.str;+ wordStartX = iter.x;+ wordMinX = q.x0 - rowStartX;+ }++ // Break to new line when a character is beyond break width.+ if (type == NVG_CHAR && nextWidth > breakRowWidth) {+ // The run length is too long, need to break to new line.+ if (breakEnd == rowStart) {+ // The current word is longer than the row length, just break it from here.+ rows[nrows].start = rowStart;+ rows[nrows].end = iter.str;+ rows[nrows].width = rowWidth * invscale;+ rows[nrows].minx = rowMinX * invscale;+ rows[nrows].maxx = rowMaxX * invscale;+ rows[nrows].next = iter.str;+ nrows++;+ if (nrows >= maxRows)+ return nrows;+ rowStartX = iter.x;+ rowStart = iter.str;+ rowEnd = iter.next;+ rowWidth = iter.nextx - rowStartX;+ rowMinX = q.x0 - rowStartX;+ rowMaxX = q.x1 - rowStartX;+ wordStart = iter.str;+ wordStartX = iter.x;+ wordMinX = q.x0 - rowStartX;+ } else {+ // Break the line from the end of the last word, and start new line from the beginning of the new.+ rows[nrows].start = rowStart;+ rows[nrows].end = breakEnd;+ rows[nrows].width = breakWidth * invscale;+ rows[nrows].minx = rowMinX * invscale;+ rows[nrows].maxx = breakMaxX * invscale;+ rows[nrows].next = wordStart;+ nrows++;+ if (nrows >= maxRows)+ return nrows;+ rowStartX = wordStartX;+ rowStart = wordStart;+ rowEnd = iter.next;+ rowWidth = iter.nextx - rowStartX;+ rowMinX = wordMinX;+ rowMaxX = q.x1 - rowStartX;+ // No change to the word start+ }+ // Set null break point+ breakEnd = rowStart;+ breakWidth = 0.0;+ breakMaxX = 0.0;+ }+ }+ }++ pcodepoint = iter.codepoint;+ ptype = type;+ }++ // Break the line from the end of the last word, and start new line from the beginning of the new.+ if (rowStart != NULL) {+ rows[nrows].start = rowStart;+ rows[nrows].end = rowEnd;+ rows[nrows].width = rowWidth * invscale;+ rows[nrows].minx = rowMinX * invscale;+ rows[nrows].maxx = rowMaxX * invscale;+ rows[nrows].next = end;+ nrows++;+ }++ return nrows;+}++float nvgTextBounds(NVGcontext* ctx, float x, float y, const char* string, const char* end, float* bounds)+{+ NVGstate* state = nvg__getState(ctx);+ float scale = nvg__getFontScale(state) * ctx->devicePxRatio;+ float invscale = 1.0f / scale;+ float width;++ if (state->fontId == FONS_INVALID) return 0;++ fonsSetSize(ctx->fs, state->fontSize*scale);+ fonsSetSpacing(ctx->fs, state->letterSpacing*scale);+ fonsSetBlur(ctx->fs, state->fontBlur*scale);+ fonsSetAlign(ctx->fs, state->textAlign);+ fonsSetFont(ctx->fs, state->fontId);++ width = fonsTextBounds(ctx->fs, x*scale, y*scale, string, end, bounds);+ if (bounds != NULL) {+ // Use line bounds for height.+ fonsLineBounds(ctx->fs, y*scale, &bounds[1], &bounds[3]);+ bounds[0] *= invscale;+ bounds[1] *= invscale;+ bounds[2] *= invscale;+ bounds[3] *= invscale;+ }+ return width * invscale;+}++void nvgTextBoxBounds(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end, float* bounds)+{+ NVGstate* state = nvg__getState(ctx);+ NVGtextRow rows[2];+ float scale = nvg__getFontScale(state) * ctx->devicePxRatio;+ float invscale = 1.0f / scale;+ int nrows = 0, i;+ int oldAlign = state->textAlign;+ int haling = state->textAlign & (NVG_ALIGN_LEFT | NVG_ALIGN_CENTER | NVG_ALIGN_RIGHT);+ int valign = state->textAlign & (NVG_ALIGN_TOP | NVG_ALIGN_MIDDLE | NVG_ALIGN_BOTTOM | NVG_ALIGN_BASELINE);+ float lineh = 0, rminy = 0, rmaxy = 0;+ float minx, miny, maxx, maxy;++ if (state->fontId == FONS_INVALID) {+ if (bounds != NULL)+ bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0f;+ return;+ }++ nvgTextMetrics(ctx, NULL, NULL, &lineh);++ state->textAlign = NVG_ALIGN_LEFT | valign;++ minx = maxx = x;+ miny = maxy = y;++ fonsSetSize(ctx->fs, state->fontSize*scale);+ fonsSetSpacing(ctx->fs, state->letterSpacing*scale);+ fonsSetBlur(ctx->fs, state->fontBlur*scale);+ fonsSetAlign(ctx->fs, state->textAlign);+ fonsSetFont(ctx->fs, state->fontId);+ fonsLineBounds(ctx->fs, 0, &rminy, &rmaxy);+ rminy *= invscale;+ rmaxy *= invscale;++ while ((nrows = nvgTextBreakLines(ctx, string, end, breakRowWidth, rows, 2))) {+ for (i = 0; i < nrows; i++) {+ NVGtextRow* row = &rows[i];+ float rminx, rmaxx, dx = 0;+ // Horizontal bounds+ if (haling & NVG_ALIGN_LEFT)+ dx = 0;+ else if (haling & NVG_ALIGN_CENTER)+ dx = breakRowWidth*0.5f - row->width*0.5f;+ else if (haling & NVG_ALIGN_RIGHT)+ dx = breakRowWidth - row->width;+ rminx = x + row->minx + dx;+ rmaxx = x + row->maxx + dx;+ minx = nvg__minf(minx, rminx);+ maxx = nvg__maxf(maxx, rmaxx);+ // Vertical bounds.+ miny = nvg__minf(miny, y + rminy);+ maxy = nvg__maxf(maxy, y + rmaxy);++ y += lineh * state->lineHeight;+ }+ string = rows[nrows-1].next;+ }++ state->textAlign = oldAlign;++ if (bounds != NULL) {+ bounds[0] = minx;+ bounds[1] = miny;+ bounds[2] = maxx;+ bounds[3] = maxy;+ }+}++void nvgTextMetrics(NVGcontext* ctx, float* ascender, float* descender, float* lineh)+{+ NVGstate* state = nvg__getState(ctx);+ float scale = nvg__getFontScale(state) * ctx->devicePxRatio;+ float invscale = 1.0f / scale;++ if (state->fontId == FONS_INVALID) return;++ fonsSetSize(ctx->fs, state->fontSize*scale);+ fonsSetSpacing(ctx->fs, state->letterSpacing*scale);+ fonsSetBlur(ctx->fs, state->fontBlur*scale);+ fonsSetAlign(ctx->fs, state->textAlign);+ fonsSetFont(ctx->fs, state->fontId);++ fonsVertMetrics(ctx->fs, ascender, descender, lineh);+ if (ascender != NULL)+ *ascender *= invscale;+ if (descender != NULL)+ *descender *= invscale;+ if (lineh != NULL)+ *lineh *= invscale;+}+// vim: ft=c nu noet ts=4
+ src/NanoVG.hs view
@@ -0,0 +1,68 @@+module NanoVG+ (module NanoVG.Internal+ ,textBreakLines+ ,textGlyphPositions+ ,text+ ) where++import qualified Data.Vector as V+import Control.Monad+import qualified Data.Text as T+import Data.Text.Foreign+import Foreign.C.Types+import Foreign.Marshal.Alloc+import Foreign.Ptr+import Foreign.Storable+import qualified NanoVG.Internal as Internal+import NanoVG.Internal hiding (textBreakLines,textGlyphPositions,text)++-- | High level wrapper around NanoVG.Internal.textBreakLines+-- This uses the fonts for width calculations so make sure you have them setup properly+textBreakLines :: Context -> T.Text -> CFloat -> CInt -> (TextRow -> CInt -> IO ()) -> IO ()+textBreakLines c text' width' chunkSize f =+ withCStringLen text' $+ \(startPtr,len) ->+ allocaBytesAligned (sizeOf (undefined :: TextRow) * fromIntegral chunkSize)+ (alignment (undefined :: TextRow)) $+ \arrayPtr ->+ do let endPtr = startPtr `plusPtr` len+ loop line ptr =+ do count <-+ Internal.textBreakLines c ptr endPtr width' arrayPtr chunkSize+ when (count > 0) $+ loop (line + count) =<< readChunk line arrayPtr count+ loop 0 startPtr+ where readChunk+ :: CInt -> TextRowPtr -> CInt -> IO (Ptr CChar)+ readChunk baseline arrayPtr count =+ do forM_ [0 .. (count - 1)] $+ \i ->+ do textRow <-+ peekElemOff arrayPtr+ (fromIntegral i)+ f textRow (baseline + i)+ next <$>+ peekElemOff arrayPtr+ (fromIntegral (count - 1))++-- | High level wrapper around NanoVG.Internal.textGlyphPositions+-- Might be changed to return a vector in the future+textGlyphPositions :: Context -> CFloat -> CFloat -> Ptr CChar -> Ptr CChar -> CInt -> IO (V.Vector GlyphPosition)+textGlyphPositions c x y startPtr endPtr maxGlyphs =+ allocaBytesAligned+ (sizeOf (undefined :: GlyphPosition) * fromIntegral maxGlyphs)+ (alignment (undefined :: GlyphPosition)) $+ \arrayPtr ->+ do count <-+ Internal.textGlyphPositions c x y startPtr endPtr arrayPtr maxGlyphs+ readChunk arrayPtr count+ where readChunk+ :: GlyphPositionPtr -> CInt -> IO (V.Vector GlyphPosition)+ readChunk arrayPtr count =+ V.generateM (fromIntegral count) $+ \i ->+ peekElemOff arrayPtr+ (fromIntegral i)++text :: Context -> CFloat -> CFloat -> T.Text -> IO ()+text c x y t = withCStringLen t $ \(ptr,len) -> Internal.text c x y ptr (ptr `plusPtr` len)
+ src/NanoVG/Internal.chs view
@@ -0,0 +1,840 @@+{-# LANGUAGE ForeignFunctionInterface #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE RecordWildCards #-}+module NanoVG.Internal+ ( FileName(..)+ , Image(..)+ , Font(..)+ , Context(..)+ , Transformation(..)+ , Extent(..)+ , Color(..)+ , Paint(..)+ , Bounds(..)+ , GlyphPosition(..)+ , GlyphPositionPtr+ , TextRow(..)+ , TextRowPtr+ , CreateFlags(..)+ , Solidity(..)+ , LineCap(..)+ , Align(..)+ , Winding(..)+ , beginFrame+ , cancelFrame+ , endFrame+ -- * Color utils+ , rgb+ , rgbf+ , rgba+ , rgbaf+ , lerpRGBA+ , transRGBA+ , transRGBAf+ , hsl+ , hsla+ -- * State handling+ , save+ , restore+ , reset+ -- * Render styles+ , strokeColor+ , strokePaint+ , fillColor+ , fillPaint+ , miterLimit+ , strokeWidth+ , lineCap+ , lineJoin+ , globalAlpha+ -- * Transforms+ , resetTransform+ , transform+ , translate+ , rotate+ , skewX+ , skewY+ , scale+ , currentTransform+ , transformIdentity+ , transformTranslate+ , transformScale+ , transformRotate+ , transformSkewX+ , transformSkewY+ , transformMultiply+ , transformPremultiply+ , transformInverse+ , transformPoint+ , degToRad+ , radToDeg+ -- * Images+ , createImage+ , createImageMem+ , createImageRGBA+ , updateImage+ , imageSize+ , deleteImage+ -- * Paints+ , linearGradient+ , boxGradient+ , radialGradient+ , imagePattern+ -- * Scissoring+ , scissor+ , intersectScissor+ , resetScissor+ -- * Paths+ , beginPath+ , moveTo+ , lineTo+ , bezierTo+ , quadTo+ , arcTo+ , closePath+ , pathWinding+ , arc+ , rect+ , roundedRect+ , ellipse+ , circle+ , fill+ , stroke+ -- * Text+ , createFont+ , createFontMem+ , findFont+ , fontSize+ , fontBlur+ , textLetterSpacing+ , textLineHeight+ , textAlign+ , fontFaceId+ , fontFace+ , text+ , textBox+ , textBounds+ , textBoxBounds+ , textGlyphPositions+ , textMetrics+ , textBreakLines+ -- * GL+ , createGL3+ , deleteGL3+ , createImageFromHandleGL3+ , imageHandleGL3+ ) where++import Data.Bits hiding (rotate)+import Data.ByteString hiding (null)+import qualified Data.Set as S+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import Data.Word+import Foreign.C.String (CString)+import Foreign.C.Types+import Foreign.Marshal.Alloc+import Foreign.Marshal.Utils+import Foreign.Ptr+import Foreign.Storable+import Linear.Matrix+import Linear.V2+import Linear.V3+import Linear.V4+import Prelude hiding (null)++-- For now only the GL3 backend is supported+#define NANOVG_GL3+-- We need to include this to define GLuint+#include "GL/glew.h"+#include "nanovg.h"+#include "nanovg_gl.h"+#include "nanovg_wrapper.h"++-- | Marshal a Haskell string into a NUL terminated C string using temporary storage.+withCString :: T.Text -> (CString -> IO b) -> IO b+withCString t = useAsCString (T.encodeUtf8 t)++-- | Wrapper around 'useAsCStringLen' that uses 'CUChar's+useAsCStringLen' :: ByteString -> ((Ptr CUChar,CInt) -> IO a) -> IO a+useAsCStringLen' bs f = useAsCStringLen bs (\(ptr,len) -> f (castPtr ptr,fromIntegral len))++-- | Wrapper around 'useAsCStringLen'' that discards the length+useAsPtr :: ByteString -> (Ptr CUChar -> IO a) -> IO a+useAsPtr bs f = useAsCStringLen' bs (f . fst)++-- | Marshalling helper for a constant zero+zero :: Num a => (a -> b) -> b+zero f = f 0++-- | Marshalling helper for a constant 'nullPtr'+null :: (Ptr a -> b) -> b+null f = f nullPtr++-- | Newtype to avoid accidental use of strings+newtype FileName = FileName { unwrapFileName :: T.Text }++-- | Newtype to avoid accidental use of ints+newtype Image = Image {imageHandle :: CInt} deriving (Show,Read,Eq,Ord)++-- | Newtype to avoid accidental use of ints+newtype Font = Font {fontHandle :: CInt} deriving (Show,Read,Eq,Ord)++-- | Opaque context that needs to be passed around+{#pointer *NVGcontext as Context newtype#}++-- | Affine matrix+--+-- > [sx kx tx]+-- > [ky sy ty]+-- > [ 0 0 1]+newtype Transformation = Transformation (M23 CFloat) deriving (Show,Read,Eq,Ord)++instance Storable Transformation where+ sizeOf _ = sizeOf (0 :: CFloat) * 6+ alignment _ = alignment (0 :: CFloat)+ peek p =+ do let p' = castPtr p :: Ptr CFloat+ a <- peek p'+ b <- peekElemOff p' 1+ c <- peekElemOff p' 2+ d <- peekElemOff p' 3+ e <- peekElemOff p' 4+ f <- peekElemOff p' 5+ pure (Transformation+ (V2 (V3 a c e)+ (V3 b d f)))+ poke p (Transformation (V2 (V3 a c e) (V3 b d f))) =+ do let p' = castPtr p :: Ptr CFloat+ poke p' a+ pokeElemOff p' 1 b+ pokeElemOff p' 2 c+ pokeElemOff p' 3 d+ pokeElemOff p' 4 e+ pokeElemOff p' 5 f++newtype Extent = Extent (V2 CFloat) deriving (Show,Read,Eq,Ord)++instance Storable Extent where+ sizeOf _ = sizeOf (0 :: CFloat) * 2+ alignment _ = alignment (0 :: CFloat)+ peek p =+ do let p' = castPtr p :: Ptr CFloat+ a <- peekElemOff p' 0+ b <- peekElemOff p' 1+ pure (Extent (V2 a b))+ poke p (Extent (V2 a b)) =+ do let p' = castPtr p :: Ptr CFloat+ pokeElemOff p' 0 a+ pokeElemOff p' 1 b++-- | rgba+data Color = Color !CFloat !CFloat !CFloat !CFloat deriving (Show,Read,Eq,Ord)++{#pointer *NVGcolor as ColorPtr -> Color#}++instance Storable Color where+ sizeOf _ = sizeOf (0 :: CFloat) * 4+ alignment _ = alignment (0 :: CFloat)+ peek p =+ do let p' = castPtr p :: Ptr CFloat+ r <- peek p'+ g <- peekElemOff p' 1+ b <- peekElemOff p' 2+ a <- peekElemOff p' 3+ pure (Color r g b a)+ poke p (Color r g b a) =+ do let p' = castPtr p :: Ptr CFloat+ poke p' r+ pokeElemOff p' 1 g+ pokeElemOff p' 2 b+ pokeElemOff p' 3 a++data Paint =+ Paint {xform :: Transformation+ ,extent :: Extent+ ,radius :: !CFloat+ ,feather :: !CFloat+ ,innerColor :: !Color+ ,outerColor :: !Color+ ,image :: !Image} deriving (Show,Read,Eq,Ord)++{#pointer *NVGpaint as PaintPtr -> Paint#}++instance Storable Paint where+ sizeOf _ = 76+ alignment _ = 4+ peek p =+ do xform <- peek (castPtr (p `plusPtr` {#offsetof NVGpaint->xform#}))+ extent <- peek (castPtr (p `plusPtr` {#offsetof NVGpaint->extent#}))+ radius <- {#get NVGpaint->radius#} p+ feather <- {#get NVGpaint->feather#} p+ innerColor <- peek (castPtr (p `plusPtr` 40))+ outerColor <- peek (castPtr (p `plusPtr` 56))+ image <- peek (castPtr (p `plusPtr` 72))+ pure (Paint xform extent radius feather innerColor outerColor (Image image))+ poke p (Paint{..}) =+ do poke (castPtr (p `plusPtr` {#offsetof NVGpaint->xform#})) xform+ poke (castPtr (p `plusPtr` {#offsetof NVGpaint->extent#})) extent+ {#set NVGpaint->radius#} p radius+ {#set NVGpaint->feather#} p feather+ poke (castPtr (p `plusPtr` 40)) innerColor+ poke (castPtr (p `plusPtr` 56)) outerColor+ poke (castPtr (p `plusPtr` 72)) (imageHandle image)++{#enum NVGwinding as Winding+ {} with prefix = "NVG_"+ deriving (Show,Read,Eq,Ord)#}++{#enum NVGsolidity as Solidity+ {underscoreToCase} with prefix = "NVG_"+ deriving (Show,Read,Eq,Ord)#}++{#enum NVGlineCap as LineCap+ {underscoreToCase} with prefix = "NVG_"+ deriving (Show,Read,Eq,Ord)#}++{#enum NVGalign as Align + {underscoreToCase} with prefix = "NVG_"+ deriving (Show,Read,Eq,Ord)#}++data GlyphPosition =+ GlyphPosition { -- | Pointer of the glyph in the input string.+ str :: !(Ptr CChar)+ -- | The x-coordinate of the logical glyph position.+ , glyphX :: !CFloat+ -- | The left bound of the glyph shape.+ , glyphPosMinX :: !CFloat+ -- | The right bound of the glyph shape.+ , glyphPosMaxX :: !CFloat} deriving (Show,Eq,Ord)++{#pointer *NVGglyphPosition as GlyphPositionPtr -> GlyphPosition#}++instance Storable GlyphPosition where+ sizeOf _ = 24+ alignment _ = {#alignof NVGglyphPosition#}+ peek p =+ do str <- {#get NVGglyphPosition->str#} p+ x <- {#get NVGglyphPosition->x#} p+ minx <- {#get NVGglyphPosition->minx#} p+ maxx <- {#get NVGglyphPosition->maxx#} p+ pure (GlyphPosition str x minx maxx)+ poke p (GlyphPosition str x minx maxx) =+ do {#set NVGglyphPosition->str#} p str+ {#set NVGglyphPosition->x#} p x+ {#set NVGglyphPosition->minx#} p minx+ {#set NVGglyphPosition->maxx#} p maxx++data TextRow =+ TextRow { -- | Pointer to the input text where the row starts.+ start :: !(Ptr CChar)+ -- | Pointer to the input text where the row ends (one past the last character).+ , end :: !(Ptr CChar)+ -- | Pointer to the beginning of the next row.+ , next :: !(Ptr CChar)+ -- | Logical width of the row.+ , width :: !CFloat+ -- | Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending.+ , textRowMinX :: !CFloat+ , textRowMaxX :: !CFloat}+ deriving (Show,Eq,Ord)++instance Storable TextRow where+ sizeOf _ = 40+ alignment _ = {#alignof NVGtextRow#}+ peek p =+ do start <- {#get NVGtextRow->start#} p+ end <- {#get NVGtextRow->end#} p+ next <- {#get NVGtextRow->next#} p+ width <- {#get NVGtextRow->width#} p+ minX <- {#get NVGtextRow->minx#} p+ maxX <- {#get NVGtextRow->maxx#} p+ pure (TextRow start end next width minX maxX)+ poke p (TextRow {..}) =+ do {#set NVGtextRow->start#} p start+ {#set NVGtextRow->end#} p end+ {#set NVGtextRow->next#} p next+ {#set NVGtextRow->width#} p width+ {#set NVGtextRow->minx#} p textRowMinX+ {#set NVGtextRow->maxx#} p textRowMaxX++{#pointer *NVGtextRow as TextRowPtr -> TextRow#}++{#enum NVGimageFlags as ImageFlags+ {underscoreToCase} with prefix = "NVG_"+ deriving (Show,Read,Eq,Ord)#}++-- | Begin drawing a new frame+--+-- Calls to nanovg drawing API should be wrapped in 'beginFrame' & 'endFrame'.+--+-- 'beginFrame' defines the size of the window to render to in relation currently+-- set viewport (i.e. glViewport on GL backends). Device pixel ration allows to+-- control the rendering on Hi-DPI devices.+--+-- For example, GLFW returns two dimension for an opened window: window size and+-- frame buffer size. In that case you would set windowWidth/Height to the window size+-- devicePixelRatio to: frameBufferWidth / windowWidth.+{#fun unsafe nvgBeginFrame as beginFrame+ {`Context',`CInt',`CInt',`Float'} -> `()'#}++-- | Cancels drawing the current frame.+{#fun unsafe nvgCancelFrame as cancelFrame+ {`Context'} -> `()'#}++-- | Ends drawing flushing remaining render state.+{#fun unsafe nvgEndFrame as endFrame+ {`Context'} -> `()'#}++-- | Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f).+{#fun pure unsafe nvgRGB_ as rgb+ {id`CUChar',id`CUChar',id`CUChar',alloca-`Color'peek*} -> `()'#}++-- | Returns a color value from red, green, blue values. Alpha will be set to 1.0f.+{#fun pure unsafe nvgRGBf_ as rgbf+ {`CFloat',`CFloat',`CFloat',alloca-`Color'peek*} -> `()'#}++-- | Returns a color value from red, green, blue and alpha values.+{#fun pure unsafe nvgRGBA_ as rgba+ {id`CUChar',id`CUChar',id`CUChar',id`CUChar',alloca-`Color'peek*} -> `()'#}++-- | Returns a color value from red, green, blue and alpha values.+{#fun pure unsafe nvgRGBAf_ as rgbaf+ {`CFloat',`CFloat',`CFloat',`CFloat',alloca-`Color'peek*} -> `()'#}++-- | Linearly interpolates from color c0 to c1, and returns resulting color value.+{#fun pure unsafe nvgLerpRGBA_ as lerpRGBA+ {with*`Color',with*`Color',`CFloat',alloca-`Color'peek*} -> `()'#}++-- | Sets transparency of a color value.+{#fun pure unsafe nvgTransRGBA_ as transRGBA+ {with*`Color',id`CUChar',alloca-`Color'peek*} -> `()'#}++-- | Sets transparency of a color value.+{#fun pure unsafe nvgTransRGBAf_ as transRGBAf+ {with*`Color',`CFloat',alloca-`Color'peek*} -> `()'#}++-- | Returns color value specified by hue, saturation and lightness.+-- HSL values are all in range [0..1], alpha will be set to 255.+{#fun pure unsafe nvgHSL_ as hsl+ {`CFloat',`CFloat',`CFloat',alloca-`Color'peek*} -> `()'#}++-- | Returns color value specified by hue, saturation and lightness and alpha.+-- HSL values are all in range [0..1], alpha in range [0..255]+{#fun pure unsafe nvgHSLA_ as hsla+ {`CFloat',`CFloat',`CFloat',id`CUChar',alloca-`Color'peek*} -> `()'#}++-- | Pushes and saves the current render state into a state stack.+-- A matching 'restore' must be used to restore the state.++{#fun unsafe nvgSave as save+ {`Context'} -> `()'#}++-- | Pops and restores current render state.+{#fun unsafe nvgRestore as restore+ {`Context'} -> `()'#}++-- | Resets current render state to default values. Does not affect the render state stack.+{#fun unsafe nvgReset as reset+ {`Context'} -> `()'#}++-- | Sets current stroke style to a solid color.+{#fun unsafe nvgStrokeColor_ as strokeColor+ {`Context',with*`Color'} -> `()'#}++-- | Sets current stroke style to a paint, which can be a one of the gradients or a pattern.+{#fun unsafe nvgStrokePaint_ as strokePaint+ {`Context',with*`Paint'} -> `()'#}++-- | Sets current fill style to a solid color.+{#fun unsafe nvgFillColor_ as fillColor+ {`Context',with*`Color'} -> `()'#}++-- | Sets current fill style to a paint, which can be a one of the gradients or a pattern.+{#fun unsafe nvgFillPaint_ as fillPaint+ {`Context',with*`Paint'} -> `()'#}++-- | Sets the miter limit of the stroke style.+-- Miter limit controls when a sharp corner is beveled.+{#fun unsafe nvgMiterLimit as miterLimit+ {`Context',`CFloat'} -> `()'#}++-- | Sets the stroke width of the stroke style.+{#fun unsafe nvgStrokeWidth as strokeWidth+ {`Context',`CFloat'} -> `()'#}++-- | Sets how the end of the line (cap) is drawn,+-- Can be one of: 'Butt' (default), 'Round', 'Square'.+{#fun unsafe nvgLineCap as lineCap+ {`Context',`LineCap'} -> `()'#}++-- | Sets how sharp path corners are drawn.+-- Can be one of 'Miter' (default), 'Round', 'Bevel.+{#fun unsafe nvgLineJoin as lineJoin+ {`Context',`LineCap'} -> `()'#}++-- | Sets the transparency applied to all rendered shapes.+-- Already transparent paths will get proportionally more transparent as well.+{#fun unsafe nvgGlobalAlpha as globalAlpha+ {`Context',`CFloat'} -> `()'#}++-- | Resets current transform to a identity matrix.+{#fun unsafe nvgResetTransform as resetTransform+ {`Context'} -> `()'#}++-- | Premultiplies current coordinate system by specified matrix.+-- The parameters are interpreted as matrix as follows:+--+-- > [a c e]+-- > [b d f]+-- > [0 0 1]+{#fun unsafe nvgTransform as transform+ {`Context',`CFloat',`CFloat',`CFloat',`CFloat',`CFloat',`CFloat'} -> `()'#}++-- | Translates current coordinate system.+{#fun unsafe nvgTranslate as translate+ {`Context',`CFloat',`CFloat'} -> `()'#}++-- | Rotates current coordinate system. Angle is specified in radians.+{#fun unsafe nvgRotate as rotate+ {`Context',`CFloat'} -> `()'#}++-- | Skews the current coordinate system along X axis. Angle is specified in radians.+{#fun unsafe nvgSkewX as skewX+ {`Context',`CFloat'} -> `()'#}++-- | Skews the current coordinate system along Y axis. Angle is specified in radians.+{#fun unsafe nvgSkewY as skewY+ {`Context',`CFloat'} -> `()'#}++-- | Scales the current coordinate system.+{#fun unsafe nvgScale as scale+ {`Context',`CFloat',`CFloat'} -> `()'#}++peekTransformation :: Ptr CFloat -> IO Transformation+peekTransformation = peek . castPtr++allocaTransformation :: (Ptr CFloat -> IO b) -> IO b+allocaTransformation f = alloca (\(p :: Ptr Transformation) -> f (castPtr p))++withTransformation :: Transformation -> (Ptr CFloat -> IO b) -> IO b+withTransformation t f = with t (\p -> f (castPtr p)) ++-- | Returns the current transformation matrix.+{#fun unsafe nvgCurrentTransform as currentTransform+ {`Context',allocaTransformation-`Transformation'peekTransformation*} -> `()'#}++-- | Sets the transform to identity matrix.+{#fun unsafe nvgTransformIdentity as transformIdentity+ {allocaTransformation-`Transformation'peekTransformation*} -> `()'#}++-- | Sets the transform to translation matrix matrix.+{#fun unsafe nvgTransformTranslate as transformTranslate+ {allocaTransformation-`Transformation'peekTransformation*,`CFloat',`CFloat'} -> `()'#}++-- | Sets the transform to scale matrix.+{#fun unsafe nvgTransformScale as transformScale+ {allocaTransformation-`Transformation'peekTransformation*,`CFloat',`CFloat'} -> `()'#}++-- | Sets the transform to rotate matrix. Angle is specified in radians.+{#fun unsafe nvgTransformRotate as transformRotate+ {allocaTransformation-`Transformation'peekTransformation*,`CFloat'} -> `()'#}++-- | Sets the transform to skew-x matrix. Angle is specified in radians.+{#fun unsafe nvgTransformSkewX as transformSkewX+ {allocaTransformation-`Transformation'peekTransformation*,`CFloat'} -> `()'#}++-- | Sets the transform to skew-y matrix. Angle is specified in radians.+{#fun unsafe nvgTransformSkewY as transformSkewY+ {allocaTransformation-`Transformation'peekTransformation*,`CFloat'} -> `()'#}++-- | Sets the transform to the result of multiplication of two transforms, of A = A*B.+{#fun unsafe nvgTransformMultiply as transformMultiply+ {withTransformation*`Transformation'peekTransformation*,withTransformation*`Transformation'} -> `()'#}++-- | Sets the transform to the result of multiplication of two transforms, of A = B*A.+{#fun unsafe nvgTransformPremultiply as transformPremultiply+ {withTransformation*`Transformation'peekTransformation*,withTransformation*`Transformation'} -> `()'#}++-- | Sets the destination to inverse of specified transform.+-- Returns 1 if the inverse could be calculated, else 0.+{#fun unsafe nvgTransformInverse as transformInverse+ {allocaTransformation-`Transformation'peekTransformation*,withTransformation*`Transformation'} -> `()'#}++-- | Transform a point by given transform.+{#fun pure unsafe nvgTransformPoint as transformPoint+ {alloca-`CFloat'peek*,alloca-`CFloat'peek*,withTransformation*`Transformation',`CFloat',`CFloat'} -> `()'#}++-- | Converts degrees to radians.+{#fun pure unsafe nvgDegToRad as degToRad+ {`CFloat'} -> `CFloat'#}++-- | Converts radians to degrees.+{#fun pure unsafe nvgRadToDeg as radToDeg+ {`CFloat'} -> `CFloat'#}++safeImage :: CInt -> Maybe Image+safeImage i+ | i < 0 = Nothing+ | otherwise = Just (Image i)++-- | Creates image by loading it from the disk from specified file name.+{#fun unsafe nvgCreateImage as createImage+ {`Context','withCString.unwrapFileName'*`FileName',`CInt'} -> `Maybe Image'safeImage#}++-- | Creates image by loading it from the specified chunk of memory.+{#fun unsafe nvgCreateImageMem as createImageMem+ {`Context',`ImageFlags',useAsCStringLen'*`ByteString'&} -> `Maybe Image'safeImage#}++-- | Creates image from specified image data.+{#fun unsafe nvgCreateImageRGBA as createImageRGBA+ {`Context',`CInt',`CInt',`ImageFlags',useAsPtr*`ByteString'} -> `Maybe Image'safeImage#}++-- | Updates image data specified by image handle.+{#fun unsafe nvgUpdateImage as updateImage+ {`Context',imageHandle`Image',useAsPtr*`ByteString'} -> `()'#}++-- | Returns the dimensions of a created image.+{#fun unsafe nvgImageSize as imageSize+ {`Context',imageHandle`Image',alloca-`CInt'peek*,alloca-`CInt'peek*} -> `()'#}++-- | Deletes created image.+{#fun unsafe nvgDeleteImage as deleteImage+ {`Context',imageHandle`Image'} -> `()'#}++-- | Creates and returns a linear gradient. Parameters (sx,sy)-(ex,ey) specify the start and end coordinates+-- of the linear gradient, icol specifies the start color and ocol the end color.+-- The gradient is transformed by the current transform when it is passed to 'fillPaint' or 'strokePaint'.+{#fun unsafe nvgLinearGradient_ as linearGradient+ {`Context',`CFloat',`CFloat',`CFloat',`CFloat',with*`Color',with*`Color',alloca-`Paint'peek*} -> `()'#}++-- | Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering+-- drop shadows or highlights for boxes. Parameters (x,y) define the top-left corner of the rectangle,+-- (w,h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry+-- the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient.+-- The gradient is transformed by the current transform when it is passed to 'fillPaint' or 'strokePaint'.+{#fun unsafe nvgBoxGradient_ as boxGradient+ {`Context',`CFloat',`CFloat',`CFloat',`CFloat',`CFloat',`CFloat',with*`Color',with*`Color',alloca-`Paint'peek*} -> `()'#}++-- | Creates and returns a radial gradient. Parameters (cx,cy) specify the center, inr and outr specify+-- the inner and outer radius of the gradient, icol specifies the start color and ocol the end color.+-- The gradient is transformed by the current transform when it is passed to 'fillPaint' or 'strokePaint'.+{#fun unsafe nvgRadialGradient_ as radialGradient+ {`Context',`CFloat',`CFloat',`CFloat',`CFloat',with*`Color',with*`Color',alloca-`Paint'peek*} -> `()'#}++-- | Creates and returns an image patter. Parameters (ox,oy) specify the left-top location of the image pattern,+-- (ex,ey) the size of one image, angle rotation around the top-left corner, image is handle to the image to render.+-- The gradient is transformed by the current transform when it is passed to 'fillPaint' or 'strokePaint'.+{#fun unsafe nvgImagePattern_ as imagePattern+ {`Context',`CFloat',`CFloat',`CFloat',`CFloat',`CFloat',imageHandle`Image',`CFloat',alloca-`Paint'peek*} -> `()'#}++-- | Sets the current scissor rectangle.+-- The scissor rectangle is transformed by the current transform.+{#fun unsafe nvgScissor as scissor+ {`Context',`CFloat',`CFloat',`CFloat',`CFloat'} -> `()'#}++-- | Intersects current scissor rectangle with the specified rectangle.+-- The scissor rectangle is transformed by the current transform.+-- Note: in case the rotation of previous scissor rect differs from+-- the current one, the intersection will be done between the specified+-- rectangle and the previous scissor rectangle transformed in the current+-- transform space. The resulting shape is always rectangle.+{#fun unsafe nvgIntersectScissor as intersectScissor+ {`Context',`CFloat',`CFloat',`CFloat',`CFloat'} -> `()'#}++-- | Reset and disables scissoring.+{#fun unsafe nvgResetScissor as resetScissor+ {`Context'} -> `()'#}++-- | Clears the current path and sub-paths.+{#fun unsafe nvgBeginPath as beginPath+ {`Context'} -> `()'#}++-- | Starts new sub-path with specified point as first point.+{#fun unsafe nvgMoveTo as moveTo+ {`Context',`CFloat',`CFloat'} -> `()'#}++-- | Adds line segment from the last point in the path to the specified point.+{#fun unsafe nvgLineTo as lineTo+ {`Context',`CFloat',`CFloat'} -> `()'#}++-- | Adds cubic bezier segment from last point in the path via two control points to the specified point.+{#fun unsafe nvgBezierTo as bezierTo+ {`Context',`CFloat',`CFloat',`CFloat',`CFloat',`CFloat',`CFloat'} -> `()'#}++-- | Adds quadratic bezier segment from last point in the path via a control point to the specified point+{#fun unsafe nvgQuadTo as quadTo+ {`Context',`CFloat',`CFloat',`CFloat',`CFloat'} -> `()'#}++-- | Adds an arc segment at the corner defined by the last path point, and two specified points.+{#fun unsafe nvgArcTo as arcTo+ {`Context',`CFloat',`CFloat',`CFloat',`CFloat',`CFloat'} -> `()'#}++-- | Closes current sub-path with a line segment.+{#fun unsafe nvgClosePath as closePath+ {`Context'} -> `()'#}++-- | Sets the current sub-path winding, see NVGwinding and NVGsolidity. +{#fun unsafe nvgPathWinding as pathWinding+ {`Context', `CInt'} -> `()'#}++-- | Creates new circle arc shaped sub-path. The arc center is at cx,cy, the arc radius is r,+-- and the arc is drawn from angle a0 to a1, and swept in direction dir (NVG_CCW, or NVG_CW).+-- Angles are specified in radians.+{#fun unsafe nvgArc as arc+ {`Context',`CFloat',`CFloat', `CFloat', `CFloat', `CFloat', `Winding'} -> `()'#}++-- | Creates new rectangle shaped sub-path.+{#fun unsafe nvgRect as rect+ {`Context',`CFloat',`CFloat',`CFloat',`CFloat'} -> `()'#}++-- | Creates new rounded rectangle shaped sub-path.+{#fun unsafe nvgRoundedRect as roundedRect+ {`Context',`CFloat',`CFloat',`CFloat',`CFloat',`CFloat'} -> `()'#}++-- | Creates new ellipse shaped sub-path.+{#fun unsafe nvgEllipse as ellipse+ {`Context',`CFloat',`CFloat',`CFloat',`CFloat'} -> `()'#}++-- | Creates new circle shaped sub-path. +{#fun unsafe nvgCircle as circle+ {`Context',`CFloat',`CFloat',`CFloat'} -> `()'#}++-- | Fills the current path with current fill style.+{#fun unsafe nvgFill as fill+ {`Context'} -> `()'#}++-- | Fills the current path with current stroke style.+{#fun unsafe nvgStroke as stroke+ {`Context'} -> `()'#}++safeFont :: CInt -> Maybe Font+safeFont i+ | i < 0 = Nothing+ | otherwise = Just (Font i)++-- | Creates font by loading it from the disk from specified file name.+-- Returns handle to the font.+{#fun unsafe nvgCreateFont as createFont+ {`Context',withCString*`T.Text','withCString.unwrapFileName'*`FileName'} -> `Maybe Font'safeFont#}++-- | Creates image by loading it from the specified memory chunk.+-- Returns handle to the font.+{#fun unsafe nvgCreateFontMem as createFontMem+ {`Context',withCString*`T.Text',useAsCStringLen'*`ByteString'&,zero-`CInt'} -> `Maybe Font'safeFont#}++-- | Finds a loaded font of specified name, and returns handle to it, or -1 if the font is not found.+{#fun unsafe nvgFindFont as findFont+ {`Context', withCString*`T.Text'} -> `Maybe Font'safeFont#}++-- | Sets the font size of current text style.+{#fun unsafe nvgFontSize as fontSize+ {`Context',`CFloat'} -> `()'#}++-- | Sets the blur of current text style.+{#fun unsafe nvgFontBlur as fontBlur+ {`Context',`CFloat'} -> `()'#}++-- | Sets the letter spacing of current text style.+{#fun unsafe nvgTextLetterSpacing as textLetterSpacing+ {`Context',`CFloat'} -> `()'#}++-- | Sets the proportional line height of current text style. The line height is specified as multiple of font size. +{#fun unsafe nvgTextLineHeight as textLineHeight+ {`Context',`CFloat'} -> `()'#}++-- | Sets the text align of current text style, see NVGalign for options.+{#fun unsafe nvgTextAlign as textAlign+ {`Context',bitMask`S.Set Align'} -> `()'#}++-- | Sets the font face based on specified id of current text style.+{#fun unsafe nvgFontFaceId as fontFaceId+ {`Context',fontHandle`Font'} -> `()'#}++-- | Sets the font face based on specified name of current text styl+{#fun unsafe nvgFontFace as fontFace+ {`Context',withCString*`T.Text'} -> `()'#}++-- | Draws text string at specified location. If end is specified only the sub-string up to the end is drawn.+{#fun unsafe nvgText as text+ {`Context',`CFloat',`CFloat',id`Ptr CChar',id`Ptr CChar'} -> `()'#}++-- | Draws multi-line text string at specified location wrapped at the specified width. If end is specified only the sub-string up to the end is drawn.+-- | White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.+-- | Words longer than the max width are slit at nearest character (i.e. no hyphenation).+{#fun unsafe nvgTextBox as textBox+ {`Context',`CFloat',`CFloat',`CFloat',withCString*`T.Text',null-`Ptr CUChar'} -> `()'#}++newtype Bounds = Bounds (V4 CFloat) deriving (Show,Read,Eq,Ord)++instance Storable Bounds where+ sizeOf _ = sizeOf (0 :: CFloat) * 4+ alignment _ = alignment (0 :: CFloat)+ peek p =+ do let p' = castPtr p :: Ptr CFloat+ a <- peekElemOff p' 0+ b <- peekElemOff p' 1+ c <- peekElemOff p' 2+ d <- peekElemOff p' 3+ pure (Bounds (V4 a b c d))+ poke p (Bounds (V4 a b c d)) =+ do let p' = castPtr p :: Ptr CFloat+ pokeElemOff p' 0 a+ pokeElemOff p' 1 b+ pokeElemOff p' 2 c+ pokeElemOff p' 3 d++peekBounds :: Ptr CFloat -> IO Bounds+peekBounds = peek . castPtr++allocaBounds :: (Ptr CFloat -> IO b) -> IO b+allocaBounds f = alloca (\(p :: Ptr Bounds) -> f (castPtr p))++-- | Measures the specified text string. Parameter bounds should be a pointer to float[4],+-- if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax]+-- Returns the horizontal advance of the measured text (i.e. where the next character should drawn).+-- Measured values are returned in local coordinate space.+{#fun unsafe nvgTextBounds as textBounds+ {`Context',`CFloat',`CFloat',withCString*`T.Text',null-`Ptr CUChar', allocaBounds-`Bounds'peekBounds*} -> `()'#}++-- | Measures the specified multi-text string. Parameter bounds should be a pointer to float[4],+-- if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax]+-- Measured values are returned in local coordinate space.+{#fun unsafe nvgTextBoxBounds as textBoxBounds+ {`Context',`CFloat',`CFloat',`CFloat',withCString*`T.Text',null-`Ptr CUChar',allocaBounds-`Bounds'peekBounds*} -> `()'#}++-- | Calculates the glyph x positions of the specified text. If end is specified only the sub-string will be used.+-- Measured values are returned in local coordinate space.+{#fun unsafe nvgTextGlyphPositions as textGlyphPositions+ {`Context',`CFloat',`CFloat',id`Ptr CChar',id`Ptr CChar',`GlyphPositionPtr', `CInt'} -> `CInt'#}++-- | Returns the vertical metrics based on the current text style.+-- Measured values are returned in local coordinate space.+{#fun unsafe nvgTextMetrics as textMetrics+ {`Context',alloca-`CFloat'peek*,alloca-`CFloat'peek*,alloca-`CFloat'peek*} -> `()'#}++-- | Breaks the specified text into lines. If end is specified only the sub-string will be used.+-- White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.+-- Words longer than the max width are slit at nearest character (i.e. no hyphenation).+{#fun unsafe nvgTextBreakLines as textBreakLines+ {`Context',id`Ptr CChar',id`Ptr CChar',`CFloat',`TextRowPtr',`CInt'} -> `CInt'#}++{#enum NVGcreateFlags as CreateFlags+ {underscoreToCase} with prefix = "NVG_"+ deriving (Show,Read,Eq,Ord)#}++bitMask :: Enum a => S.Set a -> CInt+bitMask = S.fold (.|.) 0 . S.map (fromIntegral . fromEnum)++{#fun unsafe nvgCreateGL3 as createGL3+ {bitMask`S.Set CreateFlags'} -> `Context'#}+{#fun unsafe nvgDeleteGL3 as deleteGL3+ {`Context'} -> `()'#}++type GLuint = Word32++{#fun unsafe nvglCreateImageFromHandleGL3 as createImageFromHandleGL3+ {`Context',fromIntegral`GLuint',`CInt',`CInt',`CreateFlags'} -> `Image'Image#}++{#fun unsafe nvglImageHandleGL3 as imageHandleGL3+ {`Context',imageHandle`Image'} -> `GLuint'fromIntegral#}
+ test/Contexts.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}+module Contexts where++import NanoVG+import Language.C.Inline.HaskellIdentifier+import qualified Language.C.Inline.Context as C+import qualified Data.Map as M+import qualified Language.C.Types as C++nanoVGCtx :: C.Context+nanoVGCtx = mempty { C.ctxTypesTable = M.fromList [(C.TypeName "NVGcolor",[t|Color|])+ ,(C.TypeName "NVGpaint",[t|Paint|])+ ,(C.TypeName "NVGglyphPosition",[t|GlyphPosition|])+ ,(C.TypeName "NVGtextRow",[t|TextRow|])] }
+ test/NanoVGSpec.c view
@@ -0,0 +1,104 @@++#include "nanovg.h"++void inline_c_NanoVGSpec_0_df4148b45b426a534ab3d5cdd17865b8b3e9182c(float * cInPtr_inline_c_0, float * cOutPtr_inline_c_1, int n_inline_c_2) {++ float* in = cInPtr_inline_c_0;+ float* out = cOutPtr_inline_c_1;+ for (int i = 0; i < n_inline_c_2; ++i) {+ for (int j = 0; j < 6; ++j) {+ out[6*i+j] = in[6*i+j];+ }+ }+ +}+++void inline_c_NanoVGSpec_1_1b535a31826c527af6f71272ebb477556ed8af0f(float * cInPtr_inline_c_0, float * cOutPtr_inline_c_1, int n_inline_c_2) {++ float* in = cInPtr_inline_c_0;+ float* out = cOutPtr_inline_c_1;+ for (int i = 0; i < n_inline_c_2; ++i) {+ out[2*i] = in[2*i];+ out[2*i+1] = in[2*i+1];+ }+ +}+++void inline_c_NanoVGSpec_2_c7afee80798b912241a301b61c7d8cc11c02b205(NVGcolor * cInPtr_inline_c_0, NVGcolor * cOutPtr_inline_c_1, int n_inline_c_2) {++ NVGcolor* in = cInPtr_inline_c_0;+ NVGcolor* out = cOutPtr_inline_c_1;+ for (int i = 0; i < n_inline_c_2; ++i) {+ out[i].r = in[i].r;+ out[i].g = in[i].g;+ out[i].b = in[i].b;+ out[i].a = in[i].a;+ }+ +}+++void inline_c_NanoVGSpec_3_9013abdecf112a5122b710a60f884bbeecc25595(NVGpaint * cInPtr_inline_c_0, NVGpaint * cOutPtr_inline_c_1, int n_inline_c_2) {++ NVGpaint* in = cInPtr_inline_c_0;+ NVGpaint* out = cOutPtr_inline_c_1;+ for (int i = 0; i < n_inline_c_2; ++i) {+ for (int j = 0; j < 6; ++j) {+ out[i].xform[j] = in[i].xform[j];+ }+ out[i].extent[0] = in[i].extent[0];+ out[i].extent[1] = in[i].extent[1];+ out[i].radius = in[i].radius;+ out[i].feather = in[i].feather;+ out[i].innerColor = in[i].innerColor;+ out[i].outerColor = in[i].outerColor;+ out[i].image = in[i].image;+ }+ +}+++void inline_c_NanoVGSpec_4_ff75ac86be53956ce5c18541f11daa395302bc69(NVGglyphPosition * cInPtr_inline_c_0, NVGglyphPosition * cOutPtr_inline_c_1, int n_inline_c_2) {++ NVGglyphPosition* in = cInPtr_inline_c_0;+ NVGglyphPosition* out = cOutPtr_inline_c_1;+ for (int i = 0; i < n_inline_c_2; ++i) {+ out[i].str = in[i].str;+ out[i].x = in[i].x;+ out[i].minx = in[i].minx;+ out[i].maxx = in[i].maxx;+ } + +}+++void inline_c_NanoVGSpec_5_c2a4b2347a68231f86f11ec7f3f7ed736b8342a1(NVGtextRow * cInPtr_inline_c_0, NVGtextRow * cOutPtr_inline_c_1, int n_inline_c_2) {++ NVGtextRow* in = cInPtr_inline_c_0;+ NVGtextRow* out = cOutPtr_inline_c_1;+ for (int i = 0; i < n_inline_c_2; ++i) {+ out[i].start = in[i].start;+ out[i].end = in[i].end;+ out[i].next = in[i].next;+ out[i].width = in[i].width;+ out[i].minx = in[i].minx;+ out[i].maxx = in[i].maxx;+ }+ +}+++void inline_c_NanoVGSpec_6_dc5e0871d245ac0a9a509674eff1e16900be5175(float * cInPtr_inline_c_0, float * cOutPtr_inline_c_1, int n_inline_c_2) {++ float* in = cInPtr_inline_c_0;+ float* out = cOutPtr_inline_c_1;+ for (int i = 0; i < n_inline_c_2; ++i) {+ for (int j = 0; j < 4; ++j) {+ out[4*i+j] = in[4*i+j];+ }+ }+ +}+
+ test/NanoVGSpec.hs view
@@ -0,0 +1,238 @@+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE ScopedTypeVariables #-}+module NanoVGSpec where++import Control.Monad+import qualified Language.C.Inline as C+import qualified Language.C.Inline.Context as C+import Contexts+import Data.Monoid+import qualified Language.C.Types as C+import qualified Data.Map as M+import Foreign.Marshal.Alloc+import Foreign.Ptr+import Foreign.C.Types+import Foreign.Storable+import Test.Hspec+import NanoVG+import Test.QuickCheck+import Linear.V2+import Linear.V3+import Linear.V4++C.context (C.baseCtx <> nanoVGCtx)+C.include "nanovg.h"++arbitraryCFloat :: Gen CFloat+arbitraryCFloat = realToFrac <$> (arbitrary :: Gen Float)++arbitraryPtr :: Gen (Ptr a)+arbitraryPtr = ((nullPtr `plusPtr`) . getNonNegative) <$> (arbitrary :: Gen (NonNegative Int))++instance Arbitrary Transformation where+ arbitrary =+ do a <- arbitraryCFloat+ b <- arbitraryCFloat+ c <- arbitraryCFloat+ d <- arbitraryCFloat+ e <- arbitraryCFloat+ f <- arbitraryCFloat+ pure (Transformation+ (V2 (V3 a c e)+ (V3 b d f)))++instance Arbitrary Extent where+ arbitrary =+ do a <- arbitraryCFloat+ b <- arbitraryCFloat+ pure (Extent (V2 a b))++instance Arbitrary Color where+ arbitrary =+ do r <- arbitraryCFloat+ g <- arbitraryCFloat+ b <- arbitraryCFloat+ a <- arbitraryCFloat+ pure (Color r g b a) ++instance Arbitrary Paint where+ arbitrary =+ do xform <- arbitrary+ extent <- arbitrary+ radius <- arbitraryCFloat+ feather <- arbitraryCFloat+ innerColor <- arbitrary+ outerColor <- arbitrary+ image <- (Image . fromIntegral) <$> (arbitrary :: Gen Int)+ pure (Paint xform extent radius feather innerColor outerColor image)++instance Arbitrary TextRow where+ arbitrary =+ do start <- arbitraryPtr+ end <- arbitraryPtr+ next <- arbitraryPtr+ width <- arbitraryCFloat+ minx <- arbitraryCFloat+ maxx <- arbitraryCFloat+ pure (TextRow start end next width minx maxx)++instance Arbitrary GlyphPosition where+ arbitrary =+ do str <- arbitraryPtr+ x <- arbitraryCFloat+ minx <- arbitraryCFloat+ maxx <- arbitraryCFloat+ pure (GlyphPosition str x minx maxx)++instance Arbitrary Bounds where+ arbitrary = fmap Bounds $+ V4 <$> arbitraryCFloat+ <*> arbitraryCFloat+ <*> arbitraryCFloat+ <*> arbitraryCFloat++allocaElems :: forall a b. Storable a => Int -> (Ptr a -> IO b) -> IO b +allocaElems n = allocaBytesAligned (n * sizeOf (undefined :: a)) (alignment (undefined :: a))++pokeElems :: Storable a => Ptr a -> [a] -> IO ()+pokeElems ptr as = forM_ (zip [0 ..] as) $ \(i,a) -> pokeElemOff ptr i a++peekElems :: Storable a => Int -> Ptr a -> IO [a]+peekElems n ptr = forM [0..(n-1)] (peekElemOff ptr)++roundTripElems :: (Storable a, Eq a, Show a) => [a] -> (CInt -> Ptr b -> Ptr b -> IO ()) -> IO ()+roundTripElems as f = allocaElems n $ \inPtr ->+ allocaElems n $ \outPtr -> do+ pokeElems inPtr as+ let cInPtr = castPtr inPtr+ cOutPtr = castPtr outPtr+ f (fromIntegral n) cInPtr cOutPtr+ peekElems (fromIntegral n) outPtr `shouldReturn` as+ where n = fromIntegral (length as)++roundTripTransformation :: [Transformation] -> IO ()+roundTripTransformation ts = do+ roundTripElems ts $ \n cInPtr cOutPtr ->+ [C.block| void {+ float* in = $(float* cInPtr);+ float* out = $(float* cOutPtr);+ for (int i = 0; i < $(int n); ++i) {+ for (int j = 0; j < 6; ++j) {+ out[6*i+j] = in[6*i+j];+ }+ }+ }|]++roundTripExtents :: [Extent] -> IO ()+roundTripExtents es = + roundTripElems es $ \n cInPtr cOutPtr ->+ [C.block| void {+ float* in = $(float* cInPtr);+ float* out = $(float* cOutPtr);+ for (int i = 0; i < $(int n); ++i) {+ out[2*i] = in[2*i];+ out[2*i+1] = in[2*i+1];+ }+ }|]++roundTripColors :: [Color] -> IO ()+roundTripColors cs =+ roundTripElems cs $ \n cInPtr cOutPtr ->+ [C.block| void {+ NVGcolor* in = $(NVGcolor* cInPtr);+ NVGcolor* out = $(NVGcolor* cOutPtr);+ for (int i = 0; i < $(int n); ++i) {+ out[i].r = in[i].r;+ out[i].g = in[i].g;+ out[i].b = in[i].b;+ out[i].a = in[i].a;+ }+ }|]++roundTripPaints :: [Paint] -> IO ()+roundTripPaints ps = + roundTripElems ps $ \n cInPtr cOutPtr -> + [C.block| void {+ NVGpaint* in = $(NVGpaint* cInPtr);+ NVGpaint* out = $(NVGpaint* cOutPtr);+ for (int i = 0; i < $(int n); ++i) {+ for (int j = 0; j < 6; ++j) {+ out[i].xform[j] = in[i].xform[j];+ }+ out[i].extent[0] = in[i].extent[0];+ out[i].extent[1] = in[i].extent[1];+ out[i].radius = in[i].radius;+ out[i].feather = in[i].feather;+ out[i].innerColor = in[i].innerColor;+ out[i].outerColor = in[i].outerColor;+ out[i].image = in[i].image;+ }+ }|]++roundTripGlyphPositions :: [GlyphPosition] -> IO ()+roundTripGlyphPositions ps =+ roundTripElems ps $ \n cInPtr cOutPtr -> + [C.block| void {+ NVGglyphPosition* in = $(NVGglyphPosition* cInPtr);+ NVGglyphPosition* out = $(NVGglyphPosition* cOutPtr);+ for (int i = 0; i < $(int n); ++i) {+ out[i].str = in[i].str;+ out[i].x = in[i].x;+ out[i].minx = in[i].minx;+ out[i].maxx = in[i].maxx;+ } + }|]++roundTripTextRows :: [TextRow] -> IO ()+roundTripTextRows rs =+ roundTripElems rs $ \n cInPtr cOutPtr ->+ [C.block| void {+ NVGtextRow* in = $(NVGtextRow* cInPtr);+ NVGtextRow* out = $(NVGtextRow* cOutPtr);+ for (int i = 0; i < $(int n); ++i) {+ out[i].start = in[i].start;+ out[i].end = in[i].end;+ out[i].next = in[i].next;+ out[i].width = in[i].width;+ out[i].minx = in[i].minx;+ out[i].maxx = in[i].maxx;+ }+ }|]++roundTripBounds :: [Bounds] -> IO ()+roundTripBounds bs =+ roundTripElems bs $ \n cInPtr cOutPtr ->+ [C.block| void {+ float* in = $(float* cInPtr);+ float* out = $(float* cOutPtr);+ for (int i = 0; i < $(int n); ++i) {+ for (int j = 0; j < 4; ++j) {+ out[4*i+j] = in[4*i+j];+ }+ }+ }|]++spec :: Spec+spec = do+ describe "Storable Transformation" $ do+ it "roundtrips via the storable instance" $ do+ quickCheck roundTripTransformation+ describe "Storable Extent" $ do+ it "roundtrips via the storable instance" $ do+ quickCheck roundTripExtents+ describe "Storable Color" $ do+ it "roundtrips via the storable instance" $ do+ quickCheck roundTripColors+ describe "Storable Paint" $ do+ it "roundtrips via the storable instance" $ do+ quickCheck roundTripPaints+ describe "Storable GlyphPosition" $ do+ it "roundtrips via the storable instance" $ do+ quickCheck roundTripGlyphPositions+ describe "Storable TextRow" $ do+ it "roundtrips via the storable instance" $ do+ quickCheck roundTripTextRows+ describe "Storable Bounds" $ do+ it "roundtrips via the storable instance" $ do+ quickCheck roundTripBounds
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}