~jaro/balkon

ref: f816c9028c678035b61c929c84b048067c2da027 balkon/src/Data/Text/ParagraphLayout/Internal/BiDiReorder.hs -rw-r--r-- 2.1 KiB
f816c902Jaro Allow extracting ResolvedBox from WithSpan. 1 year, 6 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
module Data.Text.ParagraphLayout.Internal.BiDiReorder
    ( Level
    , WithLevel
    , level
    , reorder
    )
where

import Data.List.NonEmpty (NonEmpty ((:|)))
import qualified Data.List.NonEmpty as NonEmpty
import Data.Semigroup (sconcat)
import Data.Word (Word8)

-- | BiDi level, between 0 and 125 inclusive.
-- Even values mean left-to-right text.
-- Odd values mean right-to-left text.
type Level = Word8

-- | Typeclass for any data structure with an associated BiDi level.
class WithLevel a where
    level :: a -> Level

-- | Generic reordering of bidirectional text according to rule L2 of UAX #9
-- <https://www.unicode.org/reports/tr9/#Reordering_Resolved_Levels>.
--
-- Given an input in logical order and its corresponding BiDi levels,
-- this algorithm produces output in visual order, always from left to right.
--
-- Although defined by UAX #9 for reordering on the glyph level, this can also
-- be used for reordering runs of text, provided that the glyphs within each
-- shaped run are already ordered visually from left to right. This is the case
-- for HarfBuzz output.
reorder :: WithLevel a => NonEmpty a -> NonEmpty a
reorder xs = reorderLevels minOddLevel maxLevel xs
    where
        minOddLevel = minimum oddLevels
        maxLevel = maximum levels
        oddLevels = 1 :| NonEmpty.filter odd levels
        levels = NonEmpty.map level xs

-- | For each integer value from @high@ to @low@ inclusive, reverse any
-- contiguous sequence of items that are at the given level or higher.
--
-- The value of @low@ must be at least 1 to avoid integer overflow.
reorderLevels :: WithLevel a => Level -> Level -> NonEmpty a -> NonEmpty a
reorderLevels low high xs =
    if low > high
        then xs
        else reorderLevels low (high - 1) $ reorderLevel high xs

-- | Reverse any contiguous sequence of items that are at level @lvl@ or higher.
reorderLevel :: WithLevel a => Level -> NonEmpty a -> NonEmpty a
reorderLevel lvl xs = sconcat $ NonEmpty.map reverseHigh $ groupHigh xs
    where
        reverseHigh g@(x :| _) = if isHigh x then NonEmpty.reverse g else g
        groupHigh = NonEmpty.groupWith1 isHigh
        isHigh x = level x >= lvl