-- | Shaping for a paragraph of plain, unidirectional text using a single font.
--
-- The input text must be encoded as UTF-8 in a contiguous byte array.
--
-- Positions and distances are represented as 32-bit integers. Their unit must
-- be defined by the caller, who must calculate the desired dimensions of the
-- EM square of the input font and set them using @hb_font_set_scale()@. For
-- example, if @1em = 20px@, if the output pixels are square, and if the output
-- coordinates are in 1/64ths of a pixel, you should set both the @x_scale@ and
-- the @y_scale@ to @1280@.
module Data.Text.ParagraphLayout.Plain
(LineHeight(..)
,Paragraph(..)
,ParagraphLayout(..)
,ParagraphOptions(..)
,Rect(..)
,Span(..)
,SpanLayout(..)
,exampleParagraph
,layoutPlain
)
where
import Data.Int (Int32)
import Data.Text (pack)
import Data.Text.Array (Array)
import Data.Text.Foreign (I8)
import Data.Text.Glyphize (Font, GlyphInfo, GlyphPos)
import Data.Text.Internal (Text(Text))
-- | Text to be laid out as a paragraph.
--
-- May be divided into any number of neighbouring spans, each of which will
-- have its own layout rectangle(s) calculated.
data Paragraph = Paragraph
Array
-- ^ A byte array containing the whole text to be laid out, in UTF-8.
I8
-- ^ Byte offset of the first span.
-- Any characters preceding this offset will not be shaped, but may still
-- be used to influence the shape of neighbouring characters.
[Span]
-- ^ Parts of the text to be laid out, in logical order.
-- The offset plus total length of all spans must not exceed array bounds.
-- Any characters following the last span will not be shaped, but may still
-- be used to influence the shape of neighbouring characters.
ParagraphOptions
-- ^ Properties applying to the paragraph as a whole.
data ParagraphOptions = ParagraphOptions
{ paragraphFont :: Font
, paragraphLineHeight :: LineHeight
, paragraphMaxWidth :: Int32
}
data LineHeight
= Absolute Int32
-- ^ Set line height independently of the font.
| Relative Float
-- ^ Set line height as a multiplier of the font's built-in value.
data Span = Span
{ spanLength :: I8
-- ^ Byte offset to the next span or the end of the paragraph text.
, spanLanguage :: String
-- ^ Used for selecting the appropriate glyphs and line breaking rules.
}
-- | The resulting layout of the whole paragraph.
data ParagraphLayout = ParagraphLayout
{ paragraphRect :: Rect Int32
, spanLayouts :: [SpanLayout]
}
deriving (Eq, Read, Show)
-- | The resulting layout of each span, which may include multiple bounding
-- boxes if broken over multiple lines.
data SpanLayout = SpanLayout [(Rect Int32, [(GlyphInfo, GlyphPos)])]
deriving (Eq, Read, Show)
-- | Rectangle containing all glyph advances in the paragraph or corresponding
-- span. This is the space that the glyphs "take up" and is probably what you
-- want to use for detecting position-based events such as mouse clicks.
--
-- Beware that actual glyphs will not be drawn exactly to the borders of this
-- rectangle -- they may be offset inwards and they can also extend outwards!
-- These are not the typographic bounding boxes that you use for determining
-- the area to draw on -- you need FreeType or a similar library for that.
--
-- The origin coordinates are relative to the paragraph.
--
-- The sizes can be positive or negative, depending on the text direction.
--
-- X coordinates increase from left to right.
-- Y coordinates increase from bottom to top.
data Rect a = Rect
{ x_origin :: a
, y_origin :: a
, x_size :: a
, y_size :: a
}
deriving (Eq, Read, Show)
-- | Interface for basic plain text layout.
--
-- The entire paragraph will be assumed to have the same text direction and
-- will be shaped using a single font, aligned to the left for LTR text or to
-- the right for RTL text.
layoutPlain :: Paragraph -> ParagraphLayout
-- Stub implementation to make this a valid Haskell source.
-- Of course, this will eventually be replaced by an actual implementation. :)
layoutPlain (Paragraph _ _ spans _)
= ParagraphLayout (Rect 0 0 0 0) (map (\_ -> SpanLayout []) spans)
exampleArray :: Array
exampleOffset :: Int
(Text exampleArray exampleOffset _) = pack "Tak jsem tady, 世界!"
exampleParagraph :: Font -> Paragraph
exampleParagraph font = Paragraph
exampleArray
(fromIntegral exampleOffset + 4)
[Span 11 "cs" -- this will contain the text "jsem tady, "
,Span 7 "ja" -- this will contain the text "世界!"
]
(ParagraphOptions font (Relative 1.5) 20000)