pictikz-1.0.0.1: src/Pictikz/Parser.hs
-- Copyright 2017 Marcelo Garlet Millani
-- This file is part of pictikz.
-- pictikz is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
-- pictikz is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
-- You should have received a copy of the GNU General Public License
-- along with pictikz. If not, see <http://www.gnu.org/licenses/>.
module Pictikz.Parser where
import Prelude hiding (splitAt)
import Data.Matrix
import qualified Pictikz.Geometry as G
import Control.Monad.Trans.State
import Control.Monad
import Data.List hiding (splitAt)
import Data.Char
import qualified Debug.Trace as D (trace)
splitBy :: (Char -> Bool) -> String -> [String]
splitBy _ [] = []
splitBy p xs =
let (w, r) = span (not . p) (dropWhile p xs)
in w : (splitBy p r)
consumeWhile :: (Char -> Bool) -> State String String
consumeWhile f = do
str <- get
let (x,r) = span f str
put r
return x
whenNotEmpty e x = do
s <- get
if s == "" then return e else x
mmFactor "in" = 25.4
mmFactor "cm" = 10
mmFactor "pt" = 2.834646
mmFactor "pc" = 0.2362205
mmFactor _ = 1
readLength len =
let (n,u) = span (\x -> isNumber x || x == '.') len in (read n :: Float) * (mmFactor u)
parseStyle style = map (\f -> let (k,v) = span (/=':') f in (k,tail v) ) $ splitBy (==';') style
parseTransform transform = evalState parseTransform' transform
where
parseTransform' :: State String (Matrix Float)
parseTransform' = do
f <- function
ps <- parameters
let m = buildTransform f ps
rest <- whenNotEmpty (identity 3) $ parseTransform'
return $ m * rest
function :: State String String
function = do
consumeWhile (\x -> x `elem` [' ','\t'])
f <- consumeWhile isAlpha
return $ map toLower f
parameters :: State String [Float]
parameters = do
consumeWhile (\x -> x `elem` [' ', '(', '\t'])
ps <- consumeWhile (/= ')')
consumeWhile (\x -> x `elem` [' ', ')', '\t'])
let params = (splitBy (==',') ps)
return $ map read params
buildTransform :: String -> [Float] -> (Matrix Float)
buildTransform "scale" [sx] = G.scale sx sx
buildTransform "scale" [sx,sy] = G.scale sx sy
buildTransform "translate" [tx] = G.translate tx 0
buildTransform "translate" [tx, ty] = G.translate tx ty
buildTransform "rotate" [a] = G.rotate a
buildTransform "rotate" [a,cx ,cy] = (G.translate cx cy) * (G.rotate a) * (G.translate (-cx) (-cy))
buildTransform "matrix" [a,b,c,d,e,f] = fromList 3 3 [a,c,e,b,d,f,0,0,1]
buildTransform "skewx" [a] = G.skewx a
buildTransform "skewy" [a] = G.skewy a
buildTransform _ _ = identity 3
parsePath path = evalState parsePath' path
where
parsePath' :: State String [(Float, Float)]
parsePath' = do
c <- command
processCommand (0,0) (Right (0,0)) c
processCommand (cx,cy) first cmd
| cmd == "m" || cmd == "l" || cmd == "t" = do
(x,y) <- coordPair
c <- command
let this = (x + cx, y + cy)
rest <- whenNotEmpty [] $ processCommand this (first >> (Left this)) (if c == "" then cmd else c)
return $ this : rest
| cmd == "M" || cmd == "L" || cmd == "T" = do
(x,y) <- coordPair
c <- command
let this = (x, y)
rest <- whenNotEmpty [] $ processCommand this (first >> (Left this)) (if c == "" then cmd else c)
return $ this : rest
| cmd == "H" = do
x <- value
c <- command
let this = (x, cy)
rest <- whenNotEmpty [] $ processCommand this (first >> (Left this)) (if c == "" then cmd else c)
return $ this : rest
| cmd == "h" = do
x <- value
c <- command
let this = (cx + x, cy)
rest <- whenNotEmpty [] $ processCommand this (first >> (Left this)) (if c == "" then cmd else c)
return $ this : rest
| cmd == "V" = do
y <- value
c <- command
let this = (cx, y)
rest <- whenNotEmpty [] $ processCommand this (first >> (Left this)) (if c == "" then cmd else c)
return $ this : rest
| cmd == "v" = do
y <- value
c <- command
let this = (cx, cy + y)
rest <- whenNotEmpty [] $ processCommand this (first >> (Left this)) (if c == "" then cmd else c)
return $ this : rest
| cmd == "c" = do
coordPair -- first and second control points are irrelevant
coordPair
(x,y) <- coordPair
c <- command
let this = (x + cx, y + cy)
rest <- whenNotEmpty [] $ processCommand this (first >> (Left this)) (if c == "" then cmd else c)
return $ this : rest
| cmd == "C" = do
coordPair -- first and second control points are irrelevant
coordPair
(x,y) <- coordPair
c <- command
let this = (x, y)
rest <- whenNotEmpty [] $ processCommand this (first >> (Left this)) (if c == "" then cmd else c)
return $ this : rest
| cmd == "s" || cmd == "q" = do
coordPair -- first control point is irrelevant
(x,y) <- coordPair
c <- command
let this = ((x + cx), (y + cy))
rest <- whenNotEmpty [] $ processCommand this (first >> (Left this)) (if c == "" then cmd else c)
return $ this : rest
| cmd == "S" || cmd == "Q"= do
coordPair -- first control point is irrelevant
(x,y) <- coordPair
c <- command
let this = (x, y)
rest <- whenNotEmpty [] $ processCommand this (first >> (Left this)) (if c == "" then cmd else c)
return $ this : rest
| cmd == "A" = do
skipNValue 5 -- rx ry x-rotation larg-arc-flag sweep-flag are irrelevant
(x,y) <- coordPair
c <- command
let this = (x, y)
rest <- whenNotEmpty [] $ processCommand this (first >> (Left this)) (if c == "" then cmd else c)
return $ this : rest
| cmd == "a" = do
skipNValue 5 -- rx ry x-rotation larg-arc-flag sweep-flag are irrelevant
(x,y) <- coordPair
c <- command
let this = (x + cx, y + cy)
rest <- whenNotEmpty [] $ processCommand this (first >> (Left this)) (if c == "" then cmd else c)
return $ this : rest
| cmd == "z" || cmd == "Z" =
case first of
Left p -> return [p]
Right p -> return [p]
coordPair :: State String (Float, Float)
coordPair = do
consumeWhile (\x -> x `elem` [' ','\t',','])
x <- consumeWhile ((\x -> not $ x `elem` [' ','\t',',']))
consumeWhile (\x -> x `elem` [' ','\t',','])
y <- consumeWhile ((\x -> not $ x `elem` [' ','\t',',']))
return (read x, read y)
command :: State String String
command = do
consumeWhile (\x -> x `elem` [' ','\t',','])
c <- consumeWhile isAlpha
return c
value :: State String Float
value = do
consumeWhile (\x -> x `elem` [' ','\t',','])
x <- consumeWhile ((\x -> not $ x `elem` [' ','\t',',']))
return $ read x
skipNValue :: Int -> State String ()
skipNValue n = replicateM_ n value