wumpus-core-0.31.0: src/Wumpus/Core/FontSize.hs
{-# OPTIONS -Wall #-}
--------------------------------------------------------------------------------
-- |
-- Module : Wumpus.Core.FontSize
-- Copyright : (c) Stephen Tetley 2009-2010
-- License : BSD3
--
-- Maintainer : stephen.tetley@gmail.com
-- Stability : unstable
-- Portability : GHC
--
-- Font size calculation for Label\'s and their bounding boxes.
--
-- Calculations are based on metrics derived from Courier at
-- 48pt. As Courier is a monospaced font, bounding boxes
-- calculated for other font families will usually have longer
-- width than is necessary for the printed text.
--
-- This is a deficiency of Wumpus, and limits its text handling
-- capabilities (for example, text cannot be automatically
-- centered). However, alternatives would need access to font
-- metrics - this would require a font loader and add
-- significant implementation complexity.
--
--------------------------------------------------------------------------------
module Wumpus.Core.FontSize
(
-- * Type synonyms
FontSize
, CharCount
-- * Courier metrics at 48 point
, courier48_width
, courier48_height
, courier48_numeral_height
, courier48_xheight
, courier48_descender_depth
, courier48_ascender_height
, courier48_spacer_width
-- * Metrics calculation
, widthAt48pt
, charWidth
, textWidth
, textHeight
, numeralHeight
, xcharHeight
, descenderDepth
, textBounds
) where
import Wumpus.Core.BoundingBox
import Wumpus.Core.Geometry
import Wumpus.Core.PtSize
import Data.AffineSpace -- package: vector-space
type CharCount = Int
type FontSize = Int
-- NOTE - I\'ve largely tried to follow the terminoly from
-- Edward Tufte\'s /Visual Explantions/, page 99.
--
-- | The width of a letter in Courier at 48 pt.
--
-- The value is not entirely accurate but it is satisfactory.
--
-- > width = 26.0
--
courier48_width :: PtSize
courier48_width = 26.0
-- | The point size of a character in Courier at 48 pt.
--
-- \*\* Naturally the height is 48.0 \*\*.
--
courier48_height :: PtSize
courier48_height = 48.0
-- | The height of a numeral without accents, ascenders or
-- descenders in Courier at 48 pt.
--
-- Note - the height of a numeral in Courier is slightly
-- larger than a upper-case letter.
--
-- > numeral_height = 30.0
--
courier48_numeral_height :: PtSize
courier48_numeral_height = 30.0
-- | The height of the body of a lower-case letter
-- (typically the letter \'x\') in Courier at 48 pt.
--
-- This is also known as the \"body height\".
--
-- > xheight = 20.0
--
courier48_xheight :: PtSize
courier48_xheight = 20.0
-- | The depth of a descender in Courier at 48 pt.
--
-- > descender_depth = 9.0
--
courier48_descender_depth :: PtSize
courier48_descender_depth = 9.0
-- | The depth of an ascender in Courier at 48 pt.
--
-- > ascender_height = 10.0
--
-- Note - for Courier point size is not the sum of
-- descender, ascender and xheight and lower-case letters with
-- ascenders are slightly taller than upper-case letters:
--
-- > descender_depth + xheight + ascender_height /= point_size
--
-- > xheight + ascender_height /= cap_height
--
-- > xheight + ascender_height == numeral_height
--
courier48_ascender_height :: PtSize
courier48_ascender_height = 10.0
-- | The spacing between letters printed directly with
-- PostScript\'s show command for Courier at 48 pt.
--
-- The value is not entirely accurate but it is satisfactory
-- for bounding box calculations.
--
-- > spacer_width = 3.0
--
courier48_spacer_width :: PtSize
courier48_spacer_width = 3.0
-- | Width of the supplied string when printed at 48pt.
--
-- (i.e. n chars + (n-1) spacers)
--
widthAt48pt :: CharCount -> PtSize
widthAt48pt n = (courier48_width * len) + (courier48_spacer_width * len_sub)
where
len = fromIntegral n
len_sub = len - 1.0
-- | Approximate the width of a monospace character using
-- metrics derived from the Courier font.
--
charWidth :: FontSize -> PtSize
charWidth sz = (fromIntegral sz)/48 * courier48_width
-- | Text width at @sz@ point size of the string @s@. All
-- characters are counted literally - special chars may cause
-- problems (this a current deficiency of Wumpus).
--
textWidth :: FontSize -> CharCount -> PtSize
textWidth sz n = (fromIntegral sz)/48 * widthAt48pt n
-- | Text height is just identity/double-coercion of the Point size.
-- i.e. @18 == 18.0@. The /size/ of a font is the maximum height:
--
textHeight :: FontSize -> PtSize
textHeight = fromIntegral
-- | Approximate the height of a numeral using metrics derived
-- from the Courier monospaced font.
--
numeralHeight :: FontSize -> PtSize
numeralHeight sz = textHeight sz * (courier48_numeral_height / courier48_height)
-- | Approximate the height of the lower-case char \'x\' using
-- metrics derived from the Courier monospaced font.
--
xcharHeight :: FontSize -> PtSize
xcharHeight sz = textHeight sz * (courier48_xheight / courier48_height)
-- | Approximate the descender depth for font size @sz@ using
-- metrics derived from the Courier monospaced font.
--
descenderDepth :: FontSize -> PtSize
descenderDepth sz = (fromIntegral sz) / 48 * courier48_descender_depth
-- | Find the bounding box for the character count at the
-- supplied font-size.
--
-- The supplied point represents the bottom left corner of the
-- a regular upper-case letter (that is without descenders).
-- The bounding box will always be /dropped/ to accommodate
-- ascenders - no interpretation of the string takes place to
-- see if it actually contains ascenders or descenders.
--
-- The metrics used are derived from Courier - a monospaced font.
-- For variable width fonts the calculated bounding box will
-- usually be too long.
--
textBounds :: (Num u, Ord u, FromPtSize u)
=> FontSize -> Point2 u -> CharCount -> BoundingBox u
textBounds sz body_bl n = bbox bl tr where
h = fromPtSize $ textHeight sz
w = fromPtSize $ textWidth sz n
dd = fromPtSize $ descenderDepth sz
bl = body_bl .-^ V2 0 dd
tr = bl .+^ V2 w h