@@ 0,0 1,20 @@
+ParagraphLayout {paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 6113, y_size = 0}, spanLayouts = [
+ SpanLayout [(Rect {x_origin = 0, y_origin = 0, x_size = 4837, y_size = 0},
+ [(GlyphInfo {codepoint = 77, cluster = 4, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 253, y_advance = 0, x_offset = 0, y_offset = 0}),
+ (GlyphInfo {codepoint = 86, cluster = 5, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 446, y_advance = 0, x_offset = 0, y_offset = 0}),
+ (GlyphInfo {codepoint = 72, cluster = 6, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 559, y_advance = 0, x_offset = 0, y_offset = 0}),
+ (GlyphInfo {codepoint = 80, cluster = 7, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 861, y_advance = 0, x_offset = 0, y_offset = 0}),
+ (GlyphInfo {codepoint = 3, cluster = 8, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 231, y_advance = 0, x_offset = 0, y_offset = 0}),
+ (GlyphInfo {codepoint = 87, cluster = 9, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 402, y_advance = 0, x_offset = 0, y_offset = 0}),
+ (GlyphInfo {codepoint = 68, cluster = 10, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 522, y_advance = 0, x_offset = 0, y_offset = 0}),
+ (GlyphInfo {codepoint = 71, cluster = 11, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 589, y_advance = 0, x_offset = 0, y_offset = 0}),
+ (GlyphInfo {codepoint = 92, cluster = 12, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 497, y_advance = 0, x_offset = 0, y_offset = 0}),
+ (GlyphInfo {codepoint = 15, cluster = 13, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 246, y_advance = 0, x_offset = 0, y_offset = 0}),
+ (GlyphInfo {codepoint = 3, cluster = 14, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 231, y_advance = 0, x_offset = 0, y_offset = 0})]
+ )],
+ SpanLayout [(Rect {x_origin = 4837, y_origin = 0, x_size = 1276, y_size = 0},
+ [(GlyphInfo {codepoint = 0, cluster = 15, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 500, y_advance = 0, x_offset = 0, y_offset = 0}),
+ (GlyphInfo {codepoint = 0, cluster = 18, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 500, y_advance = 0, x_offset = 0, y_offset = 0}),
+ (GlyphInfo {codepoint = 4, cluster = 21, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 276, y_advance = 0, x_offset = 0, y_offset = 0})]
+ )]
+]}
@@ 0,0 1,135 @@
+-- | 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)
@@ 0,0 1,65 @@
+module Data.Text.ParagraphLayout.PlainSpec (spec) where
+
+import Data.List (intersperse)
+
+import Test.Hspec
+import Test.Hspec.Golden
+import System.FilePath ((</>))
+import Data.Text.ParagraphLayout.Plain
+import Data.Text.ParagraphLayout.FontLoader
+
+prettyShow :: ParagraphLayout -> String
+prettyShow (ParagraphLayout pr sls) = showParagraphLayout where
+ showParagraphLayout = concat
+ [ "ParagraphLayout {paragraphRect = "
+ , show pr
+ , ", spanLayouts = ["
+ , newline
+ , showSpanLayouts
+ , newline
+ , "]}"
+ , newline
+ ]
+ showSpanLayouts = concat $ intersperse commaNewline $ map showSpanLayout sls
+ showSpanLayout (SpanLayout boxes) = concat
+ [ indent1
+ , "SpanLayout ["
+ , concat $ map showBox boxes
+ , "]"
+ ]
+ showBox (r, glyphs) = concat
+ [ "("
+ , show r
+ , commaNewline
+ , indent2
+ , "["
+ , showGlyphs glyphs
+ , "]"
+ , newline
+ , indent1
+ , ")"
+ ]
+ showGlyphs = concat . intersperse (commaNewline ++ indent2) . map show
+ indent1 = " "
+ indent2 = indent1 ++ indent1
+ newline = "\n"
+ commaNewline = "," ++ newline
+
+shouldBeGolden :: ParagraphLayout -> FilePath -> Golden ParagraphLayout
+shouldBeGolden output_ name = Golden
+ { output = output_
+ , encodePretty = show
+ , writeToFile = \path -> writeFile path . prettyShow
+ , readFromFile = \path -> readFile path >>= return . read
+ , goldenFile = ".golden" </> name </> "golden"
+ , actualFile = Just (".golden" </> name </> "actual")
+ , failFirstTime = False
+ }
+
+spec :: Spec
+spec = do
+ -- Note: This font does not contain Japanese glyphs.
+ describe "layoutPlain" $ before loadUbuntuRegular $ do
+ it "stub works" $ \font -> do
+ let result = layoutPlain (exampleParagraph font)
+ result `shouldBeGolden` "exampleParagraph"