hasmin-1.0: src/Hasmin/Types/Numeric.hs
{-# 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 [] = ""