M balkon.cabal => balkon.cabal +2 -0
@@ 97,6 97,7 @@ library
-- Modules exported by the library.
exposed-modules:
Data.Text.ParagraphLayout,
+ Data.Text.ParagraphLayout.Rect,
Data.Text.ParagraphLayout.Run,
Data.Text.ParagraphLayout.Span
@@ 126,6 127,7 @@ test-suite balkon-test
other-modules:
Data.Text.ParagraphLayoutSpec,
Data.Text.ParagraphLayout.FontLoader,
+ Data.Text.ParagraphLayout.RectSpec,
Data.Text.ParagraphLayout.RunSpec,
Data.Text.ParagraphLayout.SpanData
A src/Data/Text/ParagraphLayout/Rect.hs => src/Data/Text/ParagraphLayout/Rect.hs +67 -0
@@ 0,0 1,67 @@
+-- | Representation of an axis-aligned rectangle on a 2D plane, with one of its
+-- corners being a designated origin point.
+module Data.Text.ParagraphLayout.Rect
+ (Rect(..)
+ ,height
+ ,union
+ ,width
+ ,x_max
+ ,x_min
+ ,x_terminus
+ ,y_max
+ ,y_min
+ ,y_terminus
+ )
+where
+
+data Rect a = Rect { x_origin :: a, y_origin :: a, x_size :: a, y_size :: a }
+ deriving (Eq, Read, Show)
+
+-- | Absolute difference between the X coordinates of the rectangle's sides.
+width :: Num a => Rect a -> a
+width r = abs $ x_size r
+
+-- | Absolute difference between the X coordinates of the rectangle's sides.
+height :: Num a => Rect a -> a
+height r = abs $ y_size r
+
+-- | X coordinate of the corner opposite of the origin.
+x_terminus :: Num a => Rect a -> a
+x_terminus r = x_origin r + x_size r
+
+-- | Y coordinate of the corner opposite of the origin.
+y_terminus :: Num a => Rect a -> a
+y_terminus r = y_origin r + y_size r
+
+-- | The smaller of the two X coordinates of the rectangle's edges.
+x_min :: (Num a, Ord a) => Rect a -> a
+x_min r = x_origin r `min` x_terminus r
+
+-- | The smaller of the two Y coordinates of the rectangle's edges.
+y_min :: (Num a, Ord a) => Rect a -> a
+y_min r = y_origin r `min` y_terminus r
+
+-- | The larger of the two X coordinates of the rectangle's edges.
+x_max :: (Num a, Ord a) => Rect a -> a
+x_max r = x_origin r `max` x_terminus r
+
+-- | The larger of the two Y coordinates of the rectangle's edges.
+y_max :: (Num a, Ord a) => Rect a -> a
+y_max r = y_origin r `max` y_terminus r
+
+-- | Calculate the smallest rectangle that completely contains the given two
+-- rectangles.
+--
+-- The origin of the resulting rectangle will be the corner with the lowest
+-- X and Y coordinates, regardless of the origin of the input rectangles.
+union :: (Num a, Ord a) => Rect a -> Rect a -> Rect a
+union a b = Rect x1 y1 dx dy where
+ x1 = x_min a `min` x_min b
+ y1 = y_min a `min` y_min b
+ x2 = x_max a `max` x_max b
+ y2 = y_max a `max` y_max b
+ dx = x2 - x1
+ dy = y2 - y1
+
+instance (Num a, Ord a) => Semigroup (Rect a) where
+ (<>) = union
A test/Data/Text/ParagraphLayout/RectSpec.hs => test/Data/Text/ParagraphLayout/RectSpec.hs +29 -0
@@ 0,0 1,29 @@
+module Data.Text.ParagraphLayout.RectSpec (spec) where
+
+import Data.Int (Int32)
+
+import Test.Hspec
+import Data.Text.ParagraphLayout.Rect
+
+positiveRect :: Rect Int32
+positiveRect = Rect 50 60 10 10
+
+negativeRect :: Rect Int32
+negativeRect = Rect 80 90 (-15) (-15)
+
+spec :: Spec
+spec = do
+ describe "union of two rects" $ do
+ let r = union positiveRect negativeRect
+ it "has origin at 50,60" $
+ (x_origin r, y_origin r) `shouldBe` (50, 60)
+ it "has minimum coordinates at at 50,60" $
+ (x_min r, y_min r) `shouldBe` (50, 60)
+ it "has terminus at 80,90" $
+ (x_terminus r, y_terminus r) `shouldBe` (80, 90)
+ it "has maximum coordinates at 80,90" $
+ (x_max r, y_max r) `shouldBe` (80, 90)
+ it "has size 30,30" $
+ (x_size r, y_size r) `shouldBe` (30, 30)
+ it "has absolute size 30,30" $
+ (width r, height r) `shouldBe` (30, 30)