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