Skip to content
Draft
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"
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;
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!",
"你好,世界!",
"世界、こんにちは。",
"헬로우 월드!",
"👨💻👋🌐",
]
.join("\n"),
)
.alignment(Alignment::Center)
.block(
Block::bordered()
.title("Ratzilla")
.title_alignment(Alignment::Center)
.border_style(Color::Yellow),
),
f.area(),
);
});

Ok(())
}
3 changes: 2 additions & 1 deletion src/backend/canvas.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use bitvec::{bitvec, prelude::BitVec};
use ratatui::layout::Rect;
use std::io::Result as IoResult;
use unicode_width::UnicodeWidthStr;

use crate::{
backend::{
Expand Down Expand Up @@ -300,7 +301,7 @@ impl CanvasBackend {
self.canvas.context.rect(
x as f64 * CELL_WIDTH,
y as f64 * CELL_HEIGHT,
CELL_WIDTH,
cell.symbol().width() as f64 * CELL_WIDTH,
CELL_HEIGHT,
);
self.canvas.context.clip();
Expand Down
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;
for (i, 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) {
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)?;
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;
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