diff options
| author | Ellie Huxtable <ellie@elliehuxtable.com> | 2023-03-31 22:57:37 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-03-31 22:57:37 +0100 |
| commit | a515b06bcb556c1be2d0fc3095cd778d413fe40d (patch) | |
| tree | 4b544de9aa53d6976177c08b91aa3943ef4d9e92 /src/ratatui | |
| parent | feat: add github action to test the nix builds (#833) (diff) | |
| download | atuin-a515b06bcb556c1be2d0fc3095cd778d413fe40d.zip | |
Vendor ratatui temporarily (#835)
* Vendor ratatui temporarily
Once https://github.com/tui-rs-revival/ratatui/pull/114 has been merged,
we can undo this! But otherwise we can't publish to crates.io with a git
dependency.
* make tests pass
* Shush.
* these literally just fail in nix, nowhere else
idk how to work with nix properly, and they're also not our tests
Diffstat (limited to 'src/ratatui')
36 files changed, 14682 insertions, 0 deletions
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. + +[](https://github.com/tui-rs-revival/ratatui/actions?query=workflow%3ACI+) +[](https://crates.io/crates/ratatui) +[](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. +//! +//!  +//! +//! # 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; + } + } +} |
