///|
/// 布局后的格子种类。占位格是 cli-table3 里 `ColSpanCell` / `RowSpanCell`
/// 的对应物:不用继承与 `instanceof`,把三态写进类型,绘制时靠模式匹配分流。
pub enum PlacedKind {
/// 有内容的格子,可能跨行跨列。
Content
/// 跨列占位:自身不画内容,只把宽度让给左侧的格子。
ColSpan
/// 跨行占位:内容取自原格子,行号按 `(cell_offset, offset)` 偏移。
RowSpan(Int32, Int32)
} derive(Debug)
///|
pub extend PlacedKind with Debug::{to_repr}
///|
/// 布局网格里的一个位置。
pub struct Placed {
cell : Cell
kind : PlacedKind
x : Int32
y : Int32
} derive(Debug)
///|
pub extend Placed with Debug::{to_repr}
///|
/// 跨行列数的最小值:0 或负数一律当 1。
fn span_of(v : Int32) -> Int32 {
if v < 1 {
1
} else {
v
}
}
// ---------------------------------------------------------------------------
// 位置分配
// ---------------------------------------------------------------------------
///|
/// 取第一个没有被上方跨行格子占用的列。
fn next_free_col(alloc : Array[Int32], col : Int32) -> Int32 {
if col < alloc.length() && alloc[col] > 0 {
next_free_col(alloc, col + 1)
} else {
col
}
}
///|
fn alloc_reserve(alloc : Array[Int32], col : Int32, rows : Int32) -> Unit {
while alloc.length() <= col {
alloc.push(0)
}
alloc[col] = rows
}
///|
/// 一行处理完,所有占用计数减一,减到零的列重新可用。
fn alloc_tick(alloc : Array[Int32]) -> Unit {
for i in 0.. 0 {
alloc[i] = alloc[i] - 1
}
}
}
///|
/// 给每个格子分配 `x` / `y`。跨行格子会在它覆盖的列上登记剩余行数,后面的行
/// 自动跳过这些列位,所以用户不必为空出来的位置补占位符。
///
/// 返回每一行进入时"已被上方跨行格子占用"的列数,供调用方校验行宽是否合理。
fn assign_positions(grid : Array[Array[Placed]]) -> Array[Int32] {
let alloc : Array[Int32] = []
let occupied : Array[Int32] = []
for y in 0.. 0 {
count += 1
}
}
occupied.push(count)
let row = grid[y]
let mut col = 0
for i in 0.. 1 {
for cs in 0.. Bool {
let y_end = p.y - 1 + span_of(p.cell.row_span)
let y_overlap = !(p.y > y || y > y_end)
let x_end = p.x - 1 + span_of(p.cell.col_span)
let x_overlap = !(p.x > x || x > x_end)
y_overlap && x_overlap
}
///|
/// `(x, y)` 是否已被某个格子覆盖。只看到 `y` 为止的行。
fn conflict_exists(grid : Array[Array[Placed]], x : Int32, y : Int32) -> Bool {
let last = if y < grid.length() { y } else { grid.length() - 1 }
for i in 0..<=last {
for p in grid[i] {
if cells_conflict(p, x, y) {
return true
}
}
}
false
}
///|
fn all_blank(
grid : Array[Array[Placed]],
y : Int32,
x_min : Int32,
x_max : Int32,
) -> Bool {
for x in x_min.. Int32 {
let mut widest = 0
for row in grid {
for p in row {
let end = p.x + span_of(p.cell.col_span)
if end > widest {
widest = end
}
}
}
widest
}
///|
/// 按 `x` 顺序把格子插进行,保持该行有序。
fn insert_placed(row : Array[Placed], placed : Placed) -> Unit {
let mut i = 0
while i < row.length() && row[i].x < placed.x {
i += 1
}
row.insert(i, placed)
}
// ---------------------------------------------------------------------------
// 空洞填补
// ---------------------------------------------------------------------------
///|
/// 用户没铺满的位置补成空格子:先向右扩到下一个已被占用的列位,再向下扩到
/// 有内容的行为止。补出来的格子参与绘制,所以缺格子的行照样对齐。
fn fill_holes(grid : Array[Array[Placed]]) -> Unit {
let h_max = grid.length()
let w_max = grid_cols(grid)
for y in 0.. Bool {
match p.kind {
Content => true
_ => false
}
}
///|
/// 为跨行格子在其下方的每一行插入占位格。占位格持有原格子的副本,绘制时按
/// `(cell_offset, offset)` 找到原格子该画的内容行。
fn add_row_span_slots(grid : Array[Array[Placed]]) -> Unit {
for y in 0.. Unit {
let mut y = grid.length()
while y > 0 {
y -= 1
let row = grid[y]
let mut i = 0
while i < row.length() {
let p = row[i]
let col_span = span_of(p.cell.col_span)
for k in 1.. (Array[Array[Placed]], Array[Int32]) {
let grid = rows.map(fn(row) {
row.map(fn(cell) { { cell, kind: Content, x: 0, y: 0, } })
})
let occupied = assign_positions(grid)
fill_holes(grid)
add_row_span_slots(grid)
add_col_span_slots(grid)
(grid, occupied)
}
// ---------------------------------------------------------------------------
// 列宽与行高
// ---------------------------------------------------------------------------
///|
/// 格子的自然宽度:最宽一行内容加左右内边距。
fn desired_width(p : Placed, pad_l : Int32, pad_r : Int32) -> Int32 {
display_width_multiline(p.cell.content) + pad_l + pad_r
}
///|
fn sum_of(xs : Array[Int32]) -> Int32 {
let mut total = 0
for x in xs {
total += x
}
total
}
///|
/// 在给定预算里按均摊方式削列宽:每轮对所有还能再减的列减同样的量,到最小宽
/// 就停,绝不把列压到装不下内边距。
///
/// `explicit` 标记显式列宽的列:这些列不参与削减。显式 `Column.width` 是用户
/// 的决定,总宽预算只能向自动列伸手,不能反悔用户写下的数字。
fn shrink_to(
widths : Array[Int32],
budget : Int32,
min_col : Int32,
explicit : Array[Bool],
) -> Unit {
let mut steps = 0
while sum_of(widths) > budget && steps < 1000 {
steps += 1
let mut cuttable = 0
for i in 0.. min_col {
cuttable += 1
}
}
if cuttable == 0 {
break
}
let over = sum_of(widths) - budget
let per = if over / cuttable > 0 { over / cuttable } else { 1 }
for i in 0.. min_col {
let next = widths[i] - per
widths[i] = if next < min_col { min_col } else { next }
}
}
}
}
///|
/// 每列的最终显示宽度(含左右内边距)。
///
/// 先按普通格子取各自自然宽度,显式列宽覆盖之,再把跨列格子放不下的差额均摊给
/// 它覆盖的可编辑列,最后在有总宽约束时按均摊削减。
pub fn compute_col_widths(
grid : Array[Array[Placed]],
cols : Array[Column],
pad_l : Int32,
pad_r : Int32,
total_width : Int32?,
) -> Array[Int32] {
let ncols = cols.length()
let result : Array[Int32] = []
let explicit : Array[Bool] = []
for _ in 0.. 1 {
spanners.push(p)
} else if p.x < ncols {
let w = desired_width(p, pad_l, pad_r)
if w > result[p.x] {
result[p.x] = w
}
}
}
}
for i in 0.. {
result[i] = w + pad_l + pad_r
explicit[i] = true
}
None => ()
}
}
// 跨列格子放不下时,把差额均摊给它覆盖的可编辑列。
let mut k = spanners.length()
while k > 0 {
k -= 1
let p = spanners[k]
let span = span_of(p.cell.col_span)
let col = p.x
if col >= ncols {
continue
}
let needed = desired_width(p, pad_l, pad_r)
let mut existing = result[col]
let mut editable = if explicit[col] { 0 } else { 1 }
for i in 1.. existing {
let mut i = 0
while editable > 0 && needed > existing && col + i < ncols {
if !explicit[col + i] {
let raw = needed - existing
let diff = (raw + editable / 2) / editable
existing += diff
result[col + i] = result[col + i] + diff
editable -= 1
}
i += 1
}
}
}
let min_col = pad_l + pad_r + 1
for i in 0..
// 每列之间与两侧各有一条分隔线,也要算进总宽。
shrink_to(result, budget - (ncols + 1), min_col, explicit)
None => ()
}
result
}
///|
/// 格子内容在渲染时占几行。开启换行的列按列宽折行,其余按 `\n` 分行。
fn cell_lines(
p : Placed,
inner : Int32,
wrap : Bool,
on_word_boundary : Bool,
) -> Array[String] {
if wrap {
wrap_text(p.cell.content, inner, on_word_boundary)
} else {
split_lines(p.cell.content)
}
}
///|
/// 每行的内容行数(不含上下边框)。
///
/// 普通格子取自身行数。跨行格子要的行数比它覆盖的几行加起来还多时,把差额均摊
/// 给这些行,跨行内容才放得下。
pub fn compute_row_heights(
grid : Array[Array[Placed]],
cols : Array[Column],
col_widths : Array[Int32],
pad_l : Int32,
pad_r : Int32,
wrap : Bool,
on_word_boundary : Bool,
) -> Array[Int32] {
let nrows = grid.length()
let heights : Array[Int32] = []
for _ in 0.. 1 {
spanners.push(p)
} else if p.y < nrows {
if lines.length() > heights[p.y] {
heights[p.y] = lines.length()
}
}
}
}
let spans : Array[Int32] = []
for p in spanners {
spans.push(span_of(p.cell.row_span))
}
let mut k = spanners.length()
while k > 0 {
k -= 1
let p = spanners[k]
let span = span_of(p.cell.row_span)
let inner = inner_width(
col_widths,
p.x,
span_of(p.cell.col_span),
pad_l,
pad_r,
)
let needed = cell_lines(p, inner, wraps(p, cols, wrap), on_word_boundary).length()
let row = p.y
if row >= nrows {
continue
}
let mut existing = heights[row]
let mut editable = 1
for i in 1.. existing {
let mut i = 0
while editable > 0 && needed > existing && row + i < nrows {
let raw = needed - existing
let diff = (raw + editable / 2) / editable
existing += diff
heights[row + i] = heights[row + i] + diff
editable -= 1
i += 1
}
}
}
heights
}
///|
/// 该列是否开启换行:列级开关优先,其次看表格级开关。
fn wraps(p : Placed, cols : Array[Column], table_wrap : Bool) -> Bool {
table_wrap || (p.x < cols.length() && cols[p.x].wrap)
}
///|
/// 一个格子占用的内容宽度:这些列的宽度之和,加上中间的分隔线。
fn inner_width(
col_widths : Array[Int32],
x : Int32,
span : Int32,
pad_l : Int32,
pad_r : Int32,
) -> Int32 {
let mut total = 0
for i in 0.. 0 {
total += 1
}
total += w
}
let inner = total - pad_l - pad_r
if inner > 0 {
inner
} else {
1
}
}
///|
/// 算出跨行占位格相对原格子的内容行偏移。原格子跨了多行时,它的内容要按这些
/// 行的累积高度往下排。
pub fn resolve_row_span_offsets(
grid : Array[Array[Placed]],
row_heights : Array[Int32],
) -> Unit {
for y in 0.. {
let origin_y = p.y - cell_offset
let offset = dimension_sum(row_heights, origin_y, cell_offset)
row[i] = { ..p, kind: RowSpan(cell_offset, offset), }
}
_ => ()
}
}
}
}
///|
/// 原行的高度,加上中间每一行的 (1 + 高度)。与 cli-table3 的 `findDimension` 一致。
fn dimension_sum(heights : Array[Int32], start : Int32, span : Int32) -> Int32 {
let mut total = if start < heights.length() { heights[start] } else { 1 }
for i in 1..