///|
/// Horizontal alignment in monospace display columns.
pub(all) enum Alignment {
Left
Right
Center
Justify
} derive(Eq, Debug)
///|
pub extend Alignment with Eq::{not_equal, equal}
///|
pub extend Alignment with Debug::{to_repr}
///|
fn cjk(s : String) -> Bool {
match s.iter().next() {
None => false
Some(c) =>
match @data.line_break(c.to_int()) {
ID | CJ | JL | JV | JT | H2 | H3 => true
_ => false
}
}
}
///|
fn justify(text : String, extra : Int) -> String {
let gs = @grapheme.grapheme_iter(text).map(fn(g) { g.to_owned() }).to_array()
let legal = @linebreak.line_break_opportunities(text)
let slots : Array[Int] = []
let mut byte = 0
let mut content_seen = false
for i = 0; i + 1 < gs.length(); i = i + 1 {
byte += @text.byte_length(gs[i])
if gs[i] != " " {
content_seen = true
}
let space_gap = content_seen && gs[i] == " " && gs[i + 1] != " "
let cjk_gap = cjk(gs[i]) &&
cjk(gs[i + 1]) &&
(match legal.binary_search(byte) {
Ok(_) => true
Err(_) => false
})
if space_gap || cjk_gap {
slots.push(i)
}
}
if slots.is_empty() {
return text + " ".repeat(extra)
}
let each = extra / slots.length()
let remainder = extra % slots.length()
let builder = StringBuilder()
let mut slot = 0
for i, g in gs {
builder.write_string(g)
if slot < slots.length() && slots[slot] == i {
builder.write_string(
" ".repeat(each + (if slot < remainder { 1 } else { 0 })),
)
slot += 1
}
}
builder.to_string()
}
///|
/// Pad each line to width without truncating overflow. Center puts an odd extra
/// space on the right. Justify stretches inter-word spaces or permitted CJK
/// grapheme gaps; final/Hard/End lines and lines with no slots are left-aligned.
/// Source ranges are untouched. All measurements respect each line's width mode.
pub fn align_lines(
lines : Array[@wrap.Line],
width : Int,
alignment : Alignment,
) -> Array[String] {
let result = []
for i, line in lines {
let text = line.text.trim_end(chars=" \t").to_owned()
let actual = @wrap.display_width(text, ambiguous_wide=line.ambiguous_wide)
let extra = if width > actual { width - actual } else { 0 }
let value = match alignment {
Left => text + " ".repeat(extra)
Right => " ".repeat(extra) + text
Center => " ".repeat(extra / 2) + text + " ".repeat(extra - extra / 2)
Justify =>
if i + 1 == lines.length() ||
line.break_kind == @wrap.Hard ||
line.break_kind == @wrap.End ||
extra == 0 {
text + " ".repeat(extra)
} else {
justify(text, extra)
}
}
result.push(value)
}
result
}