///|
pub(all) struct Inline {
text : String
bold : Bool
italic : Bool
underline : Bool
} derive(Eq, Debug, ToJson)
///|
/// Fountain inline emphasis. At depth 16 further markup stays literal.
pub fn inline_text(text : String) -> Array[Inline] {
if text.length() > 8192 {
return [{ text, bold: false, italic: false, underline: false, }]
}
inline_scan(text, false, false, false, 0)
}
///|
fn unit_width(text : String, i : Int) -> Int {
if text[i].to_int() >= 0xD800 &&
text[i].to_int() <= 0xDBFF &&
i + 1 < text.length() {
2
} else {
1
}
}
///|
fn inline_scan(
text : String,
bold : Bool,
italic : Bool,
underline : Bool,
depth : Int,
) -> Array[Inline] {
let out : Array[Inline] = []
if depth >= 16 {
return [{ text, bold, italic, underline, }]
}
let mut i = 0
let mut start = 0
while i < text.length() {
if text[i] == '\\' &&
i + 1 < text.length() &&
(text[i + 1] == '*' || text[i + 1] == '_' || text[i + 1] == '\\') {
if start < i {
out.push({ text: text[start:i].to_owned(), bold, italic, underline, })
}
out.push({ text: text[i + 1:i + 2].to_owned(), bold, italic, underline, })
i += 2
start = i
continue
}
if text[i] == '*' || text[i] == '_' {
let mark = text[i]
let mut width = 1
while i + width < text.length() && text[i + width] == mark {
width += 1
}
let mut close = -1
if (mark == '_' && width == 1) || (mark == '*' && width <= 3) {
let mut j = i + width
if j < text.length() && text[j] != ' ' && text[j] != '\t' {
while j < text.length() {
if text[j] == '\\' && j + 1 < text.length() {
j += 2
continue
}
if text[j] == mark {
let mut run = 1
while j + run < text.length() && text[j + run] == mark {
run += 1
}
if run == width &&
j > i + width &&
text[j - 1] != ' ' &&
text[j - 1] != '\t' {
close = j
break
}
j += run
} else {
j += 1
}
}
}
}
if close >= 0 {
if start < i {
out.push({ text: text[start:i].to_owned(), bold, italic, underline, })
}
let nested = inline_scan(
text[i + width:close].to_owned(),
bold || (mark == '*' && width >= 2),
italic || (mark == '*' && width != 2),
underline || mark == '_',
depth + 1,
)
for part in nested {
out.push(part)
}
i = close + width
start = i
} else {
i += width
}
} else {
i += unit_width(text, i)
}
}
if start < text.length() {
out.push({ text: text[start:].to_owned(), bold, italic, underline, })
}
out
}
///|
pub fn plain_text(text : String) -> String {
let out = StringBuilder()
for part in inline_text(text) {
out.write_stringview(part.text[:])
}
out.to_string()
}