Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ console_error_panic_hook = "0.1.7"
thiserror = "2.0.12"
bitvec = { version = "1.0.1", default-features = false, features = ["alloc", "std"] }
beamterm-renderer = "0.1.1"
unicode-width = "0.2.0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's a 0.2.1 release of unicode-width

9 changes: 9 additions & 0 deletions examples/unicode/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "minimal"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
ratzilla = { path = "../../" }
examples-shared = { path = "../shared" }
36 changes: 36 additions & 0 deletions examples/unicode/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!doctype html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
<link rel="preload" as="style" crossorigin href="https://fontsapi.zeoseven.com/442/main/result.css"
onload="this.rel='stylesheet'" onerror="this.href='https://fontsapi-storage.zeoseven.com/442/main/result.css'" />
<noscript>
<link rel="stylesheet" href="https://fontsapi.zeoseven.com/442/main/result.css" />
</noscript>
<title>Ratzilla</title>
<style>
body {
margin: 0;
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
align-content: center;
background-color: #121212;
}

pre {
font-family: "Maple Mono NF CN", monospace;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i wasn't sure this would work on my computer, but it looked fine (not sure what it's using though)

image

font-size: 15px;
margin: 0px;
}
</style>
</head>

<body></body>

</html>
40 changes: 40 additions & 0 deletions examples/unicode/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use std::io;

use ratzilla::ratatui::{
layout::Alignment,
style::Color,
widgets::{Block, Paragraph},
};

use ratzilla::WebRenderer;

use examples_shared::backend::{BackendType, MultiBackendBuilder};

fn main() -> io::Result<()> {
let terminal = MultiBackendBuilder::with_fallback(BackendType::Dom).build_terminal()?;

terminal.draw_web(move |f| {
f.render_widget(
Paragraph::new(
[
"Hello, world!",
"你好,世界!",
"世界、こんにちは。",
// "헬로우 월드!",
// "👨💻👋🌐",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are the emoji disabled?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, ofc "As a result, misalignment may occur when displaying Korean text or emojis." - what about enforcing the width as width * 2 for double-width symbols instead of calculating it from metrics - how does it look?

]
.join("\n"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

neat and compact :)

)
.alignment(Alignment::Center)
.block(
Block::bordered()
.title("Ratzilla")
.title_alignment(Alignment::Center)
.border_style(Color::Yellow),
),
f.area(),
);
});

Ok(())
}
24 changes: 18 additions & 6 deletions src/backend/dom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use ratatui::{
layout::{Position, Size},
prelude::Backend,
};
use unicode_width::UnicodeWidthStr;
use web_sys::{
wasm_bindgen::{prelude::Closure, JsCast},
window, Document, Element, Window,
Expand Down Expand Up @@ -148,27 +149,30 @@ impl DomBackend {
fn prerender(&mut self) -> Result<(), Error> {
for line in self.buffer.iter() {
let mut line_cells: Vec<Element> = Vec::new();
let mut hyperlink: Vec<Cell> = Vec::new();
let mut hyperlink: Vec<(Cell, bool)> = Vec::new();
let mut skip = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't skip be a boolean value ? either the next cell renders as usual or it is skipped/hidden.

are there any symbols extending more than double-width - if we stick to terminals?

for (i, cell) in line.iter().enumerate() {
let overwritten = skip > 0;
skip = std::cmp::max(skip, cell.symbol().width()).saturating_sub(1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it make sense to cache the width of all encountered symbols? cell.symbol().width() looks like it's could be fairly expensive if, for example, scrolling large portions of text at once.

if cell.modifier.contains(HYPERLINK_MODIFIER) {
hyperlink.push(cell.clone());
hyperlink.push((cell.clone(), overwritten));
// If the next cell is not part of the hyperlink, close it
if !line
.get(i + 1)
.map(|c| c.modifier.contains(HYPERLINK_MODIFIER))
.unwrap_or(false)
{
let anchor = create_anchor(&self.document, &hyperlink)?;
for link_cell in &hyperlink {
let span = create_span(&self.document, link_cell)?;
for (link_cell, overwritten) in &hyperlink {
let span = create_span(&self.document, link_cell, *overwritten)?;
self.cells.push(span.clone());
anchor.append_child(&span)?;
}
line_cells.push(anchor);
hyperlink.clear();
}
} else {
let span = create_span(&self.document, cell)?;
let span = create_span(&self.document, cell, overwritten)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe it would be clearer to have the old create_span() as-is and add a create_hidden_span(document); it also doesn't require a ref to cell, since it's only used for reading Cell::symbol. what do you think?

self.cells.push(span.clone());
line_cells.push(span);
}
Expand All @@ -192,14 +196,22 @@ impl DomBackend {
/// accordingly.
fn update_grid(&mut self) -> Result<(), Error> {
for (y, line) in self.buffer.iter().enumerate() {
let mut skip = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment applies here about skip maybe being a boolean

for (x, cell) in line.iter().enumerate() {
let overwritten = skip > 0;
skip = std::cmp::max(skip, cell.symbol().width()).saturating_sub(1);
if cell.modifier.contains(HYPERLINK_MODIFIER) {
continue;
}
if cell != &self.prev_buffer[y][x] {
let elem = self.cells[y * self.buffer[0].len() + x].clone();
elem.set_inner_html(cell.symbol());
elem.set_attribute("style", &get_cell_style_as_css(cell))?;
if overwritten {
// If the cell is overwritten, hide it
elem.set_attribute("style", "display: none;")?;
} else {
elem.set_attribute("style", &get_cell_style_as_css(cell))?;
}
}
}
}
Expand Down
20 changes: 14 additions & 6 deletions src/backend/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,31 @@ use web_sys::{
};

/// Creates a new `<span>` element with the given cell.
pub(crate) fn create_span(document: &Document, cell: &Cell) -> Result<Element, Error> {
pub(crate) fn create_span(
document: &Document,
cell: &Cell,
overwritten: bool,
) -> Result<Element, Error> {
let span = document.create_element("span")?;
span.set_inner_html(cell.symbol());

let style = get_cell_style_as_css(cell);
span.set_attribute("style", &style)?;
if overwritten {
span.set_attribute("style", "display: none;")?;
} else {
let style = get_cell_style_as_css(cell);
span.set_attribute("style", &style)?;
}
Ok(span)
}

/// Creates a new `<a>` element with the given cells.
pub(crate) fn create_anchor(document: &Document, cells: &[Cell]) -> Result<Element, Error> {
pub(crate) fn create_anchor(document: &Document, cells: &[(Cell, bool)]) -> Result<Element, Error> {
let anchor = document.create_element("a")?;
anchor.set_attribute(
"href",
&cells.iter().map(|c| c.symbol()).collect::<String>(),
&cells.iter().map(|c| c.0.symbol()).collect::<String>(),
)?;
anchor.set_attribute("style", &get_cell_style_as_css(&cells[0]))?;
anchor.set_attribute("style", &get_cell_style_as_css(&cells[0].0))?;
Ok(anchor)
}

Expand Down