Cairn · module

Cairn.Text.Wrap

lean/Cairn/Text/Wrap.lean

Line breaking

Lean decides where wrapped text (Node.para) breaks; the host only measures. Everything here works on the text as a list of Unicode scalars and on its caret offsets o : Nat → Int (o i is the x offset before scalar i, from one Core Text shaping of the whole string, so a line [a, b) is o b - o a wide). The rules (docs/layout.md, "Wrapped text"):

  1. Words are maximal runs of scalars other than the break space U+0020 (isBreak). Lines break only between words, and the spaces at a break are dropped: a line runs from the start of its first word to the end of its last, with the spaces between its own words kept.
  2. Greedy fill (fill): a line takes words while the line, measured from its first scalar to the end of the next word, still fits; then the next word starts a new line.
  3. Over-wide words. A word that starts a line and is wider than the width is cut into pieces (chunk): each the longest run of its scalars, from where the last one ended, that fits, and never less than one scalar. Every piece but the last is a line of its own; the last starts a line that later words may join.
  4. At most maxLines, keeping the first (Keep.head) or the last (Keep.tail) lines.

Theorems (Cairn.Text.WrapProofs): every line fits the width or is a single scalar; the lines tile the text with only break spaces between and around them; the count is at most maxLines; tail keeps exactly the last lines and head the first.

Each function is structural recursion (fuel where the measure isn't), so #guard and decide evaluate it; the examples are in Cairn.Text.WrapProofs.

Imports Cairn.UI.Node

Order

Definitions 17

def

Cairn.Text.Wrap.isBreak

lean/Cairn/Text/Wrap.lean:35
isBreak (c : Char) : Bool

The break space: the only scalar a line breaks at, and the only one a break drops.

def

Cairn.Text.Wrap.wordsFrom

lean/Cairn/Text/Wrap.lean:39
wordsFrom : List Char → Nat → Option Nat → List (Nat × Nat)

Maximal runs of non-break scalars, as [a, b) ranges. i is the index of the list's head in the whole text; cur is where the run in progress started, if one is.

def

Cairn.Text.Wrap.words

lean/Cairn/Text/Wrap.lean:50
words (cs : List Char) : List (Nat × Nat)

The words of a text.

def

Cairn.Text.Wrap.width

lean/Cairn/Text/Wrap.lean:53
width (o : Nat → Int) (p : Nat × Nat) : Int

The width of the scalars [a, b).

def

Cairn.Text.Wrap.cutGo

lean/Cairn/Text/Wrap.lean:56
cutGo (o : Nat → Int) (W : Int) (a b : Nat) : Nat → Nat → Nat

From a piece [a, c) that is allowed, add scalars while c < b and [a, c + 1) fits.

def

Cairn.Text.Wrap.cut

lean/Cairn/Text/Wrap.lean:62
cut (o : Nat → Int) (W : Int) (a b : Nat) : Nat

Where the piece of [a, b) that starts at a ends: the longest prefix that fits, and at least one scalar.

def

Cairn.Text.Wrap.chunkGo

lean/Cairn/Text/Wrap.lean:66
chunkGo (o : Nat → Int) (W : Int) (b : Nat) :
  Nat → Nat → List (Nat × Nat) × Nat × Nat

Cut the rest of a word, [a, b), into pieces: all but the last, and the last. fuel ≥ b - a suffices.

def

Cairn.Text.Wrap.chunk

lean/Cairn/Text/Wrap.lean:76
chunk (o : Nat → Int) (W : Int) (p : Nat × Nat) :
  List (Nat × Nat) × Nat × Nat

A word as pieces (rule 3): itself if it fits, otherwise cut. The pieces but the last fill whole lines; the last one starts the line that continues with the next words.

def

Cairn.Text.Wrap.fill

lean/Cairn/Text/Wrap.lean:81
fill (o : Nat → Int) (W : Int) :
  Nat × Nat → List (Nat × Nat) → List (Nat × Nat)

Greedy fill (rule 2): cur is the line so far. A word that doesn't fit on it starts a new line, cut into pieces first if it is wider than a line.

def

Cairn.Text.Wrap.breakAll

lean/Cairn/Text/Wrap.lean:87
breakAll (o : Nat → Int) (W : Int) : List (Nat × Nat) → List (Nat × Nat)
def

Cairn.Text.Wrap.allLines

lean/Cairn/Text/Wrap.lean:92
allLines (o : Nat → Int) (W : Int) (cs : List Char) : List (Nat × Nat)

Every line of cs at width W, as [a, b) scalar ranges, before maxLines.

def

Cairn.Text.Wrap.keepLines

lean/Cairn/Text/Wrap.lean:96
keepLines {α : Type} : Option Nat → Keep → List α → List α

At most n lines, from the front or the back.

def

Cairn.Text.Wrap.lines

lean/Cairn/Text/Wrap.lean:102
lines (o : Nat → Int) (W : Int) (wr : Wrap) (cs : List Char) :
  List (Nat × Nat)

The lines a wrapped text shows.

def

Cairn.Text.Wrap.slice

lean/Cairn/Text/Wrap.lean:106
slice (cs : List Char) (p : Nat × Nat) : List Char

The scalars of the range [a, b).

def

Cairn.Text.Wrap.mapSlices

lean/Cairn/Text/Wrap.lean:111
mapSlices {β : Type} (f : Nat × Nat → List Char → β) (cs : List Char)
  (ps : List (Nat × Nat)) : List β

f applied to each range and its scalars: the specification. Each slice walks from the start of the text, so this is quadratic in the text's length; the compiler runs mapSlicesFast instead (mapSlices_eq).

def

Cairn.Text.Wrap.mapSlicesGo

lean/Cairn/Text/Wrap.lean:117
mapSlicesGo {β : Type} (f : Nat × Nat → List Char → β) (cs : List Char) :
  Nat → List Char → List (Nat × Nat) → List β

cur is cs.drop base; ranges that start at or after base drop from there, so lines in order cost one pass over the text. Any other range drops from the start.

def

Cairn.Text.Wrap.mapSlicesFast

lean/Cairn/Text/Wrap.lean:124
mapSlicesFast {β : Type} (f : Nat × Nat → List Char → β) (cs : List Char)
  (ps : List (Nat × Nat)) : List β

Theorems 2

theorem

Cairn.Text.Wrap.mapSlicesGo_eq

provedlean/Cairn/Text/Wrap.lean:128
mapSlicesGo_eq {β : Type} (f : Nat × Nat → List Char → β) (cs : List Char)
  (ps : List (Nat × Nat)) (base : Nat) :
  mapSlicesGo f cs base (List.drop base cs) ps = mapSlices f cs ps
theorem

Cairn.Text.Wrap.mapSlices_eq

provedlean/Cairn/Text/Wrap.lean:140
mapSlices_eq : @mapSlices = @mapSlicesFast