{-# LANGUAGE OverloadedStrings, NoMonomorphismRestriction, TypeFamilies, FlexibleContexts #-} -------------------------------------------------------------------- -- | -- Module : Diagrams.SVG.Attributes -- Copyright : (c) 2015 Tillmann Vogt -- License : BSD3 -- -- Maintainer: diagrams-discuss@googlegroups.com -- Stability : stable -- Portability: portable module Diagrams.SVG.Attributes ( initialStyles -- * Classes of attributes , CoreAttributes(..) , ConditionalProcessingAttributes(..) , DocumentEventAttributes(..) , GraphicalEventAttributes(..) , XlinkAttributes(..) , FilterPrimitiveAttributes(..) , NameSpaces(..) -- * General Parsing Functions , separatedBy , parseOne , parseOne' , compose , parseDouble , parseToDouble , parsePoints , parseTempl , parseIRI -- * Transformations , applyTr , parseTr -- * Parsing the style attribute , applyStyleSVG , parseStyles , parseLengths , parseViewBox , parsePA , cssStylesFromMap , fragment , p , parseSpread -- * Parsing Colors -- * Parsing preserve aspect ratio , parsePreserveAR , PreserveAR(..) , AlignSVG(..) , Place(..) , MeetOrSlice(..) , SVGStyle(..) , PresentationAttributes(..) ) where import Data.Attoparsec.Combinator import Data.Attoparsec.Text import qualified Data.Attoparsec.Text as AT import Data.Char (isAlpha, isHexDigit, digitToInt) import Data.Colour import Data.Colour.Names (readColourName) import Data.Colour.SRGB import Data.Colour.RGBSpace.HSL (hsl) import qualified Data.HashMap.Strict as H import Data.Maybe (fromMaybe, fromJust, isJust, isNothing, maybeToList, catMaybes) import qualified Data.Text as T import Data.Text(Text(..), pack, unpack, empty, cons, snoc, append) import Data.Typeable import Data.Word (Word8) import Diagrams.Prelude hiding (fillOpacity, strokeOpacity) import Diagrams.SVG.Path import Diagrams.SVG.Tree import Text.CSS.Parse import Diagrams.Core.Transform import Data.Digits --------------------------------------------------- data CoreAttributes = CA { id1 :: Maybe Text , xmlbase :: Maybe Text , xmllang :: Maybe Text , xmlspace :: Maybe Text } data ConditionalProcessingAttributes = CPA { requiredFeatures :: Maybe Text , requiredExtensions :: Maybe Text , systemLanguage :: Maybe Text } data DocumentEventAttributes = DEA { onunload :: Maybe Text , onabort :: Maybe Text , onerror :: Maybe Text , onresize :: Maybe Text , onscroll :: Maybe Text , onzoom :: Maybe Text } data GraphicalEventAttributes = GEA { onfocusin :: Maybe Text , onfocusout :: Maybe Text , onactivate :: Maybe Text , onclick :: Maybe Text , onmousedown :: Maybe Text , onmouseup :: Maybe Text , onmouseover :: Maybe Text , onmousemove :: Maybe Text , onmouseout :: Maybe Text , onload :: Maybe Text } data XlinkAttributes = XLA { xlinkHref :: Maybe Text , xlinkShow :: Maybe Text , xlinkActuate :: Maybe Text , xlinkType :: Maybe Text , xlinkRole :: Maybe Text , xlinkArcrole :: Maybe Text , xlinkTitle :: Maybe Text } data FilterPrimitiveAttributes = FPA { x :: Maybe Text , y :: Maybe Text , width :: Maybe Text , height :: Maybe Text , result :: Maybe Text } data NameSpaces = NSP { xlink :: Maybe Text , dc :: Maybe Text , cc :: Maybe Text , rdf :: Maybe Text , svg :: Maybe Text , sodipodi :: Maybe Text , inkscape :: Maybe Text } deriving Show -------------------------------------------------------------------------------- -- General parsing functions -------------------------------------------------------------------------------- -- | Parsing content separated by something, e.g. ";" like in: "a;b;c;d;" or "a;b;c;d" separatedBy parse sep = do ls <- many1 (choice [parseOne parse sep, parseOne' parse]) return ls parseOne parse sep = do AT.skipSpace s <- parse AT.string sep return s parseOne' parse = do AT.skipSpace s <- parse return s -- | See compose :: [a -> a] -> a -> a compose fs v = Prelude.foldl (flip (.)) id fs $ v parseDouble :: RealFloat n => Text -> n parseDouble l = either (const 0) (fromRational . toRational) (AT.parseOnly myDouble l) parseToDouble :: RealFloat n => Maybe Text -> Maybe n parseToDouble l | isJust l = either (const Nothing) (Just . fromRational . toRational) (AT.parseOnly myDouble (fromJust l)) | otherwise = Nothing pp = parseDouble . pack myDouble = AT.choice [dotDouble, double] dotDouble = do AT.skipSpace AT.char '.' frac <- AT.decimal let denominator = fromIntegral (10^(length $ digits 10 frac)) return ((fromIntegral frac) / denominator) parsePoints :: RealFloat n => Text -> [(n, n)] parsePoints t = either (const []) id (AT.parseOnly (many' parsePoint) t) parsePoint :: RealFloat n => Parser (n, n) parsePoint = do AT.skipSpace a <- double AT.char ',' b <- double return ( (fromRational . toRational) a, (fromRational . toRational) b) parseUntil c = AT.manyTill AT.anyChar (AT.char c) data Tup n = TS1 Text | TS2 Text Text | TS3 Text Text Text | T1 n | T2 n n | T3 n n n deriving Show parse1 = do AT.skipSpace AT.char '(' a <- AT.takeTill (== ')') AT.char ')' return (TS1 a) parse2 = do AT.skipSpace AT.char '(' a <- AT.takeTill (\c -> c == ',' || c == ' ') AT.choice [AT.char ',', AT.char ' '] AT.skipSpace b <- AT.takeTill (== ')') AT.char ')' return (TS2 a b) parse3 = do AT.skipSpace AT.char '(' a <- AT.takeTill (\c -> c == ',' || c == ' ') AT.choice [AT.char ',', AT.char ' '] b <- AT.takeTill (\c -> c == ',' || c == ' ') AT.choice [AT.char ',', AT.char ' '] c <- AT.takeTill (== ')') AT.char ')' return (TS3 a b c) ----------------------------------------------------------------------------------------------------------------- -- Transformations, see -- -- Example: transform="translate(-121.1511,-167.6958) matrix(4.675013,0,0,4.675013,-1353.75,-678.4329)" ----------------------------------------------------------------------------------------------------------------- data Transform n = Tr (Tup n) | Matrix n n n n n n | Rotate (Tup n) | Scale (Tup n) | SkewX (Tup n) | SkewY (Tup n) deriving Show parseTr :: RealFloat n => Maybe Text -> [Transform n] parseTr = reverse . catMaybes . (either (const []) id) . ( AT.parseOnly (AT.many1 parseTransform)) . (fromMaybe empty) parseTransform = AT.choice [matr, trans, scle, rot, skewX, skewY] applyTr trs = compose (map getTransformations trs) getTransformations (Tr (T1 x)) = translateX x getTransformations (Tr (T2 x y)) = (translateX x) . (translateY y) -- | See getTransformations (Matrix a b c d e f) = (translateX x) . (translateY y) . (rotateBy angle) . (scaleX scX) . (scaleY scY) where (angle, scX, scY, x, y) = matrixDecompose (Matrix a b c d e f) -- matrix(0.70710678,-0.70710678,0.70710678,0.70710678,0,0) getTransformations (Rotate (T1 angle)) = rotateBy angle getTransformations (Rotate (T3 angle x y)) = id -- rotationAbout (p2 (x,y)) (angle) getTransformations (Scale (T1 x)) = scaleX x getTransformations (Scale (T2 x y)) = (scaleX x) . (scaleY y) getTransformations (SkewX (T1 x)) = id getTransformations (SkewY (T1 y)) = id -- | See matrixDecompose (Matrix m11 m12 m21 m22 m31 m32) = (rotation, scX, scY, transX, transY) where rotation = (atan2 m12 m22) / (2*pi) scX | m11 >= 0 = sqrt (m11*m11 + m21*m21) | otherwise = - sqrt (m11*m11 + m21*m21) scY | m22 >= 0 = sqrt (m12*m12 + m22*m22) | otherwise = - sqrt (m12*m12 + m22*m22) (transX, transY) = (m31, m32) matr = do AT.skipSpace AT.string "matrix" AT.skipSpace AT.char '(' a <- parseUntil ',' b <- parseUntil ',' c <- parseUntil ',' d <- parseUntil ',' e <- parseUntil ',' f <- parseUntil ')' return (Just $ Matrix (pp a) (pp b) (pp c) (pp d) (pp e) (pp f) ) evalTup (TS1 x) = T1 (parseDouble x) evalTup (TS2 x y) = T2 (parseDouble x) (parseDouble y) evalTup (TS3 x y z) = T3 (parseDouble x) (parseDouble y) (parseDouble z) trans = do AT.skipSpace AT.string "translate" tup <- AT.choice [parse2, parse1] return (Just $ Tr (evalTup tup)) scle = do AT.skipSpace AT.string "scale" tup <- AT.choice [parse2, parse1] return (Just $ Scale (evalTup tup)) rot = do AT.skipSpace AT.string "rotate" tup <- AT.choice [parse1, parse3] return (Just $ Rotate (evalTup tup)) skewX = do AT.skipSpace AT.string "skewX" angle <- parse1 return (Just $ SkewX (evalTup angle)) skewY = do AT.skipSpace AT.string "skewY" angle <- parse1 return (Just $ SkewY (evalTup angle)) ------------------------------------------------------------------------------------------------ -- Parse the styles of various presentation attributes. -- Example: Maybe Text -> HashMaps b n -> [(SVGStyle n a)] parseStyles text hmaps = either (const []) id $ AT.parseOnly (separatedBy (parseStyleAttr hmaps) ";") (fromMaybe empty text) -- parseStyleAttr :: (Read a, RealFloat a, RealFloat n) => HashMaps b n -> Parser (SVGStyle n a) parseStyleAttr (ns,css,grad) = AT.choice [styleFillRule, styleStrokeWidth, styleStrokeDashArray, styleFill css grad, styleStroke css grad, styleStopColor, styleStopOpacity, styleFillOpacity, styleStrokeOpacity, styleOpacity, styleFontFamily, styleFontStyle, styleFontVariant, styleFontWeight, styleFontStretch, styleFontSize, styleStrokeLineCap, styleStrokeLineJoin, styleStrokeMiterLimit, styleClipPath ns, skipOne] skipOne = do str <- AT.manyTill AT.anyChar (AT.char ';') -- TODO end of input ? return EmptyStyle -- | This function is called on every tag and returns a list of style-attributes to apply -- (if there is a rule that matches) -- TO DO: CSS2 + CSS3 selectors -- cssStylesFromMap :: (Read a, RealFloat a, RealFloat n) => -- HashMaps b n -> Text -> Maybe Text -> Maybe Text -> [(SVGStyle n a)] cssStylesFromMap (ns,css,grad) tagName id_ class_ = parseStyles ( Just ( T.concat ( map f attributes ) ) ) (ns,css,grad) where f (attr, val) = (attr `Data.Text.snoc` ':') `append` (val `Data.Text.snoc` ';') styleFromClass cl = [H.lookup ('.' `Data.Text.cons` cl) css] ++ [H.lookup (tagName `append` ('.' `Data.Text.cons` cl)) css] attributes = concat $ catMaybes ( [H.lookup "*" css] ++ -- apply this style to every element (if isJust id_ then [H.lookup ('#' `Data.Text.cons` (fromJust id_)) css] else []) ++ (concat (map styleFromClass (if isJust class_ then T.words $ fromJust class_ else []))) ) -- | a template that deals with the common parser errors parseTempl :: Parser a -> Maybe Text -> Maybe a parseTempl p = (either (const Nothing) Just) . (AT.parseOnly p). (fromMaybe empty) -- | Given a minimum and maximum value of a viewbox (x or y-direction) and a maybe a Text value -- Parse this Text value as a length (with a unit) or a percentage relative to the viewbox (minx,maxx) -- If parsers fails return def p :: RealFloat n => (n,n) -> n -> Maybe Text -> n p (minx,maxx) def x = unL $ fromMaybe (Len def) $ parseTempl styleLength x where unL (Len x) = x unL (Percent x) = x/100 * (maxx-minx) parseIRI = do AT.choice [ funcIRI, absoluteOrRelativeIRI ] funcIRI = do AT.skipSpace AT.string "url(" absrel <- parseUntil '#' frag <- parseUntil ')' return (T.pack absrel, T.pack frag) absoluteOrRelativeIRI = do AT.skipSpace absrel <- parseUntil '#' frag <- takeText return (T.pack absrel, frag) fragment x = fmap snd (parseTempl parseIRI x) -- look only for the text after "#" -- | Inital styles, see: initialStyles = lwL 1 . fc black . lineCap LineCapButt . lineJoin LineJoinMiter . lineMiterLimit 4 . lcA transparent . fontSize medium -- fillRule nonzero -- TODO -- fillOpcacity 1 -- TODO -- stroke-opacity 1 # -- stroke-dasharray none -- stroke-dashoffset 0 # -- display inline applyStyleSVG stylesFromMap hmap = compose (map getStyles (stylesFromMap hmap)) getStyles (Fill c) = fcA c getStyles (FillTex x) = fillTexture x getStyles (FillRule Even_Odd) = fillRule EvenOdd getStyles (FillRule Nonzero) = id getStyles (FillRule Inherit) = id getStyles (FillOpacity x) = Diagrams.Prelude.opacity x getStyles (FontFamily str) = font str getStyles (FontStyle s) = id getStyles (FontVariant s) = id getStyles (FontWeight s) = id getStyles (FontStretch s) = id getStyles (FontSize (Len len)) = fontSize (local len) -- getStyles (FontSize (Percent len)) = fontSize (local len) getStyles (Diagrams.SVG.Attributes.Opacity x) = Diagrams.Prelude.opacity x getStyles (StrokeOpacity x) | x == 0 = lwL 0 | otherwise = Diagrams.Prelude.opacity x -- we currently don't differentiate between fill opacity and stroke opacity getStyles (Stroke x) = lcA x getStyles (StrokeTex x) = lineTexture x getStyles (StrokeWidth (Len x)) = lwL $ fromRational $ toRational x getStyles (StrokeWidth (Percent x)) = lwG x getStyles (StrokeLineCap x) = lineCap x getStyles (StrokeLineJoin x) = lineJoin x getStyles (StrokeMiterLimit x) = id getStyles (StrokeDasharray array) = dashingL (map dash array) 0 where dash (Len x) = x dash (Percent x) = x -- TODO implement percent length getStyles (ClipPath path) = clipBy path getStyles _ = id -- | Example: style="fill:#ffb13b" style="fill:red" styleFill css hmap = do AT.skipSpace AT.string "fill:" AT.skipSpace styleFillVal css hmap styleFillVal css gradients = AT.choice [ styleFillColourVal, styleFillTexURL css gradients ] styleFillColourVal = do c <- AT.choice [colorRRGGBB, colorRGB, colorString, colorRGBPercent, colorHSLPercent, colorNone, colorRGBWord] return (Fill c) styleFillTexURL css gradients = do (absrel,frag) <- parseIRI let t = H.lookup frag gradients if isJust t then return (FillTex (getTexture (fromJust t))) else return EmptyStyle where getTexture (Gr refId ga vb stops f) = f css ga (fromMaybe (0,0,0,0) vb) stops -- | Example: style="fill-rule:evenodd" styleFillRule = do AT.skipSpace AT.string "fill-rule:" AT.skipSpace styleFillRuleVal styleFillRuleVal = do AT.choice [ (do{ AT.string "evenodd"; return $ FillRule Even_Odd }), (do{ AT.string "nonzero"; return $ FillRule Nonzero }), (do{ AT.string "inherit"; return $ FillRule Inherit }) ] -- | Example: style="fill:#ffb13b" style="fill:red" styleFillOpacity = do AT.skipSpace AT.string "fill-opacity:" AT.skipSpace styleFillOpacityVal styleFillOpacityVal = do o <- myDouble return (FillOpacity $ fromRational $ toRational o) -- | Example: style="fill:#ffb13b" style="fill:red" styleOpacity = do AT.skipSpace AT.string "opacity:" AT.skipSpace styleOpacityVal styleOpacityVal = do o <- myDouble return (Diagrams.SVG.Attributes.Opacity $ fromRational $ toRational o) -- | Example: style="stroke:black" styleStroke css hmap = do AT.skipSpace AT.string "stroke:" AT.skipSpace styleStrokeVal css hmap styleStrokeVal css gradients = AT.choice [ styleStrokeColourVal, styleStrokeTexURL css gradients ] styleStrokeColourVal = do c <- AT.choice [colorRRGGBB, colorRGB, colorString, colorRGBPercent, colorHSLPercent, colorNone, colorRGBWord] return (Stroke c) styleStrokeTexURL css gradients = do (absrel,frag) <- parseIRI let t = H.lookup frag gradients if isJust t then return (StrokeTex (getTexture (fromJust t))) else return EmptyStyle where getTexture (Gr refId ga vb stops f) = f css ga (fromMaybe (0,0,0,0) vb) stops -- | Example: style="stroke-width:0.503546" styleStrokeWidth = do AT.skipSpace AT.string "stroke-width:" styleStrokeWidthVal styleStrokeWidthVal = do len <- styleLength return (StrokeWidth len) ------------------------------------------------------------------------------------- -- font styleFontFamily = do AT.skipSpace AT.string "font-family:" str <- AT.manyTill AT.anyChar theEnd return (FontFamily str) theEnd = do AT.choice [AT.char ';', do { endOfInput; return ' '}] data FStyle = NormalStyle | Italic | Oblique | FSInherit styleFontStyle = do AT.skipSpace AT.string "font-style:" AT.choice [ do { string "normal"; return (FontStyle NormalStyle)} , do { string "italic"; return (FontStyle Italic)} , do { string "oblique"; return (FontStyle Oblique)} , do { string "inherit"; return (FontStyle FSInherit)} ] data FVariant = NormalVariant | SmallCaps | VInherit styleFontVariant = do AT.skipSpace AT.string "font-variant:" AT.choice [ do { string "normal"; return (FontVariant NormalVariant)} , do { string "small-caps"; return (FontVariant SmallCaps)} , do { string "inherit"; return (FontVariant VInherit)} ] data FWeight = NormalWeight | Bold | Bolder | Lighter | N100 | N200 | N300 | N400 | N500 | N600 | N700 | N800 | N900 | FWInherit styleFontWeight = do AT.skipSpace AT.string "font-weight:" AT.choice [ do { string "normal"; return (FontWeight NormalWeight)} , do { string "bold"; return (FontWeight Bold)} , do { string "bolder"; return (FontWeight Bolder)} , do { string "lighter"; return (FontWeight Lighter)} , do { string "100"; return (FontWeight N100)} , do { string "200"; return (FontWeight N200)} , do { string "300"; return (FontWeight N300)} , do { string "400"; return (FontWeight N400)} , do { string "500"; return (FontWeight N500)} , do { string "600"; return (FontWeight N600)} , do { string "700"; return (FontWeight N700)} , do { string "800"; return (FontWeight N800)} , do { string "900"; return (FontWeight N900)} , do { string "inherit"; return (FontWeight FWInherit)} ] data FStretch = NormalStretch | Wider | Narrower | UltraCondensed | ExtraCondensed | Condensed | SemiCondensed | SemiExpanded | Expanded | ExtraExpanded | UltraExpanded | SInherit styleFontStretch = do AT.skipSpace AT.string "font-stretch:" AT.choice [ do { string "normal"; return (FontStretch NormalStretch)} , do { string "wider"; return (FontStretch Wider)} , do { string "narrower"; return (FontStretch Narrower)} , do { string "ultra-condensed"; return (FontStretch UltraCondensed)} , do { string "extra-condensed"; return (FontStretch ExtraCondensed)} , do { string "condensed"; return (FontStretch Condensed)} , do { string "semi-condensed"; return (FontStretch SemiCondensed)} , do { string "semi-expanded"; return (FontStretch SemiExpanded)} , do { string "expanded"; return (FontStretch Expanded)} , do { string "extra-expanded"; return (FontStretch ExtraExpanded)} , do { string "ultra-expanded"; return (FontStretch UltraExpanded)} , do { string "inherit"; return (FontStretch SInherit)} ] styleFontSize = do AT.skipSpace AT.string "font-size:" len <- styleLength return (FontSize len) ------------------------------------------------------------------------------------------------------------ -- | See styleLength = do AT.skipSpace d <- myDouble AT.skipSpace AT.choice [ styleLengthWithUnit (fromRational $ toRational d), lengthPercent (fromRational $ toRational d), return (Len (fromRational $ toRational d)) ] styleLengthWithUnit d = do u <- styleUnit return (Len (d * (unitFactor u))) lengthPercent d = do AT.string "%" return (Percent d) styleUnit = do AT.choice [styleEM,styleEX,stylePX,styleIN,styleCM,styleMM,stylePT,stylePC] styleEM = do { AT.choice [AT.string "em", AT.string "EM"]; return EM } styleEX = do { AT.choice [AT.string "ex", AT.string "EX"]; return EX } stylePX = do { AT.choice [AT.string "px", AT.string "PX"]; return PX } styleIN = do { AT.choice [AT.string "in", AT.string "IN"]; return IN } styleCM = do { AT.choice [AT.string "cm", AT.string "CM"]; return CM } styleMM = do { AT.choice [AT.string "mm", AT.string "MM"]; return MM } stylePT = do { AT.choice [AT.string "pt", AT.string "PT"]; return PT } stylePC = do { AT.choice [AT.string "pc", AT.string "PC"]; return PC } unitFactor EM = 1 unitFactor EX = 1 unitFactor PX = 1 unitFactor IN = 90 unitFactor CM = 35.43307 unitFactor MM = 3.543307 unitFactor PT = 1.25 unitFactor PC = 15 -- | Example: "stroke-linecap:butt" styleStrokeLineCap = do AT.skipSpace AT.string "stroke-linecap:" AT.skipSpace styleStrokeLineCapVal styleStrokeLineCapVal = do lc <- AT.choice [butt,round0,square0] return (StrokeLineCap lc) butt = do { AT.string "butt"; return LineCapButt } round0 = do { AT.string "round"; return LineCapRound } square0 = do { AT.string "square"; return LineCapSquare } -- | Example: "stroke-linejoin:miter;" styleStrokeLineJoin = do AT.skipSpace AT.string "stroke-linejoin:" AT.skipSpace styleStrokeLineJoinVal styleStrokeLineJoinVal = do lj <- AT.choice [miter,round1,bevel] return (StrokeLineJoin lj) miter = do { AT.string "miter"; return LineJoinMiter } round1 = do { AT.string "round"; return LineJoinRound } bevel = do { AT.string "bevel"; return LineJoinBevel } styleClipPath hmap = do AT.skipSpace AT.string "clip-path:" AT.skipSpace styleClipPathVal hmap styleClipPathVal hmap = do (absrel,frag) <- parseIRI let t = H.lookup frag hmap if isJust t then return (ClipPath $ evalPath hmap Nothing (fromJust t)) else return EmptyStyle -- | Evaluate the tree to a path. Is only needed for clipPaths evalPath :: RealFloat n => H.HashMap Text (Tag b n) -> Maybe (ViewBox n) -> (Tag b n) -> Path V2 n evalPath hmap (Just viewBox) (Leaf id1 path diagram) = path viewBox evalPath hmap Nothing (Leaf id1 path diagram) = path (0,0,1,1) -- shouldn't happen, there should always be a viewbox evalPath hmap _ (SubTree _ id1 _ (Just viewBox) ar f children) = mconcat (map (evalPath hmap (Just viewBox)) children) evalPath hmap (Just viewBox) (SubTree _ id1 _ Nothing ar f children) = mconcat (map (evalPath hmap (Just viewBox)) children) -- evalPath hmap (Reference selfId id1 wh f) = evalPath hmap (lookUp hmap (fragment id1)) -- TODO implement (not that common) evalPath hmap _ _ = mempty -- | Lookup a diagram and return an empty diagram in case the SVG-file has a wrong reference lookUp hmap i | isJust l = fromJust l | otherwise = Leaf Nothing mempty mempty -- an empty diagram if we can't find the id where l = H.lookup i hmap -- | Example: "stroke-miterlimit:miter;" styleStrokeMiterLimit = do AT.skipSpace AT.string "stroke-miterlimit:" AT.skipSpace styleStrokeMiterLimitVal styleStrokeMiterLimitVal = do l <- myDouble return $ StrokeMiterLimit $ (fromRational . toRational) l styleStrokeDashArray = do AT.skipSpace AT.string "stroke-dasharray:" styleStrokeDashArrayVal styleStrokeDashArrayVal = do len <- parseLengths return (StrokeDasharray len) parseLengths = separatedBy styleLength "," styleStrokeOpacity = do AT.skipSpace AT.string "stroke-opacity:" AT.skipSpace styleStrokeOpacityVal styleStrokeOpacityVal = do l <- myDouble return $ StrokeOpacity $ (fromRational . toRational) l styleStopColor = do AT.skipSpace AT.string "stop-color:" AT.skipSpace styleFillColourVal styleStopOpacity = do AT.skipSpace AT.string "stop-opacity:" AT.skipSpace styleFillOpacityVal -- TODO: Visibility, marker ----------------------------------------------------------------------- -- Colors, see and -- ----------------------------------------------------------------------- colorString = do a <- Data.Attoparsec.Text.takeWhile isAlpha c <- readColourName (unpack a) return (opaque c) colorRGB = do AT.char '#' h0 <- satisfy isHexDigit h1 <- satisfy isHexDigit h2 <- satisfy isHexDigit return $ opaque ( sRGB24 (fromIntegral ((digitToInt h0) * 16)) (fromIntegral ((digitToInt h1) * 16)) (fromIntegral ((digitToInt h2) * 16)) ) colorRRGGBB = do AT.char '#' h0 <- satisfy isHexDigit h1 <- satisfy isHexDigit h2 <- satisfy isHexDigit h3 <- satisfy isHexDigit h4 <- satisfy isHexDigit h5 <- satisfy isHexDigit return $ opaque ( sRGB24 (fromIntegral ((digitToInt h0) * 16 + (digitToInt h1)) ) (fromIntegral ((digitToInt h2) * 16 + (digitToInt h3)) ) (fromIntegral ((digitToInt h4) * 16 + (digitToInt h5)) ) ) colorRGBWord = do AT.string "rgb(" AT.skipSpace r <- decimal AT.skipSpace AT.char ',' AT.skipSpace g <- decimal AT.skipSpace AT.char ',' AT.skipSpace b <- decimal AT.skipSpace AT.char ')' return $ opaque (sRGB ((fromIntegral r)/255) ((fromIntegral g)/255) ((fromIntegral b)/255)) colorRGBPercent = do AT.string "rgb(" AT.skipSpace r <- decimal AT.char '%' AT.skipSpace AT.char ',' AT.skipSpace g <- decimal AT.char '%' AT.skipSpace AT.char ',' AT.skipSpace b <- decimal AT.char '%' AT.skipSpace AT.char ')' return $ opaque (sRGB ((fromIntegral r)/100) ((fromIntegral g)/100) ((fromIntegral b)/100)) colorHSLPercent = do AT.string "hsl(" AT.skipSpace h <- decimal AT.char '%' AT.skipSpace AT.char ',' AT.skipSpace s <- decimal AT.char '%' AT.skipSpace AT.char ',' AT.skipSpace l <- decimal AT.char '%' AT.skipSpace AT.char ')' let c = hsl (fromIntegral h) (fromIntegral s) (fromIntegral l) return $ opaque (sRGB (channelRed c) (channelGreen c) (channelBlue c)) colorNone = do AT.string "none" return transparent ------------------------------------------------------------------------------------- -- | Example: spreadMethod="pad" parseSpread :: Maybe Text -> SpreadMethod parseSpread spr | isJust parsedSpread = fromJust parsedSpread | otherwise = GradPad -- most of the time its "pad" where parsedSpread = parseTempl gradSpread spr gradSpread = AT.choice [gradPad, gradReflect, gradRepeat ] gradPad = do AT.string "pad" return GradPad gradReflect = do AT.string "reflect" return GradReflect gradRepeat = do AT.string "repeat" return GradRepeat ------------------------------------------------------------------------------------- -- | Example: viewBox="0 0 100 30" -- Viewboxes establish a new viewport. Percentages (e.g. x="50%") only make sense with a viewport. parseViewBox :: RealFloat n => Maybe Text -> Maybe Text -> Maybe Text -> Maybe (ViewBox n) parseViewBox vb w h | isJust parsedVB = parsedVB -- This is how it should always be, -- but sometimes an -tag has no viewbox attribute | pw == 0 || ph == 0 = Nothing -- TODO: What does a browser do here? | otherwise = Just (0,0,pw, ph) -- If there is no viewbox the image size is the viewbox -- TODO: What does a browser do here? -- The only other option I see is finding the min and max values of -- shapes in user coordinate system, ignoring percentages -- But one pass to just find out the viewbox? where parsedVB = parseTempl viewBox vb -- Assuming percentages are not used in width/height of the top -tag -- and there are no sub--tags that use percentage-width/height to refer to their calling viewbox -- Using width and height is a hack anyway pw | isJust w = parseDouble $ fromJust w | otherwise = 0 ph | isJust h = parseDouble $ fromJust h | otherwise = 0 viewBox = do AT.skipSpace minx <- myDouble AT.skipSpace miny <- myDouble AT.skipSpace width <- myDouble AT.skipSpace height <- myDouble AT.skipSpace return ((fromRational . toRational) minx, (fromRational . toRational) miny, (fromRational . toRational) width, (fromRational . toRational) height) ------------------------------------------------------------------------------------- -- Parse preserve aspect ratio -- e.g. preserveAspectRatio="xMaxYMax meet" ------------------------------------------------------------------------------------- parsePreserveAR x = parseTempl preserveAR x preserveAR = do AT.skipSpace align <- AT.choice [alignXMinYMin,alignXMidYMin,alignXMaxYMin,alignXMinYMid,alignXMidYMid, alignXMaxYMid,alignXMinYMax,alignXMidYMax,alignXMaxYMax] AT.skipSpace meetOrSlice <- AT.choice [meet, slice] return (PAR align meetOrSlice) meet = do AT.string "meet" return Meet slice = do AT.string "slice" return Slice alignXMinYMin = do AT.string "xMinYMin" return (AlignXY 0 0) alignXMidYMin = do AT.string "xMidYMin" return (AlignXY 0.5 0) alignXMaxYMin = do AT.string "xMaxYMin" return (AlignXY 1 0) alignXMinYMid = do AT.string "xMinYMid" return (AlignXY 0 0.5) alignXMidYMid = do AT.string "xMidYMid" return (AlignXY 0.5 0.5) alignXMaxYMid = do AT.string "xMaxYMid" return (AlignXY 1 0.5) alignXMinYMax = do AT.string "xMinYMax" return (AlignXY 0 1) alignXMidYMax = do AT.string "xMidYMax" return (AlignXY 0.5 1) alignXMaxYMax = do AT.string "xMaxYMax" return (AlignXY 1 1)