{-# LANGUAGE GeneralizedNewtypeDeriving, OverloadedStrings #-} ----------------------------------------------------------------------------- -- | -- Module : Hasmin.Types.Numeric -- Copyright : (c) 2017 Cristian Adrián Ontivero -- License : BSD3 -- Stability : experimental -- Portability : non-portable -- -- CSS Numeric data types: \<number\>, \<percentage\>, and \<alphavalue\>. -- All Rational newtypes to ensure dimension conversion precision. -- ----------------------------------------------------------------------------- module Hasmin.Types.Numeric ( Percentage(..) , toPercentage , Number(..) , toNumber , fromNumber , Alphavalue(..) , toAlphavalue , mkAlphavalue ) where import Data.Text (pack) import Hasmin.Class import Hasmin.Utils import Text.Printf (printf) -- | The \<number\> data type. Real numbers, possibly with a fractional component. -- When written literally, a number is either an integer, or zero or more -- decimal digits followed by a dot (.) followed by one or more decimal digits -- and optionally an exponent composed of "e" or "E" and an integer. It -- corresponds to the \<number-token\> production in the CSS Syntax Module -- [CSS3SYN]. As with integers, the first character of a number may be -- immediately preceded by - or + to indicate the number’s sign. -- Specifications: -- -- 1. <https://drafts.csswg.org/css-values-3/#numbers CSS Values and Units Module Level 3 (§4.2)> -- 2. <https://www.w3.org/TR/CSS2/syndata.html#numbers CSS2.1 (§4.3.1)> -- 3. <https://www.w3.org/TR/CSS1/#units CSS1 (6 Units)> newtype Number = Number { getRational :: Rational } deriving (Eq, Show, Ord, Num, Fractional, Real, RealFrac) instance ToText Number where toText = pack . trimLeadingZeros . showRat . toRational -- check if scientific notation is shorter! toNumber :: Real a => a -> Number toNumber = Number . toRational fromNumber :: Fractional a => Number -> a fromNumber = fromRational . toRational -- | The \<alphavalue\> data type. Syntactically a \<number\>. It is the -- uniform opacity setting to be applied across an entire object. Any values -- outside the range 0.0 (fully transparent) to 1.0 (fully opaque) are clamped -- to this range. Specification: -- -- 1. <https://www.w3.org/TR/css3-color/#transparency CSS Color Module Level 3 (§3.2) newtype Alphavalue = Alphavalue Rational deriving (Eq, Show, Ord, Real, RealFrac) instance Num Alphavalue where abs = id -- numbers are never negative, so abs doesn't do anything a - b = mkAlphavalue $ toRational a - toRational b a + b = mkAlphavalue $ toRational a + toRational b a * b = mkAlphavalue $ toRational a * toRational b fromInteger = toAlphavalue signum a | toRational a == 0 = 0 | otherwise = 1 instance ToText Alphavalue where toText = pack . trimLeadingZeros . showRat . toRational instance Bounded Alphavalue where minBound = 0 maxBound = 1 instance Fractional Alphavalue where fromRational = mkAlphavalue (Alphavalue a) / (Alphavalue b) = mkAlphavalue (toRational a / toRational b) toAlphavalue :: Real a => a -> Alphavalue toAlphavalue = mkAlphavalue . toRational mkAlphavalue :: Rational -> Alphavalue mkAlphavalue = Alphavalue . restrict 0 1 -- | The \<percentage\> data type. Many CSS properties can take percentage -- values, often to define sizes in terms of parent objects. Percentages are -- formed by a \<number\> immediately followed by the percentage sign %. -- There is no space between the '%' and the number. Specification: -- -- 1. <https://drafts.csswg.org/css-values-3/#percentages CSS Value and Units Module Level 3 (§4.3) -- 2. <https://www.w3.org/TR/CSS2/syndata.html#percentage-units CSS2.1 (§4.3.3) -- 3. <https://www.w3.org/TR/CSS1/#percentage-units CSS1 (§6.2) newtype Percentage = Percentage Rational deriving (Eq, Show, Ord, Num, Fractional, Real, RealFrac) instance ToText Percentage where toText = pack . (++ "%") . trimLeadingZeros . showRat . toRational toPercentage :: Real a => a -> Percentage toPercentage = Percentage . toRational -- Note: printf used instead of show to avoid scientific notation -- | Show a Rational in decimal notation, removing leading zeros, -- and not displaying fractional part if the number is an integer. showRat :: Rational -> String showRat r | abs (r - fromInteger x) < eps = printf "%d" x | otherwise = printf "%f" d where x = round r d = fromRational r :: Double trimLeadingZeros :: String -> String trimLeadingZeros l@(x:xs) | x == '-' = x : go xs | otherwise = go l where go ('0':y:ys) = go (y:ys) go z = z trimLeadingZeros [] = ""