module Hasmin.Types.Value (
Value(..)
, Values(..)
, TextV(..)
, Separator(..)
, Url(..)
, mkOther
, mkValues
, valuesToList
, optimizeFontFamily
, lowercaseText
) where
import Control.Monad.Reader (ask, Reader, mapReader)
import Data.Monoid ((<>))
import Data.Maybe (isJust, catMaybes, isNothing)
import Data.Text (Text, toCaseFold)
import qualified Data.Text as T
import Data.Text.Lazy.Builder (fromText, singleton, Builder)
import Data.String (IsString)
import Hasmin.Config
import Hasmin.Types.BgSize
import Hasmin.Types.Class
import Hasmin.Types.Color
import Hasmin.Types.Dimension
import Hasmin.Types.FilterFunction
import Hasmin.Types.Gradient
import Hasmin.Types.Numeric
import Hasmin.Types.Position
import Hasmin.Types.RepeatStyle
import Hasmin.Types.Shadow
import Hasmin.Types.String
import Hasmin.Types.TimingFunction
import Hasmin.Types.TransformFunction
import Hasmin.Utils
data Value = Inherit
| Initial
| Unset
| NumberV Number
| PercentageV Percentage
| DistanceV Distance
| AngleV Angle
| DurationV Duration
| FrequencyV Frequency
| ResolutionV Resolution
| ColorV Color
| GradientV Text Gradient
| GenericFunc Text Values
| TransformV TransformFunction
| TimingFuncV TimingFunction
| FilterV FilterFunction
| ShadowV Shadow
| ShadowText Distance Distance (Maybe Distance) (Maybe Color)
| PositionV Position
| RepeatStyleV RepeatStyle
| BgSizeV BgSize
| BgLayer (Maybe Value) (Maybe Position) (Maybe BgSize) (Maybe RepeatStyle) (Maybe TextV) (Maybe TextV) (Maybe TextV)
| FinalBgLayer (Maybe Value) (Maybe Position) (Maybe BgSize) (Maybe RepeatStyle) (Maybe TextV) (Maybe TextV) (Maybe TextV) (Maybe Color)
| SingleTransition (Maybe TextV) (Maybe Duration) (Maybe TimingFunction) (Maybe Duration)
| SingleAnimation (Maybe Duration) (Maybe TimingFunction) (Maybe Duration) (Maybe Value) (Maybe TextV) (Maybe TextV) (Maybe TextV) (Maybe Value)
| FontV (Maybe TextV) (Maybe TextV) (Maybe Value) (Maybe TextV) Value (Maybe Value) [Value]
| StringV StringType
| UrlV Url
| Format [StringType]
| Local (Either Text StringType)
| Rect Distance Distance Distance Distance
| Other TextV
deriving (Eq, Show)
newtype TextV = TextV { getText :: Text }
deriving (Show, Ord, IsString)
instance Eq TextV where
TextV t1 == TextV t2 = toCaseFold t1 == toCaseFold t2
instance ToText TextV where
toText = getText
mkOther :: Text -> Value
mkOther = Other . TextV
newtype Url = Url (Either Text StringType)
deriving (Eq, Show)
instance ToText Url where
toBuilder (Url x) = "url(" <> toBuilder x <> singleton ')'
instance Minifiable Url where
minifyWith u@(Url x) = do
conf <- ask
pure $ if shouldRemoveQuotes conf
then Url $ either Left unquoteUrl x
else u
instance ToText Value where
toBuilder Initial = "initial"
toBuilder Inherit = "inherit"
toBuilder Unset = "unset"
toBuilder (NumberV n) = toBuilder n
toBuilder (PercentageV p) = (fromText . toText) p
toBuilder (ColorV c) = toBuilder c
toBuilder (DistanceV d) = toBuilder d
toBuilder (AngleV a) = toBuilder a
toBuilder (DurationV d) = toBuilder d
toBuilder (FrequencyV f) = toBuilder f
toBuilder (ResolutionV r) = toBuilder r
toBuilder (FilterV f) = toBuilder f
toBuilder (ShadowV s) = toBuilder s
toBuilder (StringV s) = toBuilder s
toBuilder (Other t) = fromText (getText t)
toBuilder (UrlV u) = toBuilder u
toBuilder (GenericFunc n vs) = toBuilder n <> singleton '(' <> toBuilder vs <> singleton ')'
toBuilder (Local x) = "local(" <> toBuilder x <> singleton ')'
toBuilder (Format x) = "format(" <> formatString <> singleton ')'
where formatString = mconcatIntersperse id (singleton ',') (fmap toBuilder x)
toBuilder (GradientV t g) = fromText t <> singleton '(' <> toBuilder g <> singleton ')'
toBuilder (TransformV x) = toBuilder x
toBuilder (TimingFuncV x) = toBuilder x
toBuilder (Rect a b c d) = "rect(" <> funcValues <> singleton ')'
where funcValues = mconcatIntersperse toBuilder (singleton ',') [a, b, c, d]
toBuilder (PositionV p) = toBuilder p
toBuilder (RepeatStyleV r) = toBuilder r
toBuilder (BgSizeV b) = toBuilder b
toBuilder (BgLayer a b c d e f g) =
let sz = maybe mempty (\x -> singleton '/' <> toBuilder x) c
list = catMaybes [bld a, bld d, bld e, bld f, bld g]
in if null list
then maybe mempty toBuilder b <> sz
else mconcatIntersperse id (singleton ' ') list <> maybe mempty (\x -> singleton ' ' <> toBuilder x) b <> sz
toBuilder (FinalBgLayer a b c d e f g col) =
let sz = maybe mempty (\x -> singleton '/' <> toBuilder x) c
list = catMaybes [bld a, bld d, bld e, bld f, bld g]
in if null list
then let posAndSize = maybe mempty toBuilder b <> sz
in if mempty == posAndSize
then maybe mempty toBuilder col
else posAndSize <> spacePrefixed col
else mconcatIntersperse id (singleton ' ') list <> spacePrefixed b <> sz <> spacePrefixed col
where spacePrefixed :: ToText a => Maybe a -> Builder
spacePrefixed = maybe mempty (\x -> singleton ' ' <> toBuilder x)
toBuilder (SingleTransition prop t1 tf t2) =
mconcatIntersperse id (singleton ' ') $ catMaybes [bld tf, bld t1, bld t2, bld prop]
toBuilder (SingleAnimation t1 tf t2 ic ad af ap kf) =
let list = catMaybes [bld t1, bld t2, bld tf, bld ic, bld ad, bld af, bld ap, bld kf]
in mconcatIntersperse id (singleton ' ') list
toBuilder (FontV fsty fvar fwgt fstr fsz lh ff) =
let bldLh = maybe mempty (\x -> singleton '/' <> toBuilder x) lh
list = catMaybes [bld fsty, bld fvar, bld fwgt, bld fstr]
ffam = singleton ' ' <> mconcatIntersperse toBuilder (singleton ',') ff
in if null list
then toBuilder fsz <> bldLh <> ffam
else mconcatIntersperse id (singleton ' ') list <> singleton ' ' <> toBuilder fsz <> bldLh <> ffam
toBuilder (ShadowText l1 l2 ml mc) =
let maybeToBuilder :: ToText a => Maybe a -> Builder
maybeToBuilder = maybe mempty (\x -> singleton ' ' <> toBuilder x)
in toBuilder l1 <> singleton ' ' <> toBuilder l2
<> maybeToBuilder ml <> maybeToBuilder mc
instance Minifiable Value where
minifyWith (ColorV c) = ColorV <$> minifyWith c
minifyWith (DistanceV d) = DistanceV <$> minifyWith d
minifyWith (AngleV a) = AngleV <$> minifyWith a
minifyWith (DurationV d) = DurationV <$> minifyWith d
minifyWith (FrequencyV f) = FrequencyV <$> minifyWith f
minifyWith (ResolutionV r) = ResolutionV <$> minifyWith r
minifyWith (GradientV t g) = GradientV t <$> minifyWith g
minifyWith (FilterV f) = FilterV <$> minifyWith f
minifyWith (TransformV tf) = TransformV <$> minifyWith tf
minifyWith (TimingFuncV tf) = TimingFuncV <$> minifyWith tf
minifyWith (StringV s) = StringV <$> minifyWith s
minifyWith (UrlV u) = UrlV <$> minifyWith u
minifyWith (Format x) = Format <$> mapM minifyWith x
minifyWith (PositionV p) = PositionV <$> minifyWith p
minifyWith (RepeatStyleV r) = RepeatStyleV <$> minifyWith r
minifyWith (BgSizeV b) = BgSizeV <$> minifyWith b
minifyWith (ShadowV s) = ShadowV <$> minifyWith s
minifyWith (ShadowText l1 l2 ml mc) = minifyPseudoShadow ShadowText l1 l2 ml mc
minifyWith (BgLayer img pos sz rst att b1 b2) = do
(i,s,p,r,a) <- minifyBgLayer img pos sz rst att
(bgOrigin, bgClip) <- handleBoxes b1 b2
pure $ if isNothing i && isNothing p && isNothing s && isNothing r
&& isNothing a && isNothing bgOrigin && isNothing bgClip
then BgLayer (Just $ mkOther "none") p s r a bgOrigin bgClip
else BgLayer i p s r a bgOrigin bgClip
minifyWith (FinalBgLayer img pos sz rst att b1 b2 col) = do
(i,s,p,r,a) <- minifyBgLayer img pos sz rst att
c <- handleColor col
(bgOrigin, bgClip) <- handleBoxes b1 b2
pure $ if isNothing i && isNothing p && isNothing s && isNothing r
&& isNothing a && isNothing bgOrigin && isNothing bgClip && isNothing c
then FinalBgLayer (Just $ mkOther "none") p s r a bgOrigin bgClip c
else FinalBgLayer i p s r a bgOrigin bgClip c
minifyWith (SingleTransition prop tdur tf tdel) = do
let p = if prop == Just (TextV "all")
then Nothing
else prop
(tDuration, tDelay) <- handleTime tdur tdel
tfunc <- handleTimingFunction tf
pure $ if isNothing p && isNothing tDuration && isNothing tDelay && isNothing tfunc
then SingleTransition p (Just $ Duration 0 S) tfunc tDelay
else SingleTransition p tDuration tfunc tDelay
minifyWith (SingleAnimation t1 tf t2 ic ad af ap kf) = do
(tdur, tdel) <- handleTime t1 t2
tfunc <- handleTimingFunction tf
icount <- handleIterationCount ic
(kfrms, adir, afm, p) <- handleKeywords kf ad af ap
if isNothing tdur && isNothing tdel && isNothing tfunc && isNothing
icount && isNothing kfrms && isNothing adir && isNothing afm && isNothing p
then pure $ SingleAnimation tdur tfunc tdel (Just $ NumberV 1) adir afm p kfrms
else pure $ SingleAnimation tdur tfunc tdel icount adir afm p kfrms
where handleIterationCount :: Maybe Value -> Reader Config (Maybe Value)
handleIterationCount Nothing = pure Nothing
handleIterationCount (Just x) =
case x of
NumberV 1 -> pure Nothing
_ -> pure (Just x)
handleKeywords Nothing x y z = pure (Nothing, simplifyDirection x, simplifyFillMode y, simplifyPauseState z)
handleKeywords v@(Just w) x y z
| w `elem` fmap mkOther ["normal", "reverse", "alternate", "alternate-reverse"] =
pure (Just w, x, simplifyFillMode y, simplifyPauseState z)
| w == mkOther "none" && v == (Other <$> y) =
pure (Nothing, simplifyDirection x, Nothing, simplifyPauseState z)
| w `elem` fmap mkOther ["forwards", "backwards", "both"] =
pure (Just w, simplifyDirection x, y, simplifyPauseState z)
| w `elem` fmap mkOther ["running", "paused"] =
pure (Just w, simplifyDirection x, simplifyFillMode y, z)
| otherwise = pure (Just w, simplifyDirection x, simplifyFillMode y, simplifyPauseState z)
simplifyDirection = removeIfEqualTo "normal"
simplifyPauseState = removeIfEqualTo "running"
simplifyFillMode = removeIfEqualTo "none"
minifyWith (FontV fsty fvar fwgt fstr fsz lh ff) = do
let sty = removeIfEqualTo "normal" fsty
var = removeIfEqualTo "normal" fvar
str = removeIfEqualTo "normal" fstr
wgt <- optimizeFontWeight fwgt
sz <- minifyWith fsz
l <- optimizeLineHeight lh
fam <- traverse optimizeFontFamily ff
pure $ FontV sty var wgt str sz l fam
where optimizeFontWeight :: Maybe Value -> Reader Config (Maybe Value)
optimizeFontWeight Nothing = pure Nothing
optimizeFontWeight (Just x) = do
conf <- ask
pure $ replaceForSynonym (fontweightSettings conf) x
where replaceForSynonym s (Other t)
| t == TextV "normal" = Nothing
| t == TextV "bold" && s == FontWeightMinOn = Just $ NumberV 700
| otherwise = Just $ Other t
replaceForSynonym _ (NumberV 400) = Nothing
replaceForSynonym _ y = Just y
optimizeLineHeight Nothing = pure Nothing
optimizeLineHeight (Just x) =
case x of
Other t -> if t == TextV "normal"
then pure Nothing
else Just <$> minifyWith x
y -> Just <$> minifyWith y
minifyWith (GenericFunc n vs) = GenericFunc n <$> minifyWith vs
minifyWith (Local x) = do
conf <- ask
v <- lowercaseParameters x
pure . Local $ if shouldRemoveQuotes conf
then case v of
Right s -> unquoteFontFamily s
_ -> v
else v
where lowercaseParameters :: Either Text StringType -> Reader Config (Either Text StringType)
lowercaseParameters y = do
conf <- ask
case letterCase conf of
Lowercase -> case y of
Left a -> mapReader Left $ lowercaseText a
Right b -> mapReader Right $ mapString lowercaseText b >>= minifyWith
Original -> pure y
minifyWith x = pure x
handleRepeatStyle :: Maybe RepeatStyle -> Reader Config (Maybe RepeatStyle)
handleRepeatStyle (Just x)
| x == RSPair RsRepeat Nothing = pure Nothing
| otherwise = Just <$> minifyWith x
handleRepeatStyle Nothing = pure Nothing
handleImage :: Maybe Value -> Reader Config (Maybe Value)
handleImage (Just x)
| x == Other "none" = pure Nothing
| otherwise = Just <$> minifyWith x
handleImage Nothing = pure Nothing
handleBoxes :: Maybe TextV -> Maybe TextV -> Reader Config (Maybe TextV, Maybe TextV)
handleBoxes (Just o) (Just c)
| o == c = pure (Just o, Nothing)
| o == "padding-box" && c == "border-box" = pure (Nothing, Nothing)
| otherwise = pure (Just o, Just c)
handleBoxes x y = pure (x, y)
handleAttachment :: Maybe TextV -> Reader Config (Maybe TextV)
handleAttachment = maybe (pure Nothing) f
where f x = pure $ if x == TextV "scroll"
then Nothing
else Just x
handleColor :: Maybe Color -> Reader Config (Maybe Color)
handleColor = maybe (pure Nothing) f
where f x = if x == Named "transparent"
then pure Nothing
else Just <$> minifyWith x
handleBgSize :: Maybe BgSize -> Reader Config (Maybe BgSize)
handleBgSize (Just b@BgSize{}) = do
minb <- minifyWith b
pure $ if minb == BgSize (Right Auto) Nothing
then Nothing
else Just minb
handleBgSize x = pure x
handlePosition :: Bool -> Maybe Position -> Reader Config (Maybe Position)
handlePosition _ Nothing = pure Nothing
handlePosition cannotRemovePos (Just p)
| cannotRemovePos = Just <$> minifyWith p
| otherwise = do
conf <- ask
mp <- minifyWith p
pure $ if mp == Position Nothing l0 Nothing l0
then Nothing
else Just $ if True
then mp
else p
data Values = Values Value [(Separator, Value)]
deriving (Show, Eq)
instance ToText Values where
toBuilder (Values v vs) = toBuilder v <> foldr f mempty vs
where f (sep, val) z = toBuilder sep <> toBuilder val <> z
instance Minifiable Values where
minifyWith (Values v vs) = do
newV <- minifyWith v
newVs <- (mapM . mapM) minifyWith vs
pure $ Values newV newVs
data Separator = Space | Slash | Comma
deriving (Show, Eq)
instance ToText Separator where
toText Space = " "
toText Comma = ","
toText Slash = "/"
valuesToList :: Values -> [Value]
valuesToList (Values v vs) = v : map snd vs
lowercaseText :: Text -> Reader Config Text
lowercaseText t = do
conf <- ask
pure $ case letterCase conf of
Lowercase -> T.toLower t
Original -> t
mkValues :: [Value] -> Values
mkValues (x:xs) = Values x (zip (repeat Space) xs)
mkValues [ ] = error "An empty list of values isn't valid"
bld :: ToText a => Maybe a -> Maybe Builder
bld = fmap toBuilder
handleTimingFunction :: Maybe TimingFunction -> Reader Config (Maybe TimingFunction)
handleTimingFunction Nothing = pure Nothing
handleTimingFunction (Just tfunc)
| tfunc == Ease = pure Nothing
| otherwise = Just <$> minifyWith tfunc
handleTime :: Maybe Duration -> Maybe Duration -> Reader Config (Maybe Duration, Maybe Duration)
handleTime (Just t) Nothing = if t == Duration 0 S
then pure (Nothing, Nothing)
else do newT <- minifyWith t
pure (Just newT, Nothing)
handleTime (Just t1) (Just t2)
| t1 == Duration 0 S = if t2 == t1
then pure (Nothing, Nothing)
else do newT2 <- minifyWith t2
newT1 <- minifyWith t1
pure (Just newT1, Just newT2)
| otherwise = do newT1 <- minifyWith t1
if t2 == Duration 0 S
then pure (Just t1, Nothing)
else do newT2 <- minifyWith t2
pure (Just newT1, Just newT2)
handleTime _ _ = pure (Nothing, Nothing)
removeIfEqualTo :: Text -> Maybe TextV -> Maybe TextV
removeIfEqualTo _ Nothing = Nothing
removeIfEqualTo s (Just x)
| x == TextV s = Nothing
| otherwise = Just x
optimizeFontFamily :: Value -> Reader Config Value
optimizeFontFamily (Other t) = mkOther <$> lowercaseText (getText t)
optimizeFontFamily (StringV s) = do
conf <- ask
ffamily <- mapString lowercaseText s
pure $ if shouldRemoveQuotes conf
then either mkOther StringV (unquoteFontFamily ffamily)
else StringV ffamily
optimizeFontFamily x = pure x
minifyBgLayer :: Maybe Value -> Maybe Position -> Maybe BgSize
-> Maybe RepeatStyle -> Maybe TextV
-> Reader Config (Maybe Value, Maybe BgSize, Maybe Position, Maybe RepeatStyle, Maybe TextV)
minifyBgLayer img pos sz rst att = do
i <- handleImage img
s <- handleBgSize sz
p <- let cannotRemovePos = isJust s
in handlePosition cannotRemovePos pos
r <- handleRepeatStyle rst
a <- handleAttachment att
pure (i,s,p,r,a)