feat: render fixed-width text for nex
This commit is contained in:
parent
2f38b8b929
commit
9e1cfed284
19 changed files with 1160 additions and 44 deletions
|
|
@ -12,6 +12,7 @@
|
|||
use itsybitsy_core::Error;
|
||||
use itsybitsy_core::ir::{Block, Doc, Inline};
|
||||
use itsybitsy_core::render::{RenderCtx, Rendered, Renderer};
|
||||
use itsybitsy_core::wrap::{display_width, pad};
|
||||
|
||||
pub struct Gemtext;
|
||||
|
||||
|
|
@ -175,7 +176,7 @@ impl Writer {
|
|||
let widths: Vec<usize> = (0..columns)
|
||||
.map(|index| {
|
||||
all.iter()
|
||||
.map(|row| row.get(index).map_or(0, |c| c.chars().count()))
|
||||
.map(|row| row.get(index).map_or(0, |cell| display_width(cell)))
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
})
|
||||
|
|
@ -183,8 +184,9 @@ impl Writer {
|
|||
|
||||
self.line("```");
|
||||
for (index, row) in all.iter().enumerate() {
|
||||
// Padded by display width, so a wide character does not skew its column.
|
||||
let cells: Vec<String> =
|
||||
row.iter().zip(&widths).map(|(cell, width)| format!("{cell:<width$}")).collect();
|
||||
row.iter().zip(&widths).map(|(cell, width)| pad(cell, *width)).collect();
|
||||
self.line(&cells.join(" "));
|
||||
// A rule under the header row, matching the padded column widths.
|
||||
if index == 0 && !head.is_empty() {
|
||||
|
|
@ -274,20 +276,29 @@ fn quoted(blocks: &[Block]) -> Vec<String> {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod tests_support {
|
||||
use itsybitsy_core::config::PageSettings;
|
||||
use itsybitsy_core::parse;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Render a Markdown fragment the way the server would.
|
||||
fn render(markdown: &str) -> String {
|
||||
pub fn render(markdown: &str) -> String {
|
||||
let doc = parse::markdown(markdown);
|
||||
let settings = PageSettings::default();
|
||||
let ctx = RenderCtx { url: "/x", title: "T", settings: &settings, width: None };
|
||||
let out = Gemtext.render(&doc, &ctx).unwrap();
|
||||
String::from_utf8(out.body).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use itsybitsy_core::config::PageSettings;
|
||||
use itsybitsy_core::parse;
|
||||
|
||||
use super::tests_support::render;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn headings_flatten_onto_three_levels() {
|
||||
|
|
@ -387,3 +398,22 @@ mod tests {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod width_tests {
|
||||
use itsybitsy_core::wrap::display_width;
|
||||
|
||||
use super::tests_support::render;
|
||||
|
||||
#[test]
|
||||
fn table_columns_align_by_display_width_not_character_count() {
|
||||
// A CJK ideograph is two columns wide; counting characters would leave
|
||||
// the column a column short for each one.
|
||||
let out = render("| a | b |\n| --- | --- |\n| 日本 | x |\n");
|
||||
let lines: Vec<&str> = out.lines().collect();
|
||||
// Compared in columns, not bytes: a multibyte cell shifts byte offsets
|
||||
// without shifting where the text actually appears.
|
||||
let column = |line: &str, ch: char| display_width(&line[..line.find(ch).unwrap()]);
|
||||
assert_eq!(column(lines[1], 'b'), column(lines[3], 'x'), "{out}");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue