aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--Cargo.lock16
-rw-r--r--Cargo.toml9
-rw-r--r--src/command/client/search/history_list.rs4
-rw-r--r--src/command/client/search/interactive.rs4
-rw-r--r--src/main.rs3
-rw-r--r--src/ratatui/.github/ISSUE_TEMPLATE/bug_report.md60
-rw-r--r--src/ratatui/.github/ISSUE_TEMPLATE/config.yml1
-rw-r--r--src/ratatui/.github/ISSUE_TEMPLATE/feature_request.md32
-rw-r--r--src/ratatui/.github/workflows/cd.yml19
-rw-r--r--src/ratatui/.github/workflows/ci.yml76
-rw-r--r--src/ratatui/.gitignore6
-rw-r--r--src/ratatui/LICENSE21
-rw-r--r--src/ratatui/README.md136
-rw-r--r--src/ratatui/backend/crossterm.rs241
-rw-r--r--src/ratatui/backend/mod.rs58
-rw-r--r--src/ratatui/backend/termion.rs275
-rw-r--r--src/ratatui/buffer.rs736
-rw-r--r--src/ratatui/layout.rs560
-rw-r--r--src/ratatui/mod.rs177
-rw-r--r--src/ratatui/style.rs310
-rw-r--r--src/ratatui/symbols.rs233
-rw-r--r--src/ratatui/terminal.rs487
-rw-r--r--src/ratatui/text.rs430
-rw-r--r--src/ratatui/widgets/barchart.rs219
-rw-r--r--src/ratatui/widgets/block.rs573
-rw-r--r--src/ratatui/widgets/canvas/line.rs95
-rw-r--r--src/ratatui/widgets/canvas/map.rs48
-rw-r--r--src/ratatui/widgets/canvas/mod.rs510
-rw-r--r--src/ratatui/widgets/canvas/points.rs30
-rw-r--r--src/ratatui/widgets/canvas/rectangle.rs52
-rw-r--r--src/ratatui/widgets/canvas/world.rs6299
-rw-r--r--src/ratatui/widgets/chart.rs660
-rw-r--r--src/ratatui/widgets/clear.rs37
-rw-r--r--src/ratatui/widgets/gauge.rs313
-rw-r--r--src/ratatui/widgets/list.rs268
-rw-r--r--src/ratatui/widgets/mod.rs184
-rw-r--r--src/ratatui/widgets/paragraph.rs214
-rw-r--r--src/ratatui/widgets/reflow.rs534
-rw-r--r--src/ratatui/widgets/sparkline.rs155
-rw-r--r--src/ratatui/widgets/table.rs504
-rw-r--r--src/ratatui/widgets/tabs.rs129
41 files changed, 14697 insertions, 21 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 264648bb..7fa24a1f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -77,6 +77,8 @@ dependencies = [
"atuin-common",
"atuin-server",
"base64 0.20.0",
+ "bitflags",
+ "cassowary",
"chrono",
"clap",
"clap_complete",
@@ -93,7 +95,6 @@ dependencies = [
"interim",
"itertools",
"log",
- "ratatui",
"rpassword",
"runtime-format",
"semver",
@@ -102,6 +103,7 @@ dependencies = [
"tiny-bip39",
"tokio",
"tracing-subscriber",
+ "unicode-segmentation",
"unicode-width",
"whoami",
]
@@ -1584,18 +1586,6 @@ dependencies = [
]
[[package]]
-name = "ratatui"
-version = "0.20.1"
-source = "git+https://github.com/conradludgate/tui-rs-revival?branch=inline#6ed61959ecfc560e4e6a00a1410bb5fcbf0eda91"
-dependencies = [
- "bitflags",
- "cassowary",
- "crossterm",
- "unicode-segmentation",
- "unicode-width",
-]
-
-[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index af3867b7..9272dc68 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -73,15 +73,16 @@ semver = "1.0.14"
runtime-format = "0.1.2"
tiny-bip39 = "1"
futures-util = "0.3"
-ratatui = "0.20.1"
fuzzy-matcher = "0.3.7"
colored = "2.0.0"
+# ratatui
+bitflags = "1.3"
+cassowary = "0.3"
+unicode-segmentation = "1.2"
+
[dependencies.tracing-subscriber]
version = "0.3"
default-features = false
features = ["ansi", "fmt", "registry", "env-filter"]
optional = true
-
-[patch.crates-io]
-ratatui = { git = "https://github.com/conradludgate/tui-rs-revival", branch = "inline" }
diff --git a/src/command/client/search/history_list.rs b/src/command/client/search/history_list.rs
index 928fe4c7..eedab1a5 100644
--- a/src/command/client/search/history_list.rs
+++ b/src/command/client/search/history_list.rs
@@ -1,12 +1,12 @@
use std::time::Duration;
-use atuin_client::history::History;
-use ratatui::{
+use crate::ratatui::{
buffer::Buffer,
layout::Rect,
style::{Color, Modifier, Style},
widgets::{Block, StatefulWidget, Widget},
};
+use atuin_client::history::History;
use super::format_duration;
diff --git a/src/command/client/search/interactive.rs b/src/command/client/search/interactive.rs
index 5038fc83..a09cfb73 100644
--- a/src/command/client/search/interactive.rs
+++ b/src/command/client/search/interactive.rs
@@ -23,8 +23,7 @@ use super::{
engines::{SearchEngine, SearchState},
history_list::{HistoryList, ListState, PREFIX_LENGTH},
};
-use crate::{command::client::search::engines, VERSION};
-use ratatui::{
+use crate::ratatui::{
backend::{Backend, CrosstermBackend},
layout::{Alignment, Constraint, Direction, Layout},
style::{Color, Modifier, Style},
@@ -32,6 +31,7 @@ use ratatui::{
widgets::{Block, BorderType, Borders, Paragraph},
Frame, Terminal, TerminalOptions, Viewport,
};
+use crate::{command::client::search::engines, VERSION};
const RETURN_ORIGINAL: usize = usize::MAX;
const RETURN_QUERY: usize = usize::MAX - 1;
diff --git a/src/main.rs b/src/main.rs
index 2f81f4fc..9e570337 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,6 +7,9 @@ use eyre::Result;
use command::AtuinCmd;
mod command;
+#[allow(clippy::all)]
+mod ratatui;
+
const VERSION: &str = env!("CARGO_PKG_VERSION");
static HELP_TEMPLATE: &str = "\
diff --git a/src/ratatui/.github/ISSUE_TEMPLATE/bug_report.md b/src/ratatui/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 00000000..0a0f4bc6
--- /dev/null
+++ b/src/ratatui/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,60 @@
+---
+name: Bug report
+about: Create an issue about a bug you encountered
+title: ''
+labels: bug
+assignees: ''
+---
+
+<!--
+Hi there, sorry `ratatui` is not working as expected.
+Please fill this bug report conscientiously.
+A detailed and complete issue is more likely to be processed quickly.
+-->
+
+## Description
+<!--
+A clear and concise description of what the bug is.
+-->
+
+
+## To Reproduce
+<!--
+Try to reduce the issue to a simple code sample exhibiting the problem.
+Ideally, fork the project and add a test or an example.
+-->
+
+
+## Expected behavior
+<!--
+A clear and concise description of what you expected to happen.
+-->
+
+
+## Screenshots
+<!--
+If applicable, add screenshots, gifs or videos to help explain your problem.
+-->
+
+
+## Environment
+<!--
+Add a description of the systems where you are observing the issue. For example:
+- OS: Linux
+- Terminal Emulator: xterm
+- Font: Inconsolata (Patched)
+- Crate version: 0.7
+- Backend: termion
+-->
+
+- OS:
+- Terminal Emulator:
+- Font:
+- Crate version:
+- Backend:
+
+## Additional context
+<!--
+Add any other context about the problem here.
+If you already looked into the issue, include all the leads you have explored.
+-->
diff --git a/src/ratatui/.github/ISSUE_TEMPLATE/config.yml b/src/ratatui/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 00000000..3ba13e0c
--- /dev/null
+++ b/src/ratatui/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1 @@
+blank_issues_enabled: false
diff --git a/src/ratatui/.github/ISSUE_TEMPLATE/feature_request.md b/src/ratatui/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 00000000..ae095edb
--- /dev/null
+++ b/src/ratatui/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,32 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: enhancement
+assignees: ''
+
+---
+
+## Problem
+<!--
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+-->
+
+## Solution
+<!--
+A clear and concise description of what you want to happen.
+Things to consider:
+- backward compatibility
+- ease of use of the API (https://rust-lang.github.io/api-guidelines/)
+- consistency with the rest of the crate
+-->
+
+## Alternatives
+<!--
+A clear and concise description of any alternative solutions or features you've considered.
+-->
+
+## Additional context
+<!--
+Add any other context or screenshots about the feature request here.
+-->
diff --git a/src/ratatui/.github/workflows/cd.yml b/src/ratatui/.github/workflows/cd.yml
new file mode 100644
index 00000000..f61e3603
--- /dev/null
+++ b/src/ratatui/.github/workflows/cd.yml
@@ -0,0 +1,19 @@
+name: Continuous Deployment
+
+on:
+ push:
+ tags:
+ - "v*.*.*"
+
+jobs:
+ publish:
+ name: Publish on crates.io
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout the repository
+ uses: actions/checkout@v3
+ - name: Publish
+ uses: actions-rs/cargo@v1
+ with:
+ command: publish
+ args: --token ${{ secrets.CARGO_TOKEN }}
diff --git a/src/ratatui/.github/workflows/ci.yml b/src/ratatui/.github/workflows/ci.yml
new file mode 100644
index 00000000..bfa363e9
--- /dev/null
+++ b/src/ratatui/.github/workflows/ci.yml
@@ -0,0 +1,76 @@
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+
+name: CI
+
+env:
+ CI_CARGO_MAKE_VERSION: 0.35.16
+
+jobs:
+ test:
+ strategy:
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ rust: ["1.59.0", "stable"]
+ include:
+ - os: ubuntu-latest
+ triple: x86_64-unknown-linux-musl
+ - os: windows-latest
+ triple: x86_64-pc-windows-msvc
+ - os: macos-latest
+ triple: x86_64-apple-darwin
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: hecrj/setup-rust-action@50a120e4d34903c2c1383dec0e9b1d349a9cc2b1
+ with:
+ rust-version: ${{ matrix.rust }}
+ components: rustfmt,clippy
+ - uses: actions/checkout@v3
+ - name: Install cargo-make on Linux or macOS
+ if: ${{ runner.os != 'windows' }}
+ shell: bash
+ run: |
+ curl -LO 'https://github.com/sagiegurari/cargo-make/releases/download/${{ env.CI_CARGO_MAKE_VERSION }}/cargo-make-v${{ env.CI_CARGO_MAKE_VERSION }}-${{ matrix.triple }}.zip'
+ unzip 'cargo-make-v${{ env.CI_CARGO_MAKE_VERSION }}-${{ matrix.triple }}.zip'
+ cp 'cargo-make-v${{ env.CI_CARGO_MAKE_VERSION }}-${{ matrix.triple }}/cargo-make' ~/.cargo/bin/
+ cargo make --version
+ - name: Install cargo-make on Windows
+ if: ${{ runner.os == 'windows' }}
+ shell: bash
+ run: |
+ # `cargo-make-v0.35.16-{target}/` directory is created on Linux and macOS, but it is not creatd on Windows.
+ mkdir cargo-make-temporary
+ cd cargo-make-temporary
+ curl -LO 'https://github.com/sagiegurari/cargo-make/releases/download/${{ env.CI_CARGO_MAKE_VERSION }}/cargo-make-v${{ env.CI_CARGO_MAKE_VERSION }}-${{ matrix.triple }}.zip'
+ unzip 'cargo-make-v${{ env.CI_CARGO_MAKE_VERSION }}-${{ matrix.triple }}.zip'
+ cp cargo-make.exe ~/.cargo/bin/
+ cd ..
+ cargo make --version
+ - name: "Format / Build / Test"
+ run: cargo make ci
+ env:
+ RUST_BACKTRACE: full
+
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ if: github.event_name != 'pull_request'
+ uses: actions/checkout@v3
+ - name: Checkout
+ if: github.event_name == 'pull_request'
+ uses: actions/checkout@v3
+ with:
+ ref: ${{ github.event.pull_request.head.sha }}
+ - name: "Check conventional commits"
+ uses: crate-ci/committed@master
+ with:
+ args: "-vv"
+ commits: "HEAD"
+ - name: "Check typos"
+ uses: crate-ci/typos@master
diff --git a/src/ratatui/.gitignore b/src/ratatui/.gitignore
new file mode 100644
index 00000000..dcb33fbb
--- /dev/null
+++ b/src/ratatui/.gitignore
@@ -0,0 +1,6 @@
+target
+Cargo.lock
+*.log
+*.rs.rustfmt
+.gdb_history
+.idea/
diff --git a/src/ratatui/LICENSE b/src/ratatui/LICENSE
new file mode 100644
index 00000000..7a0657cb
--- /dev/null
+++ b/src/ratatui/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Florian Dehau
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/ratatui/README.md b/src/ratatui/README.md
new file mode 100644
index 00000000..05d4adb6
--- /dev/null
+++ b/src/ratatui/README.md
@@ -0,0 +1,136 @@
+# ratatui
+
+An actively maintained `tui`-rs fork.
+
+[![Build Status](https://github.com/tui-rs-revival/ratatui/workflows/CI/badge.svg)](https://github.com/tui-rs-revival/ratatui/actions?query=workflow%3ACI+)
+[![Crate Status](https://img.shields.io/crates/v/ratatui.svg)](https://crates.io/crates/ratatui)
+[![Docs Status](https://docs.rs/ratatui/badge.svg)](https://docs.rs/crate/ratatui/)
+
+<img src="./assets/demo.gif" alt="Demo cast under Linux Termite with Inconsolata font 12pt">
+
+# Install
+
+```toml
+[dependencies]
+tui = { package = "ratatui" }
+```
+
+# What is this fork?
+
+This fork was created to continue maintenance on the original TUI project. The original maintainer had created an [issue](https://github.com/fdehau/tui-rs/issues/654) explaining how he couldn't find time to continue development, which led to us creating this fork.
+
+With that in mind, **we the community** look forward to continuing the work started by [**Florian Dehau.**](https://github.com/fdehau) :rocket:
+
+In order to organize ourselves, we currently use a [discord server](https://discord.gg/pMCEU9hNEj), feel free to join and come chat ! There are also plans to implement a [matrix](https://matrix.org/) bridge in the near future.
+**Discord is not a MUST to contribute,** we follow a pretty standard github centered open source workflow keeping the most important conversations on github, open an issue or PR and it will be addressed. :smile:
+
+Please make sure you read the updated contributing guidelines, especially if you are interested in working on a PR or issue opened in the previous repository.
+
+# Introduction
+
+`ratatui`-rs is a [Rust](https://www.rust-lang.org) library to build rich terminal
+user interfaces and dashboards. It is heavily inspired by the `Javascript`
+library [blessed-contrib](https://github.com/yaronn/blessed-contrib) and the
+`Go` library [termui](https://github.com/gizak/termui).
+
+The library supports multiple backends:
+
+- [crossterm](https://github.com/crossterm-rs/crossterm) [default]
+- [termion](https://github.com/ticki/termion)
+
+The library is based on the principle of immediate rendering with intermediate
+buffers. This means that at each new frame you should build all widgets that are
+supposed to be part of the UI. While providing a great flexibility for rich and
+interactive UI, this may introduce overhead for highly dynamic content. So, the
+implementation try to minimize the number of ansi escapes sequences generated to
+draw the updated UI. In practice, given the speed of `Rust` the overhead rather
+comes from the terminal emulator than the library itself.
+
+Moreover, the library does not provide any input handling nor any event system and
+you may rely on the previously cited libraries to achieve such features.
+
+## Rust version requirements
+
+Since version 0.17.0, `ratatui` requires **rustc version 1.59.0 or greater**.
+
+# Documentation
+
+The documentation can be found on [docs.rs.](https://docs.rs/ratatui)
+
+# Demo
+
+The demo shown in the gif can be run with all available backends.
+
+```
+# crossterm
+cargo run --example demo --release -- --tick-rate 200
+# termion
+cargo run --example demo --no-default-features --features=termion --release -- --tick-rate 200
+```
+
+where `tick-rate` is the UI refresh rate in ms.
+
+The UI code is in [examples/demo/ui.rs](https://github.com/tui-rs-revival/ratatui/blob/main/examples/demo/ui.rs) while the
+application state is in [examples/demo/app.rs](https://github.com/tui-rs-revival/ratatui/blob/main/examples/demo/app.rs).
+
+If the user interface contains glyphs that are not displayed correctly by your terminal, you may want to run
+the demo without those symbols:
+
+```
+cargo run --example demo --release -- --tick-rate 200 --enhanced-graphics false
+```
+
+# Widgets
+
+## Built in
+
+The library comes with the following list of widgets:
+
+- [Block](https://github.com/tui-rs-revival/ratatui/blob/main/examples/block.rs)
+- [Gauge](https://github.com/tui-rs-revival/ratatui/blob/main/examples/gauge.rs)
+- [Sparkline](https://github.com/tui-rs-revival/ratatui/blob/main/examples/sparkline.rs)
+- [Chart](https://github.com/tui-rs-revival/ratatui/blob/main/examples/chart.rs)
+- [BarChart](https://github.com/tui-rs-revival/ratatui/blob/main/examples/barchart.rs)
+- [List](https://github.com/tui-rs-revival/ratatui/blob/main/examples/list.rs)
+- [Table](https://github.com/tui-rs-revival/ratatui/blob/main/examples/table.rs)
+- [Paragraph](https://github.com/tui-rs-revival/ratatui/blob/main/examples/paragraph.rs)
+- [Canvas (with line, point cloud, map)](https://github.com/tui-rs-revival/ratatui/blob/main/examples/canvas.rs)
+- [Tabs](https://github.com/tui-rs-revival/ratatui/blob/main/examples/tabs.rs)
+
+Click on each item to see the source of the example. Run the examples with with
+cargo (e.g. to run the gauge example `cargo run --example gauge`), and quit by pressing `q`.
+
+You can run all examples by running `cargo make run-examples` (require
+`cargo-make` that can be installed with `cargo install cargo-make`).
+
+### Third-party libraries, bootstrapping templates and widgets
+
+- [ansi-to-tui](https://github.com/uttarayan21/ansi-to-tui) — Convert ansi colored text to `tui::text::Text`
+- [color-to-tui](https://github.com/uttarayan21/color-to-tui) — Parse hex colors to `tui::style::Color`
+- [rust-tui-template](https://github.com/orhun/rust-tui-template) — A template for bootstrapping a Rust TUI application with Tui-rs & crossterm
+- [simple-tui-rs](https://github.com/pmsanford/simple-tui-rs) — A simple example tui-rs app
+- [tui-builder](https://github.com/jkelleyrtp/tui-builder) — Batteries-included MVC framework for Tui-rs + Crossterm apps
+- [tui-clap](https://github.com/kegesch/tui-clap-rs) — Use clap-rs together with Tui-rs
+- [tui-log](https://github.com/kegesch/tui-log-rs) — Example of how to use logging with Tui-rs
+- [tui-logger](https://github.com/gin66/tui-logger) — Logger and Widget for Tui-rs
+- [tui-realm](https://github.com/veeso/tui-realm) — Tui-rs framework to build stateful applications with a React/Elm inspired approach
+- [tui-realm-treeview](https://github.com/veeso/tui-realm-treeview) — Treeview component for Tui-realm
+- [tui tree widget](https://github.com/EdJoPaTo/tui-rs-tree-widget) — Tree Widget for Tui-rs
+- [tui-windows](https://github.com/markatk/tui-windows-rs) — Tui-rs abstraction to handle multiple windows and their rendering
+- [tui-textarea](https://github.com/rhysd/tui-textarea): Simple yet powerful multi-line text editor widget supporting several key shortcuts, undo/redo, text search, etc.
+- [tui-rs-tree-widgets](https://github.com/EdJoPaTo/tui-rs-tree-widget): Widget for tree data structures.
+- [tui-input](https://github.com/sayanarijit/tui-input): TUI input library supporting multiple backends and tui-rs.
+
+# Apps
+
+Check out the list of [close to 40 apps](./APPS.md) using `ratatui`!
+
+# Alternatives
+
+You might want to checkout [Cursive](https://github.com/gyscos/Cursive) for an
+alternative solution to build text user interfaces in Rust.
+
+# License
+
+[MIT](LICENSE)
+
diff --git a/src/ratatui/backend/crossterm.rs b/src/ratatui/backend/crossterm.rs
new file mode 100644
index 00000000..3dceb6ad
--- /dev/null
+++ b/src/ratatui/backend/crossterm.rs
@@ -0,0 +1,241 @@
+use crate::ratatui::{
+ backend::{Backend, ClearType},
+ buffer::Cell,
+ layout::Rect,
+ style::{Color, Modifier},
+};
+use crossterm::{
+ cursor::{Hide, MoveTo, Show},
+ execute, queue,
+ style::{
+ Attribute as CAttribute, Color as CColor, Print, SetAttribute, SetBackgroundColor,
+ SetForegroundColor,
+ },
+ terminal::{self, Clear},
+};
+use std::io::{self, Write};
+
+pub struct CrosstermBackend<W: Write> {
+ buffer: W,
+}
+
+impl<W> CrosstermBackend<W>
+where
+ W: Write,
+{
+ pub fn new(buffer: W) -> CrosstermBackend<W> {
+ CrosstermBackend { buffer }
+ }
+}
+
+impl<W> Write for CrosstermBackend<W>
+where
+ W: Write,
+{
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.buffer.write(buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.buffer.flush()
+ }
+}
+
+impl<W> Backend for CrosstermBackend<W>
+where
+ W: Write,
+{
+ fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
+ where
+ I: Iterator<Item = (u16, u16, &'a Cell)>,
+ {
+ let mut fg = Color::Reset;
+ let mut bg = Color::Reset;
+ let mut modifier = Modifier::empty();
+ let mut last_pos: Option<(u16, u16)> = None;
+ for (x, y, cell) in content {
+ // Move the cursor if the previous location was not (x - 1, y)
+ if !matches!(last_pos, Some(p) if x == p.0 + 1 && y == p.1) {
+ map_error(queue!(self.buffer, MoveTo(x, y)))?;
+ }
+ last_pos = Some((x, y));
+ if cell.modifier != modifier {
+ let diff = ModifierDiff {
+ from: modifier,
+ to: cell.modifier,
+ };
+ diff.queue(&mut self.buffer)?;
+ modifier = cell.modifier;
+ }
+ if cell.fg != fg {
+ let color = CColor::from(cell.fg);
+ map_error(queue!(self.buffer, SetForegroundColor(color)))?;
+ fg = cell.fg;
+ }
+ if cell.bg != bg {
+ let color = CColor::from(cell.bg);
+ map_error(queue!(self.buffer, SetBackgroundColor(color)))?;
+ bg = cell.bg;
+ }
+
+ map_error(queue!(self.buffer, Print(&cell.symbol)))?;
+ }
+
+ map_error(queue!(
+ self.buffer,
+ SetForegroundColor(CColor::Reset),
+ SetBackgroundColor(CColor::Reset),
+ SetAttribute(CAttribute::Reset)
+ ))
+ }
+
+ fn hide_cursor(&mut self) -> io::Result<()> {
+ map_error(execute!(self.buffer, Hide))
+ }
+
+ fn show_cursor(&mut self) -> io::Result<()> {
+ map_error(execute!(self.buffer, Show))
+ }
+
+ fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
+ crossterm::cursor::position()
+ .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))
+ }
+
+ fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
+ map_error(execute!(self.buffer, MoveTo(x, y)))
+ }
+
+ fn clear(&mut self) -> io::Result<()> {
+ self.clear_region(ClearType::All)
+ }
+
+ fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> {
+ map_error(execute!(
+ self.buffer,
+ Clear(match clear_type {
+ ClearType::All => crossterm::terminal::ClearType::All,
+ ClearType::AfterCursor => crossterm::terminal::ClearType::FromCursorDown,
+ ClearType::BeforeCursor => crossterm::terminal::ClearType::FromCursorUp,
+ ClearType::CurrentLine => crossterm::terminal::ClearType::CurrentLine,
+ ClearType::UntilNewLine => crossterm::terminal::ClearType::UntilNewLine,
+ })
+ ))
+ }
+
+ fn append_lines(&mut self, n: u16) -> io::Result<()> {
+ for _ in 0..n {
+ map_error(queue!(self.buffer, Print("\n")))?;
+ }
+ self.buffer.flush()
+ }
+
+ fn size(&self) -> io::Result<Rect> {
+ let (width, height) =
+ terminal::size().map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
+
+ Ok(Rect::new(0, 0, width, height))
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.buffer.flush()
+ }
+}
+
+fn map_error(error: crossterm::Result<()>) -> io::Result<()> {
+ error.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))
+}
+
+impl From<Color> for CColor {
+ fn from(color: Color) -> Self {
+ match color {
+ Color::Reset => CColor::Reset,
+ Color::Black => CColor::Black,
+ Color::Red => CColor::DarkRed,
+ Color::Green => CColor::DarkGreen,
+ Color::Yellow => CColor::DarkYellow,
+ Color::Blue => CColor::DarkBlue,
+ Color::Magenta => CColor::DarkMagenta,
+ Color::Cyan => CColor::DarkCyan,
+ Color::Gray => CColor::Grey,
+ Color::DarkGray => CColor::DarkGrey,
+ Color::LightRed => CColor::Red,
+ Color::LightGreen => CColor::Green,
+ Color::LightBlue => CColor::Blue,
+ Color::LightYellow => CColor::Yellow,
+ Color::LightMagenta => CColor::Magenta,
+ Color::LightCyan => CColor::Cyan,
+ Color::White => CColor::White,
+ Color::Indexed(i) => CColor::AnsiValue(i),
+ Color::Rgb(r, g, b) => CColor::Rgb { r, g, b },
+ }
+ }
+}
+
+#[derive(Debug)]
+struct ModifierDiff {
+ pub from: Modifier,
+ pub to: Modifier,
+}
+
+impl ModifierDiff {
+ fn queue<W>(&self, mut w: W) -> io::Result<()>
+ where
+ W: io::Write,
+ {
+ //use crossterm::Attribute;
+ let removed = self.from - self.to;
+ if removed.contains(Modifier::REVERSED) {
+ map_error(queue!(w, SetAttribute(CAttribute::NoReverse)))?;
+ }
+ if removed.contains(Modifier::BOLD) {
+ map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?;
+ if self.to.contains(Modifier::DIM) {
+ map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;
+ }
+ }
+ if removed.contains(Modifier::ITALIC) {
+ map_error(queue!(w, SetAttribute(CAttribute::NoItalic)))?;
+ }
+ if removed.contains(Modifier::UNDERLINED) {
+ map_error(queue!(w, SetAttribute(CAttribute::NoUnderline)))?;
+ }
+ if removed.contains(Modifier::DIM) {
+ map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?;
+ }
+ if removed.contains(Modifier::CROSSED_OUT) {
+ map_error(queue!(w, SetAttribute(CAttribute::NotCrossedOut)))?;
+ }
+ if removed.contains(Modifier::SLOW_BLINK) || removed.contains(Modifier::RAPID_BLINK) {
+ map_error(queue!(w, SetAttribute(CAttribute::NoBlink)))?;
+ }
+
+ let added = self.to - self.from;
+ if added.contains(Modifier::REVERSED) {
+ map_error(queue!(w, SetAttribute(CAttribute::Reverse)))?;
+ }
+ if added.contains(Modifier::BOLD) {
+ map_error(queue!(w, SetAttribute(CAttribute::Bold)))?;
+ }
+ if added.contains(Modifier::ITALIC) {
+ map_error(queue!(w, SetAttribute(CAttribute::Italic)))?;
+ }
+ if added.contains(Modifier::UNDERLINED) {
+ map_error(queue!(w, SetAttribute(CAttribute::Underlined)))?;
+ }
+ if added.contains(Modifier::DIM) {
+ map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;
+ }
+ if added.contains(Modifier::CROSSED_OUT) {
+ map_error(queue!(w, SetAttribute(CAttribute::CrossedOut)))?;
+ }
+ if added.contains(Modifier::SLOW_BLINK) {
+ map_error(queue!(w, SetAttribute(CAttribute::SlowBlink)))?;
+ }
+ if added.contains(Modifier::RAPID_BLINK) {
+ map_error(queue!(w, SetAttribute(CAttribute::RapidBlink)))?;
+ }
+
+ Ok(())
+ }
+}
diff --git a/src/ratatui/backend/mod.rs b/src/ratatui/backend/mod.rs
new file mode 100644
index 00000000..a360db18
--- /dev/null
+++ b/src/ratatui/backend/mod.rs
@@ -0,0 +1,58 @@
+use std::io;
+
+use crate::ratatui::buffer::Cell;
+use crate::ratatui::layout::Rect;
+
+#[cfg(feature = "termion")]
+mod termion;
+#[cfg(feature = "termion")]
+pub use self::termion::TermionBackend;
+
+mod crossterm;
+pub use self::crossterm::CrosstermBackend;
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum ClearType {
+ All,
+ AfterCursor,
+ BeforeCursor,
+ CurrentLine,
+ UntilNewLine,
+}
+
+pub trait Backend {
+ fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
+ where
+ I: Iterator<Item = (u16, u16, &'a Cell)>;
+
+ /// Insert `n` line breaks to the terminal screen
+ fn append_lines(&mut self, n: u16) -> io::Result<()> {
+ // to get around the unused warning
+ let _n = n;
+ Ok(())
+ }
+
+ fn hide_cursor(&mut self) -> Result<(), io::Error>;
+ fn show_cursor(&mut self) -> Result<(), io::Error>;
+ fn get_cursor(&mut self) -> Result<(u16, u16), io::Error>;
+ fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), io::Error>;
+
+ /// Clears the whole terminal screen
+ fn clear(&mut self) -> Result<(), io::Error>;
+
+ /// Clears a specific region of the terminal specified by the [`ClearType`] parameter
+ fn clear_region(&mut self, clear_type: ClearType) -> Result<(), io::Error> {
+ match clear_type {
+ ClearType::All => self.clear(),
+ ClearType::AfterCursor
+ | ClearType::BeforeCursor
+ | ClearType::CurrentLine
+ | ClearType::UntilNewLine => Err(io::Error::new(
+ io::ErrorKind::Other,
+ format!("clear_type [{clear_type:?}] not supported with this backend"),
+ )),
+ }
+ }
+ fn size(&self) -> Result<Rect, io::Error>;
+ fn flush(&mut self) -> Result<(), io::Error>;
+}
diff --git a/src/ratatui/backend/termion.rs b/src/ratatui/backend/termion.rs
new file mode 100644
index 00000000..76def792
--- /dev/null
+++ b/src/ratatui/backend/termion.rs
@@ -0,0 +1,275 @@
+use crate::{
+ backend::{Backend, ClearType},
+ buffer::Cell,
+ layout::Rect,
+ style::{Color, Modifier},
+};
+use std::{
+ fmt,
+ io::{self, Write},
+};
+
+pub struct TermionBackend<W>
+where
+ W: Write,
+{
+ stdout: W,
+}
+
+impl<W> TermionBackend<W>
+where
+ W: Write,
+{
+ pub fn new(stdout: W) -> TermionBackend<W> {
+ TermionBackend { stdout }
+ }
+}
+
+impl<W> Write for TermionBackend<W>
+where
+ W: Write,
+{
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.stdout.write(buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.stdout.flush()
+ }
+}
+
+impl<W> Backend for TermionBackend<W>
+where
+ W: Write,
+{
+ fn clear(&mut self) -> io::Result<()> {
+ self.clear_region(ClearType::All)
+ }
+
+ fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> {
+ match clear_type {
+ ClearType::All => write!(self.stdout, "{}", termion::clear::All)?,
+ ClearType::AfterCursor => write!(self.stdout, "{}", termion::clear::AfterCursor)?,
+ ClearType::BeforeCursor => write!(self.stdout, "{}", termion::clear::BeforeCursor)?,
+ ClearType::CurrentLine => write!(self.stdout, "{}", termion::clear::CurrentLine)?,
+ ClearType::UntilNewLine => write!(self.stdout, "{}", termion::clear::UntilNewline)?,
+ };
+ self.stdout.flush()
+ }
+
+ fn append_lines(&mut self, n: u16) -> io::Result<()> {
+ for _ in 0..n {
+ writeln!(self.stdout)?;
+ }
+ self.stdout.flush()
+ }
+
+ /// Hides cursor
+ fn hide_cursor(&mut self) -> io::Result<()> {
+ write!(self.stdout, "{}", termion::cursor::Hide)?;
+ self.stdout.flush()
+ }
+
+ /// Shows cursor
+ fn show_cursor(&mut self) -> io::Result<()> {
+ write!(self.stdout, "{}", termion::cursor::Show)?;
+ self.stdout.flush()
+ }
+
+ /// Gets cursor position (0-based index)
+ fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
+ termion::cursor::DetectCursorPos::cursor_pos(&mut self.stdout).map(|(x, y)| (x - 1, y - 1))
+ }
+
+ /// Sets cursor position (0-based index)
+ fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
+ write!(self.stdout, "{}", termion::cursor::Goto(x + 1, y + 1))?;
+ self.stdout.flush()
+ }
+
+ fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
+ where
+ I: Iterator<Item = (u16, u16, &'a Cell)>,
+ {
+ use std::fmt::Write;
+
+ let mut string = String::with_capacity(content.size_hint().0 * 3);
+ let mut fg = Color::Reset;
+ let mut bg = Color::Reset;
+ let mut modifier = Modifier::empty();
+ let mut last_pos: Option<(u16, u16)> = None;
+ for (x, y, cell) in content {
+ // Move the cursor if the previous location was not (x - 1, y)
+ if !matches!(last_pos, Some(p) if x == p.0 + 1 && y == p.1) {
+ write!(string, "{}", termion::cursor::Goto(x + 1, y + 1)).unwrap();
+ }
+ last_pos = Some((x, y));
+ if cell.modifier != modifier {
+ write!(
+ string,
+ "{}",
+ ModifierDiff {
+ from: modifier,
+ to: cell.modifier
+ }
+ )
+ .unwrap();
+ modifier = cell.modifier;
+ }
+ if cell.fg != fg {
+ write!(string, "{}", Fg(cell.fg)).unwrap();
+ fg = cell.fg;
+ }
+ if cell.bg != bg {
+ write!(string, "{}", Bg(cell.bg)).unwrap();
+ bg = cell.bg;
+ }
+ string.push_str(&cell.symbol);
+ }
+ write!(
+ self.stdout,
+ "{}{}{}{}",
+ string,
+ Fg(Color::Reset),
+ Bg(Color::Reset),
+ termion::style::Reset,
+ )
+ }
+
+ /// Return the size of the terminal
+ fn size(&self) -> io::Result<Rect> {
+ let terminal = termion::terminal_size()?;
+ Ok(Rect::new(0, 0, terminal.0, terminal.1))
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.stdout.flush()
+ }
+}
+
+struct Fg(Color);
+
+struct Bg(Color);
+
+struct ModifierDiff {
+ from: Modifier,
+ to: Modifier,
+}
+
+impl fmt::Display for Fg {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use termion::color::Color as TermionColor;
+ match self.0 {
+ Color::Reset => termion::color::Reset.write_fg(f),
+ Color::Black => termion::color::Black.write_fg(f),
+ Color::Red => termion::color::Red.write_fg(f),
+ Color::Green => termion::color::Green.write_fg(f),
+ Color::Yellow => termion::color::Yellow.write_fg(f),
+ Color::Blue => termion::color::Blue.write_fg(f),
+ Color::Magenta => termion::color::Magenta.write_fg(f),
+ Color::Cyan => termion::color::Cyan.write_fg(f),
+ Color::Gray => termion::color::White.write_fg(f),
+ Color::DarkGray => termion::color::LightBlack.write_fg(f),
+ Color::LightRed => termion::color::LightRed.write_fg(f),
+ Color::LightGreen => termion::color::LightGreen.write_fg(f),
+ Color::LightBlue => termion::color::LightBlue.write_fg(f),
+ Color::LightYellow => termion::color::LightYellow.write_fg(f),
+ Color::LightMagenta => termion::color::LightMagenta.write_fg(f),
+ Color::LightCyan => termion::color::LightCyan.write_fg(f),
+ Color::White => termion::color::LightWhite.write_fg(f),
+ Color::Indexed(i) => termion::color::AnsiValue(i).write_fg(f),
+ Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_fg(f),
+ }
+ }
+}
+impl fmt::Display for Bg {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use termion::color::Color as TermionColor;
+ match self.0 {
+ Color::Reset => termion::color::Reset.write_bg(f),
+ Color::Black => termion::color::Black.write_bg(f),
+ Color::Red => termion::color::Red.write_bg(f),
+ Color::Green => termion::color::Green.write_bg(f),
+ Color::Yellow => termion::color::Yellow.write_bg(f),
+ Color::Blue => termion::color::Blue.write_bg(f),
+ Color::Magenta => termion::color::Magenta.write_bg(f),
+ Color::Cyan => termion::color::Cyan.write_bg(f),
+ Color::Gray => termion::color::White.write_bg(f),
+ Color::DarkGray => termion::color::LightBlack.write_bg(f),
+ Color::LightRed => termion::color::LightRed.write_bg(f),
+ Color::LightGreen => termion::color::LightGreen.write_bg(f),
+ Color::LightBlue => termion::color::LightBlue.write_bg(f),
+ Color::LightYellow => termion::color::LightYellow.write_bg(f),
+ Color::LightMagenta => termion::color::LightMagenta.write_bg(f),
+ Color::LightCyan => termion::color::LightCyan.write_bg(f),
+ Color::White => termion::color::LightWhite.write_bg(f),
+ Color::Indexed(i) => termion::color::AnsiValue(i).write_bg(f),
+ Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_bg(f),
+ }
+ }
+}
+
+impl fmt::Display for ModifierDiff {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let remove = self.from - self.to;
+ if remove.contains(Modifier::REVERSED) {
+ write!(f, "{}", termion::style::NoInvert)?;
+ }
+ if remove.contains(Modifier::BOLD) {
+ // XXX: the termion NoBold flag actually enables double-underline on ECMA-48 compliant
+ // terminals, and NoFaint additionally disables bold... so we use this trick to get
+ // the right semantics.
+ write!(f, "{}", termion::style::NoFaint)?;
+
+ if self.to.contains(Modifier::DIM) {
+ write!(f, "{}", termion::style::Faint)?;
+ }
+ }
+ if remove.contains(Modifier::ITALIC) {
+ write!(f, "{}", termion::style::NoItalic)?;
+ }
+ if remove.contains(Modifier::UNDERLINED) {
+ write!(f, "{}", termion::style::NoUnderline)?;
+ }
+ if remove.contains(Modifier::DIM) {
+ write!(f, "{}", termion::style::NoFaint)?;
+
+ // XXX: the NoFaint flag additionally disables bold as well, so we need to re-enable it
+ // here if we want it.
+ if self.to.contains(Modifier::BOLD) {
+ write!(f, "{}", termion::style::Bold)?;
+ }
+ }
+ if remove.contains(Modifier::CROSSED_OUT) {
+ write!(f, "{}", termion::style::NoCrossedOut)?;
+ }
+ if remove.contains(Modifier::SLOW_BLINK) || remove.contains(Modifier::RAPID_BLINK) {
+ write!(f, "{}", termion::style::NoBlink)?;
+ }
+
+ let add = self.to - self.from;
+ if add.contains(Modifier::REVERSED) {
+ write!(f, "{}", termion::style::Invert)?;
+ }
+ if add.contains(Modifier::BOLD) {
+ write!(f, "{}", termion::style::Bold)?;
+ }
+ if add.contains(Modifier::ITALIC) {
+ write!(f, "{}", termion::style::Italic)?;
+ }
+ if add.contains(Modifier::UNDERLINED) {
+ write!(f, "{}", termion::style::Underline)?;
+ }
+ if add.contains(Modifier::DIM) {
+ write!(f, "{}", termion::style::Faint)?;
+ }
+ if add.contains(Modifier::CROSSED_OUT) {
+ write!(f, "{}", termion::style::CrossedOut)?;
+ }
+ if add.contains(Modifier::SLOW_BLINK) || add.contains(Modifier::RAPID_BLINK) {
+ write!(f, "{}", termion::style::Blink)?;
+ }
+
+ Ok(())
+ }
+}
diff --git a/src/ratatui/buffer.rs b/src/ratatui/buffer.rs
new file mode 100644
index 00000000..b2a988b7
--- /dev/null
+++ b/src/ratatui/buffer.rs
@@ -0,0 +1,736 @@
+use crate::ratatui::{
+ layout::Rect,
+ style::{Color, Modifier, Style},
+ text::{Span, Spans},
+};
+use std::cmp::min;
+use unicode_segmentation::UnicodeSegmentation;
+use unicode_width::UnicodeWidthStr;
+
+/// A buffer cell
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Cell {
+ pub symbol: String,
+ pub fg: Color,
+ pub bg: Color,
+ pub modifier: Modifier,
+}
+
+impl Cell {
+ pub fn set_symbol(&mut self, symbol: &str) -> &mut Cell {
+ self.symbol.clear();
+ self.symbol.push_str(symbol);
+ self
+ }
+
+ pub fn set_char(&mut self, ch: char) -> &mut Cell {
+ self.symbol.clear();
+ self.symbol.push(ch);
+ self
+ }
+
+ pub fn set_fg(&mut self, color: Color) -> &mut Cell {
+ self.fg = color;
+ self
+ }
+
+ pub fn set_bg(&mut self, color: Color) -> &mut Cell {
+ self.bg = color;
+ self
+ }
+
+ pub fn set_style(&mut self, style: Style) -> &mut Cell {
+ if let Some(c) = style.fg {
+ self.fg = c;
+ }
+ if let Some(c) = style.bg {
+ self.bg = c;
+ }
+ self.modifier.insert(style.add_modifier);
+ self.modifier.remove(style.sub_modifier);
+ self
+ }
+
+ pub fn style(&self) -> Style {
+ Style::default()
+ .fg(self.fg)
+ .bg(self.bg)
+ .add_modifier(self.modifier)
+ }
+
+ pub fn reset(&mut self) {
+ self.symbol.clear();
+ self.symbol.push(' ');
+ self.fg = Color::Reset;
+ self.bg = Color::Reset;
+ self.modifier = Modifier::empty();
+ }
+}
+
+impl Default for Cell {
+ fn default() -> Cell {
+ Cell {
+ symbol: " ".into(),
+ fg: Color::Reset,
+ bg: Color::Reset,
+ modifier: Modifier::empty(),
+ }
+ }
+}
+
+/// A buffer that maps to the desired content of the terminal after the draw call
+///
+/// No widget in the library interacts directly with the terminal. Instead each of them is required
+/// to draw their state to an intermediate buffer. It is basically a grid where each cell contains
+/// a grapheme, a foreground color and a background color. This grid will then be used to output
+/// the appropriate escape sequences and characters to draw the UI as the user has defined it.
+///
+/// # Examples:
+///
+/// ```
+/// use ratatui::buffer::{Buffer, Cell};
+/// use ratatui::layout::Rect;
+/// use ratatui::style::{Color, Style, Modifier};
+///
+/// let mut buf = Buffer::empty(Rect{x: 0, y: 0, width: 10, height: 5});
+/// buf.get_mut(0, 2).set_symbol("x");
+/// assert_eq!(buf.get(0, 2).symbol, "x");
+/// buf.set_string(3, 0, "string", Style::default().fg(Color::Red).bg(Color::White));
+/// assert_eq!(buf.get(5, 0), &Cell{
+/// symbol: String::from("r"),
+/// fg: Color::Red,
+/// bg: Color::White,
+/// modifier: Modifier::empty()
+/// });
+/// buf.get_mut(5, 0).set_char('x');
+/// assert_eq!(buf.get(5, 0).symbol, "x");
+/// ```
+#[derive(Debug, Clone, PartialEq, Eq, Default)]
+pub struct Buffer {
+ /// The area represented by this buffer
+ pub area: Rect,
+ /// The content of the buffer. The length of this Vec should always be equal to area.width *
+ /// area.height
+ pub content: Vec<Cell>,
+}
+
+impl Buffer {
+ /// Returns a Buffer with all cells set to the default one
+ pub fn empty(area: Rect) -> Buffer {
+ let cell: Cell = Default::default();
+ Buffer::filled(area, &cell)
+ }
+
+ /// Returns a Buffer with all cells initialized with the attributes of the given Cell
+ pub fn filled(area: Rect, cell: &Cell) -> Buffer {
+ let size = area.area() as usize;
+ let mut content = Vec::with_capacity(size);
+ for _ in 0..size {
+ content.push(cell.clone());
+ }
+ Buffer { area, content }
+ }
+
+ /// Returns a Buffer containing the given lines
+ pub fn with_lines<S>(lines: Vec<S>) -> Buffer
+ where
+ S: AsRef<str>,
+ {
+ let height = lines.len() as u16;
+ let width = lines
+ .iter()
+ .map(|i| i.as_ref().width() as u16)
+ .max()
+ .unwrap_or_default();
+ let mut buffer = Buffer::empty(Rect {
+ x: 0,
+ y: 0,
+ width,
+ height,
+ });
+ for (y, line) in lines.iter().enumerate() {
+ buffer.set_string(0, y as u16, line, Style::default());
+ }
+ buffer
+ }
+
+ /// Returns the content of the buffer as a slice
+ pub fn content(&self) -> &[Cell] {
+ &self.content
+ }
+
+ /// Returns the area covered by this buffer
+ pub fn area(&self) -> &Rect {
+ &self.area
+ }
+
+ /// Returns a reference to Cell at the given coordinates
+ pub fn get(&self, x: u16, y: u16) -> &Cell {
+ let i = self.index_of(x, y);
+ &self.content[i]
+ }
+
+ /// Returns a mutable reference to Cell at the given coordinates
+ pub fn get_mut(&mut self, x: u16, y: u16) -> &mut Cell {
+ let i = self.index_of(x, y);
+ &mut self.content[i]
+ }
+
+ /// Returns the index in the `Vec<Cell>` for the given global (x, y) coordinates.
+ ///
+ /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use ratatui::buffer::Buffer;
+ /// # use ratatui::layout::Rect;
+ /// let rect = Rect::new(200, 100, 10, 10);
+ /// let buffer = Buffer::empty(rect);
+ /// // Global coordinates to the top corner of this buffer's area
+ /// assert_eq!(buffer.index_of(200, 100), 0);
+ /// ```
+ ///
+ /// # Panics
+ ///
+ /// Panics when given an coordinate that is outside of this Buffer's area.
+ ///
+ /// ```should_panic
+ /// # use ratatui::buffer::Buffer;
+ /// # use ratatui::layout::Rect;
+ /// let rect = Rect::new(200, 100, 10, 10);
+ /// let buffer = Buffer::empty(rect);
+ /// // Top coordinate is outside of the buffer in global coordinate space, as the Buffer's area
+ /// // starts at (200, 100).
+ /// buffer.index_of(0, 0); // Panics
+ /// ```
+ pub fn index_of(&self, x: u16, y: u16) -> usize {
+ debug_assert!(
+ x >= self.area.left()
+ && x < self.area.right()
+ && y >= self.area.top()
+ && y < self.area.bottom(),
+ "Trying to access position outside the buffer: x={}, y={}, area={:?}",
+ x,
+ y,
+ self.area
+ );
+ ((y - self.area.y) * self.area.width + (x - self.area.x)) as usize
+ }
+
+ /// Returns the (global) coordinates of a cell given its index
+ ///
+ /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use ratatui::buffer::Buffer;
+ /// # use ratatui::layout::Rect;
+ /// let rect = Rect::new(200, 100, 10, 10);
+ /// let buffer = Buffer::empty(rect);
+ /// assert_eq!(buffer.pos_of(0), (200, 100));
+ /// assert_eq!(buffer.pos_of(14), (204, 101));
+ /// ```
+ ///
+ /// # Panics
+ ///
+ /// Panics when given an index that is outside the Buffer's content.
+ ///
+ /// ```should_panic
+ /// # use ratatui::buffer::Buffer;
+ /// # use ratatui::layout::Rect;
+ /// let rect = Rect::new(0, 0, 10, 10); // 100 cells in total
+ /// let buffer = Buffer::empty(rect);
+ /// // Index 100 is the 101th cell, which lies outside of the area of this Buffer.
+ /// buffer.pos_of(100); // Panics
+ /// ```
+ pub fn pos_of(&self, i: usize) -> (u16, u16) {
+ debug_assert!(
+ i < self.content.len(),
+ "Trying to get the coords of a cell outside the buffer: i={} len={}",
+ i,
+ self.content.len()
+ );
+ (
+ self.area.x + i as u16 % self.area.width,
+ self.area.y + i as u16 / self.area.width,
+ )
+ }
+
+ /// Print a string, starting at the position (x, y)
+ pub fn set_string<S>(&mut self, x: u16, y: u16, string: S, style: Style)
+ where
+ S: AsRef<str>,
+ {
+ self.set_stringn(x, y, string, usize::MAX, style);
+ }
+
+ /// Print at most the first n characters of a string if enough space is available
+ /// until the end of the line
+ pub fn set_stringn<S>(
+ &mut self,
+ x: u16,
+ y: u16,
+ string: S,
+ width: usize,
+ style: Style,
+ ) -> (u16, u16)
+ where
+ S: AsRef<str>,
+ {
+ let mut index = self.index_of(x, y);
+ let mut x_offset = x as usize;
+ let graphemes = UnicodeSegmentation::graphemes(string.as_ref(), true);
+ let max_offset = min(self.area.right() as usize, width.saturating_add(x as usize));
+ for s in graphemes {
+ let width = s.width();
+ if width == 0 {
+ continue;
+ }
+ // `x_offset + width > max_offset` could be integer overflow on 32-bit machines if we
+ // change dimensions to usize or u32 and someone resizes the terminal to 1x2^32.
+ if width > max_offset.saturating_sub(x_offset) {
+ break;
+ }
+
+ self.content[index].set_symbol(s);
+ self.content[index].set_style(style);
+ // Reset following cells if multi-width (they would be hidden by the grapheme),
+ for i in index + 1..index + width {
+ self.content[i].reset();
+ }
+ index += width;
+ x_offset += width;
+ }
+ (x_offset as u16, y)
+ }
+
+ pub fn set_spans(&mut self, x: u16, y: u16, spans: &Spans<'_>, width: u16) -> (u16, u16) {
+ let mut remaining_width = width;
+ let mut x = x;
+ for span in &spans.0 {
+ if remaining_width == 0 {
+ break;
+ }
+ let pos = self.set_stringn(
+ x,
+ y,
+ span.content.as_ref(),
+ remaining_width as usize,
+ span.style,
+ );
+ let w = pos.0.saturating_sub(x);
+ x = pos.0;
+ remaining_width = remaining_width.saturating_sub(w);
+ }
+ (x, y)
+ }
+
+ pub fn set_span(&mut self, x: u16, y: u16, span: &Span<'_>, width: u16) -> (u16, u16) {
+ self.set_stringn(x, y, span.content.as_ref(), width as usize, span.style)
+ }
+
+ #[deprecated(
+ since = "0.10.0",
+ note = "You should use styling capabilities of `Buffer::set_style`"
+ )]
+ pub fn set_background(&mut self, area: Rect, color: Color) {
+ for y in area.top()..area.bottom() {
+ for x in area.left()..area.right() {
+ self.get_mut(x, y).set_bg(color);
+ }
+ }
+ }
+
+ pub fn set_style(&mut self, area: Rect, style: Style) {
+ for y in area.top()..area.bottom() {
+ for x in area.left()..area.right() {
+ self.get_mut(x, y).set_style(style);
+ }
+ }
+ }
+
+ /// Resize the buffer so that the mapped area matches the given area and that the buffer
+ /// length is equal to area.width * area.height
+ pub fn resize(&mut self, area: Rect) {
+ let length = area.area() as usize;
+ if self.content.len() > length {
+ self.content.truncate(length);
+ } else {
+ self.content.resize(length, Default::default());
+ }
+ self.area = area;
+ }
+
+ /// Reset all cells in the buffer
+ pub fn reset(&mut self) {
+ for c in &mut self.content {
+ c.reset();
+ }
+ }
+
+ /// Merge an other buffer into this one
+ pub fn merge(&mut self, other: &Buffer) {
+ let area = self.area.union(other.area);
+ let cell: Cell = Default::default();
+ self.content.resize(area.area() as usize, cell.clone());
+
+ // Move original content to the appropriate space
+ let size = self.area.area() as usize;
+ for i in (0..size).rev() {
+ let (x, y) = self.pos_of(i);
+ // New index in content
+ let k = ((y - area.y) * area.width + x - area.x) as usize;
+ if i != k {
+ self.content[k] = self.content[i].clone();
+ self.content[i] = cell.clone();
+ }
+ }
+
+ // Push content of the other buffer into this one (may erase previous
+ // data)
+ let size = other.area.area() as usize;
+ for i in 0..size {
+ let (x, y) = other.pos_of(i);
+ // New index in content
+ let k = ((y - area.y) * area.width + x - area.x) as usize;
+ self.content[k] = other.content[i].clone();
+ }
+ self.area = area;
+ }
+
+ /// Builds a minimal sequence of coordinates and Cells necessary to update the UI from
+ /// self to other.
+ ///
+ /// We're assuming that buffers are well-formed, that is no double-width cell is followed by
+ /// a non-blank cell.
+ ///
+ /// # Multi-width characters handling:
+ ///
+ /// ```text
+ /// (Index:) `01`
+ /// Prev: `コ`
+ /// Next: `aa`
+ /// Updates: `0: a, 1: a'
+ /// ```
+ ///
+ /// ```text
+ /// (Index:) `01`
+ /// Prev: `a `
+ /// Next: `コ`
+ /// Updates: `0: コ` (double width symbol at index 0 - skip index 1)
+ /// ```
+ ///
+ /// ```text
+ /// (Index:) `012`
+ /// Prev: `aaa`
+ /// Next: `aコ`
+ /// Updates: `0: a, 1: コ` (double width symbol at index 1 - skip index 2)
+ /// ```
+ pub fn diff<'a>(&self, other: &'a Buffer) -> Vec<(u16, u16, &'a Cell)> {
+ let previous_buffer = &self.content;
+ let next_buffer = &other.content;
+
+ let mut updates: Vec<(u16, u16, &Cell)> = vec![];
+ // Cells invalidated by drawing/replacing preceding multi-width characters:
+ let mut invalidated: usize = 0;
+ // Cells from the current buffer to skip due to preceding multi-width characters taking their
+ // place (the skipped cells should be blank anyway):
+ let mut to_skip: usize = 0;
+ for (i, (current, previous)) in next_buffer.iter().zip(previous_buffer.iter()).enumerate() {
+ if (current != previous || invalidated > 0) && to_skip == 0 {
+ let (x, y) = self.pos_of(i);
+ updates.push((x, y, &next_buffer[i]));
+ }
+
+ to_skip = current.symbol.width().saturating_sub(1);
+
+ let affected_width = std::cmp::max(current.symbol.width(), previous.symbol.width());
+ invalidated = std::cmp::max(affected_width, invalidated).saturating_sub(1);
+ }
+ updates
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn cell(s: &str) -> Cell {
+ let mut cell = Cell::default();
+ cell.set_symbol(s);
+ cell
+ }
+
+ #[test]
+ fn it_translates_to_and_from_coordinates() {
+ let rect = Rect::new(200, 100, 50, 80);
+ let buf = Buffer::empty(rect);
+
+ // First cell is at the upper left corner.
+ assert_eq!(buf.pos_of(0), (200, 100));
+ assert_eq!(buf.index_of(200, 100), 0);
+
+ // Last cell is in the lower right.
+ assert_eq!(buf.pos_of(buf.content.len() - 1), (249, 179));
+ assert_eq!(buf.index_of(249, 179), buf.content.len() - 1);
+ }
+
+ #[test]
+ #[ignore]
+ #[should_panic(expected = "outside the buffer")]
+ fn pos_of_panics_on_out_of_bounds() {
+ let rect = Rect::new(0, 0, 10, 10);
+ let buf = Buffer::empty(rect);
+
+ // There are a total of 100 cells; zero-indexed means that 100 would be the 101st cell.
+ buf.pos_of(100);
+ }
+
+ #[test]
+ #[ignore]
+ #[should_panic(expected = "outside the buffer")]
+ fn index_of_panics_on_out_of_bounds() {
+ let rect = Rect::new(0, 0, 10, 10);
+ let buf = Buffer::empty(rect);
+
+ // width is 10; zero-indexed means that 10 would be the 11th cell.
+ buf.index_of(10, 0);
+ }
+
+ #[test]
+ fn buffer_set_string() {
+ let area = Rect::new(0, 0, 5, 1);
+ let mut buffer = Buffer::empty(area);
+
+ // Zero-width
+ buffer.set_stringn(0, 0, "aaa", 0, Style::default());
+ assert_eq!(buffer, Buffer::with_lines(vec![" "]));
+
+ buffer.set_string(0, 0, "aaa", Style::default());
+ assert_eq!(buffer, Buffer::with_lines(vec!["aaa "]));
+
+ // Width limit:
+ buffer.set_stringn(0, 0, "bbbbbbbbbbbbbb", 4, Style::default());
+ assert_eq!(buffer, Buffer::with_lines(vec!["bbbb "]));
+
+ buffer.set_string(0, 0, "12345", Style::default());
+ assert_eq!(buffer, Buffer::with_lines(vec!["12345"]));
+
+ // Width truncation:
+ buffer.set_string(0, 0, "123456", Style::default());
+ assert_eq!(buffer, Buffer::with_lines(vec!["12345"]));
+ }
+
+ #[test]
+ fn buffer_set_string_zero_width() {
+ let area = Rect::new(0, 0, 1, 1);
+ let mut buffer = Buffer::empty(area);
+
+ // Leading grapheme with zero width
+ let s = "\u{1}a";
+ buffer.set_stringn(0, 0, s, 1, Style::default());
+ assert_eq!(buffer, Buffer::with_lines(vec!["a"]));
+
+ // Trailing grapheme with zero with
+ let s = "a\u{1}";
+ buffer.set_stringn(0, 0, s, 1, Style::default());
+ assert_eq!(buffer, Buffer::with_lines(vec!["a"]));
+ }
+
+ #[test]
+ fn buffer_set_string_double_width() {
+ let area = Rect::new(0, 0, 5, 1);
+ let mut buffer = Buffer::empty(area);
+ buffer.set_string(0, 0, "コン", Style::default());
+ assert_eq!(buffer, Buffer::with_lines(vec!["コン "]));
+
+ // Only 1 space left.
+ buffer.set_string(0, 0, "コンピ", Style::default());
+ assert_eq!(buffer, Buffer::with_lines(vec!["コン "]));
+ }
+
+ #[test]
+ fn buffer_with_lines() {
+ let buffer =
+ Buffer::with_lines(vec!["┌────────┐", "│コンピュ│", "│ーa 上で│", "└────────┘"]);
+ assert_eq!(buffer.area.x, 0);
+ assert_eq!(buffer.area.y, 0);
+ assert_eq!(buffer.area.width, 10);
+ assert_eq!(buffer.area.height, 4);
+ }
+
+ #[test]
+ fn buffer_diffing_empty_empty() {
+ let area = Rect::new(0, 0, 40, 40);
+ let prev = Buffer::empty(area);
+ let next = Buffer::empty(area);
+ let diff = prev.diff(&next);
+ assert_eq!(diff, vec![]);
+ }
+
+ #[test]
+ fn buffer_diffing_empty_filled() {
+ let area = Rect::new(0, 0, 40, 40);
+ let prev = Buffer::empty(area);
+ let next = Buffer::filled(area, Cell::default().set_symbol("a"));
+ let diff = prev.diff(&next);
+ assert_eq!(diff.len(), 40 * 40);
+ }
+
+ #[test]
+ fn buffer_diffing_filled_filled() {
+ let area = Rect::new(0, 0, 40, 40);
+ let prev = Buffer::filled(area, Cell::default().set_symbol("a"));
+ let next = Buffer::filled(area, Cell::default().set_symbol("a"));
+ let diff = prev.diff(&next);
+ assert_eq!(diff, vec![]);
+ }
+
+ #[test]
+ fn buffer_diffing_single_width() {
+ let prev = Buffer::with_lines(vec![
+ " ",
+ "┌Title─┐ ",
+ "│ │ ",
+ "│ │ ",
+ "└──────┘ ",
+ ]);
+ let next = Buffer::with_lines(vec![
+ " ",
+ "┌TITLE─┐ ",
+ "│ │ ",
+ "│ │ ",
+ "└──────┘ ",
+ ]);
+ let diff = prev.diff(&next);
+ assert_eq!(
+ diff,
+ vec![
+ (2, 1, &cell("I")),
+ (3, 1, &cell("T")),
+ (4, 1, &cell("L")),
+ (5, 1, &cell("E")),
+ ]
+ );
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn buffer_diffing_multi_width() {
+ let prev = Buffer::with_lines(vec![
+ "┌Title─┐ ",
+ "└──────┘ ",
+ ]);
+ let next = Buffer::with_lines(vec![
+ "┌称号──┐ ",
+ "└──────┘ ",
+ ]);
+ let diff = prev.diff(&next);
+ assert_eq!(
+ diff,
+ vec![
+ (1, 0, &cell("称")),
+ // Skipped "i"
+ (3, 0, &cell("号")),
+ // Skipped "l"
+ (5, 0, &cell("─")),
+ ]
+ );
+ }
+
+ #[test]
+ fn buffer_diffing_multi_width_offset() {
+ let prev = Buffer::with_lines(vec!["┌称号──┐"]);
+ let next = Buffer::with_lines(vec!["┌─称号─┐"]);
+
+ let diff = prev.diff(&next);
+ assert_eq!(
+ diff,
+ vec![(1, 0, &cell("─")), (2, 0, &cell("称")), (4, 0, &cell("号")),]
+ );
+ }
+
+ #[test]
+ fn buffer_merge() {
+ let mut one = Buffer::filled(
+ Rect {
+ x: 0,
+ y: 0,
+ width: 2,
+ height: 2,
+ },
+ Cell::default().set_symbol("1"),
+ );
+ let two = Buffer::filled(
+ Rect {
+ x: 0,
+ y: 2,
+ width: 2,
+ height: 2,
+ },
+ Cell::default().set_symbol("2"),
+ );
+ one.merge(&two);
+ assert_eq!(one, Buffer::with_lines(vec!["11", "11", "22", "22"]));
+ }
+
+ #[test]
+ fn buffer_merge2() {
+ let mut one = Buffer::filled(
+ Rect {
+ x: 2,
+ y: 2,
+ width: 2,
+ height: 2,
+ },
+ Cell::default().set_symbol("1"),
+ );
+ let two = Buffer::filled(
+ Rect {
+ x: 0,
+ y: 0,
+ width: 2,
+ height: 2,
+ },
+ Cell::default().set_symbol("2"),
+ );
+ one.merge(&two);
+ assert_eq!(
+ one,
+ Buffer::with_lines(vec!["22 ", "22 ", " 11", " 11"])
+ );
+ }
+
+ #[test]
+ fn buffer_merge3() {
+ let mut one = Buffer::filled(
+ Rect {
+ x: 3,
+ y: 3,
+ width: 2,
+ height: 2,
+ },
+ Cell::default().set_symbol("1"),
+ );
+ let two = Buffer::filled(
+ Rect {
+ x: 1,
+ y: 1,
+ width: 3,
+ height: 4,
+ },
+ Cell::default().set_symbol("2"),
+ );
+ one.merge(&two);
+ let mut merged = Buffer::with_lines(vec!["222 ", "222 ", "2221", "2221"]);
+ merged.area = Rect {
+ x: 1,
+ y: 1,
+ width: 4,
+ height: 4,
+ };
+ assert_eq!(one, merged);
+ }
+}
diff --git a/src/ratatui/layout.rs b/src/ratatui/layout.rs
new file mode 100644
index 00000000..f5b14e35
--- /dev/null
+++ b/src/ratatui/layout.rs
@@ -0,0 +1,560 @@
+use std::cell::RefCell;
+use std::cmp::{max, min};
+use std::collections::HashMap;
+use std::rc::Rc;
+
+use cassowary::strength::{MEDIUM, REQUIRED, WEAK};
+use cassowary::WeightedRelation::*;
+use cassowary::{Constraint as CassowaryConstraint, Expression, Solver, Variable};
+
+#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
+pub enum Corner {
+ TopLeft,
+ TopRight,
+ BottomRight,
+ BottomLeft,
+}
+
+#[derive(Debug, Hash, Clone, PartialEq, Eq)]
+pub enum Direction {
+ Horizontal,
+ Vertical,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum Constraint {
+ // TODO: enforce range 0 - 100
+ Percentage(u16),
+ Ratio(u32, u32),
+ Length(u16),
+ Max(u16),
+ Min(u16),
+}
+
+impl Constraint {
+ pub fn apply(&self, length: u16) -> u16 {
+ match *self {
+ Constraint::Percentage(p) => length * p / 100,
+ Constraint::Ratio(num, den) => {
+ let r = num * u32::from(length) / den;
+ r as u16
+ }
+ Constraint::Length(l) => length.min(l),
+ Constraint::Max(m) => length.min(m),
+ Constraint::Min(m) => length.max(m),
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Margin {
+ pub vertical: u16,
+ pub horizontal: u16,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Alignment {
+ Left,
+ Center,
+ Right,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Layout {
+ direction: Direction,
+ margin: Margin,
+ constraints: Vec<Constraint>,
+ /// Whether the last chunk of the computed layout should be expanded to fill the available
+ /// space.
+ expand_to_fill: bool,
+}
+
+type Cache = HashMap<(Rect, Layout), Rc<[Rect]>>;
+thread_local! {
+ static LAYOUT_CACHE: RefCell<Cache> = RefCell::new(HashMap::new());
+}
+
+impl Default for Layout {
+ fn default() -> Layout {
+ Layout {
+ direction: Direction::Vertical,
+ margin: Margin {
+ horizontal: 0,
+ vertical: 0,
+ },
+ constraints: Vec::new(),
+ expand_to_fill: true,
+ }
+ }
+}
+
+impl Layout {
+ pub fn constraints<C>(mut self, constraints: C) -> Layout
+ where
+ C: Into<Vec<Constraint>>,
+ {
+ self.constraints = constraints.into();
+ self
+ }
+
+ pub fn margin(mut self, margin: u16) -> Layout {
+ self.margin = Margin {
+ horizontal: margin,
+ vertical: margin,
+ };
+ self
+ }
+
+ pub fn horizontal_margin(mut self, horizontal: u16) -> Layout {
+ self.margin.horizontal = horizontal;
+ self
+ }
+
+ pub fn vertical_margin(mut self, vertical: u16) -> Layout {
+ self.margin.vertical = vertical;
+ self
+ }
+
+ pub fn direction(mut self, direction: Direction) -> Layout {
+ self.direction = direction;
+ self
+ }
+
+ pub(crate) fn expand_to_fill(mut self, expand_to_fill: bool) -> Layout {
+ self.expand_to_fill = expand_to_fill;
+ self
+ }
+
+ /// Wrapper function around the cassowary-rs solver to be able to split a given
+ /// area into smaller ones based on the preferred widths or heights and the direction.
+ ///
+ /// # Examples
+ /// ```
+ /// # use ratatui::layout::{Rect, Constraint, Direction, Layout};
+ /// let chunks = Layout::default()
+ /// .direction(Direction::Vertical)
+ /// .constraints([Constraint::Length(5), Constraint::Min(0)].as_ref())
+ /// .split(Rect {
+ /// x: 2,
+ /// y: 2,
+ /// width: 10,
+ /// height: 10,
+ /// });
+ /// assert_eq!(
+ /// chunks[..],
+ /// [
+ /// Rect {
+ /// x: 2,
+ /// y: 2,
+ /// width: 10,
+ /// height: 5
+ /// },
+ /// Rect {
+ /// x: 2,
+ /// y: 7,
+ /// width: 10,
+ /// height: 5
+ /// }
+ /// ]
+ /// );
+ ///
+ /// let chunks = Layout::default()
+ /// .direction(Direction::Horizontal)
+ /// .constraints([Constraint::Ratio(1, 3), Constraint::Ratio(2, 3)].as_ref())
+ /// .split(Rect {
+ /// x: 0,
+ /// y: 0,
+ /// width: 9,
+ /// height: 2,
+ /// });
+ /// assert_eq!(
+ /// chunks[..],
+ /// [
+ /// Rect {
+ /// x: 0,
+ /// y: 0,
+ /// width: 3,
+ /// height: 2
+ /// },
+ /// Rect {
+ /// x: 3,
+ /// y: 0,
+ /// width: 6,
+ /// height: 2
+ /// }
+ /// ]
+ /// );
+ /// ```
+ pub fn split(&self, area: Rect) -> Rc<[Rect]> {
+ // TODO: Maybe use a fixed size cache ?
+ LAYOUT_CACHE.with(|c| {
+ c.borrow_mut()
+ .entry((area, self.clone()))
+ .or_insert_with(|| split(area, self))
+ .clone()
+ })
+ }
+}
+
+fn split(area: Rect, layout: &Layout) -> Rc<[Rect]> {
+ let mut solver = Solver::new();
+ let mut vars: HashMap<Variable, (usize, usize)> = HashMap::new();
+ let elements = layout
+ .constraints
+ .iter()
+ .map(|_| Element::new())
+ .collect::<Vec<Element>>();
+ let mut res = layout
+ .constraints
+ .iter()
+ .map(|_| Rect::default())
+ .collect::<Rc<[Rect]>>();
+
+ let mut results = Rc::get_mut(&mut res).expect("newly created Rc should have no shared refs");
+
+ let dest_area = area.inner(&layout.margin);
+ for (i, e) in elements.iter().enumerate() {
+ vars.insert(e.x, (i, 0));
+ vars.insert(e.y, (i, 1));
+ vars.insert(e.width, (i, 2));
+ vars.insert(e.height, (i, 3));
+ }
+ let mut ccs: Vec<CassowaryConstraint> =
+ Vec::with_capacity(elements.len() * 4 + layout.constraints.len() * 6);
+ for elt in &elements {
+ ccs.push(elt.width | GE(REQUIRED) | 0f64);
+ ccs.push(elt.height | GE(REQUIRED) | 0f64);
+ ccs.push(elt.left() | GE(REQUIRED) | f64::from(dest_area.left()));
+ ccs.push(elt.top() | GE(REQUIRED) | f64::from(dest_area.top()));
+ ccs.push(elt.right() | LE(REQUIRED) | f64::from(dest_area.right()));
+ ccs.push(elt.bottom() | LE(REQUIRED) | f64::from(dest_area.bottom()));
+ }
+ if let Some(first) = elements.first() {
+ ccs.push(match layout.direction {
+ Direction::Horizontal => first.left() | EQ(REQUIRED) | f64::from(dest_area.left()),
+ Direction::Vertical => first.top() | EQ(REQUIRED) | f64::from(dest_area.top()),
+ });
+ }
+ if layout.expand_to_fill {
+ if let Some(last) = elements.last() {
+ ccs.push(match layout.direction {
+ Direction::Horizontal => last.right() | EQ(REQUIRED) | f64::from(dest_area.right()),
+ Direction::Vertical => last.bottom() | EQ(REQUIRED) | f64::from(dest_area.bottom()),
+ });
+ }
+ }
+ match layout.direction {
+ Direction::Horizontal => {
+ for pair in elements.windows(2) {
+ ccs.push((pair[0].x + pair[0].width) | EQ(REQUIRED) | pair[1].x);
+ }
+ for (i, size) in layout.constraints.iter().enumerate() {
+ ccs.push(elements[i].y | EQ(REQUIRED) | f64::from(dest_area.y));
+ ccs.push(elements[i].height | EQ(REQUIRED) | f64::from(dest_area.height));
+ ccs.push(match *size {
+ Constraint::Length(v) => elements[i].width | EQ(MEDIUM) | f64::from(v),
+ Constraint::Percentage(v) => {
+ elements[i].width | EQ(MEDIUM) | (f64::from(v * dest_area.width) / 100.0)
+ }
+ Constraint::Ratio(n, d) => {
+ elements[i].width
+ | EQ(MEDIUM)
+ | (f64::from(dest_area.width) * f64::from(n) / f64::from(d))
+ }
+ Constraint::Min(v) => elements[i].width | GE(MEDIUM) | f64::from(v),
+ Constraint::Max(v) => elements[i].width | LE(MEDIUM) | f64::from(v),
+ });
+
+ match *size {
+ Constraint::Min(v) => {
+ ccs.push(elements[i].width | EQ(WEAK) | f64::from(v));
+ }
+ Constraint::Max(v) => {
+ ccs.push(elements[i].width | EQ(WEAK) | f64::from(v));
+ }
+ _ => {}
+ }
+ }
+ }
+ Direction::Vertical => {
+ for pair in elements.windows(2) {
+ ccs.push((pair[0].y + pair[0].height) | EQ(REQUIRED) | pair[1].y);
+ }
+ for (i, size) in layout.constraints.iter().enumerate() {
+ ccs.push(elements[i].x | EQ(REQUIRED) | f64::from(dest_area.x));
+ ccs.push(elements[i].width | EQ(REQUIRED) | f64::from(dest_area.width));
+ ccs.push(match *size {
+ Constraint::Length(v) => elements[i].height | EQ(MEDIUM) | f64::from(v),
+ Constraint::Percentage(v) => {
+ elements[i].height | EQ(MEDIUM) | (f64::from(v * dest_area.height) / 100.0)
+ }
+ Constraint::Ratio(n, d) => {
+ elements[i].height
+ | EQ(MEDIUM)
+ | (f64::from(dest_area.height) * f64::from(n) / f64::from(d))
+ }
+ Constraint::Min(v) => elements[i].height | GE(MEDIUM) | f64::from(v),
+ Constraint::Max(v) => elements[i].height | LE(MEDIUM) | f64::from(v),
+ });
+
+ match *size {
+ Constraint::Min(v) => {
+ ccs.push(elements[i].height | EQ(WEAK) | f64::from(v));
+ }
+ Constraint::Max(v) => {
+ ccs.push(elements[i].height | EQ(WEAK) | f64::from(v));
+ }
+ _ => {}
+ }
+ }
+ }
+ }
+ solver.add_constraints(&ccs).unwrap();
+ for &(var, value) in solver.fetch_changes() {
+ let (index, attr) = vars[&var];
+ let value = if value.is_sign_negative() {
+ 0
+ } else {
+ value as u16
+ };
+ match attr {
+ 0 => {
+ results[index].x = value;
+ }
+ 1 => {
+ results[index].y = value;
+ }
+ 2 => {
+ results[index].width = value;
+ }
+ 3 => {
+ results[index].height = value;
+ }
+ _ => {}
+ }
+ }
+
+ if layout.expand_to_fill {
+ // Fix imprecision by extending the last item a bit if necessary
+ if let Some(last) = results.last_mut() {
+ match layout.direction {
+ Direction::Vertical => {
+ last.height = dest_area.bottom() - last.y;
+ }
+ Direction::Horizontal => {
+ last.width = dest_area.right() - last.x;
+ }
+ }
+ }
+ }
+ res
+}
+
+/// A container used by the solver inside split
+struct Element {
+ x: Variable,
+ y: Variable,
+ width: Variable,
+ height: Variable,
+}
+
+impl Element {
+ fn new() -> Element {
+ Element {
+ x: Variable::new(),
+ y: Variable::new(),
+ width: Variable::new(),
+ height: Variable::new(),
+ }
+ }
+
+ fn left(&self) -> Variable {
+ self.x
+ }
+
+ fn top(&self) -> Variable {
+ self.y
+ }
+
+ fn right(&self) -> Expression {
+ self.x + self.width
+ }
+
+ fn bottom(&self) -> Expression {
+ self.y + self.height
+ }
+}
+
+/// A simple rectangle used in the computation of the layout and to give widgets a hint about the
+/// area they are supposed to render to.
+#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default)]
+pub struct Rect {
+ pub x: u16,
+ pub y: u16,
+ pub width: u16,
+ pub height: u16,
+}
+
+impl Rect {
+ /// Creates a new rect, with width and height limited to keep the area under max u16.
+ /// If clipped, aspect ratio will be preserved.
+ pub fn new(x: u16, y: u16, width: u16, height: u16) -> Rect {
+ let max_area = u16::max_value();
+ let (clipped_width, clipped_height) =
+ if u32::from(width) * u32::from(height) > u32::from(max_area) {
+ let aspect_ratio = f64::from(width) / f64::from(height);
+ let max_area_f = f64::from(max_area);
+ let height_f = (max_area_f / aspect_ratio).sqrt();
+ let width_f = height_f * aspect_ratio;
+ (width_f as u16, height_f as u16)
+ } else {
+ (width, height)
+ };
+ Rect {
+ x,
+ y,
+ width: clipped_width,
+ height: clipped_height,
+ }
+ }
+
+ pub fn area(self) -> u16 {
+ self.width * self.height
+ }
+
+ pub fn left(self) -> u16 {
+ self.x
+ }
+
+ pub fn right(self) -> u16 {
+ self.x.saturating_add(self.width)
+ }
+
+ pub fn top(self) -> u16 {
+ self.y
+ }
+
+ pub fn bottom(self) -> u16 {
+ self.y.saturating_add(self.height)
+ }
+
+ pub fn inner(self, margin: &Margin) -> Rect {
+ if self.width < 2 * margin.horizontal || self.height < 2 * margin.vertical {
+ Rect::default()
+ } else {
+ Rect {
+ x: self.x + margin.horizontal,
+ y: self.y + margin.vertical,
+ width: self.width - 2 * margin.horizontal,
+ height: self.height - 2 * margin.vertical,
+ }
+ }
+ }
+
+ pub fn union(self, other: Rect) -> Rect {
+ let x1 = min(self.x, other.x);
+ let y1 = min(self.y, other.y);
+ let x2 = max(self.x + self.width, other.x + other.width);
+ let y2 = max(self.y + self.height, other.y + other.height);
+ Rect {
+ x: x1,
+ y: y1,
+ width: x2 - x1,
+ height: y2 - y1,
+ }
+ }
+
+ pub fn intersection(self, other: Rect) -> Rect {
+ let x1 = max(self.x, other.x);
+ let y1 = max(self.y, other.y);
+ let x2 = min(self.x + self.width, other.x + other.width);
+ let y2 = min(self.y + self.height, other.y + other.height);
+ Rect {
+ x: x1,
+ y: y1,
+ width: x2 - x1,
+ height: y2 - y1,
+ }
+ }
+
+ pub fn intersects(self, other: Rect) -> bool {
+ self.x < other.x + other.width
+ && self.x + self.width > other.x
+ && self.y < other.y + other.height
+ && self.y + self.height > other.y
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_vertical_split_by_height() {
+ let target = Rect {
+ x: 2,
+ y: 2,
+ width: 10,
+ height: 10,
+ };
+
+ let chunks = Layout::default()
+ .direction(Direction::Vertical)
+ .constraints(
+ [
+ Constraint::Percentage(10),
+ Constraint::Max(5),
+ Constraint::Min(1),
+ ]
+ .as_ref(),
+ )
+ .split(target);
+
+ assert_eq!(target.height, chunks.iter().map(|r| r.height).sum::<u16>());
+ chunks.windows(2).for_each(|w| assert!(w[0].y <= w[1].y));
+ }
+
+ #[test]
+ fn test_rect_size_truncation() {
+ for width in 256u16..300u16 {
+ for height in 256u16..300u16 {
+ let rect = Rect::new(0, 0, width, height);
+ rect.area(); // Should not panic.
+ assert!(rect.width < width || rect.height < height);
+ // The target dimensions are rounded down so the math will not be too precise
+ // but let's make sure the ratios don't diverge crazily.
+ assert!(
+ (f64::from(rect.width) / f64::from(rect.height)
+ - f64::from(width) / f64::from(height))
+ .abs()
+ < 1.0
+ )
+ }
+ }
+
+ // One dimension below 255, one above. Area above max u16.
+ let width = 900;
+ let height = 100;
+ let rect = Rect::new(0, 0, width, height);
+ assert_ne!(rect.width, 900);
+ assert_ne!(rect.height, 100);
+ assert!(rect.width < width || rect.height < height);
+ }
+
+ #[test]
+ fn test_rect_size_preservation() {
+ for width in 0..256u16 {
+ for height in 0..256u16 {
+ let rect = Rect::new(0, 0, width, height);
+ rect.area(); // Should not panic.
+ assert_eq!(rect.width, width);
+ assert_eq!(rect.height, height);
+ }
+ }
+
+ // One dimension below 255, one above. Area below max u16.
+ let rect = Rect::new(0, 0, 300, 100);
+ assert_eq!(rect.width, 300);
+ assert_eq!(rect.height, 100);
+ }
+}
diff --git a/src/ratatui/mod.rs b/src/ratatui/mod.rs
new file mode 100644
index 00000000..d7926b96
--- /dev/null
+++ b/src/ratatui/mod.rs
@@ -0,0 +1,177 @@
+#![allow(clippy::all)]
+#![allow(warnings)]
+
+//! [ratatui](https://github.com/tui-rs-revival/ratatui) is a library used to build rich
+//! terminal users interfaces and dashboards.
+//!
+//! ![](https://raw.githubusercontent.com/tui-rs-revival/ratatui/master/assets/demo.gif)
+//!
+//! # Get started
+//!
+//! ## Adding `ratatui` as a dependency
+//!
+//! Add the following to your `Cargo.toml`:
+//! ```toml
+//! [dependencies]
+//! crossterm = "0.26"
+//! ratatui = "0.20"
+//! ```
+//!
+//! The crate is using the `crossterm` backend by default that works on most platforms. But if for
+//! example you want to use the `termion` backend instead. This can be done by changing your
+//! dependencies specification to the following:
+//!
+//! ```toml
+//! [dependencies]
+//! termion = "1.5"
+//! ratatui = { version = "0.20", default-features = false, features = ['termion'] }
+//!
+//! ```
+//!
+//! The same logic applies for all other available backends.
+//!
+//! ## Creating a `Terminal`
+//!
+//! Every application using `ratatui` should start by instantiating a `Terminal`. It is a light
+//! abstraction over available backends that provides basic functionalities such as clearing the
+//! screen, hiding the cursor, etc.
+//!
+//! ```rust,no_run
+//! use std::io;
+//! use ratatui::{backend::CrosstermBackend, Terminal};
+//!
+//! fn main() -> Result<(), io::Error> {
+//! let stdout = io::stdout();
+//! let backend = CrosstermBackend::new(stdout);
+//! let mut terminal = Terminal::new(backend)?;
+//! Ok(())
+//! }
+//! ```
+//!
+//! If you had previously chosen `termion` as a backend, the terminal can be created in a similar
+//! way:
+//!
+//! ```rust,ignore
+//! use std::io;
+//! use ratatui::{backend::TermionBackend, Terminal};
+//! use termion::raw::IntoRawMode;
+//!
+//! fn main() -> Result<(), io::Error> {
+//! let stdout = io::stdout().into_raw_mode()?;
+//! let backend = TermionBackend::new(stdout);
+//! let mut terminal = Terminal::new(backend)?;
+//! Ok(())
+//! }
+//! ```
+//!
+//! You may also refer to the examples to find out how to create a `Terminal` for each available
+//! backend.
+//!
+//! ## Building a User Interface (UI)
+//!
+//! Every component of your interface will be implementing the `Widget` trait. The library comes
+//! with a predefined set of widgets that should meet most of your use cases. You are also free to
+//! implement your own.
+//!
+//! Each widget follows a builder pattern API providing a default configuration along with methods
+//! to customize them. The widget is then rendered using [`Frame::render_widget`] which takes
+//! your widget instance and an area to draw to.
+//!
+//! The following example renders a block of the size of the terminal:
+//!
+//! ```rust,no_run
+//! use std::{io, thread, time::Duration};
+//! use ratatui::{
+//! backend::CrosstermBackend,
+//! widgets::{Widget, Block, Borders},
+//! layout::{Layout, Constraint, Direction},
+//! Terminal
+//! };
+//! use crossterm::{
+//! event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
+//! execute,
+//! terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
+//! };
+//!
+//! fn main() -> Result<(), io::Error> {
+//! // setup terminal
+//! enable_raw_mode()?;
+//! let mut stdout = io::stdout();
+//! execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
+//! let backend = CrosstermBackend::new(stdout);
+//! let mut terminal = Terminal::new(backend)?;
+//!
+//! terminal.draw(|f| {
+//! let size = f.size();
+//! let block = Block::default()
+//! .title("Block")
+//! .borders(Borders::ALL);
+//! f.render_widget(block, size);
+//! })?;
+//!
+//! thread::sleep(Duration::from_millis(5000));
+//!
+//! // restore terminal
+//! disable_raw_mode()?;
+//! execute!(
+//! terminal.backend_mut(),
+//! LeaveAlternateScreen,
+//! DisableMouseCapture
+//! )?;
+//! terminal.show_cursor()?;
+//!
+//! Ok(())
+//! }
+//! ```
+//!
+//! ## Layout
+//!
+//! The library comes with a basic yet useful layout management object called `Layout`. As you may
+//! see below and in the examples, the library makes heavy use of the builder pattern to provide
+//! full customization. And `Layout` is no exception:
+//!
+//! ```rust,no_run
+//! use ratatui::{
+//! backend::Backend,
+//! layout::{Constraint, Direction, Layout},
+//! widgets::{Block, Borders},
+//! Frame,
+//! };
+//! fn ui<B: Backend>(f: &mut Frame<B>) {
+//! let chunks = Layout::default()
+//! .direction(Direction::Vertical)
+//! .margin(1)
+//! .constraints(
+//! [
+//! Constraint::Percentage(10),
+//! Constraint::Percentage(80),
+//! Constraint::Percentage(10)
+//! ].as_ref()
+//! )
+//! .split(f.size());
+//! let block = Block::default()
+//! .title("Block")
+//! .borders(Borders::ALL);
+//! f.render_widget(block, chunks[0]);
+//! let block = Block::default()
+//! .title("Block 2")
+//! .borders(Borders::ALL);
+//! f.render_widget(block, chunks[1]);
+//! }
+//! ```
+//!
+//! This let you describe responsive terminal UI by nesting layouts. You should note that by
+//! default the computed layout tries to fill the available space completely. So if for any reason
+//! you might need a blank space somewhere, try to pass an additional constraint and don't use the
+//! corresponding area.
+
+pub mod backend;
+pub mod buffer;
+pub mod layout;
+pub mod style;
+pub mod symbols;
+pub mod terminal;
+pub mod text;
+pub mod widgets;
+
+pub use self::terminal::{Frame, Terminal, TerminalOptions, Viewport};
diff --git a/src/ratatui/style.rs b/src/ratatui/style.rs
new file mode 100644
index 00000000..4d74f6fc
--- /dev/null
+++ b/src/ratatui/style.rs
@@ -0,0 +1,310 @@
+//! `style` contains the primitives used to control how your user interface will look.
+
+use bitflags::bitflags;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+pub enum Color {
+ Reset,
+ Black,
+ Red,
+ Green,
+ Yellow,
+ Blue,
+ Magenta,
+ Cyan,
+ Gray,
+ DarkGray,
+ LightRed,
+ LightGreen,
+ LightYellow,
+ LightBlue,
+ LightMagenta,
+ LightCyan,
+ White,
+ Rgb(u8, u8, u8),
+ Indexed(u8),
+}
+
+bitflags! {
+ /// Modifier changes the way a piece of text is displayed.
+ ///
+ /// They are bitflags so they can easily be composed.
+ ///
+ /// ## Examples
+ ///
+ /// ```rust
+ /// # use ratatui::style::Modifier;
+ ///
+ /// let m = Modifier::BOLD | Modifier::ITALIC;
+ /// ```
+ #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+ pub struct Modifier: u16 {
+ const BOLD = 0b0000_0000_0001;
+ const DIM = 0b0000_0000_0010;
+ const ITALIC = 0b0000_0000_0100;
+ const UNDERLINED = 0b0000_0000_1000;
+ const SLOW_BLINK = 0b0000_0001_0000;
+ const RAPID_BLINK = 0b0000_0010_0000;
+ const REVERSED = 0b0000_0100_0000;
+ const HIDDEN = 0b0000_1000_0000;
+ const CROSSED_OUT = 0b0001_0000_0000;
+ }
+}
+
+/// Style let you control the main characteristics of the displayed elements.
+///
+/// ```rust
+/// # use ratatui::style::{Color, Modifier, Style};
+/// Style::default()
+/// .fg(Color::Black)
+/// .bg(Color::Green)
+/// .add_modifier(Modifier::ITALIC | Modifier::BOLD);
+/// ```
+///
+/// It represents an incremental change. If you apply the styles S1, S2, S3 to a cell of the
+/// terminal buffer, the style of this cell will be the result of the merge of S1, S2 and S3, not
+/// just S3.
+///
+/// ```rust
+/// # use ratatui::style::{Color, Modifier, Style};
+/// # use ratatui::buffer::Buffer;
+/// # use ratatui::layout::Rect;
+/// let styles = [
+/// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC),
+/// Style::default().bg(Color::Red),
+/// Style::default().fg(Color::Yellow).remove_modifier(Modifier::ITALIC),
+/// ];
+/// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
+/// for style in &styles {
+/// buffer.get_mut(0, 0).set_style(*style);
+/// }
+/// assert_eq!(
+/// Style {
+/// fg: Some(Color::Yellow),
+/// bg: Some(Color::Red),
+/// add_modifier: Modifier::BOLD,
+/// sub_modifier: Modifier::empty(),
+/// },
+/// buffer.get(0, 0).style(),
+/// );
+/// ```
+///
+/// The default implementation returns a `Style` that does not modify anything. If you wish to
+/// reset all properties until that point use [`Style::reset`].
+///
+/// ```
+/// # use ratatui::style::{Color, Modifier, Style};
+/// # use ratatui::buffer::Buffer;
+/// # use ratatui::layout::Rect;
+/// let styles = [
+/// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC),
+/// Style::reset().fg(Color::Yellow),
+/// ];
+/// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
+/// for style in &styles {
+/// buffer.get_mut(0, 0).set_style(*style);
+/// }
+/// assert_eq!(
+/// Style {
+/// fg: Some(Color::Yellow),
+/// bg: Some(Color::Reset),
+/// add_modifier: Modifier::empty(),
+/// sub_modifier: Modifier::empty(),
+/// },
+/// buffer.get(0, 0).style(),
+/// );
+/// ```
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+pub struct Style {
+ pub fg: Option<Color>,
+ pub bg: Option<Color>,
+ pub add_modifier: Modifier,
+ pub sub_modifier: Modifier,
+}
+
+impl Default for Style {
+ fn default() -> Style {
+ Style {
+ fg: None,
+ bg: None,
+ add_modifier: Modifier::empty(),
+ sub_modifier: Modifier::empty(),
+ }
+ }
+}
+
+impl Style {
+ /// Returns a `Style` resetting all properties.
+ pub fn reset() -> Style {
+ Style {
+ fg: Some(Color::Reset),
+ bg: Some(Color::Reset),
+ add_modifier: Modifier::empty(),
+ sub_modifier: Modifier::all(),
+ }
+ }
+
+ /// Changes the foreground color.
+ ///
+ /// ## Examples
+ ///
+ /// ```rust
+ /// # use ratatui::style::{Color, Style};
+ /// let style = Style::default().fg(Color::Blue);
+ /// let diff = Style::default().fg(Color::Red);
+ /// assert_eq!(style.patch(diff), Style::default().fg(Color::Red));
+ /// ```
+ pub fn fg(mut self, color: Color) -> Style {
+ self.fg = Some(color);
+ self
+ }
+
+ /// Changes the background color.
+ ///
+ /// ## Examples
+ ///
+ /// ```rust
+ /// # use ratatui::style::{Color, Style};
+ /// let style = Style::default().bg(Color::Blue);
+ /// let diff = Style::default().bg(Color::Red);
+ /// assert_eq!(style.patch(diff), Style::default().bg(Color::Red));
+ /// ```
+ pub fn bg(mut self, color: Color) -> Style {
+ self.bg = Some(color);
+ self
+ }
+
+ /// Changes the text emphasis.
+ ///
+ /// When applied, it adds the given modifier to the `Style` modifiers.
+ ///
+ /// ## Examples
+ ///
+ /// ```rust
+ /// # use ratatui::style::{Color, Modifier, Style};
+ /// let style = Style::default().add_modifier(Modifier::BOLD);
+ /// let diff = Style::default().add_modifier(Modifier::ITALIC);
+ /// let patched = style.patch(diff);
+ /// assert_eq!(patched.add_modifier, Modifier::BOLD | Modifier::ITALIC);
+ /// assert_eq!(patched.sub_modifier, Modifier::empty());
+ /// ```
+ pub fn add_modifier(mut self, modifier: Modifier) -> Style {
+ self.sub_modifier.remove(modifier);
+ self.add_modifier.insert(modifier);
+ self
+ }
+
+ /// Changes the text emphasis.
+ ///
+ /// When applied, it removes the given modifier from the `Style` modifiers.
+ ///
+ /// ## Examples
+ ///
+ /// ```rust
+ /// # use ratatui::style::{Color, Modifier, Style};
+ /// let style = Style::default().add_modifier(Modifier::BOLD | Modifier::ITALIC);
+ /// let diff = Style::default().remove_modifier(Modifier::ITALIC);
+ /// let patched = style.patch(diff);
+ /// assert_eq!(patched.add_modifier, Modifier::BOLD);
+ /// assert_eq!(patched.sub_modifier, Modifier::ITALIC);
+ /// ```
+ pub fn remove_modifier(mut self, modifier: Modifier) -> Style {
+ self.add_modifier.remove(modifier);
+ self.sub_modifier.insert(modifier);
+ self
+ }
+
+ /// Results in a combined style that is equivalent to applying the two individual styles to
+ /// a style one after the other.
+ ///
+ /// ## Examples
+ /// ```
+ /// # use ratatui::style::{Color, Modifier, Style};
+ /// let style_1 = Style::default().fg(Color::Yellow);
+ /// let style_2 = Style::default().bg(Color::Red);
+ /// let combined = style_1.patch(style_2);
+ /// assert_eq!(
+ /// Style::default().patch(style_1).patch(style_2),
+ /// Style::default().patch(combined));
+ /// ```
+ pub fn patch(mut self, other: Style) -> Style {
+ self.fg = other.fg.or(self.fg);
+ self.bg = other.bg.or(self.bg);
+
+ self.add_modifier.remove(other.sub_modifier);
+ self.add_modifier.insert(other.add_modifier);
+ self.sub_modifier.remove(other.add_modifier);
+ self.sub_modifier.insert(other.sub_modifier);
+
+ self
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn styles() -> Vec<Style> {
+ vec![
+ Style::default(),
+ Style::default().fg(Color::Yellow),
+ Style::default().bg(Color::Yellow),
+ Style::default().add_modifier(Modifier::BOLD),
+ Style::default().remove_modifier(Modifier::BOLD),
+ Style::default().add_modifier(Modifier::ITALIC),
+ Style::default().remove_modifier(Modifier::ITALIC),
+ Style::default().add_modifier(Modifier::ITALIC | Modifier::BOLD),
+ Style::default().remove_modifier(Modifier::ITALIC | Modifier::BOLD),
+ ]
+ }
+
+ #[test]
+ fn combined_patch_gives_same_result_as_individual_patch() {
+ let styles = styles();
+ for &a in &styles {
+ for &b in &styles {
+ for &c in &styles {
+ for &d in &styles {
+ let combined = a.patch(b.patch(c.patch(d)));
+
+ assert_eq!(
+ Style::default().patch(a).patch(b).patch(c).patch(d),
+ Style::default().patch(combined)
+ );
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn combine_individual_modifiers() {
+ use crate::ratatui::{buffer::Buffer, layout::Rect};
+
+ let mods = vec![
+ Modifier::BOLD,
+ Modifier::DIM,
+ Modifier::ITALIC,
+ Modifier::UNDERLINED,
+ Modifier::SLOW_BLINK,
+ Modifier::RAPID_BLINK,
+ Modifier::REVERSED,
+ Modifier::HIDDEN,
+ Modifier::CROSSED_OUT,
+ ];
+
+ let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
+
+ for m in &mods {
+ buffer.get_mut(0, 0).set_style(Style::reset());
+ buffer
+ .get_mut(0, 0)
+ .set_style(Style::default().add_modifier(*m));
+ let style = buffer.get(0, 0).style();
+ assert!(style.add_modifier.contains(*m));
+ assert!(!style.sub_modifier.contains(*m));
+ }
+ }
+}
diff --git a/src/ratatui/symbols.rs b/src/ratatui/symbols.rs
new file mode 100644
index 00000000..040e77f6
--- /dev/null
+++ b/src/ratatui/symbols.rs
@@ -0,0 +1,233 @@
+pub mod block {
+ pub const FULL: &str = "█";
+ pub const SEVEN_EIGHTHS: &str = "▉";
+ pub const THREE_QUARTERS: &str = "▊";
+ pub const FIVE_EIGHTHS: &str = "▋";
+ pub const HALF: &str = "▌";
+ pub const THREE_EIGHTHS: &str = "▍";
+ pub const ONE_QUARTER: &str = "▎";
+ pub const ONE_EIGHTH: &str = "▏";
+
+ #[derive(Debug, Clone)]
+ pub struct Set {
+ pub full: &'static str,
+ pub seven_eighths: &'static str,
+ pub three_quarters: &'static str,
+ pub five_eighths: &'static str,
+ pub half: &'static str,
+ pub three_eighths: &'static str,
+ pub one_quarter: &'static str,
+ pub one_eighth: &'static str,
+ pub empty: &'static str,
+ }
+
+ pub const THREE_LEVELS: Set = Set {
+ full: FULL,
+ seven_eighths: FULL,
+ three_quarters: HALF,
+ five_eighths: HALF,
+ half: HALF,
+ three_eighths: HALF,
+ one_quarter: HALF,
+ one_eighth: " ",
+ empty: " ",
+ };
+
+ pub const NINE_LEVELS: Set = Set {
+ full: FULL,
+ seven_eighths: SEVEN_EIGHTHS,
+ three_quarters: THREE_QUARTERS,
+ five_eighths: FIVE_EIGHTHS,
+ half: HALF,
+ three_eighths: THREE_EIGHTHS,
+ one_quarter: ONE_QUARTER,
+ one_eighth: ONE_EIGHTH,
+ empty: " ",
+ };
+}
+
+pub mod bar {
+ pub const FULL: &str = "█";
+ pub const SEVEN_EIGHTHS: &str = "▇";
+ pub const THREE_QUARTERS: &str = "▆";
+ pub const FIVE_EIGHTHS: &str = "▅";
+ pub const HALF: &str = "▄";
+ pub const THREE_EIGHTHS: &str = "▃";
+ pub const ONE_QUARTER: &str = "▂";
+ pub const ONE_EIGHTH: &str = "▁";
+
+ #[derive(Debug, Clone)]
+ pub struct Set {
+ pub full: &'static str,
+ pub seven_eighths: &'static str,
+ pub three_quarters: &'static str,
+ pub five_eighths: &'static str,
+ pub half: &'static str,
+ pub three_eighths: &'static str,
+ pub one_quarter: &'static str,
+ pub one_eighth: &'static str,
+ pub empty: &'static str,
+ }
+
+ pub const THREE_LEVELS: Set = Set {
+ full: FULL,
+ seven_eighths: FULL,
+ three_quarters: HALF,
+ five_eighths: HALF,
+ half: HALF,
+ three_eighths: HALF,
+ one_quarter: HALF,
+ one_eighth: " ",
+ empty: " ",
+ };
+
+ pub const NINE_LEVELS: Set = Set {
+ full: FULL,
+ seven_eighths: SEVEN_EIGHTHS,
+ three_quarters: THREE_QUARTERS,
+ five_eighths: FIVE_EIGHTHS,
+ half: HALF,
+ three_eighths: THREE_EIGHTHS,
+ one_quarter: ONE_QUARTER,
+ one_eighth: ONE_EIGHTH,
+ empty: " ",
+ };
+}
+
+pub mod line {
+ pub const VERTICAL: &str = "│";
+ pub const DOUBLE_VERTICAL: &str = "║";
+ pub const THICK_VERTICAL: &str = "┃";
+
+ pub const HORIZONTAL: &str = "─";
+ pub const DOUBLE_HORIZONTAL: &str = "═";
+ pub const THICK_HORIZONTAL: &str = "━";
+
+ pub const TOP_RIGHT: &str = "┐";
+ pub const ROUNDED_TOP_RIGHT: &str = "╮";
+ pub const DOUBLE_TOP_RIGHT: &str = "╗";
+ pub const THICK_TOP_RIGHT: &str = "┓";
+
+ pub const TOP_LEFT: &str = "┌";
+ pub const ROUNDED_TOP_LEFT: &str = "╭";
+ pub const DOUBLE_TOP_LEFT: &str = "╔";
+ pub const THICK_TOP_LEFT: &str = "┏";
+
+ pub const BOTTOM_RIGHT: &str = "┘";
+ pub const ROUNDED_BOTTOM_RIGHT: &str = "╯";
+ pub const DOUBLE_BOTTOM_RIGHT: &str = "╝";
+ pub const THICK_BOTTOM_RIGHT: &str = "┛";
+
+ pub const BOTTOM_LEFT: &str = "└";
+ pub const ROUNDED_BOTTOM_LEFT: &str = "╰";
+ pub const DOUBLE_BOTTOM_LEFT: &str = "╚";
+ pub const THICK_BOTTOM_LEFT: &str = "┗";
+
+ pub const VERTICAL_LEFT: &str = "┤";
+ pub const DOUBLE_VERTICAL_LEFT: &str = "╣";
+ pub const THICK_VERTICAL_LEFT: &str = "┫";
+
+ pub const VERTICAL_RIGHT: &str = "├";
+ pub const DOUBLE_VERTICAL_RIGHT: &str = "╠";
+ pub const THICK_VERTICAL_RIGHT: &str = "┣";
+
+ pub const HORIZONTAL_DOWN: &str = "┬";
+ pub const DOUBLE_HORIZONTAL_DOWN: &str = "╦";
+ pub const THICK_HORIZONTAL_DOWN: &str = "┳";
+
+ pub const HORIZONTAL_UP: &str = "┴";
+ pub const DOUBLE_HORIZONTAL_UP: &str = "╩";
+ pub const THICK_HORIZONTAL_UP: &str = "┻";
+
+ pub const CROSS: &str = "┼";
+ pub const DOUBLE_CROSS: &str = "╬";
+ pub const THICK_CROSS: &str = "╋";
+
+ #[derive(Debug, Clone)]
+ pub struct Set {
+ pub vertical: &'static str,
+ pub horizontal: &'static str,
+ pub top_right: &'static str,
+ pub top_left: &'static str,
+ pub bottom_right: &'static str,
+ pub bottom_left: &'static str,
+ pub vertical_left: &'static str,
+ pub vertical_right: &'static str,
+ pub horizontal_down: &'static str,
+ pub horizontal_up: &'static str,
+ pub cross: &'static str,
+ }
+
+ pub const NORMAL: Set = Set {
+ vertical: VERTICAL,
+ horizontal: HORIZONTAL,
+ top_right: TOP_RIGHT,
+ top_left: TOP_LEFT,
+ bottom_right: BOTTOM_RIGHT,
+ bottom_left: BOTTOM_LEFT,
+ vertical_left: VERTICAL_LEFT,
+ vertical_right: VERTICAL_RIGHT,
+ horizontal_down: HORIZONTAL_DOWN,
+ horizontal_up: HORIZONTAL_UP,
+ cross: CROSS,
+ };
+
+ pub const ROUNDED: Set = Set {
+ top_right: ROUNDED_TOP_RIGHT,
+ top_left: ROUNDED_TOP_LEFT,
+ bottom_right: ROUNDED_BOTTOM_RIGHT,
+ bottom_left: ROUNDED_BOTTOM_LEFT,
+ ..NORMAL
+ };
+
+ pub const DOUBLE: Set = Set {
+ vertical: DOUBLE_VERTICAL,
+ horizontal: DOUBLE_HORIZONTAL,
+ top_right: DOUBLE_TOP_RIGHT,
+ top_left: DOUBLE_TOP_LEFT,
+ bottom_right: DOUBLE_BOTTOM_RIGHT,
+ bottom_left: DOUBLE_BOTTOM_LEFT,
+ vertical_left: DOUBLE_VERTICAL_LEFT,
+ vertical_right: DOUBLE_VERTICAL_RIGHT,
+ horizontal_down: DOUBLE_HORIZONTAL_DOWN,
+ horizontal_up: DOUBLE_HORIZONTAL_UP,
+ cross: DOUBLE_CROSS,
+ };
+
+ pub const THICK: Set = Set {
+ vertical: THICK_VERTICAL,
+ horizontal: THICK_HORIZONTAL,
+ top_right: THICK_TOP_RIGHT,
+ top_left: THICK_TOP_LEFT,
+ bottom_right: THICK_BOTTOM_RIGHT,
+ bottom_left: THICK_BOTTOM_LEFT,
+ vertical_left: THICK_VERTICAL_LEFT,
+ vertical_right: THICK_VERTICAL_RIGHT,
+ horizontal_down: THICK_HORIZONTAL_DOWN,
+ horizontal_up: THICK_HORIZONTAL_UP,
+ cross: THICK_CROSS,
+ };
+}
+
+pub const DOT: &str = "•";
+
+pub mod braille {
+ pub const BLANK: u16 = 0x2800;
+ pub const DOTS: [[u16; 2]; 4] = [
+ [0x0001, 0x0008],
+ [0x0002, 0x0010],
+ [0x0004, 0x0020],
+ [0x0040, 0x0080],
+ ];
+}
+
+/// Marker to use when plotting data points
+#[derive(Debug, Clone, Copy)]
+pub enum Marker {
+ /// One point per cell in shape of dot
+ Dot,
+ /// One point per cell in shape of a block
+ Block,
+ /// Up to 8 points per cell
+ Braille,
+}
diff --git a/src/ratatui/terminal.rs b/src/ratatui/terminal.rs
new file mode 100644
index 00000000..32accffa
--- /dev/null
+++ b/src/ratatui/terminal.rs
@@ -0,0 +1,487 @@
+use crate::ratatui::{
+ backend::{Backend, ClearType},
+ buffer::Buffer,
+ layout::Rect,
+ widgets::{StatefulWidget, Widget},
+};
+use std::io;
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum Viewport {
+ Fullscreen,
+ Inline(u16),
+ Fixed(Rect),
+}
+
+impl Viewport {
+ pub fn fixed(area: Rect) -> Viewport {
+ Self::Fixed(area)
+ }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+/// Options to pass to [`Terminal::with_options`]
+pub struct TerminalOptions {
+ /// Viewport used to draw to the terminal
+ pub viewport: Viewport,
+}
+
+/// Interface to the terminal backed by Termion
+#[derive(Debug)]
+pub struct Terminal<B>
+where
+ B: Backend,
+{
+ backend: B,
+ /// Holds the results of the current and previous draw calls. The two are compared at the end
+ /// of each draw pass to output the necessary updates to the terminal
+ buffers: [Buffer; 2],
+ /// Index of the current buffer in the previous array
+ current: usize,
+ /// Whether the cursor is currently hidden
+ hidden_cursor: bool,
+ /// Viewport
+ viewport: Viewport,
+ viewport_area: Rect,
+ /// Last known size of the terminal. Used to detect if the internal buffers have to be resized.
+ last_known_size: Rect,
+ /// Last known position of the cursor. Used to find the new area when the viewport is inlined
+ /// and the terminal resized.
+ last_known_cursor_pos: (u16, u16),
+}
+
+/// Represents a consistent terminal interface for rendering.
+pub struct Frame<'a, B: 'a>
+where
+ B: Backend,
+{
+ terminal: &'a mut Terminal<B>,
+
+ /// Where should the cursor be after drawing this frame?
+ ///
+ /// If `None`, the cursor is hidden and its position is controlled by the backend. If `Some((x,
+ /// y))`, the cursor is shown and placed at `(x, y)` after the call to `Terminal::draw()`.
+ cursor_position: Option<(u16, u16)>,
+}
+
+impl<'a, B> Frame<'a, B>
+where
+ B: Backend,
+{
+ /// Frame size, guaranteed not to change when rendering.
+ pub fn size(&self) -> Rect {
+ self.terminal.viewport_area
+ }
+
+ /// Render a [`Widget`] to the current buffer using [`Widget::render`].
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use ratatui::Terminal;
+ /// # use ratatui::backend::TestBackend;
+ /// # use ratatui::layout::Rect;
+ /// # use ratatui::widgets::Block;
+ /// # let backend = TestBackend::new(5, 5);
+ /// # let mut terminal = Terminal::new(backend).unwrap();
+ /// let block = Block::default();
+ /// let area = Rect::new(0, 0, 5, 5);
+ /// let mut frame = terminal.get_frame();
+ /// frame.render_widget(block, area);
+ /// ```
+ pub fn render_widget<W>(&mut self, widget: W, area: Rect)
+ where
+ W: Widget,
+ {
+ widget.render(area, self.terminal.current_buffer_mut());
+ }
+
+ /// Render a [`StatefulWidget`] to the current buffer using [`StatefulWidget::render`].
+ ///
+ /// The last argument should be an instance of the [`StatefulWidget::State`] associated to the
+ /// given [`StatefulWidget`].
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use ratatui::Terminal;
+ /// # use ratatui::backend::TestBackend;
+ /// # use ratatui::layout::Rect;
+ /// # use ratatui::widgets::{List, ListItem, ListState};
+ /// # let backend = TestBackend::new(5, 5);
+ /// # let mut terminal = Terminal::new(backend).unwrap();
+ /// let mut state = ListState::default();
+ /// state.select(Some(1));
+ /// let items = vec![
+ /// ListItem::new("Item 1"),
+ /// ListItem::new("Item 2"),
+ /// ];
+ /// let list = List::new(items);
+ /// let area = Rect::new(0, 0, 5, 5);
+ /// let mut frame = terminal.get_frame();
+ /// frame.render_stateful_widget(list, area, &mut state);
+ /// ```
+ pub fn render_stateful_widget<W>(&mut self, widget: W, area: Rect, state: &mut W::State)
+ where
+ W: StatefulWidget,
+ {
+ widget.render(area, self.terminal.current_buffer_mut(), state);
+ }
+
+ /// After drawing this frame, make the cursor visible and put it at the specified (x, y)
+ /// coordinates. If this method is not called, the cursor will be hidden.
+ ///
+ /// Note that this will interfere with calls to `Terminal::hide_cursor()`,
+ /// `Terminal::show_cursor()`, and `Terminal::set_cursor()`. Pick one of the APIs and stick
+ /// with it.
+ pub fn set_cursor(&mut self, x: u16, y: u16) {
+ self.cursor_position = Some((x, y));
+ }
+}
+
+/// CompletedFrame represents the state of the terminal after all changes performed in the last
+/// [`Terminal::draw`] call have been applied. Therefore, it is only valid until the next call to
+/// [`Terminal::draw`].
+pub struct CompletedFrame<'a> {
+ pub buffer: &'a Buffer,
+ pub area: Rect,
+}
+
+impl<B> Drop for Terminal<B>
+where
+ B: Backend,
+{
+ fn drop(&mut self) {
+ // Attempt to restore the cursor state
+ if self.hidden_cursor {
+ if let Err(err) = self.show_cursor() {
+ eprintln!("Failed to show the cursor: {}", err);
+ }
+ }
+ }
+}
+
+impl<B> Terminal<B>
+where
+ B: Backend,
+{
+ /// Wrapper around Terminal initialization. Each buffer is initialized with a blank string and
+ /// default colors for the foreground and the background
+ pub fn new(backend: B) -> io::Result<Terminal<B>> {
+ Terminal::with_options(
+ backend,
+ TerminalOptions {
+ viewport: Viewport::Fullscreen,
+ },
+ )
+ }
+
+ pub fn with_options(mut backend: B, options: TerminalOptions) -> io::Result<Terminal<B>> {
+ let size = match options.viewport {
+ Viewport::Fullscreen | Viewport::Inline(_) => backend.size()?,
+ Viewport::Fixed(area) => area,
+ };
+ let (viewport_area, cursor_pos) = match options.viewport {
+ Viewport::Fullscreen => (size, (0, 0)),
+ Viewport::Inline(height) => compute_inline_size(&mut backend, height, size, 0)?,
+ Viewport::Fixed(area) => (area, (area.left(), area.top())),
+ };
+ Ok(Terminal {
+ backend,
+ buffers: [Buffer::empty(viewport_area), Buffer::empty(viewport_area)],
+ current: 0,
+ hidden_cursor: false,
+ viewport: options.viewport,
+ viewport_area,
+ last_known_size: size,
+ last_known_cursor_pos: cursor_pos,
+ })
+ }
+
+ /// Get a Frame object which provides a consistent view into the terminal state for rendering.
+ pub fn get_frame(&mut self) -> Frame<B> {
+ Frame {
+ terminal: self,
+ cursor_position: None,
+ }
+ }
+
+ pub fn current_buffer_mut(&mut self) -> &mut Buffer {
+ &mut self.buffers[self.current]
+ }
+
+ pub fn backend(&self) -> &B {
+ &self.backend
+ }
+
+ pub fn backend_mut(&mut self) -> &mut B {
+ &mut self.backend
+ }
+
+ /// Obtains a difference between the previous and the current buffer and passes it to the
+ /// current backend for drawing.
+ pub fn flush(&mut self) -> io::Result<()> {
+ let previous_buffer = &self.buffers[1 - self.current];
+ let current_buffer = &self.buffers[self.current];
+ let updates = previous_buffer.diff(current_buffer);
+ if let Some((col, row, _)) = updates.last() {
+ self.last_known_cursor_pos = (*col, *row);
+ }
+ self.backend.draw(updates.into_iter())
+ }
+
+ /// Updates the Terminal so that internal buffers match the requested size. Requested size will
+ /// be saved so the size can remain consistent when rendering.
+ /// This leads to a full clear of the screen.
+ pub fn resize(&mut self, size: Rect) -> io::Result<()> {
+ let next_area = match self.viewport {
+ Viewport::Fullscreen => size,
+ Viewport::Inline(height) => {
+ let offset_in_previous_viewport = self
+ .last_known_cursor_pos
+ .1
+ .saturating_sub(self.viewport_area.top());
+ compute_inline_size(&mut self.backend, height, size, offset_in_previous_viewport)?.0
+ }
+ Viewport::Fixed(area) => area,
+ };
+ self.set_viewport_area(next_area);
+ self.clear()?;
+
+ self.last_known_size = size;
+ Ok(())
+ }
+
+ fn set_viewport_area(&mut self, area: Rect) {
+ self.buffers[self.current].resize(area);
+ self.buffers[1 - self.current].resize(area);
+ self.viewport_area = area;
+ }
+
+ /// Queries the backend for size and resizes if it doesn't match the previous size.
+ pub fn autoresize(&mut self) -> io::Result<()> {
+ // fixed viewports do not get autoresized
+ if matches!(self.viewport, Viewport::Fullscreen | Viewport::Inline(_)) {
+ let size = self.size()?;
+ if size != self.last_known_size {
+ self.resize(size)?;
+ }
+ };
+ Ok(())
+ }
+
+ /// Synchronizes terminal size, calls the rendering closure, flushes the current internal state
+ /// and prepares for the next draw call.
+ pub fn draw<F>(&mut self, f: F) -> io::Result<CompletedFrame>
+ where
+ F: FnOnce(&mut Frame<B>),
+ {
+ // Autoresize - otherwise we get glitches if shrinking or potential desync between widgets
+ // and the terminal (if growing), which may OOB.
+ self.autoresize()?;
+
+ let mut frame = self.get_frame();
+ f(&mut frame);
+ // We can't change the cursor position right away because we have to flush the frame to
+ // stdout first. But we also can't keep the frame around, since it holds a &mut to
+ // Terminal. Thus, we're taking the important data out of the Frame and dropping it.
+ let cursor_position = frame.cursor_position;
+
+ // Draw to stdout
+ self.flush()?;
+
+ match cursor_position {
+ None => self.hide_cursor()?,
+ Some((x, y)) => {
+ self.show_cursor()?;
+ self.set_cursor(x, y)?;
+ }
+ }
+
+ // Swap buffers
+ self.buffers[1 - self.current].reset();
+ self.current = 1 - self.current;
+
+ // Flush
+ self.backend.flush()?;
+
+ Ok(CompletedFrame {
+ buffer: &self.buffers[1 - self.current],
+ area: self.last_known_size,
+ })
+ }
+
+ pub fn hide_cursor(&mut self) -> io::Result<()> {
+ self.backend.hide_cursor()?;
+ self.hidden_cursor = true;
+ Ok(())
+ }
+
+ pub fn show_cursor(&mut self) -> io::Result<()> {
+ self.backend.show_cursor()?;
+ self.hidden_cursor = false;
+ Ok(())
+ }
+
+ pub fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
+ self.backend.get_cursor()
+ }
+
+ pub fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
+ self.backend.set_cursor(x, y)?;
+ self.last_known_cursor_pos = (x, y);
+ Ok(())
+ }
+
+ /// Clear the terminal and force a full redraw on the next draw call.
+ pub fn clear(&mut self) -> io::Result<()> {
+ match self.viewport {
+ Viewport::Fullscreen => self.backend.clear_region(ClearType::All)?,
+ Viewport::Inline(_) => {
+ self.backend
+ .set_cursor(self.viewport_area.left(), self.viewport_area.top())?;
+ self.backend.clear_region(ClearType::AfterCursor)?;
+ }
+ Viewport::Fixed(area) => {
+ for row in area.top()..area.bottom() {
+ self.backend.set_cursor(0, row)?;
+ self.backend.clear_region(ClearType::AfterCursor)?;
+ }
+ }
+ }
+ // Reset the back buffer to make sure the next update will redraw everything.
+ self.buffers[1 - self.current].reset();
+ Ok(())
+ }
+
+ /// Queries the real size of the backend.
+ pub fn size(&self) -> io::Result<Rect> {
+ self.backend.size()
+ }
+
+ /// Insert some content before the current inline viewport. This has no effect when the
+ /// viewport is fullscreen.
+ ///
+ /// This function scrolls down the current viewport by the given height. The newly freed space is
+ /// then made available to the `draw_fn` closure through a writable `Buffer`.
+ ///
+ /// Before:
+ /// ```ignore
+ /// +-------------------+
+ /// | |
+ /// | viewport |
+ /// | |
+ /// +-------------------+
+ /// ```
+ ///
+ /// After:
+ /// ```ignore
+ /// +-------------------+
+ /// | buffer |
+ /// +-------------------+
+ /// +-------------------+
+ /// | |
+ /// | viewport |
+ /// | |
+ /// +-------------------+
+ /// ```
+ ///
+ /// # Examples
+ ///
+ /// ## Insert a single line before the current viewport
+ ///
+ /// ```rust
+ /// # use ratatui::widgets::{Paragraph, Widget};
+ /// # use ratatui::text::{Spans, Span};
+ /// # use ratatui::style::{Color, Style};
+ /// # use ratatui::{Terminal};
+ /// # use ratatui::backend::TestBackend;
+ /// # let backend = TestBackend::new(10, 10);
+ /// # let mut terminal = Terminal::new(backend).unwrap();
+ /// terminal.insert_before(1, |buf| {
+ /// Paragraph::new(Spans::from(vec![
+ /// Span::raw("This line will be added "),
+ /// Span::styled("before", Style::default().fg(Color::Blue)),
+ /// Span::raw(" the current viewport")
+ /// ])).render(buf.area, buf);
+ /// });
+ /// ```
+ pub fn insert_before<F>(&mut self, height: u16, draw_fn: F) -> io::Result<()>
+ where
+ F: FnOnce(&mut Buffer),
+ {
+ if !matches!(self.viewport, Viewport::Inline(_)) {
+ return Ok(());
+ }
+
+ self.clear()?;
+ let height = height.min(self.last_known_size.height);
+ self.backend.append_lines(height)?;
+ let missing_lines =
+ height.saturating_sub(self.last_known_size.bottom() - self.viewport_area.top());
+ let area = Rect {
+ x: self.viewport_area.left(),
+ y: self.viewport_area.top().saturating_sub(missing_lines),
+ width: self.viewport_area.width,
+ height,
+ };
+ let mut buffer = Buffer::empty(area);
+
+ draw_fn(&mut buffer);
+
+ let iter = buffer.content.iter().enumerate().map(|(i, c)| {
+ let (x, y) = buffer.pos_of(i);
+ (x, y, c)
+ });
+ self.backend.draw(iter)?;
+ self.backend.flush()?;
+
+ let remaining_lines = self.last_known_size.height - area.bottom();
+ let missing_lines = self.viewport_area.height.saturating_sub(remaining_lines);
+ self.backend.append_lines(self.viewport_area.height)?;
+
+ self.set_viewport_area(Rect {
+ x: area.left(),
+ y: area.bottom().saturating_sub(missing_lines),
+ width: area.width,
+ height: self.viewport_area.height,
+ });
+
+ Ok(())
+ }
+}
+
+fn compute_inline_size<B: Backend>(
+ backend: &mut B,
+ height: u16,
+ size: Rect,
+ offset_in_previous_viewport: u16,
+) -> io::Result<(Rect, (u16, u16))> {
+ let pos = backend.get_cursor()?;
+ let mut row = pos.1;
+
+ let max_height = size.height.min(height);
+
+ let lines_after_cursor = height
+ .saturating_sub(offset_in_previous_viewport)
+ .saturating_sub(1);
+
+ backend.append_lines(lines_after_cursor)?;
+
+ let available_lines = size.height.saturating_sub(row).saturating_sub(1);
+ let missing_lines = lines_after_cursor.saturating_sub(available_lines);
+ if missing_lines > 0 {
+ row = row.saturating_sub(missing_lines);
+ }
+ row = row.saturating_sub(offset_in_previous_viewport);
+
+ Ok((
+ Rect {
+ x: 0,
+ y: row,
+ width: size.width,
+ height: max_height,
+ },
+ pos,
+ ))
+}
diff --git a/src/ratatui/text.rs b/src/ratatui/text.rs
new file mode 100644
index 00000000..50e3d5cf
--- /dev/null
+++ b/src/ratatui/text.rs
@@ -0,0 +1,430 @@
+//! Primitives for styled text.
+//!
+//! A terminal UI is at its root a lot of strings. In order to make it accessible and stylish,
+//! those strings may be associated to a set of styles. `ratatui` has three ways to represent them:
+//! - A single line string where all graphemes have the same style is represented by a [`Span`].
+//! - A single line string where each grapheme may have its own style is represented by [`Spans`].
+//! - A multiple line string where each grapheme may have its own style is represented by a
+//! [`Text`].
+//!
+//! These types form a hierarchy: [`Spans`] is a collection of [`Span`] and each line of [`Text`]
+//! is a [`Spans`].
+//!
+//! Keep it mind that a lot of widgets will use those types to advertise what kind of string is
+//! supported for their properties. Moreover, `ratatui` provides convenient `From` implementations so
+//! that you can start by using simple `String` or `&str` and then promote them to the previous
+//! primitives when you need additional styling capabilities.
+//!
+//! For example, for the [`crate::widgets::Block`] widget, all the following calls are valid to set
+//! its `title` property (which is a [`Spans`] under the hood):
+//!
+//! ```rust
+//! # use ratatui::widgets::Block;
+//! # use ratatui::text::{Span, Spans};
+//! # use ratatui::style::{Color, Style};
+//! // A simple string with no styling.
+//! // Converted to Spans(vec![
+//! // Span { content: Cow::Borrowed("My title"), style: Style { .. } }
+//! // ])
+//! let block = Block::default().title("My title");
+//!
+//! // A simple string with a unique style.
+//! // Converted to Spans(vec![
+//! // Span { content: Cow::Borrowed("My title"), style: Style { fg: Some(Color::Yellow), .. }
+//! // ])
+//! let block = Block::default().title(
+//! Span::styled("My title", Style::default().fg(Color::Yellow))
+//! );
+//!
+//! // A string with multiple styles.
+//! // Converted to Spans(vec![
+//! // Span { content: Cow::Borrowed("My"), style: Style { fg: Some(Color::Yellow), .. } },
+//! // Span { content: Cow::Borrowed(" title"), .. }
+//! // ])
+//! let block = Block::default().title(vec![
+//! Span::styled("My", Style::default().fg(Color::Yellow)),
+//! Span::raw(" title"),
+//! ]);
+//! ```
+use crate::ratatui::style::Style;
+use std::borrow::Cow;
+use unicode_segmentation::UnicodeSegmentation;
+use unicode_width::UnicodeWidthStr;
+
+/// A grapheme associated to a style.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct StyledGrapheme<'a> {
+ pub symbol: &'a str,
+ pub style: Style,
+}
+
+/// A string where all graphemes have the same style.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Span<'a> {
+ pub content: Cow<'a, str>,
+ pub style: Style,
+}
+
+impl<'a> Span<'a> {
+ /// Create a span with no style.
+ ///
+ /// ## Examples
+ ///
+ /// ```rust
+ /// # use ratatui::text::Span;
+ /// Span::raw("My text");
+ /// Span::raw(String::from("My text"));
+ /// ```
+ pub fn raw<T>(content: T) -> Span<'a>
+ where
+ T: Into<Cow<'a, str>>,
+ {
+ Span {
+ content: content.into(),
+ style: Style::default(),
+ }
+ }
+
+ /// Create a span with a style.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use ratatui::text::Span;
+ /// # use ratatui::style::{Color, Modifier, Style};
+ /// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
+ /// Span::styled("My text", style);
+ /// Span::styled(String::from("My text"), style);
+ /// ```
+ pub fn styled<T>(content: T, style: Style) -> Span<'a>
+ where
+ T: Into<Cow<'a, str>>,
+ {
+ Span {
+ content: content.into(),
+ style,
+ }
+ }
+
+ /// Returns the width of the content held by this span.
+ pub fn width(&self) -> usize {
+ self.content.width()
+ }
+
+ /// Returns an iterator over the graphemes held by this span.
+ ///
+ /// `base_style` is the [`Style`] that will be patched with each grapheme [`Style`] to get
+ /// the resulting [`Style`].
+ ///
+ /// ## Examples
+ ///
+ /// ```rust
+ /// # use ratatui::text::{Span, StyledGrapheme};
+ /// # use ratatui::style::{Color, Modifier, Style};
+ /// # use std::iter::Iterator;
+ /// let style = Style::default().fg(Color::Yellow);
+ /// let span = Span::styled("Text", style);
+ /// let style = Style::default().fg(Color::Green).bg(Color::Black);
+ /// let styled_graphemes = span.styled_graphemes(style);
+ /// assert_eq!(
+ /// vec![
+ /// StyledGrapheme {
+ /// symbol: "T",
+ /// style: Style {
+ /// fg: Some(Color::Yellow),
+ /// bg: Some(Color::Black),
+ /// add_modifier: Modifier::empty(),
+ /// sub_modifier: Modifier::empty(),
+ /// },
+ /// },
+ /// StyledGrapheme {
+ /// symbol: "e",
+ /// style: Style {
+ /// fg: Some(Color::Yellow),
+ /// bg: Some(Color::Black),
+ /// add_modifier: Modifier::empty(),
+ /// sub_modifier: Modifier::empty(),
+ /// },
+ /// },
+ /// StyledGrapheme {
+ /// symbol: "x",
+ /// style: Style {
+ /// fg: Some(Color::Yellow),
+ /// bg: Some(Color::Black),
+ /// add_modifier: Modifier::empty(),
+ /// sub_modifier: Modifier::empty(),
+ /// },
+ /// },
+ /// StyledGrapheme {
+ /// symbol: "t",
+ /// style: Style {
+ /// fg: Some(Color::Yellow),
+ /// bg: Some(Color::Black),
+ /// add_modifier: Modifier::empty(),
+ /// sub_modifier: Modifier::empty(),
+ /// },
+ /// },
+ /// ],
+ /// styled_graphemes.collect::<Vec<StyledGrapheme>>()
+ /// );
+ /// ```
+ pub fn styled_graphemes(
+ &'a self,
+ base_style: Style,
+ ) -> impl Iterator<Item = StyledGrapheme<'a>> {
+ UnicodeSegmentation::graphemes(self.content.as_ref(), true)
+ .map(move |g| StyledGrapheme {
+ symbol: g,
+ style: base_style.patch(self.style),
+ })
+ .filter(|s| s.symbol != "\n")
+ }
+}
+
+impl<'a> From<String> for Span<'a> {
+ fn from(s: String) -> Span<'a> {
+ Span::raw(s)
+ }
+}
+
+impl<'a> From<&'a str> for Span<'a> {
+ fn from(s: &'a str) -> Span<'a> {
+ Span::raw(s)
+ }
+}
+
+/// A string composed of clusters of graphemes, each with their own style.
+#[derive(Debug, Clone, PartialEq, Default, Eq)]
+pub struct Spans<'a>(pub Vec<Span<'a>>);
+
+impl<'a> Spans<'a> {
+ /// Returns the width of the underlying string.
+ ///
+ /// ## Examples
+ ///
+ /// ```rust
+ /// # use ratatui::text::{Span, Spans};
+ /// # use ratatui::style::{Color, Style};
+ /// let spans = Spans::from(vec![
+ /// Span::styled("My", Style::default().fg(Color::Yellow)),
+ /// Span::raw(" text"),
+ /// ]);
+ /// assert_eq!(7, spans.width());
+ /// ```
+ pub fn width(&self) -> usize {
+ self.0.iter().map(Span::width).sum()
+ }
+}
+
+impl<'a> From<String> for Spans<'a> {
+ fn from(s: String) -> Spans<'a> {
+ Spans(vec![Span::from(s)])
+ }
+}
+
+impl<'a> From<&'a str> for Spans<'a> {
+ fn from(s: &'a str) -> Spans<'a> {
+ Spans(vec![Span::from(s)])
+ }
+}
+
+impl<'a> From<Vec<Span<'a>>> for Spans<'a> {
+ fn from(spans: Vec<Span<'a>>) -> Spans<'a> {
+ Spans(spans)
+ }
+}
+
+impl<'a> From<Span<'a>> for Spans<'a> {
+ fn from(span: Span<'a>) -> Spans<'a> {
+ Spans(vec![span])
+ }
+}
+
+impl<'a> From<Spans<'a>> for String {
+ fn from(line: Spans<'a>) -> String {
+ line.0.iter().fold(String::new(), |mut acc, s| {
+ acc.push_str(s.content.as_ref());
+ acc
+ })
+ }
+}
+
+/// A string split over multiple lines where each line is composed of several clusters, each with
+/// their own style.
+///
+/// A [`Text`], like a [`Span`], can be constructed using one of the many `From` implementations
+/// or via the [`Text::raw`] and [`Text::styled`] methods. Helpfully, [`Text`] also implements
+/// [`core::iter::Extend`] which enables the concatenation of several [`Text`] blocks.
+///
+/// ```rust
+/// # use ratatui::text::Text;
+/// # use ratatui::style::{Color, Modifier, Style};
+/// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
+///
+/// // An initial two lines of `Text` built from a `&str`
+/// let mut text = Text::from("The first line\nThe second line");
+/// assert_eq!(2, text.height());
+///
+/// // Adding two more unstyled lines
+/// text.extend(Text::raw("These are two\nmore lines!"));
+/// assert_eq!(4, text.height());
+///
+/// // Adding a final two styled lines
+/// text.extend(Text::styled("Some more lines\nnow with more style!", style));
+/// assert_eq!(6, text.height());
+/// ```
+#[derive(Debug, Clone, PartialEq, Default, Eq)]
+pub struct Text<'a> {
+ pub lines: Vec<Spans<'a>>,
+}
+
+impl<'a> Text<'a> {
+ /// Create some text (potentially multiple lines) with no style.
+ ///
+ /// ## Examples
+ ///
+ /// ```rust
+ /// # use ratatui::text::Text;
+ /// Text::raw("The first line\nThe second line");
+ /// Text::raw(String::from("The first line\nThe second line"));
+ /// ```
+ pub fn raw<T>(content: T) -> Text<'a>
+ where
+ T: Into<Cow<'a, str>>,
+ {
+ let lines: Vec<_> = match content.into() {
+ Cow::Borrowed("") => vec![Spans::from("")],
+ Cow::Borrowed(s) => s.lines().map(Spans::from).collect(),
+ Cow::Owned(s) if s.is_empty() => vec![Spans::from("")],
+ Cow::Owned(s) => s.lines().map(|l| Spans::from(l.to_owned())).collect(),
+ };
+
+ Text { lines }
+ }
+
+ /// Create some text (potentially multiple lines) with a style.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use ratatui::text::Text;
+ /// # use ratatui::style::{Color, Modifier, Style};
+ /// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
+ /// Text::styled("The first line\nThe second line", style);
+ /// Text::styled(String::from("The first line\nThe second line"), style);
+ /// ```
+ pub fn styled<T>(content: T, style: Style) -> Text<'a>
+ where
+ T: Into<Cow<'a, str>>,
+ {
+ let mut text = Text::raw(content);
+ text.patch_style(style);
+ text
+ }
+
+ /// Returns the max width of all the lines.
+ ///
+ /// ## Examples
+ ///
+ /// ```rust
+ /// use ratatui::text::Text;
+ /// let text = Text::from("The first line\nThe second line");
+ /// assert_eq!(15, text.width());
+ /// ```
+ pub fn width(&self) -> usize {
+ self.lines
+ .iter()
+ .map(Spans::width)
+ .max()
+ .unwrap_or_default()
+ }
+
+ /// Returns the height.
+ ///
+ /// ## Examples
+ ///
+ /// ```rust
+ /// use ratatui::text::Text;
+ /// let text = Text::from("The first line\nThe second line");
+ /// assert_eq!(2, text.height());
+ /// ```
+ pub fn height(&self) -> usize {
+ self.lines.len()
+ }
+
+ /// Apply a new style to existing text.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use ratatui::text::Text;
+ /// # use ratatui::style::{Color, Modifier, Style};
+ /// let style = Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC);
+ /// let mut raw_text = Text::raw("The first line\nThe second line");
+ /// let styled_text = Text::styled(String::from("The first line\nThe second line"), style);
+ /// assert_ne!(raw_text, styled_text);
+ ///
+ /// raw_text.patch_style(style);
+ /// assert_eq!(raw_text, styled_text);
+ /// ```
+ pub fn patch_style(&mut self, style: Style) {
+ for line in &mut self.lines {
+ for span in &mut line.0 {
+ span.style = span.style.patch(style);
+ }
+ }
+ }
+}
+
+impl<'a> From<String> for Text<'a> {
+ fn from(s: String) -> Text<'a> {
+ Text::raw(s)
+ }
+}
+
+impl<'a> From<&'a str> for Text<'a> {
+ fn from(s: &'a str) -> Text<'a> {
+ Text::raw(s)
+ }
+}
+
+impl<'a> From<Cow<'a, str>> for Text<'a> {
+ fn from(s: Cow<'a, str>) -> Text<'a> {
+ Text::raw(s)
+ }
+}
+
+impl<'a> From<Span<'a>> for Text<'a> {
+ fn from(span: Span<'a>) -> Text<'a> {
+ Text {
+ lines: vec![Spans::from(span)],
+ }
+ }
+}
+
+impl<'a> From<Spans<'a>> for Text<'a> {
+ fn from(spans: Spans<'a>) -> Text<'a> {
+ Text { lines: vec![spans] }
+ }
+}
+
+impl<'a> From<Vec<Spans<'a>>> for Text<'a> {
+ fn from(lines: Vec<Spans<'a>>) -> Text<'a> {
+ Text { lines }
+ }
+}
+
+impl<'a> IntoIterator for Text<'a> {
+ type Item = Spans<'a>;
+ type IntoIter = std::vec::IntoIter<Self::Item>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.lines.into_iter()
+ }
+}
+
+impl<'a> Extend<Spans<'a>> for Text<'a> {
+ fn extend<T: IntoIterator<Item = Spans<'a>>>(&mut self, iter: T) {
+ self.lines.extend(iter);
+ }
+}
diff --git a/src/ratatui/widgets/barchart.rs b/src/ratatui/widgets/barchart.rs
new file mode 100644
index 00000000..13f386a4
--- /dev/null
+++ b/src/ratatui/widgets/barchart.rs
@@ -0,0 +1,219 @@
+use crate::ratatui::{
+ buffer::Buffer,
+ layout::Rect,
+ style::Style,
+ symbols,
+ widgets::{Block, Widget},
+};
+use std::cmp::min;
+use unicode_width::UnicodeWidthStr;
+
+/// Display multiple bars in a single widgets
+///
+/// # Examples
+///
+/// ```
+/// # use ratatui::widgets::{Block, Borders, BarChart};
+/// # use ratatui::style::{Style, Color, Modifier};
+/// BarChart::default()
+/// .block(Block::default().title("BarChart").borders(Borders::ALL))
+/// .bar_width(3)
+/// .bar_gap(1)
+/// .bar_style(Style::default().fg(Color::Yellow).bg(Color::Red))
+/// .value_style(Style::default().fg(Color::Red).add_modifier(Modifier::BOLD))
+/// .label_style(Style::default().fg(Color::White))
+/// .data(&[("B0", 0), ("B1", 2), ("B2", 4), ("B3", 3)])
+/// .max(4);
+/// ```
+#[derive(Debug, Clone)]
+pub struct BarChart<'a> {
+ /// Block to wrap the widget in
+ block: Option<Block<'a>>,
+ /// The width of each bar
+ bar_width: u16,
+ /// The gap between each bar
+ bar_gap: u16,
+ /// Set of symbols used to display the data
+ bar_set: symbols::bar::Set,
+ /// Style of the bars
+ bar_style: Style,
+ /// Style of the values printed at the bottom of each bar
+ value_style: Style,
+ /// Style of the labels printed under each bar
+ label_style: Style,
+ /// Style for the widget
+ style: Style,
+ /// Slice of (label, value) pair to plot on the chart
+ data: &'a [(&'a str, u64)],
+ /// Value necessary for a bar to reach the maximum height (if no value is specified,
+ /// the maximum value in the data is taken as reference)
+ max: Option<u64>,
+ /// Values to display on the bar (computed when the data is passed to the widget)
+ values: Vec<String>,
+}
+
+impl<'a> Default for BarChart<'a> {
+ fn default() -> BarChart<'a> {
+ BarChart {
+ block: None,
+ max: None,
+ data: &[],
+ values: Vec::new(),
+ bar_style: Style::default(),
+ bar_width: 1,
+ bar_gap: 1,
+ bar_set: symbols::bar::NINE_LEVELS,
+ value_style: Default::default(),
+ label_style: Default::default(),
+ style: Default::default(),
+ }
+ }
+}
+
+impl<'a> BarChart<'a> {
+ pub fn data(mut self, data: &'a [(&'a str, u64)]) -> BarChart<'a> {
+ self.data = data;
+ self.values = Vec::with_capacity(self.data.len());
+ for &(_, v) in self.data {
+ self.values.push(format!("{}", v));
+ }
+ self
+ }
+
+ pub fn block(mut self, block: Block<'a>) -> BarChart<'a> {
+ self.block = Some(block);
+ self
+ }
+
+ pub fn max(mut self, max: u64) -> BarChart<'a> {
+ self.max = Some(max);
+ self
+ }
+
+ pub fn bar_style(mut self, style: Style) -> BarChart<'a> {
+ self.bar_style = style;
+ self
+ }
+
+ pub fn bar_width(mut self, width: u16) -> BarChart<'a> {
+ self.bar_width = width;
+ self
+ }
+
+ pub fn bar_gap(mut self, gap: u16) -> BarChart<'a> {
+ self.bar_gap = gap;
+ self
+ }
+
+ pub fn bar_set(mut self, bar_set: symbols::bar::Set) -> BarChart<'a> {
+ self.bar_set = bar_set;
+ self
+ }
+
+ pub fn value_style(mut self, style: Style) -> BarChart<'a> {
+ self.value_style = style;
+ self
+ }
+
+ pub fn label_style(mut self, style: Style) -> BarChart<'a> {
+ self.label_style = style;
+ self
+ }
+
+ pub fn style(mut self, style: Style) -> BarChart<'a> {
+ self.style = style;
+ self
+ }
+}
+
+impl<'a> Widget for BarChart<'a> {
+ fn render(mut self, area: Rect, buf: &mut Buffer) {
+ buf.set_style(area, self.style);
+
+ let chart_area = match self.block.take() {
+ Some(b) => {
+ let inner_area = b.inner(area);
+ b.render(area, buf);
+ inner_area
+ }
+ None => area,
+ };
+
+ if chart_area.height < 2 {
+ return;
+ }
+
+ let max = self
+ .max
+ .unwrap_or_else(|| self.data.iter().map(|t| t.1).max().unwrap_or_default());
+ let max_index = min(
+ (chart_area.width / (self.bar_width + self.bar_gap)) as usize,
+ self.data.len(),
+ );
+ let mut data = self
+ .data
+ .iter()
+ .take(max_index)
+ .map(|&(l, v)| {
+ (
+ l,
+ v * u64::from(chart_area.height - 1) * 8 / std::cmp::max(max, 1),
+ )
+ })
+ .collect::<Vec<(&str, u64)>>();
+ for j in (0..chart_area.height - 1).rev() {
+ for (i, d) in data.iter_mut().enumerate() {
+ let symbol = match d.1 {
+ 0 => self.bar_set.empty,
+ 1 => self.bar_set.one_eighth,
+ 2 => self.bar_set.one_quarter,
+ 3 => self.bar_set.three_eighths,
+ 4 => self.bar_set.half,
+ 5 => self.bar_set.five_eighths,
+ 6 => self.bar_set.three_quarters,
+ 7 => self.bar_set.seven_eighths,
+ _ => self.bar_set.full,
+ };
+
+ for x in 0..self.bar_width {
+ buf.get_mut(
+ chart_area.left() + i as u16 * (self.bar_width + self.bar_gap) + x,
+ chart_area.top() + j,
+ )
+ .set_symbol(symbol)
+ .set_style(self.bar_style);
+ }
+
+ if d.1 > 8 {
+ d.1 -= 8;
+ } else {
+ d.1 = 0;
+ }
+ }
+ }
+
+ for (i, &(label, value)) in self.data.iter().take(max_index).enumerate() {
+ if value != 0 {
+ let value_label = &self.values[i];
+ let width = value_label.width() as u16;
+ if width < self.bar_width {
+ buf.set_string(
+ chart_area.left()
+ + i as u16 * (self.bar_width + self.bar_gap)
+ + (self.bar_width - width) / 2,
+ chart_area.bottom() - 2,
+ value_label,
+ self.value_style,
+ );
+ }
+ }
+ buf.set_stringn(
+ chart_area.left() + i as u16 * (self.bar_width + self.bar_gap),
+ chart_area.bottom() - 1,
+ label,
+ self.bar_width as usize,
+ self.label_style,
+ );
+ }
+ }
+}
diff --git a/src/ratatui/widgets/block.rs b/src/ratatui/widgets/block.rs
new file mode 100644
index 00000000..20b43614
--- /dev/null
+++ b/src/ratatui/widgets/block.rs
@@ -0,0 +1,573 @@
+use crate::ratatui::{
+ buffer::Buffer,
+ layout::{Alignment, Rect},
+ style::Style,
+ symbols::line,
+ text::{Span, Spans},
+ widgets::{Borders, Widget},
+};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum BorderType {
+ Plain,
+ Rounded,
+ Double,
+ Thick,
+}
+
+impl BorderType {
+ pub fn line_symbols(border_type: BorderType) -> line::Set {
+ match border_type {
+ BorderType::Plain => line::NORMAL,
+ BorderType::Rounded => line::ROUNDED,
+ BorderType::Double => line::DOUBLE,
+ BorderType::Thick => line::THICK,
+ }
+ }
+}
+
+/// Base widget to be used with all upper level ones. It may be used to display a box border around
+/// the widget and/or add a title.
+///
+/// # Examples
+///
+/// ```
+/// # use ratatui::widgets::{Block, BorderType, Borders};
+/// # use ratatui::style::{Style, Color};
+/// Block::default()
+/// .title("Block")
+/// .borders(Borders::LEFT | Borders::RIGHT)
+/// .border_style(Style::default().fg(Color::White))
+/// .border_type(BorderType::Rounded)
+/// .style(Style::default().bg(Color::Black));
+/// ```
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Block<'a> {
+ /// Optional title place on the upper left of the block
+ title: Option<Spans<'a>>,
+ /// Title alignment. The default is top left of the block, but one can choose to place
+ /// title in the top middle, or top right of the block
+ title_alignment: Alignment,
+ /// Visible borders
+ borders: Borders,
+ /// Border style
+ border_style: Style,
+ /// Type of the border. The default is plain lines but one can choose to have rounded corners
+ /// or doubled lines instead.
+ border_type: BorderType,
+ /// Widget style
+ style: Style,
+}
+
+impl<'a> Default for Block<'a> {
+ fn default() -> Block<'a> {
+ Block {
+ title: None,
+ title_alignment: Alignment::Left,
+ borders: Borders::NONE,
+ border_style: Default::default(),
+ border_type: BorderType::Plain,
+ style: Default::default(),
+ }
+ }
+}
+
+impl<'a> Block<'a> {
+ pub fn title<T>(mut self, title: T) -> Block<'a>
+ where
+ T: Into<Spans<'a>>,
+ {
+ self.title = Some(title.into());
+ self
+ }
+
+ #[deprecated(
+ since = "0.10.0",
+ note = "You should use styling capabilities of `text::Spans` given as argument of the `title` method to apply styling to the title."
+ )]
+ pub fn title_style(mut self, style: Style) -> Block<'a> {
+ if let Some(t) = self.title {
+ let title = String::from(t);
+ self.title = Some(Spans::from(Span::styled(title, style)));
+ }
+ self
+ }
+
+ pub fn title_alignment(mut self, alignment: Alignment) -> Block<'a> {
+ self.title_alignment = alignment;
+ self
+ }
+
+ pub fn border_style(mut self, style: Style) -> Block<'a> {
+ self.border_style = style;
+ self
+ }
+
+ pub fn style(mut self, style: Style) -> Block<'a> {
+ self.style = style;
+ self
+ }
+
+ pub fn borders(mut self, flag: Borders) -> Block<'a> {
+ self.borders = flag;
+ self
+ }
+
+ pub fn border_type(mut self, border_type: BorderType) -> Block<'a> {
+ self.border_type = border_type;
+ self
+ }
+
+ /// Compute the inner area of a block based on its border visibility rules.
+ pub fn inner(&self, area: Rect) -> Rect {
+ let mut inner = area;
+ if self.borders.intersects(Borders::LEFT) {
+ inner.x = inner.x.saturating_add(1).min(inner.right());
+ inner.width = inner.width.saturating_sub(1);
+ }
+ if self.borders.intersects(Borders::TOP) || self.title.is_some() {
+ inner.y = inner.y.saturating_add(1).min(inner.bottom());
+ inner.height = inner.height.saturating_sub(1);
+ }
+ if self.borders.intersects(Borders::RIGHT) {
+ inner.width = inner.width.saturating_sub(1);
+ }
+ if self.borders.intersects(Borders::BOTTOM) {
+ inner.height = inner.height.saturating_sub(1);
+ }
+ inner
+ }
+}
+
+impl<'a> Widget for Block<'a> {
+ fn render(self, area: Rect, buf: &mut Buffer) {
+ if area.area() == 0 {
+ return;
+ }
+ buf.set_style(area, self.style);
+ let symbols = BorderType::line_symbols(self.border_type);
+
+ // Sides
+ if self.borders.intersects(Borders::LEFT) {
+ for y in area.top()..area.bottom() {
+ buf.get_mut(area.left(), y)
+ .set_symbol(symbols.vertical)
+ .set_style(self.border_style);
+ }
+ }
+ if self.borders.intersects(Borders::TOP) {
+ for x in area.left()..area.right() {
+ buf.get_mut(x, area.top())
+ .set_symbol(symbols.horizontal)
+ .set_style(self.border_style);
+ }
+ }
+ if self.borders.intersects(Borders::RIGHT) {
+ let x = area.right() - 1;
+ for y in area.top()..area.bottom() {
+ buf.get_mut(x, y)
+ .set_symbol(symbols.vertical)
+ .set_style(self.border_style);
+ }
+ }
+ if self.borders.intersects(Borders::BOTTOM) {
+ let y = area.bottom() - 1;
+ for x in area.left()..area.right() {
+ buf.get_mut(x, y)
+ .set_symbol(symbols.horizontal)
+ .set_style(self.border_style);
+ }
+ }
+
+ // Corners
+ if self.borders.contains(Borders::RIGHT | Borders::BOTTOM) {
+ buf.get_mut(area.right() - 1, area.bottom() - 1)
+ .set_symbol(symbols.bottom_right)
+ .set_style(self.border_style);
+ }
+ if self.borders.contains(Borders::RIGHT | Borders::TOP) {
+ buf.get_mut(area.right() - 1, area.top())
+ .set_symbol(symbols.top_right)
+ .set_style(self.border_style);
+ }
+ if self.borders.contains(Borders::LEFT | Borders::BOTTOM) {
+ buf.get_mut(area.left(), area.bottom() - 1)
+ .set_symbol(symbols.bottom_left)
+ .set_style(self.border_style);
+ }
+ if self.borders.contains(Borders::LEFT | Borders::TOP) {
+ buf.get_mut(area.left(), area.top())
+ .set_symbol(symbols.top_left)
+ .set_style(self.border_style);
+ }
+
+ // Title
+ if let Some(title) = self.title {
+ let left_border_dx = if self.borders.intersects(Borders::LEFT) {
+ 1
+ } else {
+ 0
+ };
+
+ let right_border_dx = if self.borders.intersects(Borders::RIGHT) {
+ 1
+ } else {
+ 0
+ };
+
+ let title_area_width = area
+ .width
+ .saturating_sub(left_border_dx)
+ .saturating_sub(right_border_dx);
+
+ let title_dx = match self.title_alignment {
+ Alignment::Left => left_border_dx,
+ Alignment::Center => area.width.saturating_sub(title.width() as u16) / 2,
+ Alignment::Right => area
+ .width
+ .saturating_sub(title.width() as u16)
+ .saturating_sub(right_border_dx),
+ };
+
+ let title_x = area.left() + title_dx;
+ let title_y = area.top();
+
+ buf.set_spans(title_x, title_y, &title, title_area_width);
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::ratatui::layout::Rect;
+
+ #[test]
+ fn inner_takes_into_account_the_borders() {
+ // No borders
+ assert_eq!(
+ Block::default().inner(Rect::default()),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0
+ },
+ "no borders, width=0, height=0"
+ );
+ assert_eq!(
+ Block::default().inner(Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 1
+ }),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 1
+ },
+ "no borders, width=1, height=1"
+ );
+
+ // Left border
+ assert_eq!(
+ Block::default().borders(Borders::LEFT).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 1
+ }),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 1
+ },
+ "left, width=0"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::LEFT).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 1
+ }),
+ Rect {
+ x: 1,
+ y: 0,
+ width: 0,
+ height: 1
+ },
+ "left, width=1"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::LEFT).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 2,
+ height: 1
+ }),
+ Rect {
+ x: 1,
+ y: 0,
+ width: 1,
+ height: 1
+ },
+ "left, width=2"
+ );
+
+ // Top border
+ assert_eq!(
+ Block::default().borders(Borders::TOP).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 0
+ }),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 0
+ },
+ "top, height=0"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::TOP).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 1
+ }),
+ Rect {
+ x: 0,
+ y: 1,
+ width: 1,
+ height: 0
+ },
+ "top, height=1"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::TOP).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 2
+ }),
+ Rect {
+ x: 0,
+ y: 1,
+ width: 1,
+ height: 1
+ },
+ "top, height=2"
+ );
+
+ // Right border
+ assert_eq!(
+ Block::default().borders(Borders::RIGHT).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 1
+ }),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 1
+ },
+ "right, width=0"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::RIGHT).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 1
+ }),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 1
+ },
+ "right, width=1"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::RIGHT).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 2,
+ height: 1
+ }),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 1
+ },
+ "right, width=2"
+ );
+
+ // Bottom border
+ assert_eq!(
+ Block::default().borders(Borders::BOTTOM).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 0
+ }),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 0
+ },
+ "bottom, height=0"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::BOTTOM).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 1
+ }),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 0
+ },
+ "bottom, height=1"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::BOTTOM).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 2
+ }),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 1
+ },
+ "bottom, height=2"
+ );
+
+ // All borders
+ assert_eq!(
+ Block::default()
+ .borders(Borders::ALL)
+ .inner(Rect::default()),
+ Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0
+ },
+ "all borders, width=0, height=0"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::ALL).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 1
+ }),
+ Rect {
+ x: 1,
+ y: 1,
+ width: 0,
+ height: 0,
+ },
+ "all borders, width=1, height=1"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::ALL).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 2,
+ height: 2,
+ }),
+ Rect {
+ x: 1,
+ y: 1,
+ width: 0,
+ height: 0,
+ },
+ "all borders, width=2, height=2"
+ );
+ assert_eq!(
+ Block::default().borders(Borders::ALL).inner(Rect {
+ x: 0,
+ y: 0,
+ width: 3,
+ height: 3,
+ }),
+ Rect {
+ x: 1,
+ y: 1,
+ width: 1,
+ height: 1,
+ },
+ "all borders, width=3, height=3"
+ );
+ }
+
+ #[test]
+ fn inner_takes_into_account_the_title() {
+ assert_eq!(
+ Block::default().title("Test").inner(Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 1,
+ }),
+ Rect {
+ x: 0,
+ y: 1,
+ width: 0,
+ height: 0,
+ },
+ );
+ assert_eq!(
+ Block::default()
+ .title("Test")
+ .title_alignment(Alignment::Center)
+ .inner(Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 1,
+ }),
+ Rect {
+ x: 0,
+ y: 1,
+ width: 0,
+ height: 0,
+ },
+ );
+ assert_eq!(
+ Block::default()
+ .title("Test")
+ .title_alignment(Alignment::Right)
+ .inner(Rect {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 1,
+ }),
+ Rect {
+ x: 0,
+ y: 1,
+ width: 0,
+ height: 0,
+ },
+ );
+ }
+}
diff --git a/src/ratatui/widgets/canvas/line.rs b/src/ratatui/widgets/canvas/line.rs
new file mode 100644
index 00000000..84d121ce
--- /dev/null
+++ b/src/ratatui/widgets/canvas/line.rs
@@ -0,0 +1,95 @@
+use crate::ratatui::{
+ style::Color,
+ widgets::canvas::{Painter, Shape},
+};
+
+/// Shape to draw a line from (x1, y1) to (x2, y2) with the given color
+#[derive(Debug, Clone)]
+pub struct Line {
+ pub x1: f64,
+ pub y1: f64,
+ pub x2: f64,
+ pub y2: f64,
+ pub color: Color,
+}
+
+impl Shape for Line {
+ fn draw(&self, painter: &mut Painter) {
+ let (x1, y1) = match painter.get_point(self.x1, self.y1) {
+ Some(c) => c,
+ None => return,
+ };
+ let (x2, y2) = match painter.get_point(self.x2, self.y2) {
+ Some(c) => c,
+ None => return,
+ };
+ let (dx, x_range) = if x2 >= x1 {
+ (x2 - x1, x1..=x2)
+ } else {
+ (x1 - x2, x2..=x1)
+ };
+ let (dy, y_range) = if y2 >= y1 {
+ (y2 - y1, y1..=y2)
+ } else {
+ (y1 - y2, y2..=y1)
+ };
+
+ if dx == 0 {
+ for y in y_range {
+ painter.paint(x1, y, self.color);
+ }
+ } else if dy == 0 {
+ for x in x_range {
+ painter.paint(x, y1, self.color);
+ }
+ } else if dy < dx {
+ if x1 > x2 {
+ draw_line_low(painter, x2, y2, x1, y1, self.color);
+ } else {
+ draw_line_low(painter, x1, y1, x2, y2, self.color);
+ }
+ } else if y1 > y2 {
+ draw_line_high(painter, x2, y2, x1, y1, self.color);
+ } else {
+ draw_line_high(painter, x1, y1, x2, y2, self.color);
+ }
+ }
+}
+
+fn draw_line_low(painter: &mut Painter, x1: usize, y1: usize, x2: usize, y2: usize, color: Color) {
+ let dx = (x2 - x1) as isize;
+ let dy = (y2 as isize - y1 as isize).abs();
+ let mut d = 2 * dy - dx;
+ let mut y = y1;
+ for x in x1..=x2 {
+ painter.paint(x, y, color);
+ if d > 0 {
+ y = if y1 > y2 {
+ y.saturating_sub(1)
+ } else {
+ y.saturating_add(1)
+ };
+ d -= 2 * dx;
+ }
+ d += 2 * dy;
+ }
+}
+
+fn draw_line_high(painter: &mut Painter, x1: usize, y1: usize, x2: usize, y2: usize, color: Color) {
+ let dx = (x2 as isize - x1 as isize).abs();
+ let dy = (y2 - y1) as isize;
+ let mut d = 2 * dx - dy;
+ let mut x = x1;
+ for y in y1..=y2 {
+ painter.paint(x, y, color);
+ if d > 0 {
+ x = if x1 > x2 {
+ x.saturating_sub(1)
+ } else {
+ x.saturating_add(1)
+ };
+ d -= 2 * dy;
+ }
+ d += 2 * dx;
+ }
+}
diff --git a/src/ratatui/widgets/canvas/map.rs b/src/ratatui/widgets/canvas/map.rs
new file mode 100644
index 00000000..d835dd31
--- /dev/null
+++ b/src/ratatui/widgets/canvas/map.rs
@@ -0,0 +1,48 @@
+use crate::ratatui::{
+ style::Color,
+ widgets::canvas::{
+ world::{WORLD_HIGH_RESOLUTION, WORLD_LOW_RESOLUTION},
+ Painter, Shape,
+ },
+};
+
+#[derive(Debug, Clone, Copy)]
+pub enum MapResolution {
+ Low,
+ High,
+}
+
+impl MapResolution {
+ fn data(self) -> &'static [(f64, f64)] {
+ match self {
+ MapResolution::Low => &WORLD_LOW_RESOLUTION,
+ MapResolution::High => &WORLD_HIGH_RESOLUTION,
+ }
+ }
+}
+
+/// Shape to draw a world map with the given resolution and color
+#[derive(Debug, Clone)]
+pub struct Map {
+ pub resolution: MapResolution,
+ pub color: Color,
+}
+
+impl Default for Map {
+ fn default() -> Map {
+ Map {
+ resolution: MapResolution::Low,
+ color: Color::Reset,
+ }
+ }
+}
+
+impl Shape for Map {
+ fn draw(&self, painter: &mut Painter) {
+ for (x, y) in self.resolution.data() {
+ if let Some((x, y)) = painter.get_point(*x, *y) {
+ painter.paint(x, y, self.color);
+ }
+ }
+ }
+}
diff --git a/src/ratatui/widgets/canvas/mod.rs b/src/ratatui/widgets/canvas/mod.rs
new file mode 100644
index 00000000..7b174596
--- /dev/null
+++ b/src/ratatui/widgets/canvas/mod.rs
@@ -0,0 +1,510 @@
+mod line;
+mod map;
+mod points;
+mod rectangle;
+mod world;
+
+pub use self::line::Line;
+pub use self::map::{Map, MapResolution};
+pub use self::points::Points;
+pub use self::rectangle::Rectangle;
+
+use crate::ratatui::{
+ buffer::Buffer,
+ layout::Rect,
+ style::{Color, Style},
+ symbols,
+ text::Spans,
+ widgets::{Block, Widget},
+};
+use std::fmt::Debug;
+
+/// Interface for all shapes that may be drawn on a Canvas widget.
+pub trait Shape {
+ fn draw(&self, painter: &mut Painter);
+}
+
+/// Label to draw some text on the canvas
+#[derive(Debug, Clone)]
+pub struct Label<'a> {
+ x: f64,
+ y: f64,
+ spans: Spans<'a>,
+}
+
+#[derive(Debug, Clone)]
+struct Layer {
+ string: String,
+ colors: Vec<Color>,
+}
+
+trait Grid: Debug {
+ fn width(&self) -> u16;
+ fn height(&self) -> u16;
+ fn resolution(&self) -> (f64, f64);
+ fn paint(&mut self, x: usize, y: usize, color: Color);
+ fn save(&self) -> Layer;
+ fn reset(&mut self);
+}
+
+#[derive(Debug, Clone)]
+struct BrailleGrid {
+ width: u16,
+ height: u16,
+ cells: Vec<u16>,
+ colors: Vec<Color>,
+}
+
+impl BrailleGrid {
+ fn new(width: u16, height: u16) -> BrailleGrid {
+ let length = usize::from(width * height);
+ BrailleGrid {
+ width,
+ height,
+ cells: vec![symbols::braille::BLANK; length],
+ colors: vec![Color::Reset; length],
+ }
+ }
+}
+
+impl Grid for BrailleGrid {
+ fn width(&self) -> u16 {
+ self.width
+ }
+
+ fn height(&self) -> u16 {
+ self.height
+ }
+
+ fn resolution(&self) -> (f64, f64) {
+ (
+ f64::from(self.width) * 2.0 - 1.0,
+ f64::from(self.height) * 4.0 - 1.0,
+ )
+ }
+
+ fn save(&self) -> Layer {
+ Layer {
+ string: String::from_utf16(&self.cells).unwrap(),
+ colors: self.colors.clone(),
+ }
+ }
+
+ fn reset(&mut self) {
+ for c in &mut self.cells {
+ *c = symbols::braille::BLANK;
+ }
+ for c in &mut self.colors {
+ *c = Color::Reset;
+ }
+ }
+
+ fn paint(&mut self, x: usize, y: usize, color: Color) {
+ let index = y / 4 * self.width as usize + x / 2;
+ if let Some(c) = self.cells.get_mut(index) {
+ *c |= symbols::braille::DOTS[y % 4][x % 2];
+ }
+ if let Some(c) = self.colors.get_mut(index) {
+ *c = color;
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+struct CharGrid {
+ width: u16,
+ height: u16,
+ cells: Vec<char>,
+ colors: Vec<Color>,
+ cell_char: char,
+}
+
+impl CharGrid {
+ fn new(width: u16, height: u16, cell_char: char) -> CharGrid {
+ let length = usize::from(width * height);
+ CharGrid {
+ width,
+ height,
+ cells: vec![' '; length],
+ colors: vec![Color::Reset; length],
+ cell_char,
+ }
+ }
+}
+
+impl Grid for CharGrid {
+ fn width(&self) -> u16 {
+ self.width
+ }
+
+ fn height(&self) -> u16 {
+ self.height
+ }
+
+ fn resolution(&self) -> (f64, f64) {
+ (f64::from(self.width) - 1.0, f64::from(self.height) - 1.0)
+ }
+
+ fn save(&self) -> Layer {
+ Layer {
+ string: self.cells.iter().collect(),
+ colors: self.colors.clone(),
+ }
+ }
+
+ fn reset(&mut self) {
+ for c in &mut self.cells {
+ *c = ' ';
+ }
+ for c in &mut self.colors {
+ *c = Color::Reset;
+ }
+ }
+
+ fn paint(&mut self, x: usize, y: usize, color: Color) {
+ let index = y * self.width as usize + x;
+ if let Some(c) = self.cells.get_mut(index) {
+ *c = self.cell_char;
+ }
+ if let Some(c) = self.colors.get_mut(index) {
+ *c = color;
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct Painter<'a, 'b> {
+ context: &'a mut Context<'b>,
+ resolution: (f64, f64),
+}
+
+impl<'a, 'b> Painter<'a, 'b> {
+ /// Convert the (x, y) coordinates to location of a point on the grid
+ ///
+ /// # Examples:
+ /// ```
+ /// use ratatui::{symbols, widgets::canvas::{Painter, Context}};
+ ///
+ /// let mut ctx = Context::new(2, 2, [1.0, 2.0], [0.0, 2.0], symbols::Marker::Braille);
+ /// let mut painter = Painter::from(&mut ctx);
+ /// let point = painter.get_point(1.0, 0.0);
+ /// assert_eq!(point, Some((0, 7)));
+ /// let point = painter.get_point(1.5, 1.0);
+ /// assert_eq!(point, Some((1, 3)));
+ /// let point = painter.get_point(0.0, 0.0);
+ /// assert_eq!(point, None);
+ /// let point = painter.get_point(2.0, 2.0);
+ /// assert_eq!(point, Some((3, 0)));
+ /// let point = painter.get_point(1.0, 2.0);
+ /// assert_eq!(point, Some((0, 0)));
+ /// ```
+ pub fn get_point(&self, x: f64, y: f64) -> Option<(usize, usize)> {
+ let left = self.context.x_bounds[0];
+ let right = self.context.x_bounds[1];
+ let top = self.context.y_bounds[1];
+ let bottom = self.context.y_bounds[0];
+ if x < left || x > right || y < bottom || y > top {
+ return None;
+ }
+ let width = (self.context.x_bounds[1] - self.context.x_bounds[0]).abs();
+ let height = (self.context.y_bounds[1] - self.context.y_bounds[0]).abs();
+ if width == 0.0 || height == 0.0 {
+ return None;
+ }
+ let x = ((x - left) * self.resolution.0 / width) as usize;
+ let y = ((top - y) * self.resolution.1 / height) as usize;
+ Some((x, y))
+ }
+
+ /// Paint a point of the grid
+ ///
+ /// # Examples:
+ /// ```
+ /// use ratatui::{style::Color, symbols, widgets::canvas::{Painter, Context}};
+ ///
+ /// let mut ctx = Context::new(1, 1, [0.0, 2.0], [0.0, 2.0], symbols::Marker::Braille);
+ /// let mut painter = Painter::from(&mut ctx);
+ /// let cell = painter.paint(1, 3, Color::Red);
+ /// ```
+ pub fn paint(&mut self, x: usize, y: usize, color: Color) {
+ self.context.grid.paint(x, y, color);
+ }
+}
+
+impl<'a, 'b> From<&'a mut Context<'b>> for Painter<'a, 'b> {
+ fn from(context: &'a mut Context<'b>) -> Painter<'a, 'b> {
+ let resolution = context.grid.resolution();
+ Painter {
+ context,
+ resolution,
+ }
+ }
+}
+
+/// Holds the state of the Canvas when painting to it.
+#[derive(Debug)]
+pub struct Context<'a> {
+ x_bounds: [f64; 2],
+ y_bounds: [f64; 2],
+ grid: Box<dyn Grid>,
+ dirty: bool,
+ layers: Vec<Layer>,
+ labels: Vec<Label<'a>>,
+}
+
+impl<'a> Context<'a> {
+ pub fn new(
+ width: u16,
+ height: u16,
+ x_bounds: [f64; 2],
+ y_bounds: [f64; 2],
+ marker: symbols::Marker,
+ ) -> Context<'a> {
+ let grid: Box<dyn Grid> = match marker {
+ symbols::Marker::Dot => Box::new(CharGrid::new(width, height, '•')),
+ symbols::Marker::Block => Box::new(CharGrid::new(width, height, '▄')),
+ symbols::Marker::Braille => Box::new(BrailleGrid::new(width, height)),
+ };
+ Context {
+ x_bounds,
+ y_bounds,
+ grid,
+ dirty: false,
+ layers: Vec::new(),
+ labels: Vec::new(),
+ }
+ }
+
+ /// Draw any object that may implement the Shape trait
+ pub fn draw<S>(&mut self, shape: &S)
+ where
+ S: Shape,
+ {
+ self.dirty = true;
+ let mut painter = Painter::from(self);
+ shape.draw(&mut painter);
+ }
+
+ /// Go one layer above in the canvas.
+ pub fn layer(&mut self) {
+ self.layers.push(self.grid.save());
+ self.grid.reset();
+ self.dirty = false;
+ }
+
+ /// Print a string on the canvas at the given position
+ pub fn print<T>(&mut self, x: f64, y: f64, spans: T)
+ where
+ T: Into<Spans<'a>>,
+ {
+ self.labels.push(Label {
+ x,
+ y,
+ spans: spans.into(),
+ });
+ }
+
+ /// Push the last layer if necessary
+ fn finish(&mut self) {
+ if self.dirty {
+ self.layer()
+ }
+ }
+}
+
+/// The Canvas widget may be used to draw more detailed figures using braille patterns (each
+/// cell can have a braille character in 8 different positions).
+/// # Examples
+///
+/// ```
+/// # use ratatui::widgets::{Block, Borders};
+/// # use ratatui::layout::Rect;
+/// # use ratatui::widgets::canvas::{Canvas, Shape, Line, Rectangle, Map, MapResolution};
+/// # use ratatui::style::Color;
+/// Canvas::default()
+/// .block(Block::default().title("Canvas").borders(Borders::ALL))
+/// .x_bounds([-180.0, 180.0])
+/// .y_bounds([-90.0, 90.0])
+/// .paint(|ctx| {
+/// ctx.draw(&Map {
+/// resolution: MapResolution::High,
+/// color: Color::White
+/// });
+/// ctx.layer();
+/// ctx.draw(&Line {
+/// x1: 0.0,
+/// y1: 10.0,
+/// x2: 10.0,
+/// y2: 10.0,
+/// color: Color::White,
+/// });
+/// ctx.draw(&Rectangle {
+/// x: 10.0,
+/// y: 20.0,
+/// width: 10.0,
+/// height: 10.0,
+/// color: Color::Red
+/// });
+/// });
+/// ```
+pub struct Canvas<'a, F>
+where
+ F: Fn(&mut Context),
+{
+ block: Option<Block<'a>>,
+ x_bounds: [f64; 2],
+ y_bounds: [f64; 2],
+ painter: Option<F>,
+ background_color: Color,
+ marker: symbols::Marker,
+}
+
+impl<'a, F> Default for Canvas<'a, F>
+where
+ F: Fn(&mut Context),
+{
+ fn default() -> Canvas<'a, F> {
+ Canvas {
+ block: None,
+ x_bounds: [0.0, 0.0],
+ y_bounds: [0.0, 0.0],
+ painter: None,
+ background_color: Color::Reset,
+ marker: symbols::Marker::Braille,
+ }
+ }
+}
+
+impl<'a, F> Canvas<'a, F>
+where
+ F: Fn(&mut Context),
+{
+ pub fn block(mut self, block: Block<'a>) -> Canvas<'a, F> {
+ self.block = Some(block);
+ self
+ }
+
+ /// Define the viewport of the canvas.
+ /// If you were to "zoom" to a certain part of the world you may want to choose different
+ /// bounds.
+ pub fn x_bounds(mut self, bounds: [f64; 2]) -> Canvas<'a, F> {
+ self.x_bounds = bounds;
+ self
+ }
+
+ /// Define the viewport of the canvas.
+ ///
+ /// If you were to "zoom" to a certain part of the world you may want to choose different
+ /// bounds.
+ pub fn y_bounds(mut self, bounds: [f64; 2]) -> Canvas<'a, F> {
+ self.y_bounds = bounds;
+ self
+ }
+
+ /// Store the closure that will be used to draw to the Canvas
+ pub fn paint(mut self, f: F) -> Canvas<'a, F> {
+ self.painter = Some(f);
+ self
+ }
+
+ pub fn background_color(mut self, color: Color) -> Canvas<'a, F> {
+ self.background_color = color;
+ self
+ }
+
+ /// Change the type of points used to draw the shapes. By default the braille patterns are used
+ /// as they provide a more fine grained result but you might want to use the simple dot or
+ /// block instead if the targeted terminal does not support those symbols.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use ratatui::widgets::canvas::Canvas;
+ /// # use ratatui::symbols;
+ /// Canvas::default().marker(symbols::Marker::Braille).paint(|ctx| {});
+ ///
+ /// Canvas::default().marker(symbols::Marker::Dot).paint(|ctx| {});
+ ///
+ /// Canvas::default().marker(symbols::Marker::Block).paint(|ctx| {});
+ /// ```
+ pub fn marker(mut self, marker: symbols::Marker) -> Canvas<'a, F> {
+ self.marker = marker;
+ self
+ }
+}
+
+impl<'a, F> Widget for Canvas<'a, F>
+where
+ F: Fn(&mut Context),
+{
+ fn render(mut self, area: Rect, buf: &mut Buffer) {
+ let canvas_area = match self.block.take() {
+ Some(b) => {
+ let inner_area = b.inner(area);
+ b.render(area, buf);
+ inner_area
+ }
+ None => area,
+ };
+
+ buf.set_style(canvas_area, Style::default().bg(self.background_color));
+
+ let width = canvas_area.width as usize;
+
+ let painter = match self.painter {
+ Some(ref p) => p,
+ None => return,
+ };
+
+ // Create a blank context that match the size of the canvas
+ let mut ctx = Context::new(
+ canvas_area.width,
+ canvas_area.height,
+ self.x_bounds,
+ self.y_bounds,
+ self.marker,
+ );
+ // Paint to this context
+ painter(&mut ctx);
+ ctx.finish();
+
+ // Retrieve painted points for each layer
+ for layer in ctx.layers {
+ for (i, (ch, color)) in layer
+ .string
+ .chars()
+ .zip(layer.colors.into_iter())
+ .enumerate()
+ {
+ if ch != ' ' && ch != '\u{2800}' {
+ let (x, y) = (i % width, i / width);
+ buf.get_mut(x as u16 + canvas_area.left(), y as u16 + canvas_area.top())
+ .set_char(ch)
+ .set_fg(color);
+ }
+ }
+ }
+
+ // Finally draw the labels
+ let left = self.x_bounds[0];
+ let right = self.x_bounds[1];
+ let top = self.y_bounds[1];
+ let bottom = self.y_bounds[0];
+ let width = (self.x_bounds[1] - self.x_bounds[0]).abs();
+ let height = (self.y_bounds[1] - self.y_bounds[0]).abs();
+ let resolution = {
+ let width = f64::from(canvas_area.width - 1);
+ let height = f64::from(canvas_area.height - 1);
+ (width, height)
+ };
+ for label in ctx
+ .labels
+ .iter()
+ .filter(|l| l.x >= left && l.x <= right && l.y <= top && l.y >= bottom)
+ {
+ let x = ((label.x - left) * resolution.0 / width) as u16 + canvas_area.left();
+ let y = ((top - label.y) * resolution.1 / height) as u16 + canvas_area.top();
+ buf.set_spans(x, y, &label.spans, canvas_area.right() - x);
+ }
+ }
+}
diff --git a/src/ratatui/widgets/canvas/points.rs b/src/ratatui/widgets/canvas/points.rs
new file mode 100644
index 00000000..d9f97522
--- /dev/null
+++ b/src/ratatui/widgets/canvas/points.rs
@@ -0,0 +1,30 @@
+use crate::ratatui::{
+ style::Color,
+ widgets::canvas::{Painter, Shape},
+};
+
+/// A shape to draw a group of points with the given color
+#[derive(Debug, Clone)]
+pub struct Points<'a> {
+ pub coords: &'a [(f64, f64)],
+ pub color: Color,
+}
+
+impl<'a> Shape for Points<'a> {
+ fn draw(&self, painter: &mut Painter) {
+ for (x, y) in self.coords {
+ if let Some((x, y)) = painter.get_point(*x, *y) {
+ painter.paint(x, y, self.color);
+ }
+ }
+ }
+}
+
+impl<'a> Default for Points<'a> {
+ fn default() -> Points<'a> {
+ Points {
+ coords: &[],
+ color: Color::Reset,
+ }
+ }
+}
diff --git a/src/ratatui/widgets/canvas/rectangle.rs b/src/ratatui/widgets/canvas/rectangle.rs
new file mode 100644
index 00000000..07ac9137
--- /dev/null
+++ b/src/ratatui/widgets/canvas/rectangle.rs
@@ -0,0 +1,52 @@
+use crate::ratatui::{
+ style::Color,
+ widgets::canvas::{Line, Painter, Shape},
+};
+
+/// Shape to draw a rectangle from a `Rect` with the given color
+#[derive(Debug, Clone)]
+pub struct Rectangle {
+ pub x: f64,
+ pub y: f64,
+ pub width: f64,
+ pub height: f64,
+ pub color: Color,
+}
+
+impl Shape for Rectangle {
+ fn draw(&self, painter: &mut Painter) {
+ let lines: [Line; 4] = [
+ Line {
+ x1: self.x,
+ y1: self.y,
+ x2: self.x,
+ y2: self.y + self.height,
+ color: self.color,
+ },
+ Line {
+ x1: self.x,
+ y1: self.y + self.height,
+ x2: self.x + self.width,
+ y2: self.y + self.height,
+ color: self.color,
+ },
+ Line {
+ x1: self.x + self.width,
+ y1: self.y,
+ x2: self.x + self.width,
+ y2: self.y + self.height,
+ color: self.color,
+ },
+ Line {
+ x1: self.x,
+ y1: self.y,
+ x2: self.x + self.width,
+ y2: self.y,
+ color: self.color,
+ },
+ ];
+ for line in &lines {
+ line.draw(painter);
+ }
+ }
+}
diff --git a/src/ratatui/widgets/canvas/world.rs b/src/ratatui/widgets/canvas/world.rs
new file mode 100644
index 00000000..b15b7c32
--- /dev/null
+++ b/src/ratatui/widgets/canvas/world.rs
@@ -0,0 +1,6299 @@
+/// [Source data](http://www.gnuplotting.org/plotting-the-world-revisited)
+
+pub static WORLD_HIGH_RESOLUTION: [(f64, f64); 5125] = [
+ (-163.7128, -78.5956),
+ (-163.1058, -78.2233),
+ (-161.2451, -78.3801),
+ (-160.2462, -78.6936),
+ (-159.4824, -79.0463),
+ (-159.2081, -79.4970),
+ (-161.1276, -79.6342),
+ (-162.4398, -79.2814),
+ (-163.0274, -78.9287),
+ (-163.0666, -78.8699),
+ (-163.7128, -78.5956),
+ (-6.1978, 53.8675),
+ (-6.0329, 53.1531),
+ (-6.7888, 52.2601),
+ (-8.5616, 51.6693),
+ (-9.9770, 51.8204),
+ (-9.1662, 52.8646),
+ (-9.6885, 53.8813),
+ (-8.3279, 54.6645),
+ (-7.5721, 55.1316),
+ (-6.7338, 55.1728),
+ (-5.6619, 54.5546),
+ (-6.1978, 53.8675),
+ (141.0002, -2.6001),
+ (142.7352, -3.2891),
+ (144.5839, -3.8614),
+ (145.2731, -4.3737),
+ (145.8297, -4.8764),
+ (145.9819, -5.4656),
+ (147.6480, -6.0836),
+ (147.8911, -6.6140),
+ (146.9709, -6.7216),
+ (147.1918, -7.3880),
+ (148.0846, -8.0441),
+ (148.7341, -9.1046),
+ (149.3068, -9.0714),
+ (149.2666, -9.5144),
+ (150.0387, -9.6843),
+ (149.7387, -9.8729),
+ (150.8016, -10.2936),
+ (150.6905, -10.5827),
+ (150.0283, -10.6524),
+ (149.7823, -10.3932),
+ (148.9231, -10.2809),
+ (147.9130, -10.1304),
+ (147.1354, -9.4924),
+ (146.5678, -8.9425),
+ (146.0484, -8.0674),
+ (144.7441, -7.6301),
+ (143.8970, -7.9153),
+ (143.2863, -8.2454),
+ (143.4139, -8.9830),
+ (142.6284, -9.3268),
+ (142.0682, -9.1595),
+ (141.0338, -9.1178),
+ (140.1434, -8.2971),
+ (139.1277, -8.0960),
+ (138.8814, -8.3809),
+ (137.6144, -8.4116),
+ (138.0390, -7.5978),
+ (138.6686, -7.3202),
+ (138.4079, -6.2328),
+ (137.9278, -5.3933),
+ (135.9892, -4.5465),
+ (135.1645, -4.4629),
+ (133.6628, -3.5388),
+ (133.3677, -4.0248),
+ (132.9839, -4.1129),
+ (132.7569, -3.7462),
+ (132.7537, -3.3117),
+ (131.9898, -2.8205),
+ (133.0668, -2.4604),
+ (133.7800, -2.4798),
+ (133.6962, -2.2145),
+ (132.2323, -2.2125),
+ (131.8362, -1.6171),
+ (130.9428, -1.4325),
+ (130.5195, -0.9377),
+ (131.8675, -0.6954),
+ (132.3801, -0.3695),
+ (133.9855, -0.7802),
+ (134.1433, -1.1518),
+ (134.4226, -2.7691),
+ (135.4576, -3.3677),
+ (136.2933, -2.3070),
+ (137.4407, -1.7035),
+ (138.3297, -1.7026),
+ (139.1849, -2.0512),
+ (139.9266, -2.4089),
+ (141.0002, -2.6001),
+ (114.2040, 4.5258),
+ (114.5999, 4.9000),
+ (115.4507, 5.4477),
+ (116.2207, 6.1431),
+ (116.7251, 6.9247),
+ (117.1296, 6.9280),
+ (117.6433, 6.4221),
+ (117.6890, 5.9874),
+ (118.3476, 5.7086),
+ (119.1819, 5.4078),
+ (119.1106, 5.0161),
+ (118.4397, 4.9665),
+ (118.6183, 4.4782),
+ (117.8820, 4.1375),
+ (117.3132, 3.2344),
+ (118.0483, 2.2876),
+ (117.8756, 1.8276),
+ (118.9967, 0.9022),
+ (117.8118, 0.7842),
+ (117.4783, 0.1024),
+ (117.5216, -0.8037),
+ (116.5600, -1.4876),
+ (116.5337, -2.4835),
+ (116.1480, -4.0127),
+ (116.0008, -3.6570),
+ (114.8648, -4.1069),
+ (114.4686, -3.4957),
+ (113.7556, -3.4391),
+ (113.2569, -3.1187),
+ (112.0681, -3.4783),
+ (111.7032, -2.9944),
+ (111.0482, -3.0494),
+ (110.2238, -2.9340),
+ (110.0709, -1.5928),
+ (109.5719, -1.3149),
+ (109.0918, -0.4595),
+ (108.9526, 0.4153),
+ (109.0691, 1.3419),
+ (109.6632, 2.0064),
+ (110.3961, 1.6637),
+ (111.1688, 1.8506),
+ (111.3700, 2.6973),
+ (111.7969, 2.8858),
+ (112.9956, 3.1023),
+ (113.7129, 3.8935),
+ (114.2040, 4.5258),
+ (-93.6127, 74.9799),
+ (-94.1569, 74.5923),
+ (-95.6086, 74.6668),
+ (-96.8209, 74.9276),
+ (-96.2885, 75.3778),
+ (-94.8508, 75.6472),
+ (-93.9777, 75.2964),
+ (-93.6127, 74.9799),
+ (-93.8400, 77.5199),
+ (-94.2956, 77.4913),
+ (-96.1696, 77.5551),
+ (-96.4363, 77.8346),
+ (-94.4225, 77.8200),
+ (-93.7206, 77.6343),
+ (-93.8400, 77.5199),
+ (-96.7543, 78.7658),
+ (-95.5592, 78.4183),
+ (-95.8302, 78.0569),
+ (-97.3098, 77.8505),
+ (-98.1242, 78.0828),
+ (-98.5528, 78.4581),
+ (-98.6319, 78.8719),
+ (-97.3372, 78.8319),
+ (-96.7543, 78.7658),
+ (-88.1503, 74.3923),
+ (-89.7647, 74.5155),
+ (-92.4224, 74.8377),
+ (-92.7682, 75.3868),
+ (-92.8899, 75.8826),
+ (-93.8938, 76.3192),
+ (-95.9624, 76.4413),
+ (-97.1213, 76.7510),
+ (-96.7451, 77.1613),
+ (-94.6840, 77.0978),
+ (-93.5739, 76.7762),
+ (-91.6050, 76.7785),
+ (-90.7418, 76.4495),
+ (-90.9696, 76.0740),
+ (-89.8222, 75.8477),
+ (-89.1870, 75.6101),
+ (-87.8382, 75.5661),
+ (-86.3791, 75.4824),
+ (-84.7896, 75.6992),
+ (-82.7534, 75.7843),
+ (-81.1285, 75.7139),
+ (-80.0575, 75.3368),
+ (-79.8339, 74.9231),
+ (-80.4577, 74.6573),
+ (-81.9488, 74.4424),
+ (-83.2288, 74.5640),
+ (-86.0974, 74.4100),
+ (-88.1503, 74.3923),
+ (-111.2644, 78.1529),
+ (-109.8544, 77.9963),
+ (-110.1869, 77.6970),
+ (-112.0511, 77.4092),
+ (-113.5342, 77.7322),
+ (-112.7245, 78.0510),
+ (-111.2644, 78.1529),
+ (-110.9636, 78.8044),
+ (-109.6631, 78.6019),
+ (-110.8813, 78.4069),
+ (-112.5420, 78.4079),
+ (-112.5258, 78.5505),
+ (-111.5000, 78.8499),
+ (-110.9636, 78.8044),
+ (-66.2824, 18.5147),
+ (-65.7713, 18.4266),
+ (-65.5910, 18.2280),
+ (-65.8471, 17.9759),
+ (-66.5999, 17.9818),
+ (-67.1841, 17.9465),
+ (-67.2424, 18.3744),
+ (-67.1006, 18.5206),
+ (-66.2824, 18.5147),
+ (-77.5696, 18.4905),
+ (-76.8966, 18.4008),
+ (-76.3653, 18.1607),
+ (-76.1996, 17.8868),
+ (-76.9025, 17.8682),
+ (-77.2063, 17.7011),
+ (-77.7660, 17.8615),
+ (-78.3377, 18.2259),
+ (-78.2177, 18.4545),
+ (-77.7973, 18.5242),
+ (-77.5696, 18.4905),
+ (-82.2681, 23.1886),
+ (-81.4044, 23.1172),
+ (-80.6187, 23.1060),
+ (-79.6795, 22.7653),
+ (-79.2814, 22.3992),
+ (-78.3474, 22.5121),
+ (-77.9932, 22.2771),
+ (-77.1464, 21.6578),
+ (-76.5238, 21.2068),
+ (-76.1946, 21.2205),
+ (-75.5982, 21.0166),
+ (-75.6710, 20.7350),
+ (-74.9338, 20.6939),
+ (-74.1780, 20.2846),
+ (-74.2966, 20.0503),
+ (-74.9615, 19.9234),
+ (-75.6346, 19.8737),
+ (-76.3236, 19.9528),
+ (-77.7554, 19.8554),
+ (-77.0851, 20.4133),
+ (-77.4926, 20.6731),
+ (-78.1372, 20.7399),
+ (-78.4828, 21.0286),
+ (-78.7198, 21.5981),
+ (-79.2849, 21.5591),
+ (-80.2174, 21.8273),
+ (-80.5175, 22.0370),
+ (-81.8209, 22.1920),
+ (-82.1699, 22.3871),
+ (-81.7950, 22.6369),
+ (-82.7758, 22.6881),
+ (-83.4944, 22.1685),
+ (-83.9088, 22.1545),
+ (-84.0521, 21.9105),
+ (-84.5470, 21.8012),
+ (-84.9749, 21.8960),
+ (-84.4470, 22.2049),
+ (-84.2303, 22.5657),
+ (-83.7782, 22.7881),
+ (-83.2675, 22.9830),
+ (-82.5104, 23.0787),
+ (-82.2681, 23.1886),
+ (-55.6002, 51.3170),
+ (-56.1340, 50.6870),
+ (-56.7958, 49.8123),
+ (-56.1431, 50.1501),
+ (-55.4714, 49.9358),
+ (-55.8224, 49.5871),
+ (-54.9351, 49.3130),
+ (-54.4737, 49.5566),
+ (-53.4765, 49.2491),
+ (-53.7860, 48.5167),
+ (-53.0861, 48.6878),
+ (-52.9586, 48.1571),
+ (-52.6480, 47.5355),
+ (-53.0691, 46.6554),
+ (-53.5214, 46.6182),
+ (-54.1789, 46.8070),
+ (-53.9618, 47.6252),
+ (-54.2404, 47.7522),
+ (-55.4007, 46.8849),
+ (-55.9974, 46.9197),
+ (-55.2912, 47.3895),
+ (-56.2507, 47.6325),
+ (-57.3252, 47.5728),
+ (-59.2660, 47.6033),
+ (-59.4194, 47.8994),
+ (-58.7965, 48.2515),
+ (-59.2316, 48.5231),
+ (-58.3918, 49.1255),
+ (-57.3586, 50.7182),
+ (-56.7386, 51.2874),
+ (-55.8709, 51.6320),
+ (-55.4069, 51.5882),
+ (-55.6002, 51.3170),
+ (-83.8826, 65.1096),
+ (-82.7875, 64.7666),
+ (-81.6420, 64.4551),
+ (-81.5534, 63.9796),
+ (-80.8173, 64.0574),
+ (-80.1034, 63.7259),
+ (-80.9910, 63.4112),
+ (-82.5471, 63.6517),
+ (-83.1087, 64.1018),
+ (-84.1004, 63.5697),
+ (-85.5234, 63.0523),
+ (-85.8667, 63.6372),
+ (-87.2219, 63.5412),
+ (-86.3527, 64.0358),
+ (-86.2248, 64.8229),
+ (-85.8838, 65.7387),
+ (-85.1613, 65.6572),
+ (-84.9757, 65.2175),
+ (-84.4640, 65.3717),
+ (-83.8826, 65.1096),
+ (-78.7706, 72.3521),
+ (-77.8246, 72.7496),
+ (-75.6058, 72.2436),
+ (-74.2286, 71.7671),
+ (-74.0991, 71.3308),
+ (-72.2422, 71.5569),
+ (-71.2000, 70.9200),
+ (-68.7860, 70.5250),
+ (-67.9149, 70.1219),
+ (-66.9690, 69.1860),
+ (-68.8051, 68.7201),
+ (-66.4498, 68.0671),
+ (-64.8623, 67.8475),
+ (-63.4249, 66.9284),
+ (-61.8519, 66.8621),
+ (-62.1631, 66.1602),
+ (-63.9184, 64.9986),
+ (-65.1488, 65.4260),
+ (-66.7212, 66.3880),
+ (-68.0150, 66.2627),
+ (-68.1412, 65.6897),
+ (-67.0896, 65.1084),
+ (-65.7320, 64.6484),
+ (-65.3201, 64.3827),
+ (-64.6694, 63.3929),
+ (-65.0138, 62.6741),
+ (-66.2750, 62.9450),
+ (-68.7831, 63.7456),
+ (-67.3696, 62.8839),
+ (-66.3282, 62.2800),
+ (-66.1655, 61.9308),
+ (-68.8773, 62.3301),
+ (-71.0234, 62.9107),
+ (-72.2353, 63.3978),
+ (-71.8862, 63.6799),
+ (-73.3783, 64.1939),
+ (-74.8344, 64.6791),
+ (-74.8185, 64.3890),
+ (-77.7099, 64.2295),
+ (-78.5559, 64.5729),
+ (-77.8972, 65.3091),
+ (-76.0182, 65.3269),
+ (-73.9597, 65.4547),
+ (-74.2938, 65.8117),
+ (-73.9449, 66.3105),
+ (-72.6511, 67.2845),
+ (-72.9260, 67.7269),
+ (-73.3116, 68.0694),
+ (-74.8433, 68.5546),
+ (-76.8691, 68.8947),
+ (-76.2286, 69.1477),
+ (-77.2873, 69.7695),
+ (-78.1686, 69.8264),
+ (-78.9572, 70.1668),
+ (-79.4924, 69.8718),
+ (-81.3054, 69.7431),
+ (-84.9447, 69.9666),
+ (-87.0600, 70.2600),
+ (-88.6817, 70.4107),
+ (-89.5134, 70.7620),
+ (-88.4677, 71.2181),
+ (-89.8881, 71.2225),
+ (-90.2051, 72.2350),
+ (-89.4365, 73.1294),
+ (-88.4082, 73.5378),
+ (-85.8261, 73.8038),
+ (-86.5621, 73.1574),
+ (-85.7743, 72.5341),
+ (-84.8501, 73.3402),
+ (-82.3155, 73.7509),
+ (-80.6000, 72.7165),
+ (-80.7489, 72.0619),
+ (-78.7706, 72.3521),
+ (-94.5036, 74.1349),
+ (-92.4200, 74.1000),
+ (-90.5097, 73.8567),
+ (-92.0039, 72.9662),
+ (-93.1962, 72.7719),
+ (-94.2690, 72.0245),
+ (-95.4098, 72.0618),
+ (-96.0337, 72.9402),
+ (-96.0182, 73.4374),
+ (-95.4957, 73.8624),
+ (-94.5036, 74.1349),
+ (-100.4383, 72.7058),
+ (-101.54, 73.36),
+ (-100.3564, 73.8438),
+ (-99.1638, 73.6333),
+ (-97.38, 73.76),
+ (-97.12, 73.47),
+ (-98.0535, 72.9905),
+ (-96.54, 72.56),
+ (-96.72, 71.66),
+ (-98.3596, 71.2728),
+ (-99.3228, 71.3563),
+ (-100.0148, 71.7382),
+ (-102.48, 72.4829),
+ (-102.48, 72.83),
+ (-100.4383, 72.7058),
+ (-107.8194, 75.8455),
+ (-106.9289, 76.0128),
+ (-105.8809, 75.9693),
+ (-105.7049, 75.4795),
+ (-106.3134, 75.0052),
+ (-109.6999, 74.8500),
+ (-112.2230, 74.4169),
+ (-113.7438, 74.3942),
+ (-113.8713, 74.7202),
+ (-111.7942, 75.1624),
+ (-116.3121, 75.0434),
+ (-117.7104, 75.2222),
+ (-116.3460, 76.1990),
+ (-115.4048, 76.4788),
+ (-112.5905, 76.1413),
+ (-110.8142, 75.5491),
+ (-109.0671, 75.4732),
+ (-110.4972, 76.4298),
+ (-109.5811, 76.7941),
+ (-108.5485, 76.6783),
+ (-108.2114, 76.2016),
+ (-107.8194, 75.8455),
+ (-122.8549, 76.1165),
+ (-121.1575, 76.8645),
+ (-119.1039, 77.5122),
+ (-117.5701, 77.4983),
+ (-116.1985, 77.6452),
+ (-116.3358, 76.8769),
+ (-117.1060, 76.5300),
+ (-118.0404, 76.4811),
+ (-119.8993, 76.0532),
+ (-121.4999, 75.9000),
+ (-122.8549, 76.1165),
+ (-121.5378, 74.4489),
+ (-120.1097, 74.2413),
+ (-117.5556, 74.1857),
+ (-116.5844, 73.8960),
+ (-115.5107, 73.4752),
+ (-116.7679, 73.2229),
+ (-119.22, 72.52),
+ (-120.46, 71.82),
+ (-120.46, 71.3836),
+ (-123.0921, 70.9016),
+ (-123.62, 71.34),
+ (-125.9289, 71.8686),
+ (-125.5, 72.2922),
+ (-124.8072, 73.0225),
+ (-123.94, 73.68),
+ (-124.9177, 74.2927),
+ (-121.5378, 74.4489),
+ (-166.4677, 60.3841),
+ (-165.6744, 60.2936),
+ (-165.5791, 59.9099),
+ (-166.1927, 59.7544),
+ (-166.8483, 59.9414),
+ (-167.4552, 60.2130),
+ (-166.4677, 60.3841),
+ (-153.2287, 57.9690),
+ (-152.5647, 57.9014),
+ (-152.1411, 57.5910),
+ (-153.0063, 57.1158),
+ (-154.0050, 56.7346),
+ (-154.5164, 56.9927),
+ (-154.6709, 57.4611),
+ (-153.7627, 57.8165),
+ (-153.2287, 57.9690),
+ (-132.7100, 54.0400),
+ (-131.7499, 54.1200),
+ (-132.0494, 52.9846),
+ (-131.1790, 52.1804),
+ (-131.5778, 52.1823),
+ (-132.1804, 52.6397),
+ (-132.5499, 53.1000),
+ (-133.0546, 53.4114),
+ (-133.2396, 53.8510),
+ (-133.1800, 54.1699),
+ (-132.7100, 54.0400),
+ (-125.4150, 49.9500),
+ (-124.9207, 49.4752),
+ (-123.9225, 49.0624),
+ (-123.5100, 48.5100),
+ (-124.0128, 48.3708),
+ (-125.6550, 48.8250),
+ (-125.9549, 49.1799),
+ (-126.8500, 49.5300),
+ (-127.0299, 49.8149),
+ (-128.0593, 49.9949),
+ (-128.4445, 50.5391),
+ (-128.3584, 50.7706),
+ (-127.3085, 50.5525),
+ (-126.6950, 50.4009),
+ (-125.7550, 50.2950),
+ (-125.4150, 49.9500),
+ (-171.7316, 63.7825),
+ (-171.1144, 63.5921),
+ (-170.4911, 63.6949),
+ (-169.6825, 63.4311),
+ (-168.6894, 63.2975),
+ (-168.7719, 63.1885),
+ (-169.5294, 62.9769),
+ (-170.2905, 63.1944),
+ (-170.6713, 63.3758),
+ (-171.5530, 63.3177),
+ (-171.7911, 63.4058),
+ (-171.7316, 63.7825),
+ (-105.4922, 79.3015),
+ (-103.5292, 79.1653),
+ (-100.8251, 78.8004),
+ (-100.0601, 78.3247),
+ (-99.6709, 77.9075),
+ (-101.3039, 78.0189),
+ (-102.9498, 78.3432),
+ (-105.1761, 78.3803),
+ (-104.2104, 78.6774),
+ (-105.4195, 78.9183),
+ (-105.4922, 79.3015),
+ (32.9469, 35.3867),
+ (33.6672, 35.3732),
+ (34.5764, 35.6715),
+ (33.9008, 35.2457),
+ (34.0048, 34.9780),
+ (32.9798, 34.5718),
+ (32.4902, 34.7016),
+ (32.2566, 35.1032),
+ (32.8024, 35.1455),
+ (32.9469, 35.3867),
+ (26.2900, 35.2999),
+ (26.1649, 35.0049),
+ (24.7249, 34.9199),
+ (24.7350, 35.0849),
+ (23.5149, 35.2799),
+ (23.6999, 35.7050),
+ (24.2466, 35.3680),
+ (25.0250, 35.4249),
+ (25.7692, 35.3540),
+ (25.7450, 35.1799),
+ (26.2900, 35.2999),
+ (49.5435, -12.4698),
+ (49.8089, -12.8952),
+ (50.0565, -13.5557),
+ (50.2174, -14.7587),
+ (50.4765, -15.2265),
+ (50.3771, -15.7060),
+ (50.2002, -16.0002),
+ (49.8606, -15.4142),
+ (49.6726, -15.7101),
+ (49.8633, -16.4510),
+ (49.7745, -16.8750),
+ (49.4986, -17.1060),
+ (49.4356, -17.9530),
+ (49.0417, -19.1187),
+ (48.5485, -20.4968),
+ (47.9307, -22.3915),
+ (47.5477, -23.7819),
+ (47.0957, -24.9416),
+ (46.2824, -25.1784),
+ (45.4095, -25.6014),
+ (44.8335, -25.3461),
+ (44.0397, -24.9883),
+ (43.7637, -24.4606),
+ (43.6977, -23.5741),
+ (43.3456, -22.7769),
+ (43.2541, -22.0574),
+ (43.4332, -21.3364),
+ (43.8936, -21.1633),
+ (43.8963, -20.8304),
+ (44.3743, -20.0723),
+ (44.4643, -19.4354),
+ (44.2324, -18.9619),
+ (44.0429, -18.3313),
+ (43.9630, -17.4099),
+ (44.3124, -16.8504),
+ (44.4465, -16.2162),
+ (44.9449, -16.1793),
+ (45.5027, -15.9743),
+ (45.8729, -15.7934),
+ (46.3122, -15.7800),
+ (46.8821, -15.2101),
+ (47.7051, -14.5943),
+ (48.0052, -14.0912),
+ (47.8689, -13.6638),
+ (48.2938, -13.7840),
+ (48.8450, -13.0891),
+ (48.8635, -12.4878),
+ (49.1946, -12.0405),
+ (49.5435, -12.4698),
+ (167.2168, -15.8918),
+ (167.8448, -16.4663),
+ (167.5151, -16.5978),
+ (167.1800, -16.1599),
+ (167.2168, -15.8918),
+ (166.7931, -15.6688),
+ (166.6498, -15.3927),
+ (166.6291, -14.6264),
+ (167.1077, -14.9339),
+ (167.2700, -15.7400),
+ (167.0012, -15.6146),
+ (166.7931, -15.6688),
+ (134.2101, -6.8952),
+ (134.1127, -6.1424),
+ (134.2903, -5.7830),
+ (134.4996, -5.4450),
+ (134.7270, -5.7375),
+ (134.7246, -6.2144),
+ (134.2101, -6.8952),
+ (-48.6606, -78.0470),
+ (-48.1513, -78.0470),
+ (-46.6628, -77.8314),
+ (-45.1547, -78.0470),
+ (-43.9208, -78.4781),
+ (-43.4899, -79.0855),
+ (-43.3724, -79.5166),
+ (-43.3332, -80.0261),
+ (-44.8805, -80.3396),
+ (-46.5061, -80.5943),
+ (-48.3864, -80.8294),
+ (-50.4821, -81.0254),
+ (-52.8519, -80.9666),
+ (-54.1642, -80.6335),
+ (-53.9879, -80.2220),
+ (-51.8531, -79.9477),
+ (-50.9913, -79.6146),
+ (-50.3645, -79.1834),
+ (-49.9141, -78.8112),
+ (-49.3069, -78.4585),
+ (-48.6606, -78.0470),
+ (-66.2900, -80.2557),
+ (-64.0376, -80.2948),
+ (-61.8832, -80.3928),
+ (-61.1389, -79.9813),
+ (-60.6101, -79.6286),
+ (-59.5720, -80.0401),
+ (-59.8658, -80.5496),
+ (-60.1596, -81.0003),
+ (-62.2553, -80.8631),
+ (-64.4881, -80.9219),
+ (-65.7416, -80.5888),
+ (-65.7416, -80.5496),
+ (-66.2900, -80.2557),
+ (-73.9158, -71.2693),
+ (-73.2303, -71.1517),
+ (-72.0747, -71.1909),
+ (-71.7809, -70.6814),
+ (-71.7221, -70.3091),
+ (-71.7417, -69.5057),
+ (-71.1738, -69.0354),
+ (-70.2532, -68.8787),
+ (-69.7244, -69.2510),
+ (-69.4894, -69.6233),
+ (-69.0585, -70.0740),
+ (-68.7255, -70.5051),
+ (-68.4513, -70.9558),
+ (-68.3338, -71.4064),
+ (-68.5101, -71.7984),
+ (-68.7842, -72.1706),
+ (-69.9594, -72.3078),
+ (-71.0758, -72.5038),
+ (-72.3881, -72.4842),
+ (-71.8984, -72.0923),
+ (-73.0736, -72.2294),
+ (-74.1900, -72.3666),
+ (-74.9538, -72.0727),
+ (-75.0126, -71.6612),
+ (-73.9158, -71.2693),
+ (-102.3307, -71.8941),
+ (-101.7039, -71.7177),
+ (-100.4309, -71.8549),
+ (-98.9815, -71.9333),
+ (-97.8847, -72.0705),
+ (-96.7879, -71.9529),
+ (-96.2003, -72.5212),
+ (-96.9837, -72.4428),
+ (-98.1980, -72.4820),
+ (-99.4320, -72.4428),
+ (-100.7834, -72.5016),
+ (-101.8018, -72.3056),
+ (-102.3307, -71.8941),
+ (-122.6217, -73.6577),
+ (-122.4062, -73.3246),
+ (-121.2115, -73.5009),
+ (-119.9188, -73.6577),
+ (-118.7241, -73.4813),
+ (-119.2921, -73.8340),
+ (-120.2322, -74.0888),
+ (-121.6228, -74.0104),
+ (-122.6217, -73.6577),
+ (-127.2831, -73.4617),
+ (-126.5584, -73.2462),
+ (-125.5595, -73.4813),
+ (-124.0318, -73.8732),
+ (-124.6194, -73.8340),
+ (-125.9121, -73.7361),
+ (-127.2831, -73.4617),
+ (165.7799, -21.0800),
+ (166.5999, -21.7000),
+ (167.1200, -22.1599),
+ (166.7400, -22.3999),
+ (166.1897, -22.1297),
+ (165.4743, -21.6796),
+ (164.8298, -21.1498),
+ (164.1679, -20.4447),
+ (164.0296, -20.1056),
+ (164.4599, -20.1200),
+ (165.0200, -20.4599),
+ (165.4600, -20.8000),
+ (165.7799, -21.0800),
+ (152.6400, -3.6599),
+ (153.0199, -3.9800),
+ (153.1400, -4.4999),
+ (152.8272, -4.7664),
+ (152.6386, -4.1761),
+ (152.4060, -3.7897),
+ (151.9532, -3.4620),
+ (151.3842, -3.0354),
+ (150.6620, -2.7414),
+ (150.9399, -2.5000),
+ (151.4799, -2.7799),
+ (151.8200, -2.9999),
+ (152.2399, -3.2400),
+ (152.6400, -3.6599),
+ (151.3013, -5.8407),
+ (150.7544, -6.0837),
+ (150.2411, -6.3177),
+ (149.7099, -6.3165),
+ (148.8900, -6.0260),
+ (148.3189, -5.7471),
+ (148.4018, -5.4377),
+ (149.2984, -5.5837),
+ (149.8455, -5.5055),
+ (149.9962, -5.0261),
+ (150.1397, -5.0013),
+ (150.2369, -5.5322),
+ (150.8074, -5.4558),
+ (151.0896, -5.1136),
+ (151.6478, -4.7570),
+ (151.5378, -4.1678),
+ (152.1367, -4.1487),
+ (152.3387, -4.3129),
+ (152.3186, -4.8676),
+ (151.9827, -5.4780),
+ (151.4591, -5.5602),
+ (151.3013, -5.8407),
+ (162.1190, -10.4827),
+ (162.3986, -10.8263),
+ (161.7000, -10.8200),
+ (161.3197, -10.2047),
+ (161.9173, -10.4466),
+ (162.1190, -10.4827),
+ (161.6799, -9.5999),
+ (161.5293, -9.7843),
+ (160.7882, -8.9175),
+ (160.5799, -8.3200),
+ (160.9200, -8.3200),
+ (161.2800, -9.1200),
+ (161.6799, -9.5999),
+ (160.8522, -9.8729),
+ (160.4625, -9.8952),
+ (159.8494, -9.7940),
+ (159.6400, -9.6399),
+ (159.7029, -9.2429),
+ (160.3629, -9.4003),
+ (160.6885, -9.6101),
+ (160.8522, -9.8729),
+ (159.6400, -8.0200),
+ (159.8750, -8.3373),
+ (159.9174, -8.5382),
+ (159.1336, -8.1141),
+ (158.5861, -7.7548),
+ (158.2111, -7.4218),
+ (158.3599, -7.3200),
+ (158.8200, -7.5600),
+ (159.6400, -8.0200),
+ (157.1400, -7.0216),
+ (157.5384, -7.3478),
+ (157.3394, -7.4047),
+ (156.9020, -7.1768),
+ (156.4913, -6.7659),
+ (156.5428, -6.5993),
+ (157.1400, -7.0216),
+ (154.7599, -5.3399),
+ (155.0629, -5.5667),
+ (155.5477, -6.2006),
+ (156.0199, -6.5400),
+ (155.8800, -6.8199),
+ (155.5999, -6.9199),
+ (155.1669, -6.5359),
+ (154.7291, -5.9007),
+ (154.5141, -5.1391),
+ (154.6525, -5.0423),
+ (154.7599, -5.3399),
+ (176.8858, -40.0659),
+ (176.5080, -40.6047),
+ (176.0124, -41.2896),
+ (175.2395, -41.6883),
+ (175.0678, -41.4258),
+ (174.6509, -41.2818),
+ (175.2276, -40.4592),
+ (174.9001, -39.9088),
+ (173.8240, -39.5088),
+ (173.8522, -39.1466),
+ (174.5748, -38.7976),
+ (174.7434, -38.0278),
+ (174.6969, -37.3811),
+ (174.2920, -36.7110),
+ (174.3190, -36.5348),
+ (173.8409, -36.1219),
+ (173.0541, -35.2371),
+ (172.6360, -34.5291),
+ (173.0070, -34.4506),
+ (173.5512, -35.0061),
+ (174.3293, -35.2654),
+ (174.6120, -36.1563),
+ (175.3366, -37.2090),
+ (175.3575, -36.5261),
+ (175.8088, -36.7989),
+ (175.9584, -37.5553),
+ (176.7631, -37.8812),
+ (177.4388, -37.9612),
+ (178.0103, -37.5798),
+ (178.5170, -37.6953),
+ (178.2747, -38.5828),
+ (177.9704, -39.1663),
+ (177.2069, -39.1457),
+ (176.9399, -39.4497),
+ (177.0329, -39.8799),
+ (176.8858, -40.0659),
+ (169.6678, -43.5553),
+ (170.5249, -43.0316),
+ (171.1250, -42.5127),
+ (171.5697, -41.7674),
+ (171.9487, -41.5144),
+ (172.0972, -40.9561),
+ (172.7985, -40.4939),
+ (173.0203, -40.9190),
+ (173.2472, -41.3319),
+ (173.9584, -40.9267),
+ (174.2475, -41.3491),
+ (174.2485, -41.7700),
+ (173.8764, -42.2331),
+ (173.2227, -42.9700),
+ (172.7112, -43.3722),
+ (173.0801, -43.8533),
+ (172.3085, -43.8656),
+ (171.4529, -44.2424),
+ (171.1851, -44.8971),
+ (170.6166, -45.9089),
+ (169.8314, -46.3557),
+ (169.3323, -46.6412),
+ (168.4113, -46.6199),
+ (167.7637, -46.2901),
+ (166.6768, -46.2199),
+ (166.5091, -45.8527),
+ (167.0463, -45.1109),
+ (168.3037, -44.1239),
+ (168.9494, -43.9358),
+ (169.6678, -43.5553),
+ (147.6892, -40.8082),
+ (148.2890, -40.8754),
+ (148.3598, -42.0623),
+ (148.0173, -42.4070),
+ (147.9140, -43.2115),
+ (147.5645, -42.9376),
+ (146.8703, -43.6345),
+ (146.6633, -43.5808),
+ (146.0483, -43.5497),
+ (145.4319, -42.6937),
+ (145.2950, -42.0336),
+ (144.7180, -41.1625),
+ (144.7437, -40.7039),
+ (145.3979, -40.7925),
+ (146.3641, -41.1376),
+ (146.9085, -41.0005),
+ (147.6892, -40.8082),
+ (126.1487, -32.2159),
+ (125.0886, -32.7287),
+ (124.2216, -32.9594),
+ (124.0289, -33.4838),
+ (123.6596, -33.8901),
+ (122.8110, -33.9144),
+ (122.1830, -34.0034),
+ (121.2991, -33.8210),
+ (120.5802, -33.9301),
+ (119.8936, -33.9760),
+ (119.2988, -34.5093),
+ (119.0073, -34.4641),
+ (118.5057, -34.7468),
+ (118.0249, -35.0647),
+ (117.2955, -35.0254),
+ (116.6251, -35.0250),
+ (115.5643, -34.3864),
+ (115.0268, -34.1965),
+ (115.0486, -33.6234),
+ (115.5451, -33.4872),
+ (115.7146, -33.2595),
+ (115.6793, -32.9003),
+ (115.8016, -32.2050),
+ (115.6896, -31.6124),
+ (115.1609, -30.6015),
+ (114.9970, -30.0307),
+ (115.0400, -29.4610),
+ (114.6419, -28.8102),
+ (114.6164, -28.5163),
+ (114.1735, -28.1180),
+ (114.0488, -27.3347),
+ (113.4774, -26.5431),
+ (113.3389, -26.1165),
+ (113.7783, -26.5490),
+ (113.4409, -25.6212),
+ (113.9369, -25.9112),
+ (114.2328, -26.2984),
+ (114.2161, -25.7862),
+ (113.7212, -24.9989),
+ (113.6253, -24.6839),
+ (113.3935, -24.3847),
+ (113.5020, -23.8063),
+ (113.7069, -23.5602),
+ (113.8434, -23.0599),
+ (113.7365, -22.4754),
+ (114.1497, -21.7558),
+ (114.2253, -22.5174),
+ (114.6477, -21.8295),
+ (115.4601, -21.4951),
+ (115.9473, -21.0686),
+ (116.7116, -20.7016),
+ (117.1663, -20.6235),
+ (117.4415, -20.7468),
+ (118.2295, -20.3742),
+ (118.8360, -20.2633),
+ (118.9878, -20.0442),
+ (119.2524, -19.9529),
+ (119.8052, -19.9765),
+ (120.8562, -19.6837),
+ (121.3998, -19.2397),
+ (121.6550, -18.7053),
+ (122.2416, -18.1976),
+ (122.2866, -17.7986),
+ (122.3127, -17.2549),
+ (123.0125, -16.4051),
+ (123.4337, -17.2685),
+ (123.8593, -17.0690),
+ (123.5032, -16.5965),
+ (123.8170, -16.1113),
+ (124.2582, -16.3279),
+ (124.3797, -15.5670),
+ (124.9261, -15.0751),
+ (125.1672, -14.6803),
+ (125.6700, -14.5100),
+ (125.6857, -14.2306),
+ (126.1251, -14.3473),
+ (126.1428, -14.0959),
+ (126.5825, -13.9527),
+ (127.0658, -13.8179),
+ (127.8046, -14.2769),
+ (128.3596, -14.8691),
+ (128.9855, -14.8759),
+ (129.6214, -14.9697),
+ (129.4096, -14.4206),
+ (129.8886, -13.6187),
+ (130.3394, -13.3573),
+ (130.1835, -13.1075),
+ (130.6177, -12.5363),
+ (131.2234, -12.1836),
+ (131.7350, -12.3024),
+ (132.5752, -12.1140),
+ (132.5572, -11.6030),
+ (131.8246, -11.2737),
+ (132.3572, -11.1285),
+ (133.0195, -11.3764),
+ (133.5508, -11.7865),
+ (134.3930, -12.0423),
+ (134.6786, -11.9411),
+ (135.2984, -12.2486),
+ (135.8826, -11.9622),
+ (136.2583, -12.0493),
+ (136.4924, -11.8572),
+ (136.9516, -12.3519),
+ (136.6851, -12.8872),
+ (136.3054, -13.2912),
+ (135.9617, -13.3245),
+ (136.0776, -13.7242),
+ (135.7838, -14.2239),
+ (135.4286, -14.7154),
+ (135.5001, -14.9977),
+ (136.2951, -15.5502),
+ (137.0653, -15.8707),
+ (137.5804, -16.2150),
+ (138.3032, -16.8076),
+ (138.5851, -16.8066),
+ (139.1085, -17.0626),
+ (139.2605, -17.3716),
+ (140.2151, -17.7108),
+ (140.8754, -17.3690),
+ (141.0711, -16.8320),
+ (141.2740, -16.3888),
+ (141.3982, -15.8405),
+ (141.7021, -15.0449),
+ (141.5633, -14.5613),
+ (141.6355, -14.2703),
+ (141.5198, -13.6980),
+ (141.6509, -12.9446),
+ (141.8426, -12.7415),
+ (141.6869, -12.4076),
+ (141.9286, -11.8774),
+ (142.1184, -11.3280),
+ (142.1437, -11.0427),
+ (142.5152, -10.6681),
+ (142.7973, -11.1573),
+ (142.8667, -11.7847),
+ (143.1159, -11.9056),
+ (143.1586, -12.3256),
+ (143.5221, -12.8343),
+ (143.5971, -13.4004),
+ (143.5618, -13.7636),
+ (143.9220, -14.5483),
+ (144.5637, -14.1711),
+ (144.8948, -14.5944),
+ (145.3747, -14.9849),
+ (145.2719, -15.4282),
+ (145.4852, -16.2856),
+ (145.6369, -16.7849),
+ (145.8888, -16.9069),
+ (146.1603, -17.7616),
+ (146.0636, -18.2800),
+ (146.3874, -18.9582),
+ (147.4710, -19.4807),
+ (148.1776, -19.9559),
+ (148.8484, -20.3912),
+ (148.7174, -20.6334),
+ (149.2894, -21.2605),
+ (149.6783, -22.3425),
+ (150.0773, -22.1227),
+ (150.4829, -22.5561),
+ (150.7272, -22.4023),
+ (150.8995, -23.4622),
+ (151.6091, -24.0762),
+ (152.0735, -24.4578),
+ (152.8551, -25.2675),
+ (153.1361, -26.0711),
+ (153.1619, -26.6413),
+ (153.0929, -27.2602),
+ (153.5694, -28.1100),
+ (153.5121, -28.9950),
+ (153.3390, -29.4582),
+ (153.0692, -30.3502),
+ (153.0895, -30.9236),
+ (152.8915, -31.6404),
+ (152.4500, -32.5500),
+ (151.7091, -33.0413),
+ (151.3439, -33.8160),
+ (151.0105, -34.3103),
+ (150.7141, -35.1734),
+ (150.3282, -35.6718),
+ (150.0752, -36.4202),
+ (149.9461, -37.1090),
+ (149.9972, -37.4252),
+ (149.4238, -37.7726),
+ (148.3046, -37.8090),
+ (147.3817, -38.2192),
+ (146.9221, -38.6065),
+ (146.3179, -39.0357),
+ (145.4896, -38.5937),
+ (144.8769, -38.4174),
+ (145.0322, -37.8961),
+ (144.4856, -38.0853),
+ (143.6099, -38.8094),
+ (142.7454, -38.5382),
+ (142.1783, -38.3800),
+ (141.6065, -38.3085),
+ (140.6385, -38.0193),
+ (139.9921, -37.4029),
+ (139.8065, -36.6436),
+ (139.5741, -36.1383),
+ (139.0828, -35.7327),
+ (138.1207, -35.6122),
+ (138.4494, -35.1272),
+ (138.2075, -34.3847),
+ (137.7191, -35.0768),
+ (136.8294, -35.2605),
+ (137.3523, -34.7073),
+ (137.5038, -34.1302),
+ (137.8901, -33.6404),
+ (137.8103, -32.9000),
+ (136.9968, -33.7527),
+ (136.3720, -34.0947),
+ (135.9890, -34.8901),
+ (135.2082, -34.4786),
+ (135.2392, -33.9479),
+ (134.6134, -33.2227),
+ (134.0859, -32.8480),
+ (134.2739, -32.6172),
+ (132.9907, -32.0112),
+ (132.2880, -31.9826),
+ (131.3263, -31.4958),
+ (129.5357, -31.5904),
+ (128.2409, -31.9484),
+ (127.1028, -32.2822),
+ (126.1487, -32.2159),
+ (81.7879, 7.5230),
+ (81.6373, 6.4817),
+ (81.2180, 6.1971),
+ (80.3483, 5.9683),
+ (79.8724, 6.7634),
+ (79.6951, 8.2008),
+ (80.1478, 9.8240),
+ (80.8388, 9.2684),
+ (81.3043, 8.5642),
+ (81.7879, 7.5230),
+ (129.3709, -2.8021),
+ (130.4713, -3.0937),
+ (130.8348, -3.8584),
+ (129.9905, -3.4463),
+ (129.1552, -3.3626),
+ (128.5906, -3.4286),
+ (127.8988, -3.3934),
+ (128.1358, -2.8436),
+ (129.3709, -2.8021),
+ (126.8749, -3.7909),
+ (126.1838, -3.6073),
+ (125.9890, -3.1772),
+ (127.0006, -3.1293),
+ (127.2492, -3.4590),
+ (126.8749, -3.7909),
+ (127.9323, 2.1745),
+ (128.0041, 1.6285),
+ (128.5945, 1.5408),
+ (128.6882, 1.1323),
+ (128.6359, 0.2584),
+ (128.1201, 0.3564),
+ (127.9680, -0.2520),
+ (128.3799, -0.7800),
+ (128.1000, -0.8999),
+ (127.6964, -0.2665),
+ (127.3994, 1.0117),
+ (127.6005, 1.8106),
+ (127.9323, 2.1745),
+ (122.9275, 0.8751),
+ (124.0775, 0.9171),
+ (125.0659, 1.6432),
+ (125.2405, 1.4198),
+ (124.4370, 0.4278),
+ (123.6855, 0.2355),
+ (122.7230, 0.4311),
+ (121.0567, 0.3812),
+ (120.1830, 0.2372),
+ (120.0408, -0.5196),
+ (120.9359, -1.4089),
+ (121.4758, -0.9559),
+ (123.3405, -0.6156),
+ (123.2583, -1.0762),
+ (122.8226, -0.9309),
+ (122.3885, -1.5168),
+ (121.5082, -1.9044),
+ (122.4545, -3.1860),
+ (122.2718, -3.5295),
+ (123.1709, -4.6836),
+ (123.1623, -5.3406),
+ (122.6285, -5.6345),
+ (122.2363, -5.2829),
+ (122.7195, -4.4641),
+ (121.7382, -4.8513),
+ (121.4894, -4.5745),
+ (121.6191, -4.1884),
+ (120.8981, -3.6021),
+ (120.9723, -2.6276),
+ (120.3054, -2.9316),
+ (120.3900, -4.0975),
+ (120.4307, -5.5282),
+ (119.7965, -5.6734),
+ (119.3669, -5.3798),
+ (119.6536, -4.4594),
+ (119.4988, -3.4944),
+ (119.0783, -3.4870),
+ (118.7677, -2.8019),
+ (119.1809, -2.1471),
+ (119.3233, -1.3531),
+ (119.8259, 0.1542),
+ (120.0357, 0.5664),
+ (120.8857, 1.3092),
+ (121.6668, 1.0139),
+ (122.9275, 0.8751),
+ (120.2950, -10.2586),
+ (118.9678, -9.5579),
+ (119.9003, -9.3613),
+ (120.4257, -9.6659),
+ (120.7755, -9.9696),
+ (120.7156, -10.2395),
+ (120.2950, -10.2586),
+ (121.3416, -8.5367),
+ (122.0073, -8.4606),
+ (122.9035, -8.0942),
+ (122.7569, -8.6498),
+ (121.2544, -8.9336),
+ (119.9243, -8.8104),
+ (119.9209, -8.4448),
+ (120.7150, -8.2369),
+ (121.3416, -8.5367),
+ (118.2606, -8.3623),
+ (118.8784, -8.2806),
+ (119.1265, -8.7058),
+ (117.9704, -8.9066),
+ (117.2777, -9.0408),
+ (116.7401, -9.0328),
+ (117.0837, -8.4571),
+ (117.6320, -8.4493),
+ (117.9000, -8.0956),
+ (118.2606, -8.3623),
+ (108.4868, -6.4219),
+ (108.6234, -6.7776),
+ (110.5392, -6.8773),
+ (110.7595, -6.4651),
+ (112.6148, -6.9460),
+ (112.9787, -7.5942),
+ (114.4789, -7.7765),
+ (115.7055, -8.3708),
+ (114.5645, -8.7518),
+ (113.4647, -8.3489),
+ (112.5596, -8.3761),
+ (111.5220, -8.3021),
+ (110.5861, -8.1226),
+ (109.4276, -7.7406),
+ (108.6936, -7.6416),
+ (108.2777, -7.7666),
+ (106.4541, -7.3548),
+ (106.2806, -6.9248),
+ (105.3654, -6.8514),
+ (106.0516, -5.8959),
+ (107.2650, -5.9549),
+ (108.0720, -6.3457),
+ (108.4868, -6.4219),
+ (104.3699, -1.0848),
+ (104.5394, -1.7823),
+ (104.8878, -2.3404),
+ (105.6221, -2.4288),
+ (106.1085, -3.0617),
+ (105.8574, -4.3055),
+ (105.8176, -5.8523),
+ (104.7103, -5.8732),
+ (103.8682, -5.0373),
+ (102.5842, -4.2202),
+ (102.1561, -3.6141),
+ (101.3991, -2.7997),
+ (100.9025, -2.0502),
+ (100.1419, -0.6503),
+ (99.2637, 0.1831),
+ (98.9700, 1.0428),
+ (98.6013, 1.8235),
+ (97.6995, 2.4531),
+ (97.1769, 3.3087),
+ (96.4240, 3.8688),
+ (95.3808, 4.9707),
+ (95.2930, 5.4798),
+ (95.9368, 5.4395),
+ (97.4848, 5.2463),
+ (98.3691, 4.2683),
+ (99.1425, 3.5903),
+ (99.6939, 3.1743),
+ (100.6414, 2.0993),
+ (101.6580, 2.0836),
+ (102.4982, 1.3987),
+ (103.0768, 0.5613),
+ (103.8383, 0.1045),
+ (103.4376, -0.7119),
+ (104.0107, -1.0592),
+ (104.3699, -1.0848),
+ (120.8338, 12.7044),
+ (120.3234, 13.4664),
+ (121.1801, 13.4296),
+ (121.5273, 13.0695),
+ (121.2621, 12.2055),
+ (120.8338, 12.7044),
+ (122.5860, 9.9810),
+ (122.8370, 10.2611),
+ (122.9474, 10.8818),
+ (123.4988, 10.9406),
+ (123.3377, 10.2673),
+ (124.0779, 11.2327),
+ (123.9824, 10.2787),
+ (123.6230, 9.9500),
+ (123.3099, 9.3182),
+ (122.9958, 9.0221),
+ (122.3800, 9.7133),
+ (122.5860, 9.9810),
+ (126.3768, 8.4147),
+ (126.4785, 7.7503),
+ (126.5374, 7.1894),
+ (126.1967, 6.2742),
+ (125.8314, 7.2937),
+ (125.3638, 6.7864),
+ (125.6831, 6.0496),
+ (125.3965, 5.5810),
+ (124.2197, 6.1613),
+ (123.9387, 6.8851),
+ (124.2436, 7.3606),
+ (123.6101, 7.8335),
+ (123.2960, 7.4188),
+ (122.8255, 7.4573),
+ (122.0854, 6.8994),
+ (121.9199, 7.1921),
+ (122.3123, 8.0349),
+ (122.9423, 8.3162),
+ (123.4876, 8.6930),
+ (123.8411, 8.2403),
+ (124.6014, 8.5141),
+ (124.7646, 8.9604),
+ (125.4713, 8.9869),
+ (125.4121, 9.7603),
+ (126.2227, 9.2860),
+ (126.3066, 8.7824),
+ (126.3768, 8.4147),
+ (109.4752, 18.1977),
+ (108.6552, 18.5076),
+ (108.6262, 19.3678),
+ (109.1190, 19.8210),
+ (110.2115, 20.1012),
+ (110.7865, 20.0775),
+ (111.0100, 19.6959),
+ (110.5706, 19.2558),
+ (110.3391, 18.6783),
+ (109.4752, 18.1977),
+ (121.7778, 24.3942),
+ (121.1756, 22.7908),
+ (120.7470, 21.9705),
+ (120.2200, 22.8148),
+ (120.1061, 23.5562),
+ (120.6946, 24.5384),
+ (121.4950, 25.2954),
+ (121.9512, 24.9975),
+ (121.7778, 24.3942),
+ (141.8846, 39.1808),
+ (140.9594, 38.1740),
+ (140.9763, 37.1420),
+ (140.5997, 36.3439),
+ (140.7740, 35.8428),
+ (140.2532, 35.1381),
+ (138.9755, 34.6676),
+ (137.2175, 34.6062),
+ (135.7929, 33.4648),
+ (135.1209, 33.8490),
+ (135.0794, 34.5965),
+ (133.3403, 34.3759),
+ (132.1567, 33.9049),
+ (130.9861, 33.8857),
+ (132.0000, 33.1499),
+ (131.3327, 31.4503),
+ (130.6863, 31.0295),
+ (130.2024, 31.4182),
+ (130.4476, 32.3194),
+ (129.8146, 32.6103),
+ (129.4084, 33.2960),
+ (130.3539, 33.6041),
+ (130.8784, 34.2327),
+ (131.8842, 34.7497),
+ (132.6176, 35.4333),
+ (134.6083, 35.7316),
+ (135.6775, 35.5271),
+ (136.7238, 37.3049),
+ (137.3906, 36.8273),
+ (138.8576, 37.8274),
+ (139.4264, 38.2159),
+ (140.0547, 39.4388),
+ (139.8833, 40.5633),
+ (140.3057, 41.1950),
+ (141.3689, 41.3785),
+ (141.9142, 39.9916),
+ (141.8846, 39.1808),
+ (144.6134, 43.9609),
+ (145.3208, 44.3847),
+ (145.5431, 43.2621),
+ (144.0596, 42.9883),
+ (143.1838, 41.9952),
+ (141.6114, 42.6787),
+ (141.0672, 41.5845),
+ (139.9551, 41.5695),
+ (139.8175, 42.5637),
+ (140.3120, 43.3332),
+ (141.3805, 43.3888),
+ (141.6719, 44.7721),
+ (141.9676, 45.5514),
+ (143.1428, 44.5103),
+ (143.9101, 44.1740),
+ (144.6134, 43.9609),
+ (8.7099, 40.8999),
+ (9.2100, 41.2099),
+ (9.8099, 40.5000),
+ (9.6695, 39.1773),
+ (9.2148, 39.2404),
+ (8.8069, 38.9066),
+ (8.4283, 39.1718),
+ (8.3882, 40.3783),
+ (8.1599, 40.9500),
+ (8.7099, 40.8999),
+ (8.7460, 42.6281),
+ (9.3900, 43.0099),
+ (9.5600, 42.1525),
+ (9.2297, 41.3800),
+ (8.7757, 41.5836),
+ (8.5442, 42.2565),
+ (8.7460, 42.6281),
+ (12.3709, 56.1114),
+ (12.6900, 55.6099),
+ (12.0899, 54.8000),
+ (11.0435, 55.3648),
+ (10.9039, 55.7799),
+ (12.3709, 56.1114),
+ (-4.2114, 58.5508),
+ (-3.0050, 58.6350),
+ (-4.0738, 57.5530),
+ (-3.0550, 57.6900),
+ (-1.9592, 57.6847),
+ (-2.2199, 56.8700),
+ (-3.1190, 55.9737),
+ (-2.0850, 55.9099),
+ (-1.1149, 54.6249),
+ (-0.4304, 54.4643),
+ (0.1849, 53.3250),
+ (0.4699, 52.9299),
+ (1.6815, 52.7395),
+ (1.5599, 52.0999),
+ (1.0505, 51.8067),
+ (1.4498, 51.2894),
+ (0.5503, 50.7657),
+ (-0.7875, 50.7749),
+ (-2.4899, 50.5000),
+ (-2.9562, 50.6968),
+ (-3.6174, 50.2283),
+ (-4.5425, 50.3418),
+ (-5.2450, 49.9599),
+ (-5.7765, 50.1596),
+ (-4.3099, 51.2100),
+ (-3.4148, 51.4260),
+ (-4.9843, 51.5934),
+ (-5.2672, 51.9914),
+ (-4.2223, 52.3013),
+ (-4.7700, 52.8400),
+ (-4.5799, 53.4950),
+ (-3.0920, 53.4044),
+ (-2.9450, 53.9849),
+ (-3.6300, 54.6150),
+ (-4.8441, 54.7909),
+ (-5.0825, 55.0616),
+ (-4.7191, 55.5084),
+ (-5.0479, 55.7839),
+ (-5.5863, 55.3111),
+ (-5.6449, 56.2750),
+ (-6.1499, 56.7850),
+ (-5.7868, 57.8188),
+ (-5.0099, 58.6300),
+ (-4.2114, 58.5508),
+ (-14.5086, 66.4558),
+ (-14.7396, 65.8087),
+ (-13.6097, 65.1266),
+ (-14.9098, 64.3640),
+ (-17.7944, 63.6787),
+ (-18.6562, 63.4963),
+ (-19.9727, 63.6436),
+ (-22.7629, 63.9601),
+ (-21.7784, 64.4021),
+ (-23.9550, 64.8911),
+ (-22.1844, 65.0849),
+ (-22.2274, 65.3785),
+ (-24.3261, 65.6111),
+ (-23.6505, 66.2625),
+ (-22.1349, 66.4104),
+ (-20.5762, 65.7321),
+ (-19.0568, 66.2766),
+ (-17.7986, 65.9938),
+ (-16.1678, 66.5268),
+ (-14.5086, 66.4558),
+ (142.9146, 53.7045),
+ (143.2608, 52.7407),
+ (143.2352, 51.7566),
+ (143.6480, 50.7476),
+ (144.6541, 48.9763),
+ (143.1739, 49.3065),
+ (142.5586, 47.8615),
+ (143.5334, 46.8367),
+ (143.5052, 46.1379),
+ (142.7477, 46.7407),
+ (142.0920, 45.9667),
+ (141.9069, 46.8059),
+ (142.0184, 47.7801),
+ (141.9044, 48.8591),
+ (142.1358, 49.6151),
+ (142.1799, 50.9523),
+ (141.5940, 51.9354),
+ (141.6825, 53.3019),
+ (142.6069, 53.7621),
+ (142.2097, 54.2254),
+ (142.6547, 54.3658),
+ (142.9146, 53.7045),
+ (118.5045, 9.3163),
+ (117.1742, 8.3674),
+ (117.6644, 9.0668),
+ (118.3869, 9.6844),
+ (118.9873, 10.3762),
+ (119.5114, 11.3696),
+ (119.6896, 10.5542),
+ (119.0294, 10.0036),
+ (118.5045, 9.3163),
+ (122.3369, 18.2248),
+ (122.1742, 17.8102),
+ (122.5156, 17.0935),
+ (122.2523, 16.2624),
+ (121.6627, 15.9310),
+ (121.5050, 15.1248),
+ (121.7288, 14.3283),
+ (122.2589, 14.2182),
+ (122.7012, 14.3365),
+ (123.9502, 13.7821),
+ (123.8551, 13.2377),
+ (124.1812, 12.9975),
+ (124.0774, 12.5366),
+ (123.2980, 13.0275),
+ (122.9286, 13.5529),
+ (122.6713, 13.1858),
+ (122.0346, 13.7844),
+ (121.1263, 13.6366),
+ (120.6286, 13.8576),
+ (120.6793, 14.2710),
+ (120.9918, 14.5253),
+ (120.6933, 14.7566),
+ (120.5641, 14.3962),
+ (120.0704, 14.9708),
+ (119.9209, 15.4063),
+ (119.8837, 16.3637),
+ (120.2864, 16.0346),
+ (120.3900, 17.5990),
+ (120.7158, 18.5052),
+ (121.3213, 18.5040),
+ (121.9376, 18.2185),
+ (122.2460, 18.4789),
+ (122.3369, 18.2248),
+ (122.0383, 11.4158),
+ (121.8835, 11.8917),
+ (122.4838, 11.5822),
+ (123.1202, 11.5836),
+ (123.1008, 11.1659),
+ (122.6377, 10.7413),
+ (122.0026, 10.4410),
+ (121.9673, 10.9056),
+ (122.0383, 11.4158),
+ (125.5025, 12.1626),
+ (125.7834, 11.0461),
+ (125.0118, 11.3114),
+ (125.0327, 10.9758),
+ (125.2774, 10.3587),
+ (124.8018, 10.1346),
+ (124.7601, 10.8379),
+ (124.4591, 10.8899),
+ (124.3025, 11.4953),
+ (124.8910, 11.4155),
+ (124.8779, 11.7941),
+ (124.2667, 12.5577),
+ (125.2271, 12.5357),
+ (125.5025, 12.1626),
+ (-77.3533, 8.6705),
+ (-76.8366, 8.6387),
+ (-76.0863, 9.3368),
+ (-75.6746, 9.4432),
+ (-75.6647, 9.7740),
+ (-75.4804, 10.6189),
+ (-74.9068, 11.0830),
+ (-74.2767, 11.1020),
+ (-74.1972, 11.3104),
+ (-73.4147, 11.2270),
+ (-72.6278, 11.7319),
+ (-72.2381, 11.9555),
+ (-71.7540, 12.4373),
+ (-71.3998, 12.3760),
+ (-71.1374, 12.1129),
+ (-71.3315, 11.7762),
+ (-71.3600, 11.5399),
+ (-71.9470, 11.4232),
+ (-71.6208, 10.9694),
+ (-71.6330, 10.4464),
+ (-72.0741, 9.8656),
+ (-71.6956, 9.0722),
+ (-71.2645, 9.1372),
+ (-71.0399, 9.8599),
+ (-71.3500, 10.2119),
+ (-71.4006, 10.9689),
+ (-70.1552, 11.3754),
+ (-70.2938, 11.8468),
+ (-69.9432, 12.1623),
+ (-69.5843, 11.4596),
+ (-68.8829, 11.4433),
+ (-68.2332, 10.8857),
+ (-68.1941, 10.5546),
+ (-67.2962, 10.5458),
+ (-66.2278, 10.6486),
+ (-65.6552, 10.2007),
+ (-64.8904, 10.0772),
+ (-64.3294, 10.3895),
+ (-64.3180, 10.6414),
+ (-63.0793, 10.7017),
+ (-61.8809, 10.7156),
+ (-62.7301, 10.4202),
+ (-62.3885, 9.9482),
+ (-61.5887, 9.8730),
+ (-60.8305, 9.3813),
+ (-60.6712, 8.5801),
+ (-60.1500, 8.6027),
+ (-59.7582, 8.3670),
+ (-59.1016, 7.9992),
+ (-58.4829, 7.3476),
+ (-58.4548, 6.8327),
+ (-58.0781, 6.8090),
+ (-57.5422, 6.3212),
+ (-57.1474, 5.9731),
+ (-55.9493, 5.7728),
+ (-55.8417, 5.9531),
+ (-55.0332, 6.0252),
+ (-53.9580, 5.7565),
+ (-53.6184, 5.6465),
+ (-52.8821, 5.4098),
+ (-51.8233, 4.5657),
+ (-51.6577, 4.1562),
+ (-51.2999, 4.1200),
+ (-51.0697, 3.6503),
+ (-50.5088, 1.9015),
+ (-49.9740, 1.7364),
+ (-49.9471, 1.0461),
+ (-50.6992, 0.2229),
+ (-50.3882, -0.0784),
+ (-48.6205, -0.2354),
+ (-48.5844, -1.2378),
+ (-47.8249, -0.5816),
+ (-46.5665, -0.9410),
+ (-44.9057, -1.5517),
+ (-44.4176, -2.1377),
+ (-44.5815, -2.6913),
+ (-43.4187, -2.3831),
+ (-41.4726, -2.9120),
+ (-39.9786, -2.8730),
+ (-38.5003, -3.7006),
+ (-37.2232, -4.8209),
+ (-36.4529, -5.1094),
+ (-35.5977, -5.1495),
+ (-35.2353, -5.4649),
+ (-34.8960, -6.7381),
+ (-34.7299, -7.3432),
+ (-35.1282, -8.9964),
+ (-35.6369, -9.6492),
+ (-37.0465, -11.0407),
+ (-37.6836, -12.1711),
+ (-38.4238, -13.0381),
+ (-38.6738, -13.0576),
+ (-38.9532, -13.7933),
+ (-38.8822, -15.6670),
+ (-39.1610, -17.2084),
+ (-39.2673, -17.8677),
+ (-39.5835, -18.2622),
+ (-39.7608, -19.5991),
+ (-40.7747, -20.9045),
+ (-40.9447, -21.9373),
+ (-41.7541, -22.3706),
+ (-41.9882, -22.9700),
+ (-43.0747, -22.9676),
+ (-44.6478, -23.3519),
+ (-45.3521, -23.7968),
+ (-46.4720, -24.0889),
+ (-47.6489, -24.8851),
+ (-48.4954, -25.8770),
+ (-48.6410, -26.6236),
+ (-48.4747, -27.1759),
+ (-48.6615, -28.1861),
+ (-48.8884, -28.6741),
+ (-49.5873, -29.2244),
+ (-50.6968, -30.9844),
+ (-51.5762, -31.7776),
+ (-52.2560, -32.2453),
+ (-52.7120, -33.1965),
+ (-53.3736, -33.7683),
+ (-53.8064, -34.3968),
+ (-54.9358, -34.9526),
+ (-55.6740, -34.7526),
+ (-56.2152, -34.8598),
+ (-57.1396, -34.4304),
+ (-57.8178, -34.4625),
+ (-58.4270, -33.9094),
+ (-58.4954, -34.4314),
+ (-57.2258, -35.2880),
+ (-57.3623, -35.9773),
+ (-56.7374, -36.4131),
+ (-56.7882, -36.9015),
+ (-57.7491, -38.1838),
+ (-59.2318, -38.7201),
+ (-61.2374, -38.9284),
+ (-62.3359, -38.8277),
+ (-62.1257, -39.4241),
+ (-62.3305, -40.1725),
+ (-62.1459, -40.6768),
+ (-62.7458, -41.0287),
+ (-63.7704, -41.1667),
+ (-64.7320, -40.8026),
+ (-65.1180, -41.0643),
+ (-64.9785, -42.0580),
+ (-64.3034, -42.3590),
+ (-63.7559, -42.0436),
+ (-63.4580, -42.5631),
+ (-64.3788, -42.8735),
+ (-65.1818, -43.4953),
+ (-65.3288, -44.5013),
+ (-65.5652, -45.0367),
+ (-66.5099, -45.0396),
+ (-67.2937, -45.5518),
+ (-67.5805, -46.3017),
+ (-66.5970, -47.0338),
+ (-65.6410, -47.2361),
+ (-65.9850, -48.1332),
+ (-67.1661, -48.6973),
+ (-67.8160, -49.8696),
+ (-68.7287, -50.2641),
+ (-69.1385, -50.7325),
+ (-68.8155, -51.7711),
+ (-68.1499, -52.3499),
+ (-68.5715, -52.2994),
+ (-69.4612, -52.2919),
+ (-69.9427, -52.5379),
+ (-70.8451, -52.8991),
+ (-71.0063, -53.8332),
+ (-71.4297, -53.8564),
+ (-72.5579, -53.5314),
+ (-73.7027, -52.8350),
+ (-74.9467, -52.2627),
+ (-75.2600, -51.6293),
+ (-74.9766, -51.0433),
+ (-75.4797, -50.3783),
+ (-75.6080, -48.6737),
+ (-75.1827, -47.7119),
+ (-74.1265, -46.9392),
+ (-75.6443, -46.6476),
+ (-74.6921, -45.7639),
+ (-74.3517, -44.1030),
+ (-73.2403, -44.4549),
+ (-72.7178, -42.3833),
+ (-73.3888, -42.1175),
+ (-73.7013, -43.3657),
+ (-74.3319, -43.2249),
+ (-74.0179, -41.7948),
+ (-73.6770, -39.9422),
+ (-73.2175, -39.2586),
+ (-73.5055, -38.2828),
+ (-73.5880, -37.1562),
+ (-73.1667, -37.1237),
+ (-72.5531, -35.5088),
+ (-71.8617, -33.9090),
+ (-71.4384, -32.4188),
+ (-71.6687, -30.9206),
+ (-71.3700, -30.0956),
+ (-71.4898, -28.8614),
+ (-70.9051, -27.6403),
+ (-70.7249, -25.7059),
+ (-70.4039, -23.6289),
+ (-70.0912, -21.3933),
+ (-70.1644, -19.7564),
+ (-70.3725, -18.3479),
+ (-71.3752, -17.7737),
+ (-71.4620, -17.3634),
+ (-73.4445, -16.3593),
+ (-75.2378, -15.2656),
+ (-76.0092, -14.6492),
+ (-76.4234, -13.8231),
+ (-76.2592, -13.5350),
+ (-77.1061, -12.2227),
+ (-78.0921, -10.3777),
+ (-79.0369, -8.3865),
+ (-79.4459, -7.9308),
+ (-79.7605, -7.1943),
+ (-80.5374, -6.5416),
+ (-81.2499, -6.1368),
+ (-80.9263, -5.6905),
+ (-81.4109, -4.7367),
+ (-81.0996, -4.0363),
+ (-80.3025, -3.4048),
+ (-79.7702, -2.6575),
+ (-79.9865, -2.2207),
+ (-80.3687, -2.6851),
+ (-80.9677, -2.2469),
+ (-80.7648, -1.9650),
+ (-80.9336, -1.0574),
+ (-80.5833, -0.9066),
+ (-80.3993, -0.2837),
+ (-80.0208, 0.3603),
+ (-80.0906, 0.7684),
+ (-79.5427, 0.9829),
+ (-78.8552, 1.3809),
+ (-78.9909, 1.6913),
+ (-78.6178, 1.7664),
+ (-78.6621, 2.2673),
+ (-78.4276, 2.6295),
+ (-77.9315, 2.6966),
+ (-77.5104, 3.3250),
+ (-77.1276, 3.8496),
+ (-77.4962, 4.0876),
+ (-77.3076, 4.6679),
+ (-77.5332, 5.5828),
+ (-77.3188, 5.8453),
+ (-77.4766, 6.6911),
+ (-77.8815, 7.2237),
+ (-74.6625, -52.8374),
+ (-73.8380, -53.0474),
+ (-72.4341, -53.7153),
+ (-71.1077, -54.0743),
+ (-70.5917, -53.6158),
+ (-70.2674, -52.9312),
+ (-69.3456, -52.5182),
+ (-68.6341, -52.6362),
+ (-68.2500, -53.1000),
+ (-67.7499, -53.8499),
+ (-66.4499, -54.4500),
+ (-65.0500, -54.7000),
+ (-65.5000, -55.1999),
+ (-66.4499, -55.2500),
+ (-66.9599, -54.8968),
+ (-67.2910, -55.3012),
+ (-68.1486, -55.6118),
+ (-68.6399, -55.5800),
+ (-69.2320, -55.4990),
+ (-69.9580, -55.1984),
+ (-71.0056, -55.0538),
+ (-72.2639, -54.4951),
+ (-73.2852, -53.9575),
+ (-74.6625, -52.8374),
+ (44.8469, 80.5898),
+ (46.7991, 80.7719),
+ (48.3184, 80.7840),
+ (48.5228, 80.5145),
+ (49.0971, 80.7539),
+ (50.0397, 80.9188),
+ (51.5229, 80.6997),
+ (51.1361, 80.5472),
+ (49.7936, 80.4154),
+ (48.8944, 80.3395),
+ (48.7549, 80.1754),
+ (47.5861, 80.0101),
+ (46.5028, 80.2472),
+ (47.0724, 80.5594),
+ (44.8469, 80.5898),
+ (53.5082, 73.7498),
+ (55.9024, 74.6274),
+ (55.6319, 75.0814),
+ (57.8686, 75.6093),
+ (61.1700, 76.2518),
+ (64.4983, 76.4390),
+ (66.2109, 76.8097),
+ (68.1570, 76.9396),
+ (68.8522, 76.5448),
+ (68.1805, 76.2336),
+ (64.6373, 75.7377),
+ (61.5835, 75.2608),
+ (58.4770, 74.3090),
+ (56.9867, 73.3330),
+ (55.4193, 72.3712),
+ (55.6228, 71.5405),
+ (57.5356, 70.7204),
+ (56.9449, 70.6327),
+ (53.6773, 70.7626),
+ (53.4120, 71.2066),
+ (51.6018, 71.4747),
+ (51.4557, 72.0148),
+ (52.4782, 72.2294),
+ (52.4441, 72.7747),
+ (54.4276, 73.6275),
+ (53.5082, 73.7498),
+ (27.4075, 80.0564),
+ (25.9246, 79.5178),
+ (23.0244, 79.4000),
+ (20.0751, 79.5668),
+ (19.8972, 79.8423),
+ (18.4622, 79.8598),
+ (17.3680, 80.3188),
+ (20.4559, 80.5981),
+ (21.9079, 80.3576),
+ (22.9192, 80.6571),
+ (25.4476, 80.4073),
+ (27.4075, 80.0564),
+ (24.7241, 77.8538),
+ (22.4903, 77.4449),
+ (20.7260, 77.6770),
+ (21.4160, 77.9350),
+ (20.8118, 78.2546),
+ (22.8842, 78.4549),
+ (23.2813, 78.0795),
+ (24.7241, 77.8538),
+ (15.1428, 79.6743),
+ (15.5225, 80.0160),
+ (16.9908, 80.0508),
+ (18.2518, 79.7017),
+ (21.5438, 78.9561),
+ (19.0273, 78.5625),
+ (18.4717, 77.8266),
+ (17.5944, 77.6379),
+ (17.1182, 76.8094),
+ (15.9131, 76.7704),
+ (13.7626, 77.3803),
+ (14.6695, 77.7356),
+ (13.1705, 78.0249),
+ (11.2222, 78.8692),
+ (10.4445, 79.6523),
+ (13.1707, 80.0104),
+ (13.7185, 79.6604),
+ (15.1428, 79.6743),
+ (-77.8815, 7.2237),
+ (-78.2149, 7.5122),
+ (-78.4291, 8.0520),
+ (-78.1820, 8.3191),
+ (-78.4354, 8.3877),
+ (-78.6221, 8.7181),
+ (-79.1203, 8.9960),
+ (-79.5578, 8.9323),
+ (-79.7605, 8.5845),
+ (-80.1644, 8.3333),
+ (-80.3826, 8.2984),
+ (-80.4806, 8.0903),
+ (-80.0036, 7.5475),
+ (-80.2766, 7.4197),
+ (-80.4211, 7.2715),
+ (-80.8864, 7.2205),
+ (-81.0595, 7.8179),
+ (-81.1897, 7.6479),
+ (-81.5195, 7.7066),
+ (-81.7213, 8.1089),
+ (-82.1314, 8.1753),
+ (-82.3909, 8.2923),
+ (-82.6054, 8.2916),
+ (-82.8200, 8.2908),
+ (-82.8509, 8.0738),
+ (-82.9657, 8.2250),
+ (-83.5084, 8.4469),
+ (-83.7114, 8.6568),
+ (-83.5963, 8.8304),
+ (-83.6326, 9.0513),
+ (-83.9098, 9.2908),
+ (-84.3034, 9.4873),
+ (-84.6476, 9.6155),
+ (-84.7133, 9.9080),
+ (-84.9756, 10.0867),
+ (-84.9113, 9.7959),
+ (-85.1109, 9.5570),
+ (-85.3394, 9.8345),
+ (-85.6607, 9.9333),
+ (-85.7974, 10.1348),
+ (-85.7917, 10.4393),
+ (-85.6593, 10.7543),
+ (-85.8005, 10.8247),
+ (-85.9417, 10.8952),
+ (-85.7125, 11.0884),
+ (-86.0584, 11.4034),
+ (-86.5258, 11.8068),
+ (-86.7459, 12.1439),
+ (-87.1675, 12.4582),
+ (-87.6684, 12.9099),
+ (-87.5574, 13.0645),
+ (-87.3923, 12.9140),
+ (-87.3166, 12.9846),
+ (-87.4894, 13.2975),
+ (-87.6412, 13.3410),
+ (-87.7931, 13.3844),
+ (-87.9041, 13.1490),
+ (-88.4833, 13.1639),
+ (-88.8432, 13.2597),
+ (-89.2567, 13.4585),
+ (-89.8123, 13.5206),
+ (-90.0955, 13.7353),
+ (-90.6086, 13.9097),
+ (-91.2324, 13.9278),
+ (-91.6897, 14.1262),
+ (-92.2277, 14.5388),
+ (-93.3594, 15.6154),
+ (-93.8751, 15.9401),
+ (-94.6916, 16.2009),
+ (-95.2502, 16.1283),
+ (-96.0533, 15.7520),
+ (-96.5574, 15.6535),
+ (-97.2635, 15.9170),
+ (-98.0130, 16.1073),
+ (-98.9476, 16.5660),
+ (-99.6973, 16.7061),
+ (-100.8294, 17.1710),
+ (-101.6660, 17.6490),
+ (-101.9185, 17.9160),
+ (-102.4781, 17.9757),
+ (-103.5009, 18.2922),
+ (-103.9175, 18.7485),
+ (-104.9920, 19.3161),
+ (-105.4930, 19.9467),
+ (-105.7313, 20.4341),
+ (-105.3977, 20.5317),
+ (-105.5006, 20.8168),
+ (-105.2707, 21.0762),
+ (-105.2658, 21.4221),
+ (-105.6031, 21.8711),
+ (-105.6934, 22.2690),
+ (-106.0287, 22.7737),
+ (-106.9099, 23.7678),
+ (-107.9154, 24.5489),
+ (-108.4019, 25.1723),
+ (-109.2601, 25.5806),
+ (-109.4440, 25.8248),
+ (-109.2916, 26.4429),
+ (-109.8014, 26.6761),
+ (-110.3917, 27.1621),
+ (-110.6410, 27.8598),
+ (-111.1789, 27.9412),
+ (-111.7596, 28.4679),
+ (-112.2282, 28.9544),
+ (-112.2718, 29.2668),
+ (-112.8095, 30.0211),
+ (-113.1638, 30.7868),
+ (-113.1486, 31.1709),
+ (-113.8718, 31.5676),
+ (-114.2057, 31.5240),
+ (-114.7764, 31.7995),
+ (-114.9366, 31.3934),
+ (-114.7712, 30.9136),
+ (-114.6738, 30.1626),
+ (-114.3309, 29.7504),
+ (-113.5888, 29.0616),
+ (-113.4240, 28.8261),
+ (-113.2719, 28.7547),
+ (-113.1400, 28.4112),
+ (-112.9622, 28.4251),
+ (-112.7615, 27.7802),
+ (-112.4579, 27.5258),
+ (-112.2449, 27.1717),
+ (-111.6164, 26.6628),
+ (-111.2846, 25.7325),
+ (-110.9878, 25.2946),
+ (-110.7100, 24.8260),
+ (-110.6550, 24.2985),
+ (-110.1728, 24.2655),
+ (-109.7718, 23.8111),
+ (-109.4091, 23.3646),
+ (-109.4333, 23.1855),
+ (-109.8542, 22.8182),
+ (-110.0313, 22.8230),
+ (-110.2950, 23.4309),
+ (-110.9495, 24.0009),
+ (-111.6705, 24.4844),
+ (-112.1820, 24.7384),
+ (-112.1489, 25.4701),
+ (-112.3007, 26.0120),
+ (-112.7772, 26.3219),
+ (-113.4646, 26.7681),
+ (-113.5967, 26.6394),
+ (-113.8489, 26.9000),
+ (-114.4657, 27.1420),
+ (-115.0551, 27.7227),
+ (-114.9822, 27.7982),
+ (-114.5703, 27.7414),
+ (-114.1993, 28.1150),
+ (-114.1620, 28.5661),
+ (-114.9318, 29.2794),
+ (-115.5186, 29.5563),
+ (-115.8873, 30.1807),
+ (-116.2583, 30.8364),
+ (-116.7215, 31.6357),
+ (-117.1277, 32.5353),
+ (-117.2959, 33.0462),
+ (-117.944, 33.6212),
+ (-118.4106, 33.7409),
+ (-118.5198, 34.0277),
+ (-119.081, 34.078),
+ (-119.4388, 34.3484),
+ (-120.3677, 34.4470),
+ (-120.6228, 34.6085),
+ (-120.7443, 35.1568),
+ (-121.7145, 36.1615),
+ (-122.5474, 37.5517),
+ (-122.5119, 37.7833),
+ (-122.9531, 38.1136),
+ (-123.7271, 38.9516),
+ (-123.8651, 39.7669),
+ (-124.3980, 40.3131),
+ (-124.1788, 41.1420),
+ (-124.2137, 41.9996),
+ (-124.5328, 42.7659),
+ (-124.1421, 43.7083),
+ (-124.0205, 44.6158),
+ (-123.8989, 45.5234),
+ (-124.0796, 46.8647),
+ (-124.3956, 47.7201),
+ (-124.6872, 48.1844),
+ (-124.5661, 48.3797),
+ (-123.12, 48.04),
+ (-122.5873, 47.0959),
+ (-122.3399, 47.3600),
+ (-122.5000, 48.1800),
+ (-122.84, 49.0),
+ (-122.9742, 49.0025),
+ (-124.9102, 49.9845),
+ (-125.6246, 50.4165),
+ (-127.4356, 50.8305),
+ (-127.9927, 51.7158),
+ (-127.8503, 52.3296),
+ (-129.1297, 52.7553),
+ (-129.3052, 53.5615),
+ (-130.5149, 54.2875),
+ (-130.5361, 54.8027),
+ (-131.0858, 55.1789),
+ (-131.9672, 55.4977),
+ (-132.2500, 56.3699),
+ (-133.5391, 57.1788),
+ (-134.0780, 58.1230),
+ (-135.0382, 58.1877),
+ (-136.6280, 58.2122),
+ (-137.8000, 58.4999),
+ (-139.8677, 59.5377),
+ (-140.8252, 59.7275),
+ (-142.5744, 60.0844),
+ (-143.9588, 59.9991),
+ (-145.9255, 60.4586),
+ (-147.1143, 60.8846),
+ (-148.2243, 60.6729),
+ (-148.0180, 59.9783),
+ (-148.5708, 59.9141),
+ (-149.7278, 59.7056),
+ (-150.6082, 59.3682),
+ (-151.7163, 59.1558),
+ (-151.8594, 59.7449),
+ (-151.4097, 60.7258),
+ (-150.3469, 61.0335),
+ (-150.6211, 61.2844),
+ (-151.8958, 60.7271),
+ (-152.5783, 60.0616),
+ (-154.0191, 59.3502),
+ (-153.2875, 58.8647),
+ (-154.2324, 58.1463),
+ (-155.3074, 57.7277),
+ (-156.3083, 57.4227),
+ (-156.5560, 56.9799),
+ (-158.1172, 56.4636),
+ (-158.4333, 55.9941),
+ (-159.6033, 55.5666),
+ (-160.2897, 55.6435),
+ (-161.2230, 55.3647),
+ (-162.2377, 55.0241),
+ (-163.0694, 54.6897),
+ (-164.7855, 54.4041),
+ (-164.9422, 54.5722),
+ (-163.8483, 55.0394),
+ (-162.8700, 55.3480),
+ (-161.8041, 55.8949),
+ (-160.5636, 56.0080),
+ (-160.0705, 56.4180),
+ (-158.6844, 57.0166),
+ (-158.4610, 57.2169),
+ (-157.7227, 57.5700),
+ (-157.5502, 58.3283),
+ (-157.0416, 58.9188),
+ (-158.1947, 58.6158),
+ (-158.5172, 58.7877),
+ (-159.0586, 58.4241),
+ (-159.7116, 58.9313),
+ (-159.9812, 58.5725),
+ (-160.3552, 59.0711),
+ (-161.3550, 58.6708),
+ (-161.9688, 58.6716),
+ (-162.0549, 59.2669),
+ (-161.8741, 59.6336),
+ (-162.5180, 59.9897),
+ (-163.8183, 59.7980),
+ (-164.6622, 60.2674),
+ (-165.3463, 60.5074),
+ (-165.3508, 61.0738),
+ (-166.1213, 61.5000),
+ (-165.7344, 62.0749),
+ (-164.9191, 62.6330),
+ (-164.5625, 63.1463),
+ (-163.7533, 63.2194),
+ (-163.0672, 63.0594),
+ (-162.2605, 63.5419),
+ (-161.5344, 63.4558),
+ (-160.7725, 63.7661),
+ (-160.9583, 64.2227),
+ (-161.5180, 64.4027),
+ (-160.7777, 64.7886),
+ (-161.3919, 64.7772),
+ (-162.4530, 64.5594),
+ (-162.7577, 64.3386),
+ (-163.5463, 64.5591),
+ (-164.9608, 64.4469),
+ (-166.4252, 64.6866),
+ (-166.8450, 65.0888),
+ (-168.1105, 65.6699),
+ (-166.7052, 66.0883),
+ (-164.4747, 66.5766),
+ (-163.6525, 66.5766),
+ (-163.7886, 66.0772),
+ (-161.6777, 66.1161),
+ (-162.4897, 66.7355),
+ (-163.7197, 67.1163),
+ (-164.4309, 67.6163),
+ (-165.3902, 68.0427),
+ (-166.7644, 68.3588),
+ (-166.2047, 68.8830),
+ (-164.4308, 68.9155),
+ (-163.1686, 69.3711),
+ (-162.9305, 69.8580),
+ (-161.9088, 70.3333),
+ (-160.9347, 70.4476),
+ (-159.0391, 70.8916),
+ (-158.1197, 70.8247),
+ (-156.5808, 71.3577),
+ (-155.0677, 71.1477),
+ (-154.3441, 70.6964),
+ (-153.9000, 70.8899),
+ (-152.2100, 70.8299),
+ (-152.2700, 70.6000),
+ (-150.7399, 70.4300),
+ (-149.7200, 70.5300),
+ (-147.6133, 70.2140),
+ (-145.6899, 70.1200),
+ (-144.9200, 69.9899),
+ (-143.5894, 70.1525),
+ (-142.0725, 69.8519),
+ (-140.9859, 69.7119),
+ (-139.1205, 69.4710),
+ (-137.5463, 68.9900),
+ (-136.5035, 68.8980),
+ (-135.6257, 69.3151),
+ (-134.4146, 69.6274),
+ (-132.9292, 69.5053),
+ (-131.4313, 69.9445),
+ (-129.7947, 70.1937),
+ (-129.1077, 69.7792),
+ (-128.3615, 70.0128),
+ (-128.1381, 70.4838),
+ (-127.4471, 70.3772),
+ (-125.7563, 69.4805),
+ (-124.4248, 70.1584),
+ (-124.2896, 69.3996),
+ (-123.0610, 69.5637),
+ (-122.6834, 69.8555),
+ (-121.4722, 69.7977),
+ (-119.9428, 69.3778),
+ (-117.6026, 69.0112),
+ (-116.2264, 68.8414),
+ (-115.2468, 68.9059),
+ (-113.8979, 68.3989),
+ (-115.3048, 67.9026),
+ (-113.4972, 67.6881),
+ (-110.7980, 67.8060),
+ (-109.9461, 67.9810),
+ (-108.8801, 67.3814),
+ (-107.7923, 67.8873),
+ (-108.8129, 68.3116),
+ (-108.1672, 68.6539),
+ (-106.9500, 68.6999),
+ (-106.1500, 68.7999),
+ (-105.3428, 68.5612),
+ (-104.3379, 68.0180),
+ (-103.2211, 68.0977),
+ (-101.4543, 67.6468),
+ (-99.9019, 67.8056),
+ (-98.4432, 67.7816),
+ (-98.5586, 68.4039),
+ (-97.6694, 68.5786),
+ (-96.1199, 68.2394),
+ (-96.1258, 67.2933),
+ (-95.4894, 68.0907),
+ (-94.6849, 68.0638),
+ (-94.2328, 69.0690),
+ (-95.3040, 69.6857),
+ (-96.4713, 70.0897),
+ (-96.3911, 71.1948),
+ (-95.2088, 71.9205),
+ (-93.8899, 71.7601),
+ (-92.8781, 71.3186),
+ (-91.5196, 70.1912),
+ (-92.4069, 69.6999),
+ (-90.5471, 69.4976),
+ (-90.5514, 68.4749),
+ (-89.2151, 69.2587),
+ (-88.0196, 68.6150),
+ (-88.3174, 67.8733),
+ (-87.3501, 67.1987),
+ (-86.3060, 67.9214),
+ (-85.5766, 68.7845),
+ (-85.5219, 69.8820),
+ (-84.1008, 69.8054),
+ (-82.6225, 69.6582),
+ (-81.2804, 69.1620),
+ (-81.2202, 68.6656),
+ (-81.9643, 68.1325),
+ (-81.2592, 67.5971),
+ (-81.3865, 67.1107),
+ (-83.3445, 66.4115),
+ (-84.7354, 66.2572),
+ (-85.7694, 66.5583),
+ (-86.0676, 66.0562),
+ (-87.0314, 65.2129),
+ (-87.3232, 64.7756),
+ (-88.4829, 64.0989),
+ (-89.9144, 64.0327),
+ (-90.7039, 63.6101),
+ (-90.7700, 62.9602),
+ (-91.9334, 62.8350),
+ (-93.1569, 62.0246),
+ (-94.2415, 60.8986),
+ (-94.6293, 60.1102),
+ (-94.6846, 58.9488),
+ (-93.2150, 58.7821),
+ (-92.7646, 57.8457),
+ (-92.2970, 57.0870),
+ (-90.8976, 57.2846),
+ (-89.0395, 56.8517),
+ (-88.0397, 56.4716),
+ (-87.3241, 55.9991),
+ (-86.0712, 55.7238),
+ (-85.0118, 55.3026),
+ (-83.3605, 55.2448),
+ (-82.2728, 55.1483),
+ (-82.4362, 54.2822),
+ (-82.1250, 53.2770),
+ (-81.4007, 52.1578),
+ (-79.9128, 51.2083),
+ (-79.1430, 51.5339),
+ (-78.6019, 52.5620),
+ (-79.1242, 54.1414),
+ (-79.8295, 54.6677),
+ (-78.2287, 55.1364),
+ (-77.0955, 55.8374),
+ (-76.5413, 56.5342),
+ (-76.6231, 57.2026),
+ (-77.3022, 58.0520),
+ (-78.5168, 58.8045),
+ (-77.3367, 59.8526),
+ (-77.7727, 60.7578),
+ (-78.1068, 62.3196),
+ (-77.4106, 62.5505),
+ (-75.6962, 62.2783),
+ (-74.6682, 62.1811),
+ (-73.8398, 62.4437),
+ (-72.9085, 62.1050),
+ (-71.6770, 61.5253),
+ (-71.3736, 61.1371),
+ (-69.5904, 61.0614),
+ (-69.6203, 60.2212),
+ (-69.2878, 58.9573),
+ (-68.3745, 58.8010),
+ (-67.6497, 58.2120),
+ (-66.2017, 58.7673),
+ (-65.2451, 59.8707),
+ (-64.5835, 60.3355),
+ (-63.8047, 59.4425),
+ (-62.5023, 58.1670),
+ (-61.3965, 56.9674),
+ (-61.7986, 56.3394),
+ (-60.4685, 55.7754),
+ (-59.5696, 55.2040),
+ (-57.9750, 54.9454),
+ (-57.3332, 54.6264),
+ (-56.9368, 53.7803),
+ (-56.1581, 53.6474),
+ (-55.7563, 53.2703),
+ (-55.6833, 52.1466),
+ (-56.4091, 51.7706),
+ (-57.1269, 51.4197),
+ (-58.7748, 51.0642),
+ (-60.0331, 50.2427),
+ (-61.7236, 50.0804),
+ (-63.8625, 50.2909),
+ (-65.3633, 50.2981),
+ (-66.3990, 50.2289),
+ (-67.2363, 49.5115),
+ (-68.5111, 49.0683),
+ (-69.9536, 47.7448),
+ (-71.1045, 46.8217),
+ (-70.2552, 46.9860),
+ (-68.6499, 48.2999),
+ (-66.5524, 49.1330),
+ (-65.0562, 49.2327),
+ (-64.1709, 48.7425),
+ (-65.1154, 48.0708),
+ (-64.7985, 46.9929),
+ (-64.4721, 46.2384),
+ (-63.1732, 45.7390),
+ (-61.5207, 45.8837),
+ (-60.5181, 47.0079),
+ (-60.4486, 46.2826),
+ (-59.8028, 45.9204),
+ (-61.0398, 45.2652),
+ (-63.2547, 44.6701),
+ (-64.2465, 44.2655),
+ (-65.3640, 43.5452),
+ (-66.1234, 43.6186),
+ (-66.1617, 44.4651),
+ (-64.4254, 45.2920),
+ (-66.0260, 45.2593),
+ (-67.1374, 45.1375),
+ (-66.9646, 44.8097),
+ (-68.0325, 44.3252),
+ (-69.06, 43.98),
+ (-70.1161, 43.6840),
+ (-70.6454, 43.0902),
+ (-70.8148, 42.8652),
+ (-70.8249, 42.3349),
+ (-70.4949, 41.8049),
+ (-70.0800, 41.7800),
+ (-70.1849, 42.1449),
+ (-69.8849, 41.9228),
+ (-69.9650, 41.6371),
+ (-70.6399, 41.4749),
+ (-71.1203, 41.4944),
+ (-71.8538, 41.3199),
+ (-72.2949, 41.2699),
+ (-72.8764, 41.2206),
+ (-73.71, 40.9311),
+ (-72.2412, 41.1194),
+ (-71.9450, 40.9300),
+ (-73.3450, 40.6300),
+ (-73.9819, 40.6280),
+ (-73.9523, 40.7507),
+ (-74.2567, 40.4734),
+ (-73.9624, 40.4276),
+ (-74.1783, 39.7092),
+ (-74.9060, 38.9395),
+ (-74.9804, 39.1963),
+ (-75.2000, 39.2484),
+ (-75.5280, 39.4984),
+ (-75.3199, 38.9599),
+ (-75.0718, 38.7820),
+ (-75.0567, 38.4041),
+ (-75.3774, 38.0155),
+ (-75.9402, 37.2169),
+ (-76.0312, 37.2565),
+ (-75.7220, 37.9370),
+ (-76.2328, 38.3192),
+ (-76.35, 39.15),
+ (-76.5427, 38.7176),
+ (-76.3293, 38.0832),
+ (-76.9899, 38.2399),
+ (-76.3016, 37.9179),
+ (-76.2587, 36.9664),
+ (-75.9718, 36.8972),
+ (-75.8680, 36.5512),
+ (-75.7274, 35.5507),
+ (-76.3631, 34.8085),
+ (-77.3976, 34.5120),
+ (-78.0549, 33.9254),
+ (-78.5543, 33.8613),
+ (-79.0606, 33.4939),
+ (-79.2035, 33.1583),
+ (-80.3013, 32.5093),
+ (-80.8649, 32.0333),
+ (-81.3362, 31.4404),
+ (-81.4904, 30.7299),
+ (-81.3137, 30.0355),
+ (-80.9799, 29.1800),
+ (-80.5355, 28.4721),
+ (-80.5299, 28.0399),
+ (-80.0565, 26.88),
+ (-80.0880, 26.2057),
+ (-80.1315, 25.8167),
+ (-80.3810, 25.2061),
+ (-80.6800, 25.0799),
+ (-81.1721, 25.2012),
+ (-81.3299, 25.6399),
+ (-81.7099, 25.8699),
+ (-82.2400, 26.7299),
+ (-82.7051, 27.4950),
+ (-82.8552, 27.8862),
+ (-82.6500, 28.5499),
+ (-82.93, 29.1),
+ (-83.7095, 29.9365),
+ (-84.1, 30.09),
+ (-85.1088, 29.6361),
+ (-85.2878, 29.6861),
+ (-85.7731, 30.1526),
+ (-86.3999, 30.4000),
+ (-87.5303, 30.2743),
+ (-88.4178, 30.3849),
+ (-89.1804, 30.3159),
+ (-89.5938, 30.1599),
+ (-89.4137, 29.8941),
+ (-89.43, 29.48864),
+ (-89.2176, 29.2910),
+ (-89.4082, 29.1596),
+ (-89.7792, 29.3071),
+ (-90.1546, 29.1174),
+ (-90.8802, 29.1485),
+ (-91.6267, 29.677),
+ (-92.4990, 29.5523),
+ (-93.2263, 29.7837),
+ (-93.8484, 29.7136),
+ (-94.6900, 29.4800),
+ (-95.6002, 28.7386),
+ (-96.5940, 28.3074),
+ (-97.1400, 27.8300),
+ (-97.3699, 27.3800),
+ (-97.3799, 26.6899),
+ (-97.3299, 26.2100),
+ (-97.1400, 25.8699),
+ (-97.5280, 24.9921),
+ (-97.7029, 24.2723),
+ (-97.7760, 22.9325),
+ (-97.8723, 22.4442),
+ (-97.6990, 21.8986),
+ (-97.3889, 21.4110),
+ (-97.1893, 20.6354),
+ (-96.5255, 19.8909),
+ (-96.2921, 19.3203),
+ (-95.9008, 18.8280),
+ (-94.8390, 18.5627),
+ (-94.4257, 18.1443),
+ (-93.5486, 18.4238),
+ (-92.7861, 18.5248),
+ (-92.0373, 18.7045),
+ (-91.4079, 18.8760),
+ (-90.7718, 19.2841),
+ (-90.5335, 19.8674),
+ (-90.4514, 20.7075),
+ (-90.2786, 20.9998),
+ (-89.6013, 21.2617),
+ (-88.5438, 21.4936),
+ (-87.6584, 21.4588),
+ (-87.0518, 21.5435),
+ (-86.8119, 21.3315),
+ (-86.8459, 20.8498),
+ (-87.3832, 20.2554),
+ (-87.6210, 19.6465),
+ (-87.4367, 19.4724),
+ (-87.5865, 19.0401),
+ (-87.8371, 18.2598),
+ (-88.0906, 18.5166),
+ (-88.3000, 18.4999),
+ (-88.2963, 18.3532),
+ (-88.1068, 18.3486),
+ (-88.1234, 18.0766),
+ (-88.2853, 17.6441),
+ (-88.1978, 17.4894),
+ (-88.3026, 17.1316),
+ (-88.2395, 17.0360),
+ (-88.3554, 16.5307),
+ (-88.5518, 16.2654),
+ (-88.7324, 16.2336),
+ (-88.9306, 15.8872),
+ (-88.6045, 15.7063),
+ (-88.5183, 15.8553),
+ (-88.1899, 15.7199),
+ (-88.1211, 15.6886),
+ (-87.9018, 15.8644),
+ (-87.6156, 15.8787),
+ (-87.5229, 15.7972),
+ (-87.3677, 15.8469),
+ (-86.9031, 15.7567),
+ (-86.4409, 15.7828),
+ (-86.1192, 15.8934),
+ (-86.0019, 16.0054),
+ (-85.6833, 15.9536),
+ (-85.4440, 15.8857),
+ (-85.1824, 15.9091),
+ (-84.9837, 15.9959),
+ (-84.5269, 15.8572),
+ (-84.3682, 15.8351),
+ (-84.0630, 15.6482),
+ (-83.7739, 15.4240),
+ (-83.4103, 15.2709),
+ (-83.1472, 14.9958),
+ (-83.2332, 14.8998),
+ (-83.2841, 14.6766),
+ (-83.1821, 14.3107),
+ (-83.4124, 13.9700),
+ (-83.5198, 13.5676),
+ (-83.5522, 13.1270),
+ (-83.4985, 12.8692),
+ (-83.4733, 12.4190),
+ (-83.6261, 12.3208),
+ (-83.7196, 11.8931),
+ (-83.6508, 11.6290),
+ (-83.8554, 11.3733),
+ (-83.8089, 11.1030),
+ (-83.6556, 10.9387),
+ (-83.5900, 10.7850),
+ (-83.4023, 10.3954),
+ (-83.0156, 9.9929),
+ (-82.5461, 9.5661),
+ (-82.1871, 9.2074),
+ (-82.2075, 8.9955),
+ (-81.8085, 8.9506),
+ (-81.7141, 9.0319),
+ (-81.4392, 8.7862),
+ (-80.9473, 8.8585),
+ (-80.5219, 9.1110),
+ (-79.9145, 9.3127),
+ (-79.5733, 9.6116),
+ (-79.0211, 9.5529),
+ (-79.0584, 9.4545),
+ (-78.5008, 9.4204),
+ (-78.0559, 9.2477),
+ (-77.7295, 8.9468),
+ (-77.3533, 8.6705),
+ (-71.7123, 19.7144),
+ (-71.5873, 19.8849),
+ (-71.3800, 19.9049),
+ (-70.8067, 19.8802),
+ (-70.2143, 19.6228),
+ (-69.9508, 19.6479),
+ (-69.7692, 19.2932),
+ (-69.2221, 19.3132),
+ (-69.2543, 19.0151),
+ (-68.8094, 18.9791),
+ (-68.3179, 18.6121),
+ (-68.6893, 18.2051),
+ (-69.1649, 18.4226),
+ (-69.6239, 18.3807),
+ (-69.9529, 18.4283),
+ (-70.1332, 18.2459),
+ (-70.5171, 18.1842),
+ (-70.6692, 18.4268),
+ (-70.9999, 18.2833),
+ (-71.4002, 17.5985),
+ (-71.6576, 17.7575),
+ (-71.7083, 18.0449),
+ (-72.3724, 18.2149),
+ (-72.8444, 18.1456),
+ (-73.4545, 18.2179),
+ (-73.9224, 18.0309),
+ (-74.4580, 18.3425),
+ (-74.3699, 18.6649),
+ (-73.4495, 18.5260),
+ (-72.6949, 18.4457),
+ (-72.3348, 18.6684),
+ (-72.7916, 19.1016),
+ (-72.7841, 19.4835),
+ (-73.4150, 19.6395),
+ (-73.1897, 19.9156),
+ (-72.5796, 19.8715),
+ (-71.7123, 19.7144),
+ (14.7612, 38.1438),
+ (15.5203, 38.2311),
+ (15.1602, 37.4440),
+ (15.3098, 37.1342),
+ (15.0999, 36.6199),
+ (14.3352, 36.9966),
+ (13.8267, 37.1045),
+ (12.4310, 37.6129),
+ (12.5709, 38.1263),
+ (13.7411, 38.0349),
+ (14.7612, 38.1438),
+ (37.5391, 44.6572),
+ (38.6799, 44.2799),
+ (39.9550, 43.4349),
+ (132.3711, 33.4636),
+ (132.9243, 34.0602),
+ (133.4929, 33.9446),
+ (133.9041, 34.3649),
+ (134.6384, 34.1492),
+ (134.7663, 33.8063),
+ (134.2034, 33.2011),
+ (133.7929, 33.5219),
+ (133.2802, 33.2895),
+ (133.0148, 32.7045),
+ (132.3631, 32.9893),
+ (132.3711, 33.4636),
+ (180.0000, 68.9636),
+ (178.5999, 69.4000),
+ (175.7240, 69.8772),
+ (173.6439, 69.8174),
+ (170.4534, 70.0970),
+ (170.0082, 69.6527),
+ (170.8168, 69.0136),
+ (169.5776, 68.6938),
+ (167.8629, 69.5687),
+ (165.9403, 69.4719),
+ (164.0524, 69.6682),
+ (162.2790, 69.6420),
+ (160.9405, 69.4372),
+ (159.7086, 69.7219),
+ (159.8303, 70.4532),
+ (158.9977, 70.8667),
+ (157.0068, 71.0314),
+ (152.9688, 70.8422),
+ (150.3511, 71.6064),
+ (149.5000, 72.1999),
+ (140.4681, 72.8494),
+ (139.1479, 72.4161),
+ (139.8698, 71.4878),
+ (138.2340, 71.6280),
+ (137.4975, 71.3476),
+ (135.5619, 71.6552),
+ (133.8576, 71.3864),
+ (132.2535, 71.8363),
+ (131.2885, 70.7869),
+ (129.7159, 71.1930),
+ (128.46, 71.98),
+ (129.0515, 72.3987),
+ (128.5912, 73.0387),
+ (126.9764, 73.5654),
+ (125.38, 73.56),
+ (123.2577, 73.7350),
+ (123.2006, 72.9712),
+ (119.02, 73.12),
+ (118.7763, 73.5877),
+ (115.5678, 73.7528),
+ (113.9688, 73.5948),
+ (113.5295, 73.3350),
+ (113.0195, 73.9769),
+ (112.1191, 73.7877),
+ (110.64, 74.04),
+ (109.4, 74.18),
+ (110.1512, 74.4767),
+ (112.7791, 75.0318),
+ (113.8853, 75.3277),
+ (114.1341, 75.8476),
+ (113.3315, 76.2222),
+ (111.07726, 76.71),
+ (108.1537, 76.7233),
+ (107.2399, 76.4799),
+ (106.9701, 76.9741),
+ (104.7050, 77.1273),
+ (106.0666, 77.3738),
+ (104.3515, 77.6979),
+ (101.9908, 77.2875),
+ (101.0353, 76.8618),
+ (100.7596, 76.4302),
+ (98.9225, 76.4468),
+ (96.6782, 75.9154),
+ (95.8600, 76.1400),
+ (93.2342, 76.0471),
+ (92.9006, 75.7733),
+ (90.2599, 75.6399),
+ (88.3156, 75.1439),
+ (87.1668, 75.1164),
+ (86.0095, 74.4596),
+ (86.8223, 73.9368),
+ (84.6552, 73.8059),
+ (82.2499, 73.8500),
+ (80.5110, 73.6482),
+ (80.6107, 72.5828),
+ (81.5000, 71.7499),
+ (79.6520, 72.3201),
+ (77.5766, 72.2671),
+ (75.9031, 71.8740),
+ (76.3591, 71.1528),
+ (75.2889, 71.3355),
+ (75.6835, 72.3005),
+ (75.1580, 72.8549),
+ (74.6592, 72.8322),
+ (74.8908, 72.1211),
+ (73.1011, 71.4471),
+ (74.3998, 70.6317),
+ (73.6018, 69.6276),
+ (73.8423, 69.0714),
+ (74.9358, 68.9891),
+ (74.4692, 68.3289),
+ (75.052, 67.76047),
+ (74.1865, 67.2842),
+ (73.9209, 66.7894),
+ (72.8207, 66.5326),
+ (72.4230, 66.1726),
+ (71.28, 66.32),
+ (73.2387, 67.7404),
+ (73.6678, 68.4079),
+ (72.5646, 69.0208),
+ (72.7918, 70.3911),
+ (72.4701, 71.0901),
+ (71.8481, 71.4089),
+ (72.7960, 72.2200),
+ (72.5875, 72.7762),
+ (69.94, 73.04),
+ (69.1963, 72.8433),
+ (68.5400, 71.9345),
+ (66.6946, 71.0289),
+ (66.7249, 70.7088),
+ (67.2597, 69.9287),
+ (66.9300, 69.4546),
+ (68.1352, 69.3564),
+ (68.1644, 69.1443),
+ (69.1806, 68.6156),
+ (68.5121, 68.0923),
+ (64.8881, 69.2348),
+ (63.5040, 69.5473),
+ (60.5500, 69.8500),
+ (60.03, 69.52),
+ (61.0778, 68.9406),
+ (59.9414, 68.2784),
+ (58.802, 68.88082),
+ (57.3170, 68.4662),
+ (55.4426, 68.4386),
+ (54.7530, 68.0868),
+ (53.4858, 68.2013),
+ (54.4717, 68.8081),
+ (53.7174, 68.8573),
+ (48.1387, 67.5223),
+ (47.8941, 66.8845),
+ (46.3491, 66.6676),
+ (45.5620, 67.0100),
+ (45.5551, 67.5665),
+ (46.8213, 67.6899),
+ (46.2500, 68.2499),
+ (43.4528, 68.5708),
+ (44.1747, 67.9616),
+ (43.6983, 67.3524),
+ (44.5322, 66.7563),
+ (43.9497, 66.0690),
+ (43.0160, 66.4185),
+ (42.0930, 66.4762),
+ (39.7626, 65.4968),
+ (40.4356, 64.7644),
+ (39.5934, 64.5207),
+ (37.1760, 65.1432),
+ (36.5395, 64.7644),
+ (37.1419, 64.3347),
+ (37.0127, 63.8498),
+ (36.2312, 64.1094),
+ (34.9439, 64.4143),
+ (34.8785, 65.4362),
+ (34.8147, 65.9001),
+ (33.1844, 66.6325),
+ (33.9186, 66.7595),
+ (38.3829, 65.9995),
+ (40.0158, 66.2661),
+ (41.1259, 66.7915),
+ (41.0598, 67.4571),
+ (40.2923, 67.9323),
+ (36.5139, 69.0634),
+ (33.7754, 69.3014),
+ (32.1327, 69.9059),
+ (31.1010, 69.5580),
+ (30.0054, 70.1862),
+ (31.2934, 70.4537),
+ (28.1655, 71.1854),
+ (26.3700, 70.9862),
+ (24.5465, 71.0304),
+ (23.0237, 70.2020),
+ (21.3784, 70.2551),
+ (19.1840, 69.8174),
+ (16.4359, 68.5632),
+ (14.7611, 67.8106),
+ (12.3583, 65.8797),
+ (10.5277, 64.4860),
+ (8.5534, 63.4540),
+ (5.9129, 62.6144),
+ (4.9920, 61.9709),
+ (5.3082, 59.6632),
+ (5.6658, 58.5881),
+ (7.0487, 58.0788),
+ (8.3820, 58.3132),
+ (10.3565, 59.4698),
+ (11.0273, 58.8561),
+ (11.7879, 57.4418),
+ (12.6251, 56.3070),
+ (12.9429, 55.3617),
+ (14.1007, 55.4077),
+ (14.6666, 56.2008),
+ (15.8797, 56.1043),
+ (16.4477, 57.0411),
+ (16.8291, 58.7198),
+ (17.8692, 58.9537),
+ (18.7877, 60.0819),
+ (17.8313, 60.6365),
+ (17.1195, 61.3411),
+ (17.8477, 62.7494),
+ (19.7788, 63.6095),
+ (21.3696, 64.4135),
+ (21.2135, 65.0260),
+ (22.1831, 65.7237),
+ (23.9033, 66.0069),
+ (25.2940, 65.5343),
+ (25.3980, 65.1114),
+ (24.7305, 64.9023),
+ (22.4427, 63.8178),
+ (21.5360, 63.1897),
+ (21.0592, 62.6073),
+ (21.5448, 61.7053),
+ (21.3222, 60.7201),
+ (22.2907, 60.3919),
+ (22.8696, 59.8463),
+ (24.4966, 60.0573),
+ (26.2551, 60.4239),
+ (28.0699, 60.5035),
+ (29.1176, 60.0280),
+ (27.9811, 59.4753),
+ (26.9491, 59.4458),
+ (25.8641, 59.6110),
+ (24.6042, 59.4658),
+ (23.3397, 59.1872),
+ (23.4265, 58.6127),
+ (24.0611, 58.2573),
+ (24.4289, 58.3834),
+ (24.3128, 57.7934),
+ (24.1207, 57.0256),
+ (23.3184, 57.0062),
+ (22.5243, 57.7533),
+ (21.5818, 57.4118),
+ (21.0904, 56.7838),
+ (21.0558, 56.0310),
+ (21.2684, 55.1904),
+ (19.8884, 54.8661),
+ (19.6606, 54.4260),
+ (18.6962, 54.4387),
+ (18.6208, 54.6826),
+ (17.6228, 54.8515),
+ (16.3634, 54.5131),
+ (14.8029, 54.0507),
+ (14.1196, 53.7570),
+ (13.6474, 54.0755),
+ (12.5184, 54.4703),
+ (11.9562, 54.1964),
+ (10.9394, 54.0086),
+ (10.9501, 54.3636),
+ (9.9395, 54.5966),
+ (9.9219, 54.9831),
+ (9.6499, 55.4699),
+ (10.3699, 56.1900),
+ (10.6678, 56.0813),
+ (10.9121, 56.4586),
+ (10.3699, 56.6099),
+ (10.2500, 56.8900),
+ (10.5461, 57.2157),
+ (10.5800, 57.7300),
+ (9.7755, 57.4479),
+ (9.4244, 57.1720),
+ (8.5434, 57.1100),
+ (8.2565, 56.8099),
+ (8.0899, 56.5400),
+ (8.1203, 55.5177),
+ (8.5262, 54.9627),
+ (8.5721, 54.3956),
+ (8.8007, 54.0207),
+ (8.1217, 53.5277),
+ (7.9362, 53.7482),
+ (7.1004, 53.6939),
+ (6.9051, 53.4821),
+ (6.0741, 53.5104),
+ (4.7059, 53.0917),
+ (3.8302, 51.6205),
+ (3.3149, 51.3457),
+ (2.5135, 51.1485),
+ (1.6390, 50.9466),
+ (1.3387, 50.1271),
+ (-0.9894, 49.3473),
+ (-1.9334, 49.7763),
+ (-1.6165, 48.6444),
+ (-3.2958, 48.9016),
+ (-4.5923, 48.6841),
+ (-4.4915, 47.9549),
+ (-2.9632, 47.5703),
+ (-2.2257, 47.0643),
+ (-1.1937, 46.0149),
+ (-1.3842, 44.0226),
+ (-1.9013, 43.4228),
+ (-3.5175, 43.4559),
+ (-4.3478, 43.4034),
+ (-5.4118, 43.5742),
+ (-6.7544, 43.5679),
+ (-7.9781, 43.7483),
+ (-9.3928, 43.0266),
+ (-8.9844, 42.5927),
+ (-9.0348, 41.8805),
+ (-8.9907, 41.5434),
+ (-8.7908, 41.1843),
+ (-8.7686, 40.7606),
+ (-8.9773, 40.1593),
+ (-9.0483, 39.7550),
+ (-9.4469, 39.3920),
+ (-9.5265, 38.7374),
+ (-9.2874, 38.3584),
+ (-8.8399, 38.2662),
+ (-8.7461, 37.6513),
+ (-8.8988, 36.8688),
+ (-8.3828, 36.9788),
+ (-7.8556, 36.8382),
+ (-7.4537, 37.0977),
+ (-6.5201, 36.9429),
+ (-6.2366, 36.3676),
+ (-5.8664, 36.0298),
+ (-5.3771, 35.9468),
+ (-4.9952, 36.3247),
+ (-4.3689, 36.6778),
+ (-3.4157, 36.6588),
+ (-2.1464, 36.6741),
+ (-1.4383, 37.4430),
+ (-0.6833, 37.6423),
+ (-0.4671, 38.2923),
+ (0.1112, 38.7385),
+ (-0.2787, 39.3099),
+ (0.1066, 40.1239),
+ (0.7213, 40.6783),
+ (0.8105, 41.0147),
+ (2.0918, 41.2260),
+ (3.0394, 41.8921),
+ (2.9859, 42.4730),
+ (3.1004, 43.0752),
+ (4.5569, 43.3996),
+ (6.5292, 43.1288),
+ (7.4351, 43.6938),
+ (7.8507, 43.7671),
+ (8.4285, 44.2312),
+ (8.8889, 44.3663),
+ (9.7024, 44.0362),
+ (10.2000, 43.9200),
+ (10.5119, 42.9314),
+ (11.1919, 42.3554),
+ (12.1066, 41.7045),
+ (12.8880, 41.2530),
+ (13.6279, 41.1882),
+ (14.0606, 40.7863),
+ (14.7032, 40.6045),
+ (14.9984, 40.1729),
+ (15.4136, 40.0483),
+ (15.7188, 39.5440),
+ (16.1093, 38.9645),
+ (15.8919, 38.7509),
+ (15.6879, 38.2145),
+ (15.6840, 37.9088),
+ (16.1009, 37.9858),
+ (16.6350, 38.8435),
+ (17.0528, 38.9028),
+ (17.1714, 39.4246),
+ (16.4487, 39.7954),
+ (16.8695, 40.4422),
+ (17.7383, 40.2776),
+ (18.2933, 39.8107),
+ (18.4802, 40.1688),
+ (18.3766, 40.3556),
+ (17.5191, 40.8771),
+ (16.7850, 41.1796),
+ (15.8893, 41.5410),
+ (16.1698, 41.7402),
+ (15.9261, 41.9613),
+ (15.1425, 41.9551),
+ (14.0298, 42.7610),
+ (13.5269, 43.5877),
+ (12.5892, 44.0913),
+ (12.2614, 44.6004),
+ (12.3838, 44.8853),
+ (12.3285, 45.3817),
+ (13.1416, 45.7366),
+ (13.9376, 45.5910),
+ (13.7150, 45.5003),
+ (13.6794, 45.4841),
+ (13.6569, 45.1369),
+ (13.9522, 44.8021),
+ (14.2587, 45.2337),
+ (14.9016, 45.0760),
+ (14.9203, 44.7384),
+ (15.3762, 44.3179),
+ (15.1744, 44.2431),
+ (16.0153, 43.5072),
+ (16.9300, 43.2099),
+ (17.5099, 42.8499),
+ (18.4500, 42.4799),
+ (18.8821, 42.2815),
+ (19.1624, 41.9550),
+ (19.3717, 41.8775),
+ (19.5400, 41.7199),
+ (19.4035, 41.4095),
+ (19.3190, 40.7272),
+ (19.4060, 40.2507),
+ (19.9600, 39.9150),
+ (19.9800, 39.6949),
+ (20.1500, 39.6249),
+ (20.2177, 39.3402),
+ (20.7300, 38.7699),
+ (21.1200, 38.3103),
+ (21.2950, 37.6449),
+ (21.6700, 36.8449),
+ (22.4900, 36.4100),
+ (23.1542, 36.4225),
+ (22.7749, 37.3050),
+ (23.4099, 37.4099),
+ (23.1150, 37.9200),
+ (24.0400, 37.6550),
+ (24.0250, 38.2199),
+ (23.5300, 38.5100),
+ (22.9730, 38.9709),
+ (23.3500, 39.1900),
+ (22.8497, 39.6593),
+ (22.6262, 40.2565),
+ (22.8139, 40.4760),
+ (23.3429, 39.9609),
+ (23.8999, 39.9620),
+ (24.4079, 40.1249),
+ (23.7148, 40.6871),
+ (24.9258, 40.9470),
+ (25.4476, 40.8525),
+ (26.0569, 40.8241),
+ (26.0433, 40.6177),
+ (26.3580, 40.1519),
+ (27.1923, 40.6905),
+ (27.6190, 40.9998),
+ (28.8064, 41.0549),
+ (28.9884, 41.2999),
+ (28.1155, 41.6228),
+ (27.9967, 42.0073),
+ (27.6738, 42.5778),
+ (28.0390, 43.2931),
+ (28.5580, 43.7074),
+ (28.8378, 44.9138),
+ (29.1416, 44.8202),
+ (29.6265, 45.0353),
+ (29.6032, 45.2933),
+ (30.3776, 46.0324),
+ (30.7487, 46.5831),
+ (31.6753, 46.7062),
+ (31.7441, 46.3333),
+ (33.2985, 46.0805),
+ (33.5881, 45.8515),
+ (32.6308, 45.5191),
+ (32.4541, 45.3274),
+ (33.5469, 45.0347),
+ (33.3264, 44.5648),
+ (33.8825, 44.3614),
+ (35.2399, 44.9399),
+ (36.3347, 45.1132),
+ (36.5299, 45.4699),
+ (35.5100, 45.4099),
+ (35.0207, 45.6512),
+ (34.9623, 46.2731),
+ (35.8236, 46.6459),
+ (36.7598, 46.6987),
+ (37.4251, 47.0222),
+ (38.2235, 47.1021),
+ (39.1212, 47.2633),
+ (39.1476, 47.0447),
+ (37.6737, 46.6365),
+ (38.2329, 46.2408),
+ (37.4031, 45.4045),
+ (36.6754, 45.2446),
+ (37.5391, 44.6572),
+ (38.6799, 44.2799),
+ (39.9550, 43.4349),
+ (40.3213, 43.1286),
+ (40.8754, 43.0136),
+ (41.4534, 42.6451),
+ (41.7031, 41.9629),
+ (41.5540, 41.5356),
+ (40.3734, 41.0136),
+ (39.5126, 41.1027),
+ (38.3476, 40.9485),
+ (36.9131, 41.3353),
+ (35.1677, 42.0402),
+ (33.5132, 42.0189),
+ (32.3479, 41.7362),
+ (31.1459, 41.0876),
+ (29.2400, 41.2199),
+ (28.8199, 40.4600),
+ (27.2800, 40.4200),
+ (26.1707, 39.4636),
+ (26.8047, 38.9857),
+ (26.3182, 38.2081),
+ (27.0487, 37.6533),
+ (27.6411, 36.6588),
+ (28.7329, 36.6768),
+ (29.6999, 36.1443),
+ (30.3910, 36.2629),
+ (30.6216, 36.6778),
+ (31.6995, 36.6442),
+ (32.5091, 36.1075),
+ (34.0268, 36.2199),
+ (34.7145, 36.7955),
+ (35.5509, 36.5654),
+ (36.1608, 36.6506),
+ (35.7820, 36.2749),
+ (36.1497, 35.8215),
+ (35.9050, 35.4100),
+ (35.9984, 34.6449),
+ (35.9795, 34.6100),
+ (35.4822, 33.9054),
+ (35.1260, 33.0909),
+ (35.0984, 33.0805),
+ (34.9554, 32.8273),
+ (34.7525, 32.0729),
+ (34.4881, 31.6055),
+ (34.5563, 31.5488),
+ (34.2654, 31.2193),
+ (33.7733, 30.9674),
+ (32.9939, 31.0240),
+ (32.1924, 31.2603),
+ (31.9604, 30.9336),
+ (31.6879, 31.4296),
+ (30.9769, 31.5558),
+ (30.0950, 31.4734),
+ (29.6834, 31.1868),
+ (28.9135, 30.8700),
+ (28.4504, 31.0257),
+ (27.4576, 31.3212),
+ (26.4953, 31.5856),
+ (25.1648, 31.5691),
+ (24.9211, 31.8993),
+ (23.9275, 32.0166),
+ (23.6091, 32.1872),
+ (23.2368, 32.1914),
+ (22.8957, 32.6385),
+ (21.5430, 32.8432),
+ (20.8545, 32.7067),
+ (20.1339, 32.2381),
+ (19.8203, 31.7517),
+ (20.0533, 30.9857),
+ (19.5740, 30.5258),
+ (19.0864, 30.2663),
+ (18.0211, 30.7635),
+ (16.6116, 31.1821),
+ (15.7139, 31.3762),
+ (15.2456, 32.2650),
+ (13.9186, 32.7119),
+ (13.0832, 32.8788),
+ (12.6633, 32.7927),
+ (11.4887, 33.1369),
+ (11.1085, 33.2933),
+ (10.8568, 33.7687),
+ (10.3396, 33.7857),
+ (10.1495, 34.3307),
+ (10.8078, 34.8335),
+ (10.9395, 35.6989),
+ (10.5932, 35.9474),
+ (10.6000, 36.4100),
+ (11.1000, 36.8999),
+ (11.0288, 37.0921),
+ (10.1806, 36.7240),
+ (10.2100, 37.2300),
+ (9.5099, 37.3499),
+ (8.4209, 36.9464),
+ (7.7370, 36.8857),
+ (7.3303, 37.1183),
+ (6.2618, 37.1106),
+ (5.3201, 36.7165),
+ (4.8157, 36.8650),
+ (3.1616, 36.7839),
+ (1.4669, 36.6056),
+ (0.5038, 36.3012),
+ (-0.1274, 35.8886),
+ (-1.2086, 35.7148),
+ (-2.1699, 35.1683),
+ (-2.6043, 35.1790),
+ (-3.6400, 35.3998),
+ (-4.5910, 35.3307),
+ (-5.1938, 35.7551),
+ (-5.9299, 35.7599),
+ (-6.2443, 35.1458),
+ (-6.9125, 34.1104),
+ (-7.6541, 33.6970),
+ (-8.6574, 33.2402),
+ (-9.3006, 32.5646),
+ (-9.4347, 32.0380),
+ (-9.8147, 31.1777),
+ (-9.5648, 29.9335),
+ (-10.3995, 29.0985),
+ (-10.9009, 28.8321),
+ (-11.6889, 28.1486),
+ (-12.6188, 28.0381),
+ (-13.1399, 27.6401),
+ (-13.7738, 26.6188),
+ (-14.4399, 26.2544),
+ (-14.8009, 25.6362),
+ (-14.8246, 25.1035),
+ (-15.0893, 24.5202),
+ (-15.4260, 24.3591),
+ (-15.9826, 23.7233),
+ (-16.3264, 23.0177),
+ (-16.2619, 22.6793),
+ (-16.5891, 22.1582),
+ (-16.9732, 21.8857),
+ (-17.0204, 21.4223),
+ (-17.0634, 20.9997),
+ (-16.5363, 20.5678),
+ (-16.2778, 20.0925),
+ (-16.3776, 19.5938),
+ (-16.2568, 19.0967),
+ (-16.1463, 18.1084),
+ (-16.2705, 17.1669),
+ (-16.5497, 16.6738),
+ (-16.4630, 16.1350),
+ (-16.7007, 15.6215),
+ (-17.1851, 14.9194),
+ (-17.6250, 14.7295),
+ (-17.1261, 14.3735),
+ (-16.7137, 13.5949),
+ (-16.8415, 13.1513),
+ (-16.6774, 12.3848),
+ (-16.6138, 12.1709),
+ (-16.3089, 11.9587),
+ (-16.3147, 11.8065),
+ (-16.0852, 11.5245),
+ (-15.6641, 11.4584),
+ (-15.1303, 11.0404),
+ (-14.8395, 10.8765),
+ (-14.6932, 10.6563),
+ (-14.5796, 10.2144),
+ (-14.3300, 10.0157),
+ (-14.0740, 9.8861),
+ (-13.6851, 9.4947),
+ (-13.2465, 8.9030),
+ (-13.1240, 8.1639),
+ (-12.9490, 7.7986),
+ (-12.4280, 7.2629),
+ (-11.7081, 6.8600),
+ (-11.4387, 6.7859),
+ (-10.7653, 6.1407),
+ (-9.9134, 5.5935),
+ (-9.0047, 4.8324),
+ (-7.9741, 4.3557),
+ (-7.7121, 4.3645),
+ (-7.5189, 4.3382),
+ (-6.5287, 4.7050),
+ (-5.8344, 4.9937),
+ (-4.6499, 5.1682),
+ (-4.0088, 5.1798),
+ (-3.3110, 4.9842),
+ (-2.8561, 4.9944),
+ (-1.9647, 4.7104),
+ (-1.0636, 5.0005),
+ (-0.5076, 5.3434),
+ (1.0601, 5.9288),
+ (1.8652, 6.1421),
+ (2.6917, 6.2588),
+ (3.5741, 6.2583),
+ (4.3256, 6.2706),
+ (5.0335, 5.6118),
+ (5.3628, 4.8879),
+ (5.8981, 4.2624),
+ (6.6980, 4.2405),
+ (7.0825, 4.4646),
+ (7.4621, 4.4121),
+ (8.5002, 4.7719),
+ (8.4888, 4.4956),
+ (8.7449, 4.3522),
+ (8.9481, 3.9041),
+ (9.4043, 3.7345),
+ (9.7951, 3.0734),
+ (9.6491, 2.2838),
+ (9.3056, 1.1609),
+ (9.4928, 1.0101),
+ (9.2913, 0.2686),
+ (9.0484, -0.4593),
+ (8.8300, -0.7790),
+ (8.7979, -1.1113),
+ (9.4052, -2.1443),
+ (10.0661, -2.9694),
+ (11.0937, -3.9788),
+ (11.9149, -5.0379),
+ (12.1823, -5.7899),
+ (12.3224, -6.1000),
+ (12.2273, -6.2944),
+ (12.7282, -6.9271),
+ (12.9330, -7.5965),
+ (13.2364, -8.5626),
+ (12.9290, -8.9590),
+ (12.8753, -9.1669),
+ (13.1209, -9.7668),
+ (13.3873, -10.3735),
+ (13.6863, -10.7310),
+ (13.7387, -11.2978),
+ (13.6337, -12.0386),
+ (13.3129, -12.4836),
+ (12.7384, -13.1379),
+ (12.5000, -13.5476),
+ (12.1756, -14.4491),
+ (12.1235, -14.8783),
+ (11.7785, -15.7938),
+ (11.6400, -16.6731),
+ (11.7341, -17.3018),
+ (11.7949, -18.0691),
+ (12.6085, -19.0453),
+ (12.8268, -19.6731),
+ (13.3524, -20.8728),
+ (13.8686, -21.6990),
+ (14.2577, -22.1112),
+ (14.3857, -22.6566),
+ (14.4081, -23.8530),
+ (14.7432, -25.3929),
+ (14.9897, -26.1173),
+ (15.2104, -27.0909),
+ (15.6018, -27.8212),
+ (16.3449, -28.5767),
+ (17.0629, -29.8759),
+ (17.0644, -29.8786),
+ (17.5669, -30.7257),
+ (18.2217, -31.6616),
+ (18.2479, -32.4291),
+ (17.9251, -32.6112),
+ (18.2500, -33.2814),
+ (18.2444, -33.8677),
+ (18.3774, -34.1365),
+ (18.4246, -33.9978),
+ (18.8553, -34.4443),
+ (19.1932, -34.4625),
+ (19.6164, -34.8191),
+ (20.0712, -34.7951),
+ (20.6890, -34.4171),
+ (21.5427, -34.2588),
+ (22.5741, -33.8640),
+ (22.9881, -33.9164),
+ (23.5940, -33.7944),
+ (24.6778, -33.9871),
+ (25.1728, -33.7968),
+ (25.7806, -33.9446),
+ (25.9096, -33.6670),
+ (26.4194, -33.6149),
+ (27.4646, -33.2269),
+ (28.2197, -32.7719),
+ (28.9255, -32.1720),
+ (30.0557, -31.1402),
+ (30.6228, -30.4237),
+ (30.9017, -29.9099),
+ (31.3255, -29.4019),
+ (31.5210, -29.2573),
+ (32.2033, -28.7524),
+ (32.4621, -28.3010),
+ (32.5802, -27.4701),
+ (32.8301, -26.7421),
+ (32.9159, -26.2158),
+ (32.6603, -26.1485),
+ (32.5746, -25.7273),
+ (33.0132, -25.3575),
+ (34.2158, -24.8163),
+ (35.0407, -24.4783),
+ (35.4587, -24.1226),
+ (35.6074, -23.7065),
+ (35.3717, -23.5353),
+ (35.5339, -23.0707),
+ (35.5625, -22.09),
+ (35.3858, -22.14),
+ (35.3734, -21.8408),
+ (35.1761, -21.2543),
+ (34.7018, -20.4970),
+ (34.7863, -19.7840),
+ (35.1983, -19.5528),
+ (35.8964, -18.8422),
+ (36.2812, -18.6596),
+ (37.4111, -17.5863),
+ (38.5383, -17.1010),
+ (39.4525, -16.7208),
+ (40.0892, -16.1007),
+ (40.4772, -15.4062),
+ (40.7754, -14.6917),
+ (40.5996, -14.2019),
+ (40.5608, -12.6391),
+ (40.4372, -11.7617),
+ (40.4783, -10.7654),
+ (40.3165, -10.3170),
+ (39.9495, -10.0984),
+ (39.5357, -9.1123),
+ (39.1865, -8.4855),
+ (39.2520, -8.0078),
+ (39.1946, -7.7039),
+ (39.4699, -7.0999),
+ (39.4400, -6.8399),
+ (38.7997, -6.4756),
+ (38.7405, -5.9089),
+ (39.2022, -4.6767),
+ (39.6049, -4.3465),
+ (39.8000, -3.6811),
+ (40.1211, -3.2776),
+ (40.2630, -2.5730),
+ (40.6378, -2.4997),
+ (40.8847, -2.0825),
+ (41.5851, -1.6832),
+ (41.8109, -1.4464),
+ (42.0415, -0.9191),
+ (43.1359, 0.2921),
+ (44.0681, 1.0528),
+ (45.5639, 2.0457),
+ (46.5647, 2.8552),
+ (47.7407, 4.2194),
+ (48.5945, 5.3391),
+ (49.4527, 6.8046),
+ (50.0709, 8.0817),
+ (50.5523, 9.1987),
+ (50.8341, 10.2797),
+ (51.0452, 10.6409),
+ (51.0415, 11.1665),
+ (51.1338, 11.7481),
+ (51.1112, 12.0246),
+ (50.7320, 12.0219),
+ (50.2587, 11.6795),
+ (49.7286, 11.5789),
+ (49.2677, 11.4303),
+ (48.9482, 11.4106),
+ (48.3787, 11.3754),
+ (48.0215, 11.1930),
+ (47.5256, 11.1272),
+ (46.6454, 10.8165),
+ (45.5569, 10.6980),
+ (44.6142, 10.4422),
+ (44.1178, 10.4455),
+ (43.6666, 10.8641),
+ (43.4706, 11.2777),
+ (43.1453, 11.4620),
+ (42.7158, 11.7356),
+ (43.2863, 11.9749),
+ (43.3178, 12.3901),
+ (43.0812, 12.6996),
+ (42.5895, 13.0004),
+ (42.2768, 13.3439),
+ (41.7349, 13.9210),
+ (41.1792, 14.4910),
+ (39.8142, 15.4356),
+ (39.2661, 15.9227),
+ (38.9906, 16.8406),
+ (38.4100, 17.9983),
+ (37.8627, 18.3678),
+ (37.4817, 18.6140),
+ (37.1147, 19.8079),
+ (36.9694, 20.8374),
+ (37.1887, 21.0188),
+ (36.86623, 22.0),
+ (36.6907, 22.2048),
+ (35.5259, 23.1024),
+ (35.4937, 23.7523),
+ (35.6924, 23.9267),
+ (34.7950, 25.0337),
+ (34.4738, 25.5985),
+ (34.1045, 26.1422),
+ (33.3487, 27.6998),
+ (32.7348, 28.7052),
+ (32.3204, 29.7604),
+ (32.4232, 29.8510),
+ (33.1367, 28.4176),
+ (33.5881, 27.9713),
+ (33.9213, 27.6487),
+ (34.1545, 27.8233),
+ (34.4265, 28.3439),
+ (34.6417, 29.0994),
+ (34.9226, 29.5013),
+ (34.9560, 29.3565),
+ (34.8322, 28.9574),
+ (34.7877, 28.6074),
+ (34.6323, 28.0585),
+ (35.1301, 28.0633),
+ (35.6401, 27.3765),
+ (36.2491, 26.5701),
+ (36.6396, 25.8262),
+ (36.9316, 25.6029),
+ (37.2094, 25.0845),
+ (37.1548, 24.8584),
+ (37.4836, 24.2854),
+ (38.0238, 24.0786),
+ (38.4927, 23.6884),
+ (39.0663, 22.5796),
+ (39.0236, 21.9868),
+ (39.1393, 21.2919),
+ (39.8016, 20.3388),
+ (40.2476, 20.1746),
+ (40.9393, 19.4864),
+ (41.2213, 18.6715),
+ (41.7543, 17.8330),
+ (42.2708, 17.4747),
+ (42.3479, 17.0758),
+ (42.6495, 16.7746),
+ (42.7793, 16.3478),
+ (42.8236, 15.9117),
+ (42.7024, 15.7188),
+ (42.8050, 15.2619),
+ (42.6048, 15.2133),
+ (42.8922, 14.8022),
+ (43.0879, 14.0626),
+ (43.2514, 13.7675),
+ (43.2228, 13.2209),
+ (43.4829, 12.6368),
+ (44.1751, 12.5859),
+ (44.4945, 12.7216),
+ (44.9895, 12.6995),
+ (45.1443, 12.9539),
+ (45.4064, 13.0269),
+ (45.6250, 13.2909),
+ (45.8775, 13.3477),
+ (46.7170, 13.3996),
+ (47.3544, 13.5922),
+ (47.9389, 14.0072),
+ (48.2389, 13.9480),
+ (48.6792, 14.0032),
+ (49.5745, 14.7087),
+ (51.1725, 15.1752),
+ (52.1681, 15.5974),
+ (52.1917, 15.9384),
+ (52.3852, 16.3824),
+ (53.1085, 16.6510),
+ (53.5705, 16.7076),
+ (54.2392, 17.0449),
+ (54.7910, 16.9506),
+ (55.2749, 17.2283),
+ (55.2699, 17.6323),
+ (55.6614, 17.8841),
+ (56.2835, 17.8760),
+ (56.5121, 18.0871),
+ (56.6096, 18.5742),
+ (57.2342, 18.9479),
+ (57.6943, 18.9447),
+ (57.7887, 19.0675),
+ (57.6657, 19.7360),
+ (57.8263, 20.2430),
+ (58.0343, 20.4814),
+ (58.4879, 20.4289),
+ (58.8611, 21.1140),
+ (59.2824, 21.4338),
+ (59.4421, 21.7145),
+ (59.8061, 22.3105),
+ (59.8080, 22.5336),
+ (59.4500, 22.6602),
+ (59.1805, 22.9923),
+ (58.7292, 23.5656),
+ (58.1369, 23.7479),
+ (57.4034, 23.8785),
+ (56.8451, 24.2416),
+ (56.3968, 24.9247),
+ (56.2610, 25.7146),
+ (56.3914, 25.8959),
+ (56.4856, 26.3091),
+ (56.3620, 26.3959),
+ (56.0708, 26.0554),
+ (55.4390, 25.4391),
+ (54.6930, 24.7978),
+ (54.0080, 24.1217),
+ (53.4040, 24.1513),
+ (52.5770, 24.1774),
+ (51.7943, 24.0198),
+ (51.7574, 24.2940),
+ (51.5795, 24.2454),
+ (51.3896, 24.6273),
+ (51.6067, 25.2156),
+ (51.5890, 25.8011),
+ (51.2864, 26.1145),
+ (51.0133, 26.0069),
+ (50.7439, 25.4824),
+ (50.8101, 24.7547),
+ (50.6605, 24.9998),
+ (50.5273, 25.3278),
+ (50.2398, 25.6080),
+ (50.1133, 25.9439),
+ (50.2129, 26.2770),
+ (50.1524, 26.6896),
+ (49.4709, 27.1099),
+ (49.2995, 27.4612),
+ (48.8075, 27.6896),
+ (48.4160, 28.5520),
+ (48.0939, 29.3062),
+ (48.1831, 29.5344),
+ (47.9745, 29.9758),
+ (48.5679, 29.9267),
+ (48.9413, 30.3170),
+ (49.5768, 29.9857),
+ (50.1150, 30.1477),
+ (50.8529, 28.8145),
+ (51.5207, 27.8656),
+ (52.4835, 27.5808),
+ (53.4930, 26.8123),
+ (54.7150, 26.4806),
+ (55.7237, 26.9646),
+ (56.4921, 27.1433),
+ (56.9707, 26.9661),
+ (57.3972, 25.7399),
+ (58.5257, 25.6099),
+ (59.6161, 25.3801),
+ (61.4973, 25.0782),
+ (62.9057, 25.2184),
+ (64.5304, 25.2370),
+ (66.3728, 25.4251),
+ (67.1454, 24.6636),
+ (67.4436, 23.9448),
+ (68.1766, 23.6919),
+ (69.3495, 22.8431),
+ (69.6449, 22.4507),
+ (69.1641, 22.0892),
+ (70.4704, 20.8773),
+ (71.1752, 20.7574),
+ (72.6305, 21.3560),
+ (72.8244, 20.4195),
+ (72.8209, 19.2082),
+ (73.1199, 17.9285),
+ (73.5341, 15.9906),
+ (74.4438, 14.6172),
+ (74.6167, 13.9925),
+ (74.8648, 12.7419),
+ (75.3961, 11.7812),
+ (75.7464, 11.3082),
+ (76.1300, 10.2996),
+ (76.5929, 8.8992),
+ (77.5398, 7.9655),
+ (77.9411, 8.2529),
+ (78.2779, 8.9330),
+ (79.1897, 9.2165),
+ (78.8853, 9.5461),
+ (79.3405, 10.3088),
+ (79.8579, 10.3572),
+ (79.8625, 12.0562),
+ (80.2862, 13.0062),
+ (80.2332, 13.8357),
+ (80.0250, 15.1364),
+ (80.3248, 15.8991),
+ (80.7919, 15.9519),
+ (81.6927, 16.3102),
+ (82.1912, 16.5566),
+ (82.1927, 17.0166),
+ (83.1892, 17.6712),
+ (83.9410, 18.3020),
+ (85.0602, 19.4785),
+ (86.4993, 20.1516),
+ (87.0331, 20.7433),
+ (86.9757, 21.4955),
+ (88.2084, 21.7031),
+ (88.8887, 21.6905),
+ (89.0319, 22.0557),
+ (89.4188, 21.9661),
+ (89.7020, 21.8571),
+ (89.8474, 22.0391),
+ (90.2729, 21.8363),
+ (90.5869, 22.3927),
+ (90.4960, 22.8050),
+ (91.4170, 22.7650),
+ (91.8348, 22.1829),
+ (92.0252, 21.7015),
+ (92.0828, 21.1921),
+ (92.3685, 20.6708),
+ (93.0782, 19.8551),
+ (93.6632, 19.7269),
+ (93.5409, 19.3664),
+ (94.3248, 18.2135),
+ (94.5334, 17.2772),
+ (94.1888, 16.0379),
+ (94.8084, 15.8034),
+ (95.3693, 15.7143),
+ (96.5057, 16.4272),
+ (97.1645, 16.9287),
+ (97.5970, 16.1005),
+ (97.7777, 14.8372),
+ (98.1036, 13.6404),
+ (98.5095, 13.1223),
+ (98.4283, 12.0329),
+ (98.7645, 11.4412),
+ (98.4571, 10.6752),
+ (98.5535, 9.9329),
+ (98.2591, 8.9739),
+ (98.1500, 8.3500),
+ (98.3396, 7.7945),
+ (98.5037, 8.3823),
+ (98.9882, 7.9079),
+ (99.5196, 7.3434),
+ (99.6906, 6.8482),
+ (100.0857, 6.4644),
+ (100.3062, 6.0405),
+ (100.1967, 5.3124),
+ (100.5574, 4.7672),
+ (100.6954, 3.9391),
+ (101.2735, 3.2702),
+ (101.3906, 2.7608),
+ (102.5736, 1.9671),
+ (103.5197, 1.2263),
+ (104.2288, 1.2930),
+ (104.2479, 1.6311),
+ (103.8546, 2.5154),
+ (103.5024, 2.7910),
+ (103.4294, 3.3828),
+ (103.3321, 3.7266),
+ (103.4385, 4.1816),
+ (103.3812, 4.8550),
+ (102.9617, 5.5244),
+ (102.3711, 6.1282),
+ (102.1411, 6.2216),
+ (101.6230, 6.7406),
+ (101.0173, 6.8568),
+ (100.4592, 7.4295),
+ (100.2796, 8.2951),
+ (99.8738, 9.2078),
+ (99.2223, 9.2392),
+ (99.1537, 9.9630),
+ (99.4789, 10.8463),
+ (100.0187, 12.3070),
+ (100.0977, 13.4068),
+ (100.9784, 13.4127),
+ (100.8318, 12.6270),
+ (101.6871, 12.6457),
+ (102.5849, 12.1865),
+ (103.0906, 11.1536),
+ (103.4972, 10.6325),
+ (104.3343, 10.4865),
+ (105.0762, 9.9184),
+ (104.7951, 9.2410),
+ (105.1582, 8.5997),
+ (106.4051, 9.5308),
+ (107.2209, 10.3644),
+ (108.3661, 11.0083),
+ (109.2001, 11.6668),
+ (109.3352, 13.4260),
+ (108.8771, 15.2766),
+ (108.2694, 16.0797),
+ (107.3619, 16.6974),
+ (106.4268, 18.0041),
+ (105.6620, 19.0581),
+ (105.8816, 19.7520),
+ (106.7150, 20.6968),
+ (108.0501, 21.5523),
+ (108.5228, 21.7152),
+ (109.8644, 21.3950),
+ (109.6276, 21.0082),
+ (109.8898, 20.2824),
+ (110.4440, 20.3410),
+ (110.5093, 20.5654),
+ (110.7854, 21.3971),
+ (111.8435, 21.5504),
+ (113.2410, 22.0513),
+ (113.8067, 22.5483),
+ (114.1525, 22.2237),
+ (114.7638, 22.6680),
+ (115.8907, 22.7828),
+ (117.2816, 23.6245),
+ (118.6568, 24.5473),
+ (119.5854, 25.7407),
+ (120.3954, 27.0532),
+ (121.1256, 28.1356),
+ (121.6844, 28.2255),
+ (121.9384, 29.0180),
+ (122.0921, 29.8325),
+ (121.5035, 30.1429),
+ (121.2642, 30.6762),
+ (121.8919, 30.9493),
+ (121.9081, 31.6921),
+ (121.2290, 32.4603),
+ (120.6203, 33.3767),
+ (120.2275, 34.3603),
+ (119.1512, 34.9098),
+ (119.6645, 35.6097),
+ (120.6370, 36.1114),
+ (121.1041, 36.6513),
+ (122.5199, 36.9306),
+ (122.3579, 37.4544),
+ (121.7112, 37.4811),
+ (120.8234, 37.8704),
+ (119.7028, 37.1563),
+ (118.9116, 37.4484),
+ (118.8781, 37.8973),
+ (118.0596, 38.0614),
+ (117.5327, 38.7376),
+ (118.0427, 39.2042),
+ (119.0234, 39.2523),
+ (119.6396, 39.8980),
+ (120.7686, 40.5933),
+ (121.6403, 40.9463),
+ (122.1685, 40.4224),
+ (121.3767, 39.7502),
+ (121.5859, 39.3608),
+ (121.0545, 38.8974),
+ (122.1313, 39.1704),
+ (122.8675, 39.6377),
+ (124.2656, 39.9284),
+ (124.7374, 39.6603),
+ (125.3211, 39.5513),
+ (125.3865, 39.3879),
+ (125.1328, 38.8485),
+ (125.2219, 38.6658),
+ (124.9859, 38.5484),
+ (124.7121, 38.1083),
+ (124.9810, 37.9488),
+ (125.2400, 37.8572),
+ (125.2753, 37.6690),
+ (125.5684, 37.7520),
+ (125.6891, 37.9400),
+ (126.1747, 37.7496),
+ (126.8601, 36.8939),
+ (126.1173, 36.7254),
+ (126.5592, 35.6845),
+ (126.3739, 34.9345),
+ (126.4857, 34.3900),
+ (127.3865, 34.4756),
+ (128.1858, 34.8903),
+ (129.0913, 35.0824),
+ (129.4683, 35.6321),
+ (129.4604, 36.7841),
+ (129.2129, 37.4323),
+ (128.3497, 38.6122),
+ (127.7833, 39.0508),
+ (127.3854, 39.2134),
+ (127.5021, 39.3239),
+ (127.5334, 39.7568),
+ (127.9674, 40.0254),
+ (128.6333, 40.1898),
+ (129.0103, 40.4854),
+ (129.1881, 40.6618),
+ (129.7051, 40.8828),
+ (129.6673, 41.6011),
+ (129.9659, 41.9413),
+ (130.4000, 42.2800),
+ (130.7800, 42.2200),
+ (130.9358, 42.5527),
+ (132.2780, 43.2845),
+ (132.9062, 42.7984),
+ (133.5368, 42.8114),
+ (134.8694, 43.3982),
+ (135.5153, 43.9889),
+ (136.8623, 45.1434),
+ (138.2197, 46.3079),
+ (138.5547, 46.9996),
+ (140.0619, 48.4467),
+ (140.5130, 50.0455),
+ (140.5974, 51.2396),
+ (141.3792, 52.2387),
+ (141.3453, 53.0895),
+ (139.9015, 54.1896),
+ (138.8046, 54.2545),
+ (138.1647, 53.7550),
+ (137.1934, 53.9773),
+ (136.7017, 54.6035),
+ (135.1261, 54.7295),
+ (138.9584, 57.0880),
+ (142.1978, 59.0399),
+ (145.4872, 59.3363),
+ (148.5448, 59.1644),
+ (149.7837, 59.6557),
+ (151.3381, 59.5039),
+ (151.2657, 58.7808),
+ (152.8118, 58.8838),
+ (155.0437, 59.1449),
+ (154.2180, 59.7581),
+ (156.7206, 61.4344),
+ (159.3023, 61.7739),
+ (160.1214, 60.5442),
+ (162.6579, 61.6424),
+ (163.2583, 62.4662),
+ (164.4735, 62.5506),
+ (163.6696, 61.1408),
+ (161.87204, 60.343),
+ (160.1506, 59.3147),
+ (158.3643, 58.0557),
+ (156.8103, 57.8320),
+ (156.7581, 57.3647),
+ (155.9144, 56.7679),
+ (155.4336, 55.3810),
+ (155.9918, 53.1589),
+ (156.42, 51.7),
+ (156.7897, 51.0110),
+ (158.2311, 51.9426),
+ (158.5309, 52.9586),
+ (160.0217, 53.2025),
+ (160.3687, 54.3443),
+ (162.1174, 54.8551),
+ (161.7014, 55.2856),
+ (162.1263, 56.1158),
+ (163.0579, 56.1592),
+ (163.1919, 57.6150),
+ (162.0529, 57.8391),
+ (162.0173, 58.2432),
+ (163.2171, 59.2110),
+ (163.5392, 59.8686),
+ (164.8767, 59.7316),
+ (165.84, 60.16),
+ (166.2949, 59.7885),
+ (168.9004, 60.5735),
+ (170.3308, 59.8817),
+ (170.6985, 60.3361),
+ (172.15, 60.95),
+ (173.6801, 61.6526),
+ (174.5692, 61.7691),
+ (177.3643, 62.5219),
+ (179.2282, 62.3041),
+ (179.4863, 62.5689),
+ (179.3703, 62.9826),
+ (178.9082, 63.2519),
+ (178.313, 64.07593),
+ (177.4112, 64.6082),
+ (178.7072, 64.5349),
+ (180.0, 64.9797),
+ (-177.5500, 68.1999),
+ (-179.9999, 68.9636),
+ (-179.9999, -16.0671),
+ (-179.7933, -16.0208),
+ (-179.9173, -16.5017),
+ (-179.9999, -16.5552),
+ (125.9470, -8.4320),
+ (126.6447, -8.3982),
+ (126.9572, -8.2733),
+ (127.3359, -8.3973),
+ (126.9679, -8.6682),
+ (125.9258, -9.1060),
+ (125.0885, -9.3931),
+ (124.4359, -10.1400),
+ (123.5799, -10.3599),
+ (123.4599, -10.2399),
+ (123.5500, -9.9000),
+ (123.9800, -9.2900),
+ (124.9686, -8.8927),
+ (125.0862, -8.6568),
+ (125.9470, -8.4320),
+ (-180.0, 68.9636),
+ (-177.5500, 68.1999),
+ (-174.9282, 67.2058),
+ (-175.0142, 66.5843),
+ (-174.3398, 66.3355),
+ (-174.5718, 67.0621),
+ (-171.8573, 66.9130),
+ (-169.8995, 65.9772),
+ (-170.8910, 65.5413),
+ (-172.5302, 65.4379),
+ (-172.555, 64.46079),
+ (-172.9553, 64.2526),
+ (-173.8918, 64.2826),
+ (-174.6539, 64.6312),
+ (-175.9835, 64.9228),
+ (-176.2071, 65.3566),
+ (-177.2226, 65.5202),
+ (-178.3599, 65.3905),
+ (-178.9033, 65.7404),
+ (-178.6861, 66.1121),
+ (-179.8837, 65.8745),
+ (-179.4326, 65.4041),
+ (-180.0, 64.9797),
+ (-180.0, 71.5157),
+ (-179.8718, 71.5576),
+ (-179.0243, 71.5555),
+ (-177.5779, 71.2694),
+ (-177.6635, 71.1327),
+ (-178.6937, 70.8930),
+ (-180.0, 70.8321),
+ (180.0, 70.8321),
+ (178.9034, 70.7811),
+ (178.7253, 71.0988),
+ (180.0, 71.5157),
+ (180.0, -16.5552),
+ (179.3641, -16.8013),
+ (178.7250, -17.0120),
+ (178.5968, -16.6391),
+ (179.0966, -16.4339),
+ (179.4135, -16.3790),
+ (180.0, -16.0671),
+ (-61.2, -51.85),
+ (-60.0, -51.25),
+ (-59.15, -51.5),
+ (-58.55, -51.1),
+ (-57.75, -51.55),
+ (-58.05, -51.9),
+ (-59.4, -52.2),
+ (-59.85, -51.85),
+ (-60.7, -52.3),
+ (-61.2, -51.85),
+ (68.935, -48.625),
+ (69.58, -48.94),
+ (70.525, -49.065),
+ (70.56, -49.255),
+ (70.28, -49.71),
+ (68.745, -49.775),
+ (68.72, -49.2425),
+ (68.8675, -48.83),
+ (68.935, -48.625),
+ (178.1255, -17.5048),
+ (178.3736, -17.3399),
+ (178.7180, -17.6284),
+ (178.5527, -18.1505),
+ (177.9326, -18.2879),
+ (177.3814, -18.1643),
+ (177.2850, -17.7246),
+ (177.6708, -17.3811),
+ (178.1255, -17.5048),
+ (-61.68, 10.76),
+ (-61.105, 10.89),
+ (-60.895, 10.855),
+ (-60.935, 10.11),
+ (-61.77, 10.0),
+ (-61.95, 10.09),
+ (-61.66, 10.365),
+ (-61.68, 10.76),
+ (-155.4021, 20.0797),
+ (-155.2245, 19.9930),
+ (-155.0622, 19.8591),
+ (-154.8074, 19.5087),
+ (-154.8314, 19.4532),
+ (-155.2221, 19.2397),
+ (-155.5421, 19.0834),
+ (-155.6881, 18.9161),
+ (-155.9366, 19.0593),
+ (-155.9080, 19.3388),
+ (-156.0734, 19.7029),
+ (-156.0236, 19.8142),
+ (-155.8500, 19.9772),
+ (-155.9190, 20.1739),
+ (-155.8610, 20.2672),
+ (-155.7850, 20.2487),
+ (-155.4021, 20.0797),
+ (-155.9956, 20.7640),
+ (-156.0792, 20.6439),
+ (-156.4144, 20.5724),
+ (-156.58673, 20.783),
+ (-156.7016, 20.8643),
+ (-156.7105, 20.9267),
+ (-156.6125, 21.0124),
+ (-156.2571, 20.9174),
+ (-155.9956, 20.7640),
+ (-156.7582, 21.1768),
+ (-156.7893, 21.0687),
+ (-157.3252, 21.0977),
+ (-157.2502, 21.2195),
+ (-156.7582, 21.1768),
+ (-158.0252, 21.7169),
+ (-157.9416, 21.6527),
+ (-157.6528, 21.3221),
+ (-157.7070, 21.2644),
+ (-157.7786, 21.2772),
+ (-158.1266, 21.3124),
+ (-158.2538, 21.5391),
+ (-158.2926, 21.5791),
+ (-158.0252, 21.7169),
+ (-159.3656, 22.2149),
+ (-159.34512, 21.982),
+ (-159.4637, 21.8829),
+ (-159.8005, 22.0653),
+ (-159.7487, 22.1382),
+ (-159.5962, 22.2361),
+ (-159.3656, 22.2149),
+ (-78.1908, 25.2103),
+ (-77.89, 25.17),
+ (-77.54, 24.34),
+ (-77.5346, 23.7597),
+ (-77.78, 23.71),
+ (-78.0340, 24.2861),
+ (-78.4084, 24.5756),
+ (-78.1908, 25.2103),
+ (-78.98, 26.79),
+ (-78.51, 26.87),
+ (-77.85, 26.84),
+ (-77.82, 26.58),
+ (-78.91, 26.42),
+ (-78.98, 26.79),
+ (-77.79, 27.04),
+ (-77.0, 26.59),
+ (-77.1725, 25.8791),
+ (-77.3564, 26.0073),
+ (-77.34, 26.53),
+ (-77.7880, 26.9251),
+ (-77.79, 27.04),
+ (-64.0148, 47.0360),
+ (-63.6645, 46.5500),
+ (-62.9393, 46.4158),
+ (-62.0120, 46.4431),
+ (-62.5039, 46.0333),
+ (-62.8743, 45.9681),
+ (-64.1428, 46.3926),
+ (-64.3926, 46.7274),
+ (-64.0148, 47.0360),
+ (46.6820, 44.6092),
+ (47.6759, 45.6414),
+ (48.6454, 45.8062),
+ (49.1011, 46.3993),
+ (50.0340, 46.6089),
+ (51.1919, 47.0487),
+ (52.0420, 46.8046),
+ (53.0427, 46.8530),
+ (53.2208, 46.2346),
+ (53.0408, 45.2590),
+ (52.1673, 45.4083),
+ (51.3168, 45.2459),
+ (51.2785, 44.5148),
+ (50.3056, 44.6098),
+ (50.3391, 44.2840),
+ (50.8912, 44.0310),
+ (51.3424, 43.1329),
+ (52.5014, 42.7922),
+ (52.6921, 42.4438),
+ (52.4463, 42.0271),
+ (52.5024, 41.7833),
+ (52.8146, 41.1353),
+ (52.9167, 41.8681),
+ (53.7217, 42.1231),
+ (54.0083, 41.5512),
+ (54.7368, 40.9510),
+ (53.8581, 40.6310),
+ (52.9152, 40.8765),
+ (52.6939, 40.0336),
+ (53.3578, 39.9752),
+ (53.1010, 39.2905),
+ (53.8809, 38.9520),
+ (53.7355, 37.9061),
+ (53.9215, 37.1989),
+ (53.8257, 36.9650),
+ (52.2640, 36.7004),
+ (50.8423, 36.8728),
+ (50.1477, 37.3745),
+ (49.1996, 37.5828),
+ (48.8832, 38.3202),
+ (48.8565, 38.8154),
+ (49.2232, 39.0492),
+ (49.3952, 39.3994),
+ (49.5692, 40.1761),
+ (50.3928, 40.2565),
+ (50.0848, 40.5261),
+ (49.6189, 40.5729),
+ (49.1102, 41.2822),
+ (48.5843, 41.8088),
+ (47.4925, 42.9865),
+ (47.5909, 43.6601),
+ (46.6820, 44.6092),
+ (-64.5191, 49.8730),
+ (-64.1732, 49.9571),
+ (-62.8582, 49.7064),
+ (-61.8355, 49.2885),
+ (-61.8063, 49.1050),
+ (-62.2931, 49.0871),
+ (-63.5892, 49.4006),
+ (-64.5191, 49.8730),
+ (-80.3153, 62.0855),
+ (-79.9293, 62.3856),
+ (-79.5200, 62.3637),
+ (-79.2658, 62.1586),
+ (-79.6575, 61.6330),
+ (-80.0995, 61.7181),
+ (-80.3621, 62.0164),
+ (-80.3153, 62.0855),
+ (-83.9936, 62.4528),
+ (-83.2504, 62.9140),
+ (-81.8769, 62.9045),
+ (-81.8982, 62.7108),
+ (-83.0685, 62.1592),
+ (-83.7746, 62.1823),
+ (-83.9936, 62.4528),
+ (-75.2159, 67.4442),
+ (-75.8658, 67.1488),
+ (-76.9868, 67.0987),
+ (-77.2364, 67.5880),
+ (-76.8116, 68.1485),
+ (-75.8952, 68.2872),
+ (-75.1145, 68.0103),
+ (-75.1033, 67.5820),
+ (-75.2159, 67.4442),
+ (-96.5574, 69.6800),
+ (-95.6476, 69.1076),
+ (-96.2695, 68.7570),
+ (-97.6174, 69.0600),
+ (-98.4318, 68.9507),
+ (-99.7974, 69.4000),
+ (-98.9174, 69.7100),
+ (-98.2182, 70.1435),
+ (-97.1574, 69.8600),
+ (-96.5574, 69.6800),
+ (-106.5225, 73.0760),
+ (-105.4024, 72.6725),
+ (-104.7748, 71.6984),
+ (-104.4647, 70.9929),
+ (-102.7853, 70.4977),
+ (-100.9807, 70.0243),
+ (-101.0893, 69.5844),
+ (-102.7311, 69.5040),
+ (-102.0932, 69.1196),
+ (-102.4302, 68.7528),
+ (-104.24, 68.91),
+ (-105.96, 69.18),
+ (-107.1225, 69.1192),
+ (-109.0, 68.78),
+ (-111.5341, 68.6300),
+ (-113.3132, 68.5355),
+ (-113.8549, 69.0074),
+ (-115.22, 69.28),
+ (-116.1079, 69.1682),
+ (-117.34, 69.96),
+ (-116.6747, 70.0665),
+ (-115.1311, 70.2373),
+ (-113.7213, 70.1923),
+ (-112.4161, 70.3663),
+ (-114.3499, 70.6000),
+ (-116.4868, 70.5204),
+ (-117.9048, 70.5405),
+ (-118.4323, 70.9092),
+ (-116.1131, 71.3091),
+ (-117.6556, 71.2951),
+ (-119.4019, 71.5585),
+ (-118.5626, 72.3078),
+ (-117.8664, 72.7059),
+ (-115.1890, 73.3145),
+ (-114.1671, 73.1214),
+ (-114.6663, 72.6527),
+ (-112.4410, 72.9553),
+ (-111.0503, 72.4504),
+ (-109.9203, 72.9611),
+ (-109.0065, 72.6333),
+ (-108.1883, 71.6508),
+ (-107.6859, 72.0654),
+ (-108.3964, 73.0895),
+ (-107.5164, 73.2359),
+ (-106.5225, 73.0760),
+ (-79.7758, 72.8029),
+ (-80.8760, 73.3331),
+ (-80.8338, 73.6931),
+ (-80.3530, 73.7597),
+ (-78.0644, 73.6519),
+ (-76.34, 73.1026),
+ (-76.2514, 72.8263),
+ (-77.3144, 72.8555),
+ (-78.3916, 72.8766),
+ (-79.4862, 72.7422),
+ (-79.7758, 72.8029),
+ (139.8631, 73.3698),
+ (140.8117, 73.7650),
+ (142.0620, 73.8575),
+ (143.4828, 73.4752),
+ (143.6038, 73.2124),
+ (142.0876, 73.2054),
+ (140.0381, 73.3169),
+ (139.8631, 73.3698),
+ (148.2222, 75.3458),
+ (150.7316, 75.0840),
+ (149.5759, 74.6889),
+ (147.9774, 74.7783),
+ (146.1191, 75.1729),
+ (146.3584, 75.4968),
+ (148.2222, 75.3458),
+ (138.8310, 76.1367),
+ (141.4716, 76.0928),
+ (145.0862, 75.5626),
+ (144.3, 74.82),
+ (140.6138, 74.8476),
+ (138.9554, 74.6114),
+ (136.9743, 75.2616),
+ (137.5117, 75.9491),
+ (138.8310, 76.1367),
+ (-98.5770, 76.5886),
+ (-98.5, 76.72),
+ (-97.7355, 76.2565),
+ (-97.7044, 75.7434),
+ (-98.16, 75.0),
+ (-99.8087, 74.8974),
+ (-100.8836, 75.0573),
+ (-100.8629, 75.6407),
+ (-102.5020, 75.5638),
+ (-102.5655, 76.3365),
+ (-101.4897, 76.3053),
+ (-99.9834, 76.6463),
+ (-98.5770, 76.5886),
+ (102.8378, 79.2812),
+ (105.3724, 78.7133),
+ (105.0754, 78.3068),
+ (99.43814, 77.921),
+ (101.2649, 79.2339),
+ (102.0863, 79.3464),
+ (102.8378, 79.2812),
+ (93.7776, 81.0246),
+ (95.9408, 81.2504),
+ (97.8838, 80.7469),
+ (100.1866, 79.7801),
+ (99.9397, 78.8809),
+ (97.7579, 78.7562),
+ (94.9725, 79.0447),
+ (93.3128, 79.4265),
+ (92.5454, 80.1437),
+ (91.1810, 80.3414),
+ (93.7776, 81.0246),
+ (-96.0164, 80.6023),
+ (-95.3234, 80.9072),
+ (-94.2984, 80.9772),
+ (-94.7354, 81.2064),
+ (-92.4098, 81.2573),
+ (-91.1328, 80.7234),
+ (-87.81, 80.32),
+ (-87.02, 79.66),
+ (-85.8143, 79.3369),
+ (-87.1875, 79.0393),
+ (-89.0353, 78.2872),
+ (-90.8043, 78.2153),
+ (-92.8766, 78.3433),
+ (-93.9511, 78.7510),
+ (-93.9357, 79.1137),
+ (-93.1452, 79.3801),
+ (-94.9739, 79.3724),
+ (-96.0761, 79.7050),
+ (-96.7097, 80.1577),
+ (-96.0164, 80.6023),
+ (-91.5870, 81.8942),
+ (-90.1, 82.085),
+ (-88.9322, 82.1175),
+ (-86.9702, 82.2796),
+ (-85.5, 82.6522),
+ (-84.2600, 82.6),
+ (-83.18, 82.32),
+ (-82.42, 82.86),
+ (-81.1, 83.02),
+ (-79.3066, 83.1305),
+ (-76.25, 83.1720),
+ (-75.7187, 83.0640),
+ (-72.8315, 83.2332),
+ (-70.6657, 83.1697),
+ (-68.5, 83.1063),
+ (-65.8273, 83.0280),
+ (-63.68, 82.9),
+ (-61.85, 82.6286),
+ (-61.8938, 82.3616),
+ (-64.334, 81.92775),
+ (-66.7534, 81.7252),
+ (-67.6575, 81.5014),
+ (-65.4803, 81.5065),
+ (-67.84, 80.9),
+ (-69.4697, 80.6168),
+ (-71.18, 79.8),
+ (-73.2428, 79.6341),
+ (-73.88, 79.4301),
+ (-76.9077, 79.3230),
+ (-75.5292, 79.1976),
+ (-76.2204, 79.0190),
+ (-75.3934, 78.5258),
+ (-76.3435, 78.1829),
+ (-77.8885, 77.8999),
+ (-78.3626, 77.5085),
+ (-79.7595, 77.2096),
+ (-79.6196, 76.9833),
+ (-77.9108, 77.0220),
+ (-77.8891, 76.7779),
+ (-80.5612, 76.1781),
+ (-83.1743, 76.4540),
+ (-86.1118, 76.2990),
+ (-87.6, 76.42),
+ (-89.4906, 76.4723),
+ (-89.6161, 76.9521),
+ (-87.7673, 77.1783),
+ (-88.26, 77.9),
+ (-87.65, 77.9702),
+ (-84.9763, 77.5387),
+ (-86.34, 78.18),
+ (-87.9619, 78.3718),
+ (-87.1519, 78.7586),
+ (-85.3786, 78.9969),
+ (-85.0949, 79.3454),
+ (-86.5073, 79.7362),
+ (-86.9317, 80.2514),
+ (-84.1984, 80.2083),
+ (-83.4086, 80.1),
+ (-81.8482, 80.4644),
+ (-84.1, 80.58),
+ (-87.5989, 80.5162),
+ (-89.3666, 80.8556),
+ (-90.2, 81.26),
+ (-91.3678, 81.5531),
+ (-91.5870, 81.8942),
+ (-46.7637, 82.6279),
+ (-43.4064, 83.2251),
+ (-39.8975, 83.1801),
+ (-38.6221, 83.5490),
+ (-35.0878, 83.6451),
+ (-27.1004, 83.5196),
+ (-20.8453, 82.7266),
+ (-22.6918, 82.3416),
+ (-26.5175, 82.2976),
+ (-31.9, 82.2),
+ (-31.3964, 82.0215),
+ (-27.8566, 82.1317),
+ (-24.8444, 81.7869),
+ (-22.9032, 82.0931),
+ (-22.0717, 81.7344),
+ (-23.1696, 81.1527),
+ (-20.6236, 81.5246),
+ (-15.7681, 81.9124),
+ (-12.7701, 81.7188),
+ (-12.2085, 81.2915),
+ (-16.2853, 80.5800),
+ (-16.85, 80.35),
+ (-20.0462, 80.1770),
+ (-17.7303, 80.1291),
+ (-18.9, 79.4),
+ (-19.7049, 78.7512),
+ (-19.6735, 77.6385),
+ (-18.4728, 76.9856),
+ (-20.0350, 76.9443),
+ (-21.6794, 76.6279),
+ (-19.8340, 76.0980),
+ (-19.5989, 75.2483),
+ (-20.6681, 75.1558),
+ (-19.3728, 74.2956),
+ (-21.5942, 74.2238),
+ (-20.4345, 73.8171),
+ (-20.7623, 73.4643),
+ (-22.1722, 73.3095),
+ (-23.5659, 73.3066),
+ (-22.3131, 72.6292),
+ (-22.2995, 72.1840),
+ (-24.2783, 72.5978),
+ (-24.7929, 72.3302),
+ (-23.4429, 72.0801),
+ (-22.1328, 71.4689),
+ (-21.7535, 70.6636),
+ (-23.53603, 70.471),
+ (-24.3070, 70.8564),
+ (-25.5434, 71.4309),
+ (-25.2013, 70.7522),
+ (-26.3627, 70.2264),
+ (-23.7274, 70.1840),
+ (-22.3490, 70.1294),
+ (-25.0292, 69.2588),
+ (-27.7473, 68.4704),
+ (-30.6737, 68.1250),
+ (-31.7766, 68.1207),
+ (-32.8110, 67.7354),
+ (-34.2019, 66.6797),
+ (-36.3528, 65.9789),
+ (-37.0437, 65.9376),
+ (-38.3750, 65.6921),
+ (-39.8122, 65.4584),
+ (-40.6689, 64.8399),
+ (-40.6828, 64.1390),
+ (-41.1887, 63.4824),
+ (-42.8193, 62.6823),
+ (-42.4166, 61.9009),
+ (-42.8661, 61.0740),
+ (-43.3784, 60.0977),
+ (-44.7875, 60.0367),
+ (-46.2636, 60.8532),
+ (-48.2629, 60.8584),
+ (-49.2330, 61.4068),
+ (-49.9003, 62.3833),
+ (-51.6332, 63.6269),
+ (-52.1401, 64.2784),
+ (-52.2765, 65.1767),
+ (-53.6616, 66.0995),
+ (-53.3016, 66.8365),
+ (-53.9691, 67.1889),
+ (-52.9804, 68.3575),
+ (-51.4753, 68.7295),
+ (-51.0804, 69.1478),
+ (-50.8712, 69.9291),
+ (-52.0135, 69.5749),
+ (-52.5579, 69.4261),
+ (-53.4562, 69.2836),
+ (-54.6833, 69.6100),
+ (-54.7500, 70.2893),
+ (-54.3588, 70.8213),
+ (-53.4313, 70.8357),
+ (-51.3901, 70.5697),
+ (-53.1093, 71.2048),
+ (-54.0042, 71.5471),
+ (-55.0, 71.4065),
+ (-55.8346, 71.6544),
+ (-54.7181, 72.5862),
+ (-55.3263, 72.9586),
+ (-56.1200, 73.6497),
+ (-57.3236, 74.7102),
+ (-58.5967, 75.0986),
+ (-58.5851, 75.5172),
+ (-61.2686, 76.1023),
+ (-63.3916, 76.1752),
+ (-66.0642, 76.1348),
+ (-68.5043, 76.0614),
+ (-69.6648, 76.3797),
+ (-71.4025, 77.0085),
+ (-68.7767, 77.3231),
+ (-66.7639, 77.3759),
+ (-71.0429, 77.6359),
+ (-73.297, 78.04419),
+ (-73.1593, 78.4327),
+ (-69.3734, 78.9138),
+ (-65.7107, 79.3943),
+ (-65.3239, 79.7581),
+ (-68.0229, 80.1172),
+ (-67.1512, 80.5158),
+ (-63.6892, 81.2139),
+ (-62.2344, 81.3211),
+ (-62.6511, 81.7704),
+ (-60.2824, 82.0336),
+ (-57.2074, 82.1907),
+ (-54.1344, 82.1996),
+ (-53.0432, 81.8883),
+ (-50.3906, 82.4388),
+ (-48.0038, 82.0648),
+ (-46.5998, 81.9859),
+ (-44.523, 81.6607),
+ (-46.9007, 82.1997),
+ (-46.7637, 82.6279),
+ (-106.6, 73.6),
+ (-105.26, 73.64),
+ (-104.5, 73.42),
+ (-105.38, 72.76),
+ (-106.94, 73.46),
+ (-106.6, 73.6),
+ (-180.0, -84.71338),
+ (-179.9424, -84.7214),
+ (-179.0586, -84.1394),
+ (-177.2567, -84.4529),
+ (-176.0846, -84.0992),
+ (-175.8298, -84.1179),
+ (-174.3825, -84.5343),
+ (-173.1165, -84.1179),
+ (-172.8891, -84.0610),
+ (-169.9512, -83.8846),
+ (-168.9999, -84.1179),
+ (-168.5301, -84.2373),
+ (-167.0220, -84.5704),
+ (-164.1821, -84.8252),
+ (-161.9297, -85.1387),
+ (-158.0713, -85.3739),
+ (-155.1922, -85.0995),
+ (-150.9420, -85.2955),
+ (-148.5330, -85.6090),
+ (-145.8889, -85.3151),
+ (-143.1077, -85.0407),
+ (-142.8922, -84.5704),
+ (-146.8290, -84.5312),
+ (-150.0607, -84.2961),
+ (-150.9029, -83.9042),
+ (-153.5862, -83.6886),
+ (-153.4099, -83.2380),
+ (-153.0377, -82.8265),
+ (-152.6656, -82.4541),
+ (-152.8615, -82.0426),
+ (-154.5262, -81.7683),
+ (-155.2901, -81.4156),
+ (-156.8374, -81.1021),
+ (-154.4087, -81.1609),
+ (-152.0976, -81.0041),
+ (-150.6482, -81.3373),
+ (-148.8659, -81.0433),
+ (-147.2207, -80.6710),
+ (-146.4177, -80.3379),
+ (-146.7702, -79.9264),
+ (-148.0629, -79.6520),
+ (-149.5319, -79.3582),
+ (-151.5884, -79.2993),
+ (-153.3903, -79.1622),
+ (-155.3293, -79.0642),
+ (-155.9756, -78.6919),
+ (-157.2683, -78.3784),
+ (-158.0517, -78.0256),
+ (-158.3651, -76.8892),
+ (-157.8754, -76.9872),
+ (-156.9745, -77.3007),
+ (-155.3293, -77.2027),
+ (-153.7428, -77.0655),
+ (-152.9202, -77.4966),
+ (-151.3337, -77.3987),
+ (-150.0019, -77.1831),
+ (-148.7484, -76.9088),
+ (-147.6124, -76.5757),
+ (-146.1044, -76.4777),
+ (-146.1435, -76.1054),
+ (-146.4960, -75.7331),
+ (-146.2023, -75.3804),
+ (-144.9096, -75.2040),
+ (-144.3220, -75.5371),
+ (-142.7943, -75.3412),
+ (-141.6387, -75.0864),
+ (-140.2090, -75.0668),
+ (-138.8575, -74.9689),
+ (-137.5062, -74.7337),
+ (-136.4289, -74.5182),
+ (-135.2145, -74.3026),
+ (-134.4311, -74.3614),
+ (-133.7456, -74.4398),
+ (-132.2571, -74.3026),
+ (-130.9253, -74.4790),
+ (-129.5542, -74.4594),
+ (-128.2420, -74.3222),
+ (-126.8906, -74.4202),
+ (-125.4020, -74.5182),
+ (-124.0114, -74.4790),
+ (-122.5621, -74.4986),
+ (-121.0736, -74.5182),
+ (-119.7025, -74.4790),
+ (-118.6841, -74.1850),
+ (-117.4698, -74.0283),
+ (-116.2163, -74.2438),
+ (-115.0215, -74.0675),
+ (-113.9443, -73.7148),
+ (-113.2979, -74.0283),
+ (-112.9454, -74.3810),
+ (-112.2990, -74.7141),
+ (-111.2610, -74.4202),
+ (-110.0663, -74.7925),
+ (-108.7149, -74.9101),
+ (-107.5593, -75.1844),
+ (-106.1491, -75.1256),
+ (-104.8760, -74.9493),
+ (-103.3679, -74.9884),
+ (-102.0165, -75.1256),
+ (-100.6455, -75.3020),
+ (-100.1166, -74.8709),
+ (-100.7630, -74.5378),
+ (-101.2527, -74.1850),
+ (-102.5453, -74.1067),
+ (-103.1133, -73.7344),
+ (-103.3287, -73.3620),
+ (-103.6812, -72.6175),
+ (-102.9174, -72.7546),
+ (-101.6052, -72.8134),
+ (-100.3125, -72.7546),
+ (-99.1373, -72.9114),
+ (-98.1188, -73.2053),
+ (-97.6880, -73.5580),
+ (-96.3365, -73.6168),
+ (-95.0439, -73.4797),
+ (-93.6729, -73.2837),
+ (-92.4390, -73.1661),
+ (-91.4205, -73.4013),
+ (-90.0887, -73.3229),
+ (-89.2269, -72.5587),
+ (-88.4239, -73.0093),
+ (-87.2683, -73.1857),
+ (-86.0148, -73.0877),
+ (-85.1922, -73.4797),
+ (-83.8799, -73.5188),
+ (-82.6656, -73.6364),
+ (-81.4709, -73.8519),
+ (-80.6874, -73.4797),
+ (-80.2957, -73.1269),
+ (-79.2968, -73.5188),
+ (-77.9258, -73.4208),
+ (-76.9073, -73.6364),
+ (-76.2218, -73.9695),
+ (-74.8900, -73.8716),
+ (-73.8520, -73.6560),
+ (-72.8335, -73.4013),
+ (-71.6192, -73.2641),
+ (-70.2090, -73.1465),
+ (-68.9359, -73.0093),
+ (-67.9566, -72.7938),
+ (-67.3690, -72.4803),
+ (-67.1340, -72.0492),
+ (-67.2515, -71.6377),
+ (-67.5649, -71.2458),
+ (-67.9174, -70.8539),
+ (-68.2308, -70.4620),
+ (-68.4854, -70.1093),
+ (-68.5442, -69.7173),
+ (-68.4462, -69.3255),
+ (-67.9762, -68.9532),
+ (-67.5844, -68.5417),
+ (-67.4278, -68.1498),
+ (-67.6236, -67.7187),
+ (-67.7411, -67.3268),
+ (-67.2515, -66.8761),
+ (-66.7031, -66.5822),
+ (-66.0568, -66.2099),
+ (-65.3713, -65.8963),
+ (-64.5682, -65.6025),
+ (-64.1765, -65.1714),
+ (-63.6281, -64.8970),
+ (-63.0013, -64.6423),
+ (-62.0416, -64.5835),
+ (-61.4149, -64.2700),
+ (-60.7098, -64.0740),
+ (-59.8872, -63.9565),
+ (-59.1625, -63.7017),
+ (-58.5945, -63.3882),
+ (-57.8111, -63.2706),
+ (-57.2235, -63.5254),
+ (-57.5957, -63.8585),
+ (-58.6141, -64.1524),
+ (-59.0450, -64.3680),
+ (-59.7893, -64.2112),
+ (-60.6119, -64.3092),
+ (-61.2974, -64.5443),
+ (-62.0221, -64.7990),
+ (-62.5117, -65.0930),
+ (-62.6488, -65.4849),
+ (-62.5901, -65.8572),
+ (-62.1200, -66.1903),
+ (-62.8055, -66.4255),
+ (-63.7456, -66.5038),
+ (-64.2941, -66.8370),
+ (-64.8816, -67.1504),
+ (-65.5084, -67.5816),
+ (-65.6650, -67.9538),
+ (-65.3125, -68.3653),
+ (-64.7837, -68.6789),
+ (-63.9611, -68.9139),
+ (-63.1972, -69.2275),
+ (-62.7859, -69.6194),
+ (-62.5705, -69.9917),
+ (-62.2767, -70.3836),
+ (-61.8066, -70.7167),
+ (-61.5129, -71.0890),
+ (-61.3758, -72.0100),
+ (-61.0819, -72.3823),
+ (-61.0036, -72.7742),
+ (-60.6902, -73.1661),
+ (-60.8273, -73.6952),
+ (-61.3758, -74.1067),
+ (-61.9633, -74.4398),
+ (-63.2952, -74.5769),
+ (-63.7456, -74.9297),
+ (-64.3528, -75.2628),
+ (-65.8609, -75.6351),
+ (-67.1928, -75.7919),
+ (-68.4462, -76.0074),
+ (-69.7977, -76.2229),
+ (-70.6007, -76.6344),
+ (-72.2067, -76.6736),
+ (-73.9695, -76.6344),
+ (-75.5559, -76.7128),
+ (-77.2403, -76.7128),
+ (-76.9269, -77.1048),
+ (-75.3992, -77.2810),
+ (-74.2828, -77.5554),
+ (-73.6561, -77.9081),
+ (-74.7725, -78.2216),
+ (-76.4961, -78.1236),
+ (-77.9258, -78.3784),
+ (-77.9846, -78.7899),
+ (-78.0237, -79.1818),
+ (-76.8486, -79.5149),
+ (-76.6332, -79.8872),
+ (-75.3600, -80.2595),
+ (-73.2448, -80.4163),
+ (-71.4429, -80.6906),
+ (-70.0131, -81.0041),
+ (-68.1916, -81.3176),
+ (-65.7042, -81.4744),
+ (-63.2560, -81.7487),
+ (-61.5520, -82.0426),
+ (-59.6914, -82.3758),
+ (-58.7121, -82.8461),
+ (-58.2224, -83.2184),
+ (-57.0081, -82.8656),
+ (-55.3628, -82.5717),
+ (-53.6197, -82.2582),
+ (-51.5436, -82.0035),
+ (-49.7613, -81.7291),
+ (-47.2739, -81.7095),
+ (-44.8257, -81.8467),
+ (-42.8083, -82.0819),
+ (-42.1620, -81.6508),
+ (-40.7714, -81.3568),
+ (-38.2448, -81.3373),
+ (-36.2666, -81.1217),
+ (-34.3863, -80.9061),
+ (-32.3102, -80.7690),
+ (-30.0970, -80.5926),
+ (-28.5498, -80.3379),
+ (-29.2549, -79.9851),
+ (-29.6858, -79.6325),
+ (-29.6858, -79.2602),
+ (-31.6248, -79.2993),
+ (-33.6813, -79.4561),
+ (-35.6399, -79.4561),
+ (-35.9141, -79.0838),
+ (-35.7770, -78.3392),
+ (-35.3265, -78.1236),
+ (-33.8967, -77.8885),
+ (-32.2123, -77.6534),
+ (-30.9980, -77.3595),
+ (-29.7837, -77.0655),
+ (-28.8827, -76.6736),
+ (-27.5117, -76.4973),
+ (-26.1603, -76.3601),
+ (-25.4748, -76.2818),
+ (-23.9275, -76.2425),
+ (-22.4585, -76.1054),
+ (-21.2246, -75.9094),
+ (-20.0103, -75.6743),
+ (-18.9135, -75.4392),
+ (-17.5229, -75.1256),
+ (-16.6415, -74.7925),
+ (-15.7014, -74.4986),
+ (-15.4077, -74.1067),
+ (-16.4653, -73.8716),
+ (-16.1127, -73.4601),
+ (-15.4468, -73.1465),
+ (-14.4088, -72.9505),
+ (-13.3119, -72.7154),
+ (-12.2935, -72.4019),
+ (-11.5100, -72.0100),
+ (-11.0204, -71.5397),
+ (-10.2957, -71.2654),
+ (-9.1010, -71.3242),
+ (-8.6113, -71.6573),
+ (-7.4166, -71.6965),
+ (-7.3774, -71.3242),
+ (-6.8682, -70.9323),
+ (-5.7909, -71.0302),
+ (-5.5363, -71.4026),
+ (-4.3416, -71.4613),
+ (-3.0489, -71.2850),
+ (-1.7954, -71.1674),
+ (-0.6594, -71.2262),
+ (-0.2286, -71.6377),
+ (0.8681, -71.3046),
+ (1.8866, -71.1282),
+ (3.0226, -70.9911),
+ (4.1390, -70.8539),
+ (5.1575, -70.6187),
+ (6.2739, -70.4620),
+ (7.1357, -70.2465),
+ (7.7428, -69.8937),
+ (8.4871, -70.1485),
+ (9.5251, -70.0113),
+ (10.2498, -70.4816),
+ (10.8178, -70.8343),
+ (11.9538, -70.6383),
+ (12.4042, -70.2465),
+ (13.4227, -69.9721),
+ (14.7349, -70.0309),
+ (15.1267, -70.4032),
+ (15.9493, -70.0309),
+ (17.0265, -69.9133),
+ (18.2017, -69.8741),
+ (19.2593, -69.8937),
+ (20.3757, -70.0113),
+ (21.4529, -70.0701),
+ (21.9230, -70.4032),
+ (22.5694, -70.6971),
+ (23.6661, -70.5208),
+ (24.8413, -70.4816),
+ (25.9773, -70.4816),
+ (27.0937, -70.4620),
+ (28.0925, -70.3248),
+ (29.1502, -70.2072),
+ (30.0315, -69.9329),
+ (30.9717, -69.7566),
+ (31.9901, -69.6586),
+ (32.7540, -69.3842),
+ (33.3024, -68.8356),
+ (33.8704, -68.5025),
+ (34.9084, -68.6592),
+ (35.3002, -69.0120),
+ (36.1620, -69.2471),
+ (37.2000, -69.1687),
+ (37.9051, -69.5214),
+ (38.6494, -69.7762),
+ (39.6678, -69.5410),
+ (40.0204, -69.1099),
+ (40.9213, -68.9336),
+ (41.9594, -68.6005),
+ (42.9387, -68.4633),
+ (44.1138, -68.2674),
+ (44.8972, -68.0518),
+ (45.7199, -67.8167),
+ (46.5033, -67.6011),
+ (47.4434, -67.7187),
+ (48.3444, -67.3660),
+ (48.9907, -67.0917),
+ (49.9308, -67.1113),
+ (50.7534, -66.8761),
+ (50.9493, -66.5234),
+ (51.7915, -66.2491),
+ (52.6141, -66.0531),
+ (53.6130, -65.8963),
+ (54.5335, -65.8180),
+ (55.4149, -65.8768),
+ (56.3550, -65.9747),
+ (57.1580, -66.2491),
+ (57.2559, -66.6802),
+ (58.1373, -67.0133),
+ (58.7445, -67.2876),
+ (59.9393, -67.4052),
+ (60.6052, -67.6795),
+ (61.4278, -67.9538),
+ (62.3874, -68.0126),
+ (63.1904, -67.8167),
+ (64.0523, -67.4052),
+ (64.9924, -67.6207),
+ (65.9717, -67.7383),
+ (66.9118, -67.8559),
+ (67.8911, -67.9343),
+ (68.8900, -67.9343),
+ (69.7126, -68.9727),
+ (69.6734, -69.2275),
+ (69.5559, -69.6782),
+ (68.5962, -69.9329),
+ (67.8127, -70.3052),
+ (67.9498, -70.6971),
+ (69.0663, -70.6775),
+ (68.9291, -71.0694),
+ (68.4199, -71.4417),
+ (67.9498, -71.8532),
+ (68.7137, -72.1668),
+ (69.8693, -72.2647),
+ (71.0248, -72.0884),
+ (71.5732, -71.6965),
+ (71.9062, -71.3242),
+ (72.4546, -71.0107),
+ (73.0814, -70.7167),
+ (73.3360, -70.3640),
+ (73.8648, -69.8741),
+ (74.4915, -69.7762),
+ (75.6275, -69.7370),
+ (76.6264, -69.6194),
+ (77.6449, -69.4626),
+ (78.1345, -69.0707),
+ (78.4283, -68.6984),
+ (79.1138, -68.3262),
+ (80.0931, -68.0715),
+ (80.9353, -67.8755),
+ (81.4837, -67.5423),
+ (82.0517, -67.3660),
+ (82.7764, -67.2092),
+ (83.7753, -67.3072),
+ (84.6762, -67.2092),
+ (85.6555, -67.0917),
+ (86.7523, -67.1504),
+ (87.4770, -66.8761),
+ (87.9862, -66.2099),
+ (88.3584, -66.4842),
+ (88.8284, -66.9545),
+ (89.6706, -67.1504),
+ (90.6303, -67.2288),
+ (91.5900, -67.1113),
+ (92.6085, -67.1896),
+ (93.5486, -67.2092),
+ (94.1754, -67.1113),
+ (95.0175, -67.1701),
+ (95.7814, -67.3856),
+ (96.6823, -67.2485),
+ (97.7596, -67.2485),
+ (98.6802, -67.1113),
+ (99.7181, -67.2485),
+ (100.3841, -66.9153),
+ (100.8933, -66.5822),
+ (101.5788, -66.3078),
+ (102.8324, -65.5632),
+ (103.4786, -65.7004),
+ (104.2425, -65.9747),
+ (104.9084, -66.3275),
+ (106.1815, -66.9349),
+ (107.1608, -66.9545),
+ (108.0813, -66.9545),
+ (109.1586, -66.8370),
+ (110.2358, -66.6998),
+ (111.0584, -66.4255),
+ (111.7439, -66.1315),
+ (112.8603, -66.0923),
+ (113.6046, -65.8768),
+ (114.3880, -66.0727),
+ (114.8973, -66.3862),
+ (115.6023, -66.6998),
+ (116.6991, -66.6606),
+ (117.3847, -66.9153),
+ (118.5794, -67.1701),
+ (119.8329, -67.2680),
+ (120.8709, -67.1896),
+ (121.6544, -66.8761),
+ (122.3203, -66.5626),
+ (123.2212, -66.4842),
+ (124.1222, -66.6214),
+ (125.1602, -66.7193),
+ (126.1003, -66.5626),
+ (127.0014, -66.5626),
+ (127.8827, -66.6606),
+ (128.8032, -66.7586),
+ (129.7042, -66.5822),
+ (130.7814, -66.4255),
+ (131.7999, -66.3862),
+ (132.9358, -66.3862),
+ (133.8564, -66.2883),
+ (134.7573, -66.2099),
+ (135.0315, -65.7200),
+ (135.0707, -65.3085),
+ (135.6974, -65.5828),
+ (135.8738, -66.0335),
+ (136.2067, -66.4450),
+ (136.6180, -66.7781),
+ (137.4602, -66.9545),
+ (138.5962, -66.8957),
+ (139.9084, -66.8761),
+ (140.8094, -66.8173),
+ (142.1216, -66.8173),
+ (143.0618, -66.7977),
+ (144.3740, -66.8370),
+ (145.4904, -66.9153),
+ (146.1955, -67.2288),
+ (145.9996, -67.6011),
+ (146.6460, -67.8951),
+ (147.7232, -68.1302),
+ (148.8396, -68.3850),
+ (150.1323, -68.5612),
+ (151.4837, -68.7181),
+ (152.5022, -68.8748),
+ (153.6381, -68.8945),
+ (154.2845, -68.5612),
+ (155.1658, -68.8356),
+ (155.9297, -69.1492),
+ (156.8111, -69.3842),
+ (158.0255, -69.4822),
+ (159.1810, -69.5998),
+ (159.6706, -69.9917),
+ (160.8066, -70.2268),
+ (161.5704, -70.5796),
+ (162.6868, -70.7363),
+ (163.8424, -70.7167),
+ (164.9196, -70.7755),
+ (166.1144, -70.7559),
+ (167.3090, -70.8343),
+ (168.4256, -70.9714),
+ (169.4635, -71.2066),
+ (170.5016, -71.4026),
+ (171.2067, -71.6965),
+ (171.0892, -72.0884),
+ (170.5604, -72.4411),
+ (170.1099, -72.8918),
+ (169.7573, -73.2445),
+ (169.2873, -73.6560),
+ (167.9751, -73.8128),
+ (167.3874, -74.1654),
+ (166.0948, -74.3810),
+ (165.6443, -74.7729),
+ (164.9588, -75.1452),
+ (164.2341, -75.4588),
+ (163.8227, -75.8703),
+ (163.5682, -76.2425),
+ (163.4702, -76.6933),
+ (163.4898, -77.0655),
+ (164.0578, -77.4574),
+ (164.2733, -77.8297),
+ (164.7434, -78.1825),
+ (166.6041, -78.3196),
+ (166.9957, -78.7507),
+ (165.1938, -78.9074),
+ (163.6662, -79.1230),
+ (161.7663, -79.1622),
+ (160.9241, -79.7304),
+ (160.7478, -80.2007),
+ (160.3169, -80.5730),
+ (159.7882, -80.9453),
+ (161.1200, -81.2785),
+ (161.6292, -81.6900),
+ (162.4909, -82.0622),
+ (163.7053, -82.3954),
+ (165.0959, -82.7089),
+ (166.6041, -83.0224),
+ (168.8956, -83.3359),
+ (169.4047, -83.8258),
+ (172.2839, -84.0414),
+ (172.4770, -84.1179),
+ (173.2240, -84.4137),
+ (175.9856, -84.1589),
+ (178.2772, -84.4725),
+ (180.0, -84.71338),
+];
+
+pub static WORLD_LOW_RESOLUTION: [(f64, f64); 1166] = [
+ (-92.32, 48.24),
+ (-88.13, 48.92),
+ (-83.11, 46.27),
+ (-81.66, 44.76),
+ (-82.09, 42.29),
+ (-77.10, 44.00),
+ (-69.95, 46.92),
+ (-65.92, 45.32),
+ (-66.37, 44.25),
+ (-61.22, 45.43),
+ (-64.94, 47.34),
+ (-64.12, 48.52),
+ (-70.68, 47.02),
+ (-67.24, 49.33),
+ (-59.82, 50.48),
+ (-56.14, 52.46),
+ (-59.07, 53.58),
+ (-58.26, 54.21),
+ (-60.69, 55.33),
+ (-61.97, 57.41),
+ (-64.35, 59.49),
+ (-67.29, 58.15),
+ (-69.89, 59.91),
+ (-71.31, 61.45),
+ (-78.22, 61.97),
+ (-77.28, 59.53),
+ (-77.09, 55.88),
+ (-79.06, 51.68),
+ (-82.23, 52.70),
+ (-86.75, 55.72),
+ (-92.17, 56.86),
+ (-95.61, 58.82),
+ (-92.66, 62.02),
+ (-90.65, 63.24),
+ (-95.96, 64.12),
+ (-89.88, 63.98),
+ (-89.30, 65.22),
+ (-86.86, 66.12),
+ (-84.54, 66.88),
+ (-82.30, 67.76),
+ (-83.10, 69.68),
+ (-86.05, 67.98),
+ (-88.18, 68.20),
+ (-91.00, 68.82),
+ (-91.72, 69.69),
+ (-93.15, 71.09),
+ (-96.58, 71.05),
+ (-93.35, 69.52),
+ (-94.23, 68.25),
+ (-95.96, 66.73),
+ (-98.83, 68.27),
+ (-102.45, 67.69),
+ (-108.34, 68.43),
+ (-105.83, 68.05),
+ (-108.15, 66.60),
+ (-111.15, 67.63),
+ (-114.10, 68.23),
+ (-120.92, 69.44),
+ (-124.32, 69.26),
+ (-128.76, 70.50),
+ (-131.86, 69.19),
+ (-131.15, 69.79),
+ (-135.81, 69.13),
+ (-140.19, 69.37),
+ (-141.20, 69.58),
+ (-141.21, 69.56),
+ (-142.49, 69.83),
+ (-148.09, 70.26),
+ (-154.37, 70.96),
+ (-159.53, 70.38),
+ (-166.64, 68.25),
+ (-161.56, 66.55),
+ (-162.99, 65.97),
+ (-168.23, 65.49),
+ (-161.12, 64.49),
+ (-165.29, 62.57),
+ (-164.58, 60.06),
+ (-162.06, 58.36),
+ (-157.85, 58.12),
+ (-162.34, 55.06),
+ (-156.52, 57.11),
+ (-153.53, 59.32),
+ (-149.18, 60.81),
+ (-149.90, 59.50),
+ (-146.54, 60.36),
+ (-139.98, 59.73),
+ (-137.12, 58.28),
+ (-136.01, 59.12),
+ (-133.84, 57.12),
+ (-131.46, 55.98),
+ (-132.08, 57.20),
+ (-140.37, 60.25),
+ (-141.21, 60.16),
+ (-133.38, 58.93),
+ (-130.88, 54.83),
+ (-128.86, 53.90),
+ (-126.58, 52.12),
+ (-127.08, 50.80),
+ (-124.42, 49.66),
+ (-122.56, 48.91),
+ (-122.44, 48.92),
+ (-124.42, 47.18),
+ (-124.52, 42.48),
+ (-123.09, 38.45),
+ (-121.73, 36.62),
+ (-117.60, 33.34),
+ (-117.28, 32.64),
+ (-117.29, 32.48),
+ (-114.75, 27.80),
+ (-112.53, 24.80),
+ (-110.55, 24.07),
+ (-114.23, 29.59),
+ (-112.58, 29.99),
+ (-109.57, 25.94),
+ (-105.61, 21.94),
+ (-102.09, 17.87),
+ (-95.75, 15.94),
+ (-92.21, 14.97),
+ (-92.22, 14.71),
+ (-86.74, 12.06),
+ (-83.03, 8.65),
+ (-79.93, 8.74),
+ (-77.00, 7.82),
+ (-81.99, 8.97),
+ (-83.92, 12.70),
+ (-86.33, 15.80),
+ (-88.40, 15.92),
+ (-88.45, 17.42),
+ (-87.01, 21.33),
+ (-91.65, 18.72),
+ (-96.96, 20.37),
+ (-97.65, 25.67),
+ (-97.62, 25.82),
+ (-95.62, 28.84),
+ (-90.77, 29.03),
+ (-87.33, 30.22),
+ (-82.69, 28.15),
+ (-80.16, 26.66),
+ (-80.74, 32.31),
+ (-76.89, 35.43),
+ (-76.47, 38.21),
+ (-75.66, 37.67),
+ (-71.31, 41.76),
+ (-69.44, 44.17),
+ (-67.69, 47.03),
+ (-73.18, 45.14),
+ (-79.26, 43.28),
+ (-82.84, 42.59),
+ (-83.49, 45.32),
+ (-86.36, 43.65),
+ (-87.75, 43.42),
+ (-86.01, 45.96),
+ (-87.00, 46.59),
+ (-91.39, 46.79),
+ (-90.05, 47.96),
+ (-152.62, 58.41),
+ (-152.60, 58.40),
+ (-153.30, 57.80),
+ (-152.40, 57.48),
+ (-153.32, 57.79),
+ (-166.96, 53.96),
+ (-167.01, 53.95),
+ (-168.36, 53.50),
+ (-168.19, 53.36),
+ (-170.73, 52.68),
+ (-170.60, 52.55),
+ (-174.47, 51.94),
+ (-174.47, 51.92),
+ (-176.58, 51.71),
+ (-176.64, 51.73),
+ (-177.55, 51.76),
+ (-177.41, 51.63),
+ (-178.27, 51.75),
+ (177.35, 51.80),
+ (177.33, 51.76),
+ (172.44, 53.00),
+ (172.55, 53.03),
+ (-123.40, 48.33),
+ (-128.00, 50.84),
+ (-123.50, 48.34),
+ (-132.49, 52.88),
+ (-132.44, 52.91),
+ (-132.64, 53.02),
+ (-131.97, 53.71),
+ (-132.63, 53.02),
+ (-55.36, 51.56),
+ (-54.66, 49.52),
+ (-53.65, 47.48),
+ (-52.98, 46.31),
+ (-56.12, 46.84),
+ (-58.47, 47.57),
+ (-57.61, 50.38),
+ (-55.39, 51.53),
+ (-61.37, 49.01),
+ (-61.80, 49.29),
+ (-61.38, 49.03),
+ (-63.01, 46.71),
+ (-64.42, 46.61),
+ (-63.04, 46.68),
+ (-60.14, 46.48),
+ (-60.14, 46.50),
+ (-71.97, 41.11),
+ (-71.97, 41.15),
+ (-80.79, 27.03),
+ (-81.01, 26.99),
+ (-113.01, 42.09),
+ (-113.10, 42.01),
+ (-155.74, 20.02),
+ (-155.73, 19.98),
+ (-156.51, 20.78),
+ (-156.51, 20.78),
+ (-157.12, 21.21),
+ (-157.08, 20.95),
+ (-157.87, 21.42),
+ (-159.53, 22.07),
+ (-117.44, 66.46),
+ (-119.59, 65.24),
+ (-123.95, 65.03),
+ (-123.69, 66.44),
+ (-119.21, 66.22),
+ (-117.44, 66.44),
+ (-120.71, 64.03),
+ (-114.91, 62.30),
+ (-109.07, 62.72),
+ (-112.62, 61.19),
+ (-118.68, 61.19),
+ (-117.01, 61.17),
+ (-115.97, 62.56),
+ (-119.46, 64.00),
+ (-120.59, 63.94),
+ (-112.31, 58.46),
+ (-108.90, 59.44),
+ (-104.14, 58.90),
+ (-102.56, 56.72),
+ (-101.82, 58.73),
+ (-104.65, 58.91),
+ (-111.00, 58.51),
+ (-112.35, 58.62),
+ (-98.74, 50.09),
+ (-99.75, 52.24),
+ (-99.62, 51.47),
+ (-98.82, 50.39),
+ (-97.02, 50.21),
+ (-97.50, 54.02),
+ (-98.69, 52.93),
+ (-97.19, 51.09),
+ (-96.98, 50.20),
+ (-95.34, 49.04),
+ (-92.32, 50.34),
+ (-94.14, 49.47),
+ (-95.36, 48.82),
+ (-80.39, 56.16),
+ (-79.22, 55.94),
+ (-80.34, 56.08),
+ (-103.56, 58.60),
+ (-103.60, 58.58),
+ (-101.82, 58.03),
+ (-102.33, 58.10),
+ (-101.77, 58.06),
+ (-101.88, 55.79),
+ (-97.92, 57.15),
+ (-101.22, 55.85),
+ (-101.88, 55.74),
+ (-77.61, 6.80),
+ (-78.70, 0.97),
+ (-80.75, -4.47),
+ (-76.19, -14.57),
+ (-70.44, -18.75),
+ (-70.68, -26.15),
+ (-71.44, -32.03),
+ (-73.38, -37.27),
+ (-73.06, -42.11),
+ (-73.17, -46.09),
+ (-73.52, -48.05),
+ (-73.67, -51.56),
+ (-71.06, -53.88),
+ (-69.14, -50.77),
+ (-67.51, -46.59),
+ (-63.49, -42.80),
+ (-62.14, -40.16),
+ (-57.12, -36.71),
+ (-53.17, -34.15),
+ (-51.26, -32.02),
+ (-48.16, -25.48),
+ (-40.73, -22.32),
+ (-38.88, -15.24),
+ (-34.60, -7.81),
+ (-41.95, -3.42),
+ (-48.02, -1.84),
+ (-48.44, -1.57),
+ (-50.81, 0.00),
+ (-54.47, 5.39),
+ (-60.59, 8.32),
+ (-64.19, 9.88),
+ (-70.78, 10.64),
+ (-70.97, 11.89),
+ (-76.26, 8.76),
+ (-77.61, 6.80),
+ (-69.14, -52.79),
+ (-66.16, -55.08),
+ (-70.01, -54.88),
+ (-70.55, -53.85),
+ (-59.29, -51.58),
+ (-59.35, -51.54),
+ (-58.65, -51.55),
+ (-58.55, -51.56),
+ (-84.39, 21.44),
+ (-73.90, 19.73),
+ (-79.27, 21.18),
+ (-83.74, 21.80),
+ (-84.32, 21.42),
+ (-66.96, 17.95),
+ (-67.05, 17.89),
+ (-77.88, 17.22),
+ (-78.06, 16.98),
+ (-74.47, 18.08),
+ (-69.88, 18.99),
+ (-71.10, 17.76),
+ (-74.45, 17.86),
+ (-85.28, 73.74),
+ (-85.79, 70.96),
+ (-85.13, 71.94),
+ (-84.74, 72.96),
+ (-80.61, 73.10),
+ (-78.45, 72.20),
+ (-75.44, 72.55),
+ (-73.89, 71.98),
+ (-72.56, 71.04),
+ (-71.49, 70.57),
+ (-69.78, 70.29),
+ (-68.12, 69.71),
+ (-65.91, 69.19),
+ (-66.92, 68.39),
+ (-64.08, 67.68),
+ (-62.50, 66.68),
+ (-63.07, 65.33),
+ (-66.11, 66.08),
+ (-67.48, 65.41),
+ (-64.05, 63.15),
+ (-66.58, 63.26),
+ (-69.04, 62.33),
+ (-72.22, 63.77),
+ (-76.88, 64.17),
+ (-73.25, 65.54),
+ (-70.09, 66.64),
+ (-72.05, 67.44),
+ (-76.32, 68.36),
+ (-78.34, 70.17),
+ (-82.12, 69.71),
+ (-87.64, 70.12),
+ (-89.68, 71.43),
+ (-85.28, 73.74),
+ (-80.90, 76.10),
+ (-84.21, 76.28),
+ (-88.94, 76.38),
+ (-85.47, 77.40),
+ (-85.43, 77.93),
+ (-87.01, 78.54),
+ (-83.17, 78.94),
+ (-84.87, 79.93),
+ (-81.33, 79.82),
+ (-76.27, 80.92),
+ (-82.88, 80.62),
+ (-82.58, 81.16),
+ (-86.51, 81.05),
+ (-89.36, 81.21),
+ (-90.45, 81.38),
+ (-89.28, 81.86),
+ (-87.21, 82.30),
+ (-80.51, 82.05),
+ (-80.16, 82.55),
+ (-77.83, 82.86),
+ (-75.51, 83.05),
+ (-71.18, 82.90),
+ (-65.10, 82.78),
+ (-63.34, 81.80),
+ (-68.26, 81.26),
+ (-69.46, 80.34),
+ (-71.05, 79.82),
+ (-74.40, 79.46),
+ (-75.42, 79.03),
+ (-75.48, 78.92),
+ (-76.01, 78.20),
+ (-80.66, 77.28),
+ (-78.07, 76.98),
+ (-80.90, 76.13),
+ (-92.86, 74.13),
+ (-92.50, 72.70),
+ (-94.89, 73.16),
+ (-92.96, 74.14),
+ (-94.80, 76.95),
+ (-89.68, 76.04),
+ (-88.52, 75.40),
+ (-82.36, 75.67),
+ (-79.39, 74.65),
+ (-86.15, 74.22),
+ (-91.70, 74.94),
+ (-95.60, 76.91),
+ (-94.87, 76.96),
+ (-99.96, 73.74),
+ (-97.89, 72.90),
+ (-98.28, 71.13),
+ (-102.04, 72.92),
+ (-101.34, 73.14),
+ (-99.69, 73.59),
+ (-107.58, 73.25),
+ (-104.59, 71.02),
+ (-101.71, 69.56),
+ (-104.07, 68.62),
+ (-106.61, 69.12),
+ (-114.09, 69.05),
+ (-113.89, 70.12),
+ (-115.88, 70.32),
+ (-116.10, 71.32),
+ (-117.45, 72.48),
+ (-113.53, 72.44),
+ (-109.84, 72.24),
+ (-106.62, 71.71),
+ (-107.43, 73.04),
+ (-120.96, 74.29),
+ (-118.37, 72.53),
+ (-123.06, 71.18),
+ (-123.40, 73.77),
+ (-120.93, 74.27),
+ (-108.83, 76.74),
+ (-106.25, 75.54),
+ (-107.08, 74.78),
+ (-112.99, 74.16),
+ (-112.28, 74.99),
+ (-116.04, 75.33),
+ (-115.27, 76.20),
+ (-110.95, 75.56),
+ (-109.77, 76.31),
+ (-108.82, 76.70),
+ (-115.70, 77.46),
+ (-118.10, 76.30),
+ (-121.13, 76.37),
+ (-116.04, 77.28),
+ (-110.01, 77.86),
+ (-112.36, 77.68),
+ (-109.96, 77.86),
+ (-109.60, 78.48),
+ (-112.20, 78.01),
+ (-109.60, 78.48),
+ (-97.87, 76.61),
+ (-99.21, 75.31),
+ (-100.86, 75.60),
+ (-99.40, 76.26),
+ (-97.79, 76.60),
+ (-94.72, 75.53),
+ (-94.66, 75.52),
+ (-104.10, 79.01),
+ (-99.19, 77.54),
+ (-103.22, 78.08),
+ (-104.30, 78.95),
+ (-93.74, 77.52),
+ (-93.74, 77.52),
+ (-96.88, 78.50),
+ (-96.91, 77.77),
+ (-96.94, 78.48),
+ (-84.69, 65.84),
+ (-81.58, 63.87),
+ (-85.00, 62.96),
+ (-84.63, 65.71),
+ (-81.84, 62.75),
+ (-82.01, 62.63),
+ (-79.88, 62.12),
+ (-79.88, 62.12),
+ (-43.53, 59.89),
+ (-45.29, 60.67),
+ (-47.91, 60.83),
+ (-49.90, 62.41),
+ (-50.71, 64.42),
+ (-51.39, 64.94),
+ (-52.96, 66.09),
+ (-53.62, 67.19),
+ (-53.51, 67.51),
+ (-51.84, 68.65),
+ (-52.19, 70.00),
+ (-51.85, 71.03),
+ (-55.41, 71.41),
+ (-54.63, 72.97),
+ (-56.98, 74.70),
+ (-61.95, 76.09),
+ (-66.38, 75.83),
+ (-71.13, 77.00),
+ (-66.81, 77.60),
+ (-70.78, 77.78),
+ (-64.96, 79.70),
+ (-63.38, 81.16),
+ (-56.89, 82.17),
+ (-48.18, 82.15),
+ (-42.08, 82.74),
+ (-38.02, 83.54),
+ (-23.96, 82.94),
+ (-25.97, 81.97),
+ (-25.99, 80.64),
+ (-13.57, 80.97),
+ (-16.60, 80.16),
+ (-19.82, 78.82),
+ (-18.80, 77.54),
+ (-21.98, 76.46),
+ (-20.69, 75.12),
+ (-21.78, 74.40),
+ (-24.10, 73.69),
+ (-26.54, 73.08),
+ (-24.63, 72.69),
+ (-21.84, 71.69),
+ (-24.62, 71.24),
+ (-27.16, 70.89),
+ (-27.21, 70.00),
+ (-24.10, 69.35),
+ (-28.35, 68.43),
+ (-32.48, 68.56),
+ (-35.26, 66.26),
+ (-37.90, 65.90),
+ (-40.04, 65.00),
+ (-40.49, 64.04),
+ (-42.01, 63.14),
+ (-42.88, 61.15),
+ (-43.09, 60.07),
+ (-43.56, 59.90),
+ (-16.26, 66.41),
+ (-15.32, 64.29),
+ (-20.14, 63.47),
+ (-21.76, 64.21),
+ (-21.33, 64.97),
+ (-23.04, 65.62),
+ (-21.76, 66.26),
+ (-18.77, 66.12),
+ (-16.23, 66.35),
+ (0.56, 51.47),
+ (-1.71, 54.94),
+ (-3.41, 57.52),
+ (-5.42, 58.14),
+ (-5.77, 55.59),
+ (-3.48, 54.82),
+ (-4.68, 52.88),
+ (-2.68, 51.58),
+ (-3.80, 50.08),
+ (1.26, 51.14),
+ (0.65, 51.41),
+ (-7.17, 54.91),
+ (-9.97, 53.47),
+ (-8.52, 51.76),
+ (-5.69, 54.79),
+ (-7.34, 55.25),
+ (-1.33, 60.66),
+ (-1.17, 60.38),
+ (-6.18, 58.44),
+ (-6.09, 58.36),
+ (-6.47, 57.58),
+ (-6.33, 57.54),
+ (-7.30, 57.54),
+ (-7.46, 57.05),
+ (-6.54, 56.94),
+ (-6.00, 55.94),
+ (-5.09, 55.55),
+ (-4.44, 54.38),
+ (-4.30, 54.19),
+ (-8.08, 71.02),
+ (-8.21, 70.86),
+ (16.92, 79.52),
+ (22.26, 78.46),
+ (16.86, 76.41),
+ (16.00, 77.39),
+ (16.03, 77.92),
+ (16.81, 79.50),
+ (14.71, 79.40),
+ (16.05, 79.12),
+ (14.02, 77.80),
+ (13.56, 78.46),
+ (12.63, 79.26),
+ (14.68, 79.40),
+ (22.01, 78.24),
+ (21.86, 78.23),
+ (21.54, 77.75),
+ (23.88, 77.26),
+ (21.53, 77.67),
+ (22.79, 77.79),
+ (23.50, 79.97),
+ (28.24, 79.54),
+ (20.85, 78.94),
+ (19.00, 79.34),
+ (21.05, 79.88),
+ (23.41, 79.96),
+ (46.98, 80.23),
+ (43.13, 79.97),
+ (47.18, 80.22),
+ (50.43, 80.19),
+ (50.55, 79.88),
+ (47.77, 79.86),
+ (50.45, 80.14),
+ (61.79, 80.18),
+ (61.79, 80.18),
+ (65.08, 80.69),
+ (64.27, 80.59),
+ (65.13, 80.68),
+ (-5.13, 35.66),
+ (4.06, 36.63),
+ (10.40, 37.12),
+ (11.36, 33.61),
+ (20.10, 30.10),
+ (23.49, 32.17),
+ (31.65, 30.80),
+ (35.76, 23.74),
+ (39.75, 14.82),
+ (42.93, 11.34),
+ (51.52, 11.45),
+ (49.82, 6.99),
+ (43.13, -0.62),
+ (39.15, -7.58),
+ (40.37, -13.20),
+ (37.74, -18.17),
+ (35.33, -22.71),
+ (32.84, -28.15),
+ (26.50, -34.39),
+ (19.55, -35.51),
+ (17.50, -30.88),
+ (12.24, -18.75),
+ (13.89, -12.81),
+ (12.05, -5.55),
+ (9.67, 0.14),
+ (7.19, 3.79),
+ (1.74, 5.39),
+ (-4.77, 4.59),
+ (-12.00, 6.75),
+ (-15.54, 10.98),
+ (-16.33, 15.50),
+ (-16.10, 22.29),
+ (-12.90, 27.12),
+ (-9.52, 31.09),
+ (-5.41, 35.58),
+ (33.71, 0.00),
+ (33.48, -3.42),
+ (33.34, -0.20),
+ (33.71, 0.00),
+ (49.30, -12.50),
+ (49.28, -18.79),
+ (43.95, -25.50),
+ (44.37, -20.08),
+ (46.34, -16.31),
+ (47.91, -14.08),
+ (49.30, -12.50),
+ (178.88, 69.10),
+ (181.20, 68.42),
+ (183.52, 67.78),
+ (188.87, 66.38),
+ (186.54, 64.74),
+ (182.87, 65.63),
+ (180.13, 65.14),
+ (179.48, 64.88),
+ (178.20, 64.29),
+ (177.46, 62.62),
+ (170.42, 60.17),
+ (164.48, 59.89),
+ (162.92, 57.34),
+ (161.82, 54.88),
+ (156.42, 51.09),
+ (156.40, 57.76),
+ (163.79, 61.73),
+ (159.90, 60.73),
+ (156.81, 61.68),
+ (153.83, 59.10),
+ (148.57, 59.46),
+ (140.77, 58.39),
+ (137.10, 54.07),
+ (140.72, 52.43),
+ (138.77, 47.30),
+ (129.92, 42.04),
+ (128.33, 38.46),
+ (126.15, 35.18),
+ (125.12, 39.08),
+ (121.62, 40.15),
+ (117.58, 38.21),
+ (121.77, 36.90),
+ (120.73, 32.65),
+ (121.28, 30.25),
+ (118.83, 24.93),
+ (112.69, 21.81),
+ (108.53, 21.73),
+ (107.55, 16.34),
+ (107.32, 10.45),
+ (104.39, 10.37),
+ (100.01, 13.52),
+ (100.26, 8.30),
+ (103.22, 1.56),
+ (98.21, 9.17),
+ (97.66, 15.36),
+ (94.21, 17.79),
+ (90.05, 21.74),
+ (90.06, 21.03),
+ (82.06, 15.95),
+ (80.05, 11.72),
+ (76.41, 8.60),
+ (72.79, 17.43),
+ (72.02, 20.00),
+ (68.98, 21.99),
+ (64.62, 24.41),
+ (57.83, 24.77),
+ (53.11, 26.20),
+ (49.67, 29.41),
+ (50.96, 25.15),
+ (54.33, 23.44),
+ (59.03, 22.57),
+ (57.87, 18.86),
+ (52.95, 15.74),
+ (47.26, 12.96),
+ (42.75, 14.68),
+ (39.93, 19.61),
+ (36.92, 25.78),
+ (33.30, 28.46),
+ (32.60, 30.63),
+ (32.18, 30.58),
+ (36.08, 35.03),
+ (32.53, 36.17),
+ (27.77, 36.94),
+ (26.51, 39.18),
+ (31.54, 40.82),
+ (38.53, 40.48),
+ (40.35, 43.17),
+ (39.88, 46.45),
+ (35.18, 44.99),
+ (33.50, 44.96),
+ (30.24, 45.14),
+ (28.70, 41.48),
+ (26.55, 39.84),
+ (23.62, 39.67),
+ (23.80, 37.34),
+ (21.90, 36.92),
+ (18.79, 42.02),
+ (14.52, 44.31),
+ (14.58, 42.25),
+ (18.32, 39.57),
+ (16.05, 39.35),
+ (11.52, 42.36),
+ (6.87, 43.08),
+ (2.80, 41.09),
+ (-1.11, 37.14),
+ (-6.24, 36.70),
+ (-8.67, 39.57),
+ (-6.51, 43.13),
+ (-0.84, 45.55),
+ (-3.93, 48.40),
+ (0.48, 49.09),
+ (4.20, 51.29),
+ (6.44, 52.92),
+ (8.42, 55.94),
+ (11.72, 55.49),
+ (11.73, 53.66),
+ (16.78, 54.14),
+ (21.40, 56.32),
+ (24.67, 57.20),
+ (28.94, 59.18),
+ (24.16, 59.52),
+ (22.07, 62.66),
+ (23.76, 65.35),
+ (18.70, 62.54),
+ (19.11, 59.67),
+ (18.40, 58.54),
+ (15.34, 55.73),
+ (11.74, 58.08),
+ (8.37, 57.68),
+ (5.80, 59.20),
+ (7.38, 60.86),
+ (7.51, 61.86),
+ (9.62, 62.99),
+ (13.37, 65.46),
+ (15.46, 67.12),
+ (18.54, 68.62),
+ (22.32, 69.64),
+ (24.77, 70.17),
+ (25.93, 69.79),
+ (28.56, 70.46),
+ (29.75, 69.76),
+ (33.83, 69.11),
+ (41.90, 66.85),
+ (35.14, 66.25),
+ (33.30, 66.07),
+ (35.46, 64.15),
+ (37.68, 64.03),
+ (41.71, 64.09),
+ (44.80, 65.58),
+ (44.87, 68.16),
+ (45.92, 66.83),
+ (51.79, 67.85),
+ (53.70, 67.89),
+ (59.68, 68.09),
+ (65.07, 69.08),
+ (68.56, 69.19),
+ (68.38, 70.97),
+ (73.03, 71.62),
+ (73.80, 68.29),
+ (69.42, 66.45),
+ (73.43, 66.36),
+ (77.51, 68.36),
+ (80.74, 66.74),
+ (75.27, 68.67),
+ (75.11, 71.80),
+ (78.62, 70.56),
+ (78.43, 71.90),
+ (82.72, 71.23),
+ (84.25, 70.03),
+ (81.40, 72.76),
+ (86.50, 74.01),
+ (87.68, 74.78),
+ (90.25, 75.23),
+ (89.68, 75.57),
+ (95.12, 75.95),
+ (99.69, 76.09),
+ (104.10, 77.52),
+ (106.34, 76.40),
+ (112.99, 75.60),
+ (107.88, 73.72),
+ (110.43, 73.71),
+ (113.34, 73.37),
+ (123.10, 73.28),
+ (128.94, 73.02),
+ (126.10, 72.24),
+ (130.53, 70.86),
+ (135.49, 71.51),
+ (139.60, 72.23),
+ (146.04, 72.39),
+ (146.92, 72.21),
+ (150.77, 71.28),
+ (159.92, 70.14),
+ (167.68, 69.63),
+ (170.20, 69.99),
+ (178.88, 69.10),
+ (68.33, 76.71),
+ (66.03, 75.62),
+ (59.10, 74.11),
+ (54.92, 73.03),
+ (56.67, 74.10),
+ (58.56, 75.09),
+ (63.86, 75.87),
+ (68.19, 76.70),
+ (53.04, 72.57),
+ (58.29, 70.39),
+ (55.03, 70.78),
+ (53.44, 72.26),
+ (53.63, 72.61),
+ (52.22, 46.50),
+ (51.73, 44.73),
+ (52.56, 41.80),
+ (53.43, 40.40),
+ (54.22, 37.86),
+ (49.04, 38.45),
+ (48.17, 42.76),
+ (49.33, 45.64),
+ (52.22, 46.50),
+ (62.32, 46.32),
+ (60.32, 43.06),
+ (59.57, 45.58),
+ (61.94, 46.33),
+ (79.55, 46.12),
+ (74.30, 44.44),
+ (78.62, 45.79),
+ (79.66, 46.07),
+ (76.81, 41.96),
+ (76.73, 41.86),
+ (35.15, 35.15),
+ (34.61, 34.84),
+ (35.18, 35.17),
+ (23.84, 35.33),
+ (24.30, 34.91),
+ (24.09, 35.39),
+ (15.54, 37.89),
+ (13.47, 37.89),
+ (15.54, 37.89),
+ (9.56, 40.95),
+ (8.46, 39.99),
+ (9.12, 40.69),
+ (9.72, 42.60),
+ (9.54, 42.35),
+ (80.60, 8.95),
+ (79.73, 5.96),
+ (80.10, 8.30),
+ (11.04, 57.44),
+ (10.67, 57.25),
+ (-77.92, 24.67),
+ (-77.98, 24.22),
+ (-77.61, 23.62),
+ (-77.18, 23.64),
+ (-75.55, 24.13),
+ (-75.41, 24.31),
+ (-91.40, -0.17),
+ (-91.52, -0.26),
+ (-60.25, 46.68),
+ (-60.71, 46.33),
+ (-63.89, 49.47),
+ (-63.45, 49.43),
+ (142.53, -10.60),
+ (145.62, -16.34),
+ (149.79, -22.09),
+ (153.21, -26.82),
+ (150.52, -35.19),
+ (145.60, -38.53),
+ (140.13, -37.69),
+ (137.34, -34.77),
+ (135.76, -34.56),
+ (131.50, -31.34),
+ (121.72, -33.65),
+ (115.62, -33.25),
+ (114.09, -26.01),
+ (114.88, -21.27),
+ (122.34, -18.13),
+ (125.32, -14.53),
+ (128.39, -14.90),
+ (132.35, -11.42),
+ (136.16, -12.43),
+ (138.07, -16.45),
+ (142.25, -10.78),
+ (144.72, -40.68),
+ (148.32, -42.14),
+ (145.57, -42.77),
+ (146.47, -41.19),
+ (172.86, -34.23),
+ (176.10, -37.52),
+ (177.06, -39.49),
+ (174.77, -38.03),
+ (172.83, -34.27),
+ (172.36, -40.53),
+ (172.92, -43.81),
+ (168.41, -46.13),
+ (170.26, -43.21),
+ (173.69, -40.94),
+ (150.74, -10.18),
+ (143.04, -8.26),
+ (138.48, -6.97),
+ (131.95, -2.94),
+ (130.91, -1.35),
+ (134.38, -2.64),
+ (141.24, -2.62),
+ (148.19, -8.15),
+ (150.75, -10.27),
+ (117.24, 7.01),
+ (117.90, 0.76),
+ (113.89, -3.50),
+ (109.44, -0.82),
+ (113.13, 3.38),
+ (117.24, 7.01),
+ (95.31, 5.75),
+ (102.32, 1.40),
+ (106.03, -2.98),
+ (101.46, -2.81),
+ (95.20, 5.73),
+ (140.91, 41.53),
+ (140.79, 35.75),
+ (136.82, 34.56),
+ (133.56, 34.72),
+ (132.49, 35.41),
+ (136.73, 37.20),
+ (139.82, 40.00),
+ (140.68, 41.43),
+ (133.71, 34.30),
+ (131.41, 31.58),
+ (129.38, 33.10),
+ (133.90, 34.37),
+ (141.89, 45.50),
+ (144.12, 42.92),
+ (140.30, 41.64),
+ (141.53, 45.30),
+ (141.89, 45.53),
+ (142.57, 54.36),
+ (143.64, 49.19),
+ (141.99, 45.88),
+ (141.92, 50.85),
+ (142.60, 54.34),
+ (121.92, 25.48),
+ (120.53, 24.70),
+ (121.70, 25.51),
+ (110.81, 20.07),
+ (109.20, 19.66),
+ (110.81, 20.07),
+ (106.51, -6.16),
+ (114.15, -7.72),
+ (108.71, -7.89),
+ (106.51, -6.16),
+ (164.27, -20.01),
+ (164.16, -20.27),
+ (178.61, -17.04),
+ (178.61, -17.04),
+ (179.45, -16.43),
+ (179.35, -16.43),
+ (-172.55, -13.39),
+ (-172.61, -13.78),
+ (122.26, 18.67),
+ (123.05, 13.86),
+ (120.73, 13.80),
+ (120.43, 16.43),
+ (121.72, 18.40),
+ (125.34, 9.79),
+ #[allow(clippy::approx_constant)]
+ (125.56, 6.28),
+ (122.38, 7.00),
+ (125.10, 9.38),
+ (119.64, 11.35),
+ (118.81, 10.16),
+ (119.59, 10.86),
+ (119.64, 11.35),
+ (-179.87, 65.14),
+ (-177.13, 65.63),
+ (-173.46, 64.74),
+ (-171.13, 66.38),
+ (-176.48, 67.78),
+ (-178.80, 68.42),
+ (101.96, 79.08),
+ (101.31, 77.86),
+ (101.22, 79.04),
+ (94.29, 79.29),
+ (95.31, 78.68),
+ (100.02, 79.43),
+ (97.26, 79.62),
+ (95.44, 79.65),
+ (95.46, 80.62),
+ (92.39, 79.66),
+ (95.07, 80.54),
+ (138.54, 76.05),
+ (144.93, 75.45),
+ (140.30, 74.99),
+ (137.27, 75.44),
+ (138.29, 75.98),
+ (146.08, 75.29),
+ (147.75, 74.73),
+ (145.85, 75.06),
+ (141.44, 73.88),
+ (141.48, 73.84),
+ (0.01, -71.68),
+ (6.57, -70.57),
+ (15.04, -70.44),
+ (25.10, -70.75),
+ (33.37, -69.10),
+ (38.46, -69.77),
+ (42.85, -68.16),
+ (46.59, -67.23),
+ (49.35, -66.96),
+ (52.90, -65.97),
+ (58.46, -67.20),
+ (63.60, -67.58),
+ (70.63, -68.41),
+ (69.24, -70.36),
+ (76.20, -69.44),
+ (88.08, -66.64),
+ (94.98, -66.52),
+ (101.53, -66.09),
+ (111.31, -65.91),
+ (118.64, -66.87),
+ (126.24, -66.24),
+ (133.09, -66.18),
+ (139.85, -66.72),
+ (146.86, -67.96),
+ (153.65, -68.82),
+ (159.94, -69.57),
+ (164.10, -70.67),
+ (170.19, -71.94),
+ (165.68, -74.64),
+ (163.82, -77.60),
+ (162.10, -78.95),
+ (166.72, -82.84),
+ (175.58, -83.86),
+ (-178.56, -84.37),
+ (-147.96, -85.40),
+ (-152.96, -81.12),
+ (-153.95, -79.50),
+ (-151.24, -77.48),
+ (-146.74, -76.44),
+ (-137.68, -75.16),
+ (-131.63, -74.63),
+ (-123.05, -74.41),
+ (-114.76, -73.97),
+ (-111.91, -75.41),
+ (-105.05, -74.77),
+ (-100.90, -74.21),
+ (-101.04, -73.18),
+ (-100.28, -73.06),
+ (-93.06, -73.33),
+ (-85.40, -73.18),
+ (-79.82, -73.04),
+ (-78.21, -72.52),
+ (-71.90, -73.41),
+ (-67.51, -71.10),
+ (-67.57, -68.92),
+ (-66.65, -66.83),
+ (-64.30, -65.28),
+ (-59.14, -63.74),
+ (-59.58, -64.37),
+ (-62.50, -65.94),
+ (-62.48, -66.66),
+ (-65.64, -68.02),
+ (-63.85, -69.07),
+ (-61.69, -70.87),
+ (-60.89, -72.71),
+ (-61.07, -74.30),
+ (-63.33, -75.88),
+ (-76.05, -77.06),
+ (-83.04, -77.12),
+ (-74.30, -80.83),
+ (-56.40, -82.14),
+ (-42.46, -81.65),
+ (-31.60, -80.17),
+ (-34.01, -79.20),
+ (-32.48, -77.28),
+ (-26.28, -76.18),
+ (-17.18, -73.45),
+ (-11.20, -72.01),
+ (-8.67, -71.98),
+ (-5.45, -71.45),
+ (-0.82, -71.74),
+ (0.07, -71.68),
+ (164.65, -77.89),
+ (170.95, -77.37),
+ (179.67, -78.25),
+ (-178.74, -78.24),
+ (-165.76, -78.47),
+ (-158.42, -77.73),
+ (-58.98, -64.63),
+ (-60.99, -68.62),
+ (-61.02, -71.70),
+ (-62.01, -74.94),
+ (-52.00, -77.07),
+ (-42.23, -77.80),
+ (-36.22, -78.03),
+ (-35.03, -77.81),
+ (-26.13, -75.54),
+ (-19.35, -73.04),
+ (-12.16, -71.86),
+ (-6.15, -70.65),
+ (-0.57, -69.14),
+ (4.93, -70.25),
+ (10.91, -69.99),
+ (16.52, -69.87),
+ (25.41, -70.22),
+ (32.13, -69.29),
+ (33.62, -69.58),
+ (70.56, -68.53),
+ (73.91, -69.51),
+ (81.42, -67.87),
+ (84.67, -66.41),
+ (89.07, -66.73),
+ (-135.79, -74.67),
+ (-124.34, -73.22),
+ (-116.65, -74.08),
+ (-109.93, -74.64),
+ (-105.36, -74.56),
+ (-105.83, -74.77),
+ (-69.30, -70.06),
+ (-71.33, -72.68),
+ (-71.42, -71.85),
+ (-75.10, -71.46),
+ (-71.79, -70.55),
+ (-70.34, -69.26),
+ (-69.34, -70.13),
+ (-49.20, -77.83),
+ (-44.59, -78.79),
+ (-44.14, -80.13),
+ (-59.04, -79.95),
+ (-49.28, -77.84),
+ (-48.24, -77.81),
+ (-58.13, -80.12),
+ (-63.25, -80.20),
+ (-58.32, -80.12),
+ (-163.64, -78.74),
+ (-161.20, -79.93),
+ (-163.62, -78.74),
+ (66.82, 66.82),
+ (66.82, 66.82),
+];
diff --git a/src/ratatui/widgets/chart.rs b/src/ratatui/widgets/chart.rs
new file mode 100644
index 00000000..a57a392b
--- /dev/null
+++ b/src/ratatui/widgets/chart.rs
@@ -0,0 +1,660 @@
+use std::{borrow::Cow, cmp::max};
+
+use unicode_width::UnicodeWidthStr;
+
+use crate::ratatui::layout::Alignment;
+use crate::ratatui::{
+ buffer::Buffer,
+ layout::{Constraint, Rect},
+ style::{Color, Style},
+ symbols,
+ text::{Span, Spans},
+ widgets::{
+ canvas::{Canvas, Line, Points},
+ Block, Borders, Widget,
+ },
+};
+
+/// An X or Y axis for the chart widget
+#[derive(Debug, Clone)]
+pub struct Axis<'a> {
+ /// Title displayed next to axis end
+ title: Option<Spans<'a>>,
+ /// Bounds for the axis (all data points outside these limits will not be represented)
+ bounds: [f64; 2],
+ /// A list of labels to put to the left or below the axis
+ labels: Option<Vec<Span<'a>>>,
+ /// The style used to draw the axis itself
+ style: Style,
+ /// The alignment of the labels of the Axis
+ labels_alignment: Alignment,
+}
+
+impl<'a> Default for Axis<'a> {
+ fn default() -> Axis<'a> {
+ Axis {
+ title: None,
+ bounds: [0.0, 0.0],
+ labels: None,
+ style: Default::default(),
+ labels_alignment: Alignment::Left,
+ }
+ }
+}
+
+impl<'a> Axis<'a> {
+ pub fn title<T>(mut self, title: T) -> Axis<'a>
+ where
+ T: Into<Spans<'a>>,
+ {
+ self.title = Some(title.into());
+ self
+ }
+
+ #[deprecated(
+ since = "0.10.0",
+ note = "You should use styling capabilities of `text::Spans` given as argument of the `title` method to apply styling to the title."
+ )]
+ pub fn title_style(mut self, style: Style) -> Axis<'a> {
+ if let Some(t) = self.title {
+ let title = String::from(t);
+ self.title = Some(Spans::from(Span::styled(title, style)));
+ }
+ self
+ }
+
+ pub fn bounds(mut self, bounds: [f64; 2]) -> Axis<'a> {
+ self.bounds = bounds;
+ self
+ }
+
+ pub fn labels(mut self, labels: Vec<Span<'a>>) -> Axis<'a> {
+ self.labels = Some(labels);
+ self
+ }
+
+ pub fn style(mut self, style: Style) -> Axis<'a> {
+ self.style = style;
+ self
+ }
+
+ /// Defines the alignment of the labels of the axis.
+ /// The alignment behaves differently based on the axis:
+ /// - Y-Axis: The labels are aligned within the area on the left of the axis
+ /// - X-Axis: The first X-axis label is aligned relative to the Y-axis
+ pub fn labels_alignment(mut self, alignment: Alignment) -> Axis<'a> {
+ self.labels_alignment = alignment;
+ self
+ }
+}
+
+/// Used to determine which style of graphing to use
+#[derive(Debug, Clone, Copy)]
+pub enum GraphType {
+ /// Draw each point
+ Scatter,
+ /// Draw each point and lines between each point using the same marker
+ Line,
+}
+
+/// A group of data points
+#[derive(Debug, Clone)]
+pub struct Dataset<'a> {
+ /// Name of the dataset (used in the legend if shown)
+ name: Cow<'a, str>,
+ /// A reference to the actual data
+ data: &'a [(f64, f64)],
+ /// Symbol used for each points of this dataset
+ marker: symbols::Marker,
+ /// Determines graph type used for drawing points
+ graph_type: GraphType,
+ /// Style used to plot this dataset
+ style: Style,
+}
+
+impl<'a> Default for Dataset<'a> {
+ fn default() -> Dataset<'a> {
+ Dataset {
+ name: Cow::from(""),
+ data: &[],
+ marker: symbols::Marker::Dot,
+ graph_type: GraphType::Scatter,
+ style: Style::default(),
+ }
+ }
+}
+
+impl<'a> Dataset<'a> {
+ pub fn name<S>(mut self, name: S) -> Dataset<'a>
+ where
+ S: Into<Cow<'a, str>>,
+ {
+ self.name = name.into();
+ self
+ }
+
+ pub fn data(mut self, data: &'a [(f64, f64)]) -> Dataset<'a> {
+ self.data = data;
+ self
+ }
+
+ pub fn marker(mut self, marker: symbols::Marker) -> Dataset<'a> {
+ self.marker = marker;
+ self
+ }
+
+ pub fn graph_type(mut self, graph_type: GraphType) -> Dataset<'a> {
+ self.graph_type = graph_type;
+ self
+ }
+
+ pub fn style(mut self, style: Style) -> Dataset<'a> {
+ self.style = style;
+ self
+ }
+}
+
+/// A container that holds all the infos about where to display each elements of the chart (axis,
+/// labels, legend, ...).
+#[derive(Debug, Clone, PartialEq, Default)]
+struct ChartLayout {
+ /// Location of the title of the x axis
+ title_x: Option<(u16, u16)>,
+ /// Location of the title of the y axis
+ title_y: Option<(u16, u16)>,
+ /// Location of the first label of the x axis
+ label_x: Option<u16>,
+ /// Location of the first label of the y axis
+ label_y: Option<u16>,
+ /// Y coordinate of the horizontal axis
+ axis_x: Option<u16>,
+ /// X coordinate of the vertical axis
+ axis_y: Option<u16>,
+ /// Area of the legend
+ legend_area: Option<Rect>,
+ /// Area of the graph
+ graph_area: Rect,
+}
+
+/// A widget to plot one or more dataset in a cartesian coordinate system
+///
+/// # Examples
+///
+/// ```
+/// # use ratatui::symbols;
+/// # use ratatui::widgets::{Block, Borders, Chart, Axis, Dataset, GraphType};
+/// # use ratatui::style::{Style, Color};
+/// # use ratatui::text::Span;
+/// let datasets = vec![
+/// Dataset::default()
+/// .name("data1")
+/// .marker(symbols::Marker::Dot)
+/// .graph_type(GraphType::Scatter)
+/// .style(Style::default().fg(Color::Cyan))
+/// .data(&[(0.0, 5.0), (1.0, 6.0), (1.5, 6.434)]),
+/// Dataset::default()
+/// .name("data2")
+/// .marker(symbols::Marker::Braille)
+/// .graph_type(GraphType::Line)
+/// .style(Style::default().fg(Color::Magenta))
+/// .data(&[(4.0, 5.0), (5.0, 8.0), (7.66, 13.5)]),
+/// ];
+/// Chart::new(datasets)
+/// .block(Block::default().title("Chart"))
+/// .x_axis(Axis::default()
+/// .title(Span::styled("X Axis", Style::default().fg(Color::Red)))
+/// .style(Style::default().fg(Color::White))
+/// .bounds([0.0, 10.0])
+/// .labels(["0.0", "5.0", "10.0"].iter().cloned().map(Span::from).collect()))
+/// .y_axis(Axis::default()
+/// .title(Span::styled("Y Axis", Style::default().fg(Color::Red)))
+/// .style(Style::default().fg(Color::White))
+/// .bounds([0.0, 10.0])
+/// .labels(["0.0", "5.0", "10.0"].iter().cloned().map(Span::from).collect()));
+/// ```
+#[derive(Debug, Clone)]
+pub struct Chart<'a> {
+ /// A block to display around the widget eventually
+ block: Option<Block<'a>>,
+ /// The horizontal axis
+ x_axis: Axis<'a>,
+ /// The vertical axis
+ y_axis: Axis<'a>,
+ /// A reference to the datasets
+ datasets: Vec<Dataset<'a>>,
+ /// The widget base style
+ style: Style,
+ /// Constraints used to determine whether the legend should be shown or not
+ hidden_legend_constraints: (Constraint, Constraint),
+}
+
+impl<'a> Chart<'a> {
+ pub fn new(datasets: Vec<Dataset<'a>>) -> Chart<'a> {
+ Chart {
+ block: None,
+ x_axis: Axis::default(),
+ y_axis: Axis::default(),
+ style: Default::default(),
+ datasets,
+ hidden_legend_constraints: (Constraint::Ratio(1, 4), Constraint::Ratio(1, 4)),
+ }
+ }
+
+ pub fn block(mut self, block: Block<'a>) -> Chart<'a> {
+ self.block = Some(block);
+ self
+ }
+
+ pub fn style(mut self, style: Style) -> Chart<'a> {
+ self.style = style;
+ self
+ }
+
+ pub fn x_axis(mut self, axis: Axis<'a>) -> Chart<'a> {
+ self.x_axis = axis;
+ self
+ }
+
+ pub fn y_axis(mut self, axis: Axis<'a>) -> Chart<'a> {
+ self.y_axis = axis;
+ self
+ }
+
+ /// Set the constraints used to determine whether the legend should be shown or not.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use ratatui::widgets::Chart;
+ /// # use ratatui::layout::Constraint;
+ /// let constraints = (
+ /// Constraint::Ratio(1, 3),
+ /// Constraint::Ratio(1, 4)
+ /// );
+ /// // Hide the legend when either its width is greater than 33% of the total widget width
+ /// // or if its height is greater than 25% of the total widget height.
+ /// let _chart: Chart = Chart::new(vec![])
+ /// .hidden_legend_constraints(constraints);
+ /// ```
+ pub fn hidden_legend_constraints(mut self, constraints: (Constraint, Constraint)) -> Chart<'a> {
+ self.hidden_legend_constraints = constraints;
+ self
+ }
+
+ /// Compute the internal layout of the chart given the area. If the area is too small some
+ /// elements may be automatically hidden
+ fn layout(&self, area: Rect) -> ChartLayout {
+ let mut layout = ChartLayout::default();
+ if area.height == 0 || area.width == 0 {
+ return layout;
+ }
+ let mut x = area.left();
+ let mut y = area.bottom() - 1;
+
+ if self.x_axis.labels.is_some() && y > area.top() {
+ layout.label_x = Some(y);
+ y -= 1;
+ }
+
+ layout.label_y = self.y_axis.labels.as_ref().and(Some(x));
+ x += self.max_width_of_labels_left_of_y_axis(area, self.y_axis.labels.is_some());
+
+ if self.x_axis.labels.is_some() && y > area.top() {
+ layout.axis_x = Some(y);
+ y -= 1;
+ }
+
+ if self.y_axis.labels.is_some() && x + 1 < area.right() {
+ layout.axis_y = Some(x);
+ x += 1;
+ }
+
+ if x < area.right() && y > 1 {
+ layout.graph_area = Rect::new(x, area.top(), area.right() - x, y - area.top() + 1);
+ }
+
+ if let Some(ref title) = self.x_axis.title {
+ let w = title.width() as u16;
+ if w < layout.graph_area.width && layout.graph_area.height > 2 {
+ layout.title_x = Some((x + layout.graph_area.width - w, y));
+ }
+ }
+
+ if let Some(ref title) = self.y_axis.title {
+ let w = title.width() as u16;
+ if w + 1 < layout.graph_area.width && layout.graph_area.height > 2 {
+ layout.title_y = Some((x, area.top()));
+ }
+ }
+
+ if let Some(inner_width) = self.datasets.iter().map(|d| d.name.width() as u16).max() {
+ let legend_width = inner_width + 2;
+ let legend_height = self.datasets.len() as u16 + 2;
+ let max_legend_width = self
+ .hidden_legend_constraints
+ .0
+ .apply(layout.graph_area.width);
+ let max_legend_height = self
+ .hidden_legend_constraints
+ .1
+ .apply(layout.graph_area.height);
+ if inner_width > 0
+ && legend_width < max_legend_width
+ && legend_height < max_legend_height
+ {
+ layout.legend_area = Some(Rect::new(
+ layout.graph_area.right() - legend_width,
+ layout.graph_area.top(),
+ legend_width,
+ legend_height,
+ ));
+ }
+ }
+ layout
+ }
+
+ fn max_width_of_labels_left_of_y_axis(&self, area: Rect, has_y_axis: bool) -> u16 {
+ let mut max_width = self
+ .y_axis
+ .labels
+ .as_ref()
+ .map(|l| l.iter().map(Span::width).max().unwrap_or_default() as u16)
+ .unwrap_or_default();
+
+ if let Some(first_x_label) = self.x_axis.labels.as_ref().and_then(|labels| labels.get(0)) {
+ let first_label_width = first_x_label.content.width() as u16;
+ let width_left_of_y_axis = match self.x_axis.labels_alignment {
+ Alignment::Left => {
+ // The last character of the label should be below the Y-Axis when it exists, not on its left
+ let y_axis_offset = if has_y_axis { 1 } else { 0 };
+ first_label_width.saturating_sub(y_axis_offset)
+ }
+ Alignment::Center => first_label_width / 2,
+ Alignment::Right => 0,
+ };
+ max_width = max(max_width, width_left_of_y_axis);
+ }
+ // labels of y axis and first label of x axis can take at most 1/3rd of the total width
+ max_width.min(area.width / 3)
+ }
+
+ fn render_x_labels(
+ &mut self,
+ buf: &mut Buffer,
+ layout: &ChartLayout,
+ chart_area: Rect,
+ graph_area: Rect,
+ ) {
+ let y = match layout.label_x {
+ Some(y) => y,
+ None => return,
+ };
+ let labels = self.x_axis.labels.as_ref().unwrap();
+ let labels_len = labels.len() as u16;
+ if labels_len < 2 {
+ return;
+ }
+
+ let width_between_ticks = graph_area.width / labels_len;
+
+ let label_area = self.first_x_label_area(
+ y,
+ labels.first().unwrap().width() as u16,
+ width_between_ticks,
+ chart_area,
+ graph_area,
+ );
+
+ let label_alignment = match self.x_axis.labels_alignment {
+ Alignment::Left => Alignment::Right,
+ Alignment::Center => Alignment::Center,
+ Alignment::Right => Alignment::Left,
+ };
+
+ Self::render_label(buf, labels.first().unwrap(), label_area, label_alignment);
+
+ for (i, label) in labels[1..labels.len() - 1].iter().enumerate() {
+ // We add 1 to x (and width-1 below) to leave at least one space before each intermediate labels
+ let x = graph_area.left() + (i + 1) as u16 * width_between_ticks + 1;
+ let label_area = Rect::new(x, y, width_between_ticks.saturating_sub(1), 1);
+
+ Self::render_label(buf, label, label_area, Alignment::Center);
+ }
+
+ let x = graph_area.right() - width_between_ticks;
+ let label_area = Rect::new(x, y, width_between_ticks, 1);
+ // The last label should be aligned Right to be at the edge of the graph area
+ Self::render_label(buf, labels.last().unwrap(), label_area, Alignment::Right);
+ }
+
+ fn first_x_label_area(
+ &self,
+ y: u16,
+ label_width: u16,
+ max_width_after_y_axis: u16,
+ chart_area: Rect,
+ graph_area: Rect,
+ ) -> Rect {
+ let (min_x, max_x) = match self.x_axis.labels_alignment {
+ Alignment::Left => (chart_area.left(), graph_area.left()),
+ Alignment::Center => (
+ chart_area.left(),
+ graph_area.left() + max_width_after_y_axis.min(label_width),
+ ),
+ Alignment::Right => (
+ graph_area.left().saturating_sub(1),
+ graph_area.left() + max_width_after_y_axis,
+ ),
+ };
+
+ Rect::new(min_x, y, max_x - min_x, 1)
+ }
+
+ fn render_label(buf: &mut Buffer, label: &Span, label_area: Rect, alignment: Alignment) {
+ let label_width = label.width() as u16;
+ let bounded_label_width = label_area.width.min(label_width);
+
+ let x = match alignment {
+ Alignment::Left => label_area.left(),
+ Alignment::Center => label_area.left() + label_area.width / 2 - bounded_label_width / 2,
+ Alignment::Right => label_area.right() - bounded_label_width,
+ };
+
+ buf.set_span(x, label_area.top(), label, bounded_label_width);
+ }
+
+ fn render_y_labels(
+ &mut self,
+ buf: &mut Buffer,
+ layout: &ChartLayout,
+ chart_area: Rect,
+ graph_area: Rect,
+ ) {
+ let x = match layout.label_y {
+ Some(x) => x,
+ None => return,
+ };
+ let labels = self.y_axis.labels.as_ref().unwrap();
+ let labels_len = labels.len() as u16;
+ for (i, label) in labels.iter().enumerate() {
+ let dy = i as u16 * (graph_area.height - 1) / (labels_len - 1);
+ if dy < graph_area.bottom() {
+ let label_area = Rect::new(
+ x,
+ graph_area.bottom().saturating_sub(1) - dy,
+ (graph_area.left() - chart_area.left()).saturating_sub(1),
+ 1,
+ );
+ Self::render_label(buf, label, label_area, self.y_axis.labels_alignment);
+ }
+ }
+ }
+}
+
+impl<'a> Widget for Chart<'a> {
+ fn render(mut self, area: Rect, buf: &mut Buffer) {
+ if area.area() == 0 {
+ return;
+ }
+ buf.set_style(area, self.style);
+ // Sample the style of the entire widget. This sample will be used to reset the style of
+ // the cells that are part of the components put on top of the grah area (i.e legend and
+ // axis names).
+ let original_style = buf.get(area.left(), area.top()).style();
+
+ let chart_area = match self.block.take() {
+ Some(b) => {
+ let inner_area = b.inner(area);
+ b.render(area, buf);
+ inner_area
+ }
+ None => area,
+ };
+
+ let layout = self.layout(chart_area);
+ let graph_area = layout.graph_area;
+ if graph_area.width < 1 || graph_area.height < 1 {
+ return;
+ }
+
+ self.render_x_labels(buf, &layout, chart_area, graph_area);
+ self.render_y_labels(buf, &layout, chart_area, graph_area);
+
+ if let Some(y) = layout.axis_x {
+ for x in graph_area.left()..graph_area.right() {
+ buf.get_mut(x, y)
+ .set_symbol(symbols::line::HORIZONTAL)
+ .set_style(self.x_axis.style);
+ }
+ }
+
+ if let Some(x) = layout.axis_y {
+ for y in graph_area.top()..graph_area.bottom() {
+ buf.get_mut(x, y)
+ .set_symbol(symbols::line::VERTICAL)
+ .set_style(self.y_axis.style);
+ }
+ }
+
+ if let Some(y) = layout.axis_x {
+ if let Some(x) = layout.axis_y {
+ buf.get_mut(x, y)
+ .set_symbol(symbols::line::BOTTOM_LEFT)
+ .set_style(self.x_axis.style);
+ }
+ }
+
+ for dataset in &self.datasets {
+ Canvas::default()
+ .background_color(self.style.bg.unwrap_or(Color::Reset))
+ .x_bounds(self.x_axis.bounds)
+ .y_bounds(self.y_axis.bounds)
+ .marker(dataset.marker)
+ .paint(|ctx| {
+ ctx.draw(&Points {
+ coords: dataset.data,
+ color: dataset.style.fg.unwrap_or(Color::Reset),
+ });
+ if let GraphType::Line = dataset.graph_type {
+ for data in dataset.data.windows(2) {
+ ctx.draw(&Line {
+ x1: data[0].0,
+ y1: data[0].1,
+ x2: data[1].0,
+ y2: data[1].1,
+ color: dataset.style.fg.unwrap_or(Color::Reset),
+ })
+ }
+ }
+ })
+ .render(graph_area, buf);
+ }
+
+ if let Some(legend_area) = layout.legend_area {
+ buf.set_style(legend_area, original_style);
+ Block::default()
+ .borders(Borders::ALL)
+ .render(legend_area, buf);
+ for (i, dataset) in self.datasets.iter().enumerate() {
+ buf.set_string(
+ legend_area.x + 1,
+ legend_area.y + 1 + i as u16,
+ &dataset.name,
+ dataset.style,
+ );
+ }
+ }
+
+ if let Some((x, y)) = layout.title_x {
+ let title = self.x_axis.title.unwrap();
+ let width = graph_area.right().saturating_sub(x);
+ buf.set_style(
+ Rect {
+ x,
+ y,
+ width,
+ height: 1,
+ },
+ original_style,
+ );
+ buf.set_spans(x, y, &title, width);
+ }
+
+ if let Some((x, y)) = layout.title_y {
+ let title = self.y_axis.title.unwrap();
+ let width = graph_area.right().saturating_sub(x);
+ buf.set_style(
+ Rect {
+ x,
+ y,
+ width,
+ height: 1,
+ },
+ original_style,
+ );
+ buf.set_spans(x, y, &title, width);
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ struct LegendTestCase {
+ chart_area: Rect,
+ hidden_legend_constraints: (Constraint, Constraint),
+ legend_area: Option<Rect>,
+ }
+
+ #[test]
+ fn it_should_hide_the_legend() {
+ let data = [(0.0, 5.0), (1.0, 6.0), (3.0, 7.0)];
+ let cases = [
+ LegendTestCase {
+ chart_area: Rect::new(0, 0, 100, 100),
+ hidden_legend_constraints: (Constraint::Ratio(1, 4), Constraint::Ratio(1, 4)),
+ legend_area: Some(Rect::new(88, 0, 12, 12)),
+ },
+ LegendTestCase {
+ chart_area: Rect::new(0, 0, 100, 100),
+ hidden_legend_constraints: (Constraint::Ratio(1, 10), Constraint::Ratio(1, 4)),
+ legend_area: None,
+ },
+ ];
+ for case in &cases {
+ let datasets = (0..10)
+ .map(|i| {
+ let name = format!("Dataset #{}", i);
+ Dataset::default().name(name).data(&data)
+ })
+ .collect::<Vec<_>>();
+ let chart = Chart::new(datasets)
+ .x_axis(Axis::default().title("X axis"))
+ .y_axis(Axis::default().title("Y axis"))
+ .hidden_legend_constraints(case.hidden_legend_constraints);
+ let layout = chart.layout(case.chart_area);
+ assert_eq!(layout.legend_area, case.legend_area);
+ }
+ }
+}
diff --git a/src/ratatui/widgets/clear.rs b/src/ratatui/widgets/clear.rs
new file mode 100644
index 00000000..a7c8d34b
--- /dev/null
+++ b/src/ratatui/widgets/clear.rs
@@ -0,0 +1,37 @@
+use crate::ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
+
+/// A widget to clear/reset a certain area to allow overdrawing (e.g. for popups).
+///
+/// This widget **cannot be used to clear the terminal on the first render** as `ratatui` assumes the
+/// render area is empty. Use [`crate::Terminal::clear`] instead.
+///
+/// # Examples
+///
+/// ```
+/// # use ratatui::widgets::{Clear, Block, Borders};
+/// # use ratatui::layout::Rect;
+/// # use ratatui::Frame;
+/// # use ratatui::backend::Backend;
+/// fn draw_on_clear<B: Backend>(f: &mut Frame<B>, area: Rect) {
+/// let block = Block::default().title("Block").borders(Borders::ALL);
+/// f.render_widget(Clear, area); // <- this will clear/reset the area first
+/// f.render_widget(block, area); // now render the block widget
+/// }
+/// ```
+///
+/// # Popup Example
+///
+/// For a more complete example how to utilize `Clear` to realize popups see
+/// the example `examples/popup.rs`
+#[derive(Debug, Clone)]
+pub struct Clear;
+
+impl Widget for Clear {
+ fn render(self, area: Rect, buf: &mut Buffer) {
+ for x in area.left()..area.right() {
+ for y in area.top()..area.bottom() {
+ buf.get_mut(x, y).reset();
+ }
+ }
+ }
+}
diff --git a/src/ratatui/widgets/gauge.rs b/src/ratatui/widgets/gauge.rs
new file mode 100644
index 00000000..6b53b975
--- /dev/null
+++ b/src/ratatui/widgets/gauge.rs
@@ -0,0 +1,313 @@
+use crate::ratatui::{
+ buffer::Buffer,
+ layout::Rect,
+ style::{Color, Style},
+ symbols,
+ text::{Span, Spans},
+ widgets::{Block, Widget},
+};
+
+/// A widget to display a task progress.
+///
+/// # Examples:
+///
+/// ```
+/// # use ratatui::widgets::{Widget, Gauge, Block, Borders};
+/// # use ratatui::style::{Style, Color, Modifier};
+/// Gauge::default()
+/// .block(Block::default().borders(Borders::ALL).title("Progress"))
+/// .gauge_style(Style::default().fg(Color::White).bg(Color::Black).add_modifier(Modifier::ITALIC))
+/// .percent(20);
+/// ```
+#[derive(Debug, Clone)]
+pub struct Gauge<'a> {
+ block: Option<Block<'a>>,
+ ratio: f64,
+ label: Option<Span<'a>>,
+ use_unicode: bool,
+ style: Style,
+ gauge_style: Style,
+}
+
+impl<'a> Default for Gauge<'a> {
+ fn default() -> Gauge<'a> {
+ Gauge {
+ block: None,
+ ratio: 0.0,
+ label: None,
+ use_unicode: false,
+ style: Style::default(),
+ gauge_style: Style::default(),
+ }
+ }
+}
+
+impl<'a> Gauge<'a> {
+ pub fn block(mut self, block: Block<'a>) -> Gauge<'a> {
+ self.block = Some(block);
+ self
+ }
+
+ pub fn percent(mut self, percent: u16) -> Gauge<'a> {
+ assert!(
+ percent <= 100,
+ "Percentage should be between 0 and 100 inclusively."
+ );
+ self.ratio = f64::from(percent) / 100.0;
+ self
+ }
+
+ /// Sets ratio ([0.0, 1.0]) directly.
+ pub fn ratio(mut self, ratio: f64) -> Gauge<'a> {
+ assert!(
+ (0.0..=1.0).contains(&ratio),
+ "Ratio should be between 0 and 1 inclusively."
+ );
+ self.ratio = ratio;
+ self
+ }
+
+ pub fn label<T>(mut self, label: T) -> Gauge<'a>
+ where
+ T: Into<Span<'a>>,
+ {
+ self.label = Some(label.into());
+ self
+ }
+
+ pub fn style(mut self, style: Style) -> Gauge<'a> {
+ self.style = style;
+ self
+ }
+
+ pub fn gauge_style(mut self, style: Style) -> Gauge<'a> {
+ self.gauge_style = style;
+ self
+ }
+
+ pub fn use_unicode(mut self, unicode: bool) -> Gauge<'a> {
+ self.use_unicode = unicode;
+ self
+ }
+}
+
+impl<'a> Widget for Gauge<'a> {
+ fn render(mut self, area: Rect, buf: &mut Buffer) {
+ buf.set_style(area, self.style);
+ let gauge_area = match self.block.take() {
+ Some(b) => {
+ let inner_area = b.inner(area);
+ b.render(area, buf);
+ inner_area
+ }
+ None => area,
+ };
+ buf.set_style(gauge_area, self.gauge_style);
+ if gauge_area.height < 1 {
+ return;
+ }
+
+ // compute label value and its position
+ // label is put at the center of the gauge_area
+ let label = {
+ let pct = f64::round(self.ratio * 100.0);
+ self.label
+ .unwrap_or_else(|| Span::from(format!("{}%", pct)))
+ };
+ let clamped_label_width = gauge_area.width.min(label.width() as u16);
+ let label_col = gauge_area.left() + (gauge_area.width - clamped_label_width) / 2;
+ let label_row = gauge_area.top() + gauge_area.height / 2;
+
+ // the gauge will be filled proportionally to the ratio
+ let filled_width = f64::from(gauge_area.width) * self.ratio;
+ let end = if self.use_unicode {
+ gauge_area.left() + filled_width.floor() as u16
+ } else {
+ gauge_area.left() + filled_width.round() as u16
+ };
+ for y in gauge_area.top()..gauge_area.bottom() {
+ // render the filled area (left to end)
+ for x in gauge_area.left()..end {
+ // spaces are needed to apply the background styling
+ buf.get_mut(x, y)
+ .set_symbol(" ")
+ .set_fg(self.gauge_style.bg.unwrap_or(Color::Reset))
+ .set_bg(self.gauge_style.fg.unwrap_or(Color::Reset));
+ }
+ if self.use_unicode && self.ratio < 1.0 {
+ buf.get_mut(end, y)
+ .set_symbol(get_unicode_block(filled_width % 1.0));
+ }
+ }
+ // set the span
+ buf.set_span(label_col, label_row, &label, clamped_label_width);
+ }
+}
+
+fn get_unicode_block<'a>(frac: f64) -> &'a str {
+ match (frac * 8.0).round() as u16 {
+ 1 => symbols::block::ONE_EIGHTH,
+ 2 => symbols::block::ONE_QUARTER,
+ 3 => symbols::block::THREE_EIGHTHS,
+ 4 => symbols::block::HALF,
+ 5 => symbols::block::FIVE_EIGHTHS,
+ 6 => symbols::block::THREE_QUARTERS,
+ 7 => symbols::block::SEVEN_EIGHTHS,
+ 8 => symbols::block::FULL,
+ _ => " ",
+ }
+}
+
+/// A compact widget to display a task progress over a single line.
+///
+/// # Examples:
+///
+/// ```
+/// # use ratatui::widgets::{Widget, LineGauge, Block, Borders};
+/// # use ratatui::style::{Style, Color, Modifier};
+/// # use ratatui::symbols;
+/// LineGauge::default()
+/// .block(Block::default().borders(Borders::ALL).title("Progress"))
+/// .gauge_style(Style::default().fg(Color::White).bg(Color::Black).add_modifier(Modifier::BOLD))
+/// .line_set(symbols::line::THICK)
+/// .ratio(0.4);
+/// ```
+pub struct LineGauge<'a> {
+ block: Option<Block<'a>>,
+ ratio: f64,
+ label: Option<Spans<'a>>,
+ line_set: symbols::line::Set,
+ style: Style,
+ gauge_style: Style,
+}
+
+impl<'a> Default for LineGauge<'a> {
+ fn default() -> Self {
+ Self {
+ block: None,
+ ratio: 0.0,
+ label: None,
+ style: Style::default(),
+ line_set: symbols::line::NORMAL,
+ gauge_style: Style::default(),
+ }
+ }
+}
+
+impl<'a> LineGauge<'a> {
+ pub fn block(mut self, block: Block<'a>) -> Self {
+ self.block = Some(block);
+ self
+ }
+
+ pub fn ratio(mut self, ratio: f64) -> Self {
+ assert!(
+ (0.0..=1.0).contains(&ratio),
+ "Ratio should be between 0 and 1 inclusively."
+ );
+ self.ratio = ratio;
+ self
+ }
+
+ pub fn line_set(mut self, set: symbols::line::Set) -> Self {
+ self.line_set = set;
+ self
+ }
+
+ pub fn label<T>(mut self, label: T) -> Self
+ where
+ T: Into<Spans<'a>>,
+ {
+ self.label = Some(label.into());
+ self
+ }
+
+ pub fn style(mut self, style: Style) -> Self {
+ self.style = style;
+ self
+ }
+
+ pub fn gauge_style(mut self, style: Style) -> Self {
+ self.gauge_style = style;
+ self
+ }
+}
+
+impl<'a> Widget for LineGauge<'a> {
+ fn render(mut self, area: Rect, buf: &mut Buffer) {
+ buf.set_style(area, self.style);
+ let gauge_area = match self.block.take() {
+ Some(b) => {
+ let inner_area = b.inner(area);
+ b.render(area, buf);
+ inner_area
+ }
+ None => area,
+ };
+
+ if gauge_area.height < 1 {
+ return;
+ }
+
+ let ratio = self.ratio;
+ let label = self
+ .label
+ .unwrap_or_else(move || Spans::from(format!("{:.0}%", ratio * 100.0)));
+ let (col, row) = buf.set_spans(
+ gauge_area.left(),
+ gauge_area.top(),
+ &label,
+ gauge_area.width,
+ );
+ let start = col + 1;
+ if start >= gauge_area.right() {
+ return;
+ }
+
+ let end = start
+ + (f64::from(gauge_area.right().saturating_sub(start)) * self.ratio).floor() as u16;
+ for col in start..end {
+ buf.get_mut(col, row)
+ .set_symbol(self.line_set.horizontal)
+ .set_style(Style {
+ fg: self.gauge_style.fg,
+ bg: None,
+ add_modifier: self.gauge_style.add_modifier,
+ sub_modifier: self.gauge_style.sub_modifier,
+ });
+ }
+ for col in end..gauge_area.right() {
+ buf.get_mut(col, row)
+ .set_symbol(self.line_set.horizontal)
+ .set_style(Style {
+ fg: self.gauge_style.bg,
+ bg: None,
+ add_modifier: self.gauge_style.add_modifier,
+ sub_modifier: self.gauge_style.sub_modifier,
+ });
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ #[should_panic]
+ fn gauge_invalid_percentage() {
+ Gauge::default().percent(110);
+ }
+
+ #[test]
+ #[should_panic]
+ fn gauge_invalid_ratio_upper_bound() {
+ Gauge::default().ratio(1.1);
+ }
+
+ #[test]
+ #[should_panic]
+ fn gauge_invalid_ratio_lower_bound() {
+ Gauge::default().ratio(-0.5);
+ }
+}
diff --git a/src/ratatui/widgets/list.rs b/src/ratatui/widgets/list.rs
new file mode 100644
index 00000000..9608d299
--- /dev/null
+++ b/src/ratatui/widgets/list.rs
@@ -0,0 +1,268 @@
+use crate::ratatui::{
+ buffer::Buffer,
+ layout::{Corner, Rect},
+ style::Style,
+ text::Text,
+ widgets::{Block, StatefulWidget, Widget},
+};
+use unicode_width::UnicodeWidthStr;
+
+#[derive(Debug, Clone, Default)]
+pub struct ListState {
+ offset: usize,
+ selected: Option<usize>,
+}
+
+impl ListState {
+ pub fn selected(&self) -> Option<usize> {
+ self.selected
+ }
+
+ pub fn select(&mut self, index: Option<usize>) {
+ self.selected = index;
+ if index.is_none() {
+ self.offset = 0;
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct ListItem<'a> {
+ content: Text<'a>,
+ style: Style,
+}
+
+impl<'a> ListItem<'a> {
+ pub fn new<T>(content: T) -> ListItem<'a>
+ where
+ T: Into<Text<'a>>,
+ {
+ ListItem {
+ content: content.into(),
+ style: Style::default(),
+ }
+ }
+
+ pub fn style(mut self, style: Style) -> ListItem<'a> {
+ self.style = style;
+ self
+ }
+
+ pub fn height(&self) -> usize {
+ self.content.height()
+ }
+
+ pub fn width(&self) -> usize {
+ self.content.width()
+ }
+}
+
+/// A widget to display several items among which one can be selected (optional)
+///
+/// # Examples
+///
+/// ```
+/// # use ratatui::widgets::{Block, Borders, List, ListItem};
+/// # use ratatui::style::{Style, Color, Modifier};
+/// let items = [ListItem::new("Item 1"), ListItem::new("Item 2"), ListItem::new("Item 3")];
+/// List::new(items)
+/// .block(Block::default().title("List").borders(Borders::ALL))
+/// .style(Style::default().fg(Color::White))
+/// .highlight_style(Style::default().add_modifier(Modifier::ITALIC))
+/// .highlight_symbol(">>");
+/// ```
+#[derive(Debug, Clone)]
+pub struct List<'a> {
+ block: Option<Block<'a>>,
+ items: Vec<ListItem<'a>>,
+ /// Style used as a base style for the widget
+ style: Style,
+ start_corner: Corner,
+ /// Style used to render selected item
+ highlight_style: Style,
+ /// Symbol in front of the selected item (Shift all items to the right)
+ highlight_symbol: Option<&'a str>,
+ /// Whether to repeat the highlight symbol for each line of the selected item
+ repeat_highlight_symbol: bool,
+}
+
+impl<'a> List<'a> {
+ pub fn new<T>(items: T) -> List<'a>
+ where
+ T: Into<Vec<ListItem<'a>>>,
+ {
+ List {
+ block: None,
+ style: Style::default(),
+ items: items.into(),
+ start_corner: Corner::TopLeft,
+ highlight_style: Style::default(),
+ highlight_symbol: None,
+ repeat_highlight_symbol: false,
+ }
+ }
+
+ pub fn block(mut self, block: Block<'a>) -> List<'a> {
+ self.block = Some(block);
+ self
+ }
+
+ pub fn style(mut self, style: Style) -> List<'a> {
+ self.style = style;
+ self
+ }
+
+ pub fn highlight_symbol(mut self, highlight_symbol: &'a str) -> List<'a> {
+ self.highlight_symbol = Some(highlight_symbol);
+ self
+ }
+
+ pub fn highlight_style(mut self, style: Style) -> List<'a> {
+ self.highlight_style = style;
+ self
+ }
+
+ pub fn repeat_highlight_symbol(mut self, repeat: bool) -> List<'a> {
+ self.repeat_highlight_symbol = repeat;
+ self
+ }
+
+ pub fn start_corner(mut self, corner: Corner) -> List<'a> {
+ self.start_corner = corner;
+ self
+ }
+
+ fn get_items_bounds(
+ &self,
+ selected: Option<usize>,
+ offset: usize,
+ max_height: usize,
+ ) -> (usize, usize) {
+ let offset = offset.min(self.items.len().saturating_sub(1));
+ let mut start = offset;
+ let mut end = offset;
+ let mut height = 0;
+ for item in self.items.iter().skip(offset) {
+ if height + item.height() > max_height {
+ break;
+ }
+ height += item.height();
+ end += 1;
+ }
+
+ let selected = selected.unwrap_or(0).min(self.items.len() - 1);
+ while selected >= end {
+ height = height.saturating_add(self.items[end].height());
+ end += 1;
+ while height > max_height {
+ height = height.saturating_sub(self.items[start].height());
+ start += 1;
+ }
+ }
+ while selected < start {
+ start -= 1;
+ height = height.saturating_add(self.items[start].height());
+ while height > max_height {
+ end -= 1;
+ height = height.saturating_sub(self.items[end].height());
+ }
+ }
+ (start, end)
+ }
+}
+
+impl<'a> StatefulWidget for List<'a> {
+ type State = ListState;
+
+ fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
+ buf.set_style(area, self.style);
+ let list_area = match self.block.take() {
+ Some(b) => {
+ let inner_area = b.inner(area);
+ b.render(area, buf);
+ inner_area
+ }
+ None => area,
+ };
+
+ if list_area.width < 1 || list_area.height < 1 {
+ return;
+ }
+
+ if self.items.is_empty() {
+ return;
+ }
+ let list_height = list_area.height as usize;
+
+ let (start, end) = self.get_items_bounds(state.selected, state.offset, list_height);
+ state.offset = start;
+
+ let highlight_symbol = self.highlight_symbol.unwrap_or("");
+ let blank_symbol = " ".repeat(highlight_symbol.width());
+
+ let mut current_height = 0;
+ let has_selection = state.selected.is_some();
+ for (i, item) in self
+ .items
+ .iter_mut()
+ .enumerate()
+ .skip(state.offset)
+ .take(end - start)
+ {
+ let (x, y) = match self.start_corner {
+ Corner::BottomLeft => {
+ current_height += item.height() as u16;
+ (list_area.left(), list_area.bottom() - current_height)
+ }
+ _ => {
+ let pos = (list_area.left(), list_area.top() + current_height);
+ current_height += item.height() as u16;
+ pos
+ }
+ };
+ let area = Rect {
+ x,
+ y,
+ width: list_area.width,
+ height: item.height() as u16,
+ };
+ let item_style = self.style.patch(item.style);
+ buf.set_style(area, item_style);
+
+ let is_selected = state.selected.map(|s| s == i).unwrap_or(false);
+ for (j, line) in item.content.lines.iter().enumerate() {
+ // if the item is selected, we need to display the highlight symbol:
+ // - either for the first line of the item only,
+ // - or for each line of the item if the appropriate option is set
+ let symbol = if is_selected && (j == 0 || self.repeat_highlight_symbol) {
+ highlight_symbol
+ } else {
+ &blank_symbol
+ };
+ let (elem_x, max_element_width) = if has_selection {
+ let (elem_x, _) = buf.set_stringn(
+ x,
+ y + j as u16,
+ symbol,
+ list_area.width as usize,
+ item_style,
+ );
+ (elem_x, (list_area.width - (elem_x - x)))
+ } else {
+ (x, list_area.width)
+ };
+ buf.set_spans(elem_x, y + j as u16, line, max_element_width);
+ }
+ if is_selected {
+ buf.set_style(area, self.highlight_style);
+ }
+ }
+ }
+}
+
+impl<'a> Widget for List<'a> {
+ fn render(self, area: Rect, buf: &mut Buffer) {
+ let mut state = ListState::default();
+ StatefulWidget::render(self, area, buf, &mut state);
+ }
+}
diff --git a/src/ratatui/widgets/mod.rs b/src/ratatui/widgets/mod.rs
new file mode 100644
index 00000000..5a6167b8
--- /dev/null
+++ b/src/ratatui/widgets/mod.rs
@@ -0,0 +1,184 @@
+//! `widgets` is a collection of types that implement [`Widget`] or [`StatefulWidget`] or both.
+//!
+//! All widgets are implemented using the builder pattern and are consumable objects. They are not
+//! meant to be stored but used as *commands* to draw common figures in the UI.
+//!
+//! The available widgets are:
+//! - [`Block`]
+//! - [`Tabs`]
+//! - [`List`]
+//! - [`Table`]
+//! - [`Paragraph`]
+//! - [`Chart`]
+//! - [`BarChart`]
+//! - [`Gauge`]
+//! - [`Sparkline`]
+//! - [`Clear`]
+
+mod barchart;
+mod block;
+pub mod canvas;
+mod chart;
+mod clear;
+mod gauge;
+mod list;
+mod paragraph;
+mod reflow;
+mod sparkline;
+mod table;
+mod tabs;
+
+pub use self::barchart::BarChart;
+pub use self::block::{Block, BorderType};
+pub use self::chart::{Axis, Chart, Dataset, GraphType};
+pub use self::clear::Clear;
+pub use self::gauge::{Gauge, LineGauge};
+pub use self::list::{List, ListItem, ListState};
+pub use self::paragraph::{Paragraph, Wrap};
+pub use self::sparkline::Sparkline;
+pub use self::table::{Cell, Row, Table, TableState};
+pub use self::tabs::Tabs;
+
+use crate::ratatui::{buffer::Buffer, layout::Rect};
+use bitflags::bitflags;
+
+bitflags! {
+ /// Bitflags that can be composed to set the visible borders essentially on the block widget.
+ pub struct Borders: u8 {
+ /// Show no border (default)
+ const NONE = 0b0000;
+ /// Show the top border
+ const TOP = 0b0001;
+ /// Show the right border
+ const RIGHT = 0b0010;
+ /// Show the bottom border
+ const BOTTOM = 0b0100;
+ /// Show the left border
+ const LEFT = 0b1000;
+ /// Show all borders
+ const ALL = Self::TOP.bits | Self::RIGHT.bits | Self::BOTTOM.bits | Self::LEFT.bits;
+ }
+}
+
+/// Base requirements for a Widget
+pub trait Widget {
+ /// Draws the current state of the widget in the given buffer. That is the only method required
+ /// to implement a custom widget.
+ fn render(self, area: Rect, buf: &mut Buffer);
+}
+
+/// A `StatefulWidget` is a widget that can take advantage of some local state to remember things
+/// between two draw calls.
+///
+/// Most widgets can be drawn directly based on the input parameters. However, some features may
+/// require some kind of associated state to be implemented.
+///
+/// For example, the [`List`] widget can highlight the item currently selected. This can be
+/// translated in an offset, which is the number of elements to skip in order to have the selected
+/// item within the viewport currently allocated to this widget. The widget can therefore only
+/// provide the following behavior: whenever the selected item is out of the viewport scroll to a
+/// predefined position (making the selected item the last viewable item or the one in the middle
+/// for example). Nonetheless, if the widget has access to the last computed offset then it can
+/// implement a natural scrolling experience where the last offset is reused until the selected
+/// item is out of the viewport.
+///
+/// ## Examples
+///
+/// ```rust,no_run
+/// # use std::io;
+/// # use ratatui::Terminal;
+/// # use ratatui::backend::{Backend, TestBackend};
+/// # use ratatui::widgets::{Widget, List, ListItem, ListState};
+///
+/// // Let's say we have some events to display.
+/// struct Events {
+/// // `items` is the state managed by your application.
+/// items: Vec<String>,
+/// // `state` is the state that can be modified by the UI. It stores the index of the selected
+/// // item as well as the offset computed during the previous draw call (used to implement
+/// // natural scrolling).
+/// state: ListState
+/// }
+///
+/// impl Events {
+/// fn new(items: Vec<String>) -> Events {
+/// Events {
+/// items,
+/// state: ListState::default(),
+/// }
+/// }
+///
+/// pub fn set_items(&mut self, items: Vec<String>) {
+/// self.items = items;
+/// // We reset the state as the associated items have changed. This effectively reset
+/// // the selection as well as the stored offset.
+/// self.state = ListState::default();
+/// }
+///
+/// // Select the next item. This will not be reflected until the widget is drawn in the
+/// // `Terminal::draw` callback using `Frame::render_stateful_widget`.
+/// pub fn next(&mut self) {
+/// let i = match self.state.selected() {
+/// Some(i) => {
+/// if i >= self.items.len() - 1 {
+/// 0
+/// } else {
+/// i + 1
+/// }
+/// }
+/// None => 0,
+/// };
+/// self.state.select(Some(i));
+/// }
+///
+/// // Select the previous item. This will not be reflected until the widget is drawn in the
+/// // `Terminal::draw` callback using `Frame::render_stateful_widget`.
+/// pub fn previous(&mut self) {
+/// let i = match self.state.selected() {
+/// Some(i) => {
+/// if i == 0 {
+/// self.items.len() - 1
+/// } else {
+/// i - 1
+/// }
+/// }
+/// None => 0,
+/// };
+/// self.state.select(Some(i));
+/// }
+///
+/// // Unselect the currently selected item if any. The implementation of `ListState` makes
+/// // sure that the stored offset is also reset.
+/// pub fn unselect(&mut self) {
+/// self.state.select(None);
+/// }
+/// }
+///
+/// # let backend = TestBackend::new(5, 5);
+/// # let mut terminal = Terminal::new(backend).unwrap();
+///
+/// let mut events = Events::new(vec![
+/// String::from("Item 1"),
+/// String::from("Item 2")
+/// ]);
+///
+/// loop {
+/// terminal.draw(|f| {
+/// // The items managed by the application are transformed to something
+/// // that is understood by ratatui.
+/// let items: Vec<ListItem>= events.items.iter().map(|i| ListItem::new(i.as_ref())).collect();
+/// // The `List` widget is then built with those items.
+/// let list = List::new(items);
+/// // Finally the widget is rendered using the associated state. `events.state` is
+/// // effectively the only thing that we will "remember" from this draw call.
+/// f.render_stateful_widget(list, f.size(), &mut events.state);
+/// });
+///
+/// // In response to some input events or an external http request or whatever:
+/// events.next();
+/// }
+/// ```
+pub trait StatefulWidget {
+ type State;
+ fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State);
+}
diff --git a/src/ratatui/widgets/paragraph.rs b/src/ratatui/widgets/paragraph.rs
new file mode 100644
index 00000000..5edfe08e
--- /dev/null
+++ b/src/ratatui/widgets/paragraph.rs
@@ -0,0 +1,214 @@
+use crate::ratatui::{
+ buffer::Buffer,
+ layout::{Alignment, Rect},
+ style::Style,
+ text::{StyledGrapheme, Text},
+ widgets::{
+ reflow::{LineComposer, LineTruncator, WordWrapper},
+ Block, Widget,
+ },
+};
+use std::iter;
+use unicode_width::UnicodeWidthStr;
+
+fn get_line_offset(line_width: u16, text_area_width: u16, alignment: Alignment) -> u16 {
+ match alignment {
+ Alignment::Center => (text_area_width / 2).saturating_sub(line_width / 2),
+ Alignment::Right => text_area_width.saturating_sub(line_width),
+ Alignment::Left => 0,
+ }
+}
+
+/// A widget to display some text.
+///
+/// # Examples
+///
+/// ```
+/// # use ratatui::text::{Text, Spans, Span};
+/// # use ratatui::widgets::{Block, Borders, Paragraph, Wrap};
+/// # use ratatui::style::{Style, Color, Modifier};
+/// # use ratatui::layout::{Alignment};
+/// let text = vec![
+/// Spans::from(vec![
+/// Span::raw("First"),
+/// Span::styled("line",Style::default().add_modifier(Modifier::ITALIC)),
+/// Span::raw("."),
+/// ]),
+/// Spans::from(Span::styled("Second line", Style::default().fg(Color::Red))),
+/// ];
+/// Paragraph::new(text)
+/// .block(Block::default().title("Paragraph").borders(Borders::ALL))
+/// .style(Style::default().fg(Color::White).bg(Color::Black))
+/// .alignment(Alignment::Center)
+/// .wrap(Wrap { trim: true });
+/// ```
+#[derive(Debug, Clone)]
+pub struct Paragraph<'a> {
+ /// A block to wrap the widget in
+ block: Option<Block<'a>>,
+ /// Widget style
+ style: Style,
+ /// How to wrap the text
+ wrap: Option<Wrap>,
+ /// The text to display
+ text: Text<'a>,
+ /// Scroll
+ scroll: (u16, u16),
+ /// Alignment of the text
+ alignment: Alignment,
+}
+
+/// Describes how to wrap text across lines.
+///
+/// ## Examples
+///
+/// ```
+/// # use ratatui::widgets::{Paragraph, Wrap};
+/// # use ratatui::text::Text;
+/// let bullet_points = Text::from(r#"Some indented points:
+/// - First thing goes here and is long so that it wraps
+/// - Here is another point that is long enough to wrap"#);
+///
+/// // With leading spaces trimmed (window width of 30 chars):
+/// Paragraph::new(bullet_points.clone()).wrap(Wrap { trim: true });
+/// // Some indented points:
+/// // - First thing goes here and is
+/// // long so that it wraps
+/// // - Here is another point that
+/// // is long enough to wrap
+///
+/// // But without trimming, indentation is preserved:
+/// Paragraph::new(bullet_points).wrap(Wrap { trim: false });
+/// // Some indented points:
+/// // - First thing goes here
+/// // and is long so that it wraps
+/// // - Here is another point
+/// // that is long enough to wrap
+/// ```
+#[derive(Debug, Clone, Copy)]
+pub struct Wrap {
+ /// Should leading whitespace be trimmed
+ pub trim: bool,
+}
+
+impl<'a> Paragraph<'a> {
+ pub fn new<T>(text: T) -> Paragraph<'a>
+ where
+ T: Into<Text<'a>>,
+ {
+ Paragraph {
+ block: None,
+ style: Default::default(),
+ wrap: None,
+ text: text.into(),
+ scroll: (0, 0),
+ alignment: Alignment::Left,
+ }
+ }
+
+ pub fn block(mut self, block: Block<'a>) -> Paragraph<'a> {
+ self.block = Some(block);
+ self
+ }
+
+ pub fn style(mut self, style: Style) -> Paragraph<'a> {
+ self.style = style;
+ self
+ }
+
+ pub fn wrap(mut self, wrap: Wrap) -> Paragraph<'a> {
+ self.wrap = Some(wrap);
+ self
+ }
+
+ pub fn scroll(mut self, offset: (u16, u16)) -> Paragraph<'a> {
+ self.scroll = offset;
+ self
+ }
+
+ pub fn alignment(mut self, alignment: Alignment) -> Paragraph<'a> {
+ self.alignment = alignment;
+ self
+ }
+}
+
+impl<'a> Widget for Paragraph<'a> {
+ fn render(mut self, area: Rect, buf: &mut Buffer) {
+ buf.set_style(area, self.style);
+ let text_area = match self.block.take() {
+ Some(b) => {
+ let inner_area = b.inner(area);
+ b.render(area, buf);
+ inner_area
+ }
+ None => area,
+ };
+
+ if text_area.height < 1 {
+ return;
+ }
+
+ let style = self.style;
+ let mut styled = self.text.lines.iter().flat_map(|spans| {
+ spans
+ .0
+ .iter()
+ .flat_map(|span| span.styled_graphemes(style))
+ // Required given the way composers work but might be refactored out if we change
+ // composers to operate on lines instead of a stream of graphemes.
+ .chain(iter::once(StyledGrapheme {
+ symbol: "\n",
+ style: self.style,
+ }))
+ });
+
+ let mut line_composer: Box<dyn LineComposer> = if let Some(Wrap { trim }) = self.wrap {
+ Box::new(WordWrapper::new(&mut styled, text_area.width, trim))
+ } else {
+ let mut line_composer = Box::new(LineTruncator::new(&mut styled, text_area.width));
+ if let Alignment::Left = self.alignment {
+ line_composer.set_horizontal_offset(self.scroll.1);
+ }
+ line_composer
+ };
+ let mut y = 0;
+ while let Some((current_line, current_line_width)) = line_composer.next_line() {
+ if y >= self.scroll.0 {
+ let mut x = get_line_offset(current_line_width, text_area.width, self.alignment);
+ for StyledGrapheme { symbol, style } in current_line {
+ let width = symbol.width();
+ if width == 0 {
+ continue;
+ }
+ buf.get_mut(text_area.left() + x, text_area.top() + y - self.scroll.0)
+ .set_symbol(if symbol.is_empty() {
+ // If the symbol is empty, the last char which rendered last time will
+ // leave on the line. It's a quick fix.
+ " "
+ } else {
+ symbol
+ })
+ .set_style(*style);
+ x += width as u16;
+ }
+ }
+ y += 1;
+ if y >= text_area.height + self.scroll.0 {
+ break;
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn zero_width_char_at_end_of_line() {
+ let line = "foo\0";
+ let paragraph = Paragraph::new(line);
+ let mut buf = Buffer::with_lines(vec![line]);
+ paragraph.render(*buf.area(), &mut buf);
+ }
+}
diff --git a/src/ratatui/widgets/reflow.rs b/src/ratatui/widgets/reflow.rs
new file mode 100644
index 00000000..3806b33e
--- /dev/null
+++ b/src/ratatui/widgets/reflow.rs
@@ -0,0 +1,534 @@
+use crate::ratatui::text::StyledGrapheme;
+use unicode_segmentation::UnicodeSegmentation;
+use unicode_width::UnicodeWidthStr;
+
+const NBSP: &str = "\u{00a0}";
+
+/// A state machine to pack styled symbols into lines.
+/// Cannot implement it as Iterator since it yields slices of the internal buffer (need streaming
+/// iterators for that).
+pub trait LineComposer<'a> {
+ fn next_line(&mut self) -> Option<(&[StyledGrapheme<'a>], u16)>;
+}
+
+/// A state machine that wraps lines on word boundaries.
+pub struct WordWrapper<'a, 'b> {
+ symbols: &'b mut dyn Iterator<Item = StyledGrapheme<'a>>,
+ max_line_width: u16,
+ current_line: Vec<StyledGrapheme<'a>>,
+ next_line: Vec<StyledGrapheme<'a>>,
+ /// Removes the leading whitespace from lines
+ trim: bool,
+}
+
+impl<'a, 'b> WordWrapper<'a, 'b> {
+ pub fn new(
+ symbols: &'b mut dyn Iterator<Item = StyledGrapheme<'a>>,
+ max_line_width: u16,
+ trim: bool,
+ ) -> WordWrapper<'a, 'b> {
+ WordWrapper {
+ symbols,
+ max_line_width,
+ current_line: vec![],
+ next_line: vec![],
+ trim,
+ }
+ }
+}
+
+impl<'a, 'b> LineComposer<'a> for WordWrapper<'a, 'b> {
+ fn next_line(&mut self) -> Option<(&[StyledGrapheme<'a>], u16)> {
+ if self.max_line_width == 0 {
+ return None;
+ }
+ std::mem::swap(&mut self.current_line, &mut self.next_line);
+ self.next_line.truncate(0);
+
+ let mut current_line_width = self
+ .current_line
+ .iter()
+ .map(|StyledGrapheme { symbol, .. }| symbol.width() as u16)
+ .sum();
+
+ let mut symbols_to_last_word_end: usize = 0;
+ let mut width_to_last_word_end: u16 = 0;
+ let mut prev_whitespace = false;
+ let mut symbols_exhausted = true;
+ for StyledGrapheme { symbol, style } in &mut self.symbols {
+ symbols_exhausted = false;
+ let symbol_whitespace = symbol.chars().all(&char::is_whitespace) && symbol != NBSP;
+
+ // Ignore characters wider that the total max width.
+ if symbol.width() as u16 > self.max_line_width
+ // Skip leading whitespace when trim is enabled.
+ || self.trim && symbol_whitespace && symbol != "\n" && current_line_width == 0
+ {
+ continue;
+ }
+
+ // Break on newline and discard it.
+ if symbol == "\n" {
+ if prev_whitespace {
+ current_line_width = width_to_last_word_end;
+ self.current_line.truncate(symbols_to_last_word_end);
+ }
+ break;
+ }
+
+ // Mark the previous symbol as word end.
+ if symbol_whitespace && !prev_whitespace {
+ symbols_to_last_word_end = self.current_line.len();
+ width_to_last_word_end = current_line_width;
+ }
+
+ self.current_line.push(StyledGrapheme { symbol, style });
+ current_line_width += symbol.width() as u16;
+
+ if current_line_width > self.max_line_width {
+ // If there was no word break in the text, wrap at the end of the line.
+ let (truncate_at, truncated_width) = if symbols_to_last_word_end != 0 {
+ (symbols_to_last_word_end, width_to_last_word_end)
+ } else {
+ (self.current_line.len() - 1, self.max_line_width)
+ };
+
+ // Push the remainder to the next line but strip leading whitespace:
+ {
+ let remainder = &self.current_line[truncate_at..];
+ if let Some(remainder_nonwhite) =
+ remainder.iter().position(|StyledGrapheme { symbol, .. }| {
+ !symbol.chars().all(&char::is_whitespace)
+ })
+ {
+ self.next_line
+ .extend_from_slice(&remainder[remainder_nonwhite..]);
+ }
+ }
+ self.current_line.truncate(truncate_at);
+ current_line_width = truncated_width;
+ break;
+ }
+
+ prev_whitespace = symbol_whitespace;
+ }
+
+ // Even if the iterator is exhausted, pass the previous remainder.
+ if symbols_exhausted && self.current_line.is_empty() {
+ None
+ } else {
+ Some((&self.current_line[..], current_line_width))
+ }
+ }
+}
+
+/// A state machine that truncates overhanging lines.
+pub struct LineTruncator<'a, 'b> {
+ symbols: &'b mut dyn Iterator<Item = StyledGrapheme<'a>>,
+ max_line_width: u16,
+ current_line: Vec<StyledGrapheme<'a>>,
+ /// Record the offset to skip render
+ horizontal_offset: u16,
+}
+
+impl<'a, 'b> LineTruncator<'a, 'b> {
+ pub fn new(
+ symbols: &'b mut dyn Iterator<Item = StyledGrapheme<'a>>,
+ max_line_width: u16,
+ ) -> LineTruncator<'a, 'b> {
+ LineTruncator {
+ symbols,
+ max_line_width,
+ horizontal_offset: 0,
+ current_line: vec![],
+ }
+ }
+
+ pub fn set_horizontal_offset(&mut self, horizontal_offset: u16) {
+ self.horizontal_offset = horizontal_offset;
+ }
+}
+
+impl<'a, 'b> LineComposer<'a> for LineTruncator<'a, 'b> {
+ fn next_line(&mut self) -> Option<(&[StyledGrapheme<'a>], u16)> {
+ if self.max_line_width == 0 {
+ return None;
+ }
+
+ self.current_line.truncate(0);
+ let mut current_line_width = 0;
+
+ let mut skip_rest = false;
+ let mut symbols_exhausted = true;
+ let mut horizontal_offset = self.horizontal_offset as usize;
+ for StyledGrapheme { symbol, style } in &mut self.symbols {
+ symbols_exhausted = false;
+
+ // Ignore characters wider that the total max width.
+ if symbol.width() as u16 > self.max_line_width {
+ continue;
+ }
+
+ // Break on newline and discard it.
+ if symbol == "\n" {
+ break;
+ }
+
+ if current_line_width + symbol.width() as u16 > self.max_line_width {
+ // Exhaust the remainder of the line.
+ skip_rest = true;
+ break;
+ }
+
+ let symbol = if horizontal_offset == 0 {
+ symbol
+ } else {
+ let w = symbol.width();
+ if w > horizontal_offset {
+ let t = trim_offset(symbol, horizontal_offset);
+ horizontal_offset = 0;
+ t
+ } else {
+ horizontal_offset -= w;
+ ""
+ }
+ };
+ current_line_width += symbol.width() as u16;
+ self.current_line.push(StyledGrapheme { symbol, style });
+ }
+
+ if skip_rest {
+ for StyledGrapheme { symbol, .. } in &mut self.symbols {
+ if symbol == "\n" {
+ break;
+ }
+ }
+ }
+
+ if symbols_exhausted && self.current_line.is_empty() {
+ None
+ } else {
+ Some((&self.current_line[..], current_line_width))
+ }
+ }
+}
+
+/// This function will return a str slice which start at specified offset.
+/// As src is a unicode str, start offset has to be calculated with each character.
+fn trim_offset(src: &str, mut offset: usize) -> &str {
+ let mut start = 0;
+ for c in UnicodeSegmentation::graphemes(src, true) {
+ let w = c.width();
+ if w <= offset {
+ offset -= w;
+ start += c.len();
+ } else {
+ break;
+ }
+ }
+ &src[start..]
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use unicode_segmentation::UnicodeSegmentation;
+
+ enum Composer {
+ WordWrapper { trim: bool },
+ LineTruncator,
+ }
+
+ fn run_composer(which: Composer, text: &str, text_area_width: u16) -> (Vec<String>, Vec<u16>) {
+ let style = Default::default();
+ let mut styled =
+ UnicodeSegmentation::graphemes(text, true).map(|g| StyledGrapheme { symbol: g, style });
+ let mut composer: Box<dyn LineComposer> = match which {
+ Composer::WordWrapper { trim } => {
+ Box::new(WordWrapper::new(&mut styled, text_area_width, trim))
+ }
+ Composer::LineTruncator => Box::new(LineTruncator::new(&mut styled, text_area_width)),
+ };
+ let mut lines = vec![];
+ let mut widths = vec![];
+ while let Some((styled, width)) = composer.next_line() {
+ let line = styled
+ .iter()
+ .map(|StyledGrapheme { symbol, .. }| *symbol)
+ .collect::<String>();
+ assert!(width <= text_area_width);
+ lines.push(line);
+ widths.push(width);
+ }
+ (lines, widths)
+ }
+
+ #[test]
+ fn line_composer_one_line() {
+ let width = 40;
+ for i in 1..width {
+ let text = "a".repeat(i);
+ let (word_wrapper, _) =
+ run_composer(Composer::WordWrapper { trim: true }, &text, width as u16);
+ let (line_truncator, _) = run_composer(Composer::LineTruncator, &text, width as u16);
+ let expected = vec![text];
+ assert_eq!(word_wrapper, expected);
+ assert_eq!(line_truncator, expected);
+ }
+ }
+
+ #[test]
+ fn line_composer_short_lines() {
+ let width = 20;
+ let text =
+ "abcdefg\nhijklmno\npabcdefg\nhijklmn\nopabcdefghijk\nlmnopabcd\n\n\nefghijklmno";
+ let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
+ let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
+
+ let wrapped: Vec<&str> = text.split('\n').collect();
+ assert_eq!(word_wrapper, wrapped);
+ assert_eq!(line_truncator, wrapped);
+ }
+
+ #[test]
+ fn line_composer_long_word() {
+ let width = 20;
+ let text = "abcdefghijklmnopabcdefghijklmnopabcdefghijklmnopabcdefghijklmno";
+ let (word_wrapper, _) =
+ run_composer(Composer::WordWrapper { trim: true }, text, width as u16);
+ let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width as u16);
+
+ let wrapped = vec![
+ &text[..width],
+ &text[width..width * 2],
+ &text[width * 2..width * 3],
+ &text[width * 3..],
+ ];
+ assert_eq!(
+ word_wrapper, wrapped,
+ "WordWrapper should detect the line cannot be broken on word boundary and \
+ break it at line width limit."
+ );
+ assert_eq!(line_truncator, vec![&text[..width]]);
+ }
+
+ #[test]
+ fn line_composer_long_sentence() {
+ let width = 20;
+ let text =
+ "abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab c d e f g h i j k l m n o";
+ let text_multi_space =
+ "abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab c d e f g h i j k l \
+ m n o";
+ let (word_wrapper_single_space, _) =
+ run_composer(Composer::WordWrapper { trim: true }, text, width as u16);
+ let (word_wrapper_multi_space, _) = run_composer(
+ Composer::WordWrapper { trim: true },
+ text_multi_space,
+ width as u16,
+ );
+ let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width as u16);
+
+ let word_wrapped = vec![
+ "abcd efghij",
+ "klmnopabcd efgh",
+ "ijklmnopabcdefg",
+ "hijkl mnopab c d e f",
+ "g h i j k l m n o",
+ ];
+ assert_eq!(word_wrapper_single_space, word_wrapped);
+ assert_eq!(word_wrapper_multi_space, word_wrapped);
+
+ assert_eq!(line_truncator, vec![&text[..width]]);
+ }
+
+ #[test]
+ fn line_composer_zero_width() {
+ let width = 0;
+ let text = "abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab ";
+ let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
+ let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
+
+ let expected: Vec<&str> = Vec::new();
+ assert_eq!(word_wrapper, expected);
+ assert_eq!(line_truncator, expected);
+ }
+
+ #[test]
+ fn line_composer_max_line_width_of_1() {
+ let width = 1;
+ let text = "abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab ";
+ let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
+ let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
+
+ let expected: Vec<&str> = UnicodeSegmentation::graphemes(text, true)
+ .filter(|g| g.chars().any(|c| !c.is_whitespace()))
+ .collect();
+ assert_eq!(word_wrapper, expected);
+ assert_eq!(line_truncator, vec!["a"]);
+ }
+
+ #[test]
+ fn line_composer_max_line_width_of_1_double_width_characters() {
+ let width = 1;
+ let text = "コンピュータ上で文字を扱う場合、典型的には文字\naaaによる通信を行う場合にその\
+ 両端点では、";
+ let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
+ let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
+ assert_eq!(word_wrapper, vec!["", "a", "a", "a"]);
+ assert_eq!(line_truncator, vec!["", "a"]);
+ }
+
+ /// Tests WordWrapper with words some of which exceed line length and some not.
+ #[test]
+ fn line_composer_word_wrapper_mixed_length() {
+ let width = 20;
+ let text = "abcd efghij klmnopabcdefghijklmnopabcdefghijkl mnopab cdefghi j klmno";
+ let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
+ assert_eq!(
+ word_wrapper,
+ vec![
+ "abcd efghij",
+ "klmnopabcdefghijklmn",
+ "opabcdefghijkl",
+ "mnopab cdefghi j",
+ "klmno",
+ ]
+ )
+ }
+
+ #[test]
+ fn line_composer_double_width_chars() {
+ let width = 20;
+ let text = "コンピュータ上で文字を扱う場合、典型的には文字による通信を行う場合にその両端点\
+ では、";
+ let (word_wrapper, word_wrapper_width) =
+ run_composer(Composer::WordWrapper { trim: true }, text, width);
+ let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
+ assert_eq!(line_truncator, vec!["コンピュータ上で文字"]);
+ let wrapped = vec![
+ "コンピュータ上で文字",
+ "を扱う場合、典型的に",
+ "は文字による通信を行",
+ "う場合にその両端点で",
+ "は、",
+ ];
+ assert_eq!(word_wrapper, wrapped);
+ assert_eq!(word_wrapper_width, vec![width, width, width, width, 4]);
+ }
+
+ #[test]
+ fn line_composer_leading_whitespace_removal() {
+ let width = 20;
+ let text = "AAAAAAAAAAAAAAAAAAAA AAA";
+ let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
+ let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
+ assert_eq!(word_wrapper, vec!["AAAAAAAAAAAAAAAAAAAA", "AAA",]);
+ assert_eq!(line_truncator, vec!["AAAAAAAAAAAAAAAAAAAA"]);
+ }
+
+ /// Tests truncation of leading whitespace.
+ #[test]
+ fn line_composer_lots_of_spaces() {
+ let width = 20;
+ let text = " ";
+ let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
+ let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
+ assert_eq!(word_wrapper, vec![""]);
+ assert_eq!(line_truncator, vec![" "]);
+ }
+
+ /// Tests an input starting with a letter, followed by spaces - some of the behaviour is
+ /// incidental.
+ #[test]
+ fn line_composer_char_plus_lots_of_spaces() {
+ let width = 20;
+ let text = "a ";
+ let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
+ let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
+ // What's happening below is: the first line gets consumed, trailing spaces discarded,
+ // after 20 of which a word break occurs (probably shouldn't). The second line break
+ // discards all whitespace. The result should probably be vec!["a"] but it doesn't matter
+ // that much.
+ assert_eq!(word_wrapper, vec!["a", ""]);
+ assert_eq!(line_truncator, vec!["a "]);
+ }
+
+ #[test]
+ fn line_composer_word_wrapper_double_width_chars_mixed_with_spaces() {
+ let width = 20;
+ // Japanese seems not to use spaces but we should break on spaces anyway... We're using it
+ // to test double-width chars.
+ // You are more than welcome to add word boundary detection based of alterations of
+ // hiragana and katakana...
+ // This happens to also be a test case for mixed width because regular spaces are single width.
+ let text = "コンピュ ータ上で文字を扱う場合、 典型的には文 字による 通信を行 う場合にその両端点では、";
+ let (word_wrapper, word_wrapper_width) =
+ run_composer(Composer::WordWrapper { trim: true }, text, width);
+ assert_eq!(
+ word_wrapper,
+ vec![
+ "コンピュ",
+ "ータ上で文字を扱う場",
+ "合、 典型的には文",
+ "字による 通信を行",
+ "う場合にその両端点で",
+ "は、",
+ ]
+ );
+ // Odd-sized lines have a space in them.
+ assert_eq!(word_wrapper_width, vec![8, 20, 17, 17, 20, 4]);
+ }
+
+ /// Ensure words separated by nbsp are wrapped as if they were a single one.
+ #[test]
+ fn line_composer_word_wrapper_nbsp() {
+ let width = 20;
+ let text = "AAAAAAAAAAAAAAA AAAA\u{00a0}AAA";
+ let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
+ assert_eq!(word_wrapper, vec!["AAAAAAAAAAAAAAA", "AAAA\u{00a0}AAA",]);
+
+ // Ensure that if the character was a regular space, it would be wrapped differently.
+ let text_space = text.replace('\u{00a0}', " ");
+ let (word_wrapper_space, _) =
+ run_composer(Composer::WordWrapper { trim: true }, &text_space, width);
+ assert_eq!(word_wrapper_space, vec!["AAAAAAAAAAAAAAA AAAA", "AAA",]);
+ }
+
+ #[test]
+ fn line_composer_word_wrapper_preserve_indentation() {
+ let width = 20;
+ let text = "AAAAAAAAAAAAAAAAAAAA AAA";
+ let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: false }, text, width);
+ assert_eq!(word_wrapper, vec!["AAAAAAAAAAAAAAAAAAAA", " AAA",]);
+ }
+
+ #[test]
+ fn line_composer_word_wrapper_preserve_indentation_with_wrap() {
+ let width = 10;
+ let text = "AAA AAA AAAAA AA AAAAAA\n B\n C\n D";
+ let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: false }, text, width);
+ assert_eq!(
+ word_wrapper,
+ vec!["AAA AAA", "AAAAA AA", "AAAAAA", " B", " C", " D"]
+ );
+ }
+
+ #[test]
+ fn line_composer_word_wrapper_preserve_indentation_lots_of_whitespace() {
+ let width = 10;
+ let text = " 4 Indent\n must wrap!";
+ let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: false }, text, width);
+ assert_eq!(
+ word_wrapper,
+ vec![
+ " ",
+ " 4",
+ "Indent",
+ " ",
+ " must",
+ "wrap!"
+ ]
+ );
+ }
+}
diff --git a/src/ratatui/widgets/sparkline.rs b/src/ratatui/widgets/sparkline.rs
new file mode 100644
index 00000000..a05df112
--- /dev/null
+++ b/src/ratatui/widgets/sparkline.rs
@@ -0,0 +1,155 @@
+use crate::ratatui::{
+ buffer::Buffer,
+ layout::Rect,
+ style::Style,
+ symbols,
+ widgets::{Block, Widget},
+};
+use std::cmp::min;
+
+/// Widget to render a sparkline over one or more lines.
+///
+/// # Examples
+///
+/// ```
+/// # use ratatui::widgets::{Block, Borders, Sparkline};
+/// # use ratatui::style::{Style, Color};
+/// Sparkline::default()
+/// .block(Block::default().title("Sparkline").borders(Borders::ALL))
+/// .data(&[0, 2, 3, 4, 1, 4, 10])
+/// .max(5)
+/// .style(Style::default().fg(Color::Red).bg(Color::White));
+/// ```
+#[derive(Debug, Clone)]
+pub struct Sparkline<'a> {
+ /// A block to wrap the widget in
+ block: Option<Block<'a>>,
+ /// Widget style
+ style: Style,
+ /// A slice of the data to display
+ data: &'a [u64],
+ /// The maximum value to take to compute the maximum bar height (if nothing is specified, the
+ /// widget uses the max of the dataset)
+ max: Option<u64>,
+ /// A set of bar symbols used to represent the give data
+ bar_set: symbols::bar::Set,
+}
+
+impl<'a> Default for Sparkline<'a> {
+ fn default() -> Sparkline<'a> {
+ Sparkline {
+ block: None,
+ style: Default::default(),
+ data: &[],
+ max: None,
+ bar_set: symbols::bar::NINE_LEVELS,
+ }
+ }
+}
+
+impl<'a> Sparkline<'a> {
+ pub fn block(mut self, block: Block<'a>) -> Sparkline<'a> {
+ self.block = Some(block);
+ self
+ }
+
+ pub fn style(mut self, style: Style) -> Sparkline<'a> {
+ self.style = style;
+ self
+ }
+
+ pub fn data(mut self, data: &'a [u64]) -> Sparkline<'a> {
+ self.data = data;
+ self
+ }
+
+ pub fn max(mut self, max: u64) -> Sparkline<'a> {
+ self.max = Some(max);
+ self
+ }
+
+ pub fn bar_set(mut self, bar_set: symbols::bar::Set) -> Sparkline<'a> {
+ self.bar_set = bar_set;
+ self
+ }
+}
+
+impl<'a> Widget for Sparkline<'a> {
+ fn render(mut self, area: Rect, buf: &mut Buffer) {
+ let spark_area = match self.block.take() {
+ Some(b) => {
+ let inner_area = b.inner(area);
+ b.render(area, buf);
+ inner_area
+ }
+ None => area,
+ };
+
+ if spark_area.height < 1 {
+ return;
+ }
+
+ let max = match self.max {
+ Some(v) => v,
+ None => *self.data.iter().max().unwrap_or(&1u64),
+ };
+ let max_index = min(spark_area.width as usize, self.data.len());
+ let mut data = self
+ .data
+ .iter()
+ .take(max_index)
+ .map(|e| {
+ if max != 0 {
+ e * u64::from(spark_area.height) * 8 / max
+ } else {
+ 0
+ }
+ })
+ .collect::<Vec<u64>>();
+ for j in (0..spark_area.height).rev() {
+ for (i, d) in data.iter_mut().enumerate() {
+ let symbol = match *d {
+ 0 => self.bar_set.empty,
+ 1 => self.bar_set.one_eighth,
+ 2 => self.bar_set.one_quarter,
+ 3 => self.bar_set.three_eighths,
+ 4 => self.bar_set.half,
+ 5 => self.bar_set.five_eighths,
+ 6 => self.bar_set.three_quarters,
+ 7 => self.bar_set.seven_eighths,
+ _ => self.bar_set.full,
+ };
+ buf.get_mut(spark_area.left() + i as u16, spark_area.top() + j)
+ .set_symbol(symbol)
+ .set_style(self.style);
+
+ if *d > 8 {
+ *d -= 8;
+ } else {
+ *d = 0;
+ }
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn it_does_not_panic_if_max_is_zero() {
+ let widget = Sparkline::default().data(&[0, 0, 0]);
+ let area = Rect::new(0, 0, 3, 1);
+ let mut buffer = Buffer::empty(area);
+ widget.render(area, &mut buffer);
+ }
+
+ #[test]
+ fn it_does_not_panic_if_max_is_set_to_zero() {
+ let widget = Sparkline::default().data(&[0, 1, 2]).max(0);
+ let area = Rect::new(0, 0, 3, 1);
+ let mut buffer = Buffer::empty(area);
+ widget.render(area, &mut buffer);
+ }
+}
diff --git a/src/ratatui/widgets/table.rs b/src/ratatui/widgets/table.rs
new file mode 100644
index 00000000..d987a8d9
--- /dev/null
+++ b/src/ratatui/widgets/table.rs
@@ -0,0 +1,504 @@
+use crate::ratatui::{
+ buffer::Buffer,
+ layout::{Constraint, Direction, Layout, Rect},
+ style::Style,
+ text::Text,
+ widgets::{Block, StatefulWidget, Widget},
+};
+use unicode_width::UnicodeWidthStr;
+
+/// A [`Cell`] contains the [`Text`] to be displayed in a [`Row`] of a [`Table`].
+///
+/// It can be created from anything that can be converted to a [`Text`].
+/// ```rust
+/// # use ratatui::widgets::Cell;
+/// # use ratatui::style::{Style, Modifier};
+/// # use ratatui::text::{Span, Spans, Text};
+/// # use std::borrow::Cow;
+/// Cell::from("simple string");
+///
+/// Cell::from(Span::from("span"));
+///
+/// Cell::from(Spans::from(vec![
+/// Span::raw("a vec of "),
+/// Span::styled("spans", Style::default().add_modifier(Modifier::BOLD))
+/// ]));
+///
+/// Cell::from(Text::from("a text"));
+///
+/// Cell::from(Text::from(Cow::Borrowed("hello")));
+/// ```
+///
+/// You can apply a [`Style`] on the entire [`Cell`] using [`Cell::style`] or rely on the styling
+/// capabilities of [`Text`].
+#[derive(Debug, Clone, PartialEq, Eq, Default)]
+pub struct Cell<'a> {
+ content: Text<'a>,
+ style: Style,
+}
+
+impl<'a> Cell<'a> {
+ /// Set the `Style` of this cell.
+ pub fn style(mut self, style: Style) -> Self {
+ self.style = style;
+ self
+ }
+}
+
+impl<'a, T> From<T> for Cell<'a>
+where
+ T: Into<Text<'a>>,
+{
+ fn from(content: T) -> Cell<'a> {
+ Cell {
+ content: content.into(),
+ style: Style::default(),
+ }
+ }
+}
+
+/// Holds data to be displayed in a [`Table`] widget.
+///
+/// A [`Row`] is a collection of cells. It can be created from simple strings:
+/// ```rust
+/// # use ratatui::widgets::Row;
+/// Row::new(vec!["Cell1", "Cell2", "Cell3"]);
+/// ```
+///
+/// But if you need a bit more control over individual cells, you can explicitly create [`Cell`]s:
+/// ```rust
+/// # use ratatui::widgets::{Row, Cell};
+/// # use ratatui::style::{Style, Color};
+/// Row::new(vec![
+/// Cell::from("Cell1"),
+/// Cell::from("Cell2").style(Style::default().fg(Color::Yellow)),
+/// ]);
+/// ```
+///
+/// You can also construct a row from any type that can be converted into [`Text`]:
+/// ```rust
+/// # use std::borrow::Cow;
+/// # use ratatui::widgets::Row;
+/// Row::new(vec![
+/// Cow::Borrowed("hello"),
+/// Cow::Owned("world".to_uppercase()),
+/// ]);
+/// ```
+///
+/// By default, a row has a height of 1 but you can change this using [`Row::height`].
+#[derive(Debug, Clone, PartialEq, Eq, Default)]
+pub struct Row<'a> {
+ cells: Vec<Cell<'a>>,
+ height: u16,
+ style: Style,
+ bottom_margin: u16,
+}
+
+impl<'a> Row<'a> {
+ /// Creates a new [`Row`] from an iterator where items can be converted to a [`Cell`].
+ pub fn new<T>(cells: T) -> Self
+ where
+ T: IntoIterator,
+ T::Item: Into<Cell<'a>>,
+ {
+ Self {
+ height: 1,
+ cells: cells.into_iter().map(|c| c.into()).collect(),
+ style: Style::default(),
+ bottom_margin: 0,
+ }
+ }
+
+ /// Set the fixed height of the [`Row`]. Any [`Cell`] whose content has more lines than this
+ /// height will see its content truncated.
+ pub fn height(mut self, height: u16) -> Self {
+ self.height = height;
+ self
+ }
+
+ /// Set the [`Style`] of the entire row. This [`Style`] can be overridden by the [`Style`] of a
+ /// any individual [`Cell`] or event by their [`Text`] content.
+ pub fn style(mut self, style: Style) -> Self {
+ self.style = style;
+ self
+ }
+
+ /// Set the bottom margin. By default, the bottom margin is `0`.
+ pub fn bottom_margin(mut self, margin: u16) -> Self {
+ self.bottom_margin = margin;
+ self
+ }
+
+ /// Returns the total height of the row.
+ fn total_height(&self) -> u16 {
+ self.height.saturating_add(self.bottom_margin)
+ }
+}
+
+/// A widget to display data in formatted columns.
+///
+/// It is a collection of [`Row`]s, themselves composed of [`Cell`]s:
+/// ```rust
+/// # use ratatui::widgets::{Block, Borders, Table, Row, Cell};
+/// # use ratatui::layout::Constraint;
+/// # use ratatui::style::{Style, Color, Modifier};
+/// # use ratatui::text::{Text, Spans, Span};
+/// Table::new(vec![
+/// // Row can be created from simple strings.
+/// Row::new(vec!["Row11", "Row12", "Row13"]),
+/// // You can style the entire row.
+/// Row::new(vec!["Row21", "Row22", "Row23"]).style(Style::default().fg(Color::Blue)),
+/// // If you need more control over the styling you may need to create Cells directly
+/// Row::new(vec![
+/// Cell::from("Row31"),
+/// Cell::from("Row32").style(Style::default().fg(Color::Yellow)),
+/// Cell::from(Spans::from(vec![
+/// Span::raw("Row"),
+/// Span::styled("33", Style::default().fg(Color::Green))
+/// ])),
+/// ]),
+/// // If a Row need to display some content over multiple lines, you just have to change
+/// // its height.
+/// Row::new(vec![
+/// Cell::from("Row\n41"),
+/// Cell::from("Row\n42"),
+/// Cell::from("Row\n43"),
+/// ]).height(2),
+/// ])
+/// // You can set the style of the entire Table.
+/// .style(Style::default().fg(Color::White))
+/// // It has an optional header, which is simply a Row always visible at the top.
+/// .header(
+/// Row::new(vec!["Col1", "Col2", "Col3"])
+/// .style(Style::default().fg(Color::Yellow))
+/// // If you want some space between the header and the rest of the rows, you can always
+/// // specify some margin at the bottom.
+/// .bottom_margin(1)
+/// )
+/// // As any other widget, a Table can be wrapped in a Block.
+/// .block(Block::default().title("Table"))
+/// // Columns widths are constrained in the same way as Layout...
+/// .widths(&[Constraint::Length(5), Constraint::Length(5), Constraint::Length(10)])
+/// // ...and they can be separated by a fixed spacing.
+/// .column_spacing(1)
+/// // If you wish to highlight a row in any specific way when it is selected...
+/// .highlight_style(Style::default().add_modifier(Modifier::BOLD))
+/// // ...and potentially show a symbol in front of the selection.
+/// .highlight_symbol(">>");
+/// ```
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Table<'a> {
+ /// A block to wrap the widget in
+ block: Option<Block<'a>>,
+ /// Base style for the widget
+ style: Style,
+ /// Width constraints for each column
+ widths: &'a [Constraint],
+ /// Space between each column
+ column_spacing: u16,
+ /// Style used to render the selected row
+ highlight_style: Style,
+ /// Symbol in front of the selected rom
+ highlight_symbol: Option<&'a str>,
+ /// Optional header
+ header: Option<Row<'a>>,
+ /// Data to display in each row
+ rows: Vec<Row<'a>>,
+}
+
+impl<'a> Table<'a> {
+ pub fn new<T>(rows: T) -> Self
+ where
+ T: IntoIterator<Item = Row<'a>>,
+ {
+ Self {
+ block: None,
+ style: Style::default(),
+ widths: &[],
+ column_spacing: 1,
+ highlight_style: Style::default(),
+ highlight_symbol: None,
+ header: None,
+ rows: rows.into_iter().collect(),
+ }
+ }
+
+ pub fn block(mut self, block: Block<'a>) -> Self {
+ self.block = Some(block);
+ self
+ }
+
+ pub fn header(mut self, header: Row<'a>) -> Self {
+ self.header = Some(header);
+ self
+ }
+
+ pub fn widths(mut self, widths: &'a [Constraint]) -> Self {
+ let between_0_and_100 = |&w| match w {
+ Constraint::Percentage(p) => p <= 100,
+ _ => true,
+ };
+ assert!(
+ widths.iter().all(between_0_and_100),
+ "Percentages should be between 0 and 100 inclusively."
+ );
+ self.widths = widths;
+ self
+ }
+
+ pub fn style(mut self, style: Style) -> Self {
+ self.style = style;
+ self
+ }
+
+ pub fn highlight_symbol(mut self, highlight_symbol: &'a str) -> Self {
+ self.highlight_symbol = Some(highlight_symbol);
+ self
+ }
+
+ pub fn highlight_style(mut self, highlight_style: Style) -> Self {
+ self.highlight_style = highlight_style;
+ self
+ }
+
+ pub fn column_spacing(mut self, spacing: u16) -> Self {
+ self.column_spacing = spacing;
+ self
+ }
+
+ fn get_columns_widths(&self, max_width: u16, has_selection: bool) -> Vec<u16> {
+ let mut constraints = Vec::with_capacity(self.widths.len() * 2 + 1);
+ if has_selection {
+ let highlight_symbol_width =
+ self.highlight_symbol.map(|s| s.width() as u16).unwrap_or(0);
+ constraints.push(Constraint::Length(highlight_symbol_width));
+ }
+ for constraint in self.widths {
+ constraints.push(*constraint);
+ constraints.push(Constraint::Length(self.column_spacing));
+ }
+ if !self.widths.is_empty() {
+ constraints.pop();
+ }
+ let chunks = Layout::default()
+ .direction(Direction::Horizontal)
+ .constraints(constraints)
+ .expand_to_fill(false)
+ .split(Rect {
+ x: 0,
+ y: 0,
+ width: max_width,
+ height: 1,
+ });
+ let mut chunks = &chunks[..];
+ if has_selection {
+ chunks = &chunks[1..];
+ }
+ chunks.iter().step_by(2).map(|c| c.width).collect()
+ }
+
+ fn get_row_bounds(
+ &self,
+ selected: Option<usize>,
+ offset: usize,
+ max_height: u16,
+ ) -> (usize, usize) {
+ let offset = offset.min(self.rows.len().saturating_sub(1));
+ let mut start = offset;
+ let mut end = offset;
+ let mut height = 0;
+ for item in self.rows.iter().skip(offset) {
+ if height + item.height > max_height {
+ break;
+ }
+ height += item.total_height();
+ end += 1;
+ }
+
+ let selected = selected.unwrap_or(0).min(self.rows.len() - 1);
+ while selected >= end {
+ height = height.saturating_add(self.rows[end].total_height());
+ end += 1;
+ while height > max_height {
+ height = height.saturating_sub(self.rows[start].total_height());
+ start += 1;
+ }
+ }
+ while selected < start {
+ start -= 1;
+ height = height.saturating_add(self.rows[start].total_height());
+ while height > max_height {
+ end -= 1;
+ height = height.saturating_sub(self.rows[end].total_height());
+ }
+ }
+ (start, end)
+ }
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct TableState {
+ offset: usize,
+ selected: Option<usize>,
+}
+
+impl TableState {
+ pub fn selected(&self) -> Option<usize> {
+ self.selected
+ }
+
+ pub fn select(&mut self, index: Option<usize>) {
+ self.selected = index;
+ if index.is_none() {
+ self.offset = 0;
+ }
+ }
+
+ /// Returns a copy of the receiver's scroll offset.
+ ///
+ /// This is useful, for example, if you need to "synchronize" the scrolling of a `Table` and a `Paragraph`.
+ pub fn offset(&self) -> usize {
+ self.offset
+ }
+}
+
+impl<'a> StatefulWidget for Table<'a> {
+ type State = TableState;
+
+ fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
+ if area.area() == 0 {
+ return;
+ }
+ buf.set_style(area, self.style);
+ let table_area = match self.block.take() {
+ Some(b) => {
+ let inner_area = b.inner(area);
+ b.render(area, buf);
+ inner_area
+ }
+ None => area,
+ };
+
+ let has_selection = state.selected.is_some();
+ let columns_widths = self.get_columns_widths(table_area.width, has_selection);
+ let highlight_symbol = self.highlight_symbol.unwrap_or("");
+ let blank_symbol = " ".repeat(highlight_symbol.width());
+ let mut current_height = 0;
+ let mut rows_height = table_area.height;
+
+ // Draw header
+ if let Some(ref header) = self.header {
+ let max_header_height = table_area.height.min(header.total_height());
+ buf.set_style(
+ Rect {
+ x: table_area.left(),
+ y: table_area.top(),
+ width: table_area.width,
+ height: table_area.height.min(header.height),
+ },
+ header.style,
+ );
+ let mut col = table_area.left();
+ if has_selection {
+ col += (highlight_symbol.width() as u16).min(table_area.width);
+ }
+ for (width, cell) in columns_widths.iter().zip(header.cells.iter()) {
+ render_cell(
+ buf,
+ cell,
+ Rect {
+ x: col,
+ y: table_area.top(),
+ width: *width,
+ height: max_header_height,
+ },
+ );
+ col += *width + self.column_spacing;
+ }
+ current_height += max_header_height;
+ rows_height = rows_height.saturating_sub(max_header_height);
+ }
+
+ // Draw rows
+ if self.rows.is_empty() {
+ return;
+ }
+ let (start, end) = self.get_row_bounds(state.selected, state.offset, rows_height);
+ state.offset = start;
+ for (i, table_row) in self
+ .rows
+ .iter_mut()
+ .enumerate()
+ .skip(state.offset)
+ .take(end - start)
+ {
+ let (row, col) = (table_area.top() + current_height, table_area.left());
+ current_height += table_row.total_height();
+ let table_row_area = Rect {
+ x: col,
+ y: row,
+ width: table_area.width,
+ height: table_row.height,
+ };
+ buf.set_style(table_row_area, table_row.style);
+ let is_selected = state.selected.map(|s| s == i).unwrap_or(false);
+ let table_row_start_col = if has_selection {
+ let symbol = if is_selected {
+ highlight_symbol
+ } else {
+ &blank_symbol
+ };
+ let (col, _) =
+ buf.set_stringn(col, row, symbol, table_area.width as usize, table_row.style);
+ col
+ } else {
+ col
+ };
+ let mut col = table_row_start_col;
+ for (width, cell) in columns_widths.iter().zip(table_row.cells.iter()) {
+ render_cell(
+ buf,
+ cell,
+ Rect {
+ x: col,
+ y: row,
+ width: *width,
+ height: table_row.height,
+ },
+ );
+ col += *width + self.column_spacing;
+ }
+ if is_selected {
+ buf.set_style(table_row_area, self.highlight_style);
+ }
+ }
+ }
+}
+
+fn render_cell(buf: &mut Buffer, cell: &Cell, area: Rect) {
+ buf.set_style(area, cell.style);
+ for (i, spans) in cell.content.lines.iter().enumerate() {
+ if i as u16 >= area.height {
+ break;
+ }
+ buf.set_spans(area.x, area.y + i as u16, spans, area.width);
+ }
+}
+
+impl<'a> Widget for Table<'a> {
+ fn render(self, area: Rect, buf: &mut Buffer) {
+ let mut state = TableState::default();
+ StatefulWidget::render(self, area, buf, &mut state);
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ #[should_panic]
+ fn table_invalid_percentages() {
+ Table::new(vec![]).widths(&[Constraint::Percentage(110)]);
+ }
+}
diff --git a/src/ratatui/widgets/tabs.rs b/src/ratatui/widgets/tabs.rs
new file mode 100644
index 00000000..9b5f5469
--- /dev/null
+++ b/src/ratatui/widgets/tabs.rs
@@ -0,0 +1,129 @@
+use crate::ratatui::{
+ buffer::Buffer,
+ layout::Rect,
+ style::Style,
+ symbols,
+ text::{Span, Spans},
+ widgets::{Block, Widget},
+};
+
+/// A widget to display available tabs in a multiple panels context.
+///
+/// # Examples
+///
+/// ```
+/// # use ratatui::widgets::{Block, Borders, Tabs};
+/// # use ratatui::style::{Style, Color};
+/// # use ratatui::text::{Spans};
+/// # use ratatui::symbols::{DOT};
+/// let titles = ["Tab1", "Tab2", "Tab3", "Tab4"].iter().cloned().map(Spans::from).collect();
+/// Tabs::new(titles)
+/// .block(Block::default().title("Tabs").borders(Borders::ALL))
+/// .style(Style::default().fg(Color::White))
+/// .highlight_style(Style::default().fg(Color::Yellow))
+/// .divider(DOT);
+/// ```
+#[derive(Debug, Clone)]
+pub struct Tabs<'a> {
+ /// A block to wrap this widget in if necessary
+ block: Option<Block<'a>>,
+ /// One title for each tab
+ titles: Vec<Spans<'a>>,
+ /// The index of the selected tabs
+ selected: usize,
+ /// The style used to draw the text
+ style: Style,
+ /// Style to apply to the selected item
+ highlight_style: Style,
+ /// Tab divider
+ divider: Span<'a>,
+}
+
+impl<'a> Tabs<'a> {
+ pub fn new(titles: Vec<Spans<'a>>) -> Tabs<'a> {
+ Tabs {
+ block: None,
+ titles,
+ selected: 0,
+ style: Default::default(),
+ highlight_style: Default::default(),
+ divider: Span::raw(symbols::line::VERTICAL),
+ }
+ }
+
+ pub fn block(mut self, block: Block<'a>) -> Tabs<'a> {
+ self.block = Some(block);
+ self
+ }
+
+ pub fn select(mut self, selected: usize) -> Tabs<'a> {
+ self.selected = selected;
+ self
+ }
+
+ pub fn style(mut self, style: Style) -> Tabs<'a> {
+ self.style = style;
+ self
+ }
+
+ pub fn highlight_style(mut self, style: Style) -> Tabs<'a> {
+ self.highlight_style = style;
+ self
+ }
+
+ pub fn divider<T>(mut self, divider: T) -> Tabs<'a>
+ where
+ T: Into<Span<'a>>,
+ {
+ self.divider = divider.into();
+ self
+ }
+}
+
+impl<'a> Widget for Tabs<'a> {
+ fn render(mut self, area: Rect, buf: &mut Buffer) {
+ buf.set_style(area, self.style);
+ let tabs_area = match self.block.take() {
+ Some(b) => {
+ let inner_area = b.inner(area);
+ b.render(area, buf);
+ inner_area
+ }
+ None => area,
+ };
+
+ if tabs_area.height < 1 {
+ return;
+ }
+
+ let mut x = tabs_area.left();
+ let titles_length = self.titles.len();
+ for (i, title) in self.titles.into_iter().enumerate() {
+ let last_title = titles_length - 1 == i;
+ x = x.saturating_add(1);
+ let remaining_width = tabs_area.right().saturating_sub(x);
+ if remaining_width == 0 {
+ break;
+ }
+ let pos = buf.set_spans(x, tabs_area.top(), &title, remaining_width);
+ if i == self.selected {
+ buf.set_style(
+ Rect {
+ x,
+ y: tabs_area.top(),
+ width: pos.0.saturating_sub(x),
+ height: 1,
+ },
+ self.highlight_style,
+ );
+ }
+ x = pos.0.saturating_add(1);
+ let remaining_width = tabs_area.right().saturating_sub(x);
+ if remaining_width == 0 || last_title {
+ break;
+ }
+ let pos = buf.set_span(x, tabs_area.top(), &self.divider, remaining_width);
+ x = pos.0;
+ }
+ }
+}