- openFont :: FilePath -> FontData
- makeOutlMaps :: String -> (Rx, Int) -> (FontData, OutlineMap, OutlineTexMap)
- makeTexMap :: (Rx, Int) -> Props -> Transf -> TexMap
- data Mode
- data Spacing
- data Rx
- displayString :: String -> String -> (Rx, Int) -> Mode -> Spacing -> (V3, V3, V3) -> P -> Props -> Transf -> TexMap -> Scene
- type Kern = (Map String [Int], Map String [Int], Map String [Int], Map String [Int], Vector X)
- type SvgGlyph = Map String (String, X, String)
- type FontData = (SvgGlyph, Kern, [Float], String)
- type OutlineMap = Map String [[(X, Y)]]
- type OutlineTexMap = Map String [[F2P]]
- type P = [Char] -> [String]
- data CharProp
- type Props = Map String CharProp
- type Transf = Map String (Geometry -> Geometry)
- type TexMap = Map (String, String, String) (TextureObject, String)
- type X = Float
- type Y = Float
Open an SVG-Font File and extract the data
Some explanation how 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 (add 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
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
Texture images for all combinations of characters, fonts and transformations for combinations of fonts and transformations only those are used that exist in this combination in the property list while every character is used that exists in the svg-font file.
Every glyph has a unique constant horiz. advance
Mono spacing between glyphs. Experimental. Better use a monospaced font from the beginning. The longest glyph influences the bbox that is used.
The y resolution is constant. The x resolution of a glyph changes in non-mono-spaced fonts.
The x-resolution of a single glvph is chosen so that all pixels from several glyphs have the same size
The whole string as one texture (not implemented yet)
Main library function, explained with an example (that is also in Fonts.hs):
main = do args <- getArgs let str = if null args then "Haskell" else head args resolution = (400, 400)
The resolution is used for textures but also for outlines. Every outline point is placed in one grid position
mode = INSIDE_V2 spacing = KERN tex = ConstDx bit = makeOutlMaps "......srcTest/Bitstream.svg" resolution lin = makeOutlMap "../../..src\Test/LinLibertine.svg" resolution
Several different fonts can be used. They are stored in Data.Map structures to avoid recalculation. Lazy Evaluation ensures that outlines are only calculated if needed.
o = V3 0 0 0 -- origin v1 = V3 (-5) 0 0 -- direction of char-advance v2 = V3 0 0 1 -- height direction v3 = V3 0 0.1 0 -- extrusion
The position and size of the string
f :: String -> [String] f str = take (length str) (concat (repeat ["p","q"]))
Assigning a property to every character by a string. Here an alternation of 3d and textured characters
props :: Props props = Map.fromList [(p, Outl bit to3d), (q, Outl bit to3d2), (r, Tex bit red), (s, Tex bit blue) ]
Finite data structures are assigned to every property string. This is needed to lazily make a Data.Map with every possible representation of a character. A textured character needs an unchanged (maybe colored) outline. Thats why "q" uses the id function. If several fonts are used, kerning is disabled between every two characters that are from different fonts.
transf :: Transf transf = Map.fromList [(to3d,to3d), (to3d2,to3d2), (red, red.bgWhite), (blue,blue.bgWhite), (id,id)]
Although there might be finitely many functions that make sense it is still to much and we need a small finite list for Data.Map. The number of combinations is ((Number of chars in font0) + (Number of chars in font1) + ...) * (number of transformation functions) and every of these combinations is built (but only evaluated if needed because of lazy evaluation) Example: (font1: 40 chars + font2: 1000 chars) X (5 colors (transformation functions)) 5200 key-value-pairs (assuming all colors are used in both fonts).
to3d geom = red $ ( ((extrude v3).deleteHoles) geom ) `atop` ( tri ((translate v3) geom) ) to3d2 geom = blue $ ( ((extrude (0,0.2,0)).deleteHoles) geom )
atop( tri ((translate (0,0.2,0)) geom) ) tri = (triangulate ketTri).deleteHoles -- openglTriangulation red = changeDiffuseColor red (1,0,0,1) -- if used with textures diffuse is interpreted as foreground color blue = changeDiffuseColor blue (0,0,1,1) bgWhite = changeAmbientColor white (1,1,1,1) -- if used with textures interpreted as background color
The functions can be anything like extrusion, triangulation, color
texmap = makeTexMap resolution props transf
Again a Data.Map-structure to avoid recalculation of textures. This has been separated from the outlineMap because it may one day also store the transformations applied to the outlines and maybe not every char is a texture
node = displayString str node resolution mode spacing (o,v1,v2) f props transf texmap genCollada (lightedScene node) emptyAnim putStrLn "Collada File generated"
node is a Node in a Scenegraph, that is inserted into a lighted scene and written into file that can be viewed in Blender