SVGFonts 0.3 → 0.4
raw patch · 10 files changed
+338/−1190 lines, 10 filesdep +collada-outputdep +collada-typesdep +triangulationdep ~GLUTdep ~OpenGLdep ~containers
Dependencies added: collada-output, collada-types, triangulation
Dependency ranges changed: GLUT, OpenGL, containers, directory, parsec
Files
- README +4/−9
- SVGFonts.cabal +33/−13
- TODO +2/−5
- src/Graphics/SVGFonts/KETTriangulation.hs +0/−94
- src/Graphics/SVGFonts/RasterFont.hs +107/−112
- src/Graphics/SVGFonts/ReadFont.hs +158/−286
- src/Graphics/SVGFonts/Triangulation.hs +0/−177
- src/Graphics/SVGFonts/TriangulationUtils.hs +0/−226
- src/Test/Fonts.hs +34/−164
- src/Test/PointOfView.hs +0/−104
README view
@@ -15,20 +15,15 @@ doens't work edit the file by hand or even cooler: help me with a patch to make this library more robust. Remember that a lot of fonts are not allowed to be distributed freely. - src/Test/Font.hs is an example how to use this library. - You can navigate around the 3d string with w,a,s,d, + arrows +Example: + src/Test/Font.hs generates a Collada file that can be read with tools like blender. + The data that is passed to genCollada is represented with a data type that I use for all my libraries to allow easy combination. The main function to generate a visual representation of a string is: - displayString :: String -> (Int,Int) -> Mode -> Spacing -> O -> V -> V -> P -> [AObj] + displayString :: String -> (Int,Int) -> Props -> Transf -> Mode -> Spacing -> O -> V -> V -> P -> TexMap -> [Geometry] The list [AObj] can contain various data: - Textures that can be used directly by OpenGL (TextureObject) - Names of texture files in the fontcache (collada-output needs this) - Extruded or non extruded outlines - - The data to generate can be specified by the last argument, the property-function: - type P = [Char] -> [(Char,[CharProp])] - that assigns properties to every character of the string (i.e. color of a char in syntaxhighlighting, Font) - - The [AObj] list has to be converted to whatever types you use
SVGFonts.cabal view
@@ -1,9 +1,27 @@ Name: SVGFonts -Version: 0.3 +Version: 0.4 Synopsis: Fonts from the SVG-Font format -Description: Parse SVG-Font files and generate outlines or textures or texture files from text strings. - This library uses algorithms similar to Freetype and therefore needs no binding. - Unfortunately some speed issues (xml, rasterization) and bugs (triangulation,rasterization) make an actual usage problematic, but they will be solved. +Description: SVG-Font is a very easy to parse format and was therefore chosen for a font library + completely written in Haskell + . + To use this library with your own font, the font has to be converted with <http://fontforge.sourceforge.net/> + Features + . + * A very easy and powerful functional interface, see the Font.hs example + . + * The generated outlines can be transformed in various ways, i.e. + elimination of holes for easy extrusion (with the triangulation library) + . + * complete implementation of the SVG-Font format, that allows Kerning + (i.e. the two characters in "VA" have a shorter distance than in "VV") + . + * Everything is stored in a simple SceneGraph data type: <http://hackage.haskell.org/package/collada-types> + . + . + Some speed issues are left to be solved (xml) and rasterization is not finnished + . + Speed issues can be solved by trimming the svg file to only those characters that are used + . category: Graphics License: BSD3 License-file: LICENSE @@ -20,23 +38,25 @@ build-depends: haskell98, base == 4.*, - OpenGL, - GLUT, + OpenGL >= 2.2.3.0, + GLUT >= 2.1.2.1, xml == 1.3.*, - parsec == 2.1.*, + parsec == 3.1.*, array, - containers == 0.3.*, + containers == 0.4.*, SVGPath == 1.0.*, split == 0.1.*, - directory == 1.0.*, + directory == 1.1.*, bytestring == 0.9.1.*, tga, - tuple + tuple, + collada-types >= 0.2, + collada-output >= 0.5, + triangulation >= 0.2 exposed-modules: Graphics.SVGFonts.ReadFont - Graphics.SVGFonts.KETTriangulation - + Graphics.SVGFonts.RasterFont Executable Fonts hs-source-dirs: src main-is: Test/Fonts.hs - other-modules: Test.PointOfView, Graphics.SVGFonts.ReadFont, Paths_SVGFonts + other-modules: Graphics.SVGFonts.ReadFont, Paths_SVGFonts
TODO view
@@ -1,8 +1,5 @@-TO DO: - - Find errors in rasterization -- Find errors in triangulation -- Rasterization with Bytestring (if it is faster) -- Integration in collada-output +- integration in other libraries +- auto hinting - Binary XML (i.e. fast infoset), because xml-parsing has a speed problem - Unicode
− src/Graphics/SVGFonts/KETTriangulation.hs
@@ -1,94 +0,0 @@--------------------------------------------------------------------------------- --- Copyright (C) 1997, 1998, 2008 Joern Dinkla, www.dinkla.net --------------------------------------------------------------------------------- --- --- see --- Joern Dinkla, Geometrische Algorithmen in Haskell, Diploma Thesis, --- University of Bonn, Germany, 1998. --- --- triangulation of simple polygons after Kong, Everett, Toussaint 91 --- with some changes by T.Vogt: return indices instead of coordinates of triangles - -module Graphics.SVGFonts.KETTriangulation (ketTri,cycle_n) where -import List ( (\\) )-import Debug.Trace - -type XYI = (Float,Float,Int) - -ketTri :: [(Float,Float)] -> [(Int,Int,Int)] -ketTri poly = scan vs stack rs - where ps@(p1:p2:p3:qs) = vertices poly - vs = qs ++ [p1] - stack = [p3, p2, p1, last ps] - rs = reflexVertices ps - -scan :: [XYI] -> [XYI] -> [XYI] -> [(Int,Int,Int)] -scan [] _ _ = [] -scan [v] [x_p, x_i, _, _] rs = [(sel3_3 x_i, sel3_3 x_p, sel3_3 v)] -scan (v:vs) ss@[_,_,_] rs = scan vs (v:ss) rs -scan vs@(v:vs') ss@(x_p:x_i:ss'@(x_m:x_mm:xs)) rs - | isEar rs x_m x_i x_p = (sel3_3 x_m, sel3_3 x_i, sel3_3 x_p) : scan vs (x_p:ss') rs' - | otherwise = scan vs' (v:ss) rs - where rs' = rs \\ (isConvex x_m x_p v ++ isConvex x_mm x_m x_p) - isConvex im i ip = if isLeftTurn im i ip then [i] else [] - -isEar :: [XYI] -> XYI -> XYI -> XYI -> Bool -isEar [] _ _ _ = True -isEar rs m x p = isLeftTurn m x p && not (any ( (m,x,p) `containsBNV`) rs) - -reflexVertices :: [XYI] -> [XYI] -reflexVertices xs = [ x | (m,x,p) <- angles xs, isRightTurnOrOn m x p ] - -isRightTurnOrOn m x p = (area2 m x p) <= 0 -isLeftTurn m x p = (area2 m x p) > 0 -area2 (x2,y2,_) (x0,y0,_) (x1,y1,_) = (x1-x0)*(y2-y0)-(x2-x0)*(y1-y0) - -containsBNV (s,t,v) p = (a==b && b==c) - where a = isLeftTurn s t p - b = isLeftTurn t v p - c = isLeftTurn v s p - -angles :: [a] -> [(a,a,a)] -angles xs = zip3 (rotateR xs) xs (rotateL xs) - -rotateL xs = tail xs ++ [head xs] -rotateR xs = [last xs] ++ init xs -sel3_1 (x,y,z) = x -sel3_2 (x,y,z) = y -sel3_3 (x,y,z) = z - --- make vertices of polygon counterclockwise and add an index -vertices :: [(Float,Float)] -> [XYI] -vertices qs | polygon_direction ps = ps - | otherwise = reverse ps - where ps = zipWith (\(x,y) z -> (x,y,z) ) qs [0..] - --- the direction (clockwise or counterclockwise) of a polygon can be obtained by looking at a maximal point --- polygon_direction :: [XYI] -> Bool --- polygon_direction poly = isLeftTurn (p (l-1) poly) (p l poly) (p (l+1) poly) --- where p l poly = head (drop (l `mod` lp) poly) --- l = maxim poly 0 0 0 0 --- lp = length poly --- -- the index of the right-/upmost point --- maxim [] l ml mx my = ml --- maxim (x:xs) l ml mx my | ((sel3_1 x) > mx) && ((sel3_2 x) >= my) = maxim xs (l+1) l (sel3_1 x) (sel3_2 x) --- | otherwise = maxim xs (l+1) ml mx my - -polygon_direction :: [XYI] -> Bool -polygon_direction poly = trace (show (sum (zipWith crossp l1 l2))) (sum (zipWith crossp l1 l2) > 0)- where l1 = map (\(a,b) -> b `sub` a) ((tail c) ++ [head c])- l2 = map (\(a,b) -> a `sub` b) c- c = map to_tuple (cycle_neighbours poly) -- [(p0,p1,0),(p1,p2,1),(p2,p3,2),..- to_tuple (a:b:[]) = (a,b)- sub (x0,y0,_) (x1,y1,_) = (x0-x1, y0-y1, 0)- crossp (v0,v1,_) (w0,w1,_) = v0*w1-v1*w0---- return a list containing lists of every element with its neighbour --- i.e. [e1,e2,e3] -> [ [e1,e2], [e2,e3], [e3, e1] ] -cycle_neighbours :: [a] -> [[a]] -cycle_neighbours [] = [] -cycle_neighbours xs = cycle_n (head xs) xs - -cycle_n :: a -> [a] -> [[a]] -cycle_n f (x:y:xs) = [x,y] : (cycle_n f (y:xs)) -cycle_n f e = [[head e, f ]] -- if the upper doesn't match close cycle
src/Graphics/SVGFonts/RasterFont.hs view
@@ -1,29 +1,32 @@ {-# LANGUAGE CPP, MultiParamTypeClasses, FlexibleContexts #-} module Graphics.SVGFonts.RasterFont - (raster, withCheckImage, createTexture, texData, bucketSort2d, array2d_to1d, + (raster, withCheckImage, createTexture, texData, bitmask, line, bitSum, endBits, bits, F2P, AA(..), Bitmask, ) where import Data.Array.MArray import Data.Array.ST (STArray,STUArray) -import Data.Array hiding (elems,array) +import Data.Array hiding (elems,array,bounds) import Data.Array.Unboxed -import Data.Bits ( (.&.) ) +import Data.Bits ( (.&.), testBit ) import Data.Word import Data.Bits (testBit,setBit, shiftL, bit, shiftR) import Data.STRef +import Data.Maybe +import Data.List import Control.Monad (when, forM_, forM) import Control.Monad.ST -import Graphics.Rendering.OpenGL hiding (get,set) +import Graphics.Rendering.OpenGL hiding (get) import Foreign (mallocBytes, withArray) import Debug.Trace import Graphics.Formats.TGA.TGA import qualified Data.ByteString as B +import System.IO.Unsafe (unsafePerformIO) var = Data.STRef.newSTRef get = Data.STRef.readSTRef -set = Data.STRef.writeSTRef +sett = Data.STRef.writeSTRef mutate = Data.STRef.modifySTRef type F2P = (X, Y, AA) @@ -36,19 +39,18 @@ instance Show AA where show (NB) = "NB" -- No Bitmask - show (B bits) = "B " -- ++ show bits + show (B bits) = "B " ++ show (elems bits) raster (dx,dy) xs = -- trace (" xs " ++ show (map (\(x,y,_)->(round x, round y)) (rasterr (dx,dy) xs))) $ + -- trace (" xs " ++ show (map (\(x,y,b) -> b) (rasterr (dx,dy) xs))) $ rasterr (dx,dy) xs rasterr :: (X,Y) -> [F2] -> [F2P] rasterr _ [a] = [] rasterr (dx,dy) ((p0x,p0y):((p1x,p1y):bs)) | (abs (p1x-p0x)) < dx && (abs (p1y-p0y)) < dy = div22 ++ rest -- line that is at most one pixel long - | (abs (p1x-p0x)) < dx = -- < 1 && p0x_int == p1x_int = - rasterStraightLine True ++ rest -- vertical line - | (abs (p1y-p0y)) < dy = -- 1 && p0y_int == p1y_int = - rasterStraightLine False ++ rest -- horizontal line + | (abs (p1x-p0x)) < dx = rasterStraightLine True ++ rest -- vertical line + | (abs (p1y-p0y)) < dy = rasterStraightLine False ++ rest -- horizontal line | otherwise = trace "test" rest -- should not happen where rest = rasterr (dx,dy) ((p1x, p1y):bs) (p0x_int, p0y_int) | p0y < p1y = (truncate p0x, truncate p0y) @@ -59,10 +61,10 @@ rasterStraightLine up | up = (divide 1 len_y smallerX p0y_int (line 1 len_y pixP0 pixP1)) -- up | otherwise = (divide len_x 1 smallerX p0y_int (line len_x 1 pixP2 pixP3)) -- right divide :: Int -> Int -> Int -> Int -> UArray (Int,Int) Word16 -> [F2P] - divide nx ny x y a = [ (fromIntegral (ex+x), fromIntegral (ey+y), B (mark (ey*nx+ex))) | -- (bitmask ex ey a)) | + divide nx ny x y a = [ (fromIntegral (ex+x), fromIntegral (ey+y), B (bitmask ex ey a)) | -- (mark (ey*nx+ex))) | -- (bitmask ex ey a)) | ey <- [0..(ny-1)], ex <- [0..(nx-1)] ] div22 = divide 2 2 p0x_int p0y_int (line 2 2 pixP4 pixP5) -- (array ((0,0),(0,15)) []) - mark n = array (0,15) [ (i,32) | i <- [0..15] ] -- bitmask ex ey a + -- mark n = array (0,15) [ (i,32) | i <- [0..15] ] -- bitmask ex ey a len_x = abs (p1x_int - p0x_int) len_y = p1y_int - p0y_int smallerX = truncate $ if (p0x < p1x) then p0x else p1x @@ -82,60 +84,22 @@ frac = snd.properFraction --- fil xs = filter (>0) (elems xs) - texData :: (Int,Int) -> [(Int, Int, Bitmask)] -> TGAData -texData (rx,ry) border_points = tga $ array2d_to1d rx ry $ fillSpans $ (bucketSort2d rx ry border_points) +texData (rx,ry) border_points = tga $ fillSpace (rx,ry) border_points where tga t = (TGAData (B.empty) - (RGB32 (B.concat (map color (elems t))) ) + (RGB32 (color t)) 0 0 rx ry) - color x = B.cons x $ B.cons x $ B.cons x $ B.singleton x - --- |Fill a bitmask with 1s if the bit/subpixel is inside a polygon, (the bitmask was filled before with --- subpixels of outline drawing of the polygon). The bitmask is an array of 16 word16 where the --- x-direction are the bit position in the word16s. Horizontal(x-direction) lines are drawn if the --- line is inside the polygon, called a span (see Jordan curve theorem). --- A subpixel marks the beginnig or the end of a span depending on the state of the line. --- At the beginning all lines are white, if one intersects a subpixel it becomes black until it --- hits a subpixel again. The states at the end of the lines are stored for the next pixel in fillSpans. --- If these state bits are all one or all zero and the next bitmask is completely zero then fewer --- calculations need to be done. - - fillSpans :: (UArray (Int,Int) Bool, UArray (Int,Int) Word16) -> UArray (Int,Int) Word16 - fillSpans (ar_bool, ar_word) = runST ( -- trace ("span " ++ show (fil ar_word)) $ runST ( - do ar <- newArray ((0,0),(rx-1,ry-1)) 0 :: ST s (STUArray s (Int,Int) Word16) - arb <- thaw2 ar_bool - arw <- thaw2 ar_word - forM_ [0..(ry-1)] ( \y -> do - startBits <- var (0::Word16) - forM_ [0..(rx-1)] ( \x -> do - b <- readArray arb (x,y) - sb <- get startBits - case b of True -> do s <- var $ False - forM_ [0..15] ( \suby -> do - set s (testBit sb suby) - forM_ [0..15] ( \subx -> do - line <- readArray arw (x,y*16+suby) - let b = (testBit line subx) - t <- get s - when (t==False && b == True) (writeArray arw (x,y*16+suby) (setBit line subx)) - when (t==False && b == True) (set s True) -- start filling - when (t==True && b == False) (writeArray arw (x,y*16+suby) (setBit line subx)) -- continue - when (t==True && b == True) (set s False) ) ) -- stop - bitm <- unsafeFreeze arw - let bsum = bitSum (bitmask x y bitm) -- trace (show ((bitmask x y bitm))) $ - set startBits (endBits (bitmask x y bitm)) -- ((endBits newMask) && not (endBits bm) ) - writeArray ar (x,y) bsum - False -> do writeArray ar (x,y) (shiftL (bits sb) 4) )) -- the same as: (bits startbits)*16 - a' <- unsafeFreeze ar - return (a' :: UArray (Int,Int) Word16) ) - + color x | b == Nothing = B.empty + | otherwise = B.cons c $ B.cons c $ B.cons c $ B.cons c (color xs) + where b = B.uncons x + c = fst (fromJust b) + xs = snd (fromJust b) withCheckImage :: [Word8] -> TextureSize2D -> ([Word8] -> [(Color4 GLubyte)]) -> (PixelData (Color4 GLubyte) -> IO ()) -> IO () withCheckImage xs (TextureSize2D w h) f act = - withArray (f xs) -- (map f (map fromIntegral xs)) + withArray (f xs) $ act. PixelData RGBA UnsignedByte createTexture :: (Int,Int) -> TGAData -> String -> IO (TextureObject, String) @@ -147,60 +111,42 @@ textureBinding Texture2D $= Just texName textureFilter Texture2D $= ((Nearest, Nothing), Nearest) let imageSize = TextureSize2D (fromIntegral rx) (fromIntegral ry) - withCheckImage xs imageSize f $ -- (\c -> Color4 c c c 255) $ + withCheckImage xs imageSize f $ texImage2D Nothing NoProxy 0 RGBA' imageSize 0 return (texName, fileName) f (a:(b:(c:(d:ds)))) = (Color4 (fromIntegral a) (fromIntegral b) (fromIntegral c) 255) : (f ds) f _ = [] --- type Bitmask = UArray Int Word16 --- |generate a 2-dimensional array in which pixels are inserted -bucketSort2d :: Int -> Int -> [(Int,Int,Bitmask)] -> (UArray (Int,Int) Bool, UArray (Int,Int) Word16) -bucketSort2d rx ry pixels = -- trace ("bucket " ++ show pixels) -- (map (\(x,y,z) -> (x,y)) pixels) ) - (runST ( do - ar0 <- newArray ((0,0),(rx-1,ry-1)) False :: ST s (STUArray s (Int,Int) Bool) -- == True if pixel has a grey value (the outline goes through it) - ar1 <- newArray ((0,0),(rx-1,ry*16-1)) 0 :: ST s (STUArray s (Int,Int) Word16)-- an unboxed array of unboxed arrays is simulated with one big array - forM pixels (\(ix,iy,mask) -> do - when (ix >= 0 && iy >= 0 && (ix+iy*rx) < rx*ry) ( do - bbool <- readArray ar0 (ix, iy) - bm1 <- thawSTU mask - case bbool of False -> do writeArray ar0 (ix,iy) True - forM_ [0..15] ( \y -> do - e <- readArray bm1 y - writeArray ar1 (ix,iy*16+y) e ) - True -> do writeArray ar0 (0,0) True --- forM_ [0..15] ( \y -> do --- e0 <- readArray ar1 (ix,iy*16+y) --- e1 <- readArray bm1 y --- writeArray ar1 (ix,iy*16+y) (e0 `xor` e1) ) - ) ) - a0 <- unsafeFreeze ar0 - a1 <- unsafeFreeze ar1 - return (a0 :: UArray (Int,Int) Bool, a1 :: UArray (Int,Int) Word16) )) +fillSpace :: (Int, Int) -> [(Int,Int,Bitmask)] -> B.ByteString +fillSpace (rx,ry) pixels = fill (rx,ry) (sortBy sxy pixels) False + where sxy (x0,y0,b0) (x1,y1,b1) | y0 < y1 || (y0 == y1 && x0 < x1) = LT + | y0 == y1 && x0 == x1 = EQ + | otherwise = GT -ro = (\(x,y,z)->(round x, round y, z)) +fill :: (Int,Int) -> [(Int,Int,Bitmask)] -> Bool -> B.ByteString +fill (rx,ry) ((x0,y0,b0):(x1,y1,b1):cs) inside | y0 == y1 && inside == False + = B.cons (bitSum fbm) $ if ((x1-x0)>1) then B.append (B.replicate (x1-x0) ((endBits fbm)*16)) (fill (rx,ry) ((x1,y1,b1):cs) True) + else (fill (rx,ry) ((x1,y1,b1):cs) True) + | y0 == y1 && inside == True + = B.cons (256-(bitSum fbm)) $ if ((x1-x0)>1) then B.append (B.replicate (x1-x0) ((16-(endBits fbm))*16)) (fill (rx,ry) ((x1,y1,b1):cs) False) + else (fill (rx,ry) ((x1,y1,b1):cs) False) + | y0 /= y1 = B.append (B.append (B.replicate (rx-x0) ((16-(endBits fbm))*16)) + (B.replicate (x1+(y1-y0)*rx) 0)) (fill (rx,ry) ((x1,y1,b1):cs) True) + where + fbm = fillBitmask b0 +fill _ ((x0,y0,b0):cs) _ = B.empty + thawSTU :: (IArray UArray e, MArray (STUArray s) e (ST s)) => UArray Int e -> ST s (STUArray s Int e) thawSTU = thaw thaw2 :: (IArray UArray e, MArray (STUArray s) e (ST s)) => UArray (Int,Int) e -> ST s (STUArray s (Int,Int) e) thaw2 = thaw -array2d_to1d :: Int -> Int -> UArray (Int,Int) Word16 -> UArray Int Word8 -array2d_to1d rx ry mask = runST $ do -- trace ("mask " ++ show mask) runST $ do - ar <- thaw2 mask - m <- newArray (0,rx*ry-1) (0::Word8) :: ST s (STUArray s Int Word8) - forM_ [0..(ry-1)] ( \y -> do - forM_ [0..(rx-1)] ( \x -> do - e <- readArray ar (x,y) - writeArray m (x+y*rx) (fromIntegral e) - ) ) - m' <- unsafeFreeze m - return (m' :: UArray Int Word8) - --- |slice out a single bitmask from an array of bitmasks +-- |Slice out a single bitmask from an array of bitmasks +-- -- (this should be an unboxed array of unboxed arrays, -- but since this is not so easy in Haskell, one 2d-array with a supersampled y-coordinate (*16) is used) -- the x-supersampling are the bits in the Word16 @@ -214,7 +160,23 @@ m' <- unsafeFreeze m return (m' :: Bitmask) --- | bresenham line algorithm + +fillBitmask :: Bitmask -> Bitmask +fillBitmask mask = runST $ do -- trace ("x,y "++ show ix ++ "," ++ show iy ++ " ar ") (runST $ do + m <- newArray (0,15) (0::Word16) :: ST s (STUArray s Int Word16) + ar <- thawSTU mask + forM_ [0..15] $ \y -> do + e <- readArray ar y + writeArray m y (fillLine e) + m' <- unsafeFreeze m + return (m' :: Bitmask) + +-- | after the first occurrance of a 1 set all bits to 1 example: 00001000 ~> 00001111 +fillLine :: Word16 -> Word16 +fillLine w16 = w16 + w16 - 1 + +-- | Bresenham line algorithm +-- -- the data structure for the line is a 2d array of bits, in x-direction a sequence of word16s -- example: line :: Int -> Int -> (Int,Int) -> (Int,Int) -> UArray (Int,Int) Word16 @@ -245,10 +207,9 @@ setPix (a, x, y) = when (x<nx*16 && y<ny*16 && x>=0 && y>=0) (writeArray a ((x `div` 16), y) (setBit (0::Word16) (x `mod` 16))) -- (x `mod` 16) can be replaced by x? --- data Bitmask = UArray Int Word16 --- | the sum of all bits in a Bitmask, 16 rows with 16 bits each => between 0 and 255 -bitSum :: Bitmask -> Word16 -bitSum mask = runST ( do + +bitSum :: Bitmask -> Word8 +bitSum mask = fromIntegral $ runST ( do s <- var $ 0 ar <- thawSTU mask forM_ [0..15] $ \y -> do @@ -256,9 +217,8 @@ mutate s (+ bits e) get s) --- | return the the rightmost column of a bitmask -endBits :: Bitmask -> Word16 -endBits mask = runST ( do +endBits :: Bitmask -> Word8 +endBits mask = fromIntegral $ runST ( do s <- var $ 0 ar <- thawSTU mask forM_ [0..15] $ \y -> do @@ -266,20 +226,55 @@ mutate s (+ shiftL (e .&. (bit 15)) y) get s) --- | count the number of 1-bits with divide and conquer --- it can be done a little bit faster, but for the beginning it should just be correct --- see "Hacker's Delight by Henry S. Warren, Addison Wesley" for bit counting bits :: Word16 -> Word16 bits w16 = runST ( do s <- var $ w16 -- fst line: starting to count the 1's in 2-tuples: 0x55 = 01010101 -- 2nd line: 4 tuples: 0x33 = 0011001100 sv <- get s - set s (sv .&. 0x5555 + (shiftR sv 1) .&. 0x5555) + sett s (sv .&. 0x5555 + (shiftR sv 1) .&. 0x5555) sv <- get s - set s (sv .&. 0x3333 + (shiftR sv 2) .&. 0x3333) + sett s (sv .&. 0x3333 + (shiftR sv 2) .&. 0x3333) sv <- get s - set s (sv .&. 0x0F0F + (shiftR sv 4) .&. 0x0F0F) + sett s (sv .&. 0x0F0F + (shiftR sv 4) .&. 0x0F0F) sv <- get s - set s (sv .&. 0x00FF + (shiftR sv 8)) + sett s (sv .&. 0x00FF + (shiftR sv 8)) get s ) + +-- | The sum of all bits in a Bitmask, 16 rows with 16 bits each => between 0 and 255 +-- bitSum2 :: B.ByteString -> Word8 -- 8 bytes == 64 grey values +-- bitSum2 bs = B.foldl (+) 0 (B.take 8 (map bits2 bs)) + +-- | Return the the rightmost column of a bitmask +-- endBits2 :: B.ByteString -> Word8 -- 64 grey values +-- endBits2 bs = (B.foldl (+) 0 (B.take 8 (map (\x -> x .&. 1) bs))) * 8 + +tga_trace :: (UArray (Int,Int) Bool, UArray (Int,Int) Word16) -> (UArray (Int,Int) Bool, UArray (Int,Int) Word16) +tga_trace (a,b) = unsafePerformIO $ + do writeTGA "test.tga" tga + return (a,b) + where + tga = trace ((show rx) ++ " " ++ (show ry)) (TGAData (B.empty) + (RGB32 $ B.concat $ map color $ concat $ map bits $ elems b) + 0 0 + (rx*16) ry) + (_,(rx,ry)) = bounds b + bits :: Word16 -> [Word8] + bits a = map (\x -> if x==True then 255 else 0) ( + (testBit a 0) : (testBit a 1) : (testBit a 2) : + (testBit a 3) : (testBit a 4) : (testBit a 5) : + (testBit a 6) : (testBit a 7) : (testBit a 8) : + (testBit a 9) : (testBit a 10): (testBit a 11): + (testBit a 12): (testBit a 13): (testBit a 14): + [testBit a 15] ) + color x = B.cons x $ B.cons x $ B.cons x $ B.singleton x + + -- |Fill a bitmask with 1s if the bit/subpixel is inside a polygon, (the bitmask was filled before with + -- subpixels of outline drawing of the polygon). The bitmask is an array of 16 word16 where the + -- x-direction are the bit position in the word16s. Horizontal(x-direction) lines are drawn if the + -- line is inside the polygon, called a span (see Jordan curve theorem). + -- A subpixel marks the beginnig or the end of a span depending on the state of the line. + -- At the beginning all lines are white, if one intersects a subpixel it becomes black until it + -- hits a subpixel again. The states at the end of the lines are stored for the next pixel in fillSpans. + -- If these state bits are all one or all zero and the next bitmask is completely zero then fewer + -- calculations need to be done.
src/Graphics/SVGFonts/ReadFont.hs view
@@ -1,42 +1,52 @@+{-# LANGUAGE CPP, MultiParamTypeClasses, FlexibleContexts #-} module Graphics.SVGFonts.ReadFont -(triang, cycleNeighbours, displayString, AObj(..),Prop(..), Mode(..), Spacing(..), CharProp(..), makeMaps) +(displayString, + makeOutlMap, makeTexMap, + openFont, + Mode(..), Spacing(..), CharProp(..) +) where -import Text.XML.Light -import System.IO.Unsafe (unsafePerformIO) -import Text.ParserCombinators.Parsec hiding (spaces) -import Text.ParserCombinators.Parsec.Expr -import qualified Text.ParserCombinators.Parsec.Token as P -import Text.ParserCombinators.Parsec.Language( javaStyle ) +import Data.Array hiding (elems,array,bounds) import Data.Char -import Data.Word -import Data.Bits -import Data.Array.Unboxed --- import Graphics.SVGFonts.KETTriangulation ( ketTri ) -import Graphics.SVGFonts.Triangulation (adaptTri, Triangle(..), cycle_n) +import Data.List +import Data.List.Split +import Data.Maybe +import qualified Data.Map as Map +import Data.Tuple.Select +import Debug.Trace +import Graphics.Formats.Collada.ColladaTypes +import Graphics.Formats.Collada.GenerateObjects (cube, blue, obj, makeScene, get_name) +import Graphics.Formats.TGA.TGA +import Graphics.Rendering.OpenGL hiding (Triangle) import Graphics.SVGFonts.RasterFont (raster,F2P,Bitmask,AA(..),createTexture,texData) import Graphics.SVG.ReadPath -import Debug.Trace +import Graphics.Triangulation.Triangulation(polygonDirection, alternate) import List(intersect,sortBy) -import Graphics.Rendering.OpenGL hiding (Triangle) -import Control.Monad -import Control.Monad.ST -import qualified Data.STRef -import Data.Maybe -import qualified Data.Map as Map import System.Directory -import Graphics.Formats.TGA.TGA -import Data.List.Split -import Data.Tuple.Select +import System.IO.Unsafe (unsafePerformIO) +import Text.XML.Light -- http://www.w3.org/TR/SVG/fonts.html#KernElements -type Kern = ( Map.Map Char [Int], Map.Map Char [Int], Map.Map Char [Int], Map.Map Char [Int], Array Int X) +type Kern = ( Map.Map Char [Int], Map.Map Char [Int], Map.Map Char [Int], Map.Map Char [Int], Array Int X ) type SvgGlyph = Map.Map Char (String, X, String) type FontData = (SvgGlyph, Kern, String, String) type X = Float type Y = Float type F2 = (X,Y) +-- | Open an SVG-Font File and extract the data +-- +-- Some explanation how kerning(<http://en.wikipedia.org/wiki/Kerning>) is computed +-- +-- In Linlibertine.svg, there are two groups of chars: i.e. +-- \<hkern g1=\"f,longs,uni1E1F,f_f\" g2=\"parenright,bracketright,braceright\" k=\"-37\" /> +-- this line means: If there is an f followed by parentright, reduce the horizontal advance by 37. Therefore to quickly +-- check if two characters need kerning assign an index to the second group (g2 or u2) and assign to every unicode in +-- the first group (g1 or u1) this index, then sort these tuples after their name (for binary search). Because the same +-- unicode char can appear in several g1s, reduce this 'multiset', ie all the (\"name1\",0) (\"name1\",1) to (\"name1\",[0,1]). +-- Now the g2s are converted in the same way as the g1s. Whenever two consecutive chars are being printed try to find an +-- intersection of the list assigned to the first char and second char openFont :: FilePath -> FontData openFont file = (Map.fromList (zip4 (unicodes, glyphNames, horiz, ds)), -- sort after unicode (transform u1s, transform u2s, transform g1s, transform g2s, kAr), bbox, fname file ) @@ -56,21 +66,12 @@ g1s = map (fromMaybe "") $ map (findAttr (unqual "g1")) selectKerns g2s = map (fromMaybe "") $ map (findAttr (unqual "g2")) selectKerns ks = map (fromMaybe "") $ map (findAttr (unqual "k")) selectKerns - kAr = listArray (0,(length ks)-1) (map read ks) + kAr = Data.Array.listArray (0,(length ks)-1) (map read ks) transform chars = Map.fromList $ map ch $ multiSet $ map (\(x,y) -> (x,[y])) $ sort fst $ concat $ index chars ch = \(x,y) -> (myHead x,y) - -- ^ see Linlibertine.svg, there are two groups of chars: i.e. - -- <hkern g1="f,longs,uni1E1F,f_f" g2="parenright,bracketright,braceright" k="-37" /> - -- this line means: if there is an f followed by parentright reduce the horizontal advance by 37. Therefore to quickly - -- check if two characters need kerning assign an index to the second group (g2 or u2) and assign to every unicode in - -- the first group (g1 or u1) this index, then sort these tuples after their name (for binary search). Because the same - -- unicode char can appear in several g1s, reduce this 'multiset', ie all the ("name1",0) ("name1",1) to ("name1",[0,1]). - -- Now the g2s are converted in the same way as the g1s. Whenever two consecutive chars are being printed try to find an - -- intersection of the list assigned to the first char and second char - - index u = addIndex (map (splitBy isColon) u) -- ^ie ["aa,b","c,d"] to [["aa","b"],["c","d"]] - -- to [("aa",0),("b",0)],[("c",1), ("d",1)] + index u = addIndex (map (splitWhen isColon) u) -- ie ["aa,b","c,d"] to [["aa","b"],["c","d"]] + -- to [("aa",0),("b",0)],[("c",1), ("d",1)] isColon = (== ',') addIndex qs = zipWith (\x y -> (map (f x) y)) [0..] qs @@ -78,7 +79,7 @@ sort f xs = sortBy (\x y -> compare (f x) (f y) ) xs multiSet [] = [] - multiSet (a:[]) = [a] -- ^ example: [("n1",[0]),("n1",[1]),("n2",[1])] to [("n1",[0,1]),("n2",[1])] + multiSet (a:[]) = [a] -- example: [("n1",[0]),("n1",[1]),("n2",[1])] to [("n1",[0,1]),("n2",[1])] multiSet (a:b:bs) | fst a == fst b = multiSet ( (fst a, (snd a) ++ (snd b)) : bs) | otherwise = a : (multiSet (b:bs)) @@ -87,135 +88,13 @@ fname f = last $ init $ concat (map (splitOn "/") (splitOn "." f)) zip4 (a:as, b:bs, c:cs, d:ds) = (myHead a, (b, read c, d)) : (zip4 (as,bs,cs,ds)) zip4 _ = [] - -splitBy :: (a -> Bool) -> [a] -> [[a]] -splitBy _ [] = [] -splitBy f list = first : splitBy f (dropWhile f rest) where - (first, rest) = break f list --- |convert path-commands to outline points, which consist of bitmaps (resolution 16x16) for subpixel rasterization -commandsToRasterPoints :: [PathCommand] -> F2 -> [[F2P]] -commandsToRasterPoints commands (dx, dy) | length result == 0 = [] - | otherwise = map (raster (dx, dy)) result - where result = ctp commands [(0,0)] (0,0) True 255 (dx/2,dy) - ---------------------------------------------------- - -data Tree = Node Int [F2] [Tree] | Nil -- c is the nesting counter and p a polygon - -instance Show Tree where - show (Node c p tree) = "Node" ++ (show c) ++ (show (length p)) ++ "[" ++ (concat(map show tree)) ++ "]" - show (Nil) = "Nil" - --- |transform a polygon with (nested) holes into one outline. --- I.e. the chars a,b,d,e,g,o,p,q contain holes tat have to be deleted. --- This is useful for operations like extrusion. -deleteHoles :: [PathCommand] -> F2 -> [[F2]] -deleteHoles commands deltas = flatten trees - where trees = generateTrees insidePoly $ commandsToPoints commands deltas - flatten [] = [] - flatten (Nil:ts) = flatten ts - flatten ((Node c poly [Nil]):ts) = (direction c (polygonDirection poly) poly) : (flatten ts) - flatten ((Node c poly ps) :ts) = ( embed (flatten ps) (direction c (polygonDirection poly) poly) ) : (flatten ts) - --- |cut a polygon at a good position and insert the contained hole-polygon with opposite direction -embed :: [[F2]] -> [F2] -> [F2] -embed [] poly = poly -embed (s:sub_polys) poly = embed sub_polys ((take (n+1) poly) ++ s ++ (drop n poly)) - where n = fst (rotatePoly (head s) poly) - --- |make sure that direction (clockwise or ccw) of polygons alternates depending on the nesting number c of poly -direction :: Int -> Bool -> [F2] -> [F2] -direction c b poly | (b && (even c)) || (not b && (odd c)) = poly - | otherwise = reverse poly - --- |f should be the funtion to test "contains" --- the trees then are the hierarchy of containedness of outlines -generateTrees :: ([F2]->[F2]->Bool) -> [[F2]] -> [Tree] -generateTrees f [] = [] -generateTrees f ps = mergeNodes f (map (\p -> Node 0 p []) ps) [] - -mergeNodes :: ([F2]->[F2]->Bool) -> [Tree] -> [Tree] -> [Tree] -mergeNodes f [] result = result -mergeNodes f (v:vs) result = mergeNodes f vs (insertNode f v result) - -insertNode :: ([F2]->[F2]->Bool) -> Tree -> [Tree] -> [Tree] -insertNode f (Node _ poly []) ((Node c p []):ts)|(f poly p)= (Node c p [Node (c+1) poly []]) : ts - |(f p poly)= (Node c poly [Node (c+1) p []]) : ts - |otherwise = (Node c p []) : ( insertNode f (Node (c+1) poly []) ts) -insertNode f (Node c poly []) [] = [(Node c poly [])] -insertNode f (Node _ poly []) ((Node c p ps):ts)|(f poly p) = (Node c p (insertNode f (Node (c+1) poly []) ps)) : ts - |(f p poly) = [Node (c-1) poly ((Node c p ps) : ts)] - |otherwise = (Node c p ps): ( insertNode f (Node c poly []) ts) - --- |how many positions to rotate a polygon until the start point is nearest to some other point --- call i.e. with nearest (3,4) [(0,0),(1,2), ... ] 0 0 -rotatePoly :: F2 -> [F2] -> (Int,X) -rotatePoly p points = (fst tup, snd tup) - where tup = nearest p points (-1) 0 0 - -nearest :: F2 -> [F2] -> X -> Int -> Int -> (Int,X) -nearest _ [] dist l ml = (ml,dist) -nearest (x0,y0) ((x1,y1):ps) dist l ml | (newDist < dist) || (dist < 0) = nearest (x0,y0) ps newDist (l+1) l - | otherwise = nearest (x0,y0) ps dist (l+1) ml - where newDist = (x0-x1)*(x0-x1)+(y0-y1)*(y0-y1) - - -polygonDirection :: [F2] -> Bool -polygonDirection poly = sum (zipWith crossp l1 l2) > 0 - where l1 = map (\(a,b) -> b `sub` a) ((tail c) ++ [head c]) - l2 = map (\(a,b) -> a `sub` b) c - c = map (\list -> (head list, last list)) (cycleNeighbours poly) -- [(p0,p1),(p1,p2),(p2,p3),.. - sub (x0,y0) (x1,y1) = (x0-x1, y0-y1) - crossp (v0,v1) (w0,w1) = v0*w1-v1*w0 - -insidePoly :: [F2] -> [F2] -> Bool -insidePoly [] _ = False -insidePoly _ [] = False -insidePoly poly1 poly2 = pointInside (head poly1) poly2 - --- |A point is inside a polygon if it has an odd number of intersections with the boundary (Jordan Curve theorem) -pointInside :: F2 -> [F2] -> Bool -pointInside (x,y) poly = (length intersectPairs) `mod` 2 == 1 -- trace (show intersectPairs) - where intersectPairs = [ p | p <- allPairs, positiveXAxis p, aboveBelow p] --, specialCases p] - allPairs = cycleNeighbours poly - positiveXAxis p = (x0 p) > x || (x1 p) > x -- ^intersect with positive x-axis - -- only lines with one point above + one point below can intersect - aboveBelow p = (((y0 p)> y && (y1 p)< y) || ((y0 p) < y && (y1 p) > y)) - specialCases p = (((dir1 p) > 0 && (dir2 p) <= 0) || ((dir1 p) <= 0 && (dir2 p) > 0))-- cross product for special cases - dir1 p = cross ((x1 p)-(x0 p),(y1 p)-(y0 p)) (1,0) - dir2 p = cross ((x1 p)-(x0 p),(y1 p)-(y0 p)) (x-(x0 p),y-(y0 p)) - cross (a0,b0) (a1,b1) = a0*b1 - a1*b0 - x0 p = fst (head p) - x1 p = fst (last p) - y0 p = snd (head p) - y1 p = snd (last p) - --- |return a list containing lists of every element with its neighbour --- i.e. [e1,e2,e3] -> [ [e1,e2], [e2,e3], [e3, e1] ] -cycleNeighbours :: [a] -> [[a]] -cycleNeighbours [] = [] -cycleNeighbours xs = cycleN (head xs) xs - -cycleN :: a -> [a] -> [[a]] -cycleN f (x:y:xs) = [x,y] : (cycle_n f (y:xs)) -cycleN f e = [[head e, f ]] -- ^if the upper doesn't match close cycle - -triang :: [(X,Y)] -> [(Int,Int,Int)] -triang poly = [] -- map indi (adaptTri poly) -- ketTri poly - where indi (Triangle ((_,_,i0), (_,_,i1), (_,_,i2))) = (i0,i1,i2) - -data F a = NotFound | Found a -fromFound (Found a) = a -instance Eq (F a) where - NotFound == NotFound = True - _ == _ = False - -- type Kern = ( Map.Map Char [Int], Map.Map Char [Int], Map.Map Char [Int], Map.Map Char [Int], Array Int X) -- |horizontal advances of characters inside a string -horizontalAdvances :: [(Char,FontData)] -> Bool -> [X] -- Kern -> SvgGlyph -> [X] -horizontalAdvances strfont kerning = hlist strfont -- kern glyphData = hlist str +horizontalAdvances :: [(Char,FontData)] -> Bool -> [X] +horizontalAdvances strfont kerning = hlist strfont where hlist :: [(Char,FontData)] -> [X] + hlist [] = [] hlist [(ch0,fd0)] = [ha ch0 fd0] hlist ((ch0,fd0):(ch1,fd1):s) = ((ha ch0 fd0) + (ka ch0 ch1 fd0 fd1 (sel2 fd0))) : (hlist ((ch1,fd1):s)) ka :: Char -> Char -> FontData -> FontData -> Kern -> Float @@ -225,64 +104,35 @@ ha ch fd = sel2 $ fromJust $ Map.lookup ch (sel1 fd) kernAdvance :: Char -> Char -> Kern -> Bool -> X -kernAdvance ch0 ch1 kern u | u && (length s0) > 0 = (sel5 kern)!(head s0) - | not u && (length s1) > 0 = (sel5 kern)!(head s1) +kernAdvance ch0 ch1 kern u | u && (length s0) > 0 = (sel5 kern)Data.Array.!(head s0) + | not u && (length s1) > 0 = (sel5 kern)Data.Array.!(head s1) | otherwise = 0 where s0 = intersect (s sel1 ch0) (s sel2 ch1) s1 = intersect (s sel3 ch0) (s sel4 ch1) s sel ch = concat (maybeToList (Map.lookup ch (sel kern))) --- |extrude a 2d polygon to 3d, the same points are added again with extrusion direction v -add_points3d :: V -> Prop -> [AObj] -> [AObj] -add_points3d v pr [] = [] -add_points3d v pr poly = - poly ++ -- bottom polygon - ( map (add_points v pr) (map (\x -> Annotate x pr) (cycleNeighbours (de_ann(head poly)))) ) ++ --side polygons - [ Annotate (map (add v) (de_ann(head poly))) (property (head poly)) ] -- top polygon - -property (Annotate ps pr) = pr -de_ann (Annotate ps pr) = ps - --- extrude a line to a construct a polygon -add_points :: V -> Prop -> AObj -> AObj -add_points v pr (Annotate p _) = Annotate (p ++ (map (add v) (reverse p))) pr --- add_points v pr _ = Annotate [] pr - - -type O = V -- position vector -data Prop = RedGreenBlue (Float, Float, Float) | N | -- N = not visible - TexObj (TextureObject,String) | - Textur (Maybe TextureObject) | - TexFile String | - Triangul [(Int,Int,Int)] -- list of triangles - -data CharProp = Outline V | Textured | ObjColor Int Int Int Int | Font (FontData, OutlineMap, TexMap) -data AObj = Annotate [V] Prop type V = (Float,Float,Float) -- x,y,z -blue = RedGreenBlue (0,0,1) +type O = V -- position vector set_len (x,y,z) l = (x*c*l, y*c*l, z*c*l) where c = 1 / v_len (x,y,z) v_len (x,y,z) = sqrt (x*x+y*y+z*z) - divide (x,y,z) c = (x/c, y/c, z/c) mul (x,y,z) c = (x*c, y*c, z*c) add (x0,y0,z0) (x1,y1,z1) = (x0+x1, y0+y1, z0+z1) - n = (0,0,0) --- ------------------------ --- main library functions --------------------------- -type TexMap = Map.Map Char (TextureObject, String) -type OutlineMap = Map.Map Char [([F2], Prop)] -data Mode = INSIDE_V1_V2_V3 | -- the string is inside v1 v2 v3 boundaries (height/length-relation not kept) - INSIDE_V1 | -- stay inside v1 boundary, size of v2 adjusted to height/length-relation - INSIDE_V2 -- stay inside v2 boundary, size of v1 adjusted to height/length-relation -data Spacing = MONO | -- use mono spacing between glyphs - HADV | -- every glyph has a unique constant horiz. advance - KERN -- same as HADV but sometimes overridden by kerning: i.e. the horizontal advance in "VV" is bigger than in "VA" +type TexMap = Map.Map (Char,String,String,Bool) (TextureObject, String) +type OutlineMap = Map.Map Char [[F2]] +data Mode = INSIDE_V1_V2 -- ^The string is inside v1 v2 boundaries (height/length-relation not kept) + | INSIDE_V1 -- ^Stay inside v1 boundary, size of v2 adjusted to height/length-relation + | INSIDE_V2 -- ^Stay inside v2 boundary, size of v1 adjusted to height/length-relation -mV1V2V3 INSIDE_V1_V2_V3 = True +data Spacing = MONO -- ^Use mono spacing between glyphs (used in programming) + | HADV -- ^Every glyph has a unique constant horiz. advance + | KERN -- ^Same as HADV but sometimes overridden by kerning: i.e. the horizontal advance in \"VV\" is bigger than in \"VA\" + -- <http://en.wikipedia.org/wiki/Kerning> + +mV1V2V3 INSIDE_V1_V2 = True mV1V2V3 _ = False mV1 INSIDE_V1 = True mV1 _ = False @@ -295,126 +145,142 @@ isKern _ = False type FileName = String -type P = [Char] -> [(Char,[CharProp])] +type P = [Char] -> [String] +data CharProp = Prop (FontData, OutlineMap) String Bool +type Transf = Map.Map String (Geometry -> Geometry) +type Props = Map.Map String CharProp +-- | Main library function +-- +-- The string to display, resolution: (Int,Int) , mode, spacing, position and size apply to a whole string and therefore +-- are given here. There are local properties that can be unique for every char like Font, Color, ... . +-- These are given with a property function that assign a list of properties to every char: P -displayString :: String -> (Int,Int) -> Mode -> Spacing -> O -> V -> V -> P -> [AObj] -displayString str (rx,ry) mode spacing o v1 v2 f | mV1V2V3 mode = concat (make_string v1 v2) - | mV1 mode = concat (make_string v1 new_v2) - | mV2 mode = concat (make_string new_v1 v2) +displayString :: String -> String -> (Int,Int) -> Props -> Transf -> Mode -> Spacing -> O -> V -> V -> P -> TexMap -> Scene +displayString str sid (rx,ry) props transf mode spacing o v1 v2 f tex | mV1V2V3 mode = make_string v1 v2 + | mV1 mode = make_string v1 new_v2 + | mV2 mode = make_string new_v1 v2 where - -- :type FontData = ([(glyph_names, unicodes, horiz_advance, ds)], Kern, bbox-string) - make_string u1 u2 = map (getC u1 u2 (sumh,(max_x,max_y)) (rx,ry)) (zip4 str hor_pos hs properties) + -- FontData = ([(glyph_names, unicodes, horiz_advance, ds)], Kern, bbox-string, filename) + make_string u1 u2 = makeScene sid $ map (\(x,y) -> obj (get_name (head x)) x y) (geometrieVs u1 u2) + geometrieVs u1 u2 = map (getC u1 u2 sumh (rx,ry) tex) (zip5 str hor_pos hs properties trList) sumh | isMono spacing = max_x * (fromIntegral (length str)) -- not meant to be monospaced, so this is just a hack | otherwise = sum hs -- maybe a very long glyph can mess up a font hor_pos | isMono spacing = reverse $ added (o: (replicate (length str) (v1_advance `mul` max_x))) | otherwise = reverse $ added (o: stretch hs) - hs = horizontalAdvances (zip str fontList) (isKern spacing) -- kern_list (sel1 fontD) - properties = map snd (f str) + hs = horizontalAdvances (zip str fontList) (isKern spacing) + properties = map (fromJust.((\x y -> Map.lookup y x) props)) (f str) fontList = map (sel1.getFont) properties + trList = map (fromJust.((\x -> Map.lookup x transf).getTr)) properties stretch = map (v1_advance `mul`) added = snd.(foldl (\(h,l) (b,_) -> (h`add`b, (h`add`b):l)) ((0,0,0),[])).(map (\x->(x,[]))) -- [o,o+h0,o+h0+h1,..] - new_v1 = set_len v1 ( (v_len v2) * (sumh/max_y) ) - new_v2 = set_len v2 ( (v_len v1) * (max_y/sumh) ) - max_x = maximum (map maximum_x fontList) - max_y = maximum (map maximum_y fontList) -- max height of glyph + new_v1 = set_len v1 ( (v_len v2) * (sumh/max_y) ) -- in case there are several fonts in a string + new_v2 = set_len v2 ( (v_len v1) * (max_y/sumh) ) -- max_y is the average of max heights + max_x = (sum (map maximum_x fontList)) / (fromIntegral (length fontList)) -- difficult to treat different fonts in one string + max_y = (sum (map maximum_y fontList)) / (fromIntegral (length fontList)) -- max height of glyph v1_advance | mV1V2V3 mode || mV1 mode = v1 `divide` sumh | mV2 mode = new_v1 `divide` sumh - - zip4 (a:as) (b:bs) (c:cs) (d:ds) = (a, b, c, d) : (zip4 as bs cs ds) - zip4 _ _ _ _ = [] - -getC u1 u2 tri (rx,ry) (ch,h,h_ad,pr) | or (map hasTex pr)= texChar (sel1 sfd) tm u1 u2 tri (ch,h,h_ad) - | (length v3s) > 0 = polygonChar (sel1 sfd) om sfd u1 u2 (head v3s) tri (rx,ry) (ch,h) - | otherwise = [] - where hasTex :: CharProp -> Bool - hasTex Textured = True - hasTex _ = False - - v3s :: [V] - v3s = concat (map outl pr) - outl (Outline v3) = [v3] - outl _ = [] + zip5 (a:as) (b:bs) (c:cs) (d:ds) (e:es) = (a, b, c, d, e) : (zip5 as bs cs ds es) + zip5 _ _ _ _ _ = [] +getC u1 u2 sh (rx,ry) tex (ch,h,h_ad,pr,tr) | isTex pr = texChar (sel1 sfd) tex u1 u2 sh (ch,h,h_ad) + | otherwise = (map tr (fst pc), snd pc) + where pc = polygonChar (sel1 sfd) om sfd u1 u2 sh (rx,ry) (ch,h) + isTex :: CharProp -> Bool + isTex (Prop _ _ b) = b sfd = sel1 fd fd = getFont pr om = sel2 fd - tm = sel3 fd -getFont :: [CharProp] -> (FontData, OutlineMap, TexMap) -getFont pr = head $ concat (map gF pr) -- if several Fonts are given, take the first - where gF (Font (fontD, outlMap, texMap)) = [(fontD, outlMap, texMap)] - gF _ = [] +-- data CharProp = Prop (FontData, OutlineMap) Bool (Geometry -> Geometry) +getFont :: CharProp -> (FontData, OutlineMap) +getFont (Prop pr _ _) = pr +getTr :: CharProp -> String +getTr (Prop _ tr _) = tr maximum_y fontData = read (head (drop 3 bbox)) -- bbox lower left x, lower left y, upper right x, upper right y - where bbox = splitBy isSpace (sel3 fontData) + where bbox = splitWhen isSpace (sel3 fontData) maximum_x fontData = read (head (drop 2 bbox)) - where bbox = splitBy isSpace (sel3 fontData) - + where bbox = splitWhen isSpace (sel3 fontData) -polygonChar :: SvgGlyph -> OutlineMap -> FontData -> V -> V -> V -> (Float, F2) -> (Int,Int) -> (Char,V) -> [AObj] -polygonChar g outl fontD v1 v2 v3 (sum_of_hs, (max_x, max_y)) (rx,ry) (ch,h) = glyph_faces - where - glyph_faces = if (length out_tri) == 0 then [] else glyph3d -- all the triangles/quads the whole glyph has - -- tail (init glyph3d) -- with tail and init top and bottom polygon are deleted - glyph3d = concat ( map (\(o,t) -> add_points3d v3 blue [ Annotate (map resize o) t ] ) (out_tri) ) - resize (x,y) = h `add` (v1 `mul` (x * (fst deltas) / sum_of_hs)) `add` (v2 `mul` (y * (snd deltas)/ max_y)) +polygonChar :: SvgGlyph -> OutlineMap -> FontData -> V -> V -> Float -> (Int,Int) -> (Char,V) -> ([Geometry],V) +polygonChar g outl fontD v1 v2 sum_of_hs (rx,ry) (ch,h) = ([geometry],h) + where -- h `add` + resize (x,y) = (v1 `mul` (x * (fst deltas) / sum_of_hs)) `add` (v2 `mul` (y * (snd deltas)/ max_y)) deltas = (max_x/(fromIntegral rx), max_y/(fromIntegral ry)) - out_tri = fromJust $ Map.lookup ch outl + out = fromJust $ Map.lookup ch outl + max_x = maximum_x fontD + max_y = maximum_y fontD + l = map (map resize) out + geometry = Geometry ("outline_" ++ [ch]) + [ LP (LinePrimitive indices indices [] [blue]) ] + (Vertices "cube_vertices" (concat l) -- vertices + (replicate (length (concat l)) (0,0,1)) )-- normals + indices = parts 0 lengths + parts n (l:ls) = [n..(n+l-1)] : (parts (n+l) ls) + parts _ [] = [] + lengths = map length l -texChar :: SvgGlyph -> TexMap -> V -> V -> (Float, F2) -> (Char,V,Float) -> [AObj] -texChar g texmap v1 v2 (sumh, (max_x, max_y)) (ch,h,h_ad) = - [ Annotate [ res (n,v2), res (n,n), res (v1,n), res (v1,v2) ] (TexObj tex) ] -- n = (0,0,0) +texChar :: SvgGlyph -> TexMap -> V -> V -> Float -> (Char,V,Float) -> ([Geometry],V) +texChar g texmap v1 v2 sum_of_hs (ch,h,h_ad) = ([geometry],h) +-- [ Annotate [ res (n,v2), res (n,n), res (v1,n), res (v1,v2) ] (TexObj tex) ] -- n = (0,0,0) where - res (v,w) = h `add` (v `mul` (h_ad / sumh)) `add` w - tex = fromJust (Map.lookup ch texmap) - - -makeMaps :: String -> (Int,Int) -> (FontData, OutlineMap, TexMap) -makeMaps str (rx,ry) = (fontD, makeOutlMap fontD (rx,ry), makeTexMap fontD (rx,ry)) - where fontD = openFont str + geometry = Geometry "outline" + [ LP (LinePrimitive indices indices [] [blue]) ] + (Vertices "cube_vertices" l -- vertices + (replicate 4 (0,0,1)) ) -- normals + l = [res (n,v2), res (n,n), res (v1,n), res (v1,v2)] + indices = [[0,1,2,3]] + res (v,w) = h `add` (v `mul` (h_ad / sum_of_hs)) `add` w +-- tex = fromJust (Map.lookup ch texmap) +-- | Avoid recalculation of font data, outlines and textures +-- -- a char like "i" has two outlines (one for the dot) and two triangulations -makeOutlMap :: FontData -> (Int,Int) -> OutlineMap -- type OutlineMap = Map.Map Char [([F2], Prop)] -makeOutlMap fontD (rx,ry) = Map.fromList [ (ch, zip (outlines ch) (triangles ch)) | ch <- allUnicodes ] +makeOutlMap :: String -> (Int,Int) -> (FontData, OutlineMap) -- type OutlineMap = Map.Map Char [([F2], Prop)] +makeOutlMap str (rx,ry) = (fontD, Map.fromList [ (ch, outlines ch) | ch <- allUnicodes ] ) where allUnicodes = Map.keys (sel1 fontD) - outlines ch = fst (glyph ch) - triangles ch = map (\x -> Triangul x) (snd (glyph ch)) max_x = maximum_x fontD max_y = maximum_y fontD - deltas = (max_x/(fromIntegral rx), max_y/(fromIntegral ry)) -- [ ([F2],[ (Int,Int,Int) ]) ] - glyph ch = trace ("outl ch " ++ show ch) getGlyphPolygon ch (sel1 fontD) deltas + deltas = (max_x/(fromIntegral rx), max_y/(fromIntegral ry)) + outlines ch = getGlyphPolygon ch (sel1 fontD) deltas + fontD = openFont str -makeTexMap :: FontData -> (Int,Int) -> TexMap -makeTexMap fontD (rx,ry) = Map.fromList [ (ch, (glyph ch)) | ch <- allUnicodes ] - where - allUnicodes = Map.keys (sel1 fontD) - max_x = maximum_x fontD - max_y = maximum_y fontD - nrx ch = fromIntegral $ round ((fromIntegral ry)*((h ch)/max_y)) -- ry/rx * (h_ad/max_y) * rx - h ch = sel2 (fromJust (Map.lookup ch (sel1 fontD))) - deltas ch = (max_x/(fromIntegral (nrx ch)), max_y/(fromIntegral ry)) - glyph ch = trace ("tex ch " ++ show ch) (unsafePerformIO $ getGlyphTexture ch fontD (nrx ch, fromIntegral ry) (deltas ch)) +-- data CharProp = Prop (FontData, OutlineMap) String Bool +makeTexMap :: (Int,Int) -> Props -> Transf -> TexMap +makeTexMap (rx,ry) ps trs = Map.fromList [ (getID ch p, (glyph ch p)) | p <- (Map.elems ps), isTex p, ch <- (allUnicodes p) ] + where + allUnicodes (Prop (fontD,_) _ _) = Map.keys (sel1 fontD) + max_x ch fontD = h ch fontD -- maximum_x fontD + max_y fontD = maximum_y fontD + nrx ch fontD = fromIntegral $ round ((fromIntegral ry)*((h ch fontD)/ (max_y fontD))) -- ry/rx * (h_ad/max_y) * rx + h ch fontD = sel2 (fromJust (Map.lookup ch (sel1 fontD))) + deltas ch fontD = ((max_x ch fontD)/(fromIntegral (nrx ch fontD)), (max_y fontD)/(fromIntegral ry)) + isTex (Prop _ _ tex) = tex + glyph ch (Prop (fontD,outl) tr _) = unsafePerformIO $ getGlyphTexture ch fontD (nrx ch fontD, fromIntegral ry) (deltas ch fontD) + getID :: Char -> CharProp -> (Char,String,String,Bool) + getID ch (Prop (fontD, _) transformation tex) = (ch, sel4 fontD, transformation, tex) +-- trNames = zip trs (map (\(Geometry str _ _) -> str) $ map (f cube) trs) +-- f a b = b a -getGlyphPolygon :: Char -> SvgGlyph -> F2 -> ([[F2]], [[ (Int,Int,Int) ]]) -getGlyphPolygon ch glyph deltas = (outlines, map triang outlines) -- the triangulation is a list of triangles(3 indices) - where d = sel3 (fromJust element) -- ie. a letter like 'i' consists of two lists of triangles +getGlyphPolygon :: Char -> SvgGlyph -> F2 -> [[F2]] +getGlyphPolygon ch glyph deltas = outlines -- the triangulation is a list of triangles(3 indices) + where d = sel3 (fromJust element) -- ie. a letter like 'i' consists of two lists of triangles element = Map.lookup ch glyph - outl = (deleteHoles commands deltas) - outlines = map (\o -> direction 0 (polygonDirection o) o) outl + outlines = commandsToPoints commands deltas commands | (length d) == 0 = [] | otherwise = unsafePerformIO ( pathFromString d ) getGlyphTexture :: Char -> FontData -> (Int,Int) -> F2 -> IO (TextureObject,String) -getGlyphTexture ch fontD (rx,ry) (dx,dy) = trace (show fileName) - (do fileExists <- doesFileExist fileName - if fileExists then do tga <- (readTGA fileName) - createTexture (rx,ry) tga fileName - else do (writeTGA fileName (texData (rx,ry) border_points)) - tga <- (readTGA fileName) - createTexture (rx,ry) tga fileName) +getGlyphTexture ch fontD (rx,ry) (dx,dy) = (do fileExists <- doesFileExist fileName + if fileExists then do tga <- (readTGA fileName) + createTexture (rx,ry) tga fileName + else do (writeTGA fileName (texData (rx,ry) border_points)) + tga <- (readTGA fileName) + createTexture (rx,ry) tga fileName + ) where d = sel3 (fromJust element) fileName = [ch] ++ "_" ++ (sel4 fontD) ++ (show rx) ++ "x" ++ (show ry) ++ ".tga" element = Map.lookup ch (sel1 fontD) @@ -423,3 +289,9 @@ texturing = True commands | (length d) == 0 = [] | otherwise = unsafePerformIO ( pathFromString d ) + +-- |convert path-commands to outline points, which consist of bitmaps (resolution 16x16) for subpixel rasterization +commandsToRasterPoints :: [PathCommand] -> F2 -> [[F2P]] +commandsToRasterPoints commands (dx, dy) | length result == 0 = [] + | otherwise = map (raster (dx, dy)) result + where result = ctp commands [(0,0)] (0,0) True 255 (dx,dy)
− src/Graphics/SVGFonts/Triangulation.hs
@@ -1,177 +0,0 @@-module Graphics.SVGFonts.Triangulation (adaptTri,Triangle(..),cycle_n) where--import Maybe ( fromJust, mapMaybe, isJust )-import List ( elemIndex )-import Array ( Array, listArray, (!) )-import Graphics.SVGFonts.TriangulationUtils hiding (intersection,vertices)-import Debug.Trace--diagonal :: (Ord a, Floating a) => [P2 a] -> Line a-diagonal ps = case searchTriangles (m,i,p) ps of- Nothing -> Segment m p- Just q -> Segment i q- where (m,i,p) = findConvexVertex ps--findConvexVertex :: (Ord a, Num a) => [P2 a] -> (P2 a, P2 a, P2 a)-findConvexVertex = head . filter (uncurry3 isLeftTurn) . angles--searchTriangles :: (Floating a, Ord a) => Angle a -> [P2 a] -> Maybe (P2 a)-searchTriangles x@(m,i,p) ps- | y == a || y == b = Just y- | isJust inLeft && l /= m = inLeft- | isJust inRight && r /= p = inRight- | otherwise = Nothing- where ray = bisectInteriorAngle x- ps' = sublist p m ps- (a, y, b) = intersection ray x ps'- inLeft = vertexInTriangle (i,y,b) (sublist b m ps')- (Just l) = inLeft- inRight = vertexInTriangle (y,i,a) (sublist p a ps')- (Just r) = inRight--bisectInteriorAngle :: (Ord a, Floating a) => Angle a -> Line a-bisectInteriorAngle (m,(ix,iy,ii),p)--- | phi > 0 = Ray i (rotate m i (-(2*pi-phi)/2))- | phi > 0 = Ray i (rotate m (ix,iy,ii) (phi/2 - pi))--- | phi == 0 = Ray i (ix+1,iy,ii)- | phi == 0 = Ray i (ix+1,iy,ii)- | phi < 0 = Ray i (rotate m i (phi/2))- where phi = angle3 p i m- i = (ix,iy,ii)--intersection :: (Ord a, Fractional a) => Line a -> Angle a -> [P2 a] -> Angle a-intersection ray (m,i,p) ps = minimumWith (\ (_,y,_) -> sqrDistance i y) is- where - is = mapMaybe inters (init (edges ps))- inters (i,j)- | isJust res = Just (i,y,j)- | otherwise = Nothing- where- res = intersect ray (Segment i j)- y = fromJust res--vertexInTriangle :: (Ord a, Num a) => Angle a -> [P2 a] -> Maybe (P2 a)-vertexInTriangle t@(p,q,r) xs = case filter (Triangle t `contains`) xs of - [] -> Nothing- ys -> Just (minimumBy (isLeftTurn p) ys)---- Wir benötigen folgende Hilfsfunktionen:---- S.15, |Wedge p q r| ist der Teil der Ebene, der ``rechts'' durch den von |p| ausgehenden Strahl--- durch |q| und ``rechts'' durch |r| begrenzt wird. Wir benötigen einen Test, ob eine Strecke in--- einem Keil enthalten ist.--inWedge :: (Ord a, Floating a) => (P2 a, P2 a) -> (P2 a, P2 a, P2 a) -> Bool-(a,b) `inWedge` (p,q,s)- | r > 0 = not ( (0<i && i<r) || (0<j && j<r) )- | r < 0 = i<=0 && i>r && j<=0 && j>r- | r == 0 = True- | otherwise = False --- temp solution- where r = angleWrt p q s- i = angleWrt p q a- j = angleWrt p q b--adaptTri :: (Floating a, Ord a) => [(a,a)] -> [Triangle a] -- newtype (Num a) => Triangle a = Triangle ((a,a,Int), (a,a,Int), (a,a,Int))-adaptTri poly = triangulate ds- where- ds = [(i,j), (j,i)] -- the polygon is splitted in two parts: the lines from i to j (i,j) and from j to i- where i = sel3_3 xi- j = sel3_3 xj- (Segment xi xj) = diagonal ps -- a diagonal is a line inside the polygon-- triangulate [] = []- triangulate ((i,j):ds) = map snd ts ++ triangulate (ds' ++ ds)- where (ds', ts) = sleeve i j []-- ps = vertices poly- n = length ps- arr = listArray (0,n-1) ps- pred i = (i-1) `mod` n- succ i = (i+1) `mod` n- angle i = (arr!(pred i), arr!i, arr!(succ i))-- sleeve i j ts -- ts are the accumulated triangles- | i==j || i==succ j || j==succ i = ([], ts) -- a triangle with i,j and something between- | (xi,xip) `inWedge` (xi,xj,xim) && (xj,xjm) `inWedge` (xj,xjp,xi) = turns (orientation xjm xj xi) (orientation xip xi xj)- | otherwise = freeTriangle- where- (xim,xi,xip) = angle i- (xjm,xj,xjp) = angle j- inBetween = sublist xip xjm ps- index x = (i + 1 + (fromJust (elemIndex x inBetween))) `mod` n-- turns Clockwise Clockwise = nexti- turns Collinear Clockwise = nexti- turns CounterClockwise CounterClockwise = prevj- turns CounterClockwise Collinear = prevj- turns CounterClockwise Clockwise- | xjm `inInteriourOf` (Triangle (xip,xi,xj)) = prevj- | otherwise = nexti- turns _ _- | null wrongTriangles = newFreeTriangle- | otherwise = freeTriangle-- nexti = sleeve (succ i) j (((j,i,succ i), Triangle (xj,xi,xip)) : ts)- prevj = sleeve i (pred j) (((j,i,pred j), Triangle (xj,xi,xjm)) : ts)-- (ts', wrongTriangles) - | (xi,xip) `inWedge` (xi,xj,xim) = findWrong ts- | otherwise = let (vs, w:ss) = splitWhile notInTri ts- (xs, ws) = findWrong ss - in (xs, ws ++ [w] ++ reverse vs)- where- notInTri (_,t) = trace (show (not (t `containsBNV` xip))) (not (t `containsBNV` xip))- findWrong ts = foldl delete (ts, []) (zip inBetween (tail inBetween))- delete (ts, ts') (p, q) = (ds, reverse cs ++ ts')- where- (cs, ds) = splitWhile (doIntersect (Segment p q) . diag) ts- diag ((j,i,n), Triangle (xj,xi,xn)) - | n == succ i = Segment xn xj- | n == pred j = Segment xi xn-- -- after Lemma 5- freeTriangle - | null wrongTriangles = ([], ts)- | otherwise = ([(s,k),(k,r)], (undefined, Triangle (xr,xs,xk)) : ts')- where- ((r,s,t), tri@(Triangle (xr,xs,xt))) = head wrongTriangles- inTriangle = filter (tri `containsBNV`) inBetween- xk = minimumWith (key xs xr) inTriangle- k = index xk-- key p q x = (angle3 x q p, sqrDistance q x)- - -- after Lemma 2+3- newFreeTriangle = ([(i,l),(l,j)], (undefined, Triangle (xj,xi,xl)) : ts)- where- xk = fromJust (searchTriangles (xj,xi,xip) inBetween)- inTriangle = filter (Triangle (xj,xi,xk) `contains`)- (sublist2 xk xj inBetween)- xl | null inTriangle = xk- | otherwise = minimumWith (key xi xj) inTriangle- l = index xl---- make vertices of polygon counterclockwise and add an index-vertices :: (Floating a, Ord a) => [(a,a)] -> [(a,a,Int)]-vertices qs | polygon_direction ps = ps- | otherwise = reverse ps- where ps = zipWith (\(x,y) z -> (x,y,z) ) qs [0..]--polygon_direction :: (Floating a, Ord a) => [(a,a,Int)] -> Bool-polygon_direction poly = sum (zipWith crossp l1 l2) > 0- where l1 = map (\(a,b) -> b `sub` a) ((tail c) ++ [head c])- l2 = map (\(a,b) -> a `sub` b) c- c = map to_tuple (cycle_neighbours poly) -- [(p0,p1,0),(p1,p2,1),(p2,p3,2),..- to_tuple (a:b:[]) = (a,b)- sub (x0,y0,_) (x1,y1,_) = (x0-x1, y0-y1, 0)- crossp (v0,v1,_) (w0,w1,_) = v0*w1-v1*w0---- return a list containing lists of every element with its neighbour--- i.e. [e1,e2,e3] -> [ [e1,e2], [e2,e3], [e3, e1] ]-cycle_neighbours :: [a] -> [[a]]-cycle_neighbours [] = []-cycle_neighbours xs = cycle_n (head xs) xs--cycle_n :: a -> [a] -> [[a]]-cycle_n f (x:y:xs) = [x,y] : (cycle_n f (y:xs))-cycle_n f e = [[head e, f ]] -- if the upper doesn't match close cycle
− src/Graphics/SVGFonts/TriangulationUtils.hs
@@ -1,226 +0,0 @@-module Graphics.SVGFonts.TriangulationUtils where-import Maybe ( isJust, fromJust )--type P2 a = (a,a,Int)-type Angle a = (P2 a, P2 a, P2 a)-data (Num a) => Line a = Segment { point1, point2 :: P2 a }- | Ray { point1, point2 :: P2 a }- | Line { point1, point2 :: P2 a }--angles :: [a] -> [(a,a,a)]-angles xs = zip3 (rotateR xs) xs (rotateL xs)--isRightTurnOrOn m x p = (area2 m x p) <= 0-isLeftTurn m x p = (area2 m x p) > 0-area2 (x2,y2,_) (x0,y0,_) (x1,y1,_) = (x1-x0)*(y2-y0)-(x2-x0)*(y1-y0)--containsBNV (Triangle (s,t,v)) p = (a==b && b==c)- where a = isLeftTurn s t p- b = isLeftTurn t v p- c = isLeftTurn v s p--data Orientation = Collinear | Clockwise | CounterClockwise - deriving (Eq, Show)--orientation :: (Num a, Ord a) => P2 a -> P2 a -> P2 a -> Orientation-orientation p q r- | s>0 = CounterClockwise -- to the left- | s==0 = Collinear- | s<0 = Clockwise -- to the right- where s = area2 p q r--angle2 :: (Floating a, Ord a) => P2 a -> P2 a -> a-angle2 x y | denum == 0 = 0- | cosPhi >= 1.0 = 0- | cosPhi <= -1.0 = pi- | cross2 x y >= 0 = acos cosPhi- | otherwise = - acos cosPhi- where denum = (x <.> x) * (y <.> y)- cosPhi = (x <.> y) / sqrt denum--(x0,y0,i) <.> (x1,y1,b) = x0*y0 + x1*y1--cross2 :: Num a => P2 a -> P2 a -> a-(x0,y0,a) `cross2` (x1,y1,b) = x0*y1 - x1*y0--rotateL xs = tail xs ++ [head xs]-rotateR xs = [last xs] ++ init xs--add (ax,ay,i) (bx,by,_) = (ax+bx,ay+by,i)-sub (ax,ay,i) (bx,by,_) = (ax-bx,ay-by,i)--sel3_1 (x,y,z) = x-sel3_2 (x,y,z) = y-sel3_3 (x,y,z) = z--uncurry3 :: (a -> b -> c -> d) -> ((a, b, c) -> d)-uncurry3 f p = f (sel3_1 p) (sel3_2 p) (sel3_3 p)--sublist, sublist2, takeDrop :: Eq a => a -> a -> [a] -> [a]-sublist i j xs = takeDrop i j xs ++ [j]-sublist2 i j xs = case takeDrop i j xs of { [] -> []; ys -> tail ys }-takeDrop i j xs = takeWhile (/=j) (dropWhile (/=i) (xs++xs))--rotate :: (Floating a, Ord a) => P2 a -> P2 a -> a -> P2 a-rotate p o phi = o `add` (rotateOrg (p `sub` o) phi)--rotateOrg :: (Floating a, Ord a) => P2 a -> a -> P2 a-rotateOrg (x,y,i) phi = (x*cos phi - y*sin phi, x*sin phi + y*cos phi,i)--angleWrt, angle3 :: (Floating a, Ord a) => P2 a -> P2 a -> P2 a -> a-angleWrt p q r = angle2 (q `sub` p) (r `sub` p)-angle3 p q r = (-1) * angle2 (p `sub` q) (r `sub` q)--choose1, choose2 :: Rel a -> (a -> a -> a)-choose1 rel x y = if x `rel` y then x else y-choose2 rel x y = if x `rel` y then y else x--minimumBy, maximumBy :: Rel a -> [a] -> a-minimumBy rel = foldl1 (\ x y -> if x `rel` y then x else y)--- minimumBy = foldl1 . choose1-maximumBy = foldl1 . choose2--extremaBy :: Rel a -> [a] -> [a]-extremaBy cmp [] = []-extremaBy cmp [x] = [x]-extremaBy cmp xs@(_:_:_) = [minimumBy cmp xs, maximumBy cmp xs]---type Rel a = a -> a -> Bool-data With a b = a :& b--sat :: With a b -> b-sat (a :& b) = b--liftToWith :: (a -> b -> c) -> With a d -> With b e -> c-liftToWith r (a :& _) (b :& _) = a `r` b--instance Eq a => Eq (With a b) where- (==) = liftToWith (==)--instance Ord a => Ord (With a b) where- compare = liftToWith compare--minimumWith, maximumWith :: Ord b => (a -> b) -> [a] -> a-minimumWith f xs = sat (minimum [f x :& x | x<-xs])-maximumWith f xs = sat (maximum [f x :& x | x<-xs])--minima, maxima :: Ord a => [a] -> [a]-minima = minimaBy compare-maxima = maximaBy compare--minimaBy, maximaBy :: (a -> a -> Ordering) -> [a] -> [a]-minimaBy cmp (x:xs) = foldl f [x] xs- where f ms@(h:_) b = case cmp b h of- LT -> [b]- EQ -> b:ms- GT -> ms --maximaBy cmp (x:xs) = foldl f [x] xs- where f ms@(h:_) b = case cmp b h of- GT -> [b]- EQ -> b:ms- LT -> ms --minimaWith, maximaWith :: Ord b => (a -> b) -> [a] -> [a]-minimaWith f xs = map sat (minima [f x :& x | x<-xs])-maximaWith f xs = map sat (maxima [f x :& x | x<-xs])--minimaWithBy, maximaWithBy :: (a -> a -> Ordering) -> (b -> a) -> [b] -> [b]-minimaWithBy cmp f xs = map sat (minimaBy (liftToWith cmp) [f x :& x | x<-xs])-maximaWithBy cmp f xs = map sat (maximaBy (liftToWith cmp) [f x :& x | x<-xs])----sqrDistance (x0,y0,_) (x1,y1,_) = r <.> r where r = (x0-x1,y0-y1,0)--edges :: [a] -> [(a,a)]-edges xs = zip xs (rotateL xs)---intersect, strictIntersect :: (Ord a, Fractional a) => Line a -> Line a -> Maybe (P2 a)-doIntersect,doStrictIntersect :: (Ord a, Fractional a) => Line a -> Line a -> Bool--intersect s1 s2 - | isJust res && ok s1 r && ok s2 s = Just i- | otherwise = Nothing- where- res = intersection s1 s2- (i,r,s) = fromJust res- ok (Segment _ _) r = 0<=r && r<=1- ok (Ray _ _) r = r>=0- ok (Line _ _) r = True--{--intersect = interAux paramOk- where paramOk (Segment _ _) r = 0<=r && r<=1- paramOk (Ray _ _) r = r>=0- paramOk (Line _ _) r = True--}--doIntersect s t = isJust (intersect s t)--strictIntersect = interAux paramOk- where - paramOk (Segment _ _) r = 0<r && r<1- paramOk (Ray _ _) r = r>0- paramOk (Line _ _) r = True-doStrictIntersect s t = isJust (strictIntersect s t)--interAux :: Fractional a => (Line a -> a -> Bool) -> Line a -> Line a -> Maybe (P2 a)-interAux ok s1 s2 = if isJust res && ok s1 r && ok s2 s then Just i else Nothing- where- res = intersection s1 s2- (i,r,s) = fromJust res--k <*> p = mapP (k*) p-mapP f (x,y,i) = (f x, f y, i)--intersection :: Fractional a => Line a -> Line a -> Maybe (P2 a,a,a)-intersection s1 s2- | denom == 0 = Nothing- | otherwise = Just (i, r, s)- where (xa,ya,_) = point1 s1- (xb,yb,_) = point2 s1- (xc,yc,_) = point1 s2- (xd,yd,_) = point2 s2- denom = (xb-xa)*(yd-yc)-(yb-ya)*(xd-xc)- r = ((ya-yc)*(xd-xc)-(xa-xc)*(yd-yc)) / denom- s = ((ya-yc)*(xb-xa)-(xa-xc)*(yb-ya)) / denom--- i = Point2 (xa + r*(xb-xa), ya + r*(yb-ya))- i = (xa+r*xb-xa, ya+r*yb-ya,0)--splitWhile :: (a -> Bool) -> [a] -> ([a], [a])-splitWhile p xs = split xs []- where split [] ls = (reverse ls, [])- split ys@(x:xs) ls = if p x then split xs (x:ls) else (reverse ls, ys)---newtype (Num a) => Triangle a = Triangle (P2 a, P2 a, P2 a)---- vertices :: (Num a) => Triangle (P2 a, P2 a, P2 a) -> [P2 a]-vertices (Triangle (p,q,r)) = [p,q,r]---- segments :: (Num a) => Triangle (P2 a, P2 a, P2 a) -> [Line a]-segments (Triangle (p,q,r)) = [Segment p q, Segment q r, Segment r p]---- area :: Fractional a => Triangle (P2 a, P2 a, P2 a) -> a-area (Triangle (p,q,r)) = 0.5 * area2 p q r---- contains :: (Num a, Ord a) => Triangle (P2 a, P2 a, P2 a) -> P2 a -> Bool-contains tri@(Triangle (s,t,v)) p = containsBNV tri p || p==s || p==t || p==v---- inInteriourOf :: (Num a, Ord a) => P2 a -> Triangle (P2 a, P2 a, P2 a) -> Bool-inInteriourOf p tri = checkBy test tri p- where test Collinear b c = False- test a Collinear c = False- test a b Collinear = False- test a b c = a==b && b==c--type Rel3 a = a -> a -> a -> Bool---- checkBy :: (Num a, Ord a) => Rel3 Orientation -> Triangle (P2 a, P2 a, P2 a) -> P2 a -> Bool-checkBy chk (Triangle (s,t,v)) p = chk a b c- where a = orientation s t p- b = orientation t v p- c = orientation v s p
src/Test/Fonts.hs view
@@ -1,178 +1,48 @@ module Main where -import Graphics.Rendering.OpenGL -import Graphics.UI.GLUT as GLUT-- (getArgsAndInitialize, initialDisplayMode, createWindow, windowSize, keyboardMouseCallback, idleCallback, displayCallback, reshapeCallback, mainLoop, swapBuffers, DisplayMode(..)) -import Data.IORef -import System.IO.Unsafe (unsafePerformIO) -import Test.PointOfView -import Graphics.SVGFonts.ReadFont(displayString, AObj(..), Prop(..), Mode(..), Spacing(..), CharProp(..), makeMaps) -import Data.Tuple.Select -import Paths_SVGFonts(getDataFileName) - -main= do - (progName,_) <- getArgsAndInitialize - initialDisplayMode $= [WithDepthBuffer, DoubleBuffered, RGBAMode, WithAlphaComponent, WithAccumBuffer] - createWindow progName - windowSize $= Size 800 600 - myInit - - gawFile <- getDataFileName "src/Test/GirlsareWeird.svg" - linFile <- getDataFileName "src/Test/LinLibertine.svg" +import Graphics.SVGFonts.ReadFont (displayString, makeOutlMap, makeTexMap, Mode(..), Spacing(..), CharProp(..)) +import Graphics.Formats.Collada.ColladaTypes (Geometry) +import Graphics.Formats.Collada.GenerateObjects (lightedScene, lightedSceneNode, emptyAnim) +import Graphics.Formats.Collada.GenerateCollada (genCollada) +import Graphics.Formats.Collada.Transformations (extrude, atop, translate) +import Graphics.Triangulation.Triangulation (triangulate, deleteHoles) +import Graphics.Triangulation.KETTriangulation (ketTri) +import qualified Data.Map as Map +import System.Environment - let resolution = (400, 400) - -- mode: INSIDE_V1_V2_V3: the string is inside v1 v2 v3 boundaries (height/length-relation not kept) - -- mode: INSIDE_V1: stay inside v1 boundary, size of v2 adjusted to height/length-relation - -- mode: INSIDE_V2: stay inside v2 boundary, size of v1 adjusted to height/length-relation +main = do + args <- getArgs + let str = if (length args)>0 then head args else "Haskell" -- ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + resolution = (300, 300) +-- mode: INSIDE_V1_V2: the string is inside v1 v2 boundaries (height/length-relation not kept) +-- mode: INSIDE_V1: stay inside v1 boundary, size of v2 adjusted to height/length-relation +-- mode: INSIDE_V2: stay inside v2 boundary, size of v1 adjusted to height/length-relation mode = INSIDE_V2 - -- spacing: MONO: use mono spacing between glyphs - -- HADV: every glyph has a unique constant horiz. advance - -- KERN: same as 1 but sometimes overridden by kerning: i.e. the horizontal advance in "VV" is bigger than in "VA" +-- spacing: MONO: Use mono spacing between glyphs +-- HADV: Every glyph has a unique constant horiz. advance +-- KERN: Same as HADV but sometimes overridden by kerning: i.e. the horizontal advance in "VV" is bigger than in "VA" spacing = HADV - gaw = makeMaps gawFile resolution - lin = makeMaps linFile resolution - o = (0,0,0) -- origin + gaw = makeOutlMap "../../../src/Test/GirlsareWeird.svg" resolution + lin = makeOutlMap "../../../src/Test/LinLibertine.svg" resolution + o = (0,0,1.1) -- origin v1 = (5,0,0) -- direction of char-advance v2 = (0,0,-1) -- height direction v3 = (0,0.2,0) -- extrusion - f :: [Char] -> [(Char,[CharProp])] - f (a:(b:(c:d))) = addStrProp ( [(a,[Textured, ObjColor 255 0 0 255]), -- no color yet - (b,[Outline v3]), - (c,[Textured, ObjColor 0 0 255 255])] ++ (f d) ) [Font gaw] - f _ = [] -- data CharProp = Outline V | Textured | ObjColor Int Int Int Int | Font (FontData, OutlineMap, TexMap) - addStrProp xs prop = map (\(a,bs) -> (a, bs ++ prop)) xs - onlyTextures (Annotate vs (TexObj (texobjs,str))) = Annotate vs (Textur (Just texobjs)) - -- str is the filename of the texture (for collada-output) - onlyTextures (Annotate vs rest) = Annotate vs rest - str = displayString "abcdef" resolution mode spacing (0,0,1.1) v1 v2 f - - g <- return ( G (map onlyTextures str) ) - - putStrLn "Font read" - l <- defineNewList Compile $ do display_g ( g ) - - pPos <- newIORef (0::GLdouble,1::GLdouble,0::GLdouble, 0::GLdouble,0::GLdouble,0::GLdouble, - 0::GLdouble,0::GLdouble,-1::GLdouble) - -- 0,1,0 : point of location in x,y,z then 0,0,0: where to look at - -- 0,0,-1: up direction - keystate <- newIORef [] - keyboardMouseCallback $= Just (keyboard keystate) - idleCallback$= Just (idle pPos) - displayCallback $= display keystate pPos l - reshapeCallback $= Just reshape - mainLoop - --- Initialize lighting and other values. -myInit :: IO () -myInit = do - materialAmbient Front $= Color4 1 0.5 1 1 - materialSpecular Front $= Color4 0.2 1 0.8 1 - materialShininess Front $= 50 - - position (Light 0) $= Vertex4 5 5 10 0 - lightModelAmbient $= Color4 0.2 0.2 0.2 1 - - -- cullFace $= Just Back - lighting $= Enabled - light (Light 0) $= Enabled - depthFunc $= Just Less - shadeModel $= Flat - normalize $= Enabled - rowAlignment Unpack $= 1 - - clearColor $= Color4 0.2 0 0 0 - clearAccum $= Color4 0 0 0 0 - - blendFunc $= (SrcAlpha, OneMinusSrcAlpha) - textureFunction $= Replace - texture Texture2D $= Enabled - -display keystate pPos l = do - loadIdentity - setPointOfView keystate pPos - clear [ColorBuffer,DepthBuffer] - callList l - --deleteLists [l] - swapBuffers - -data Obj = G [ AObj ] | -- extruded objects in coords (G for Geometry) - Nil - -blue = RedGreenBlue (0,0,1) - --- ======================================================== --- conversion into G [AObj] (mainly with eval3d) --- meaning of G [AObj] by example: --- G [Annotate [1] pr, Annotate [2] pr, Annotate [3] pr, Annotate [4] pr] = four single points (0d) --- G [Annotate [1,2] pr, Annotate [2,3] pr, Annotate [3,4] pr, Annotate [4,1] pr] = four lines (1d) --- G [Annotate [1,2,3,4] pr] a rectangle (2d) --- G [Annotate [1,2,3,4] pr, Annotate [3,4,5,6] pr, Annotate [1,2,7,8] pr] = a 3d object, consisting of several rectangles --- ======================================================== - -redG (G obj) = obj - -add (x0,y0,z0) (x1,y1,z1) = (x0+x1, y0+y1, z0+z1) -sub (x0,y0,z0) (x1,y1,z1) = (x0-x1, y0-y1, z0-z1) - -skalar (x,y,z) (a,b,c) = x*a + y*b + z*c - --- ==================================================================== --- displaying (this is slow immediate mode, but it is just for testing) --- ==================================================================== -loc = preservingMatrix - -display_g :: Obj -> IO() -display_g (G []) = return () -display_g Nil = return() - -display_g (G (p:ps)) = disp p >> display_g (G ps) - -map3 = map (\(x,y,z) -> (realToFrac x, realToFrac y, realToFrac z)) - -disp :: AObj -> IO() -disp (Annotate ps ( Triangul tris )) = do diplay_primitive (generate_from_index (map3 ps) tris) blue Triangles -disp (Annotate ps pr) | (length ps) == 1 = do diplay_primitive (map3 ps) pr Points - | (length ps) == 2 = do diplay_primitive (map3 ps) pr Lines - | otherwise = do diplay_primitive (map3 ps) pr Polygon - -generate_from_index :: [(GLfloat,GLfloat,GLfloat)] -> [(Int,Int,Int)] -> [(GLfloat,GLfloat,GLfloat)] -generate_from_index ps [] = [] -generate_from_index ps (t:tris) = [tp0, tp1, tp2] ++ (generate_from_index ps tris) - where tp0 = head(drop (sel1 t) ps) - tp1 = head(drop (sel2 t) ps) - tp2 = head(drop (sel3 t) ps) - -norm points = (set_len (kreuz (v0 `sub` v1) (v2 `sub` v1) ) 1) - where v0 = head points - v1 = head (drop 1 points) - v2 = head (drop 2 points) - -diplay_primitive p (RedGreenBlue(r,g,b)) primitiveShape - | (length p) >=3 = do - materialAmbientAndDiffuse Front $= Color4 (realToFrac r) (realToFrac g) (realToFrac b) 1 - currentNormal $= Normal3 (sel1(norm p)) (sel2(norm p)) (sel3(norm p)) - displayPoints p primitiveShape - | otherwise = do - materialAmbientAndDiffuse Front $= Color4 (realToFrac r) (realToFrac g) (realToFrac b) 1 - displayPoints p primitiveShape + f :: String -> [String] -- assigning a property to every character by a unique string + f str = replicate (length str) "p" -diplay_primitive points (Textur image) primitiveShape = do - textureBinding Texture2D $= image - let verts = makeV points - let texs = [(TexCoord2 0 1), (TexCoord2 0 0), (TexCoord2 1 0), (TexCoord2 1 1)] - renderPrimitive Polygon $ do mapVertices texs verts - flush + prop :: Map.Map String CharProp -- data CharProp = Prop (FontData, OutlineMap) String Textured + prop = Map.fromList [("p", Prop lin "to3d" False)] -displayPoints points primitiveShape = do - renderPrimitive primitiveShape$makeVertices points - flush + transf :: Map.Map String (Geometry -> Geometry) -- transformation of a Geoemtry Node (i.e. triangulation) + transf = Map.fromList [("to3d", to3d)] -makeVertices = mapM_ ( \(x,y,z) -> vertex$Vertex3 x y z ) -makeV = map ( \(x,y,z) -> (Vertex3 x y z) ) + to3d geom = ( ((extrude (0,0.1,0)).deleteHoles) geom ) `atop` ( tri (translate (0,0.1,0) geom) ) -- not the perfect solution since some points are generated twice + tri = (triangulate ketTri).deleteHoles -setVertex :: (TexCoord2 GLfloat, Vertex3 GLfloat) -> IO () -setVertex (texCoordinates, vertexCoordinates) = do texCoord texCoordinates; vertex vertexCoordinates; + tex = makeTexMap resolution prop transf + node = displayString str "node" resolution prop transf mode spacing o v1 v2 f tex --- A routine used to draw a list of coordinates. -mapVertices :: [(TexCoord2 GLfloat)] -> [(Vertex3 GLfloat)] -> IO () -mapVertices texs verts = mapM_ setVertex (zip texs verts) + genCollada (lightedScene node) emptyAnim + putStrLn "Collada File generated"
− src/Test/PointOfView.hs
@@ -1,104 +0,0 @@-module Test.PointOfView where - -import Graphics.Rendering.OpenGL -import Graphics.UI.GLUT -- hiding (GLdouble, get, lookAt, Vertex3, Vector3, $=, viewport) -import Data.IORef -import System(ExitCode(..), exitWith) -import Data.List (nub) --- import Graphics.UI.GLUT ( Key(SpecialKey, Char), postRedisplay, SpecialKey(..), KeyState(..) ) - -setPointOfView :: IORef [Key] -> IORef (GLdouble,GLdouble,GLdouble, GLdouble,GLdouble,GLdouble, GLdouble,GLdouble,GLdouble) -> IO () -setPointOfView ks pPos = do - keystate <- get ks - if Char 'w' `elem` keystate then do move_vw pPos 0.01 -- vorward - postRedisplay Nothing - else return() - if Char 's' `elem` keystate then do move_vw pPos (negate 0.01) -- backward - postRedisplay Nothing - else return() - if Char 'a' `elem` keystate then do strafe pPos (0.01) -- left - postRedisplay Nothing - else return() - if Char 'd' `elem` keystate then do strafe pPos (negate 0.01) - postRedisplay Nothing -- right - else return() - if (SpecialKey KeyLeft) `elem` keystate then do rot pPos (0,negate 0.002) - postRedisplay Nothing - else return() - if (SpecialKey KeyRight) `elem` keystate then do rot pPos (0, 0.002) - postRedisplay Nothing - else return() - if (SpecialKey KeyUp) `elem` keystate then do rot pPos (0.002, 0) - postRedisplay Nothing - else return() - if (SpecialKey KeyDown) `elem` keystate then do rot pPos (negate 0.002,0) - postRedisplay Nothing - else return() - (x,y,z,lx,ly,lz,ux,uy,uz) <- get pPos - lookAt (Vertex3 x y z) (Vertex3 lx ly lz) (Vector3 ux uy uz) -- x y z: the point where the viewer is situated - -- lx ly lz: the point at which the viewer is looking - -- ux uy uz: a vector, which is up for the viewer - -keyboard :: IORef [Key] -> Key -> KeyState -> t -> t1 -> IO () -keyboard keystate key ks _ _ = do - case (key,ks) of - (Char '\27',_) -> do exitWith ExitSuccess - (Char 'q',_) -> do exitWith ExitSuccess - (_,Down) -> modifyIORef keystate $ nub . (++[key]) - (_,Up) -> modifyIORef keystate $ filter (/=key) - ---move_vw :: IORef (GLdouble,GLdouble,GLdouble,GLdouble,GLdouble,GLdouble,GLdouble,GLdouble,GLdouble) -> GLdouble -> IO () -move_vw pPos delta = do - (x,y,z,lx,ly,lz,ux,uy,uz) <- get pPos - let dx = (lx-x)*delta - dy = (ly-y)*delta - dz = (lz-z)*delta - pPos $= (x+dx, y+dy, z+dz, lx+dx, ly+dy, lz+dz, ux,uy,uz) - -kreuz (v0,v1,v2) (w0,w1,w2) = (v1*w2-v2*w1, v2*w0-v0*w2, v0*w1-v1*w0) -kreuz2 (v0,v1,v2,pr) (w0,w1,w2,_) = (v1*w2-v2*w1, v2*w0-v0*w2, v0*w1-v1*w0, pr) -set_len (x,y,z) l = (x*c*l, y*c*l, z*c*l) where c = 1 / v_len (x,y,z) -v_len (x,y,z) = sqrt (x*x+y*y+z*z) - -strafe pPos delta = do - (x,y,z,lx,ly,lz,ux,uy,uz) <- get pPos - let (dx, dy, dz) = set_len (kreuz (x-lx,y-ly,z-lz) (ux,uy,uz)) delta - pPos $= (x+dx, y+dy, z+dz, lx+dx, ly+dy, lz+dz, ux,uy,uz) - -divide (x,y,z) c = (x/c, y/c, z/c) -mul (x,y,z) c = (x*c, y*c, z*c) - -rot pPos (alpha, beta) = do - (px,py,pz,lx,ly,lz,ux,uy,uz) <- get pPos - return (set_len (ux,uy,uz) (v_len (lx-px,ly-py,lz-pz))) - let lx_alpha = px + (lx-px)*(cos alpha) + ux*(sin alpha) - ly_alpha = py + (ly-py)*(cos alpha) + uy*(sin alpha) - lz_alpha = pz + (lz-pz)*(cos alpha) + uz*(sin alpha) - ux_alpha = - (lx-px)*(sin alpha) + ux*(cos alpha) - uy_alpha = - (ly-py)*(sin alpha) + uy*(cos alpha) - uz_alpha = - (lz-pz)*(sin alpha) + uz*(cos alpha) - k = (kreuz (lx_alpha-px, ly_alpha-py, lz_alpha-pz) (ux_alpha, uy_alpha, uz_alpha)) - (kx,ky,kz) = (k `divide` (v_len k)) `mul` (v_len (ux, uy, uz)) - - pPos $= (px, py, pz, - px + (lx_alpha-px)*(cos beta) + kx*(sin beta), - py + (ly_alpha-py)*(cos beta) + ky*(sin beta), - pz + (lz_alpha-pz)*(cos beta) + kz*(sin beta), - ux_alpha, uy_alpha, uz_alpha) - -idle pPos = do - postRedisplay Nothing - -reshape screenSize@(Size w h) = do - viewport $= ((Position 0 0), screenSize) - matrixMode $= Projection - loadIdentity - let near = 0.001 - far = 40 - fov = 90 - ang = (fov*pi)/(360) -- equals 45 degree - top = near / ( cos(ang) / sin(ang) ) -- top/near has the same value as 1/(x/y) - aspect = fromIntegral(w)/fromIntegral(h) - right = top*aspect - frustum (-right) right (-top) top near far - matrixMode $= Modelview 0