~jaro/balkon

e1e85a256529d73d676750940101567be9d73c87 — Jaro 1 year, 10 months ago 7119d63
Separate modules.
M balkon.cabal => balkon.cabal +4 -1
@@ 55,7 55,10 @@ library
    exposed-modules:  Data.Text.ParagraphLayout

    -- Modules included in this library but not exported.
    other-modules:    Data.Text.Script
    other-modules:
        Data.Text.ParagraphLayout.Run,
        Data.Text.ParagraphLayout.Span,
        Data.Text.Script

    -- LANGUAGE extensions used by modules in this package.
    -- other-extensions:

M src/Data/Text/ParagraphLayout.hs => src/Data/Text/ParagraphLayout.hs +2 -94
@@ 4,50 4,14 @@ where
import Data.Text.Glyphize
    (Buffer(..)
    ,ContentType(ContentTypeUnicode)
    ,Direction(..)
    ,Font
    ,GlyphInfo
    ,GlyphPos
    ,defaultBuffer
    ,shape
    )
import qualified Data.Text.ICU.Char as ICUChar
import Data.Text.Lazy (Text)
import qualified Data.Text.Lazy as Text
import Data.Text.Script (charScript)

type ScriptCode = String
type Language = String

-- Paragraph is broken into spans by the caller.
--
-- Each span could have a different font family, size, style, text decoration,
-- colour, language, etc.
--
-- TODO: Add all relevant attributes.
--
data Span = Span
    { spanText :: Text
    , spanFont :: Font
    , spanLanguage :: Maybe Language
    }
    deriving (Eq, Show)

-- Each span can be broken into one or more runs by Balkón.
--
-- Each run could have a different script, language, or direction.
--
data Run = Run
    { runText :: Text
    , runDirection :: Maybe Direction
    , runScript :: Maybe ScriptCode
    , runOriginalSpan :: Span
    }
    deriving (Eq, Show)

type ProtoRun = (String, Maybe Direction, ScriptCode)

data Merged a = Incompatible | Merged a
import Data.Text.ParagraphLayout.Run
import Data.Text.ParagraphLayout.Span

data Position = Beginning | Middle | End | Only
    deriving (Eq)


@@ 61,62 25,6 @@ data Position = Beginning | Middle | End | Only
layout :: [Span] -> [[(GlyphInfo, GlyphPos)]]
layout = layoutRuns . concat . map spanToRuns

-- TODO: Optimise and preserve the Data.Text.Lazy structure.
spanToRuns :: Span -> [Run]
spanToRuns s = map run $ protoRuns chars
    where
        chars = reverse $ Text.unpack $ spanText s
        run (t, d, sc) = Run
            { runText = Text.pack t
            , runDirection = d
            , runScript = Just sc
            , runOriginalSpan = s
            }

-- TODO: Try to avoid reversing.
protoRuns :: [Char] -> [ProtoRun]
protoRuns = reverse . map (\(t, d, s) -> (reverse t, d, s)) . foldr foldRun []

foldRun :: Char -> [ProtoRun] -> [ProtoRun]
foldRun c [] =
    -- If there are no runs, create a new run with a single character.
    [([c], charDirection c, charScript c)]
foldRun c (r@(oldString, d1, s1):rs) =
    case (mergeDirections d1 d2, mergeScripts s1 s2) of
        -- If direction & script are compatible, add to existing run.
        (Merged d, Merged s) -> ((c:oldString, d, s):rs)
        -- Otherwise create a new run.
        _ -> (([c], d2, s2):r:rs)
    where
        d2 = charDirection c
        s2 = charScript c

-- Simplified detection of text direction for unidirectional text.
mergeDirections :: Maybe Direction -> Maybe Direction -> Merged (Maybe Direction)
mergeDirections Nothing Nothing = Merged Nothing
mergeDirections (Just d1) Nothing = Merged (Just d1)
mergeDirections Nothing (Just d2) = Merged (Just d2)
mergeDirections (Just d1) (Just d2)
    | d1 == d2 = Merged (Just d1)
    | otherwise = Incompatible

-- TODO: Implement proper inheritance rules.
mergeScripts :: ScriptCode -> ScriptCode -> Merged ScriptCode
mergeScripts "Zyyy" s2 = Merged s2
mergeScripts s1 "Zyyy" = Merged s1
mergeScripts s1 "Zinh" = Merged s1
mergeScripts s1 s2
    | s1 == s2 = Merged s1
    | otherwise = Incompatible

-- TODO: Use the BiDi algorithm to support bidirectional text.
charDirection :: Char -> Maybe Direction
charDirection c = case ICUChar.direction c of
    ICUChar.LeftToRight -> Just DirLTR
    ICUChar.RightToLeft -> Just DirRTL
    ICUChar.RightToLeftArabic -> Just DirRTL
    _ -> Nothing

layoutRuns :: [Run] -> [[(GlyphInfo, GlyphPos)]]
layoutRuns [] = []
layoutRuns [s] = [layoutOneRun Only s]

A src/Data/Text/ParagraphLayout/Run.hs => src/Data/Text/ParagraphLayout/Run.hs +84 -0
@@ 0,0 1,84 @@
module Data.Text.ParagraphLayout.Run (Run(..), spanToRuns)
where

import Data.Text.Glyphize (Direction(..))
import qualified Data.Text.ICU.Char as ICUChar
import Data.Text.Lazy (Text)
import qualified Data.Text.Lazy as Text
import Data.Text.Script (charScript)

import Data.Text.ParagraphLayout.Span

type ScriptCode = String

-- Each span can be broken into one or more runs by Balkón.
--
-- Each run could have a different script, language, or direction.
--
data Run = Run
    { runText :: Text
    , runDirection :: Maybe Direction
    , runScript :: Maybe ScriptCode
    , runOriginalSpan :: Span
    }
    deriving (Eq, Show)

type ProtoRun = (String, Maybe Direction, ScriptCode)

data Merged a = Incompatible | Merged a

-- TODO: Optimise and preserve the Data.Text.Lazy structure.
spanToRuns :: Span -> [Run]
spanToRuns s = map run $ protoRuns chars
    where
        chars = reverse $ Text.unpack $ spanText s
        run (t, d, sc) = Run
            { runText = Text.pack t
            , runDirection = d
            , runScript = Just sc
            , runOriginalSpan = s
            }

-- TODO: Try to avoid reversing.
protoRuns :: [Char] -> [ProtoRun]
protoRuns = reverse . map (\(t, d, s) -> (reverse t, d, s)) . foldr foldRun []

foldRun :: Char -> [ProtoRun] -> [ProtoRun]
foldRun c [] =
    -- If there are no runs, create a new run with a single character.
    [([c], charDirection c, charScript c)]
foldRun c (r@(oldString, d1, s1):rs) =
    case (mergeDirections d1 d2, mergeScripts s1 s2) of
        -- If direction & script are compatible, add to existing run.
        (Merged d, Merged s) -> ((c:oldString, d, s):rs)
        -- Otherwise create a new run.
        _ -> (([c], d2, s2):r:rs)
    where
        d2 = charDirection c
        s2 = charScript c

-- Simplified detection of text direction for unidirectional text.
mergeDirections :: Maybe Direction -> Maybe Direction -> Merged (Maybe Direction)
mergeDirections Nothing Nothing = Merged Nothing
mergeDirections (Just d1) Nothing = Merged (Just d1)
mergeDirections Nothing (Just d2) = Merged (Just d2)
mergeDirections (Just d1) (Just d2)
    | d1 == d2 = Merged (Just d1)
    | otherwise = Incompatible

-- TODO: Implement proper inheritance rules.
mergeScripts :: ScriptCode -> ScriptCode -> Merged ScriptCode
mergeScripts "Zyyy" s2 = Merged s2
mergeScripts s1 "Zyyy" = Merged s1
mergeScripts s1 "Zinh" = Merged s1
mergeScripts s1 s2
    | s1 == s2 = Merged s1
    | otherwise = Incompatible

-- TODO: Use the BiDi algorithm to support bidirectional text.
charDirection :: Char -> Maybe Direction
charDirection c = case ICUChar.direction c of
    ICUChar.LeftToRight -> Just DirLTR
    ICUChar.RightToLeft -> Just DirRTL
    ICUChar.RightToLeftArabic -> Just DirRTL
    _ -> Nothing

A src/Data/Text/ParagraphLayout/Span.hs => src/Data/Text/ParagraphLayout/Span.hs +21 -0
@@ 0,0 1,21 @@
module Data.Text.ParagraphLayout.Span (Span(..))
where

import Data.Text.Glyphize (Font)
import Data.Text.Lazy (Text)

type Language = String

-- Paragraph is broken into spans by the caller.
--
-- Each span could have a different font family, size, style, text decoration,
-- colour, language, etc.
--
-- TODO: Add all relevant attributes.
--
data Span = Span
    { spanText :: Text
    , spanFont :: Font
    , spanLanguage :: Maybe Language
    }
    deriving (Eq, Show)