From f6a295a145b206c5947ee1c60642537e3e8c511b Mon Sep 17 00:00:00 2001 From: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> Date: Sun, 29 Jun 2025 19:03:47 +0100 Subject: [PATCH 1/2] add static export functionality with WebDriver Add static export functionality using WebDriver to control a host browser. - allow user to reuse the exporter object but also keep the old style Kaleido API - allow user to set browser capabilities via setter - allow user to configure html2pdf timeout - use data uri for CDN case and file for offline case when generating the html for static image export - there are size restrictions when using data-uri , webdriver cannot load large data-uri files, so for offline mode, default to using html returned by to_file - use build.rs to download webdriver chromedriver/geckodriver - contains documentation and add example for plotly_static - include an example on how to use static export for json data - refactored usage of imageformat between packages: plotly, plotly_kaleido, plotly_static - the new static export is included in the plotly crate as default and kaleido is marked as deprecated using deprecation warnings - added custom CI setup for Windows as Windows GitHub Action with Chrome setup is unique - expand ci with Firefox + Ubuntu for plotly_static - add ci step for publishing plotly_static Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- .github/scripts/build-book-examples.sh | 82 + .../scripts/setup-windows-static-export.ps1 | 104 + .github/workflows/book.yml | 14 +- .github/workflows/ci.yml | 155 +- .github/workflows/publish_plotly_static.yml | 22 + .github/workflows/release.yml | 7 + CHANGELOG.md | 2 + Cargo.toml | 2 +- README.md | 32 +- docs/book/src/SUMMARY.md | 1 + docs/book/src/fundamentals.md | 10 +- .../src/fundamentals/static_image_export.md | 229 + docs/book/src/getting_started.md | 17 +- examples/Cargo.toml | 1 + examples/custom_controls/src/main.rs | 2 +- .../Cargo.toml | 4 +- .../consistent_static_format_export/README.md | 2 +- .../src/main.rs | 40 +- examples/kaleido/src/main.rs | 18 +- examples/static_export/Cargo.toml | 12 + examples/static_export/README.md | 93 + examples/static_export/src/main.rs | 112 + plotly/Cargo.toml | 35 +- plotly/{templates => resource}/plotly.min.js | 0 .../tex-mml-chtml-3.2.0.js | 0 .../{templates => resource}/tex-svg-3.2.2.js | 0 plotly/src/common/color.rs | 3 +- plotly/src/configuration.rs | 1 - plotly/src/lib.rs | 31 +- plotly/src/plot.rs | 522 ++- plotly/templates/static_plot.html | 30 +- plotly/templates/template.json | 787 ---- plotly_derive/Cargo.toml | 2 +- plotly_kaleido/Cargo.toml | 2 +- plotly_kaleido/src/lib.rs | 115 +- plotly_static/Cargo.toml | 45 + plotly_static/LICENSE | 21 + plotly_static/README.md | 158 + plotly_static/build.rs | 342 ++ plotly_static/examples/README.md | 110 + plotly_static/examples/generate_static.rs | 135 + plotly_static/examples/sample_plot.json | 37 + plotly_static/examples/test_chrome_path.rs | 28 + plotly_static/resource/html2pdf.bundle.min.js | 3 + plotly_static/resource/plotly.min.js | 3879 +++++++++++++++++ plotly_static/resource/tex-mml-chtml-3.2.0.js | 1 + plotly_static/resource/tex-svg-3.2.2.js | 1 + plotly_static/src/lib.rs | 1459 +++++++ plotly_static/src/template.rs | 280 ++ plotly_static/src/webdriver.rs | 669 +++ 50 files changed, 8655 insertions(+), 1002 deletions(-) create mode 100755 .github/scripts/build-book-examples.sh create mode 100644 .github/scripts/setup-windows-static-export.ps1 create mode 100644 .github/workflows/publish_plotly_static.yml create mode 100644 examples/static_export/Cargo.toml create mode 100644 examples/static_export/README.md create mode 100644 examples/static_export/src/main.rs rename plotly/{templates => resource}/plotly.min.js (100%) rename plotly/{templates => resource}/tex-mml-chtml-3.2.0.js (100%) rename plotly/{templates => resource}/tex-svg-3.2.2.js (100%) delete mode 100644 plotly/templates/template.json create mode 100644 plotly_static/Cargo.toml create mode 100644 plotly_static/LICENSE create mode 100644 plotly_static/README.md create mode 100644 plotly_static/build.rs create mode 100644 plotly_static/examples/README.md create mode 100644 plotly_static/examples/generate_static.rs create mode 100644 plotly_static/examples/sample_plot.json create mode 100644 plotly_static/examples/test_chrome_path.rs create mode 100644 plotly_static/resource/html2pdf.bundle.min.js create mode 100644 plotly_static/resource/plotly.min.js create mode 100644 plotly_static/resource/tex-mml-chtml-3.2.0.js create mode 100644 plotly_static/resource/tex-svg-3.2.2.js create mode 100644 plotly_static/src/lib.rs create mode 100644 plotly_static/src/template.rs create mode 100644 plotly_static/src/webdriver.rs diff --git a/.github/scripts/build-book-examples.sh b/.github/scripts/build-book-examples.sh new file mode 100755 index 00000000..8c428aee --- /dev/null +++ b/.github/scripts/build-book-examples.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +# Script to build examples needed for book artifacts +# This script is used by both the CI build_book job and the book.yml workflow + +# Note: static_export example requires Chrome setup for WebDriver functionality +# The CI jobs set up Chrome, while when running locally, you may need to setup +# your browser and webdriver manually to ensure this example works properly. + +set -e # Exit on any error + +# Function to display usage +usage() { + echo "Usage: $0 " + echo " examples_directory: Path to the examples directory (e.g., examples or \$GITHUB_WORKSPACE/examples)" + echo "" + echo "This script builds all examples needed for the book documentation." + exit 1 +} + +# Check if examples directory is provided +if [ $# -ne 1 ]; then + usage +fi + +EXAMPLES_DIR="$1" + +# Validate that the examples directory exists +if [ ! -d "$EXAMPLES_DIR" ]; then + echo "Error: Examples directory '$EXAMPLES_DIR' does not exist" + exit 1 +fi + +echo "Building examples for book artifacts from: $EXAMPLES_DIR" + +# List of examples needed for the book, sorted alphabetically +# These examples generate HTML files that are included in the book documentation +BOOK_EXAMPLES=( + "3d_charts" + "basic_charts" + "custom_controls" + "financial_charts" + "scientific_charts" + "shapes" + "static_export" + "statistical_charts" + "subplots" + "themes" +) + +# Build each example +for example in "${BOOK_EXAMPLES[@]}"; do + example_path="$EXAMPLES_DIR/$example" + + if [ ! -d "$example_path" ]; then + echo "Warning: Example directory '$example_path' does not exist, skipping..." + continue + fi + + echo "Building $example example..." + cd "$example_path" + + # Check if Cargo.toml exists + if [ ! -f "Cargo.toml" ]; then + echo "Warning: No Cargo.toml found in $example_path, skipping..." + cd - > /dev/null + continue + fi + + # Run the example + if cargo run; then + echo "✓ Successfully built $example" + else + echo "✗ Failed to build $example" + exit 1 + fi + + # Return to the original directory + cd - > /dev/null +done + +echo "All examples built successfully!" diff --git a/.github/scripts/setup-windows-static-export.ps1 b/.github/scripts/setup-windows-static-export.ps1 new file mode 100644 index 00000000..96796833 --- /dev/null +++ b/.github/scripts/setup-windows-static-export.ps1 @@ -0,0 +1,104 @@ +# Windows environment setup for plotly static export tests +# This script sets up Chrome, chromedriver, and environment variables for Windows CI + +param( + [string]$ChromeVersion, + [string]$ChromePath, + [string]$ChromeDriverPath +) + +Write-Host "=== Setting up Windows environment for static export ===" + +# Find chromedriver path +$chromedriverPath = $ChromeDriverPath +if (-not (Test-Path $chromedriverPath)) { + Write-Host "Action output chromedriver path not found, searching for alternatives..." + + $commonPaths = @( + "C:\Program Files\Google\Chrome\Application\chromedriver.exe", + "C:\Program Files (x86)\Google\Chrome\Application\chromedriver.exe", + "$env:USERPROFILE\AppData\Local\Google\Chrome\Application\chromedriver.exe", + "$env:PROGRAMFILES\Google\Chrome\Application\chromedriver.exe", + "$env:PROGRAMFILES(X86)\Google\Chrome\Application\chromedriver.exe" + ) + + foreach ($path in $commonPaths) { + if (Test-Path $path) { + Write-Host "Using chromedriver from: $path" + $chromedriverPath = $path + break + } + } +} + +# Find Chrome path +$chromePath = $ChromePath +if (-not (Test-Path $chromePath)) { + # Try the tool cache path first + $toolCachePath = "C:\hostedtoolcache\windows\setup-chrome\chromium\$ChromeVersion\x64\chrome.exe" + if (Test-Path $toolCachePath) { + $chromePath = $toolCachePath + Write-Host "Using Chrome from setup-chrome installation: $chromePath" + } else { + # Fallback: search for Chrome in the tool cache + $toolCacheDir = "C:\hostedtoolcache\windows\setup-chrome\chromium" + if (Test-Path $toolCacheDir) { + $chromeExe = Get-ChildItem -Path $toolCacheDir -Recurse -Name "chrome.exe" | Select-Object -First 1 + if ($chromeExe) { + $chromePath = Join-Path $toolCacheDir $chromeExe + Write-Host "Using Chrome from tool cache search: $chromePath" + } else { + $chromePath = "C:\Program Files\Google\Chrome\Application\chrome.exe" + Write-Host "Using system Chrome: $chromePath" + } + } else { + $chromePath = "C:\Program Files\Google\Chrome\Application\chrome.exe" + Write-Host "Using system Chrome: $chromePath" + } + } +} + +# Set environment variables +$env:WEBDRIVER_PATH = $chromedriverPath +$env:BROWSER_PATH = $chromePath +$env:RUST_LOG = "debug" +$env:RUST_BACKTRACE = "1" +$env:ANGLE_DEFAULT_PLATFORM = "swiftshader" + +Write-Host "Environment variables set:" +Write-Host "WEBDRIVER_PATH: $env:WEBDRIVER_PATH" +Write-Host "BROWSER_PATH: $env:BROWSER_PATH" +Write-Host "RUST_LOG: $env:RUST_LOG" +Write-Host "RUST_BACKTRACE: $env:RUST_BACKTRACE" + +# Verify paths exist +if (-not (Test-Path $env:WEBDRIVER_PATH)) { + Write-Error "Chromedriver executable not found at: $env:WEBDRIVER_PATH" + Write-Host "Available chromedriver locations:" + Get-ChildItem -Path "C:\Program Files\Google\Chrome\Application\" -Name "chromedriver*" -ErrorAction SilentlyContinue + Get-ChildItem -Path "C:\Program Files (x86)\Google\Chrome\Application\" -Name "chromedriver*" -ErrorAction SilentlyContinue + exit 1 +} + +if (-not (Test-Path $env:BROWSER_PATH)) { + Write-Error "Chrome not found at: $env:BROWSER_PATH" + exit 1 +} + +# Test Chrome version +try { + $chromeVersion = & "$env:BROWSER_PATH" --version 2>&1 + Write-Host "Chrome version: $chromeVersion" +} catch { + Write-Host "Failed to get Chrome version: $_" +} + +# Test chromedriver version +try { + $chromedriverVersion = & "$env:WEBDRIVER_PATH" --version 2>&1 + Write-Host "Chromedriver version: $chromedriverVersion" +} catch { + Write-Host "Failed to get chromedriver version: $_" +} + +Write-Host "=== Windows environment setup completed ===" \ No newline at end of file diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index b08ad106..07a6ed7d 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -15,16 +15,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Setup Chrome (for static_export) + uses: browser-actions/setup-chrome@v1 + with: + chrome-version: 'latest' + install-chromedriver: true - run: cargo install mdbook --no-default-features --features search --vers "^0.4" --locked --quiet - name: Build examples - run: | - cd ${{ github.workspace }}/examples/basic_charts && cargo run - cd ${{ github.workspace }}/examples/statistical_charts && cargo run - cd ${{ github.workspace }}/examples/scientific_charts && cargo run - cd ${{ github.workspace }}/examples/financial_charts && cargo run - cd ${{ github.workspace }}/examples/3d_charts && cargo run - cd ${{ github.workspace }}/examples/subplots && cargo run - cd ${{ github.workspace }}/examples/shapes && cargo run + run: .github/scripts/build-book-examples.sh ${{ github.workspace }}/examples - run: mdbook build docs/book - name: Checkout gh-pages branch run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc382cfc..153fc4dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,14 +33,22 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Setup Chrome + uses: browser-actions/setup-chrome@v1 + with: + chrome-version: 'latest' + install-chromedriver: true - uses: dtolnay/rust-toolchain@stable with: components: clippy targets: wasm32-unknown-unknown + # lint plotly_static for all features + - run: cargo clippy -p plotly_static --features geckodriver,webdriver_download -- -D warnings -A deprecated + - run: cargo clippy -p plotly_static --features chromedriver,webdriver_download -- -D warnings -A deprecated # lint the main library workspace for non-wasm target - - run: cargo clippy --all-features -- -D warnings + - run: cargo clippy --features all -- -D warnings -A deprecated # lint the non-wasm examples - - run: cd ${{ github.workspace }}/examples && cargo clippy --workspace --exclude "wasm*" -- -D warnings + - run: cd ${{ github.workspace }}/examples && cargo clippy --workspace --exclude "wasm*" --exclude "kaleido" -- -D warnings # lint the plotly library for wasm target - run: cargo clippy --package plotly --target wasm32-unknown-unknown -- -D warnings # lint the wasm examples @@ -65,14 +73,121 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + include: + - os: ubuntu-latest + browser: chrome + features: plotly_ndarray,plotly_image,static_export_default,debug + - os: ubuntu-latest + browser: firefox + features: plotly_ndarray,plotly_image,static_export_geckodriver,static_export_wd_download,debug + - os: windows-latest + browser: chrome + features: plotly_ndarray,plotly_image,static_export_chromedriver,debug + - os: macos-latest + browser: chrome + features: plotly_ndarray,plotly_image,static_export_default,debug runs-on: ${{ matrix.os }} + timeout-minutes: ${{ matrix.os == 'windows-latest' && 30 || 10 }} steps: + - name: Setup Chrome + if: matrix.browser == 'chrome' + uses: browser-actions/setup-chrome@v1 + with: + chrome-version: 'latest' + install-chromedriver: true + id: setup-chrome + + - name: Setup Firefox + if: matrix.browser == 'firefox' + uses: browser-actions/setup-firefox@v1 + with: + firefox-version: 'latest' + id: setup-firefox + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable - - run: cargo test --features plotly_ndarray,plotly_image,kaleido - - if: ${{ matrix.os == 'windows-latest' }} - run: gci -recurse -filter "*example*" + + # Cache cargo registry for all platforms + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-registry- + + # Windows-specific environment setup for static export tests + - name: Setup Windows environment for static export + if: matrix.os == 'windows-latest' + run: | + .\.github\scripts\setup-windows-static-export.ps1 ` + -ChromeVersion "${{ steps.setup-chrome.outputs.chrome-version }}" ` + -ChromePath "${{ steps.setup-chrome.outputs.chrome-path }}" ` + -ChromeDriverPath "${{ steps.setup-chrome.outputs.chromedriver-path }}" + + # Run tests on Ubuntu with Chrome + - name: Run tests (${{ matrix.os }} - Chrome) + if: matrix.os == 'ubuntu-latest' && matrix.browser == 'chrome' + run: cargo test --workspace --features ${{ matrix.features }} --exclude plotly_kaleido + + # Install xvfb for Firefox WebGL support + - name: Install xvfb + if: matrix.os == 'ubuntu-latest' && matrix.browser == 'firefox' + run: | + sudo apt-get update + sudo apt-get install -y xvfb + + # Run tests on Ubuntu with Firefox + - name: Run tests (${{ matrix.os }} - Firefox) + if: matrix.os == 'ubuntu-latest' && matrix.browser == 'firefox' + run: | + # Set environment variables for Firefox WebDriver + export BROWSER_PATH="${{ steps.setup-firefox.outputs.firefox-path }}" + export RUST_LOG="debug" + export RUST_BACKTRACE="1" + + echo "Environment variables set:" + echo "BROWSER_PATH: $BROWSER_PATH" + echo "RUST_LOG: $RUST_LOG" + + xvfb-run -s "-screen 0 1920x1080x24" cargo test --workspace --features ${{ matrix.features }} --exclude plotly_kaleido + + # Run tests on macOS with Chrome + - name: Run tests (${{ matrix.os }} - Chrome) + if: matrix.os == 'macos-latest' && matrix.browser == 'chrome' + run: cargo test --workspace --features ${{ matrix.features }} --exclude plotly_kaleido + + # Run tests on Windows with Chrome WebDriver + - name: Run tests (${{ matrix.os }} - Chrome) + if: matrix.os == 'windows-latest' && matrix.browser == 'chrome' + shell: pwsh + run: | + # Set environment variables for WebDriver + $env:WEBDRIVER_PATH = "${{ steps.setup-chrome.outputs.chromedriver-path }}" + $env:BROWSER_PATH = "${{ steps.setup-chrome.outputs.chrome-path }}" + $env:RUST_LOG = "debug" + $env:RUST_BACKTRACE = "1" + $env:ANGLE_DEFAULT_PLATFORM = "swiftshader" + + Write-Host "Environment variables set:" + Write-Host "WEBDRIVER_PATH: $env:WEBDRIVER_PATH" + Write-Host "BROWSER_PATH: $env:BROWSER_PATH" + + cargo test --workspace --features ${{ matrix.features }} --exclude plotly_kaleido + + - name: Upload static image(s) artifact + uses: actions/upload-artifact@v4 + with: + name: example-pdf-${{ matrix.os }}-${{ matrix.browser }} + path: | + ${{ github.workspace }}/plotly_static/static_example.* + ${{ github.workspace }}/plotly/plotly_example.* + ${{ github.workspace }}/plotly/plotly_example_surface.* + retention-days: 30 code-coverage: name: Code Coverage @@ -112,6 +227,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + + - name: Setup Chrome (for static_export) + if: matrix.example == 'static_export' + uses: browser-actions/setup-chrome@v1 + with: + chrome-version: 'latest' + install-chromedriver: true + - uses: dtolnay/rust-toolchain@stable - run: cd ${{ github.workspace }}/examples/${{ matrix.example }} && cargo build @@ -134,17 +257,17 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + + - name: Setup Chrome (for static_export) + uses: browser-actions/setup-chrome@v1 + with: + chrome-version: 'latest' + install-chromedriver: true + id: setup-chrome + - uses: dtolnay/rust-toolchain@stable - run: cargo install mdbook --no-default-features --features search --vers "^0.4" --locked --quiet - name: Build examples to generate needed html files - run: | - cd ${{ github.workspace }}/examples/basic_charts && cargo run - cd ${{ github.workspace }}/examples/statistical_charts && cargo run - cd ${{ github.workspace }}/examples/scientific_charts && cargo run - cd ${{ github.workspace }}/examples/financial_charts && cargo run - cd ${{ github.workspace }}/examples/3d_charts && cargo run - cd ${{ github.workspace }}/examples/subplots && cargo run - cd ${{ github.workspace }}/examples/shapes && cargo run - cd ${{ github.workspace }}/examples/themes && cargo run + run: .github/scripts/build-book-examples.sh ${{ github.workspace }}/examples - name: Build book - run: mdbook build docs/book \ No newline at end of file + run: mdbook build docs/book diff --git a/.github/workflows/publish_plotly_static.yml b/.github/workflows/publish_plotly_static.yml new file mode 100644 index 00000000..461219d7 --- /dev/null +++ b/.github/workflows/publish_plotly_static.yml @@ -0,0 +1,22 @@ +name: Publish plotly-static + +on: + workflow_dispatch: + +jobs: + create-crates-io-release: + name: Deploy to crates.io + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Setup Chrome (for static_export) + uses: browser-actions/setup-chrome@v1 + with: + chrome-version: 'latest' + install-chromedriver: true + + - run: cargo login ${{ env.CRATES_IO_TOKEN }} + env: + CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} + - run: cargo publish --allow-dirty -p plotly_static --features webdriver_downlaod,chromedriver diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 25ffd779..8f38bf32 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,10 +15,17 @@ jobs: - run: cargo login ${{ env.CRATES_IO_TOKEN }} env: CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} + - name: Setup Chrome (for static_export) + uses: browser-actions/setup-chrome@v1 + with: + chrome-version: 'latest' + install-chromedriver: true - run: cargo publish --allow-dirty -p plotly_derive - run: sleep 10 - run: cargo publish --allow-dirty -p plotly_kaleido - run: sleep 10 + - run: cargo publish --allow-dirty -p plotly_static --features webdriver_downlaod,chromedriver + - run: sleep 10 - run: cargo publish --allow-dirty -p plotly create-gh-release: diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f816656..027d822a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - [[#314](https://github.com/plotly/plotly.rs/pull/314)] Add axis range bounds support - [[#317](https://github.com/plotly/plotly.rs/pull/317)] Show rangebreak usage with ints - [[#318](https://github.com/plotly/plotly.rs/pull/318)] Add slider support +- [[#319](https://github.com/plotly/plotly.rs/pull/319)] Introduce `plotly_static` package for static export of plots using WebDriver driven browsers + ### Fixed - [[#284](https://github.com/plotly/plotly.rs/pull/284)] Allow plotly package to be compiled for android diff --git a/Cargo.toml b/Cargo.toml index 5f2ceea2..4d31d6d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "2" -members = ["plotly", "plotly_derive", "plotly_kaleido"] +members = ["plotly", "plotly_derive", "plotly_kaleido", "plotly_static"] diff --git a/README.md b/README.md index 149b3005..243b5724 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,6 @@ A plotting library for Rust powered by [Plotly.js](https://plot.ly/javascript/). Documentation and numerous interactive examples are available in the [Plotly.rs Book](https://plotly.github.io/plotly.rs/content/getting_started.html), the [examples/](https://github.com/plotly/plotly.rs/tree/main/examples) directory and [docs.rs](https://docs.rs/crate/plotly). - For changes since the last version, please consult the [changelog](https://github.com/plotly/plotly.rs/tree/main/CHANGELOG.md). # Basic Usage @@ -62,7 +61,7 @@ Add this to your `Cargo.toml`: ```toml [dependencies] -plotly = "0.12" +plotly = "0.13" ``` ## Exporting a single Interactive Plot @@ -108,7 +107,32 @@ When the applications developed with `plotly.rs` are intended for other targets Kaleido binaries are available on Github [release page](https://github.com/plotly/Kaleido/releases). It currently supports Linux(`x86_64`), Windows(`x86_64`) and MacOS(`x86_64`/`aarch64`). -## Exporting a Static Images +## Exporting Static Images with plotly_static (Recommended) + +The recommended way to export static images is using the `plotly_static` backend, which uses a headless browser via WebDriver (Chrome or Firefox) for rendering. This is available via the `static_export_default` feature: + +```toml +[dependencies] +plotly = { version = "0.13", features = ["static_export_default"] } +``` + +This supports PNG, JPEG, WEBP, SVG, and PDF formats: + +```rust +use plotly::{Plot, Scatter, ImageFormat}; + +let mut plot = Plot::new(); +plot.add_trace(Scatter::new(vec![0, 1, 2], vec![2, 1, 0])); + +plot.write_image("out.png", ImageFormat::PNG, 800, 600, 1.0)?; +plot.write_image("out.svg", ImageFormat::SVG, 800, 600, 1.0)?; +let base64_data = plot.to_base64(ImageFormat::PNG, 800, 600, 1.0)?; +let svg_string = plot.to_svg(800, 600, 1.0)?; +``` + +**Note:** This feature requires a WebDriver-compatible browser (Chrome or Firefox) as well as a Webdriver (chromedriver/geckodriver) to be available on the system. For advanced usage, see the [`plotly_static` crate documentation](https://docs.rs/plotly_static/). + +## Exporting Static Images with Kaleido (to be deprecated) Enable the `kaleido` feature and opt in for automatic downloading of the `kaleido` binaries by doing the following @@ -116,7 +140,7 @@ Enable the `kaleido` feature and opt in for automatic downloading of the `kaleid # Cargo.toml [dependencies] -plotly = { version = "0.12", features = ["kaleido", "kaleido_download"] } +plotly = { version = "0.13", features = ["kaleido", "kaleido_download"] } ``` Alternatively, enable only the `kaleido` feature and manually install Kaleido. diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md index c8d24e20..f53ab8c5 100644 --- a/docs/book/src/SUMMARY.md +++ b/docs/book/src/SUMMARY.md @@ -7,6 +7,7 @@ - [ndarray Support](./fundamentals/ndarray_support.md) - [Shapes](./fundamentals/shapes.md) - [Themes](./fundamentals/themes.md) + - [Static Image Export](./fundamentals/static_image_export.md) - [Recipes](./recipes.md) - [Basic Charts](./recipes/basic_charts.md) - [Scatter Plots](./recipes/basic_charts/scatter_plots.md) diff --git a/docs/book/src/fundamentals.md b/docs/book/src/fundamentals.md index 9fea76a1..e231b130 100644 --- a/docs/book/src/fundamentals.md +++ b/docs/book/src/fundamentals.md @@ -18,4 +18,12 @@ # Fundamentals -Functionality that applies to the library as a whole is described in the next sections. \ No newline at end of file +Functionality that applies to the library as a whole is described in the next sections. + +## Core Features + +- **[Jupyter Support](./fundamentals/jupyter_support.md)**: Interactive plotting in Jupyter notebooks +- **[ndarray Support](./fundamentals/ndarray_support.md)**: Integration with the ndarray crate for numerical computing +- **[Shapes](./fundamentals/shapes.md)**: Adding shapes and annotations to plots +- **[Themes](./fundamentals/themes.md)**: Customizing plot appearance with themes +- **[Static Image Export](./fundamentals/static_image_export.md)**: Exporting plots to static images (PNG, JPEG, SVG, PDF) using WebDriver \ No newline at end of file diff --git a/docs/book/src/fundamentals/static_image_export.md b/docs/book/src/fundamentals/static_image_export.md index 0540b0b5..3eec1438 100644 --- a/docs/book/src/fundamentals/static_image_export.md +++ b/docs/book/src/fundamentals/static_image_export.md @@ -1 +1,230 @@ # Static Image Export + +The `plotly` crate provides static image export functionality through the `plotly_static` crate, which uses WebDriver and headless browsers to render plots as static images. + +## Overview + +Static image export allows you to convert Plotly plots into various image formats (PNG, JPEG, WEBP, SVG, PDF) for use in reports, web applications, or any scenario where you need static images. + +## Feature Flags + +The static export functionality is controlled by feature flags in the main `plotly` crate: + +### Required Features (choose one): +- `static_export_chromedriver`: Uses Chrome/Chromium for rendering (requires chromedriver) +- `static_export_geckodriver`: Uses Firefox for rendering (requires geckodriver) + +### Optional Features: +- `static_export_wd_download`: Automatically downloads WebDriver binaries at build time +- `static_export_default`: Convenience feature that includes chromedriver + downloader + +### Cargo.toml Configuration Examples: + +```toml +# Basic usage with manual Chromedriver installation +[dependencies] +plotly = { version = "0.13", features = ["static_export_chromedriver"] } + +# With automatic Chromedriver download +[dependencies] +plotly = { version = "0.13", features = ["static_export_chromedriver", "static_export_wd_download"] } + +# Recommended: Default configuration with Chromedriver + auto-download +[dependencies] +plotly = { version = "0.13", features = ["static_export_default"] } +``` + +## Prerequisites + +1. **WebDriver Installation**: You need either chromedriver or geckodriver installed + - Chrome: Download from https://chromedriver.chromium.org/ + - Firefox: Download from https://github.com/mozilla/geckodriver/releases + - Or use the `static_export_wd_download` feature for automatic download + +2. **Browser Installation**: You need Chrome/Chromium or Firefox installed + +3. **Environment Variables** (optional): + - Set `WEBDRIVER_PATH` to specify custom WebDriver binary location (should point to the full executable path) + - Set `BROWSER_PATH` to specify custom browser binary location (should point to the full executable path) + + ```bash + export WEBDRIVER_PATH=/path/to/chromedriver + export BROWSER_PATH=/path/to/chrome + ``` + +## Basic Usage + +### Simple Export + +```rust +use plotly::{Plot, Scatter}; +use plotly::plotly_static::ImageFormat; + +let mut plot = Plot::new(); +plot.add_trace(Scatter::new(vec![1, 2, 3], vec![4, 5, 6])); + +// Export to PNG file +plot.write_image("my_plot", ImageFormat::PNG, 800, 600, 1.0) + .expect("Failed to export plot"); +``` + +### Efficient Exporter Reuse + +For better performance when exporting multiple plots, reuse a single `StaticExporter`: + +```rust +use plotly::{Plot, Scatter}; +use plotly::plotly_static::{StaticExporterBuilder, ImageFormat}; + +let mut plot1 = Plot::new(); +plot1.add_trace(Scatter::new(vec![1, 2, 3], vec![4, 5, 6])); + +let mut plot2 = Plot::new(); +plot2.add_trace(Scatter::new(vec![2, 3, 4], vec![5, 6, 7])); + +// Create a single exporter to reuse +let mut exporter = StaticExporterBuilder::default() + .build() + .expect("Failed to create StaticExporter"); + +// Export multiple plots using the same exporter +plot1.write_image_with_exporter(&mut exporter, "plot1", ImageFormat::PNG, 800, 600, 1.0) + .expect("Failed to export plot1"); +plot2.write_image_with_exporter(&mut exporter, "plot2", ImageFormat::JPEG, 800, 600, 1.0) + .expect("Failed to export plot2"); +``` + +## Supported Formats + +### Raster Formats +- **PNG**: Portable Network Graphics, lossless compression +- **JPEG**: Joint Photographic Experts Group, lossy compression (smaller files) +- **WEBP**: Google's image format + +### Vector Formats +- **SVG**: Scalable Vector Graphics +- **PDF**: Portable Document Format + +### Deprecated +- **EPS**: Encapsulated PostScript (will be removed in version 0.14.0) + +## String Export + +For web applications or APIs, you can export to strings: + +```rust +use plotly::{Plot, Scatter}; +use plotly::plotly_static::{StaticExporterBuilder, ImageFormat}; + +let mut plot = Plot::new(); +plot.add_trace(Scatter::new(vec![1, 2, 3], vec![4, 5, 6])); + +let mut exporter = StaticExporterBuilder::default() + .build() + .expect("Failed to create StaticExporter"); + +// Get base64 data (useful for embedding in HTML) +let base64_data = plot.to_base64_with_exporter(&mut exporter, ImageFormat::PNG, 400, 300, 1.0) + .expect("Failed to export plot"); + +// Get SVG data (vector format, scalable) +let svg_data = plot.to_svg_with_exporter(&mut exporter, 400, 300, 1.0) + .expect("Failed to export plot"); +``` + +## Advanced Configuration + +### Custom WebDriver Configuration + +```rust +use plotly::plotly_static::StaticExporterBuilder; + +let mut exporter = StaticExporterBuilder::default() + .webdriver_port(4445) // Use different port for parallel operations + .spawn_webdriver(true) // Explicitly spawn WebDriver + .offline_mode(true) // Use bundled JavaScript (no internet required) + .webdriver_browser_caps(vec![ + "--headless".to_string(), + "--no-sandbox".to_string(), + "--disable-gpu".to_string(), + "--disable-dev-shm-usage".to_string(), + ]) + .build() + .expect("Failed to create StaticExporter"); +``` + +### Parallel Usage + +For parallel operations (tests, etc.), use unique ports: + +```rust +use plotly::plotly_static::StaticExporterBuilder; +use std::sync::atomic::{AtomicU32, Ordering}; + +// Generate unique ports for parallel usage +static PORT_COUNTER: AtomicU32 = AtomicU32::new(4444); + +fn get_unique_port() -> u32 { + PORT_COUNTER.fetch_add(1, Ordering::SeqCst) +} + +// Each thread/process should use a unique port +let mut exporter = StaticExporterBuilder::default() + .webdriver_port(get_unique_port()) + .build() + .expect("Failed to build StaticExporter"); +``` + +## Logging Support + +Enable logging for debugging and monitoring: + +```rust +use plotly::plotly_static::StaticExporterBuilder; + +// Initialize logging (typically done once at the start of your application) +env_logger::init(); + +// Set log level via environment variable +// RUST_LOG=debug cargo run + +let mut exporter = StaticExporterBuilder::default() + .build() + .expect("Failed to create StaticExporter"); +``` + +## Performance Considerations + +- **Exporter Reuse**: Create a single `StaticExporter` and reuse it for multiple plots +- **Parallel Usage**: Use unique ports for parallel operations (tests, etc.) +- **Resource Management**: The exporter automatically manages WebDriver lifecycle +- **Format Selection**: Choose appropriate formats for your use case: + - PNG: Good quality, lossless + - JPEG: Smaller files, lossy + - SVG: Scalable, good for web + - PDF: Good for printing + +## Complete Example + +See the [static export example](../../../examples/static_export/) for a complete working example that demonstrates: + +- Multiple export formats +- Exporter reuse +- String export +- Logging +- Error handling + +To run the example: + +```bash +cd examples/static_export +cargo run +``` +**NOTE** Set `RUST_LOG=debug` to see detailed WebDriver operations and troubleshooting information. + +## Related Documentation + +- [plotly_static crate documentation](https://docs.rs/plotly_static/) +- [WebDriver specification](https://w3c.github.io/webdriver/) +- [GeckoDriver documentation](https://firefox-source-docs.mozilla.org/testing/geckodriver/) +- [ChromeDriver documentation](https://chromedriver.chromium.org/) diff --git a/docs/book/src/getting_started.md b/docs/book/src/getting_started.md index 12744c46..512a279e 100644 --- a/docs/book/src/getting_started.md +++ b/docs/book/src/getting_started.md @@ -22,7 +22,7 @@ To start using [plotly.rs](https://github.com/plotly/plotly.rs) in your project ```toml [dependencies] -plotly = "0.12" +plotly = "0.13" ``` [Plotly.rs](https://github.com/plotly/plotly.rs) is ultimately a thin wrapper around the `plotly.js` library. The main job of this library is to provide `structs` and `enums` which get serialized to `json` and passed to the `plotly.js` library to actually do the heavy lifting. As such, if you are familiar with `plotly.js` or its derivatives (e.g. the equivalent Python library), then you should find [`plotly.rs`](https://github.com/plotly/plotly.rs) intuitive to use. @@ -91,15 +91,26 @@ plot.write_image("/home/user/plot_name.ext", ImageFormat::PNG, 1280, 900, 1.0); The extension in the file-name path is optional as the appropriate extension (`ImageFormat::PNG`) will be included. Note that in all functions that save files to disk, both relative and absolute paths are supported. -## Saving Plots +## Saving Plots with Kaleido (legacy) To add the ability to save plots in the following formats: png, jpeg, webp, svg, pdf and eps, you can use the `kaleido` feature. This feature depends on [plotly/Kaleido](https://github.com/plotly/Kaleido): a cross-platform open source library for generating static images. All the necessary binaries have been included with `plotly_kaleido` for `Linux`, `Windows` and `MacOS`. Previous versions of [plotly.rs](https://github.com/plotly/plotly.rs) used the `orca` feature, however, this has been deprecated as it provided the same functionality but required additional installation steps. To enable the `kaleido` feature add the following to your `Cargo.toml`: ```toml [dependencies] -plotly = { version = "0.12", features = ["kaleido"] } +plotly = { version = "0.13", features = ["kaleido"] } ``` +## Static Image Export with WebDriver (recommended) + +For static image export using WebDriver and headless browsers, you can use the `plotly_static` feature. This feature supports the same formats as Kaleido (png, jpeg, webp, svg, pdf) but uses WebDriver for the static export process. To enable static export, add the following to your `Cargo.toml`: + +```toml +[dependencies] +plotly = { version = "0.13", features = ["static_export_default"] } +``` + +The `static_export_default` feature includes Chrome WebDriver support with automatic download. For Firefox support, use `static_export_geckodriver` instead. See the [Static Image Export](../fundamentals/static_image_export.md) chapter for a detailed usage example. + ## WebAssembly Support As of v0.8.0, [plotly.rs](https://github.com/plotly/plotly.rs) can now be used in a `Wasm` environment by enabling the `wasm` feature in your `Cargo.toml`: diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 3550ecb3..ebbe9f87 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -15,6 +15,7 @@ members = [ "scientific_charts", "shapes", "statistical_charts", + "static_export", "subplots", "themes" ] diff --git a/examples/custom_controls/src/main.rs b/examples/custom_controls/src/main.rs index 694d5d4f..a82d2da7 100644 --- a/examples/custom_controls/src/main.rs +++ b/examples/custom_controls/src/main.rs @@ -426,7 +426,7 @@ fn main() { bar_chart_with_modifiable_bar_mode(false, "bar_chart"); // Silder examples - bar_chart_with_slider_customization(true, "bar_chart_with_slider_customization"); + bar_chart_with_slider_customization(false, "bar_chart_with_slider_customization"); sinusoidal_slider_example(false, "sinusoidal_slider_example"); gdp_life_expectancy_slider_example(false, "gdp_life_expectancy_slider_example"); } diff --git a/examples/customization/consistent_static_format_export/Cargo.toml b/examples/customization/consistent_static_format_export/Cargo.toml index 84ab4165..937c6512 100644 --- a/examples/customization/consistent_static_format_export/Cargo.toml +++ b/examples/customization/consistent_static_format_export/Cargo.toml @@ -6,4 +6,6 @@ authors = ["Yuriy D. Sibirmovsky"] description = "This example demonstrates exporting a plot to SVG, PNG, and PDF and keeping the font size consistent across all formats." [dependencies] -plotly = { path = "../../../plotly", features = ["kaleido", "kaleido_download"] } \ No newline at end of file +plotly = { path = "../../../plotly", features = ["static_export_default"] } +env_logger = "0.10" +log = "0.4" diff --git a/examples/customization/consistent_static_format_export/README.md b/examples/customization/consistent_static_format_export/README.md index a10d024a..f8fc63ba 100644 --- a/examples/customization/consistent_static_format_export/README.md +++ b/examples/customization/consistent_static_format_export/README.md @@ -1,6 +1,6 @@ # SVG Export Example -This example demonstrates exporting a plot to SVG, PNG, and PDF using plotly.rs and keeping font size and style consistent accross SVG and other formats. +This example demonstrates exporting a plot to SVG, PNG, and PDF using plotly.rs and keeping font size and style consistent across SVG and other formats. This example is based on [GitHub Issue #171](https://github.com/plotly/plotly.rs/issues/171). diff --git a/examples/customization/consistent_static_format_export/src/main.rs b/examples/customization/consistent_static_format_export/src/main.rs index 2bf7b595..813d5571 100644 --- a/examples/customization/consistent_static_format_export/src/main.rs +++ b/examples/customization/consistent_static_format_export/src/main.rs @@ -1,14 +1,16 @@ +use log::info; use plotly::color::{NamedColor, Rgb}; use plotly::common::{Anchor, Font, Line, Marker, MarkerSymbol, Mode, Title}; use plotly::layout::{Axis, ItemSizing, Legend, Margin, Shape, ShapeLine, ShapeType}; -use plotly::{ImageFormat, Layout, Plot, Scatter}; +use plotly::plotly_static::{ImageFormat, StaticExporterBuilder}; +use plotly::{Layout, Plot, Scatter}; fn line_and_scatter_plot( x1: Vec, y1: Vec, x2: Vec, y2: Vec, - flnm: &str, + file_name: &str, title: &str, ) { let bgcol = Rgb::new(255, 255, 255); @@ -140,18 +142,26 @@ fn line_and_scatter_plot( plot.add_trace(trace2); plot.set_layout(layout); - // Export to multiple formats to demonstrate the SVG export issue - println!("Exporting plot to multiple formats..."); - println!("Note: SVG export may have font sizing issues compared to PNG/PDF"); - - plot.write_image(flnm, ImageFormat::PDF, 1280, 960, 1.0); - plot.write_image(flnm, ImageFormat::SVG, 1280, 960, 1.0); - plot.write_image(flnm, ImageFormat::PNG, 1280, 960, 1.0); - - println!("Export complete. Check the output files:"); - println!(" - {flnm}.pdf"); - println!(" - {flnm}.svg"); - println!(" - {flnm}.png"); + let mut exporter = StaticExporterBuilder::default() + .spawn_webdriver(true) + .webdriver_port(4444) + .build() + .unwrap(); + + info!("Exporting to PNG format..."); + plot.write_image_with_exporter(&mut exporter, file_name, ImageFormat::PNG, 1280, 960, 1.0) + .unwrap(); + info!("Exporting to SVG format..."); + plot.write_image_with_exporter(&mut exporter, file_name, ImageFormat::SVG, 1280, 960, 1.0) + .unwrap(); + info!("Exporting to PDF format..."); + plot.write_image_with_exporter(&mut exporter, file_name, ImageFormat::PDF, 1280, 960, 1.0) + .unwrap(); + + info!("Export complete. Check the output files:"); + info!(" - {file_name}.pdf"); + info!(" - {file_name}.svg"); + info!(" - {file_name}.png"); } fn read_from_file(file_path: &str) -> Vec> { @@ -204,6 +214,8 @@ fn read_from_file(file_path: &str) -> Vec> { } fn main() { + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); + let data = read_from_file("assets/data_file.dat"); let x1 = data[0].clone(); let y1 = data[1].clone(); diff --git a/examples/kaleido/src/main.rs b/examples/kaleido/src/main.rs index 89d89d08..de855bf7 100644 --- a/examples/kaleido/src/main.rs +++ b/examples/kaleido/src/main.rs @@ -16,12 +16,16 @@ fn main() { // The image will be saved to format!("output/image.{image_format}") relative to // the current working directory. - plot.write_image(&filename, ImageFormat::EPS, width, height, scale); - plot.write_image(&filename, ImageFormat::JPEG, width, height, scale); - plot.write_image(&filename, ImageFormat::PDF, width, height, scale); - plot.write_image(&filename, ImageFormat::PNG, width, height, scale); - plot.write_image(&filename, ImageFormat::SVG, width, height, scale); - plot.write_image(&filename, ImageFormat::WEBP, width, height, scale); + #[allow(deprecated)] + { + plot.write_image(&filename, ImageFormat::EPS, width, height, scale); + plot.write_image(&filename, ImageFormat::JPEG, width, height, scale); + plot.write_image(&filename, ImageFormat::PDF, width, height, scale); + plot.write_image(&filename, ImageFormat::PNG, width, height, scale); + plot.write_image(&filename, ImageFormat::SVG, width, height, scale); + plot.write_image(&filename, ImageFormat::WEBP, width, height, scale); - let _svg_string = plot.to_svg(width, height, scale); + let svg_string = plot.to_svg(width, height, scale); + println!("SVG plot string: {svg_string}"); + } } diff --git a/examples/static_export/Cargo.toml b/examples/static_export/Cargo.toml new file mode 100644 index 00000000..71c75304 --- /dev/null +++ b/examples/static_export/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "static_export_example" +version = "0.1.0" +authors = ["Andrei Gherghescu andrei-ng@protonmail.com"] +edition = "2021" +description = "Example demonstrating static image export using plotly_static with WebDriver" +readme = "README.md" + +[dependencies] +plotly = { path = "../../plotly", features = ["static_export_default"] } +env_logger = "0.10" +log = "0.4" diff --git a/examples/static_export/README.md b/examples/static_export/README.md new file mode 100644 index 00000000..5c5149b3 --- /dev/null +++ b/examples/static_export/README.md @@ -0,0 +1,93 @@ +# Static Export Example + +This example demonstrates how to use the `plotly_static` crate for exporting Plotly plots to static images. + +The `plotly_static` provides a interface for converting Plotly plots into various static image formats (PNG, JPEG, WEBP, SVG, PDF) using WebDriver and headless browsers. + +In this example it is shown how to use the `StaticExporter` with the old style Kaleido API and also with the new style API. Using the former API is fine for one time static exports, but that API will crate an instance of the `StaticExporter` for each `write_image` call. The new style API is recommended for performance as the same instance of the `StaticExporter` can be reused across multiple exports. + +See also the `Static Image Export` section in the book for a more detailed description. + +## Overview + + +## Features + +- **Multiple Export Formats**: PNG, JPEG, SVG, PDF +- **Exporter Reuse (new API)**: Efficient reuse of a single `StaticExporter` instance +- **String Export**: Base64 and SVG string output for web applications +- **Logging**: Debug information and progress monitoring +- **Error Handling**: Error handling with `Result` types + +## Prerequisites + +1. **Browser Installation**: You need either Firefox (for geckodriver) or Chrome/Chromium (for chromedriver) +2. **WebDriver**: Chromedriver automatically downloaded with the `static_export_default` feature +3. **Internet Connection**: Required for WebDriver download (if using `webdriver_download` feature) + +## Feature Flags + +The example uses `static_export_default` which includes: +- `plotly_static`: Core static export functionality +- `plotly_static/chromedriver`: Chrome WebDriver support +- `plotly_static/webdriver_download`: Automatic WebDriver download + +### Alternative Configurations + +```toml +# Use Firefox instead of Chrome/Chromium +plotly = { version = "0.13", features = ["static_export_geckodriver", "static_export_wd_download"] } + +# Manual Geckodriver installation (no automatic download) +plotly = { version = "0.13", features = ["static_export_geckodriver"] } + +# Manual Chromedriver installation (no automatic download) +plotly = { version = "0.13", features = ["static_export_chromedriver"] } +``` + +## Running the Example + +```bash +# Basic run +cargo run + +# With debug logging +RUST_LOG=debug cargo run + +# With custom WebDriver path +WEBDRIVER_PATH=/path/to/chromedriver cargo run +``` + +## Output + +The example generates several files: +- `plot1.png`: Raster format, good for web/screens +- `plot2.jpeg`: Compressed raster, smaller file size +- `plot3.svg`: Vector format, scalable, good for web +- `plot1.pdf`: Vector format, good for printing + +## Advanced Configuration + +The example includes commented code showing advanced configuration options: +- Custom WebDriver ports for parallel usage +- Offline mode for bundled JavaScript +- Custom browser capabilities +- Explicit WebDriver spawning + +## Troubleshooting + +### Common Issues + +1. **WebDriver not found**: Ensure the browser is installed and WebDriver is available +2. **Port conflicts**: Use unique ports for parallel operations + +### Debug Information + +Set `RUST_LOG=debug` or`RUST_LOG=trace`to see detailed WebDriver operations and troubleshooting information. + +## Related Documentation + +- [plotly_static crate documentation](https://docs.rs/plotly_static/) +- [WebDriver specification](https://w3c.github.io/webdriver/) +- [GeckoDriver documentation](https://firefox-source-docs.mozilla.org/testing/geckodriver/) +- [ChromeDriver documentation](https://chromedriver.chromium.org/) diff --git a/examples/static_export/src/main.rs b/examples/static_export/src/main.rs new file mode 100644 index 00000000..391ed41c --- /dev/null +++ b/examples/static_export/src/main.rs @@ -0,0 +1,112 @@ +use log::info; +use plotly::plotly_static::{ImageFormat, StaticExporterBuilder}; +use plotly::{Plot, Scatter}; + +fn main() -> Result<(), Box> { + // Set log level to info by default + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); + + // Create multiple plots for demonstration + let mut plot1 = Plot::new(); + plot1.add_trace(Scatter::new(vec![1, 2, 3, 4], vec![10, 15, 13, 17]).name("trace1")); + + let mut plot2 = Plot::new(); + plot2.add_trace(Scatter::new(vec![2, 3, 4, 5], vec![16, 5, 11, 9]).name("trace2")); + + let mut plot3 = Plot::new(); + plot3.add_trace(Scatter::new(vec![1, 2, 3, 4], vec![12, 9, 15, 12]).name("trace3")); + + std::fs::create_dir_all("./output").unwrap(); + + info!("Exporting multiple plots using a Kaleido style API ..."); + plot1.write_image("./output/plot1_legacy_api", ImageFormat::PNG, 800, 600, 1.0)?; + plot2.write_image( + "./output/plot2_legacy_api", + ImageFormat::JPEG, + 800, + 600, + 1.0, + )?; + plot3.write_image("./output/plot3_legacy_api", ImageFormat::SVG, 800, 600, 1.0)?; + plot1.write_image("./output/plot3_legacy_api", ImageFormat::PDF, 800, 600, 1.0)?; + + // Create a single StaticExporter to reuse across all plots + // This is more efficient than creating a new exporter for each plot which + // happens implicitly in the calls above using the old API + info!("Creating StaticExporter with default configuration..."); + let mut exporter = StaticExporterBuilder::default() + .build() + .expect("Failed to create StaticExporter"); + + info!("Exporting multiple plots using a single StaticExporter..."); + // Export all plots using the same exporter + plot1.write_image_with_exporter( + &mut exporter, + "./output/plot1_new_api", + ImageFormat::PNG, + 800, + 600, + 1.0, + )?; + plot2.write_image_with_exporter( + &mut exporter, + "./output/plot2_new_api", + ImageFormat::JPEG, + 800, + 600, + 1.0, + )?; + plot3.write_image_with_exporter( + &mut exporter, + "./output/plot3_new_api", + ImageFormat::SVG, + 800, + 600, + 1.0, + )?; + + plot1.write_image_with_exporter( + &mut exporter, + "./output/plot1_new_api", + ImageFormat::PDF, + 800, + 600, + 1.0, + )?; + + // Demonstrate string-based export + info!("Exporting to base64 and SVG strings..."); + // Get base64 data (useful for embedding in HTML or APIs) + let base64_data = + plot1.to_base64_with_exporter(&mut exporter, ImageFormat::PNG, 400, 300, 1.0)?; + info!("Base64 data length: {}", base64_data.len()); + + let svg_data = plot1.to_svg_with_exporter(&mut exporter, 400, 300, 1.0)?; + info!("SVG data starts with: {}", &svg_data[..50]); + + info!("All exports completed successfully!"); + info!("Generated files:"); + info!(" - plot1.png (raster format, good for web/screens)"); + info!(" - plot2.jpeg (compressed raster, smaller file size)"); + info!(" - plot3.svg (vector format, scalable, good for web)"); + info!(" - plot1.pdf (vector format, good for printing)"); + + // Demonstrate advanced configuration (commented out for this example) + /* + // For parallel usage or custom configuration: + let mut custom_exporter = StaticExporterBuilder::default() + .webdriver_port(4445) // Use different port for parallel operations + .spawn_webdriver(true) // Explicitly spawn WebDriver + .offline_mode(true) // Use bundled JavaScript (no internet required) + .webdriver_browser_caps(vec![ + "--headless".to_string(), + "--no-sandbox".to_string(), + "--disable-gpu".to_string(), + "--disable-dev-shm-usage".to_string(), + ]) + .build() + .expect("Failed to create custom StaticExporter"); + */ + + Ok(()) +} diff --git a/plotly/Cargo.toml b/plotly/Cargo.toml index 92d669f1..d173285a 100644 --- a/plotly/Cargo.toml +++ b/plotly/Cargo.toml @@ -1,8 +1,11 @@ [package] name = "plotly" -version = "0.12.1" +version = "0.13.0" description = "A plotting library powered by Plotly.js" -authors = ["Ioannis Giagkiozis "] +authors = [ + "Ioannis Giagkiozis ", + "Andrei Gherghescu ", +] license = "MIT" readme = "../README.md" homepage = "https://github.com/plotly/plotly.rs" @@ -14,9 +17,30 @@ keywords = ["plot", "chart", "plotly"] exclude = ["target/*"] [features] +# DEPRECATED: kaleido feature will be removed in version 0.14.0. Use `static_export_*` features instead. kaleido = ["plotly_kaleido"] +# DEPRECATED: kaleido_download feature will be removed in version 0.14.0. Use `static_export_wd_download` instead. kaleido_download = ["plotly_kaleido/download"] +static_export_chromedriver = ["plotly_static", "plotly_static/chromedriver"] +static_export_geckodriver = ["plotly_static", "plotly_static/geckodriver"] +static_export_wd_download = ["plotly_static/webdriver_download"] +static_export_default = [ + "plotly_static", + "plotly_static/chromedriver", + "plotly_static/webdriver_download", +] + +# All non-conflicting features +all = [ + "plotly_ndarray", + "plotly_image", + "plotly_embed_js", + "static_export_default", +] +# This is used for enabling extra debugging messages and debugging functionality +debug = ["plotly_static?/debug"] + plotly_ndarray = ["ndarray"] plotly_image = ["image"] plotly_embed_js = [] @@ -26,8 +50,9 @@ askama = { version = "0.14.0", features = ["serde_json"] } dyn-clone = "1" erased-serde = "0.4" image = { version = "0.25", optional = true } -plotly_derive = { version = "0.12", path = "../plotly_derive" } -plotly_kaleido = { version = "0.12", path = "../plotly_kaleido", optional = true } +plotly_derive = { version = "0.13", path = "../plotly_derive" } +plotly_static = { version = "0.0.1", path = "../plotly_static", optional = true } +plotly_kaleido = { version = "0.13", path = "../plotly_kaleido", optional = true } ndarray = { version = "0.16", optional = true } once_cell = "1" serde = { version = "1.0", features = ["derive"] } @@ -53,6 +78,6 @@ image = "0.25" itertools = ">=0.10, <0.15" itertools-num = "0.1" ndarray = "0.16" -plotly_kaleido = { path = "../plotly_kaleido", features = ["download"] } +plotly_static = { path = "../plotly_static" } rand_distr = "0.5" base64 = "0.22" diff --git a/plotly/templates/plotly.min.js b/plotly/resource/plotly.min.js similarity index 100% rename from plotly/templates/plotly.min.js rename to plotly/resource/plotly.min.js diff --git a/plotly/templates/tex-mml-chtml-3.2.0.js b/plotly/resource/tex-mml-chtml-3.2.0.js similarity index 100% rename from plotly/templates/tex-mml-chtml-3.2.0.js rename to plotly/resource/tex-mml-chtml-3.2.0.js diff --git a/plotly/templates/tex-svg-3.2.2.js b/plotly/resource/tex-svg-3.2.2.js similarity index 100% rename from plotly/templates/tex-svg-3.2.2.js rename to plotly/resource/tex-svg-3.2.2.js diff --git a/plotly/src/common/color.rs b/plotly/src/common/color.rs index 238712b8..dbbecc32 100644 --- a/plotly/src/common/color.rs +++ b/plotly/src/common/color.rs @@ -189,8 +189,7 @@ impl FromStr for Rgba { let prefix: &[_] = &['r', 'g', 'b', 'a', '(']; let trimmed = rgba.trim_start_matches(prefix).trim_end_matches(')'); let fields: Vec<&str> = trimmed.split(',').collect(); - dbg!(&fields); - println!("{:?}", &fields); + // println!("{:?}", &fields); if fields.len() != 4 { Err(ParseError::new("Invalid string length of for RGBA color")) } else { diff --git a/plotly/src/configuration.rs b/plotly/src/configuration.rs index ae8c9352..ce2e4fff 100644 --- a/plotly/src/configuration.rs +++ b/plotly/src/configuration.rs @@ -10,7 +10,6 @@ pub enum ImageButtonFormats { Webp, } -// TODO: should this be behind the plotly-kaleido feature? #[serde_with::skip_serializing_none] #[derive(Serialize, Debug, Default, Clone)] pub struct ToImageButtonOptions { diff --git a/plotly/src/lib.rs b/plotly/src/lib.rs index 8d112b23..23290350 100644 --- a/plotly/src/lib.rs +++ b/plotly/src/lib.rs @@ -1,16 +1,41 @@ //! # Plotly.rs //! //! A plotting library for Rust powered by [Plotly.js](https://plot.ly/javascript/). +//! +//! ## Feature Deprecation Notice +//! +//! The `kaleido` and `kaleido_download` features are deprecated since version +//! 0.13.0 and will be removed in version 0.14.0. Please migrate to the +//! `plotly_static` and `plotly_static_download` features instead. #![recursion_limit = "256"] // lets us use a large serde_json::json! macro for testing crate::layout::Axis extern crate askama; extern crate rand; extern crate serde; +#[cfg(feature = "kaleido")] +#[deprecated( + since = "0.13.0", + note = "kaleido feature is deprecated and will be removed in version 0.14.0. Use plotly_static feature instead" +)] +const _KALEIDO_DEPRECATED: () = (); + +#[cfg(feature = "kaleido_download")] +#[deprecated( + since = "0.13.0", + note = "kaleido_download feature is deprecated and will be removed in version 0.14.0. Use plotly_static_download feature instead" +)] +const _KALEIDO_DOWNLOAD_DEPRECATED: () = (); + #[cfg(all(feature = "kaleido", target_family = "wasm"))] compile_error!( r#"The "kaleido" feature is not available on "wasm" targets. Please compile without this feature for the wasm target family."# ); +#[cfg(all(feature = "kaleido", feature = "plotly_static"))] +compile_error!( + r#"The "kaleido" feature and "plotly_static" are conflictings. Please select only one of them."# +); + #[cfg(feature = "plotly_ndarray")] pub mod ndarray; #[cfg(feature = "plotly_ndarray")] @@ -31,7 +56,11 @@ pub mod traces; pub use common::color; pub use configuration::Configuration; pub use layout::Layout; -pub use plot::{ImageFormat, Plot, Trace}; +pub use plot::{Plot, Trace}; +#[cfg(feature = "kaleido")] +pub use plotly_kaleido::ImageFormat; +#[cfg(feature = "plotly_static")] +pub use plotly_static; // Also provide easy access to modules which contain additional trace-specific types pub use traces::{ box_plot, contour, heat_map, histogram, image, mesh3d, sankey, scatter, scatter3d, diff --git a/plotly/src/plot.rs b/plotly/src/plot.rs index c334b896..e17f575f 100644 --- a/plotly/src/plot.rs +++ b/plotly/src/plot.rs @@ -3,6 +3,10 @@ use std::{fs::File, io::Write, path::Path}; use askama::Template; use dyn_clone::DynClone; use erased_serde::Serialize as ErasedSerialize; +#[cfg(feature = "kaleido")] +use plotly_kaleido::ImageFormat; +#[cfg(feature = "plotly_static")] +use plotly_static::ImageFormat; use rand::{ distr::{Alphanumeric, SampleString}, rng, @@ -18,6 +22,7 @@ struct PlotTemplate<'a> { js_scripts: &'a str, } +#[cfg(any(feature = "kaleido", feature = "plotly_static"))] #[derive(Template)] #[template(path = "static_plot.html", escape = "none")] #[cfg(all(not(target_family = "wasm"), not(target_os = "android")))] @@ -45,14 +50,14 @@ struct JupyterNotebookPlotTemplate<'a> { #[cfg(all(not(target_family = "wasm"), not(target_os = "android")))] const DEFAULT_HTML_APP_NOT_FOUND: &str = r#"Could not find default application for HTML files. -Consider using the `to_html` method obtain a string representation instead. If using the `kaleido` feature the +Consider using the `to_html` method obtain a string representation instead. If using the `kaleido` or `plotly_static` feature the `write_image` method can be used to produce a static image in one of the following formats: - ImageFormat::PNG - ImageFormat::JPEG - ImageFormat::WEBP - ImageFormat::SVG - ImageFormat::PDF -- ImageFormat::EPS +- ImageFormat::EPS // will be removed in version 0.14.0 Used as follows: let plot = Plot::new(); @@ -65,34 +70,6 @@ plot.write_image("filename", ImageFormat::PNG, width, height, scale); See https://plotly.github.io/plotly.rs/content/getting_started.html for further details. "#; -/// Image format for static image export. -#[derive(Debug)] -pub enum ImageFormat { - PNG, - JPEG, - WEBP, - SVG, - PDF, - EPS, -} - -impl std::fmt::Display for ImageFormat { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Self::PNG => "png", - Self::JPEG => "jpeg", - Self::WEBP => "webp", - Self::SVG => "svg", - Self::PDF => "pdf", - Self::EPS => "eps", - } - ) - } -} - /// A struct that implements `Trace` can be serialized to json format that is /// understood by Plotly.js. pub trait Trace: DynClone + ErasedSerialize { @@ -289,10 +266,11 @@ impl Plot { /// Display the fully rendered `Plot` as a static image of the given format /// in the default system browser. #[cfg(all(not(target_family = "wasm"), not(target_os = "android")))] + #[cfg(any(feature = "kaleido", feature = "plotly_static"))] pub fn show_image(&self, format: ImageFormat, width: usize, height: usize) { use std::env; - let rendered = self.render_static(format, width, height); + let rendered = self.render_static(&format, width, height); // Set up the temp file with a unique filename. let mut temp = env::temp_dir(); @@ -390,7 +368,15 @@ impl Plot { } /// Convert the `Plot` to a static image of the given image format and save - /// at the given location. + /// at the given location using kaleido. + /// + /// This function is deprecated since version 0.13.0. The kaleido-based + /// implementation will be removed in version 0.14.0. Use + /// `plotly_static` feature instead for static image export functionality. + #[deprecated( + since = "0.13.0", + note = "kaleido-based implementation is deprecated. Use plotly_static feature instead. The kaleido implementation will be removed in version 0.14.0" + )] #[cfg(feature = "kaleido")] pub fn write_image>( &self, @@ -405,7 +391,7 @@ impl Plot { .save( filename.as_ref(), &serde_json::to_value(self).unwrap(), - &format.to_string(), + format, width, height, scale, @@ -414,8 +400,16 @@ impl Plot { } /// Convert the `Plot` to a static image and return the image as a `base64` - /// String Supported formats are [ImageFormat::JPEG], [ImageFormat::PNG] - /// and [ImageFormat::WEBP] + /// String using kaleido. Supported formats are [ImageFormat::JPEG], + /// [ImageFormat::PNG] and [ImageFormat::WEBP] + /// + /// This function is deprecated since version 0.13.0. The kaleido-based + /// implementation will be removed in version 0.14.0. Use + /// `plotly_static` feature instead for static image export functionality. + #[deprecated( + since = "0.13.0", + note = "kaleido-based implementation is deprecated. Use plotly_static feature instead. The kaleido implementation will be removed in version 0.14.0" + )] #[cfg(feature = "kaleido")] pub fn to_base64( &self, @@ -430,7 +424,7 @@ impl Plot { kaleido .image_to_string( &serde_json::to_value(self).unwrap(), - &format.to_string(), + format, width, height, scale, @@ -444,14 +438,22 @@ impl Plot { } } - /// Convert the `Plot` to SVG and return it as a String. + /// Convert the `Plot` to SVG and return it as a String using kaleido. + /// + /// This function is deprecated since version 0.13.0. The kaleido-based + /// implementation will be removed in version 0.14.0. Use + /// `plotly_static` feature instead for static image export functionality. + #[deprecated( + since = "0.13.0", + note = "kaleido-based implementation is deprecated. Use plotly_static feature instead. The kaleido implementation will be removed in version 0.14.0" + )] #[cfg(feature = "kaleido")] pub fn to_svg(&self, width: usize, height: usize, scale: f64) -> String { let kaleido = plotly_kaleido::Kaleido::new(); kaleido .image_to_string( &serde_json::to_value(self).unwrap(), - "svg", + ImageFormat::SVG, width, height, scale, @@ -459,6 +461,249 @@ impl Plot { .unwrap_or_else(|_| panic!("Kaleido failed to generate image")) } + /// Convert the `Plot` to a static image of the given image format and save + /// at the given location. + /// + /// This method requires the usage of the `plotly_static` crate using one of + /// the available feature flags. For advanced usage (parallelism, exporter reuse, custom config), see the [plotly_static documentation](https://docs.rs/plotly_static/). + /// + /// **Note:** This method creates a new `StaticExporter` (and thus a new + /// WebDriver instance) for each call, which is not performant for + /// repeated operations. For better performance and resource management, + /// consider using `write_image_with_exporter` to reuse a single + /// `StaticExporter` instance across multiple operations. + #[cfg(feature = "plotly_static")] + pub fn write_image>( + &self, + filename: P, + format: ImageFormat, + width: usize, + height: usize, + scale: f64, + ) -> Result<(), Box> { + let mut exporter = plotly_static::StaticExporterBuilder::default() + .build() + .map_err(|e| format!("Failed to create StaticExporter: {e}"))?; + self.write_image_with_exporter(&mut exporter, filename, format, width, height, scale) + } + + /// Convert the `Plot` to a static image and return the image as a `base64` + /// String. Supported formats are [ImageFormat::JPEG], + /// [ImageFormat::PNG] and [ImageFormat::WEBP]. + /// + /// This method uses the [plotly_static](https://docs.rs/plotly_static/) crate and requires a WebDriver-compatible browser (Chrome or Firefox) to be available on the system. + /// + /// For advanced usage (parallelism, exporter reuse, custom config), see the [plotly_static documentation](https://docs.rs/plotly_static/). + /// + /// + /// **Note:** This method creates a new `StaticExporter` (and thus a new + /// WebDriver instance) for each call, which is not performant for + /// repeated operations. For better performance and resource management, + /// consider using `to_base64_with_exporter` to reuse a single + /// `StaticExporter` instance across multiple operations. + #[cfg(feature = "plotly_static")] + pub fn to_base64( + &self, + format: ImageFormat, + width: usize, + height: usize, + scale: f64, + ) -> Result> { + let mut exporter = plotly_static::StaticExporterBuilder::default() + .build() + .map_err(|e| format!("Failed to create StaticExporter: {e}"))?; + self.to_base64_with_exporter(&mut exporter, format, width, height, scale) + } + + /// Convert the `Plot` to SVG and return it as a String using plotly_static. + /// + /// This method requires the usage of the `plotly_static` crate using one of + /// the available feature flags. For advanced usage (parallelism, exporter reuse, custom config), see the [plotly_static documentation](https://docs.rs/plotly_static/). + /// + /// **Note:** This method creates a new `StaticExporter` (and thus a new + /// WebDriver instance) for each call, which is not performant for + /// repeated operations. For better performance and resource management, + /// consider using `to_svg_with_exporter` to reuse a single + /// `StaticExporter` instance across multiple operations. + #[cfg(feature = "plotly_static")] + pub fn to_svg( + &self, + width: usize, + height: usize, + scale: f64, + ) -> Result> { + let mut exporter = plotly_static::StaticExporterBuilder::default() + .build() + .map_err(|e| format!("Failed to create StaticExporter: {e}"))?; + self.to_svg_with_exporter(&mut exporter, width, height, scale) + } + + /// Convert the `Plot` to a static image of the given image format and save + /// at the given location using a provided StaticExporter. + /// + /// This method allows you to reuse a StaticExporter instance across + /// multiple plots, which is more efficient than creating a new one for + /// each operation. + /// + /// This method requires the usage of the `plotly_static` crate using one of + /// the available feature flags. For advanced usage (parallelism, exporter reuse, custom config), see the [plotly_static documentation](https://docs.rs/plotly_static/). + /// + /// # Arguments + /// + /// * `exporter` - A mutable reference to a StaticExporter instance + /// * `filename` - The destination path for the output file + /// * `format` - The desired output image format + /// * `width` - The width of the output image in pixels + /// * `height` - The height of the output image in pixels + /// * `scale` - The scale factor for the image (1.0 = normal size) + /// + /// # Examples + /// + /// ```no_run + /// use plotly::{Plot, Scatter}; + /// use plotly_static::{StaticExporterBuilder, ImageFormat}; + /// + /// let mut plot = Plot::new(); + /// plot.add_trace(Scatter::new(vec![1, 2, 3], vec![4, 5, 6])); + /// + /// let mut exporter = StaticExporterBuilder::default() + /// .build() + /// .expect("Failed to create StaticExporter"); + /// + /// // Export multiple plots using the same exporter + /// plot.write_image_with_exporter(&mut exporter, "plot1", ImageFormat::PNG, 800, 600, 1.0) + /// .expect("Failed to export plot"); + /// ``` + #[cfg(feature = "plotly_static")] + pub fn write_image_with_exporter>( + &self, + exporter: &mut plotly_static::StaticExporter, + filename: P, + format: ImageFormat, + width: usize, + height: usize, + scale: f64, + ) -> Result<(), Box> { + exporter.write_fig( + filename.as_ref(), + &serde_json::to_value(self)?, + format, + width, + height, + scale, + ) + } + + /// Convert the `Plot` to a static image and return the image as a `base64` + /// String using a provided StaticExporter. Supported formats are + /// [ImageFormat::JPEG], [ImageFormat::PNG] and [ImageFormat::WEBP]. + /// + /// This method allows you to reuse a StaticExporter instance across + /// multiple plots, which is more efficient than creating a new one for + /// each operation. + /// + /// This method requires the usage of the `plotly_static` crate using one of + /// the available feature flags. For advanced usage (parallelism, exporter reuse, custom config), see the [plotly_static documentation](https://docs.rs/plotly_static/). + /// + /// # Arguments + /// + /// * `exporter` - A mutable reference to a StaticExporter instance + /// * `format` - The desired output image format + /// * `width` - The width of the output image in pixels + /// * `height` - The height of the output image in pixels + /// * `scale` - The scale factor for the image (1.0 = normal size) + /// + /// # Examples + /// + /// ```no_run + /// use plotly::{Plot, Scatter}; + /// use plotly_static::{StaticExporterBuilder, ImageFormat}; + /// + /// let mut plot = Plot::new(); + /// plot.add_trace(Scatter::new(vec![1, 2, 3], vec![4, 5, 6])); + /// + /// let mut exporter = StaticExporterBuilder::default() + /// .build() + /// .expect("Failed to create StaticExporter"); + /// + /// let base64_data = plot.to_base64_with_exporter(&mut exporter, ImageFormat::PNG, 800, 600, 1.0) + /// .expect("Failed to export plot"); + /// ``` + #[cfg(feature = "plotly_static")] + pub fn to_base64_with_exporter( + &self, + exporter: &mut plotly_static::StaticExporter, + format: ImageFormat, + width: usize, + height: usize, + scale: f64, + ) -> Result> { + match format { + ImageFormat::JPEG | ImageFormat::PNG | ImageFormat::WEBP => { + exporter.write_to_string( + &serde_json::to_value(self)?, + format, + width, + height, + scale, + ) + } + _ => { + Err(format!("Cannot generate base64 string for ImageFormat:{format}. Allowed formats are JPEG, PNG, WEBP").into()) + } + } + } + + /// Convert the `Plot` to SVG and return it as a String using a provided + /// StaticExporter. + /// + /// This method allows you to reuse a StaticExporter instance across + /// multiple plots, which is more efficient than creating a new one for + /// each operation. + /// + /// This method requires the usage of the `plotly_static` crate using one of + /// the available feature flags. For advanced usage (parallelism, exporter reuse, custom config), see the [plotly_static documentation](https://docs.rs/plotly_static/). + /// + /// # Arguments + /// + /// * `exporter` - A mutable reference to a StaticExporter instance + /// * `width` - The width of the output image in pixels + /// * `height` - The height of the output image in pixels + /// * `scale` - The scale factor for the image (1.0 = normal size) + /// + /// # Examples + /// + /// ```no_run + /// use plotly::{Plot, Scatter}; + /// use plotly_static::StaticExporterBuilder; + /// + /// let mut plot = Plot::new(); + /// plot.add_trace(Scatter::new(vec![1, 2, 3], vec![4, 5, 6])); + /// + /// let mut exporter = StaticExporterBuilder::default() + /// .build() + /// .expect("Failed to create StaticExporter"); + /// + /// let svg_data = plot.to_svg_with_exporter(&mut exporter, 800, 600, 1.0) + /// .expect("Failed to export plot"); + /// ``` + #[cfg(feature = "plotly_static")] + pub fn to_svg_with_exporter( + &self, + exporter: &mut plotly_static::StaticExporter, + width: usize, + height: usize, + scale: f64, + ) -> Result> { + exporter.write_to_string( + &serde_json::to_value(self)?, + ImageFormat::SVG, + width, + height, + scale, + ) + } + fn render(&self) -> String { let tmpl = PlotTemplate { plot: self, @@ -468,10 +713,11 @@ impl Plot { } #[cfg(all(not(target_family = "wasm"), not(target_os = "android")))] - fn render_static(&self, format: ImageFormat, width: usize, height: usize) -> String { + #[cfg(any(feature = "kaleido", feature = "plotly_static"))] + pub fn render_static(&self, format: &ImageFormat, width: usize, height: usize) -> String { let tmpl = StaticPlotTemplate { plot: self, - format, + format: format.clone(), js_scripts: &self.js_scripts, width, height, @@ -509,12 +755,12 @@ impl Plot { pub fn offline_js_sources() -> String { // Note that since 'tex-mml-chtml' conflicts with 'tex-svg' when generating // Latex Titles we no longer include it. - let local_tex_svg_js = include_str!("../templates/tex-svg-3.2.2.js"); - let local_plotly_js = include_str!("../templates/plotly.min.js"); + let local_tex_svg_js = include_str!("../resource/tex-svg-3.2.2.js"); + let local_plotly_js = include_str!("../resource/plotly.min.js"); format!( "\n - \n" + \n", ) .to_string() } @@ -595,9 +841,14 @@ impl PartialEq for Plot { #[cfg(test)] mod tests { use std::path::PathBuf; + use std::sync::atomic::{AtomicU32, Ordering}; - use serde_json::{json, to_value}; #[cfg(feature = "kaleido")] + use plotly_kaleido::ImageFormat; + #[cfg(feature = "plotly_static")] + use plotly_static::ImageFormat; + use serde_json::{json, to_value}; + #[cfg(any(feature = "kaleido", feature = "plotly_static"))] use {base64::engine::general_purpose, base64::Engine}; use super::*; @@ -743,108 +994,145 @@ mod tests { assert!(plot1 == plot2); } - #[test] - #[ignore] // Don't really want it to try and open a browser window every time we run a test. - #[cfg(not(target_family = "wasm"))] - fn show_image() { - let plot = create_test_plot(); - plot.show_image(ImageFormat::PNG, 1024, 680); - } - #[test] fn save_html() { let plot = create_test_plot(); - let dst = PathBuf::from("example.html"); + let dst = PathBuf::from("plotly_example.html"); plot.write_html(&dst); assert!(dst.exists()); + #[cfg(not(feature = "debug"))] assert!(std::fs::remove_file(&dst).is_ok()); - assert!(!dst.exists()); } - #[cfg(not(target_os = "macos"))] + #[cfg(feature = "plotly_static")] + // Helper to generate unique ports for parallel tests + static PORT_COUNTER: AtomicU32 = AtomicU32::new(4444); + + #[cfg(feature = "plotly_static")] + fn get_unique_port() -> u32 { + PORT_COUNTER.fetch_add(1, Ordering::SeqCst) + } + #[test] - #[cfg(feature = "kaleido")] + #[cfg(feature = "plotly_static")] fn save_to_png() { let plot = create_test_plot(); - let dst = PathBuf::from("example.png"); - plot.write_image(&dst, ImageFormat::PNG, 1024, 680, 1.0); + let dst = PathBuf::from("plotly_example.png"); + let mut exporter = plotly_static::StaticExporterBuilder::default() + .webdriver_port(get_unique_port()) + .build() + .unwrap(); + plot.write_image_with_exporter(&mut exporter, &dst, ImageFormat::PNG, 1024, 680, 1.0) + .unwrap(); assert!(dst.exists()); + let metadata = std::fs::metadata(&dst).expect("Could not retrieve file metadata"); + let file_size = metadata.len(); + assert!(file_size > 0,); + #[cfg(not(feature = "debug"))] assert!(std::fs::remove_file(&dst).is_ok()); - assert!(!dst.exists()); } - #[cfg(not(target_os = "macos"))] #[test] - #[cfg(feature = "kaleido")] + #[cfg(feature = "plotly_static")] fn save_to_jpeg() { let plot = create_test_plot(); - let dst = PathBuf::from("example.jpeg"); - plot.write_image(&dst, ImageFormat::JPEG, 1024, 680, 1.0); + let dst = PathBuf::from("plotly_example.jpeg"); + let mut exporter = plotly_static::StaticExporterBuilder::default() + .webdriver_port(get_unique_port()) + .build() + .unwrap(); + plot.write_image_with_exporter(&mut exporter, &dst, ImageFormat::JPEG, 1024, 680, 1.0) + .unwrap(); assert!(dst.exists()); + let metadata = std::fs::metadata(&dst).expect("Could not retrieve file metadata"); + let file_size = metadata.len(); + assert!(file_size > 0,); + #[cfg(not(feature = "debug"))] assert!(std::fs::remove_file(&dst).is_ok()); - assert!(!dst.exists()); } - #[cfg(not(target_os = "macos"))] #[test] - #[cfg(feature = "kaleido")] + #[cfg(feature = "plotly_static")] fn save_to_svg() { let plot = create_test_plot(); - let dst = PathBuf::from("example.svg"); - plot.write_image(&dst, ImageFormat::SVG, 1024, 680, 1.0); + let dst = PathBuf::from("plotly_example.svg"); + let mut exporter = plotly_static::StaticExporterBuilder::default() + .webdriver_port(get_unique_port()) + .build() + .unwrap(); + plot.write_image_with_exporter(&mut exporter, &dst, ImageFormat::SVG, 1024, 680, 1.0) + .unwrap(); assert!(dst.exists()); + let metadata = std::fs::metadata(&dst).expect("Could not retrieve file metadata"); + let file_size = metadata.len(); + assert!(file_size > 0,); + #[cfg(not(feature = "debug"))] assert!(std::fs::remove_file(&dst).is_ok()); - assert!(!dst.exists()); } #[test] - #[ignore] // This seems to fail unpredictably on MacOs. - #[cfg(feature = "kaleido")] - fn save_to_eps() { - let plot = create_test_plot(); - let dst = PathBuf::from("example.eps"); - plot.write_image(&dst, ImageFormat::EPS, 1024, 680, 1.0); - assert!(dst.exists()); - assert!(std::fs::remove_file(&dst).is_ok()); - assert!(!dst.exists()); - } - - #[cfg(not(target_os = "macos"))] - #[test] - #[cfg(feature = "kaleido")] + #[cfg(feature = "plotly_static")] fn save_to_pdf() { let plot = create_test_plot(); - let dst = PathBuf::from("example.pdf"); - plot.write_image(&dst, ImageFormat::PDF, 1024, 680, 1.0); + let dst = PathBuf::from("plotly_example.pdf"); + #[cfg(feature = "debug")] + let mut exporter = plotly_static::StaticExporterBuilder::default() + .spawn_webdriver(true) + .webdriver_port(get_unique_port()) + .pdf_export_timeout(750) + .build() + .unwrap(); + #[cfg(not(feature = "debug"))] + let mut exporter = plotly_static::StaticExporterBuilder::default() + .webdriver_port(get_unique_port()) + .build() + .unwrap(); + plot.write_image_with_exporter(&mut exporter, &dst, ImageFormat::PDF, 1024, 680, 1.0) + .unwrap(); assert!(dst.exists()); + let metadata = std::fs::metadata(&dst).expect("Could not retrieve file metadata"); + let file_size = metadata.len(); + assert!(file_size > 0,); + #[cfg(not(feature = "debug"))] assert!(std::fs::remove_file(&dst).is_ok()); - assert!(!dst.exists()); } - #[cfg(not(target_os = "macos"))] #[test] - #[cfg(feature = "kaleido")] + #[cfg(feature = "plotly_static")] fn save_to_webp() { let plot = create_test_plot(); - let dst = PathBuf::from("example.webp"); - plot.write_image(&dst, ImageFormat::WEBP, 1024, 680, 1.0); + let dst = PathBuf::from("plotly_example.webp"); + let mut exporter = plotly_static::StaticExporterBuilder::default() + .webdriver_port(get_unique_port()) + .build() + .unwrap(); + plot.write_image_with_exporter(&mut exporter, &dst, ImageFormat::WEBP, 1024, 680, 1.0) + .unwrap(); assert!(dst.exists()); + let metadata = std::fs::metadata(&dst).expect("Could not retrieve file metadata"); + let file_size = metadata.len(); + assert!(file_size > 0,); + #[cfg(not(feature = "debug"))] assert!(std::fs::remove_file(&dst).is_ok()); - assert!(!dst.exists()); } #[test] - #[cfg(not(target_os = "macos"))] - #[cfg(feature = "kaleido")] + #[cfg(feature = "plotly_static")] fn image_to_base64() { let plot = create_test_plot(); + let mut exporter = plotly_static::StaticExporterBuilder::default() + .webdriver_port(get_unique_port()) + .build() + .unwrap(); - let image_base64 = plot.to_base64(ImageFormat::PNG, 200, 150, 1.0); + let image_base64 = plot + .to_base64_with_exporter(&mut exporter, ImageFormat::PNG, 200, 150, 1.0) + .unwrap(); assert!(!image_base64.is_empty()); let result_decoded = general_purpose::STANDARD.decode(image_base64).unwrap(); - let expected = "iVBORw0KGgoAAAANSUhEUgAAAMgAAACWCAYAAACb3McZAAAH0klEQVR4Xu2bSWhVZxiGv2gC7SKJWrRWxaGoULsW7L7gXlAMKApiN7pxI46ggnNQcDbOoAZUcCG4CCiIQ4MSkWKFLNSCihTR2ESTCNVb/lMTEmvu8OYuTN/nQBHb895zv+f9H+6ZWpHL5XLBBgEIfJZABYKwMiAwMAEEYXVAIA8BBGF5QABBWAMQ0AjwC6JxI2VCAEFMimZMjQCCaNxImRBAEJOiGVMjgCAaN1ImBBDEpGjG1AggiMaNlAkBBDEpmjE1AgiicSNlQgBBTIpmTI0AgmjcSJkQQBCTohlTI4AgGjdSJgQQxKRoxtQIIIjGjZQJAQQxKZoxNQIIonEjZUIAQUyKZkyNAIJo3EiZEEAQk6IZUyOAIBo3UiYEEMSkaMbUCCCIxo2UCQEEMSmaMTUCCKJxI2VCAEFMimZMjQCCaNxImRBAEJOiGVMjgCAaN1ImBBDEpGjG1AggiMaNlAkBBDEpmjE1AgiicSNlQgBBTIpmTI0AgmjcSJkQQBCTohlTI4AgGjdSJgQQxKRoxtQIIIjGjZQJAQQxKZoxNQIIonEjZUIAQUyKZkyNAIJo3EiZEEAQk6IZUyOAIBo3UiYEEMSkaMbUCCCIxo2UCQEEMSmaMTUCCKJxI2VCAEFMimZMjQCCaNxImRBAEJOiGVMjgCAaN1ImBBDEpGjG1AggiMaNlAkBBDEpmjE1AgiicSNlQgBBTIpmTI0AgmjcSJkQQBCTohlTI4AgGjdSJgQQxKRoxtQIIIjGjZQJAQQxKZoxNQIIonEjZUIAQUyKZkyNAIJo3EiZEEAQk6IZUyOAIBo3UiYEEMSkaMbUCCCIxo2UCQEEMSmaMTUCCPKR26NHj+LUqVNx69atuHDhQtTW1vYSvX37dhw4cCC6u7tj4sSJsXr16hg5cqRGnNSQIoAgH+vavHlzzJ49O9auXRvnzp3rFeTNmzdRV1cXHz58yP7J5XIxbdq02Lt375Aqmi+rEUCQT7glSfoKcunSpdizZ0+MGDEik+PVq1cxfPjwuHz5clRVVWnUSQ0ZAghSQJA1a9ZEOsVqaGiIHTt2xLNnz6Krqys7HRs/fvyQKZovqhFAkAKCpFOuO3fuxOjRo+Pdu3fR3t6e/ZIcPHgwpk6dqlEnNWQIIEgBQTZu3Bg3b96MioqKmDBhQjx58iQT5OTJk/1+QX599DLqGpr/U3wuF1FRUb71MOv7b6Lmq8qYMa42Hjz/K5p+/7Pfh6f/9tuG2eU7oPknIUgBQbZu3RpXrlyJ7du3Z9ceK1euzAQ5c+ZMjBkzpjc9kCDVaTF/V5PtlxZ3z1bzdVXMGPfvv69vao2WP9r6fZMfx9XEzz98G0/buuJpW2c8eN4eHd1/99tnIPkaf5kVP/U5lvkaH9T4CFJAkBUrVsT9+/dj6dKlkS7YOzo6It3ZOnr0aEyePHlQ8Al/+QQQJCJb9EmAtL18+TJGjRqVnVIdOnQo6uvro7m5Ofv7sGHDslu9aduyZUvMnDnzy2+YbzgoAghSAN/bt29j/vz58f79++zUKv2ZZJo7d+6gwBMeGgQQpEBPTU1NsWvXruw5SNra2tqiuro6Tpw4kf3J9v8mgCBl7Hcwr6Tke9Ul31e8evVqnD59OrsFnW4apGum9DoMW3kIIEh5OGYX7osWLYp012v69OnZon38+HGsX7++qCMM9KpLvnB6aLl8+fLYt29fdsu5sbEx7t69Gzt37izqmOxUmACCFGZU1B7Xrl2LdDqWFnraOjs7Y968eXHx4sWSXkn59FWXfAdP10cvXrzovZv28OHDWLduXSYKW3kIIEh5OGbPRV6/fh3Lli3r/cQkyO7du0t6JaUUQT796ufPn4/W1tZMErbyEECQ8nCM48eP997h6vnIBQsWxIYNG0p6JUUV5N69e9mpVRKy7wPMMo1n+zEIUqbqz549m93h6vsLMmfOnOy1+FJealQEuXHjRhw+fDg2bdoUU6ZMKdNEfEwigCBlWgfXr1/PXoFPF+lpS6dbCxcuzK5BKisriz5KqYKkFyn3798f27Zti7FjxxZ9HHYsjgCCFMep4F7pgnnx4sXZRXq6i3Xs2LHsqXx6d6uUrRRB0jGXLFmSvSc2adKkUg7DvkUSQJAiQRWzW0tLS3ZKle5gpf/rcNWqVUU9TMz3qkvPA8rPHf/Th5g9+xw5cqSo4xYzk/s+COK+Apg/LwEEYYFAIA8BBGF5QABBWAMQ0AjwC6JxI2VCAEFMimZMjQCCaNxImRBAEJOiGVMjgCAaN1ImBBDEpGjG1AggiMaNlAkBBDEpmjE1AgiicSNlQgBBTIpmTI0AgmjcSJkQQBCTohlTI4AgGjdSJgQQxKRoxtQIIIjGjZQJAQQxKZoxNQIIonEjZUIAQUyKZkyNAIJo3EiZEEAQk6IZUyOAIBo3UiYEEMSkaMbUCCCIxo2UCQEEMSmaMTUCCKJxI2VCAEFMimZMjQCCaNxImRBAEJOiGVMjgCAaN1ImBBDEpGjG1AggiMaNlAkBBDEpmjE1AgiicSNlQgBBTIpmTI0AgmjcSJkQQBCTohlTI4AgGjdSJgQQxKRoxtQIIIjGjZQJAQQxKZoxNQIIonEjZUIAQUyKZkyNAIJo3EiZEEAQk6IZUyOAIBo3UiYEEMSkaMbUCCCIxo2UCQEEMSmaMTUCCKJxI2VC4B+Ci/5sJeSfvgAAAABJRU5ErkJggg=="; + let expected = "iVBORw0KGgoAAAANSUhEUgAAAMgAAACWCAYAAACb3McZAAAH0klEQVR4Xu2bSWhVZxiGv2gC7SKJWrRWxaGoULsW7L7gXlAMKApiN7pxI46ggnNQcDbOoAZUcCG4CCiIQ4MSkWKFLNSCihTR2ESTCNVb/lMTEmvu8OYuTN/nQBHb895zv+f9H+6ZWpHL5XLBBgEIfJZABYKwMiAwMAEEYXVAIA8BBGF5QABBWAMQ0AjwC6JxI2VCAEFMimZMjQCCaNxImRBAEJOiGVMjgCAaN1ImBBDEpGjG1AggiMaNlAkBBDEpmjE1AgiicSNlQgBBTIpmTI0AgmjcSJkQQBCTohlTI4AgGjdSJgQQxKRoxtQIIIjGjZQJAQQxKZoxNQIIonEjZUIAQUyKZkyNAIJo3EiZEEAQk6IZUyOAIBo3UiYEEMSkaMbUCCCIxo2UCQEEMSmaMTUCCKJxI2VCAEFMimZMjQCCaNxImRBAEJOiGVMjgCAaN1ImBBDEpGjG1AggiMaNlAkBBDEpmjE1AgiicSNlQgBBTIpmTI0AgmjcSJkQQBCTohlTI4AgGjdSJgQQxKRoxtQIIIjGjZQJAQQxKZoxNQIIonEjZUIAQUyKZkyNAIJo3EiZEEAQk6IZUyOAIBo3UiYEEMSkaMbUCCCIxo2UCQEEMSmaMTUCCPKR26NHj+LUqVNx69atuHDhQtTW1vYSvX37dhw4cCC6u7tj4sSJsXr16hg5cqRGnNSQIoAgH+vavHlzzJ49O9auXRvnzp3rFeTNmzdRV1cXHz58yP7J5XIxbdq02Lt375Aqmi+rEUCQT7glSfoKcunSpdizZ0+MGDEik+PVq1cxfPjwuHz5clRVVWnUSQ0ZAghSQJA1a9ZEOsVqaGiIHTt2xLNnz6Krqys7HRs/fvyQKZovqhFAkAKCpFOuO3fuxOjRo+Pdu3fR3t6e/ZIcPHgwpk6dqlEnNWQIIEgBQTZu3Bg3b96MioqKmDBhQjx58iQT5OTJk/1+QX599DLqGpr/U3wuF1FRUb71MOv7b6Lmq8qYMa42Hjz/K5p+/7Pfh6f/9tuG2eU7oPknIUgBQbZu3RpXrlyJ7du3Z9ceK1euzAQ5c+ZMjBkzpjc9kCDVaTF/V5PtlxZ3z1bzdVXMGPfvv69vao2WP9r6fZMfx9XEzz98G0/buuJpW2c8eN4eHd1/99tnIPkaf5kVP/U5lvkaH9T4CFJAkBUrVsT9+/dj6dKlkS7YOzo6It3ZOnr0aEyePHlQ8Al/+QQQJCJb9EmAtL18+TJGjRqVnVIdOnQo6uvro7m5Ofv7sGHDslu9aduyZUvMnDnzy2+YbzgoAghSAN/bt29j/vz58f79++zUKv2ZZJo7d+6gwBMeGgQQpEBPTU1NsWvXruw5SNra2tqiuro6Tpw4kf3J9v8mgCBl7Hcwr6Tke9Ul31e8evVqnD59OrsFnW4apGum9DoMW3kIIEh5OGYX7osWLYp012v69OnZon38+HGsX7++qCMM9KpLvnB6aLl8+fLYt29fdsu5sbEx7t69Gzt37izqmOxUmACCFGZU1B7Xrl2LdDqWFnraOjs7Y968eXHx4sWSXkn59FWXfAdP10cvXrzovZv28OHDWLduXSYKW3kIIEh5OGbPRV6/fh3Lli3r/cQkyO7du0t6JaUUQT796ufPn4/W1tZMErbyEECQ8nCM48eP997h6vnIBQsWxIYNG0p6JUUV5N69e9mpVRKy7wPMMo1n+zEIUqbqz549m93h6vsLMmfOnOy1+FJealQEuXHjRhw+fDg2bdoUU6ZMKdNEfEwigCBlWgfXr1/PXoFPF+lpS6dbCxcuzK5BKisriz5KqYKkFyn3798f27Zti7FjxxZ9HHYsjgCCFMep4F7pgnnx4sXZRXq6i3Xs2LHsqXx6d6uUrRRB0jGXLFmSvSc2adKkUg7DvkUSQJAiQRWzW0tLS3ZKle5gpf/rcNWqVUU9TMz3qkvPA8rPHf/Th5g9+xw5cqSo4xYzk/s+COK+Apg/LwEEYYFAIA8BBGF5QABBWAMQ0AjwC6JxI2VCAEFMimZMjQCCaNxImRBAEJOiGVMjgCAaN1ImBBDEpGjG1AggiMaNlAkBBDEpmjE1AgiicSNlQgBBTIpmTI0AgmjcSJkQQBCTohlTI4AgGjdSJgQQxKRoxtQIIIjGjZQJAQQxKZoxNQIIonEjZUIAQUyKZkyNAIJo3EiZEEAQk6IZUyOAIBo3UiYEEMSkaMbUCCCIxo2UCQEEMSmaMTUCCKJxI2VC4B+Ci/5sJeSfvgAAAABJRU5ErkJggg=="; let expected_decoded = general_purpose::STANDARD.decode(expected).unwrap(); // Comparing the result seems to end up being a flaky test. @@ -854,19 +1142,16 @@ mod tests { } #[test] - #[cfg(feature = "kaleido")] - fn image_to_base64_invalid_format() { - let plot = create_test_plot(); - let image_base64 = plot.to_base64(ImageFormat::EPS, 200, 150, 1.0); - assert!(image_base64.is_empty()); - } - - #[test] - #[cfg(not(target_os = "macos"))] - #[cfg(feature = "kaleido")] + #[cfg(feature = "plotly_static")] fn image_to_svg_string() { let plot = create_test_plot(); - let image_svg = plot.to_svg(200, 150, 1.0); + let mut exporter = plotly_static::StaticExporterBuilder::default() + .webdriver_port(get_unique_port()) + .build() + .unwrap(); + let image_svg = plot + .to_svg_with_exporter(&mut exporter, 200, 150, 1.0) + .unwrap(); assert!(!image_svg.is_empty()); @@ -877,9 +1162,8 @@ mod tests { assert_eq!(expected[..LEN], image_svg[..LEN]); } - #[cfg(target_os = "macos")] #[test] - #[cfg(feature = "kaleido")] + #[cfg(feature = "plotly_static")] fn save_surface_to_png() { use crate::Surface; let mut plot = Plot::new(); @@ -896,11 +1180,25 @@ mod tests { .name("Surface"); plot.add_trace(surface); - let dst = PathBuf::from("example.png"); - plot.write_image("example.png", ImageFormat::PNG, 800, 600, 1.0); + let dst = PathBuf::from("plotly_example_surface.png"); + let mut exporter = plotly_static::StaticExporterBuilder::default() + .webdriver_port(get_unique_port()) + .build() + .unwrap(); + + assert!(!plot + .to_base64_with_exporter(&mut exporter, ImageFormat::PNG, 1024, 680, 1.0) + .unwrap() + .is_empty()); + + plot.write_image_with_exporter(&mut exporter, &dst, ImageFormat::PNG, 800, 600, 1.0) + .unwrap(); assert!(dst.exists()); + + let metadata = std::fs::metadata(&dst).expect("Could not retrieve file metadata"); + let file_size = metadata.len(); + assert!(file_size > 0,); + #[cfg(not(feature = "debug"))] assert!(std::fs::remove_file(&dst).is_ok()); - assert!(!dst.exists()); - assert!(!plot.to_base64(ImageFormat::PNG, 1024, 680, 1.0).is_empty()); } } diff --git a/plotly/templates/static_plot.html b/plotly/templates/static_plot.html index a35a5b4e..5696db45 100644 --- a/plotly/templates/static_plot.html +++ b/plotly/templates/static_plot.html @@ -1,22 +1,22 @@ - - + + {{js_scripts}} - + - -
+ +
- - + + - -
- + img_element.setAttribute("src", data_url); + +
+ diff --git a/plotly/templates/template.json b/plotly/templates/template.json deleted file mode 100644 index fe19a415..00000000 --- a/plotly/templates/template.json +++ /dev/null @@ -1,787 +0,0 @@ -{ - "data": { - "bar": [ - { - "error_x": { - "color": "#2a3f5f" - }, - "error_y": { - "color": "#2a3f5f" - }, - "marker": { - "line": { - "color": "#E5ECF6", - "width": 0.5 - } - }, - "type": "bar" - } - ], - "barpolar": [ - { - "marker": { - "line": { - "color": "#E5ECF6", - "width": 0.5 - } - }, - "type": "barpolar" - } - ], - "carpet": [ - { - "aaxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "white", - "linecolor": "white", - "minorgridcolor": "white", - "startlinecolor": "#2a3f5f" - }, - "baxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "white", - "linecolor": "white", - "minorgridcolor": "white", - "startlinecolor": "#2a3f5f" - }, - "type": "carpet" - } - ], - "choropleth": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "choropleth" - } - ], - "contour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ], - "type": "contour" - } - ], - "contourcarpet": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "contourcarpet" - } - ], - "heatmap": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ], - "type": "heatmap" - } - ], - "heatmapgl": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ], - "type": "heatmapgl" - } - ], - "histogram": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "histogram" - } - ], - "histogram2d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ], - "type": "histogram2d" - } - ], - "histogram2dcontour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ], - "type": "histogram2dcontour" - } - ], - "mesh3d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "mesh3d" - } - ], - "parcoords": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "parcoords" - } - ], - "scatter": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatter" - } - ], - "scatter3d": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatter3d" - } - ], - "scattercarpet": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattercarpet" - } - ], - "scattergeo": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergeo" - } - ], - "scattergl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergl" - } - ], - "scattermapbox": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattermapbox" - } - ], - "scatterpolar": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolar" - } - ], - "scatterpolargl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolargl" - } - ], - "scatterternary": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterternary" - } - ], - "surface": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ], - "type": "surface" - } - ], - "table": [ - { - "cells": { - "fill": { - "color": "#EBF0F8" - }, - "line": { - "color": "white" - } - }, - "header": { - "fill": { - "color": "#C8D4E3" - }, - "line": { - "color": "white" - } - }, - "type": "table" - } - ] - }, - "layout": { - "annotationdefaults": { - "arrowcolor": "#2a3f5f", - "arrowhead": 0, - "arrowwidth": 1 - }, - "colorscale": { - "diverging": [ - [ - 0, - "#8e0152" - ], - [ - 0.1, - "#c51b7d" - ], - [ - 0.2, - "#de77ae" - ], - [ - 0.3, - "#f1b6da" - ], - [ - 0.4, - "#fde0ef" - ], - [ - 0.5, - "#f7f7f7" - ], - [ - 0.6, - "#e6f5d0" - ], - [ - 0.7, - "#b8e186" - ], - [ - 0.8, - "#7fbc41" - ], - [ - 0.9, - "#4d9221" - ], - [ - 1, - "#276419" - ] - ], - "sequential": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ], - "sequentialminus": [ - [ - 0.0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1.0, - "#f0f921" - ] - ] - }, - "colorway": [ - "#636efa", - "#EF553B", - "#00cc96", - "#ab63fa", - "#FFA15A", - "#19d3f3", - "#FF6692", - "#B6E880", - "#FF97FF", - "#FECB52" - ], - "font": { - "color": "#2a3f5f" - }, - "geo": { - "bgcolor": "white", - "lakecolor": "white", - "landcolor": "#E5ECF6", - "showlakes": true, - "showland": true, - "subunitcolor": "white" - }, - "hoverlabel": { - "align": "left" - }, - "hovermode": "closest", - "mapbox": { - "style": "light" - }, - "paper_bgcolor": "white", - "plot_bgcolor": "#E5ECF6", - "polar": { - "angularaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "bgcolor": "#E5ECF6", - "radialaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - } - }, - "scene": { - "xaxis": { - "backgroundcolor": "#E5ECF6", - "gridcolor": "white", - "gridwidth": 2, - "linecolor": "white", - "showbackground": true, - "ticks": "", - "zerolinecolor": "white" - }, - "yaxis": { - "backgroundcolor": "#E5ECF6", - "gridcolor": "white", - "gridwidth": 2, - "linecolor": "white", - "showbackground": true, - "ticks": "", - "zerolinecolor": "white" - }, - "zaxis": { - "backgroundcolor": "#E5ECF6", - "gridcolor": "white", - "gridwidth": 2, - "linecolor": "white", - "showbackground": true, - "ticks": "", - "zerolinecolor": "white" - } - }, - "shapedefaults": { - "line": { - "color": "#2a3f5f" - } - }, - "ternary": { - "aaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "baxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "bgcolor": "#E5ECF6", - "caxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - } - }, - "title": { - "x": 0.05 - }, - "xaxis": { - "automargin": true, - "gridcolor": "white", - "linecolor": "white", - "ticks": "", - "zerolinecolor": "white", - "zerolinewidth": 2 - }, - "yaxis": { - "automargin": true, - "gridcolor": "white", - "linecolor": "white", - "ticks": "", - "zerolinecolor": "white", - "zerolinewidth": 2 - } - } -} \ No newline at end of file diff --git a/plotly_derive/Cargo.toml b/plotly_derive/Cargo.toml index 3d9c7f43..6dcccd90 100644 --- a/plotly_derive/Cargo.toml +++ b/plotly_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "plotly_derive" -version = "0.12.1" +version = "0.13.0" description = "Internal proc macro crate for Plotly-rs." authors = ["Ioannis Giagkiozis "] license = "MIT" diff --git a/plotly_kaleido/Cargo.toml b/plotly_kaleido/Cargo.toml index 7cd015fb..94aa5936 100644 --- a/plotly_kaleido/Cargo.toml +++ b/plotly_kaleido/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "plotly_kaleido" -version = "0.12.1" +version = "0.13.0" description = "Additional output format support for plotly using Kaleido" authors = [ "Ioannis Giagkiozis ", diff --git a/plotly_kaleido/src/lib.rs b/plotly_kaleido/src/lib.rs index fdf90dba..b8c26847 100644 --- a/plotly_kaleido/src/lib.rs +++ b/plotly_kaleido/src/lib.rs @@ -21,6 +21,74 @@ use base64::{engine::general_purpose, Engine as _}; use serde::{Deserialize, Serialize}; use serde_json::Value; +/// Image format for static image export using Kaleido. +/// +/// This enum defines all the image formats that can be exported from Plotly +/// plots using the Kaleido engine. Kaleido supports all these formats natively. +#[derive(Debug, Clone)] +#[allow(deprecated)] +pub enum ImageFormat { + /// Portable Network Graphics format + PNG, + /// Joint Photographic Experts Group format + JPEG, + /// WebP format (Google's image format) + WEBP, + /// Scalable Vector Graphics format + SVG, + /// Portable Document Format + PDF, + /// Encapsulated PostScript format (deprecated) + /// + /// This format is deprecated since version 0.13.0 and will be removed in + /// version 0.14.0. Use SVG or PDF instead for vector graphics. EPS is + /// not supported in the open source version. + #[deprecated( + since = "0.13.0", + note = "Use SVG or PDF instead. EPS variant will be removed in version 0.14.0" + )] + EPS, +} + +impl std::fmt::Display for ImageFormat { + /// Converts the ImageFormat to its lowercase string representation. + /// + /// # Examples + /// + /// ```rust + /// use plotly_kaleido::ImageFormat; + /// + /// assert_eq!(ImageFormat::PNG.to_string(), "png"); + /// assert_eq!(ImageFormat::SVG.to_string(), "svg"); + /// assert_eq!(ImageFormat::PDF.to_string(), "pdf"); + /// assert_eq!(ImageFormat::EPS.to_string(), "eps"); + /// ``` + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::PNG => "png", + Self::JPEG => "jpeg", + Self::WEBP => "webp", + Self::SVG => "svg", + Self::PDF => "pdf", + #[allow(deprecated)] + Self::EPS => "eps", + } + ) + } +} + +impl serde::Serialize for ImageFormat { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + #[allow(dead_code)] #[derive(Deserialize, Debug)] struct KaleidoResult { @@ -44,9 +112,7 @@ impl KaleidoResult { #[derive(Serialize)] struct PlotData<'a> { - // TODO: as with `data`, it would be much better if this were a plotly::ImageFormat, but - // problems with cyclic dependencies. - format: String, + format: ImageFormat, width: usize, height: usize, scale: f64, @@ -56,9 +122,15 @@ struct PlotData<'a> { } impl<'a> PlotData<'a> { - fn new(data: &'a Value, format: &str, width: usize, height: usize, scale: f64) -> PlotData<'a> { + fn new( + data: &'a Value, + format: ImageFormat, + width: usize, + height: usize, + scale: f64, + ) -> PlotData<'a> { PlotData { - format: format.to_string(), + format, width, height, scale, @@ -137,17 +209,18 @@ impl Kaleido { &self, dst: &Path, plotly_data: &Value, - format: &str, + format: ImageFormat, width: usize, height: usize, scale: f64, ) -> Result<(), Box> { let mut dst = PathBuf::from(dst); - dst.set_extension(format); + dst.set_extension(format.to_string()); - let image_data = self.convert(plotly_data, format, width, height, scale)?; + let image_data = self.convert(plotly_data, format.clone(), width, height, scale)?; + #[allow(deprecated)] let data = match format { - "svg" | "eps" => image_data.as_bytes(), + ImageFormat::SVG | ImageFormat::EPS => image_data.as_bytes(), _ => &general_purpose::STANDARD.decode(image_data).unwrap(), }; let mut file = File::create(dst.as_path())?; @@ -164,12 +237,12 @@ impl Kaleido { pub fn image_to_string( &self, plotly_data: &Value, - format: &str, + format: ImageFormat, width: usize, height: usize, scale: f64, ) -> Result> { - let image_data = self.convert(plotly_data, format, width, height, scale)?; + let image_data = self.convert(plotly_data, format.clone(), width, height, scale)?; Ok(image_data) } @@ -178,12 +251,13 @@ impl Kaleido { pub fn convert( &self, plotly_data: &Value, - format: &str, + format: ImageFormat, width: usize, height: usize, scale: f64, ) -> Result> { let p = self.cmd_path.to_str().unwrap(); + let format_str = format.to_string(); // Removed flag 'disable-gpu' as it causes issues on MacOS and other platforms // see Kaleido issue #323 @@ -238,7 +312,7 @@ impl Kaleido { } // Don't eat up Kaleido/Chromium errors but show them in the terminal - println!("Kaleido failed to generate static image for format: {format}."); + println!("Kaleido failed to generate static image for format: {format_str}."); println!("Kaleido stderr output:"); let stderr = process.stderr.take().unwrap(); let stderr_lines = BufReader::new(stderr).lines(); @@ -307,7 +381,7 @@ mod tests { #[test] fn plot_data_to_json() { let test_plot = create_test_plot(); - let kaleido_data = PlotData::new(&test_plot, "png", 400, 500, 1.); + let kaleido_data = PlotData::new(&test_plot, ImageFormat::PNG, 400, 500, 1.); let expected = json!({ "data": test_plot, "format": "png", @@ -325,7 +399,7 @@ mod tests { let test_plot = create_test_plot(); let k = Kaleido::new(); let dst = PathBuf::from("example.png"); - let r = k.save(dst.as_path(), &test_plot, "png", 1200, 900, 4.5); + let r = k.save(dst.as_path(), &test_plot, ImageFormat::PNG, 1200, 900, 4.5); assert!(r.is_ok()); assert!(dst.exists()); let metadata = std::fs::metadata(&dst).expect("Could not retrieve file metadata"); @@ -339,7 +413,7 @@ mod tests { let test_plot = create_test_plot(); let k = Kaleido::new(); let dst = PathBuf::from("example.jpeg"); - let r = k.save(dst.as_path(), &test_plot, "jpeg", 1200, 900, 4.5); + let r = k.save(dst.as_path(), &test_plot, ImageFormat::JPEG, 1200, 900, 4.5); assert!(r.is_ok()); assert!(dst.exists()); let metadata = std::fs::metadata(&dst).expect("Could not retrieve file metadata"); @@ -353,7 +427,7 @@ mod tests { let test_plot = create_test_plot(); let k = Kaleido::new(); let dst = PathBuf::from("example.webp"); - let r = k.save(dst.as_path(), &test_plot, "webp", 1200, 900, 4.5); + let r = k.save(dst.as_path(), &test_plot, ImageFormat::WEBP, 1200, 900, 4.5); assert!(r.is_ok()); assert!(dst.exists()); let metadata = std::fs::metadata(&dst).expect("Could not retrieve file metadata"); @@ -367,7 +441,7 @@ mod tests { let test_plot = create_test_plot(); let k = Kaleido::new(); let dst = PathBuf::from("example.svg"); - let r = k.save(dst.as_path(), &test_plot, "svg", 1200, 900, 4.5); + let r = k.save(dst.as_path(), &test_plot, ImageFormat::SVG, 1200, 900, 4.5); assert!(r.is_ok()); assert!(dst.exists()); let metadata = std::fs::metadata(&dst).expect("Could not retrieve file metadata"); @@ -381,7 +455,7 @@ mod tests { let test_plot = create_test_plot(); let k = Kaleido::new(); let dst = PathBuf::from("example.pdf"); - let r = k.save(dst.as_path(), &test_plot, "pdf", 1200, 900, 4.5); + let r = k.save(dst.as_path(), &test_plot, ImageFormat::PDF, 1200, 900, 4.5); assert!(r.is_ok()); assert!(dst.exists()); let metadata = std::fs::metadata(&dst).expect("Could not retrieve file metadata"); @@ -397,7 +471,8 @@ mod tests { let test_plot = create_test_plot(); let k = Kaleido::new(); let dst = PathBuf::from("example.eps"); - let r = k.save(dst.as_path(), &test_plot, "eps", 1200, 900, 4.5); + #[allow(deprecated)] + let r = k.save(dst.as_path(), &test_plot, ImageFormat::EPS, 1200, 900, 4.5); assert!(r.is_ok()); assert!(std::fs::remove_file(dst.as_path()).is_ok()); } diff --git a/plotly_static/Cargo.toml b/plotly_static/Cargo.toml new file mode 100644 index 00000000..9afc1559 --- /dev/null +++ b/plotly_static/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "plotly_static" +version = "0.0.1" +description = "Export Plotly graphs to static images using WebDriver" +authors = ["Andrei Gherghescu andrei-ng@protonmail.com"] +license = "MIT" +workspace = ".." +homepage = "https://github.com/plotly/plotly.rs" +repository = "https://github.com/plotly/plotly.rs" +edition = "2021" +keywords = ["plot", "static", "image", "export", "chart", "plotly", "ndarray"] + +exclude = ["target/*"] + +[features] +webdriver_download = [] +geckodriver = [] +chromedriver = [] +# This is used for enabling extra debugging messages and debugging functionality +debug = [] + +[dependencies] +log = "0.4" +serde = { version = "1.0", features = ["derive"] } +rand = "0.9" +serde_json = "1.0" +base64 = "0.22" +fantoccini = "0.21" +tokio = { version = "1", features = ["full"] } +anyhow = "1.0" +urlencoding = "2" +reqwest = { version = "0.11", features = ["blocking"] } + +[dev-dependencies] +plotly_static = { path = "." } +ndarray = { version = "0.16" } +env_logger = "0.10" +clap = { version = "4.0", features = ["derive"] } + +[build-dependencies] +tokio = { version = "1", features = ["full"] } +anyhow = "1.0" +dirs = "5.0" +zip = "4.0" +webdriver-downloader = "0.16" diff --git a/plotly_static/LICENSE b/plotly_static/LICENSE new file mode 100644 index 00000000..d7ee7c8a --- /dev/null +++ b/plotly_static/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2024 Plotly, Inc + +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. \ No newline at end of file diff --git a/plotly_static/README.md b/plotly_static/README.md new file mode 100644 index 00000000..3fd15c7b --- /dev/null +++ b/plotly_static/README.md @@ -0,0 +1,158 @@ +# plotly_static + +Export Plotly plots to static images using WebDriver and headless browsers. + +## Overview + +`plotly_static` provides a Rust interface for converting Plotly plots from JSON data into various static image formats (PNG, JPEG, WEBP, SVG, PDF) using WebDriver and headless browsers. + +## Features + +- **Multiple Formats**: PNG, JPEG, WEBP, SVG, PDF +- **Browser Support**: Chrome/Chromium (chromedriver) and Firefox (geckodriver) +- **Efficient**: Reuse `StaticExporter` instances for multiple exports +- **String Export**: Base64 and SVG output for web applications +- **Parallel Safe**: Designed for concurrent usage +- **Automatic Management**: Handles WebDriver lifecycle and cleanup + +## Quick Start + +```rust +use plotly_static::{StaticExporterBuilder, ImageFormat}; +use serde_json::json; +use std::path::Path; + +// Create a simple plot as JSON +let plot = json!({ + "data": [{ + "type": "scatter", + "x": [1, 2, 3, 4], + "y": [10, 11, 12, 13] + }], + "layout": { + "title": "Simple Scatter Plot" + } +}); + +// Build and use StaticExporter +let mut exporter = StaticExporterBuilder::default() + .build() + .expect("Failed to build StaticExporter"); + +// Export to PNG +exporter.write_fig( + Path::new("my_plot"), + &plot, + ImageFormat::PNG, + 800, + 600, + 1.0 +).expect("Failed to export plot"); +``` + +## Usage + +Add to your `Cargo.toml`: + +```toml +[dependencies] +plotly_static = { version = "0.0.1", features = ["chromedriver", "webdriver_download"] } +serde_json = "1.0" +``` + +### Feature Flags + +- `chromedriver`: Use Chromedriver and Chrome/Chromium browser for rendering and export +- `geckodriver`: Use Geckodriver Firefox browser for rendering for rendering and export +- `webdriver_download`: Auto-download the chosen WebDriver binary + +## Prerequisites + +1. **Browser**: Chrome/Chromium or Firefox installed +2. **WebDriver**: Manually installed or automatically downloaded and installed with the `webdriver_download` feature +3. **Internet Connectivity**: Required for WebDriver download when using the auto-download and install feature + +## Advanced Usage + +### Static Exporter Reuse + +```rust +use plotly_static::{StaticExporterBuilder, ImageFormat}; +use serde_json::json; +use std::path::Path; + +let plot1 = json!({ + "data": [{"type": "scatter", "x": [1,2,3], "y": [4,5,6]}], + "layout": {"title": "Plot 1"} +}); + +let plot2 = json!({ + "data": [{"type": "scatter", "x": [2,3,4], "y": [5,6,7]}], + "layout": {"title": "Plot 2"} +}); + +let mut exporter = StaticExporterBuilder::default() + .build() + .expect("Failed to create StaticExporter"); + +// Reuse for multiple exports +exporter.write_fig(Path::new("plot1"), &plot1, ImageFormat::PNG, 800, 600, 1.0)?; +exporter.write_fig(Path::new("plot2"), &plot2, ImageFormat::JPEG, 800, 600, 1.0)?; +``` + +### String Export + +```rust +use plotly_static::{StaticExporterBuilder, ImageFormat}; +use serde_json::json; + +let plot = json!({ + "data": [{"type": "scatter", "x": [1,2,3], "y": [4,5,6]}], + "layout": {} +}); + +let mut exporter = StaticExporterBuilder::default() + .build() + .expect("Failed to create StaticExporter"); + +// Get base64 data for web embedding +let base64_data = exporter.write_to_string(&plot, ImageFormat::PNG, 400, 300, 1.0)?; + +// Get SVG data (vector format) +let svg_data = exporter.write_to_string(&plot, ImageFormat::SVG, 400, 300, 1.0)?; +``` + +### Custom Configuration + +```rust +use plotly_static::StaticExporterBuilder; + +let mut exporter = StaticExporterBuilder::default() + .webdriver_port(4445) // Unique port for parallel usage + .offline_mode(true) // Use bundled JavaScript + .webdriver_browser_caps(vec![ + "--headless".to_string(), + "--no-sandbox".to_string(), + ]) + .build()?; +``` + +## Environment Variables + +- `WEBDRIVER_PATH`: Custom WebDriver binary location +- `BROWSER_PATH`: Custom browser binary location + +## Examples + +Check the self contatined examples in the examples folder. + +Similar examples are available in the [Plotly.rs package](https://github.com/plotly/plotly.rs), in [Plotly.rs Book](https://plotly.github.io/plotly.rs/) as well as the example in [Plotly.rs examples/static_export](https://github.com/plotly/plotly.rs/tree/main/examples/static_export). + +## Documentation + +- [API Documentation](https://docs.rs/plotly_static/) +- [Static Image Export Guide](../../docs/book/src/fundamentals/static_image_export.md) + +## License + +This package is licensed under the MIT License. \ No newline at end of file diff --git a/plotly_static/build.rs b/plotly_static/build.rs new file mode 100644 index 00000000..f8b2c076 --- /dev/null +++ b/plotly_static/build.rs @@ -0,0 +1,342 @@ +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::time::Duration; + +use anyhow::{anyhow, Context, Result}; +use tokio::time::sleep; +use webdriver_downloader::prelude::*; + +// Enforce that only one driver feature is enabled +#[cfg(all(feature = "geckodriver", feature = "chromedriver"))] +compile_error!("Only one of 'geckodriver' or 'chromedriver' features can be enabled at a time."); + +// Enforce that at least one driver feature is enabled +#[cfg(not(any(feature = "geckodriver", feature = "chromedriver")))] +compile_error!("At least one of 'geckodriver' or 'chromedriver' features must be enabled."); + +#[cfg(target_os = "windows")] +const DRIVER_EXT: &str = ".exe"; +#[cfg(not(target_os = "windows"))] +const DRIVER_EXT: &str = ""; + +const BROWSER_BIN_PATH_ENV: &str = "BROWSER_PATH"; +const WEBDRIVER_BIN_PATH_ENV: &str = "WEBDRIVER_PATH"; +const INSTALL_PATH_ENV: &str = "WEBDRIVER_INSTALL_PATH"; +const GECKODRIVER_NAME: &str = "geckodriver"; +const CHROMEDRIVER_NAME: &str = "chromedriver"; + +const MAX_DOWNLOAD_RETRIES: u32 = 3; +const INITIAL_RETRY_DELAY: u64 = 2; + +struct WebdriverDownloadConfig { + driver_name: &'static str, + get_browser_path: fn() -> Result, +} + +/// Get user's bin directory for driver installs (e.g., $HOME/.local/bin or +/// %USERPROFILE%\.local\bin) or set it to the one specified via the ENV +/// variable `WEBDRIVER_INSTALL_PATH` +fn user_bin_dir() -> PathBuf { + if let Ok(bin) = env::var(INSTALL_PATH_ENV) { + return PathBuf::from(bin); + } + if let Some(home) = dirs::home_dir() { + #[cfg(target_os = "windows")] + { + return home.join(".local").join("bin"); + } + #[cfg(not(target_os = "windows"))] + { + return home.join(".local").join("bin"); + } + } + PathBuf::from(".") +} + +/// Check if a driver is already installed at the given path from environment +/// variable +fn is_webdriver_available(bin_name: &str) -> bool { + // First check environment variable path + if let Ok(path) = env::var(WEBDRIVER_BIN_PATH_ENV) { + let bin_path = if cfg!(target_os = "windows") && !path.to_lowercase().ends_with(".exe") { + format!("{path}{DRIVER_EXT}") + } else { + path + }; + let exe = Path::new(&bin_path); + if exe.exists() && exe.is_file() { + println!("{bin_name} found at path specified in {WEBDRIVER_BIN_PATH_ENV}: {bin_path}"); + return true; + } + } + + // Check if webdriver exists in user's bin directory + let bin_dir = user_bin_dir(); + let bin_path = bin_dir.join(format!("{bin_name}{DRIVER_EXT}")); + if bin_path.exists() && bin_path.is_file() { + println!( + "{} found in user's bin directory: {}", + bin_name, + bin_path.display() + ); + println!( + "cargo:rustc-env=WEBDRIVER_DOWNLOAD_PATH={}", + bin_path.to_string_lossy() + ); + return true; + } + + false +} + +async fn download_with_retry( + driver_info: &impl WebdriverDownloadInfo, + reinstall: bool, + skip_verification: bool, + num_tries: usize, +) -> Result<()> { + let mut attempts = 0; + let mut last_error = None; + + while attempts < MAX_DOWNLOAD_RETRIES { + match download(driver_info, reinstall, skip_verification, num_tries).await { + Ok(_) => { + return Ok(()); + } + Err(e) => { + last_error = Some(e); + attempts += 1; + if attempts < MAX_DOWNLOAD_RETRIES { + let delay = Duration::from_secs(INITIAL_RETRY_DELAY * 2u64.pow(attempts - 1)); + println!( + "cargo:warning=Download attempt {attempts} failed, retrying in {delay:?}..." + ); + sleep(delay).await; + } + } + } + } + + Err(anyhow!( + "Failed to download driver after {MAX_DOWNLOAD_RETRIES} attempts: {last_error:?}", + )) +} + +fn setup_driver(config: &WebdriverDownloadConfig) -> Result<()> { + if is_webdriver_available(config.driver_name) { + return Ok(()); + } + println!( + "cargo::warning=You can specify {WEBDRIVER_BIN_PATH_ENV} to an existing {CHROMEDRIVER_NAME}/{GECKODRIVER_NAME} installation to avoid downloads." + ); + println!( + "cargo::warning=You can override browser detection using {BROWSER_BIN_PATH_ENV} environment variable." + ); + + println!( + "cargo::warning={} selected but not installed, will be downloaded ... ", + config.driver_name + ); + + let browser_path = (config.get_browser_path)() + .with_context(|| format!("Failed to detect browser path for {}", config.driver_name))?; + let browser_version = get_browser_version(&browser_path).with_context(|| { + format!( + "Failed to get version for browser at {}", + browser_path.display() + ) + })?; + println!("cargo::warning=Browser version detected: {browser_version}"); + + let webdriver_bin_dir = user_bin_dir(); + println!("cargo::warning=Driver will be installed in: {webdriver_bin_dir:?}"); + + fs::create_dir_all(&webdriver_bin_dir).with_context(|| { + format!( + "Failed to create directory: {}", + webdriver_bin_dir.display() + ) + })?; + let webdriver_bin = webdriver_bin_dir.join(config.driver_name); + + println!( + "cargo::rerun-if-changed={}", + webdriver_bin.to_string_lossy() + ); + + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .context("Failed to create Tokio runtime")?; + + match config.driver_name { + CHROMEDRIVER_NAME => { + let driver_info = ChromedriverInfo::new(webdriver_bin.clone(), browser_path); + runtime + .block_on(async { download_with_retry(&driver_info, false, true, 1).await }) + .with_context(|| { + format!("Failed to download and install {}", config.driver_name) + })?; + } + GECKODRIVER_NAME => { + let driver_info = GeckodriverInfo::new(webdriver_bin.clone(), browser_path); + runtime + .block_on(async { download_with_retry(&driver_info, false, true, 1).await }) + .with_context(|| { + format!("Failed to download and install {}", config.driver_name) + })?; + } + _ => return Err(anyhow!("Unsupported driver type: {}", config.driver_name)), + } + + println!( + "cargo:rustc-env=WEBDRIVER_DOWNLOAD_PATH={}", + webdriver_bin.to_string_lossy() + ); + + Ok(()) +} + +#[cfg(feature = "chromedriver")] +fn get_chrome_path() -> Result { + if let Ok(chrome_path) = env::var(BROWSER_BIN_PATH_ENV) { + let path = PathBuf::from(&chrome_path); + if path.exists() { + Ok(path) + } else { + Err(anyhow!("Chrome not found on path: {chrome_path}")).with_context(|| { + format!("Please set {BROWSER_BIN_PATH_ENV} to a valid Chrome installation") + }) + } + } else { + let new_browser_path = os_specific::chromedriver_for_testing::default_browser_path()?; + let old_browser_path = os_specific::chromedriver_old::default_browser_path()?; + if new_browser_path.exists() { + Ok(new_browser_path) + } else if old_browser_path.exists() { + Ok(old_browser_path) + } else { + Err(anyhow!("Chrome browser not detected")).with_context(|| { + format!("Use {BROWSER_BIN_PATH_ENV} to point to a valid Chrome installation") + }) + } + } +} + +#[cfg(feature = "geckodriver")] +fn get_firefox_path() -> Result { + if let Ok(firefox_path) = env::var(BROWSER_BIN_PATH_ENV) { + let path = PathBuf::from(firefox_path.clone()); + if path.exists() { + Ok(path) + } else { + Err(anyhow!("Firefox not found on path: {firefox_path}")).with_context(|| { + format!("Please set {BROWSER_BIN_PATH_ENV} to a valid Firefox installation",) + }) + } + } else { + let browser_path = os_specific::geckodriver::default_browser_path()?; + if browser_path.exists() { + Ok(browser_path) + } else { + Err(anyhow!("Firefox browser not detected")).with_context(|| { + format!("Use {BROWSER_BIN_PATH_ENV} to point to a valid Firefox installation",) + }) + } + } +} + +fn get_browser_version(path: &PathBuf) -> Result { + let output = Command::new(path) + .arg("--version") + .output() + .with_context(|| format!("Failed to execute browser at {}", path.display()))?; + let out_str = String::from_utf8_lossy(&output.stdout); + out_str + .split_whitespace() + .find(|s| s.chars().next().unwrap_or(' ').is_ascii_digit()) + .map(|v| v.to_string()) + .ok_or(anyhow!( + "Failed to get browser version for browser: {}", + path.display() + )) + .with_context(|| { + format!( + "Browser at {} did not return a valid version string", + path.display() + ) + }) +} + +async fn download( + driver_info: &impl WebdriverDownloadInfo, + reinstall: bool, + skip_verification: bool, + num_tries: usize, +) -> Result<(), WebdriverDownloadError> { + if !reinstall && driver_info.is_installed().await { + println!("cargo::warning=Driver already installed ..."); + Ok(()) + } else { + if skip_verification { + driver_info.download_install().await?; + } else { + driver_info.download_verify_install(num_tries).await?; + } + + println!("cargo::warning=Driver installed successfully ..."); + Ok(()) + } +} + +fn main() -> Result<()> { + if cfg!(feature = "webdriver_download") { + println!("cargo:rerun-if-changed=src/lib.rs"); + let webdriver_bin_dir = user_bin_dir(); + println!( + "cargo::rerun-if-changed={}", + webdriver_bin_dir.to_string_lossy() + ); + + #[cfg(feature = "chromedriver")] + { + let config = WebdriverDownloadConfig { + driver_name: CHROMEDRIVER_NAME, + get_browser_path: get_chrome_path, + }; + setup_driver(&config)?; + } + + #[cfg(feature = "geckodriver")] + { + let config = WebdriverDownloadConfig { + driver_name: GECKODRIVER_NAME, + get_browser_path: get_firefox_path, + }; + setup_driver(&config)?; + } + + #[cfg(not(any(feature = "chromedriver", feature = "geckodriver")))] + { + println!("cargo::warning=No specific driver feature enabled, skipping driver setup"); + } + } else { + #[cfg(feature = "chromedriver")] + { + let msg = format!("'webdriver_download' feature disabled. Please install a '{CHROMEDRIVER_NAME}' version manually and make the environment variable 'WEBDRIVER_PATH' point to it."); + println!("cargo::warning={msg}"); + } + #[cfg(feature = "geckodriver")] + { + let msg = format!("'webdriver_download' feature disabled. Please install a '{GECKODRIVER_NAME}' version manually and make the environment variable 'WEBDRIVER_PATH' point to it."); + println!("cargo::warning={msg}"); + } + #[cfg(not(any(feature = "chromedriver", feature = "geckodriver")))] + { + println!("cargo::warning='webdriver_download' feature disabled and no driver feature enabled"); + } + } + Ok(()) +} diff --git a/plotly_static/examples/README.md b/plotly_static/examples/README.md new file mode 100644 index 00000000..c10a2d85 --- /dev/null +++ b/plotly_static/examples/README.md @@ -0,0 +1,110 @@ +# Plotly Static Export CLI Example + +This example demonstrates how to use the `plotly_static` crate with `clap` to create a command-line tool for exporting Plotly plots to static images. + +## Usage + +### Basic Usage + +Export a plot from a JSON file (using Chrome driver): +```bash +cargo run --example main --features chromedriver -- -i sample_plot.json -o my_plot -f png +``` + +Export a plot from a JSON file (using Firefox/Gecko driver): +```bash +cargo run --example main --features geckodriver -- -i sample_plot.json -o my_plot -f png +``` + +Export a plot from stdin: +```bash +cat sample_plot.json | cargo run --example main --features chromedriver -- -f svg -o output +``` + +### Web Driver Options + +The example supports two different web drivers for rendering plots: + +- **Chrome Driver** (`--features chromedriver`): Uses Chrome/Chromium browser for rendering +- **Gecko Driver** (`--features geckodriver`): Uses Firefox browser for rendering + +You must specify one of these features when running the example. For example: +```bash +# Use Chrome driver +cargo run --example main --features chromedriver -- -i plot.json -o output.png + +# Use Firefox driver +cargo run --example main --features geckodriver -- -i plot.json -o output.png +``` + +### Logging + +The example uses `env_logger` for logging. You can enable different log levels using the `RUST_LOG` environment variable: + +```bash +# Enable info level logging +RUST_LOG=info cargo run --example main --features chromedriver -- -i sample_plot.json -o my_plot -f png + +# Enable debug level logging for more verbose output +RUST_LOG=debug cargo run --example main --features geckodriver -- -i sample_plot.json -o my_plot -f png + +# Enable all logging levels +RUST_LOG=trace cargo run --example main --features chromedriver -- -i sample_plot.json -o my_plot -f png +``` + +### Command Line Options + +- `-i, --input`: Input file containing Plotly JSON (use '-' for stdin, default: "-") +- `-o, --output`: Output file path (default: "output") +- `-f, --format`: Image format (png, jpeg, webp, svg, pdf, default: png) +- `--width`: Image width in pixels (default: 800) +- `--height`: Image height in pixels (default: 600) +- `-s, --scale`: Image scale factor (default: 1.0) +- `--offline`: Use offline mode (bundled JavaScript) + +### Examples + +Export to PNG with custom dimensions: +```bash +cargo run --example main --features chromedriver -- -i sample_plot.json -o plot -f png --width 1200 --height 800 +``` + +Export to SVG from stdin: +```bash +echo '{"data":[{"type":"scatter","x":[1,2,3],"y":[4,5,6]}],"layout":{}}' | \ +cargo run --example main --features geckodriver -- -f svg -o scatter_plot +``` + +Export to PDF with high resolution: +```bash +cargo run --example main --features chromedriver -- -i sample_plot.json -o report -f pdf --width 1600 --height 1200 -s 2.0 +``` + +### JSON Format + +The input JSON should follow the Plotly figure specification: + +```json +{ + "data": [ + { + "type": "surface", + "x": [1.0, 2.0, 3.0], + "y": [4.0, 5.0, 6.0], + "z": [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]] + } + ], + "layout": {}, + "config": {} +} +``` + +## Features + +- Support for all major image formats (PNG, JPEG, WEBP, SVG, PDF) +- Input from files or stdin +- Customizable dimensions and scale +- Offline mode support +- Comprehensive error handling +- Built-in help and version information +- Configurable logging with environment variables \ No newline at end of file diff --git a/plotly_static/examples/generate_static.rs b/plotly_static/examples/generate_static.rs new file mode 100644 index 00000000..932d5d3a --- /dev/null +++ b/plotly_static/examples/generate_static.rs @@ -0,0 +1,135 @@ +use std::fs; +use std::io::{self, Read}; +use std::path::PathBuf; + +use clap::{Parser, ValueEnum}; +use log::info; +use plotly_static::{ImageFormat, StaticExporterBuilder}; +use serde_json::Value; + +#[derive(Parser)] +#[command(name = "plotly-static-export")] +#[command(about = "Export Plotly plots to static images")] +#[command(version)] +struct Cli { + /// Input file containing Plotly JSON (use '-' for stdin) + #[arg(short, long, required = true, default_value = "-")] + input: String, + + /// Output file path + #[arg(short, long, default_value = "output")] + output: PathBuf, + + /// Image format + #[arg(short, long, value_enum, default_value_t = ImageFormatArg::PNG)] + format: ImageFormatArg, + + /// Image width in pixels + #[arg(long, default_value_t = 800)] + width: usize, + + /// Image height in pixels + #[arg(long, default_value_t = 600)] + height: usize, + + /// Image scale factor + #[arg(short, long, default_value_t = 1.0)] + scale: f64, + + /// Use offline mode (bundled JavaScript) + #[arg(long)] + offline: bool, +} + +#[derive(ValueEnum, Clone)] +#[allow(clippy::upper_case_acronyms)] +enum ImageFormatArg { + PNG, + JPEG, + WEBP, + SVG, + PDF, +} + +impl std::fmt::Display for ImageFormatArg { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + ImageFormatArg::PNG => "PNG", + ImageFormatArg::JPEG => "JPEG", + ImageFormatArg::WEBP => "WEBP", + ImageFormatArg::SVG => "SVG", + ImageFormatArg::PDF => "PDF", + } + ) + } +} + +impl From for ImageFormat { + fn from(format: ImageFormatArg) -> Self { + match format { + ImageFormatArg::PNG => ImageFormat::PNG, + ImageFormatArg::JPEG => ImageFormat::JPEG, + ImageFormatArg::WEBP => ImageFormat::WEBP, + ImageFormatArg::SVG => ImageFormat::SVG, + ImageFormatArg::PDF => ImageFormat::PDF, + } + } +} + +fn read_json_from_stdin() -> Result> { + let mut buffer = String::new(); + io::stdin().read_to_string(&mut buffer)?; + let json: Value = serde_json::from_str(&buffer)?; + Ok(json) +} + +fn read_json_from_file(path: &str) -> Result> { + let content = fs::read_to_string(path)?; + let json: Value = serde_json::from_str(&content)?; + Ok(json) +} + +fn main() -> Result<(), Box> { + env_logger::init(); + let cli = Cli::parse(); + + // Read JSON input + let plot_json = if cli.input == "-" { + info!("Reading Plotly JSON from stdin..."); + read_json_from_stdin()? + } else { + info!("Reading Plotly JSON from file: {}", cli.input); + read_json_from_file(&cli.input)? + }; + + // Validate that the JSON has the expected structure + if !plot_json.is_object() { + return Err("Invalid JSON: expected an object".into()); + } + + // Build StaticExporter instance + let mut exporter = StaticExporterBuilder::default() + .offline_mode(cli.offline) + .build()?; + + info!( + "Exporting plot to {} format ({}x{} pixels, scale: {})...", + cli.format, cli.width, cli.height, cli.scale + ); + + // Export the plot + exporter.write_fig( + &cli.output, + &plot_json, + cli.format.into(), + cli.width, + cli.height, + cli.scale, + )?; + + info!("Successfully exported plot to: {}", cli.output.display()); + Ok(()) +} diff --git a/plotly_static/examples/sample_plot.json b/plotly_static/examples/sample_plot.json new file mode 100644 index 00000000..d469a33b --- /dev/null +++ b/plotly_static/examples/sample_plot.json @@ -0,0 +1,37 @@ +{ + "data": [ + { + "name": "Surface", + "type": "surface", + "x": [ + 1.0, + 2.0, + 3.0 + ], + "y": [ + 4.0, + 5.0, + 6.0 + ], + "z": [ + [ + 1.0, + 2.0, + 3.0 + ], + [ + 4.0, + 5.0, + 6.0 + ], + [ + 7.0, + 8.0, + 9.0 + ] + ] + } + ], + "layout": {}, + "config": {} +} \ No newline at end of file diff --git a/plotly_static/examples/test_chrome_path.rs b/plotly_static/examples/test_chrome_path.rs new file mode 100644 index 00000000..2c7eab5d --- /dev/null +++ b/plotly_static/examples/test_chrome_path.rs @@ -0,0 +1,28 @@ +use plotly_static::{ImageFormat, StaticExporterBuilder}; +use serde_json::json; + +fn main() { + // Set BROWSER_PATH to test the binary capability + std::env::set_var("BROWSER_PATH", "/usr/bin/google-chrome"); + + let plot = json!({ + "data": [{ + "type": "scatter", + "x": [1, 2, 3], + "y": [4, 5, 6] + }], + "layout": {} + }); + + let mut exporter = StaticExporterBuilder::default() + .build() + .expect("Failed to build StaticExporter"); + + // Test that we can export with the custom Chrome binary + let svg_data = exporter + .write_to_string(&plot, ImageFormat::SVG, 800, 600, 1.0) + .expect("Failed to export plot"); + + println!("Successfully exported SVG with custom Chrome binary!"); + println!("SVG length: {} characters", svg_data.len()); +} diff --git a/plotly_static/resource/html2pdf.bundle.min.js b/plotly_static/resource/html2pdf.bundle.min.js new file mode 100644 index 00000000..c57dc929 --- /dev/null +++ b/plotly_static/resource/html2pdf.bundle.min.js @@ -0,0 +1,3 @@ +/*! For license information please see html2pdf.bundle.min.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("html2pdf",[],t):"object"==typeof exports?exports.html2pdf=t():e.html2pdf=t()}(self,(function(){return function(){var e,t,r={"./node_modules/@babel/runtime-corejs3/core-js-stable/array/from.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/array/from.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/array/is-array.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/array/is-array.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/date/now.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/date/now.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/bind.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/bind.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/concat.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/concat.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/every.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/every.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/fill.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/fill.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/filter.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/filter.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/for-each.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/for-each.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/includes.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/includes.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/index-of.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/index-of.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/map.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/map.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/reduce.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/reduce.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/reverse.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/reverse.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/slice.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/slice.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/some.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/some.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/starts-with.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/starts-with.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/trim.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/trim.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/instance/values.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/instance/values.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/map.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/map/index.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/object/define-properties.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/object/define-properties.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/object/define-property.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/object/define-property.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/object/get-own-property-descriptor.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/object/get-own-property-descriptor.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/object/get-own-property-descriptors.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/object/get-own-property-descriptors.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/object/get-own-property-symbols.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/object/get-own-property-symbols.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/object/keys.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/object/keys.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/parse-float.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/parse-float.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/parse-int.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/parse-int.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/promise.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/promise/index.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/reflect/apply.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/reflect/apply.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/reflect/construct.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/reflect/construct.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/reflect/delete-property.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/reflect/delete-property.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/reflect/get-prototype-of.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/reflect/get-prototype-of.js")},"./node_modules/@babel/runtime-corejs3/core-js-stable/symbol.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/stable/symbol/index.js")},"./node_modules/@babel/runtime-corejs3/core-js/array/from.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/features/array/from.js")},"./node_modules/@babel/runtime-corejs3/core-js/array/is-array.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/features/array/is-array.js")},"./node_modules/@babel/runtime-corejs3/core-js/get-iterator-method.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/features/get-iterator-method.js")},"./node_modules/@babel/runtime-corejs3/core-js/get-iterator.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/features/get-iterator.js")},"./node_modules/@babel/runtime-corejs3/core-js/instance/slice.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/features/instance/slice.js")},"./node_modules/@babel/runtime-corejs3/core-js/object/create.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/features/object/create.js")},"./node_modules/@babel/runtime-corejs3/core-js/object/define-property.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/features/object/define-property.js")},"./node_modules/@babel/runtime-corejs3/core-js/object/get-own-property-descriptor.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/features/object/get-own-property-descriptor.js")},"./node_modules/@babel/runtime-corejs3/core-js/object/get-prototype-of.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/features/object/get-prototype-of.js")},"./node_modules/@babel/runtime-corejs3/core-js/object/set-prototype-of.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/features/object/set-prototype-of.js")},"./node_modules/@babel/runtime-corejs3/core-js/promise.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/features/promise/index.js")},"./node_modules/@babel/runtime-corejs3/core-js/reflect/get.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/features/reflect/get.js")},"./node_modules/@babel/runtime-corejs3/core-js/symbol.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/features/symbol/index.js")},"./node_modules/@babel/runtime-corejs3/core-js/symbol/iterator.js":function(e,t,r){e.exports=r("./node_modules/core-js-pure/features/symbol/iterator.js")},"./node_modules/@babel/runtime-corejs3/helpers/esm/arrayLikeToArray.js":function(e,t,r){"use strict";function n(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=new Array(t);rA){var c=u;u=A,A=c}}else{if("l"!==e&&"landscape"!==e)throw"Invalid orientation: "+e;e="l",A>u&&(c=u,u=A,A=c)}return{width:u,height:A,unit:t,k:a}},t.default=n.jsPDF},"./src/plugin/pagebreaks.js":function(e,t,r){"use strict";r.r(t),r("./node_modules/core-js/modules/es.array.concat.js"),r("./node_modules/core-js/modules/es.array.slice.js"),r("./node_modules/core-js/modules/es.array.join.js"),r("./node_modules/core-js/modules/web.dom-collections.for-each.js"),r("./node_modules/core-js/modules/es.object.keys.js");var n=r("./src/worker.js"),o=r("./src/utils.js"),s={toContainer:n.default.prototype.toContainer};n.default.template.opt.pagebreak={mode:["css","legacy"],before:[],after:[],avoid:[]},n.default.prototype.toContainer=function(){return s.toContainer.call(this).then((function(){var e=this.prop.container,t=this.prop.pageSize.inner.px.height,r=[].concat(this.opt.pagebreak.mode),n={avoidAll:-1!==r.indexOf("avoid-all"),css:-1!==r.indexOf("css"),legacy:-1!==r.indexOf("legacy")},s={},i=this;["before","after","avoid"].forEach((function(t){var r=n.avoidAll&&"avoid"===t;s[t]=r?[]:[].concat(i.opt.pagebreak[t]||[]),s[t].length>0&&(s[t]=Array.prototype.slice.call(e.querySelectorAll(s[t].join(", "))))}));var a=e.querySelectorAll(".html2pdf__page-break");a=Array.prototype.slice.call(a);var A=e.querySelectorAll("*");Array.prototype.forEach.call(A,(function(e){var r={before:!1,after:n.legacy&&-1!==a.indexOf(e),avoid:n.avoidAll};if(n.css){var i=window.getComputedStyle(e),A=["always","page","left","right"];r={before:r.before||-1!==A.indexOf(i.breakBefore||i.pageBreakBefore),after:r.after||-1!==A.indexOf(i.breakAfter||i.pageBreakAfter),avoid:r.avoid||-1!==["avoid","avoid-page"].indexOf(i.breakInside||i.pageBreakInside)}}Object.keys(r).forEach((function(t){r[t]=r[t]||-1!==s[t].indexOf(e)}));var u=e.getBoundingClientRect();if(r.avoid&&!r.before){var c=Math.floor(u.top/t),l=Math.floor(u.bottom/t),d=Math.abs(u.bottom-u.top)/t;l!==c&&d<=1&&(r.before=!0)}if(r.before){var f=(0,o.createElement)("div",{style:{display:"block",height:t-u.top%t+"px"}});e.parentNode.insertBefore(f,e)}r.after&&(f=(0,o.createElement)("div",{style:{display:"block",height:t-u.bottom%t+"px"}}),e.parentNode.insertBefore(f,e.nextSibling))}))}))}},"./src/utils.js":function(e,t,r){"use strict";function n(e){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}r.r(t),r.d(t,{objType:function(){return o},createElement:function(){return s},cloneNode:function(){return i},unitConvert:function(){return a},toPx:function(){return A}}),r("./node_modules/core-js/modules/es.number.constructor.js"),r("./node_modules/core-js/modules/es.symbol.js"),r("./node_modules/core-js/modules/es.symbol.description.js"),r("./node_modules/core-js/modules/es.object.to-string.js"),r("./node_modules/core-js/modules/es.symbol.iterator.js"),r("./node_modules/core-js/modules/es.array.iterator.js"),r("./node_modules/core-js/modules/es.string.iterator.js"),r("./node_modules/core-js/modules/web.dom-collections.iterator.js");var o=function(e){var t=n(e);return"undefined"===t?"undefined":"string"===t||e instanceof String?"string":"number"===t||e instanceof Number?"number":"function"===t||e instanceof Function?"function":e&&e.constructor===Array?"array":e&&1===e.nodeType?"element":"object"===t?"object":"unknown"},s=function(e,t){var r=document.createElement(e);if(t.className&&(r.className=t.className),t.innerHTML){r.innerHTML=t.innerHTML;for(var n=r.getElementsByTagName("script"),o=n.length;o-- >0;null)n[o].parentNode.removeChild(n[o])}for(var s in t.style)r.style[s]=t.style[s];return r},i=function e(t,r){for(var n=3===t.nodeType?document.createTextNode(t.nodeValue):t.cloneNode(!1),o=t.firstChild;o;o=o.nextSibling)!0!==r&&1===o.nodeType&&"SCRIPT"===o.nodeName||n.appendChild(e(o,r));return 1===t.nodeType&&("CANVAS"===t.nodeName?(n.width=t.width,n.height=t.height,n.getContext("2d").drawImage(t,0,0)):"TEXTAREA"!==t.nodeName&&"SELECT"!==t.nodeName||(n.value=t.value),n.addEventListener("load",(function(){n.scrollTop=t.scrollTop,n.scrollLeft=t.scrollLeft}),!0)),n},a=function(e,t){if("number"===o(e))return 72*e/96/t;var r={};for(var n in e)r[n]=72*e[n]/96/t;return r},A=function(e,t){return Math.floor(e*t/72*96)}},"./src/worker.js":function(e,t,r){"use strict";r.r(t),r("./node_modules/core-js/modules/es.object.assign.js"),r("./node_modules/core-js/modules/es.array.map.js"),r("./node_modules/core-js/modules/es.object.keys.js"),r("./node_modules/core-js/modules/es.array.concat.js"),r("./node_modules/core-js/modules/es.object.to-string.js"),r("./node_modules/core-js/modules/es.regexp.to-string.js"),r("./node_modules/core-js/modules/es.function.name.js"),r("./node_modules/core-js/modules/web.dom-collections.for-each.js");var n=r("./node_modules/jspdf/dist/jspdf.es.min.js"),o=r("./node_modules/html2canvas/dist/html2canvas.js"),s=r("./src/utils.js"),i=r("./node_modules/es6-promise/dist/es6-promise.js"),a=r.n(i)().Promise,A=function e(t){var r=Object.assign(e.convert(a.resolve()),JSON.parse(JSON.stringify(e.template))),n=e.convert(a.resolve(),r);return(n=n.setProgress(1,e,1,[e])).set(t)};(A.prototype=Object.create(a.prototype)).constructor=A,A.convert=function(e,t){return e.__proto__=t||A.prototype,e},A.template={prop:{src:null,container:null,overlay:null,canvas:null,img:null,pdf:null,pageSize:null},progress:{val:0,state:null,n:0,stack:[]},opt:{filename:"file.pdf",margin:[0,0,0,0],image:{type:"jpeg",quality:.95},enableLinks:!0,html2canvas:{},jsPDF:{}}},A.prototype.from=function(e,t){return this.then((function(){switch(t=t||function(e){switch((0,s.objType)(e)){case"string":return"string";case"element":return"canvas"===e.nodeName.toLowerCase?"canvas":"element";default:return"unknown"}}(e)){case"string":return this.set({src:(0,s.createElement)("div",{innerHTML:e})});case"element":return this.set({src:e});case"canvas":return this.set({canvas:e});case"img":return this.set({img:e});default:return this.error("Unknown source type.")}}))},A.prototype.to=function(e){switch(e){case"container":return this.toContainer();case"canvas":return this.toCanvas();case"img":return this.toImg();case"pdf":return this.toPdf();default:return this.error("Invalid target.")}},A.prototype.toContainer=function(){return this.thenList([function(){return this.prop.src||this.error("Cannot duplicate - no source HTML.")},function(){return this.prop.pageSize||this.setPageSize()}]).then((function(){var e={position:"fixed",overflow:"hidden",zIndex:1e3,left:0,right:0,bottom:0,top:0,backgroundColor:"rgba(0,0,0,0.8)"},t={position:"absolute",width:this.prop.pageSize.inner.width+this.prop.pageSize.unit,left:0,right:0,top:0,height:"auto",margin:"auto",backgroundColor:"white"};e.opacity=0;var r=(0,s.cloneNode)(this.prop.src,this.opt.html2canvas.javascriptEnabled);this.prop.overlay=(0,s.createElement)("div",{className:"html2pdf__overlay",style:e}),this.prop.container=(0,s.createElement)("div",{className:"html2pdf__container",style:t}),this.prop.container.appendChild(r),this.prop.overlay.appendChild(this.prop.container),document.body.appendChild(this.prop.overlay)}))},A.prototype.toCanvas=function(){var e=[function(){return document.body.contains(this.prop.container)||this.toContainer()}];return this.thenList(e).then((function(){var e=Object.assign({},this.opt.html2canvas);return delete e.onrendered,o(this.prop.container,e)})).then((function(e){(this.opt.html2canvas.onrendered||function(){})(e),this.prop.canvas=e,document.body.removeChild(this.prop.overlay)}))},A.prototype.toImg=function(){return this.thenList([function(){return this.prop.canvas||this.toCanvas()}]).then((function(){var e=this.prop.canvas.toDataURL("image/"+this.opt.image.type,this.opt.image.quality);this.prop.img=document.createElement("img"),this.prop.img.src=e}))},A.prototype.toPdf=function(){return this.thenList([function(){return this.prop.canvas||this.toCanvas()}]).then((function(){var e=this.prop.canvas,t=this.opt,r=e.height,o=Math.floor(e.width*this.prop.pageSize.inner.ratio),s=Math.ceil(r/o),i=this.prop.pageSize.inner.height,a=document.createElement("canvas"),A=a.getContext("2d");a.width=e.width,a.height=o,this.prop.pdf=this.prop.pdf||new n.jsPDF(t.jsPDF);for(var u=0;u~\.\[:]+)/g,Je=/(\.[^\s\+>~\.\[:]+)/g,Ye=/(::[^\s\+>~\.\[:]+|:first-line|:first-letter|:before|:after)/gi,Ze=/(:[\w-]+\([^\)]*\))/gi,$e=/(:[^\s\+>~\.\[:]+)/g,et=/([^\s\+>~\.\[:]+)/g;function tt(e,t){var r=e.match(t);return r?[e.replace(t," "),r.length]:[e,0]}function rt(e){var t=[0,0,0],r=e.replace(/:not\(([^\)]*)\)/g," $1 ").replace(/{[\s\S]*/gm," "),n=0,o=tt(r,Xe),s=(0,u.default)(o,2);r=s[0],n=s[1],t[1]+=n;var i=tt(r,We),a=(0,u.default)(i,2);r=a[0],n=a[1],t[0]+=n;var A=tt(r,Je),c=(0,u.default)(A,2);r=c[0],n=c[1],t[1]+=n;var l=tt(r,Ye),d=(0,u.default)(l,2);r=d[0],n=d[1],t[2]+=n;var f=tt(r,Ze),h=(0,u.default)(f,2);r=h[0],n=h[1],t[1]+=n;var p=tt(r,$e),m=(0,u.default)(p,2);r=m[0],n=m[1],t[1]+=n;var g=tt(r=r.replace(/[\*\s\+>~]/g," ").replace(/[#\.]/g," "),et),y=(0,u.default)(g,2);return r=y[0],n=y[1],t[2]+=n,t.join("")}var nt=1e-8;function ot(e){return Math.sqrt(Math.pow(e[0],2)+Math.pow(e[1],2))}function st(e,t){return(e[0]*t[0]+e[1]*t[1])/(ot(e)*ot(t))}function it(e,t){return(e[0]*t[1]0&&void 0!==arguments[0]?arguments[0]:" ",o=this.document,s=this.name;return A()(t=G()(r=Re(this.getString())).call(r).split(n)).call(t,(function(t){return new e(o,s,t)}))}},{key:"hasValue",value:function(e){var t=this.value;return null!==t&&""!==t&&(e||0!==t)&&void 0!==t}},{key:"isString",value:function(e){var t=this.value,r="string"==typeof t;return r&&e?e.test(t):r}},{key:"isUrlDefinition",value:function(){return this.isString(/^url\(/)}},{key:"isPixels",value:function(){if(!this.hasValue())return!1;var e=this.getString();switch(!0){case/px$/.test(e):case/^[0-9]+$/.test(e):return!0;default:return!1}}},{key:"setValue",value:function(e){return this.value=e,this}},{key:"getValue",value:function(e){return void 0===e||this.hasValue()?this.value:e}},{key:"getNumber",value:function(e){if(!this.hasValue())return void 0===e?0:i()(e);var t=this.value,r=i()(t);return this.isString(/%$/)&&(r/=100),r}},{key:"getString",value:function(e){return void 0===e||this.hasValue()?void 0===this.value?"":String(this.value):String(e)}},{key:"getColor",value:function(e){var t=this.getString(e);return this.isNormalizedColor||(this.isNormalizedColor=!0,t=Ge(t),this.value=t),t}},{key:"getDpi",value:function(){return 96}},{key:"getRem",value:function(){return this.document.rootEmSize}},{key:"getEm",value:function(){return this.document.emSize}},{key:"getUnits",value:function(){return this.getString().replace(/[0-9\.\-]/g,"")}},{key:"getPixels",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];if(!this.hasValue())return 0;var r="boolean"==typeof e?[void 0,e]:[e],n=(0,u.default)(r,2),o=n[0],s=n[1],i=this.document.screen.viewPort;switch(!0){case this.isString(/vmin$/):return this.getNumber()/100*Math.min(i.computeSize("x"),i.computeSize("y"));case this.isString(/vmax$/):return this.getNumber()/100*Math.max(i.computeSize("x"),i.computeSize("y"));case this.isString(/vw$/):return this.getNumber()/100*i.computeSize("x");case this.isString(/vh$/):return this.getNumber()/100*i.computeSize("y");case this.isString(/rem$/):return this.getNumber()*this.getRem();case this.isString(/em$/):return this.getNumber()*this.getEm();case this.isString(/ex$/):return this.getNumber()*this.getEm()/2;case this.isString(/px$/):return this.getNumber();case this.isString(/pt$/):return this.getNumber()*this.getDpi()*(1/72);case this.isString(/pc$/):return 15*this.getNumber();case this.isString(/cm$/):return this.getNumber()*this.getDpi()/2.54;case this.isString(/mm$/):return this.getNumber()*this.getDpi()/25.4;case this.isString(/in$/):return this.getNumber()*this.getDpi();case this.isString(/%$/)&&s:return this.getNumber()*this.getEm();case this.isString(/%$/):return this.getNumber()*i.computeSize(o);default:var a=this.getNumber();return t&&a<1?a*i.computeSize(o):a}}},{key:"getMilliseconds",value:function(){return this.hasValue()?this.isString(/ms$/)?this.getNumber():1e3*this.getNumber():0}},{key:"getRadians",value:function(){if(!this.hasValue())return 0;switch(!0){case this.isString(/deg$/):return this.getNumber()*(Math.PI/180);case this.isString(/grad$/):return this.getNumber()*(Math.PI/200);case this.isString(/rad$/):return this.getNumber();default:return this.getNumber()*(Math.PI/180)}}},{key:"getDefinition",value:function(){var e=this.getString(),t=e.match(/#([^\)'"]+)/);return t&&(t=t[1]),t||(t=e),this.document.definitions[t]}},{key:"getFillStyleDefinition",value:function(e,t){var r=this.getDefinition();if(!r)return null;if("function"==typeof r.createGradient)return r.createGradient(this.document.ctx,e,t);if("function"==typeof r.createPattern){if(r.getHrefAttribute().hasValue()){var n=r.getAttribute("patternTransform");r=r.getHrefAttribute().getDefinition(),n.hasValue()&&r.getAttribute("patternTransform",!0).setValue(n.value)}return r.createPattern(this.document.ctx,e,t)}return null}},{key:"getTextBaseline",value:function(){return this.hasValue()?e.textBaselineMapping[this.getString()]:null}},{key:"addOpacity",value:function(t){for(var r=this.getColor(),n=r.length,o=0,s=0;s1&&void 0!==arguments[1]?arguments[1]:0,n=Ke(t),o=(0,u.default)(n,2),s=o[0],i=void 0===s?r:s,a=o[1],A=void 0===a?r:a;return new e(i,A)}},{key:"parseScale",value:function(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1,n=Ke(t),o=(0,u.default)(n,2),s=o[0],i=void 0===s?r:s,a=o[1],A=void 0===a?i:a;return new e(i,A)}},{key:"parsePath",value:function(t){for(var r=Ke(t),n=r.length,o=[],s=0;s0}},{key:"runEvents",value:function(){if(this.working){var e=this.screen,t=this.events,r=this.eventElements,n=e.ctx.canvas.style;n&&(n.cursor=""),g()(t).call(t,(function(e,t){for(var n=e.run,o=r[t];o;)n(o),o=o.parent})),this.events=[],this.eventElements=[]}}},{key:"checkPath",value:function(e,t){if(this.working&&t){var r=this.events,n=this.eventElements;g()(r).call(r,(function(r,o){var s=r.x,i=r.y;!n[o]&&t.isPointInPath&&t.isPointInPath(s,i)&&(n[o]=e)}))}}},{key:"checkBoundingBox",value:function(e,t){if(this.working&&t){var r=this.events,n=this.eventElements;g()(r).call(r,(function(r,o){var s=r.x,i=r.y;!n[o]&&t.isPointInBox(s,i)&&(n[o]=e)}))}}},{key:"mapXY",value:function(e,t){for(var r=this.screen,n=r.window,o=r.ctx,s=new mt(e,t),i=o.canvas;i;)s.x-=i.offsetLeft,s.y-=i.offsetTop,i=i.offsetParent;return n.scrollX&&(s.x+=n.scrollX),n.scrollY&&(s.y+=n.scrollY),s}},{key:"onClick",value:function(e){var t=this.mapXY((e||event).clientX,(e||event).clientY),r=t.x,n=t.y;this.events.push({type:"onclick",x:r,y:n,run:function(e){e.onClick&&e.onClick()}})}},{key:"onMouseMove",value:function(e){var t=this.mapXY((e||event).clientX,(e||event).clientY),r=t.x,n=t.y;this.events.push({type:"onmousemove",x:r,y:n,run:function(e){e.onMouseMove&&e.onMouseMove()}})}}]),e}(),yt="undefined"!=typeof window?window:null,vt="undefined"!=typeof fetch?K()(fetch).call(fetch,void 0):null,wt=function(){function e(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=r.fetch,o=void 0===n?vt:n,s=r.window,i=void 0===s?yt:s;(0,F.default)(this,e),this.ctx=t,this.FRAMERATE=30,this.MAX_VIRTUAL_PIXELS=3e4,this.CLIENT_WIDTH=800,this.CLIENT_HEIGHT=600,this.viewPort=new pt,this.mouse=new gt(this),this.animations=[],this.waits=[],this.frameDuration=0,this.isReadyLock=!1,this.isFirstRender=!0,this.intervalId=null,this.window=i,this.fetch=o}return(0,U.default)(e,[{key:"wait",value:function(e){this.waits.push(e)}},{key:"ready",value:function(){return this.readyPromise?this.readyPromise:M().resolve()}},{key:"isReady",value:function(){var e;if(this.isReadyLock)return!0;var t=k()(e=this.waits).call(e,(function(e){return e()}));return t&&(this.waits=[],this.resolveReady&&this.resolveReady()),this.isReadyLock=t,t}},{key:"setDefaults",value:function(e){e.strokeStyle="rgba(0,0,0,0)",e.lineCap="butt",e.lineJoin="miter",e.miterLimit=4}},{key:"setViewBox",value:function(e){var t=e.document,r=e.ctx,n=e.aspectRatio,o=e.width,s=e.desiredWidth,i=e.height,a=e.desiredHeight,A=e.minX,c=void 0===A?0:A,l=e.minY,d=void 0===l?0:l,f=e.refX,h=e.refY,p=e.clip,m=void 0!==p&&p,g=e.clipX,y=void 0===g?0:g,v=e.clipY,w=void 0===v?0:v,b=Re(n).replace(/^defer\s/,"").split(" "),B=(0,u.default)(b,2),j=B[0]||"xMidYMid",_=B[1]||"meet",C=o/s,x=i/a,E=Math.min(C,x),N=Math.max(C,x),Q=s,F=a;"meet"===_&&(Q*=E,F*=E),"slice"===_&&(Q*=N,F*=N);var U=new ht(t,"refX",f),S=new ht(t,"refY",h),L=U.hasValue()&&S.hasValue();if(L&&r.translate(-E*U.getPixels("x"),-E*S.getPixels("y")),m){var T=E*y,H=E*w;r.beginPath(),r.moveTo(T,H),r.lineTo(o,H),r.lineTo(o,i),r.lineTo(T,i),r.closePath(),r.clip()}if(!L){var I="meet"===_&&E===x,P="slice"===_&&N===x,O="meet"===_&&E===C,k="slice"===_&&N===C;/^xMid/.test(j)&&(I||P)&&r.translate(o/2-Q/2,0),/YMid$/.test(j)&&(O||k)&&r.translate(0,i/2-F/2),/^xMax/.test(j)&&(I||P)&&r.translate(o-Q,0),/YMax$/.test(j)&&(O||k)&&r.translate(0,i-F)}switch(!0){case"none"===j:r.scale(C,x);break;case"meet"===_:r.scale(E,E);break;case"slice"===_:r.scale(N,N)}r.translate(-c,-d)}},{key:"start",value:function(e){var t=this,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=r.enableRedraw,o=void 0!==n&&n,s=r.ignoreMouse,i=void 0!==s&&s,a=r.ignoreAnimation,A=void 0!==a&&a,u=r.ignoreDimensions,c=void 0!==u&&u,l=r.ignoreClear,d=void 0!==l&&l,f=r.forceRedraw,h=r.scaleWidth,p=r.scaleHeight,m=r.offsetX,g=r.offsetY,y=this.FRAMERATE,v=this.mouse,w=1e3/y;if(this.frameDuration=w,this.readyPromise=new(M())((function(e){t.resolveReady=e})),this.isReady()&&this.render(e,c,d,h,p,m,g),o){var b=P()(),B=b,j=0,_=function r(){b=P()(),(j=b-B)>=w&&(B=b-j%w,t.shouldUpdate(A,f)&&(t.render(e,c,d,h,p,m,g),v.runEvents())),t.intervalId=V()(r)};i||v.start(),this.intervalId=V()(_)}}},{key:"stop",value:function(){this.intervalId&&(V().cancel(this.intervalId),this.intervalId=null),this.mouse.stop()}},{key:"shouldUpdate",value:function(e,t){if(!e){var r,n=this.frameDuration;if(H()(r=this.animations).call(r,(function(e,t){return t.update(n)||e}),!1))return!0}return!("function"!=typeof t||!t())||!(this.isReadyLock||!this.isReady())||!!this.mouse.hasEvents()}},{key:"render",value:function(e,t,r,n,o,s,i){var a=this.CLIENT_WIDTH,A=this.CLIENT_HEIGHT,u=this.viewPort,c=this.ctx,l=this.isFirstRender,d=c.canvas;u.clear(),d.width&&d.height?u.setCurrent(d.width,d.height):u.setCurrent(a,A);var f=e.getStyle("width"),h=e.getStyle("height");!t&&(l||"number"!=typeof n&&"number"!=typeof o)&&(f.hasValue()&&(d.width=f.getPixels("x"),d.style&&(d.style.width="".concat(d.width,"px"))),h.hasValue()&&(d.height=h.getPixels("y"),d.style&&(d.style.height="".concat(d.height,"px"))));var p=d.clientWidth||d.width,m=d.clientHeight||d.height;if(t&&f.hasValue()&&h.hasValue()&&(p=f.getPixels("x"),m=h.getPixels("y")),u.setCurrent(p,m),"number"==typeof s&&e.getAttribute("x",!0).setValue(s),"number"==typeof i&&e.getAttribute("y",!0).setValue(i),"number"==typeof n||"number"==typeof o){var g,y,v=Ke(e.getAttribute("viewBox").getString()),w=0,b=0;if("number"==typeof n){var B=e.getStyle("width");B.hasValue()?w=B.getPixels("x")/n:isNaN(v[2])||(w=v[2]/n)}if("number"==typeof o){var j=e.getStyle("height");j.hasValue()?b=j.getPixels("y")/o:isNaN(v[3])||(b=v[3]/o)}w||(w=b),b||(b=w),e.getAttribute("width",!0).setValue(n),e.getAttribute("height",!0).setValue(o);var _=e.getStyle("transform",!0,!0);_.setValue(L()(g=L()(y="".concat(_.getString()," scale(")).call(y,1/w,", ")).call(g,1/b,")"))}r||c.clearRect(0,0,p,m),e.render(c),l&&(this.isFirstRender=!1)}}]),e}();wt.defaultWindow=yt,wt.defaultFetch=vt;var bt=wt.defaultFetch,Bt="undefined"!=typeof DOMParser?DOMParser:null,jt=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=t.fetch,n=void 0===r?bt:r,o=t.DOMParser,s=void 0===o?Bt:o;(0,F.default)(this,e),this.fetch=n,this.DOMParser=s}var t,r;return(0,U.default)(e,[{key:"parse",value:(r=(0,N.default)(E().mark((function e(t){return E().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!/^=0;r--)t[r].unapply(e)}},{key:"applyToPoint",value:function(e){for(var t=this.transforms,r=t.length,n=0;n2&&void 0!==arguments[2]&&arguments[2];if((0,F.default)(this,e),this.document=t,this.node=r,this.captureTextNodes=i,this.attributes={},this.styles={},this.stylesSpecificity={},this.animationFrozen=!1,this.animationFrozenValue="",this.parent=null,this.children=[],r&&1===r.nodeType){if(g()(n=ae()(r.attributes)).call(n,(function(e){var r=Ve(e.nodeName);s.attributes[r]=new ht(t,r,e.value)})),this.addStylesFromStyleDefinition(),this.getAttribute("style").hasValue()){var a,c=A()(a=this.getAttribute("style").getString().split(";")).call(a,(function(e){return G()(e).call(e)}));g()(c).call(c,(function(e){var r;if(e){var n=A()(r=e.split(":")).call(r,(function(e){return G()(e).call(e)})),o=(0,u.default)(n,2),i=o[0],a=o[1];s.styles[i]=new ht(t,i,a)}}))}var l=t.definitions,d=this.getAttribute("id");d.hasValue()&&(l[d.getValue()]||(l[d.getValue()]=this)),g()(o=ae()(r.childNodes)).call(o,(function(e){if(1===e.nodeType)s.addChild(e);else if(i&&(3===e.nodeType||4===e.nodeType)){var r=t.createTextNode(e);r.getText().length>0&&s.addChild(r)}}))}}return(0,U.default)(e,[{key:"getAttribute",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],r=this.attributes[e];if(!r&&t){var n=new ht(this.document,e,"");return this.attributes[e]=n,n}return r||ht.empty(this.document)}},{key:"getHrefAttribute",value:function(){for(var e in this.attributes)if("href"===e||/:href$/.test(e))return this.attributes[e];return ht.empty(this.document)}},{key:"getStyle",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],r=arguments.length>2&&void 0!==arguments[2]&&arguments[2],n=this.styles[e];if(n)return n;var o=this.getAttribute(e);if(o&&o.hasValue())return this.styles[e]=o,o;if(!r){var s=this.parent;if(s){var i=s.getStyle(e);if(i&&i.hasValue())return i}}if(t){var a=new ht(this.document,e,"");return this.styles[e]=a,a}return n||ht.empty(this.document)}},{key:"render",value:function(e){if("none"!==this.getStyle("display").getString()&&"hidden"!==this.getStyle("visibility").getString()){if(e.save(),this.getStyle("mask").hasValue()){var t=this.getStyle("mask").getDefinition();t&&(this.applyEffects(e),t.apply(e,this))}else if("none"!==this.getStyle("filter").getValue("none")){var r=this.getStyle("filter").getDefinition();r&&(this.applyEffects(e),r.apply(e,this))}else this.setContext(e),this.renderChildren(e),this.clearContext(e);e.restore()}}},{key:"setContext",value:function(e){}},{key:"applyEffects",value:function(e){var t=Ut.fromElement(this.document,this);t&&t.apply(e);var r=this.getStyle("clip-path",!1,!0);if(r.hasValue()){var n=r.getDefinition();n&&n.apply(e)}}},{key:"clearContext",value:function(e){}},{key:"renderChildren",value:function(e){var t;g()(t=this.children).call(t,(function(t){t.render(e)}))}},{key:"addChild",value:function(t){var r,n=t instanceof e?t:this.document.createElement(t);n.parent=this,se()(r=e.ignoreChildTypes).call(r,n.type)||this.children.push(n)}},{key:"matchesSelector",value:function(e){var t,r=this.node;if("function"==typeof r.matches)return r.matches(e);var n=r.getAttribute("class");return!(!n||""===n)&&ne()(t=n.split(" ")).call(t,(function(t){if(".".concat(t)===e)return!0}))}},{key:"addStylesFromStyleDefinition",value:function(){var e=this.document,t=e.styles,r=e.stylesSpecificity;for(var n in t)if("@"!==n[0]&&this.matchesSelector(n)){var o=t[n],s=r[n];if(o)for(var i in o){var a=this.stylesSpecificity[i];void 0===a&&(a="000"),s>=a&&(this.styles[i]=o[i],this.stylesSpecificity[i]=s)}}}},{key:"removeStyles",value:function(e,t){return H()(t).call(t,(function(t,r){var n,o=e.getStyle(r);if(!o.hasValue())return t;var s=o.getString();return o.setValue(""),L()(n=[]).call(n,(0,te.default)(t),[[r,s]])}),[])}},{key:"restoreStyles",value:function(e,t){g()(t).call(t,(function(t){var r=(0,u.default)(t,2),n=r[0],o=r[1];e.getStyle(n,!0).setValue(o)}))}}]),e}();St.ignoreChildTypes=["title"];var Lt=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(e,t,r){return(0,F.default)(this,o),n.call(this,e,t,r)}return o}(St);function Tt(e){var t=G()(e).call(e);return/^('|")/.test(t)?t:'"'.concat(t,'"')}function Ht(e){if(!e)return"";var t=G()(e).call(e).toLowerCase();switch(t){case"normal":case"italic":case"oblique":case"inherit":case"initial":case"unset":return t;default:return/^oblique\s+(-|)\d+deg$/.test(t)?t:""}}function It(e){if(!e)return"";var t=G()(e).call(e).toLowerCase();switch(t){case"normal":case"bold":case"lighter":case"bolder":case"inherit":case"initial":case"unset":return t;default:return/^[\d.]+$/.test(t)?t:""}}var Pt=function(){function e(t,r,n,o,s,i){(0,F.default)(this,e);var a=i?"string"==typeof i?e.parse(i):i:{};this.fontFamily=s||a.fontFamily,this.fontSize=o||a.fontSize,this.fontStyle=t||a.fontStyle,this.fontWeight=n||a.fontWeight,this.fontVariant=r||a.fontVariant}return(0,U.default)(e,[{key:"toString",value:function(){var e,t,r;return G()(e=[Ht(this.fontStyle),this.fontVariant,It(this.fontWeight),this.fontSize,(t=this.fontFamily,"undefined"==typeof process?t:A()(r=G()(t).call(t).split(",")).call(r,Tt).join(","))].join(" ")).call(e)}}],[{key:"parse",value:function(){var t,r=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",n=arguments.length>1?arguments[1]:void 0,o="",s="",i="",a="",A="",c=G()(t=Re(r)).call(t).split(" "),l={fontSize:!1,fontStyle:!1,fontWeight:!1,fontVariant:!1};return g()(c).call(c,(function(t){var r,n,c;switch(!0){case!l.fontStyle&&se()(r=e.styles).call(r,t):"inherit"!==t&&(o=t),l.fontStyle=!0;break;case!l.fontVariant&&se()(n=e.variants).call(n,t):"inherit"!==t&&(s=t),l.fontStyle=!0,l.fontVariant=!0;break;case!l.fontWeight&&se()(c=e.weights).call(c,t):"inherit"!==t&&(i=t),l.fontStyle=!0,l.fontVariant=!0,l.fontWeight=!0;break;case!l.fontSize:if("inherit"!==t){var d=t.split("/"),f=(0,u.default)(d,1);a=f[0]}l.fontStyle=!0,l.fontVariant=!0,l.fontWeight=!0,l.fontSize=!0;break;default:"inherit"!==t&&(A+=t)}})),new e(o,s,i,a,A,n)}}]),e}();Pt.styles="normal|italic|oblique|inherit",Pt.variants="normal|small-caps|inherit",Pt.weights="normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900|inherit";var Ot=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:Number.NaN,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:Number.NaN,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:Number.NaN,o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:Number.NaN;(0,F.default)(this,e),this.x1=t,this.y1=r,this.x2=n,this.y2=o,this.addPoint(t,r),this.addPoint(n,o)}return(0,U.default)(e,[{key:"addPoint",value:function(e,t){void 0!==e&&((isNaN(this.x1)||isNaN(this.x2))&&(this.x1=e,this.x2=e),ethis.x2&&(this.x2=e)),void 0!==t&&((isNaN(this.y1)||isNaN(this.y2))&&(this.y1=t,this.y2=t),tthis.y2&&(this.y2=t))}},{key:"addX",value:function(e){this.addPoint(e,null)}},{key:"addY",value:function(e){this.addPoint(null,e)}},{key:"addBoundingBox",value:function(e){if(e){var t=e.x1,r=e.y1,n=e.x2,o=e.y2;this.addPoint(t,r),this.addPoint(n,o)}}},{key:"sumCubic",value:function(e,t,r,n,o){return Math.pow(1-e,3)*t+3*Math.pow(1-e,2)*e*r+3*(1-e)*Math.pow(e,2)*n+Math.pow(e,3)*o}},{key:"bezierCurveAdd",value:function(e,t,r,n,o){var s=6*t-12*r+6*n,i=-3*t+9*r-9*n+3*o,a=3*r-3*t;if(0!==i){var A=Math.pow(s,2)-4*a*i;if(!(A<0)){var u=(-s+Math.sqrt(A))/(2*i);01&&void 0!==arguments[1]&&arguments[1];if(!t){var r=this.getStyle("fill"),n=this.getStyle("fill-opacity"),o=this.getStyle("stroke"),s=this.getStyle("stroke-opacity");if(r.isUrlDefinition()){var i=r.getFillStyleDefinition(this,n);i&&(e.fillStyle=i)}else if(r.hasValue()){"currentColor"===r.getString()&&r.setValue(this.getStyle("color").getColor());var a=r.getColor();"inherit"!==a&&(e.fillStyle="none"===a?"rgba(0,0,0,0)":a)}if(n.hasValue()){var A=new ht(this.document,"fill",e.fillStyle).addOpacity(n).getColor();e.fillStyle=A}if(o.isUrlDefinition()){var u=o.getFillStyleDefinition(this,s);u&&(e.strokeStyle=u)}else if(o.hasValue()){"currentColor"===o.getString()&&o.setValue(this.getStyle("color").getColor());var c=o.getString();"inherit"!==c&&(e.strokeStyle="none"===c?"rgba(0,0,0,0)":c)}if(s.hasValue()){var l=new ht(this.document,"stroke",e.strokeStyle).addOpacity(s).getString();e.strokeStyle=l}var d=this.getStyle("stroke-width");if(d.hasValue()){var f=d.getPixels();e.lineWidth=f||nt}var h=this.getStyle("stroke-linecap"),p=this.getStyle("stroke-linejoin"),m=this.getStyle("stroke-miterlimit"),g=this.getStyle("paint-order"),y=this.getStyle("stroke-dasharray"),v=this.getStyle("stroke-dashoffset");if(h.hasValue()&&(e.lineCap=h.getString()),p.hasValue()&&(e.lineJoin=p.getString()),m.hasValue()&&(e.miterLimit=m.getNumber()),g.hasValue()&&(e.paintOrder=g.getValue()),y.hasValue()&&"none"!==y.getString()){var w=Ke(y.getString());void 0!==e.setLineDash?e.setLineDash(w):void 0!==e.webkitLineDash?e.webkitLineDash=w:void 0===e.mozDash||1===w.length&&0===w[0]||(e.mozDash=w);var b=v.getPixels();void 0!==e.lineDashOffset?e.lineDashOffset=b:void 0!==e.webkitLineDashOffset?e.webkitLineDashOffset=b:void 0!==e.mozDashOffset&&(e.mozDashOffset=b)}}if(this.modifiedEmSizeStack=!1,void 0!==e.font){var B=this.getStyle("font"),j=this.getStyle("font-style"),_=this.getStyle("font-variant"),C=this.getStyle("font-weight"),x=this.getStyle("font-size"),E=this.getStyle("font-family"),N=new Pt(j.getString(),_.getString(),C.getString(),x.hasValue()?"".concat(x.getPixels(!0),"px"):"",E.getString(),Pt.parse(B.getString(),e.font));j.setValue(N.fontStyle),_.setValue(N.fontVariant),C.setValue(N.fontWeight),x.setValue(N.fontSize),E.setValue(N.fontFamily),e.font=N.toString(),x.isPixels()&&(this.document.emSize=x.getPixels(),this.modifiedEmSizeStack=!0)}t||(this.applyEffects(e),e.globalAlpha=this.calculateOpacity())}},{key:"clearContext",value:function(e){(0,de.default)((0,ee.default)(o.prototype),"clearContext",this).call(this,e),this.modifiedEmSizeStack&&this.document.popEmSize()}}]),o}(St);var Rt=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(e,t,r){var s;return(0,F.default)(this,o),(s=n.call(this,e,t,(this instanceof o?this.constructor:void 0)===o||r)).type="text",s.x=0,s.y=0,s.measureCache=-1,s}return(0,U.default)(o,[{key:"setContext",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];(0,de.default)((0,ee.default)(o.prototype),"setContext",this).call(this,e,t);var r=this.getStyle("dominant-baseline").getTextBaseline()||this.getStyle("alignment-baseline").getTextBaseline();r&&(e.textBaseline=r)}},{key:"initializeCoordinates",value:function(e){this.x=this.getAttribute("x").getPixels("x"),this.y=this.getAttribute("y").getPixels("y");var t=this.getAttribute("dx"),r=this.getAttribute("dy");t.hasValue()&&(this.x+=t.getPixels("x")),r.hasValue()&&(this.y+=r.getPixels("y")),this.x+=this.getAnchorDelta(e,this,0)}},{key:"getBoundingBox",value:function(e){var t,r=this;if("text"!==this.type)return this.getTElementBoundingBox(e);this.initializeCoordinates(e);var n=null;return g()(t=this.children).call(t,(function(t,o){var s=r.getChildBoundingBox(e,r,r,o);n?n.addBoundingBox(s):n=s})),n}},{key:"getFontSize",value:function(){var e=this.document,t=this.parent,r=Pt.parse(e.ctx.font).fontSize;return t.getStyle("font-size").getNumber(r)}},{key:"getTElementBoundingBox",value:function(e){var t=this.getFontSize();return new Ot(this.x,this.y-t,this.x+this.measureText(e),this.y)}},{key:"getGlyph",value:function(e,t,r){var n=t[r],o=null;if(e.isArabic){var s=t.length,i=t[r-1],a=t[r+1],A="isolated";(0===r||" "===i)&&r0&&" "!==i&&r0&&" "!==i&&(r===s-1||" "===a)&&(A="initial"),void 0!==e.glyphs[n]&&((o=e.glyphs[n][A])||"glyph"!==e.glyphs[n].type||(o=e.glyphs[n]))}else o=e.glyphs[n];return o||(o=e.missingGlyph),o}},{key:"getText",value:function(){return""}},{key:"getTextFromNode",value:function(e){var t=e||this.node,r=ae()(t.parentNode.childNodes),n=le()(r).call(r,t),o=r.length-1,s=Re(t.value||t.text||t.textContent||"");return 0===n&&(s=Me(s)),n===o&&(s=De(s)),s}},{key:"renderChildren",value:function(e){var t,r=this;if("text"===this.type){this.initializeCoordinates(e),g()(t=this.children).call(t,(function(t,n){r.renderChild(e,r,r,n)}));var n=this.document.screen.mouse;n.isWorking()&&n.checkBoundingBox(this,this.getBoundingBox(e))}else this.renderTElementChildren(e)}},{key:"renderTElementChildren",value:function(e){var t=this.document,r=this.parent,n=this.getText(),o=r.getStyle("font-family").getDefinition();if(o)for(var s,i=o.fontFace.unitsPerEm,a=Pt.parse(t.ctx.font),A=r.getStyle("font-size").getNumber(a.fontSize),u=r.getStyle("font-style").getString(a.fontStyle),c=A/i,l=o.isRTL?ue()(s=n.split("")).call(s).join(""):n,d=Ke(r.getAttribute("dx").getString()),f=l.length,h=0;hr&&i.getAttribute("x").hasValue()||i.getAttribute("text-anchor").hasValue()));A++)a+=i.measureTextRecursive(e);return-1*("end"===n?a:a/2)}return 0}},{key:"adjustChildCoordinates",value:function(e,t,r,n){var o=r.children[n];if("function"!=typeof o.measureText)return o;e.save(),o.setContext(e,!0);var s=o.getAttribute("x"),i=o.getAttribute("y"),a=o.getAttribute("dx"),A=o.getAttribute("dy"),u=o.getAttribute("text-anchor").getString("start");if(0===n&&"textNode"!==o.type&&(s.hasValue()||s.setValue(t.getAttribute("x").getValue("0")),i.hasValue()||i.setValue(t.getAttribute("y").getValue("0")),a.hasValue()||a.setValue(t.getAttribute("dx").getValue("0")),A.hasValue()||A.setValue(t.getAttribute("dy").getValue("0"))),s.hasValue()){if(o.x=s.getPixels("x")+t.getAnchorDelta(e,r,n),"start"!==u){var c=o.measureTextRecursive(e);o.x+=-1*("end"===u?c:c/2)}a.hasValue()&&(o.x+=a.getPixels("x"))}else{if("start"!==u){var l=o.measureTextRecursive(e);t.x+=-1*("end"===u?l:l/2)}a.hasValue()&&(t.x+=a.getPixels("x")),o.x=t.x}return t.x=o.x+o.measureText(e),i.hasValue()?(o.y=i.getPixels("y"),A.hasValue()&&(o.y+=A.getPixels("y"))):(A.hasValue()&&(t.y+=A.getPixels("y")),o.y=t.y),t.y=o.y,o.clearContext(e),e.restore(),o}},{key:"getChildBoundingBox",value:function(e,t,r,n){var o,s=this.adjustChildCoordinates(e,t,r,n);if("function"!=typeof s.getBoundingBox)return null;var i=s.getBoundingBox(e);return i?(g()(o=s.children).call(o,(function(r,n){var o=t.getChildBoundingBox(e,t,s,n);i.addBoundingBox(o)})),i):null}},{key:"renderChild",value:function(e,t,r,n){var o,s=this.adjustChildCoordinates(e,t,r,n);s.render(e),g()(o=s.children).call(o,(function(r,n){t.renderChild(e,t,s,n)}))}},{key:"measureTextRecursive",value:function(e){var t;return H()(t=this.children).call(t,(function(t,r){return t+r.measureTextRecursive(e)}),this.measureText(e))}},{key:"measureText",value:function(e){var t=this.measureCache;if(~t)return t;var r=this.getText(),n=this.measureTargetText(e,r);return this.measureCache=n,n}},{key:"measureTargetText",value:function(e,t){if(!t.length)return 0;var r=this.parent,n=r.getStyle("font-family").getDefinition();if(n){for(var o,s=this.getFontSize(),i=n.isRTL?ue()(o=t.split("")).call(o).join(""):t,a=Ke(r.getAttribute("dx").getString()),A=i.length,u=0,c=0;c0?"":s.getTextFromNode(),s}return(0,U.default)(o,[{key:"getText",value:function(){return this.text}}]),o}(Rt);var Dt=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(){var e;return(0,F.default)(this,o),(e=n.apply(this,arguments)).type="textNode",e}return o}(Mt);var Kt=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(e){var t;return(0,F.default)(this,o),(t=n.call(this,e.replace(/[+-.]\s+/g,"-").replace(/[^MmZzLlHhVvCcSsQqTtAae\d\s.,+-].*/g,""))).control=null,t.start=null,t.current=null,t.command=null,t.commands=t.commands,t.i=-1,t.previousCommand=null,t.points=[],t.angles=[],t}return(0,U.default)(o,[{key:"reset",value:function(){this.i=-1,this.command=null,this.previousCommand=null,this.start=new mt(0,0),this.control=new mt(0,0),this.current=new mt(0,0),this.points=[],this.angles=[]}},{key:"isEnd",value:function(){return this.i>=this.commands.length-1}},{key:"next",value:function(){var e=this.commands[++this.i];return this.previousCommand=this.command,this.command=e,e}},{key:"getPoint",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"x",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"y",r=new mt(this.command[e],this.command[t]);return this.makeAbsolute(r)}},{key:"getAsControlPoint",value:function(e,t){var r=this.getPoint(e,t);return this.control=r,r}},{key:"getAsCurrentPoint",value:function(e,t){var r=this.getPoint(e,t);return this.current=r,r}},{key:"getReflectedControlPoint",value:function(){var e=this.previousCommand.type;if(e!==pe.SVGPathData.CURVE_TO&&e!==pe.SVGPathData.SMOOTH_CURVE_TO&&e!==pe.SVGPathData.QUAD_TO&&e!==pe.SVGPathData.SMOOTH_QUAD_TO)return this.current;var t=this.current,r=t.x,n=t.y,o=this.control,s=o.x,i=o.y;return new mt(2*r-s,2*n-i)}},{key:"makeAbsolute",value:function(e){if(this.command.relative){var t=this.current,r=t.x,n=t.y;e.x+=r,e.y+=n}return e}},{key:"addMarker",value:function(e,t,r){var n=this.points,o=this.angles;r&&o.length>0&&!o[o.length-1]&&(o[o.length-1]=n[n.length-1].angleTo(r)),this.addMarkerAngle(e,t?t.angleTo(e):null)}},{key:"addMarkerAngle",value:function(e,t){this.points.push(e),this.angles.push(t)}},{key:"getMarkerPoints",value:function(){return this.points}},{key:"getMarkerAngles",value:function(){for(var e=this.angles,t=e.length,r=0;ra?i:a,g=i>a?1:i/a,y=i>a?a/i:1;e.translate(c.x,c.y),e.rotate(u),e.scale(g,y),e.arc(0,0,m,l,l+d,Boolean(1-A)),e.scale(1/g,1/y),e.rotate(-u),e.translate(-c.x,-c.y)}}},{key:"pathZ",value:function(e,t){o.pathZ(this.pathParser),e&&t.x1!==t.x2&&t.y1!==t.y2&&e.closePath()}}],[{key:"pathM",value:function(e){var t=e.getAsCurrentPoint();return e.start=e.current,{point:t}}},{key:"pathL",value:function(e){return{current:e.current,point:e.getAsCurrentPoint()}}},{key:"pathH",value:function(e){var t=e.current,r=e.command,n=new mt((r.relative?t.x:0)+r.x,t.y);return e.current=n,{current:t,point:n}}},{key:"pathV",value:function(e){var t=e.current,r=e.command,n=new mt(t.x,(r.relative?t.y:0)+r.y);return e.current=n,{current:t,point:n}}},{key:"pathC",value:function(e){return{current:e.current,point:e.getPoint("x1","y1"),controlPoint:e.getAsControlPoint("x2","y2"),currentPoint:e.getAsCurrentPoint()}}},{key:"pathS",value:function(e){return{current:e.current,point:e.getReflectedControlPoint(),controlPoint:e.getAsControlPoint("x2","y2"),currentPoint:e.getAsCurrentPoint()}}},{key:"pathQ",value:function(e){return{current:e.current,controlPoint:e.getAsControlPoint("x1","y1"),currentPoint:e.getAsCurrentPoint()}}},{key:"pathT",value:function(e){var t=e.current,r=e.getReflectedControlPoint();return e.control=r,{current:t,controlPoint:r,currentPoint:e.getAsCurrentPoint()}}},{key:"pathA",value:function(e){var t=e.current,r=e.command,n=r.rX,o=r.rY,s=r.xRot,i=r.lArcFlag,a=r.sweepFlag,A=s*(Math.PI/180),u=e.getAsCurrentPoint(),c=new mt(Math.cos(A)*(t.x-u.x)/2+Math.sin(A)*(t.y-u.y)/2,-Math.sin(A)*(t.x-u.x)/2+Math.cos(A)*(t.y-u.y)/2),l=Math.pow(c.x,2)/Math.pow(n,2)+Math.pow(c.y,2)/Math.pow(o,2);l>1&&(n*=Math.sqrt(l),o*=Math.sqrt(l));var d=(i===a?-1:1)*Math.sqrt((Math.pow(n,2)*Math.pow(o,2)-Math.pow(n,2)*Math.pow(c.y,2)-Math.pow(o,2)*Math.pow(c.x,2))/(Math.pow(n,2)*Math.pow(c.y,2)+Math.pow(o,2)*Math.pow(c.x,2)));isNaN(d)&&(d=0);var f=new mt(d*n*c.y/o,d*-o*c.x/n),h=new mt((t.x+u.x)/2+Math.cos(A)*f.x-Math.sin(A)*f.y,(t.y+u.y)/2+Math.sin(A)*f.x+Math.cos(A)*f.y),p=it([1,0],[(c.x-f.x)/n,(c.y-f.y)/o]),m=[(c.x-f.x)/n,(c.y-f.y)/o],g=[(-c.x-f.x)/n,(-c.y-f.y)/o],y=it(m,g);return st(m,g)<=-1&&(y=Math.PI),st(m,g)>=1&&(y=0),{currentPoint:u,rX:n,rY:o,sweepFlag:a,xAxisRotation:A,centp:h,a1:p,ad:y}}},{key:"pathZ",value:function(e){e.current=e.start}}]),o}(kt);var Vt=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(){var e;return(0,F.default)(this,o),(e=n.apply(this,arguments)).type="svg",e.root=!1,e}return(0,U.default)(o,[{key:"setContext",value:function(e){var t=this.document,r=t.screen,n=t.window,s=e.canvas;if(r.setDefaults(e),s.style&&void 0!==e.font&&n&&void 0!==n.getComputedStyle){e.font=n.getComputedStyle(s).getPropertyValue("font");var i=new ht(t,"fontSize",Pt.parse(e.font).fontSize);i.hasValue()&&(t.rootEmSize=i.getPixels("y"),t.emSize=t.rootEmSize)}this.getAttribute("x").hasValue()||this.getAttribute("x",!0).setValue(0),this.getAttribute("y").hasValue()||this.getAttribute("y",!0).setValue(0);var a=r.viewPort,A=a.width,u=a.height;this.getStyle("width").hasValue()||this.getStyle("width",!0).setValue("100%"),this.getStyle("height").hasValue()||this.getStyle("height",!0).setValue("100%"),this.getStyle("color").hasValue()||this.getStyle("color",!0).setValue("black");var c=this.getAttribute("refX"),l=this.getAttribute("refY"),d=this.getAttribute("viewBox"),f=d.hasValue()?Ke(d.getString()):null,h=!this.root&&"visible"!==this.getStyle("overflow").getValue("hidden"),p=0,m=0,g=0,y=0;f&&(p=f[0],m=f[1]),this.root||(A=this.getStyle("width").getPixels("x"),u=this.getStyle("height").getPixels("y"),"marker"===this.type&&(g=p,y=m,p=0,m=0)),r.viewPort.setCurrent(A,u),this.node&&this.getStyle("transform",!1,!0).hasValue()&&!this.getStyle("transform-origin",!1,!0).hasValue()&&this.getStyle("transform-origin",!0,!0).setValue("50% 50%"),(0,de.default)((0,ee.default)(o.prototype),"setContext",this).call(this,e),e.translate(this.getAttribute("x").getPixels("x"),this.getAttribute("y").getPixels("y")),f&&(A=f[2],u=f[3]),t.setViewBox({ctx:e,aspectRatio:this.getAttribute("preserveAspectRatio").getString(),width:r.viewPort.width,desiredWidth:A,height:r.viewPort.height,desiredHeight:u,minX:p,minY:m,refX:c.getValue(),refY:l.getValue(),clip:h,clipX:g,clipY:y}),f&&(r.viewPort.removeCurrent(),r.viewPort.setCurrent(A,u))}},{key:"clearContext",value:function(e){(0,de.default)((0,ee.default)(o.prototype),"clearContext",this).call(this,e),this.document.screen.viewPort.removeCurrent()}},{key:"resize",value:function(e){var t,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:e,n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],o=this.getAttribute("width",!0),s=this.getAttribute("height",!0),i=this.getAttribute("viewBox"),a=this.getAttribute("style"),A=o.getNumber(0),u=s.getNumber(0);if(n)if("string"==typeof n)this.getAttribute("preserveAspectRatio",!0).setValue(n);else{var c=this.getAttribute("preserveAspectRatio");c.hasValue()&&c.setValue(c.getString().replace(/^\s*(\S.*\S)\s*$/,"$1"))}if(o.setValue(e),s.setValue(r),i.hasValue()||i.setValue(L()(t="0 0 ".concat(A||e," ")).call(t,u||r)),a.hasValue()){var l=this.getStyle("width"),d=this.getStyle("height");l.hasValue()&&l.setValue("".concat(e,"px")),d.hasValue()&&d.setValue("".concat(r,"px"))}}}]),o}(kt);var qt=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(){var e;return(0,F.default)(this,o),(e=n.apply(this,arguments)).type="rect",e}return(0,U.default)(o,[{key:"path",value:function(e){var t=this.getAttribute("x").getPixels("x"),r=this.getAttribute("y").getPixels("y"),n=this.getStyle("width",!1,!0).getPixels("x"),o=this.getStyle("height",!1,!0).getPixels("y"),s=this.getAttribute("rx"),i=this.getAttribute("ry"),a=s.getPixels("x"),A=i.getPixels("y");if(s.hasValue()&&!i.hasValue()&&(A=a),i.hasValue()&&!s.hasValue()&&(a=A),a=Math.min(a,n/2),A=Math.min(A,o/2),e){var u=(Math.sqrt(2)-1)/3*4;e.beginPath(),o>0&&n>0&&(e.moveTo(t+a,r),e.lineTo(t+n-a,r),e.bezierCurveTo(t+n-a+u*a,r,t+n,r+A-u*A,t+n,r+A),e.lineTo(t+n,r+o-A),e.bezierCurveTo(t+n,r+o-A+u*A,t+n-a+u*a,r+o,t+n-a,r+o),e.lineTo(t+a,r+o),e.bezierCurveTo(t+a-u*a,r+o,t,r+o-A+u*A,t,r+o-A),e.lineTo(t,r+A),e.bezierCurveTo(t,r+A-u*A,t+a-u*a,r,t+a,r),e.closePath())}return new Ot(t,r,t+n,r+o)}},{key:"getMarkers",value:function(){return null}}]),o}(zt);var Gt=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(){var e;return(0,F.default)(this,o),(e=n.apply(this,arguments)).type="circle",e}return(0,U.default)(o,[{key:"path",value:function(e){var t=this.getAttribute("cx").getPixels("x"),r=this.getAttribute("cy").getPixels("y"),n=this.getAttribute("r").getPixels();return e&&n>0&&(e.beginPath(),e.arc(t,r,n,0,2*Math.PI,!1),e.closePath()),new Ot(t-n,r-n,t+n,r+n)}},{key:"getMarkers",value:function(){return null}}]),o}(zt);var Xt=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(){var e;return(0,F.default)(this,o),(e=n.apply(this,arguments)).type="ellipse",e}return(0,U.default)(o,[{key:"path",value:function(e){var t=(Math.sqrt(2)-1)/3*4,r=this.getAttribute("rx").getPixels("x"),n=this.getAttribute("ry").getPixels("y"),o=this.getAttribute("cx").getPixels("x"),s=this.getAttribute("cy").getPixels("y");return e&&r>0&&n>0&&(e.beginPath(),e.moveTo(o+r,s),e.bezierCurveTo(o+r,s+t*n,o+t*r,s+n,o,s+n),e.bezierCurveTo(o-t*r,s+n,o-r,s+t*n,o-r,s),e.bezierCurveTo(o-r,s-t*n,o-t*r,s-n,o,s-n),e.bezierCurveTo(o+t*r,s-n,o+r,s-t*n,o+r,s),e.closePath()),new Ot(o-r,s-n,o+r,s+n)}},{key:"getMarkers",value:function(){return null}}]),o}(zt);var Wt=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(){var e;return(0,F.default)(this,o),(e=n.apply(this,arguments)).type="line",e}return(0,U.default)(o,[{key:"getPoints",value:function(){return[new mt(this.getAttribute("x1").getPixels("x"),this.getAttribute("y1").getPixels("y")),new mt(this.getAttribute("x2").getPixels("x"),this.getAttribute("y2").getPixels("y"))]}},{key:"path",value:function(e){var t=this.getPoints(),r=(0,u.default)(t,2),n=r[0],o=n.x,s=n.y,i=r[1],a=i.x,A=i.y;return e&&(e.beginPath(),e.moveTo(o,s),e.lineTo(a,A)),new Ot(o,s,a,A)}},{key:"getMarkers",value:function(){var e=this.getPoints(),t=(0,u.default)(e,2),r=t[0],n=t[1],o=r.angleTo(n);return[[r,o],[n,o]]}}]),o}(zt);var Jt=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(e,t,r){var s;return(0,F.default)(this,o),(s=n.call(this,e,t,r)).type="polyline",s.points=[],s.points=mt.parsePath(s.getAttribute("points").getString()),s}return(0,U.default)(o,[{key:"path",value:function(e){var t=this.points,r=(0,u.default)(t,1)[0],n=r.x,o=r.y,s=new Ot(n,o);return e&&(e.beginPath(),e.moveTo(n,o)),g()(t).call(t,(function(t){var r=t.x,n=t.y;s.addPoint(r,n),e&&e.lineTo(r,n)})),s}},{key:"getMarkers",value:function(){var e=this.points,t=e.length-1,r=[];return g()(e).call(e,(function(n,o){o!==t&&r.push([n,n.angleTo(e[o+1])])})),r.length>0&&r.push([e[e.length-1],r[r.length-1][1]]),r}}]),o}(zt);var Yt=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(){var e;return(0,F.default)(this,o),(e=n.apply(this,arguments)).type="polygon",e}return(0,U.default)(o,[{key:"path",value:function(e){var t=(0,de.default)((0,ee.default)(o.prototype),"path",this).call(this,e),r=(0,u.default)(this.points,1)[0],n=r.x,s=r.y;return e&&(e.lineTo(n,s),e.closePath()),t}}]),o}(Jt);var Zt=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(){var e;return(0,F.default)(this,o),(e=n.apply(this,arguments)).type="pattern",e}return(0,U.default)(o,[{key:"createPattern",value:function(e,t,r){var n=this.getStyle("width").getPixels("x",!0),o=this.getStyle("height").getPixels("y",!0),s=new Vt(this.document,null);s.attributes.viewBox=new ht(this.document,"viewBox",this.getAttribute("viewBox").getValue()),s.attributes.width=new ht(this.document,"width","".concat(n,"px")),s.attributes.height=new ht(this.document,"height","".concat(o,"px")),s.attributes.transform=new ht(this.document,"transform",this.getAttribute("patternTransform").getValue()),s.children=this.children;var i=this.document.createCanvas(n,o),a=i.getContext("2d"),A=this.getAttribute("x"),u=this.getAttribute("y");A.hasValue()&&u.hasValue()&&a.translate(A.getPixels("x",!0),u.getPixels("y",!0)),r.hasValue()?this.styles["fill-opacity"]=r:ge()(this.styles,"fill-opacity");for(var c=-1;c<=1;c++)for(var l=-1;l<=1;l++)a.save(),s.attributes.x=new ht(this.document,"x",c*i.width),s.attributes.y=new ht(this.document,"y",l*i.height),s.render(a),a.restore();return e.createPattern(i,"repeat")}}]),o}(St);var $t=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(){var e;return(0,F.default)(this,o),(e=n.apply(this,arguments)).type="marker",e}return(0,U.default)(o,[{key:"render",value:function(e,t,r){if(t){var n=t.x,o=t.y,s=this.getAttribute("orient").getValue("auto"),i=this.getAttribute("markerUnits").getValue("strokeWidth");e.translate(n,o),"auto"===s&&e.rotate(r),"strokeWidth"===i&&e.scale(e.lineWidth,e.lineWidth),e.save();var a=new Vt(this.document,null);a.type=this.type,a.attributes.viewBox=new ht(this.document,"viewBox",this.getAttribute("viewBox").getValue()),a.attributes.refX=new ht(this.document,"refX",this.getAttribute("refX").getValue()),a.attributes.refY=new ht(this.document,"refY",this.getAttribute("refY").getValue()),a.attributes.width=new ht(this.document,"width",this.getAttribute("markerWidth").getValue()),a.attributes.height=new ht(this.document,"height",this.getAttribute("markerHeight").getValue()),a.attributes.overflow=new ht(this.document,"overflow",this.getAttribute("overflow").getValue()),a.attributes.fill=new ht(this.document,"fill",this.getAttribute("fill").getColor("black")),a.attributes.stroke=new ht(this.document,"stroke",this.getAttribute("stroke").getValue("none")),a.children=this.children,a.render(e),e.restore(),"strokeWidth"===i&&e.scale(1/e.lineWidth,1/e.lineWidth),"auto"===s&&e.rotate(-r),e.translate(-n,-o)}}}]),o}(St);var er=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(){var e;return(0,F.default)(this,o),(e=n.apply(this,arguments)).type="defs",e}return(0,U.default)(o,[{key:"render",value:function(){}}]),o}(St);var tr=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(){var e;return(0,F.default)(this,o),(e=n.apply(this,arguments)).type="g",e}return(0,U.default)(o,[{key:"getBoundingBox",value:function(e){var t,r=new Ot;return g()(t=this.children).call(t,(function(t){r.addBoundingBox(t.getBoundingBox(e))})),r}}]),o}(kt);var rr=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(e,t,r){var s;(0,F.default)(this,o),(s=n.call(this,e,t,r)).attributesToInherit=["gradientUnits"],s.stops=[];var i=(0,ye.default)(s),a=i.stops,A=i.children;return g()(A).call(A,(function(e){"stop"===e.type&&a.push(e)})),s}return(0,U.default)(o,[{key:"getGradientUnits",value:function(){return this.getAttribute("gradientUnits").getString("objectBoundingBox")}},{key:"createGradient",value:function(e,t,r){var n=this,o=this;this.getHrefAttribute().hasValue()&&(o=this.getHrefAttribute().getDefinition(),this.inheritStopContainer(o));var s=o.stops,i=this.getGradient(e,t);if(!i)return this.addParentOpacity(r,s[s.length-1].color);if(g()(s).call(s,(function(e){i.addColorStop(e.offset,n.addParentOpacity(r,e.color))})),this.getAttribute("gradientTransform").hasValue()){var a=this.document,A=a.screen,c=A.MAX_VIRTUAL_PIXELS,l=A.viewPort,d=(0,u.default)(l.viewPorts,1)[0],f=new qt(a,null);f.attributes.x=new ht(a,"x",-c/3),f.attributes.y=new ht(a,"y",-c/3),f.attributes.width=new ht(a,"width",c),f.attributes.height=new ht(a,"height",c);var h=new tr(a,null);h.attributes.transform=new ht(a,"transform",this.getAttribute("gradientTransform").getValue()),h.children=[f];var p=new Vt(a,null);p.attributes.x=new ht(a,"x",0),p.attributes.y=new ht(a,"y",0),p.attributes.width=new ht(a,"width",d.width),p.attributes.height=new ht(a,"height",d.height),p.children=[h];var m=a.createCanvas(d.width,d.height),y=m.getContext("2d");return y.fillStyle=i,p.render(y),y.createPattern(m,"no-repeat")}return i}},{key:"inheritStopContainer",value:function(e){var t,r=this;g()(t=this.attributesToInherit).call(t,(function(t){!r.getAttribute(t).hasValue()&&e.getAttribute(t).hasValue()&&r.getAttribute(t,!0).setValue(e.getAttribute(t).getValue())}))}},{key:"addParentOpacity",value:function(e,t){return e.hasValue()?new ht(this.document,"color",t).addOpacity(e).getColor():t}}]),o}(St);var nr=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(e,t,r){var s;return(0,F.default)(this,o),(s=n.call(this,e,t,r)).type="linearGradient",s.attributesToInherit.push("x1","y1","x2","y2"),s}return(0,U.default)(o,[{key:"getGradient",value:function(e,t){var r="objectBoundingBox"===this.getGradientUnits(),n=r?t.getBoundingBox(e):null;if(r&&!n)return null;this.getAttribute("x1").hasValue()||this.getAttribute("y1").hasValue()||this.getAttribute("x2").hasValue()||this.getAttribute("y2").hasValue()||(this.getAttribute("x1",!0).setValue(0),this.getAttribute("y1",!0).setValue(0),this.getAttribute("x2",!0).setValue(1),this.getAttribute("y2",!0).setValue(0));var o=r?n.x+n.width*this.getAttribute("x1").getNumber():this.getAttribute("x1").getPixels("x"),s=r?n.y+n.height*this.getAttribute("y1").getNumber():this.getAttribute("y1").getPixels("y"),i=r?n.x+n.width*this.getAttribute("x2").getNumber():this.getAttribute("x2").getPixels("x"),a=r?n.y+n.height*this.getAttribute("y2").getNumber():this.getAttribute("y2").getPixels("y");return o===i&&s===a?null:e.createLinearGradient(o,s,i,a)}}]),o}(rr);var or=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(e,t,r){var s;return(0,F.default)(this,o),(s=n.call(this,e,t,r)).type="radialGradient",s.attributesToInherit.push("cx","cy","r","fx","fy","fr"),s}return(0,U.default)(o,[{key:"getGradient",value:function(e,t){var r="objectBoundingBox"===this.getGradientUnits(),n=t.getBoundingBox(e);if(r&&!n)return null;this.getAttribute("cx").hasValue()||this.getAttribute("cx",!0).setValue("50%"),this.getAttribute("cy").hasValue()||this.getAttribute("cy",!0).setValue("50%"),this.getAttribute("r").hasValue()||this.getAttribute("r",!0).setValue("50%");var o=r?n.x+n.width*this.getAttribute("cx").getNumber():this.getAttribute("cx").getPixels("x"),s=r?n.y+n.height*this.getAttribute("cy").getNumber():this.getAttribute("cy").getPixels("y"),i=o,a=s;this.getAttribute("fx").hasValue()&&(i=r?n.x+n.width*this.getAttribute("fx").getNumber():this.getAttribute("fx").getPixels("x")),this.getAttribute("fy").hasValue()&&(a=r?n.y+n.height*this.getAttribute("fy").getNumber():this.getAttribute("fy").getPixels("y"));var A=r?(n.width+n.height)/2*this.getAttribute("r").getNumber():this.getAttribute("r").getPixels(),u=this.getAttribute("fr").getPixels();return e.createRadialGradient(i,a,u,o,s,A)}}]),o}(rr);var sr=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(e,t,r){var s;(0,F.default)(this,o),(s=n.call(this,e,t,r)).type="stop";var i=Math.max(0,Math.min(1,s.getAttribute("offset").getNumber())),a=s.getStyle("stop-opacity"),A=s.getStyle("stop-color",!0);return""===A.getString()&&A.setValue("#000"),a.hasValue()&&(A=A.addOpacity(a)),s.offset=i,s.color=A.getColor(),s}return o}(St);var ir=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(e,t,r){var s;return(0,F.default)(this,o),(s=n.call(this,e,t,r)).type="animate",s.duration=0,s.initialValue=null,s.initialUnits="",s.removed=!1,s.frozen=!1,e.screen.animations.push((0,ye.default)(s)),s.begin=s.getAttribute("begin").getMilliseconds(),s.maxDuration=s.begin+s.getAttribute("dur").getMilliseconds(),s.from=s.getAttribute("from"),s.to=s.getAttribute("to"),s.values=s.getAttribute("values"),we()(s).hasValue()&&we()(s).setValue(we()(s).getString().split(";")),s}return(0,U.default)(o,[{key:"getProperty",value:function(){var e=this.getAttribute("attributeType").getString(),t=this.getAttribute("attributeName").getString();return"CSS"===e?this.parent.getStyle(t,!0):this.parent.getAttribute(t,!0)}},{key:"calcValue",value:function(){var e,t=this.initialUnits,r=this.getProgress(),n=r.progress,o=r.from,s=r.to,i=o.getNumber()+(s.getNumber()-o.getNumber())*n;return"%"===t&&(i*=100),L()(e="".concat(i)).call(e,t)}},{key:"update",value:function(e){var t=this.parent,r=this.getProperty();if(this.initialValue||(this.initialValue=r.getString(),this.initialUnits=r.getUnits()),this.duration>this.maxDuration){var n=this.getAttribute("fill").getString("remove");if("indefinite"===this.getAttribute("repeatCount").getString()||"indefinite"===this.getAttribute("repeatDur").getString())this.duration=0;else if("freeze"!==n||this.frozen){if("remove"===n&&!this.removed)return this.removed=!0,r.setValue(t.animationFrozen?t.animationFrozenValue:this.initialValue),!0}else this.frozen=!0,t.animationFrozen=!0,t.animationFrozenValue=r.getString();return!1}this.duration+=e;var o=!1;if(this.begine.length)&&(t=e.length);for(var r=0,n=new Array(t);r=e.length?{done:!0}:{done:!1,value:e[n++]}},e:function(e){throw e},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var s,i=!0,a=!1;return{s:function(){r=_e()(e)},n:function(){var e=r.next();return i=e.done,e},e:function(e){a=!0,s=e},f:function(){try{i||null==r.return||r.return()}finally{if(a)throw s}}}}((0,ye.default)(s).children);try{for(A.s();!(i=A.n()).done;){var u=i.value;switch(u.type){case"font-face":s.fontFace=u;var c=u.getStyle("font-family");c.hasValue()&&(a[c.getString()]=(0,ye.default)(s));break;case"missing-glyph":s.missingGlyph=u;break;case"glyph":var l=u;l.arabicForm?(s.isRTL=!0,s.isArabic=!0,void 0===s.glyphs[l.unicode]&&(s.glyphs[l.unicode]={}),s.glyphs[l.unicode][l.arabicForm]=l):s.glyphs[l.unicode]=l}}}catch(e){A.e(e)}finally{A.f()}return s}return(0,U.default)(o,[{key:"render",value:function(){}}]),o}(St);var lr=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(e,t,r){var s;return(0,F.default)(this,o),(s=n.call(this,e,t,r)).type="font-face",s.ascent=s.getAttribute("ascent").getNumber(),s.descent=s.getAttribute("descent").getNumber(),s.unitsPerEm=s.getAttribute("units-per-em").getNumber(),s}return o}(St);var dr=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(){var e;return(0,F.default)(this,o),(e=n.apply(this,arguments)).type="missing-glyph",e.horizAdvX=0,e}return o}(zt);var fr=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(e,t,r){var s;return(0,F.default)(this,o),(s=n.call(this,e,t,r)).type="glyph",s.horizAdvX=s.getAttribute("horiz-adv-x").getNumber(),s.unicode=s.getAttribute("unicode").getString(),s.arabicForm=s.getAttribute("arabic-form").getString(),s}return o}(zt);var hr=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(){var e;return(0,F.default)(this,o),(e=n.apply(this,arguments)).type="tref",e}return(0,U.default)(o,[{key:"getText",value:function(){var e=this.getHrefAttribute().getDefinition();if(e){var t=e.children[0];if(t)return t.getText()}return""}}]),o}(Rt);var pr=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(e,t,r){var s,i;(0,F.default)(this,o),(i=n.call(this,e,t,r)).type="a";var a=t.childNodes,A=a[0],u=a.length>0&&k()(s=ae()(a)).call(s,(function(e){return 3===e.nodeType}));return i.hasText=u,i.text=u?i.getTextFromNode(A):"",i}return(0,U.default)(o,[{key:"getText",value:function(){return this.text}},{key:"renderChildren",value:function(e){if(this.hasText){(0,de.default)((0,ee.default)(o.prototype),"renderChildren",this).call(this,e);var t=this.document,r=this.x,n=this.y,s=t.screen.mouse,i=new ht(t,"fontSize",Pt.parse(t.ctx.font).fontSize);s.isWorking()&&s.checkBoundingBox(this,new Ot(r,n-i.getPixels("y"),r+this.measureText(e),n))}else if(this.children.length>0){var a=new tr(this.document,null);a.children=this.children,a.parent=this,a.render(e)}}},{key:"onClick",value:function(){var e=this.document.window;e&&e.open(this.getHrefAttribute().getString())}},{key:"onMouseMove",value:function(){this.document.ctx.canvas.style.cursor="pointer"}}]),o}(Rt);function mr(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=new Array(t);rA?a:A,p=a>A?1:a/A,m=a>A?A/a:1;e&&(e.translate(s,i),e.rotate(d),e.scale(p,m),e.arc(0,0,h,c,c+l,Boolean(1-f)),e.scale(1/p,1/m),e.rotate(-d),e.translate(-s,-i));break;case Kt.CLOSE_PATH:e&&e.closePath()}}))}},{key:"renderChildren",value:function(e){this.setTextData(e),e.save();var t=this.parent.getStyle("text-decoration").getString(),r=this.getFontSize(),n=this.glyphInfo,o=e.fillStyle;"underline"===t&&e.beginPath(),g()(n).call(n,(function(n,o){var s=n.p0,i=n.p1,a=n.rotation,A=n.text;e.save(),e.translate(s.x,s.y),e.rotate(a),e.fillStyle&&e.fillText(A,0,0),e.strokeStyle&&e.strokeText(A,0,0),e.restore(),"underline"===t&&(0===o&&e.moveTo(s.x,s.y+r/8),e.lineTo(i.x,i.y+r/5))})),"underline"===t&&(e.lineWidth=r/20,e.strokeStyle=o,e.stroke(),e.closePath()),e.restore()}},{key:"getLetterSpacingAt",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0;return this.letterSpacingCache[e]||0}},{key:"findSegmentToFitChar",value:function(e,t,r,n,o,s,i,a,A){var u=s,c=this.measureText(e,a);" "===a&&"justify"===t&&r-1&&(u+=this.getLetterSpacingAt(A));var l=this.textHeight/20,d=this.getEquidistantPointOnPath(u,l,0),f=this.getEquidistantPointOnPath(u+c,l,0),h={p0:d,p1:f},p=d&&f?Math.atan2(f.y-d.y,f.x-d.x):0;if(i){var m=Math.cos(Math.PI/2+p)*i,g=Math.cos(-p)*i;h.p0=yr(yr({},d),{},{x:d.x+m,y:d.y+g}),h.p1=yr(yr({},f),{},{x:f.x+m,y:f.y+g})}return{offset:u+=c,segment:h,rotation:p}}},{key:"measureText",value:function(e,t){var r=this.measuresCache,n=t||this.getText();if(r.has(n))return r.get(n);var o=this.measureTargetText(e,n);return r.set(n,o),o}},{key:"setTextData",value:function(e){var t,r=this;if(!this.glyphInfo){var n=this.getText(),o=n.split(""),s=n.split(" ").length-1,i=A()(t=this.parent.getAttribute("dx").split()).call(t,(function(e){return e.getPixels("x")})),a=this.parent.getAttribute("dy").getPixels("y"),u=this.parent.getStyle("text-anchor").getString("start"),c=this.getStyle("letter-spacing"),l=this.parent.getStyle("letter-spacing"),d=0;c.hasValue()&&"inherit"!==c.getValue()?c.hasValue()&&"initial"!==c.getValue()&&"unset"!==c.getValue()&&(d=c.getPixels()):d=l.getPixels();var f=[],h=n.length;this.letterSpacingCache=f;for(var p=0;p0&&(A-=2*Math.PI),1===o&&A<0&&(A+=2*Math.PI),[i.x,i.y,r,n,a,A,s,o]}},{key:"calcLength",value:function(e,t,r,n){var o=0,s=null,i=null,a=0;switch(r){case Kt.LINE_TO:return this.getLineLength(e,t,n[0],n[1]);case Kt.CURVE_TO:for(o=0,s=this.getPointOnCubicBezier(0,e,t,n[0],n[1],n[2],n[3],n[4],n[5]),a=.01;a<=1;a+=.01)i=this.getPointOnCubicBezier(a,e,t,n[0],n[1],n[2],n[3],n[4],n[5]),o+=this.getLineLength(s.x,s.y,i.x,i.y),s=i;return o;case Kt.QUAD_TO:for(o=0,s=this.getPointOnQuadraticBezier(0,e,t,n[0],n[1],n[2],n[3]),a=.01;a<=1;a+=.01)i=this.getPointOnQuadraticBezier(a,e,t,n[0],n[1],n[2],n[3]),o+=this.getLineLength(s.x,s.y,i.x,i.y),s=i;return o;case Kt.ARC:o=0;var A=n[4],u=n[5],c=n[4]+u,l=Math.PI/180;if(Math.abs(A-c)c;a-=l)i=this.getPointOnEllipticalArc(n[0],n[1],n[2],n[3],a,0),o+=this.getLineLength(s.x,s.y,i.x,i.y),s=i;else for(a=A+l;a5&&void 0!==arguments[5]?arguments[5]:t,i=arguments.length>6&&void 0!==arguments[6]?arguments[6]:r,a=(o-r)/(n-t+nt),A=Math.sqrt(e*e/(1+a*a));nt)return null;var o,s=function(e,t){var r;if(void 0===Fe()||null==Ne()(e)){if(xe()(e)||(r=function(e,t){var r;if(e){if("string"==typeof e)return mr(e,t);var n=Se()(r=Object.prototype.toString.call(e)).call(r,8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?ae()(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?mr(e,t):void 0}}(e))||t&&e&&"number"==typeof e.length){r&&(e=r);var n=0,o=function(){};return{s:o,n:function(){return n>=e.length?{done:!0}:{done:!1,value:e[n++]}},e:function(e){throw e},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var s,i=!0,a=!1;return{s:function(){r=_e()(e)},n:function(){var e=r.next();return i=e.done,e},e:function(e){a=!0,s=e},f:function(){try{i||null==r.return||r.return()}finally{if(a)throw s}}}}(this.dataArray);try{for(s.s();!(o=s.n()).done;){var i=o.value;if(!i||!(i.pathLength<5e-5||r+i.pathLength+5e-5=0&&A>l)break;n=this.getPointOnEllipticalArc(i.points[0],i.points[1],i.points[2],i.points[3],A,i.points[6]);break;case Kt.CURVE_TO:(A=a/i.pathLength)>1&&(A=1),n=this.getPointOnCubicBezier(A,i.start.x,i.start.y,i.points[0],i.points[1],i.points[2],i.points[3],i.points[4],i.points[5]);break;case Kt.QUAD_TO:(A=a/i.pathLength)>1&&(A=1),n=this.getPointOnQuadraticBezier(A,i.start.x,i.start.y,i.points[0],i.points[1],i.points[2],i.points[3])}if(n)return n;break}r+=i.pathLength}}catch(e){s.e(e)}finally{s.f()}return null}},{key:"getLineLength",value:function(e,t,r,n){return Math.sqrt((r-e)*(r-e)+(n-t)*(n-t))}},{key:"getPathLength",value:function(){var e;return-1===this.pathLength&&(this.pathLength=H()(e=this.dataArray).call(e,(function(e,t){return t.pathLength>0?e+t.pathLength:e}),0)),this.pathLength}},{key:"getPointOnCubicBezier",value:function(e,t,r,n,o,s,i,a,A){return{x:a*at(e)+s*At(e)+n*ut(e)+t*ct(e),y:A*at(e)+i*At(e)+o*ut(e)+r*ct(e)}}},{key:"getPointOnQuadraticBezier",value:function(e,t,r,n,o,s,i){return{x:s*lt(e)+n*dt(e)+t*ft(e),y:i*lt(e)+o*dt(e)+r*ft(e)}}},{key:"getPointOnEllipticalArc",value:function(e,t,r,n,o,s){var i=Math.cos(s),a=Math.sin(s),A=r*Math.cos(o),u=n*Math.sin(o);return{x:e+(A*i-u*a),y:t+(A*a+u*i)}}},{key:"buildEquidistantCache",value:function(e,t){var r=this.getPathLength(),n=t||.25,o=e||r/100;if(!this.equidistantCache||this.equidistantCache.step!==o||this.equidistantCache.precision!==n){this.equidistantCache={step:o,precision:n,points:[]};for(var s=0,i=0;i<=r;i+=n){var a=this.getPointOnPath(i),A=this.getPointOnPath(i+n);a&&A&&(s+=this.getLineLength(a.x,a.y,A.x,A.y))>=o&&(this.equidistantCache.points.push({x:a.x,y:a.y,distance:i}),s-=o)}}}},{key:"getEquidistantPointOnPath",value:function(e,t,r){if(this.buildEquidistantCache(t,r),e<0||e-this.getPathLength()>5e-5)return null;var n=Math.round(e/this.getPathLength()*(this.equidistantCache.points.length-1));return this.equidistantCache.points[n]||null}}]),o}(Rt);var wr=function(e){(0,Z.default)(i,e);var t,r,n,o,s=(n=i,o=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,t=(0,ee.default)(n);if(o){var r=(0,ee.default)(this).constructor;e=Y()(t,arguments,r)}else e=t.apply(this,arguments);return(0,$.default)(this,e)});function i(e,t,r){var n;(0,F.default)(this,i),(n=s.call(this,e,t,r)).type="image",n.loaded=!1;var o=n.getHrefAttribute().getString();if(!o)return(0,$.default)(n);var a=/\.svg$/.test(o);return e.images.push((0,ye.default)(n)),a?n.loadSvg(o):n.loadImage(o),n.isSvg=a,n}return(0,U.default)(i,[{key:"loadImage",value:(r=(0,N.default)(E().mark((function e(t){var r;return E().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,this.document.createImage(t);case 3:r=e.sent,this.image=r,e.next=10;break;case 7:e.prev=7,e.t0=e.catch(0),console.error('Error while loading image "'.concat(t,'":'),e.t0);case 10:this.loaded=!0;case 11:case"end":return e.stop()}}),e,this,[[0,7]])}))),function(e){return r.apply(this,arguments)})},{key:"loadSvg",value:(t=(0,N.default)(E().mark((function e(t){var r,n;return E().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,this.document.fetch(t);case 3:return r=e.sent,e.next=6,r.text();case 6:n=e.sent,this.image=n,e.next=13;break;case 10:e.prev=10,e.t0=e.catch(0),console.error('Error while loading image "'.concat(t,'":'),e.t0);case 13:this.loaded=!0;case 14:case"end":return e.stop()}}),e,this,[[0,10]])}))),function(e){return t.apply(this,arguments)})},{key:"renderChildren",value:function(e){var t=this.document,r=this.image,n=this.loaded,o=this.getAttribute("x").getPixels("x"),s=this.getAttribute("y").getPixels("y"),i=this.getStyle("width").getPixels("x"),a=this.getStyle("height").getPixels("y");if(n&&r&&i&&a){if(e.save(),this.isSvg)t.canvg.forkString(e,this.image,{ignoreMouse:!0,ignoreAnimation:!0,ignoreDimensions:!0,ignoreClear:!0,offsetX:o,offsetY:s,scaleWidth:i,scaleHeight:a}).render();else{var A=this.image;e.translate(o,s),t.setViewBox({ctx:e,aspectRatio:this.getAttribute("preserveAspectRatio").getString(),width:i,desiredWidth:A.width,height:a,desiredHeight:A.height}),this.loaded&&(void 0===A.complete||A.complete)&&e.drawImage(A,0,0)}e.restore()}}},{key:"getBoundingBox",value:function(){var e=this.getAttribute("x").getPixels("x"),t=this.getAttribute("y").getPixels("y"),r=this.getStyle("width").getPixels("x"),n=this.getStyle("height").getPixels("y");return new Ot(e,t,e+r,t+n)}}]),i}(kt);var br=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(){var e;return(0,F.default)(this,o),(e=n.apply(this,arguments)).type="symbol",e}return(0,U.default)(o,[{key:"render",value:function(e){}}]),o}(kt),Br=function(){function e(t){(0,F.default)(this,e),this.document=t,this.loaded=!1,t.fonts.push(this)}var t;return(0,U.default)(e,[{key:"load",value:(t=(0,N.default)(E().mark((function e(t,r){var n,o,s,i;return E().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,o=this.document,e.next=4,o.canvg.parser.load(r);case 4:s=e.sent,i=s.getElementsByTagName("font"),g()(n=ae()(i)).call(n,(function(e){var r=o.createElement(e);o.definitions[t]=r})),e.next=12;break;case 9:e.prev=9,e.t0=e.catch(0),console.error('Error while loading font "'.concat(r,'":'),e.t0);case 12:this.loaded=!0;case 13:case"end":return e.stop()}}),e,this,[[0,9]])}))),function(e,r){return t.apply(this,arguments)})}]),e}();var jr=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(e,t,r){var s,i;(0,F.default)(this,o),(i=n.call(this,e,t,r)).type="style";var a=Re(A()(s=ae()(t.childNodes)).call(s,(function(e){return e.data})).join("").replace(/(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(^[\s]*\/\/.*)/gm,"").replace(/@import.*;/g,"")).split("}");return g()(a).call(a,(function(t){var r=G()(t).call(t);if(r){var n=r.split("{"),o=n[0].split(","),s=n[1].split(";");g()(o).call(o,(function(t){var r=G()(t).call(t);if(r){var n=e.styles[r]||{};if(g()(s).call(s,(function(t){var r,o,s=le()(t).call(t,":"),i=G()(r=t.substr(0,s)).call(r),a=G()(o=t.substr(s+1,t.length-s)).call(o);i&&a&&(n[i]=new ht(e,i,a))})),e.styles[r]=n,e.stylesSpecificity[r]=rt(r),"@font-face"===r){var o=n["font-family"].getString().replace(/"|'/g,""),i=n.src.getString().split(",");g()(i).call(i,(function(t){if(le()(t).call(t,'format("svg")')>0){var r=qe(t);r&&new Br(e).load(o,r)}}))}}}))}})),i}return o}(St);jr.parseExternalUrl=qe;var _r=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(){var e;return(0,F.default)(this,o),(e=n.apply(this,arguments)).type="use",e}return(0,U.default)(o,[{key:"setContext",value:function(e){(0,de.default)((0,ee.default)(o.prototype),"setContext",this).call(this,e);var t=this.getAttribute("x"),r=this.getAttribute("y");t.hasValue()&&e.translate(t.getPixels("x"),0),r.hasValue()&&e.translate(0,r.getPixels("y"))}},{key:"path",value:function(e){var t=this.element;t&&t.path(e)}},{key:"renderChildren",value:function(e){var t=this.document,r=this.element;if(r){var n=r;if("symbol"===r.type&&((n=new Vt(t,null)).attributes.viewBox=new ht(t,"viewBox",r.getAttribute("viewBox").getString()),n.attributes.preserveAspectRatio=new ht(t,"preserveAspectRatio",r.getAttribute("preserveAspectRatio").getString()),n.attributes.overflow=new ht(t,"overflow",r.getAttribute("overflow").getString()),n.children=r.children,r.styles.opacity=new ht(t,"opacity",this.calculateOpacity())),"svg"===n.type){var o=this.getStyle("width",!1,!0),s=this.getStyle("height",!1,!0);o.hasValue()&&(n.attributes.width=new ht(t,"width",o.getString())),s.hasValue()&&(n.attributes.height=new ht(t,"height",s.getString()))}var i=n.parent;n.parent=this,n.render(e),n.parent=i}}},{key:"getBoundingBox",value:function(e){var t=this.element;return t?t.getBoundingBox(e):null}},{key:"elementTransform",value:function(){var e=this.document,t=this.element;return Ut.fromElement(e,t)}},{key:"element",get:function(){return this._element||(this._element=this.getHrefAttribute().getDefinition()),this._element}}]),o}(kt);function Cr(e,t,r,n,o,s){return e[r*n*4+4*t+s]}function xr(e,t,r,n,o,s,i){e[r*n*4+4*t+s]=i}function Er(e,t,r){return e[t]*r}function Nr(e,t,r,n){return t+Math.cos(e)*r+Math.sin(e)*n}var Qr=function(e){(0,Z.default)(o,e);var t,r,n=(t=o,r=function(){if("undefined"==typeof Reflect||!Y())return!1;if(Y().sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Y()(Date,[],(function(){}))),!0}catch(e){return!1}}(),function(){var e,n=(0,ee.default)(t);if(r){var o=(0,ee.default)(this).constructor;e=Y()(n,arguments,o)}else e=n.apply(this,arguments);return(0,$.default)(this,e)});function o(e,t,r){var s;(0,F.default)(this,o),(s=n.call(this,e,t,r)).type="feColorMatrix";var i=Ke(s.getAttribute("values").getString());switch(s.getAttribute("type").getString("matrix")){case"saturate":var a=i[0];i=[.213+.787*a,.715-.715*a,.072-.072*a,0,0,.213-.213*a,.715+.285*a,.072-.072*a,0,0,.213-.213*a,.715-.715*a,.072+.928*a,0,0,0,0,0,1,0,0,0,0,0,1];break;case"hueRotate":var A=i[0]*Math.PI/180;i=[Nr(A,.213,.787,-.213),Nr(A,.715,-.715,-.715),Nr(A,.072,-.072,.928),0,0,Nr(A,.213,-.213,.143),Nr(A,.715,.285,.14),Nr(A,.072,-.072,-.283),0,0,Nr(A,.213,-.213,-.787),Nr(A,.715,-.715,.715),Nr(A,.072,.928,.072),0,0,0,0,0,1,0,0,0,0,0,1];break;case"luminanceToAlpha":i=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,.2125,.7154,.0721,0,0,0,0,0,0,1]}return s.matrix=i,s.includeOpacity=s.getAttribute("includeOpacity").hasValue(),s}return(0,U.default)(o,[{key:"apply",value:function(e,t,r,n,o){for(var s=this.includeOpacity,i=this.matrix,a=e.getImageData(0,0,n,o),A=0;A1&&void 0!==o[1]&&o[1],n=document.createElement("img"),r&&(n.crossOrigin="Anonymous"),e.abrupt("return",new(M())((function(e,r){n.onload=function(){e(n)},n.onerror=function(){r()},n.src=t})));case 4:case"end":return e.stop()}}),e)})))).apply(this,arguments)}var Kr=function(){function e(t){var r,n,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},s=o.rootEmSize,i=void 0===s?12:s,a=o.emSize,A=void 0===a?12:a,u=o.createCanvas,c=void 0===u?e.createCanvas:u,l=o.createImage,d=void 0===l?e.createImage:l,f=o.anonymousCrossOrigin;(0,F.default)(this,e),this.canvg=t,this.definitions={},this.styles={},this.stylesSpecificity={},this.images=[],this.fonts=[],this.emSizeStack=[],this.uniqueId=0,this.screen=t.screen,this.rootEmSize=i,this.emSize=A,this.createCanvas=c,this.createImage=this.bindCreateImage(d,f),this.screen.wait(K()(r=this.isImagesLoaded).call(r,this)),this.screen.wait(K()(n=this.isFontsLoaded).call(n,this))}return(0,U.default)(e,[{key:"bindCreateImage",value:function(e,t){return"boolean"==typeof t?function(r,n){return e(r,"boolean"==typeof n?n:t)}:e}},{key:"popEmSize",value:function(){this.emSizeStack.pop()}},{key:"getUniqueId",value:function(){return"canvg".concat(++this.uniqueId)}},{key:"isImagesLoaded",value:function(){var e;return k()(e=this.images).call(e,(function(e){return e.loaded}))}},{key:"isFontsLoaded",value:function(){var e;return k()(e=this.fonts).call(e,(function(e){return e.loaded}))}},{key:"createDocumentElement",value:function(e){var t=this.createElement(e.documentElement);return t.root=!0,t.addStylesFromStyleDefinition(),this.documentElement=t,t}},{key:"createElement",value:function(t){var r=t.nodeName.replace(/^[^:]+:/,""),n=e.elementTypes[r];return void 0!==n?new n(this,t):new Lt(this,t)}},{key:"createTextNode",value:function(e){return new Dt(this,e)}},{key:"setViewBox",value:function(e){this.screen.setViewBox(function(e){for(var t=1;t2&&void 0!==arguments[2]?arguments[2]:{};(0,F.default)(this,e),this.parser=new jt(n),this.screen=new wt(t,n),this.options=n;var o=new Kr(this,n),s=o.createDocumentElement(r);this.document=o,this.documentElement=s}var t,r;return(0,U.default)(e,[{key:"fork",value:function(t,r){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return e.from(t,r,Vr(Vr({},this.options),n))}},{key:"forkString",value:function(t,r){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return e.fromString(t,r,Vr(Vr({},this.options),n))}},{key:"ready",value:function(){return this.screen.ready()}},{key:"isReady",value:function(){return this.screen.isReady()}},{key:"render",value:(r=(0,N.default)(E().mark((function e(){var t,r=arguments;return E().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return t=r.length>0&&void 0!==r[0]?r[0]:{},this.start(Vr({enableRedraw:!0,ignoreAnimation:!0,ignoreMouse:!0},t)),e.next=4,this.ready();case 4:this.stop();case 5:case"end":return e.stop()}}),e,this)}))),function(){return r.apply(this,arguments)})},{key:"start",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.documentElement,r=this.screen,n=this.options;r.start(t,Vr(Vr({enableRedraw:!0},n),e))}},{key:"stop",value:function(){this.screen.stop()}},{key:"resize",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:e,r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];this.documentElement.resize(e,t,r)}}],[{key:"from",value:(t=(0,N.default)(E().mark((function t(r,n){var o,s,i,a=arguments;return E().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return o=a.length>2&&void 0!==a[2]?a[2]:{},s=new jt(o),t.next=4,s.parse(n);case 4:return i=t.sent,t.abrupt("return",new e(r,i,o));case 6:case"end":return t.stop()}}),t)}))),function(e,r){return t.apply(this,arguments)})},{key:"fromString",value:function(t,r){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},o=new jt(n),s=o.parseFromString(r);return new e(t,s,n)}}]),e}(),Gr=Object.freeze({__proto__:null,offscreen:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.DOMParser,r={window:null,ignoreAnimation:!0,ignoreMouse:!0,DOMParser:t,createCanvas:function(e,t){return new OffscreenCanvas(e,t)},createImage:function(e){return(0,N.default)(E().mark((function t(){var r,n,o;return E().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return t.next=2,fetch(e);case 2:return r=t.sent,t.next=5,r.blob();case 5:return n=t.sent,t.next=8,createImageBitmap(n);case 8:return o=t.sent,t.abrupt("return",o);case 10:case"end":return t.stop()}}),t)})))()}};return"undefined"==typeof DOMParser&&void 0!==t||ge()(r,"DOMParser"),r},node:function(e){var t=e.DOMParser,r=e.canvas;return{window:null,ignoreAnimation:!0,ignoreMouse:!0,DOMParser:t,fetch:e.fetch,createCanvas:r.createCanvas,createImage:r.loadImage}}});t.default=qr},"./node_modules/core-js-pure/es/array/from.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.string.iterator.js"),r("./node_modules/core-js-pure/modules/es.array.from.js");var n=r("./node_modules/core-js-pure/internals/path.js");e.exports=n.Array.from},"./node_modules/core-js-pure/es/array/is-array.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.array.is-array.js");var n=r("./node_modules/core-js-pure/internals/path.js");e.exports=n.Array.isArray},"./node_modules/core-js-pure/es/array/virtual/concat.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.array.concat.js");var n=r("./node_modules/core-js-pure/internals/entry-virtual.js");e.exports=n("Array").concat},"./node_modules/core-js-pure/es/array/virtual/every.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.array.every.js");var n=r("./node_modules/core-js-pure/internals/entry-virtual.js");e.exports=n("Array").every},"./node_modules/core-js-pure/es/array/virtual/fill.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.array.fill.js");var n=r("./node_modules/core-js-pure/internals/entry-virtual.js");e.exports=n("Array").fill},"./node_modules/core-js-pure/es/array/virtual/filter.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.array.filter.js");var n=r("./node_modules/core-js-pure/internals/entry-virtual.js");e.exports=n("Array").filter},"./node_modules/core-js-pure/es/array/virtual/for-each.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.array.for-each.js");var n=r("./node_modules/core-js-pure/internals/entry-virtual.js");e.exports=n("Array").forEach},"./node_modules/core-js-pure/es/array/virtual/includes.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.array.includes.js");var n=r("./node_modules/core-js-pure/internals/entry-virtual.js");e.exports=n("Array").includes},"./node_modules/core-js-pure/es/array/virtual/index-of.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.array.index-of.js");var n=r("./node_modules/core-js-pure/internals/entry-virtual.js");e.exports=n("Array").indexOf},"./node_modules/core-js-pure/es/array/virtual/map.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.array.map.js");var n=r("./node_modules/core-js-pure/internals/entry-virtual.js");e.exports=n("Array").map},"./node_modules/core-js-pure/es/array/virtual/reduce.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.array.reduce.js");var n=r("./node_modules/core-js-pure/internals/entry-virtual.js");e.exports=n("Array").reduce},"./node_modules/core-js-pure/es/array/virtual/reverse.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.array.reverse.js");var n=r("./node_modules/core-js-pure/internals/entry-virtual.js");e.exports=n("Array").reverse},"./node_modules/core-js-pure/es/array/virtual/slice.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.array.slice.js");var n=r("./node_modules/core-js-pure/internals/entry-virtual.js");e.exports=n("Array").slice},"./node_modules/core-js-pure/es/array/virtual/some.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.array.some.js");var n=r("./node_modules/core-js-pure/internals/entry-virtual.js");e.exports=n("Array").some},"./node_modules/core-js-pure/es/array/virtual/values.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.array.iterator.js");var n=r("./node_modules/core-js-pure/internals/entry-virtual.js");e.exports=n("Array").values},"./node_modules/core-js-pure/es/date/now.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.date.now.js");var n=r("./node_modules/core-js-pure/internals/path.js");e.exports=n.Date.now},"./node_modules/core-js-pure/es/function/virtual/bind.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.function.bind.js");var n=r("./node_modules/core-js-pure/internals/entry-virtual.js");e.exports=n("Function").bind},"./node_modules/core-js-pure/es/instance/bind.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/function/virtual/bind.js"),o=Function.prototype;e.exports=function(e){var t=e.bind;return e===o||e instanceof Function&&t===o.bind?n:t}},"./node_modules/core-js-pure/es/instance/concat.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/array/virtual/concat.js"),o=Array.prototype;e.exports=function(e){var t=e.concat;return e===o||e instanceof Array&&t===o.concat?n:t}},"./node_modules/core-js-pure/es/instance/every.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/array/virtual/every.js"),o=Array.prototype;e.exports=function(e){var t=e.every;return e===o||e instanceof Array&&t===o.every?n:t}},"./node_modules/core-js-pure/es/instance/fill.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/array/virtual/fill.js"),o=Array.prototype;e.exports=function(e){var t=e.fill;return e===o||e instanceof Array&&t===o.fill?n:t}},"./node_modules/core-js-pure/es/instance/filter.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/array/virtual/filter.js"),o=Array.prototype;e.exports=function(e){var t=e.filter;return e===o||e instanceof Array&&t===o.filter?n:t}},"./node_modules/core-js-pure/es/instance/includes.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/array/virtual/includes.js"),o=r("./node_modules/core-js-pure/es/string/virtual/includes.js"),s=Array.prototype,i=String.prototype;e.exports=function(e){var t=e.includes;return e===s||e instanceof Array&&t===s.includes?n:"string"==typeof e||e===i||e instanceof String&&t===i.includes?o:t}},"./node_modules/core-js-pure/es/instance/index-of.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/array/virtual/index-of.js"),o=Array.prototype;e.exports=function(e){var t=e.indexOf;return e===o||e instanceof Array&&t===o.indexOf?n:t}},"./node_modules/core-js-pure/es/instance/map.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/array/virtual/map.js"),o=Array.prototype;e.exports=function(e){var t=e.map;return e===o||e instanceof Array&&t===o.map?n:t}},"./node_modules/core-js-pure/es/instance/reduce.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/array/virtual/reduce.js"),o=Array.prototype;e.exports=function(e){var t=e.reduce;return e===o||e instanceof Array&&t===o.reduce?n:t}},"./node_modules/core-js-pure/es/instance/reverse.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/array/virtual/reverse.js"),o=Array.prototype;e.exports=function(e){var t=e.reverse;return e===o||e instanceof Array&&t===o.reverse?n:t}},"./node_modules/core-js-pure/es/instance/slice.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/array/virtual/slice.js"),o=Array.prototype;e.exports=function(e){var t=e.slice;return e===o||e instanceof Array&&t===o.slice?n:t}},"./node_modules/core-js-pure/es/instance/some.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/array/virtual/some.js"),o=Array.prototype;e.exports=function(e){var t=e.some;return e===o||e instanceof Array&&t===o.some?n:t}},"./node_modules/core-js-pure/es/instance/starts-with.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/string/virtual/starts-with.js"),o=String.prototype;e.exports=function(e){var t=e.startsWith;return"string"==typeof e||e===o||e instanceof String&&t===o.startsWith?n:t}},"./node_modules/core-js-pure/es/instance/trim.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/string/virtual/trim.js"),o=String.prototype;e.exports=function(e){var t=e.trim;return"string"==typeof e||e===o||e instanceof String&&t===o.trim?n:t}},"./node_modules/core-js-pure/es/map/index.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.map.js"),r("./node_modules/core-js-pure/modules/es.object.to-string.js"),r("./node_modules/core-js-pure/modules/es.string.iterator.js"),r("./node_modules/core-js-pure/modules/web.dom-collections.iterator.js");var n=r("./node_modules/core-js-pure/internals/path.js");e.exports=n.Map},"./node_modules/core-js-pure/es/object/create.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.object.create.js");var n=r("./node_modules/core-js-pure/internals/path.js").Object;e.exports=function(e,t){return n.create(e,t)}},"./node_modules/core-js-pure/es/object/define-properties.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.object.define-properties.js");var n=r("./node_modules/core-js-pure/internals/path.js").Object,o=e.exports=function(e,t){return n.defineProperties(e,t)};n.defineProperties.sham&&(o.sham=!0)},"./node_modules/core-js-pure/es/object/define-property.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.object.define-property.js");var n=r("./node_modules/core-js-pure/internals/path.js").Object,o=e.exports=function(e,t,r){return n.defineProperty(e,t,r)};n.defineProperty.sham&&(o.sham=!0)},"./node_modules/core-js-pure/es/object/get-own-property-descriptor.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.object.get-own-property-descriptor.js");var n=r("./node_modules/core-js-pure/internals/path.js").Object,o=e.exports=function(e,t){return n.getOwnPropertyDescriptor(e,t)};n.getOwnPropertyDescriptor.sham&&(o.sham=!0)},"./node_modules/core-js-pure/es/object/get-own-property-descriptors.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.object.get-own-property-descriptors.js");var n=r("./node_modules/core-js-pure/internals/path.js");e.exports=n.Object.getOwnPropertyDescriptors},"./node_modules/core-js-pure/es/object/get-own-property-symbols.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.symbol.js");var n=r("./node_modules/core-js-pure/internals/path.js");e.exports=n.Object.getOwnPropertySymbols},"./node_modules/core-js-pure/es/object/get-prototype-of.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.object.get-prototype-of.js");var n=r("./node_modules/core-js-pure/internals/path.js");e.exports=n.Object.getPrototypeOf},"./node_modules/core-js-pure/es/object/keys.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.object.keys.js");var n=r("./node_modules/core-js-pure/internals/path.js");e.exports=n.Object.keys},"./node_modules/core-js-pure/es/object/set-prototype-of.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.object.set-prototype-of.js");var n=r("./node_modules/core-js-pure/internals/path.js");e.exports=n.Object.setPrototypeOf},"./node_modules/core-js-pure/es/parse-float.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.parse-float.js");var n=r("./node_modules/core-js-pure/internals/path.js");e.exports=n.parseFloat},"./node_modules/core-js-pure/es/parse-int.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.parse-int.js");var n=r("./node_modules/core-js-pure/internals/path.js");e.exports=n.parseInt},"./node_modules/core-js-pure/es/promise/index.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.aggregate-error.js"),r("./node_modules/core-js-pure/modules/es.object.to-string.js"),r("./node_modules/core-js-pure/modules/es.promise.js"),r("./node_modules/core-js-pure/modules/es.promise.all-settled.js"),r("./node_modules/core-js-pure/modules/es.promise.any.js"),r("./node_modules/core-js-pure/modules/es.promise.finally.js"),r("./node_modules/core-js-pure/modules/es.string.iterator.js"),r("./node_modules/core-js-pure/modules/web.dom-collections.iterator.js");var n=r("./node_modules/core-js-pure/internals/path.js");e.exports=n.Promise},"./node_modules/core-js-pure/es/reflect/apply.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.reflect.apply.js");var n=r("./node_modules/core-js-pure/internals/path.js");e.exports=n.Reflect.apply},"./node_modules/core-js-pure/es/reflect/construct.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.reflect.construct.js");var n=r("./node_modules/core-js-pure/internals/path.js");e.exports=n.Reflect.construct},"./node_modules/core-js-pure/es/reflect/delete-property.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.reflect.delete-property.js");var n=r("./node_modules/core-js-pure/internals/path.js");e.exports=n.Reflect.deleteProperty},"./node_modules/core-js-pure/es/reflect/get-prototype-of.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.reflect.get-prototype-of.js");var n=r("./node_modules/core-js-pure/internals/path.js");e.exports=n.Reflect.getPrototypeOf},"./node_modules/core-js-pure/es/reflect/get.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.reflect.get.js");var n=r("./node_modules/core-js-pure/internals/path.js");e.exports=n.Reflect.get},"./node_modules/core-js-pure/es/string/virtual/includes.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.string.includes.js");var n=r("./node_modules/core-js-pure/internals/entry-virtual.js");e.exports=n("String").includes},"./node_modules/core-js-pure/es/string/virtual/starts-with.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.string.starts-with.js");var n=r("./node_modules/core-js-pure/internals/entry-virtual.js");e.exports=n("String").startsWith},"./node_modules/core-js-pure/es/string/virtual/trim.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.string.trim.js");var n=r("./node_modules/core-js-pure/internals/entry-virtual.js");e.exports=n("String").trim},"./node_modules/core-js-pure/es/symbol/index.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.array.concat.js"),r("./node_modules/core-js-pure/modules/es.object.to-string.js"),r("./node_modules/core-js-pure/modules/es.symbol.js"),r("./node_modules/core-js-pure/modules/es.symbol.async-iterator.js"),r("./node_modules/core-js-pure/modules/es.symbol.description.js"),r("./node_modules/core-js-pure/modules/es.symbol.has-instance.js"),r("./node_modules/core-js-pure/modules/es.symbol.is-concat-spreadable.js"),r("./node_modules/core-js-pure/modules/es.symbol.iterator.js"),r("./node_modules/core-js-pure/modules/es.symbol.match.js"),r("./node_modules/core-js-pure/modules/es.symbol.match-all.js"),r("./node_modules/core-js-pure/modules/es.symbol.replace.js"),r("./node_modules/core-js-pure/modules/es.symbol.search.js"),r("./node_modules/core-js-pure/modules/es.symbol.species.js"),r("./node_modules/core-js-pure/modules/es.symbol.split.js"),r("./node_modules/core-js-pure/modules/es.symbol.to-primitive.js"),r("./node_modules/core-js-pure/modules/es.symbol.to-string-tag.js"),r("./node_modules/core-js-pure/modules/es.symbol.unscopables.js"),r("./node_modules/core-js-pure/modules/es.json.to-string-tag.js"),r("./node_modules/core-js-pure/modules/es.math.to-string-tag.js"),r("./node_modules/core-js-pure/modules/es.reflect.to-string-tag.js");var n=r("./node_modules/core-js-pure/internals/path.js");e.exports=n.Symbol},"./node_modules/core-js-pure/es/symbol/iterator.js":function(e,t,r){r("./node_modules/core-js-pure/modules/es.symbol.iterator.js"),r("./node_modules/core-js-pure/modules/es.string.iterator.js"),r("./node_modules/core-js-pure/modules/web.dom-collections.iterator.js");var n=r("./node_modules/core-js-pure/internals/well-known-symbol-wrapped.js");e.exports=n.f("iterator")},"./node_modules/core-js-pure/features/array/from.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/array/from.js");e.exports=n},"./node_modules/core-js-pure/features/array/is-array.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/array/is-array.js");e.exports=n},"./node_modules/core-js-pure/features/get-iterator-method.js":function(e,t,r){r("./node_modules/core-js-pure/modules/web.dom-collections.iterator.js"),r("./node_modules/core-js-pure/modules/es.string.iterator.js");var n=r("./node_modules/core-js-pure/internals/get-iterator-method.js");e.exports=n},"./node_modules/core-js-pure/features/get-iterator.js":function(e,t,r){r("./node_modules/core-js-pure/modules/web.dom-collections.iterator.js"),r("./node_modules/core-js-pure/modules/es.string.iterator.js");var n=r("./node_modules/core-js-pure/internals/get-iterator.js");e.exports=n},"./node_modules/core-js-pure/features/instance/slice.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/instance/slice.js");e.exports=n},"./node_modules/core-js-pure/features/object/create.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/object/create.js");e.exports=n},"./node_modules/core-js-pure/features/object/define-property.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/object/define-property.js");e.exports=n},"./node_modules/core-js-pure/features/object/get-own-property-descriptor.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/object/get-own-property-descriptor.js");e.exports=n},"./node_modules/core-js-pure/features/object/get-prototype-of.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/object/get-prototype-of.js");e.exports=n},"./node_modules/core-js-pure/features/object/set-prototype-of.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/object/set-prototype-of.js");e.exports=n},"./node_modules/core-js-pure/features/promise/index.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/promise/index.js");r("./node_modules/core-js-pure/modules/esnext.aggregate-error.js"),r("./node_modules/core-js-pure/modules/esnext.promise.all-settled.js"),r("./node_modules/core-js-pure/modules/esnext.promise.try.js"),r("./node_modules/core-js-pure/modules/esnext.promise.any.js"),e.exports=n},"./node_modules/core-js-pure/features/reflect/get.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/reflect/get.js");e.exports=n},"./node_modules/core-js-pure/features/symbol/index.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/symbol/index.js");r("./node_modules/core-js-pure/modules/esnext.symbol.async-dispose.js"),r("./node_modules/core-js-pure/modules/esnext.symbol.dispose.js"),r("./node_modules/core-js-pure/modules/esnext.symbol.matcher.js"),r("./node_modules/core-js-pure/modules/esnext.symbol.metadata.js"),r("./node_modules/core-js-pure/modules/esnext.symbol.observable.js"),r("./node_modules/core-js-pure/modules/esnext.symbol.pattern-match.js"),r("./node_modules/core-js-pure/modules/esnext.symbol.replace-all.js"),e.exports=n},"./node_modules/core-js-pure/features/symbol/iterator.js":function(e,t,r){var n=r("./node_modules/core-js-pure/es/symbol/iterator.js");e.exports=n},"./node_modules/core-js-pure/internals/a-function.js":function(e){e.exports=function(e){if("function"!=typeof e)throw TypeError(String(e)+" is not a function");return e}},"./node_modules/core-js-pure/internals/a-possible-prototype.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/is-object.js");e.exports=function(e){if(!n(e)&&null!==e)throw TypeError("Can't set "+String(e)+" as a prototype");return e}},"./node_modules/core-js-pure/internals/add-to-unscopables.js":function(e){e.exports=function(){}},"./node_modules/core-js-pure/internals/an-instance.js":function(e){e.exports=function(e,t,r){if(!(e instanceof t))throw TypeError("Incorrect "+(r?r+" ":"")+"invocation");return e}},"./node_modules/core-js-pure/internals/an-object.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/is-object.js");e.exports=function(e){if(!n(e))throw TypeError(String(e)+" is not an object");return e}},"./node_modules/core-js-pure/internals/array-fill.js":function(e,t,r){"use strict";var n=r("./node_modules/core-js-pure/internals/to-object.js"),o=r("./node_modules/core-js-pure/internals/to-absolute-index.js"),s=r("./node_modules/core-js-pure/internals/to-length.js");e.exports=function(e){for(var t=n(this),r=s(t.length),i=arguments.length,a=o(i>1?arguments[1]:void 0,r),A=i>2?arguments[2]:void 0,u=void 0===A?r:o(A,r);u>a;)t[a++]=e;return t}},"./node_modules/core-js-pure/internals/array-for-each.js":function(e,t,r){"use strict";var n=r("./node_modules/core-js-pure/internals/array-iteration.js").forEach,o=r("./node_modules/core-js-pure/internals/array-method-is-strict.js")("forEach");e.exports=o?[].forEach:function(e){return n(this,e,arguments.length>1?arguments[1]:void 0)}},"./node_modules/core-js-pure/internals/array-from.js":function(e,t,r){"use strict";var n=r("./node_modules/core-js-pure/internals/function-bind-context.js"),o=r("./node_modules/core-js-pure/internals/to-object.js"),s=r("./node_modules/core-js-pure/internals/call-with-safe-iteration-closing.js"),i=r("./node_modules/core-js-pure/internals/is-array-iterator-method.js"),a=r("./node_modules/core-js-pure/internals/to-length.js"),A=r("./node_modules/core-js-pure/internals/create-property.js"),u=r("./node_modules/core-js-pure/internals/get-iterator-method.js");e.exports=function(e){var t,r,c,l,d,f,h=o(e),p="function"==typeof this?this:Array,m=arguments.length,g=m>1?arguments[1]:void 0,y=void 0!==g,v=u(h),w=0;if(y&&(g=n(g,m>2?arguments[2]:void 0,2)),null==v||p==Array&&i(v))for(r=new p(t=a(h.length));t>w;w++)f=y?g(h[w],w):h[w],A(r,w,f);else for(d=(l=v.call(h)).next,r=new p;!(c=d.call(l)).done;w++)f=y?s(l,g,[c.value,w],!0):c.value,A(r,w,f);return r.length=w,r}},"./node_modules/core-js-pure/internals/array-includes.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/to-indexed-object.js"),o=r("./node_modules/core-js-pure/internals/to-length.js"),s=r("./node_modules/core-js-pure/internals/to-absolute-index.js"),i=function(e){return function(t,r,i){var a,A=n(t),u=o(A.length),c=s(i,u);if(e&&r!=r){for(;u>c;)if((a=A[c++])!=a)return!0}else for(;u>c;c++)if((e||c in A)&&A[c]===r)return e||c||0;return!e&&-1}};e.exports={includes:i(!0),indexOf:i(!1)}},"./node_modules/core-js-pure/internals/array-iteration.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/function-bind-context.js"),o=r("./node_modules/core-js-pure/internals/indexed-object.js"),s=r("./node_modules/core-js-pure/internals/to-object.js"),i=r("./node_modules/core-js-pure/internals/to-length.js"),a=r("./node_modules/core-js-pure/internals/array-species-create.js"),A=[].push,u=function(e){var t=1==e,r=2==e,u=3==e,c=4==e,l=6==e,d=7==e,f=5==e||l;return function(h,p,m,g){for(var y,v,w=s(h),b=o(w),B=n(p,m,3),j=i(b.length),_=0,C=g||a,x=t?C(h,j):r||d?C(h,0):void 0;j>_;_++)if((f||_ in b)&&(v=B(y=b[_],_,w),e))if(t)x[_]=v;else if(v)switch(e){case 3:return!0;case 5:return y;case 6:return _;case 2:A.call(x,y)}else switch(e){case 4:return!1;case 7:A.call(x,y)}return l?-1:u||c?c:x}};e.exports={forEach:u(0),map:u(1),filter:u(2),some:u(3),every:u(4),find:u(5),findIndex:u(6),filterOut:u(7)}},"./node_modules/core-js-pure/internals/array-method-has-species-support.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/fails.js"),o=r("./node_modules/core-js-pure/internals/well-known-symbol.js"),s=r("./node_modules/core-js-pure/internals/engine-v8-version.js"),i=o("species");e.exports=function(e){return s>=51||!n((function(){var t=[];return(t.constructor={})[i]=function(){return{foo:1}},1!==t[e](Boolean).foo}))}},"./node_modules/core-js-pure/internals/array-method-is-strict.js":function(e,t,r){"use strict";var n=r("./node_modules/core-js-pure/internals/fails.js");e.exports=function(e,t){var r=[][e];return!!r&&n((function(){r.call(null,t||function(){throw 1},1)}))}},"./node_modules/core-js-pure/internals/array-reduce.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/a-function.js"),o=r("./node_modules/core-js-pure/internals/to-object.js"),s=r("./node_modules/core-js-pure/internals/indexed-object.js"),i=r("./node_modules/core-js-pure/internals/to-length.js"),a=function(e){return function(t,r,a,A){n(r);var u=o(t),c=s(u),l=i(u.length),d=e?l-1:0,f=e?-1:1;if(a<2)for(;;){if(d in c){A=c[d],d+=f;break}if(d+=f,e?d<0:l<=d)throw TypeError("Reduce of empty array with no initial value")}for(;e?d>=0:l>d;d+=f)d in c&&(A=r(A,c[d],d,u));return A}};e.exports={left:a(!1),right:a(!0)}},"./node_modules/core-js-pure/internals/array-species-create.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/is-object.js"),o=r("./node_modules/core-js-pure/internals/is-array.js"),s=r("./node_modules/core-js-pure/internals/well-known-symbol.js")("species");e.exports=function(e,t){var r;return o(e)&&("function"!=typeof(r=e.constructor)||r!==Array&&!o(r.prototype)?n(r)&&null===(r=r[s])&&(r=void 0):r=void 0),new(void 0===r?Array:r)(0===t?0:t)}},"./node_modules/core-js-pure/internals/call-with-safe-iteration-closing.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/an-object.js"),o=r("./node_modules/core-js-pure/internals/iterator-close.js");e.exports=function(e,t,r,s){try{return s?t(n(r)[0],r[1]):t(r)}catch(t){throw o(e),t}}},"./node_modules/core-js-pure/internals/check-correctness-of-iteration.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/well-known-symbol.js")("iterator"),o=!1;try{var s=0,i={next:function(){return{done:!!s++}},return:function(){o=!0}};i[n]=function(){return this},Array.from(i,(function(){throw 2}))}catch(e){}e.exports=function(e,t){if(!t&&!o)return!1;var r=!1;try{var s={};s[n]=function(){return{next:function(){return{done:r=!0}}}},e(s)}catch(e){}return r}},"./node_modules/core-js-pure/internals/classof-raw.js":function(e){var t={}.toString;e.exports=function(e){return t.call(e).slice(8,-1)}},"./node_modules/core-js-pure/internals/classof.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/to-string-tag-support.js"),o=r("./node_modules/core-js-pure/internals/classof-raw.js"),s=r("./node_modules/core-js-pure/internals/well-known-symbol.js")("toStringTag"),i="Arguments"==o(function(){return arguments}());e.exports=n?o:function(e){var t,r,n;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(r=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),s))?r:i?o(t):"Object"==(n=o(t))&&"function"==typeof t.callee?"Arguments":n}},"./node_modules/core-js-pure/internals/collection-strong.js":function(e,t,r){"use strict";var n=r("./node_modules/core-js-pure/internals/object-define-property.js").f,o=r("./node_modules/core-js-pure/internals/object-create.js"),s=r("./node_modules/core-js-pure/internals/redefine-all.js"),i=r("./node_modules/core-js-pure/internals/function-bind-context.js"),a=r("./node_modules/core-js-pure/internals/an-instance.js"),A=r("./node_modules/core-js-pure/internals/iterate.js"),u=r("./node_modules/core-js-pure/internals/define-iterator.js"),c=r("./node_modules/core-js-pure/internals/set-species.js"),l=r("./node_modules/core-js-pure/internals/descriptors.js"),d=r("./node_modules/core-js-pure/internals/internal-metadata.js").fastKey,f=r("./node_modules/core-js-pure/internals/internal-state.js"),h=f.set,p=f.getterFor;e.exports={getConstructor:function(e,t,r,u){var c=e((function(e,n){a(e,c,t),h(e,{type:t,index:o(null),first:void 0,last:void 0,size:0}),l||(e.size=0),null!=n&&A(n,e[u],{that:e,AS_ENTRIES:r})})),f=p(t),m=function(e,t,r){var n,o,s=f(e),i=g(e,t);return i?i.value=r:(s.last=i={index:o=d(t,!0),key:t,value:r,previous:n=s.last,next:void 0,removed:!1},s.first||(s.first=i),n&&(n.next=i),l?s.size++:e.size++,"F"!==o&&(s.index[o]=i)),e},g=function(e,t){var r,n=f(e),o=d(t);if("F"!==o)return n.index[o];for(r=n.first;r;r=r.next)if(r.key==t)return r};return s(c.prototype,{clear:function(){for(var e=f(this),t=e.index,r=e.first;r;)r.removed=!0,r.previous&&(r.previous=r.previous.next=void 0),delete t[r.index],r=r.next;e.first=e.last=void 0,l?e.size=0:this.size=0},delete:function(e){var t=this,r=f(t),n=g(t,e);if(n){var o=n.next,s=n.previous;delete r.index[n.index],n.removed=!0,s&&(s.next=o),o&&(o.previous=s),r.first==n&&(r.first=o),r.last==n&&(r.last=s),l?r.size--:t.size--}return!!n},forEach:function(e){for(var t,r=f(this),n=i(e,arguments.length>1?arguments[1]:void 0,3);t=t?t.next:r.first;)for(n(t.value,t.key,this);t&&t.removed;)t=t.previous},has:function(e){return!!g(this,e)}}),s(c.prototype,r?{get:function(e){var t=g(this,e);return t&&t.value},set:function(e,t){return m(this,0===e?0:e,t)}}:{add:function(e){return m(this,e=0===e?0:e,e)}}),l&&n(c.prototype,"size",{get:function(){return f(this).size}}),c},setStrong:function(e,t,r){var n=t+" Iterator",o=p(t),s=p(n);u(e,t,(function(e,t){h(this,{type:n,target:e,state:o(e),kind:t,last:void 0})}),(function(){for(var e=s(this),t=e.kind,r=e.last;r&&r.removed;)r=r.previous;return e.target&&(e.last=r=r?r.next:e.state.first)?"keys"==t?{value:r.key,done:!1}:"values"==t?{value:r.value,done:!1}:{value:[r.key,r.value],done:!1}:(e.target=void 0,{value:void 0,done:!0})}),r?"entries":"values",!r,!0),c(t)}}},"./node_modules/core-js-pure/internals/collection.js":function(e,t,r){"use strict";var n=r("./node_modules/core-js-pure/internals/export.js"),o=r("./node_modules/core-js-pure/internals/global.js"),s=r("./node_modules/core-js-pure/internals/internal-metadata.js"),i=r("./node_modules/core-js-pure/internals/fails.js"),a=r("./node_modules/core-js-pure/internals/create-non-enumerable-property.js"),A=r("./node_modules/core-js-pure/internals/iterate.js"),u=r("./node_modules/core-js-pure/internals/an-instance.js"),c=r("./node_modules/core-js-pure/internals/is-object.js"),l=r("./node_modules/core-js-pure/internals/set-to-string-tag.js"),d=r("./node_modules/core-js-pure/internals/object-define-property.js").f,f=r("./node_modules/core-js-pure/internals/array-iteration.js").forEach,h=r("./node_modules/core-js-pure/internals/descriptors.js"),p=r("./node_modules/core-js-pure/internals/internal-state.js"),m=p.set,g=p.getterFor;e.exports=function(e,t,r){var p,y=-1!==e.indexOf("Map"),v=-1!==e.indexOf("Weak"),w=y?"set":"add",b=o[e],B=b&&b.prototype,j={};if(h&&"function"==typeof b&&(v||B.forEach&&!i((function(){(new b).entries().next()})))){p=t((function(t,r){m(u(t,p,e),{type:e,collection:new b}),null!=r&&A(r,t[w],{that:t,AS_ENTRIES:y})}));var _=g(e);f(["add","clear","delete","forEach","get","has","set","keys","values","entries"],(function(e){var t="add"==e||"set"==e;!(e in B)||v&&"clear"==e||a(p.prototype,e,(function(r,n){var o=_(this).collection;if(!t&&v&&!c(r))return"get"==e&&void 0;var s=o[e](0===r?0:r,n);return t?this:s}))})),v||d(p.prototype,"size",{configurable:!0,get:function(){return _(this).collection.size}})}else p=r.getConstructor(t,e,y,w),s.REQUIRED=!0;return l(p,e,!1,!0),j[e]=p,n({global:!0,forced:!0},j),v||r.setStrong(p,e,y),p}},"./node_modules/core-js-pure/internals/correct-is-regexp-logic.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/well-known-symbol.js")("match");e.exports=function(e){var t=/./;try{"/./"[e](t)}catch(r){try{return t[n]=!1,"/./"[e](t)}catch(e){}}return!1}},"./node_modules/core-js-pure/internals/correct-prototype-getter.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/fails.js");e.exports=!n((function(){function e(){}return e.prototype.constructor=null,Object.getPrototypeOf(new e)!==e.prototype}))},"./node_modules/core-js-pure/internals/create-iterator-constructor.js":function(e,t,r){"use strict";var n=r("./node_modules/core-js-pure/internals/iterators-core.js").IteratorPrototype,o=r("./node_modules/core-js-pure/internals/object-create.js"),s=r("./node_modules/core-js-pure/internals/create-property-descriptor.js"),i=r("./node_modules/core-js-pure/internals/set-to-string-tag.js"),a=r("./node_modules/core-js-pure/internals/iterators.js"),A=function(){return this};e.exports=function(e,t,r){var u=t+" Iterator";return e.prototype=o(n,{next:s(1,r)}),i(e,u,!1,!0),a[u]=A,e}},"./node_modules/core-js-pure/internals/create-non-enumerable-property.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/descriptors.js"),o=r("./node_modules/core-js-pure/internals/object-define-property.js"),s=r("./node_modules/core-js-pure/internals/create-property-descriptor.js");e.exports=n?function(e,t,r){return o.f(e,t,s(1,r))}:function(e,t,r){return e[t]=r,e}},"./node_modules/core-js-pure/internals/create-property-descriptor.js":function(e){e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},"./node_modules/core-js-pure/internals/create-property.js":function(e,t,r){"use strict";var n=r("./node_modules/core-js-pure/internals/to-primitive.js"),o=r("./node_modules/core-js-pure/internals/object-define-property.js"),s=r("./node_modules/core-js-pure/internals/create-property-descriptor.js");e.exports=function(e,t,r){var i=n(t);i in e?o.f(e,i,s(0,r)):e[i]=r}},"./node_modules/core-js-pure/internals/define-iterator.js":function(e,t,r){"use strict";var n=r("./node_modules/core-js-pure/internals/export.js"),o=r("./node_modules/core-js-pure/internals/create-iterator-constructor.js"),s=r("./node_modules/core-js-pure/internals/object-get-prototype-of.js"),i=r("./node_modules/core-js-pure/internals/object-set-prototype-of.js"),a=r("./node_modules/core-js-pure/internals/set-to-string-tag.js"),A=r("./node_modules/core-js-pure/internals/create-non-enumerable-property.js"),u=r("./node_modules/core-js-pure/internals/redefine.js"),c=r("./node_modules/core-js-pure/internals/well-known-symbol.js"),l=r("./node_modules/core-js-pure/internals/is-pure.js"),d=r("./node_modules/core-js-pure/internals/iterators.js"),f=r("./node_modules/core-js-pure/internals/iterators-core.js"),h=f.IteratorPrototype,p=f.BUGGY_SAFARI_ITERATORS,m=c("iterator"),g="keys",y="values",v="entries",w=function(){return this};e.exports=function(e,t,r,c,f,b,B){o(r,t,c);var j,_,C,x=function(e){if(e===f&&U)return U;if(!p&&e in Q)return Q[e];switch(e){case g:case y:case v:return function(){return new r(this,e)}}return function(){return new r(this)}},E=t+" Iterator",N=!1,Q=e.prototype,F=Q[m]||Q["@@iterator"]||f&&Q[f],U=!p&&F||x(f),S="Array"==t&&Q.entries||F;if(S&&(j=s(S.call(new e)),h!==Object.prototype&&j.next&&(l||s(j)===h||(i?i(j,h):"function"!=typeof j[m]&&A(j,m,w)),a(j,E,!0,!0),l&&(d[E]=w))),f==y&&F&&F.name!==y&&(N=!0,U=function(){return F.call(this)}),l&&!B||Q[m]===U||A(Q,m,U),d[t]=U,f)if(_={values:x(y),keys:b?U:x(g),entries:x(v)},B)for(C in _)(p||N||!(C in Q))&&u(Q,C,_[C]);else n({target:t,proto:!0,forced:p||N},_);return _}},"./node_modules/core-js-pure/internals/define-well-known-symbol.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/path.js"),o=r("./node_modules/core-js-pure/internals/has.js"),s=r("./node_modules/core-js-pure/internals/well-known-symbol-wrapped.js"),i=r("./node_modules/core-js-pure/internals/object-define-property.js").f;e.exports=function(e){var t=n.Symbol||(n.Symbol={});o(t,e)||i(t,e,{value:s.f(e)})}},"./node_modules/core-js-pure/internals/descriptors.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/fails.js");e.exports=!n((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},"./node_modules/core-js-pure/internals/document-create-element.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/global.js"),o=r("./node_modules/core-js-pure/internals/is-object.js"),s=n.document,i=o(s)&&o(s.createElement);e.exports=function(e){return i?s.createElement(e):{}}},"./node_modules/core-js-pure/internals/dom-iterables.js":function(e){e.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},"./node_modules/core-js-pure/internals/engine-is-browser.js":function(e){e.exports="object"==typeof window},"./node_modules/core-js-pure/internals/engine-is-ios.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/engine-user-agent.js");e.exports=/(?:iphone|ipod|ipad).*applewebkit/i.test(n)},"./node_modules/core-js-pure/internals/engine-is-node.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/classof-raw.js"),o=r("./node_modules/core-js-pure/internals/global.js");e.exports="process"==n(o.process)},"./node_modules/core-js-pure/internals/engine-is-webos-webkit.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/engine-user-agent.js");e.exports=/web0s(?!.*chrome)/i.test(n)},"./node_modules/core-js-pure/internals/engine-user-agent.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/get-built-in.js");e.exports=n("navigator","userAgent")||""},"./node_modules/core-js-pure/internals/engine-v8-version.js":function(e,t,r){var n,o,s=r("./node_modules/core-js-pure/internals/global.js"),i=r("./node_modules/core-js-pure/internals/engine-user-agent.js"),a=s.process,A=a&&a.versions,u=A&&A.v8;u?o=(n=u.split("."))[0]<4?1:n[0]+n[1]:i&&(!(n=i.match(/Edge\/(\d+)/))||n[1]>=74)&&(n=i.match(/Chrome\/(\d+)/))&&(o=n[1]),e.exports=o&&+o},"./node_modules/core-js-pure/internals/entry-virtual.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/path.js");e.exports=function(e){return n[e+"Prototype"]}},"./node_modules/core-js-pure/internals/enum-bug-keys.js":function(e){e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},"./node_modules/core-js-pure/internals/export.js":function(e,t,r){"use strict";var n=r("./node_modules/core-js-pure/internals/global.js"),o=r("./node_modules/core-js-pure/internals/object-get-own-property-descriptor.js").f,s=r("./node_modules/core-js-pure/internals/is-forced.js"),i=r("./node_modules/core-js-pure/internals/path.js"),a=r("./node_modules/core-js-pure/internals/function-bind-context.js"),A=r("./node_modules/core-js-pure/internals/create-non-enumerable-property.js"),u=r("./node_modules/core-js-pure/internals/has.js"),c=function(e){var t=function(t,r,n){if(this instanceof e){switch(arguments.length){case 0:return new e;case 1:return new e(t);case 2:return new e(t,r)}return new e(t,r,n)}return e.apply(this,arguments)};return t.prototype=e.prototype,t};e.exports=function(e,t){var r,l,d,f,h,p,m,g,y=e.target,v=e.global,w=e.stat,b=e.proto,B=v?n:w?n[y]:(n[y]||{}).prototype,j=v?i:i[y]||(i[y]={}),_=j.prototype;for(d in t)r=!s(v?d:y+(w?".":"#")+d,e.forced)&&B&&u(B,d),h=j[d],r&&(p=e.noTargetGet?(g=o(B,d))&&g.value:B[d]),f=r&&p?p:t[d],r&&typeof h==typeof f||(m=e.bind&&r?a(f,n):e.wrap&&r?c(f):b&&"function"==typeof f?a(Function.call,f):f,(e.sham||f&&f.sham||h&&h.sham)&&A(m,"sham",!0),j[d]=m,b&&(u(i,l=y+"Prototype")||A(i,l,{}),i[l][d]=f,e.real&&_&&!_[d]&&A(_,d,f)))}},"./node_modules/core-js-pure/internals/fails.js":function(e){e.exports=function(e){try{return!!e()}catch(e){return!0}}},"./node_modules/core-js-pure/internals/freezing.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/fails.js");e.exports=!n((function(){return Object.isExtensible(Object.preventExtensions({}))}))},"./node_modules/core-js-pure/internals/function-bind-context.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/a-function.js");e.exports=function(e,t,r){if(n(e),void 0===t)return e;switch(r){case 0:return function(){return e.call(t)};case 1:return function(r){return e.call(t,r)};case 2:return function(r,n){return e.call(t,r,n)};case 3:return function(r,n,o){return e.call(t,r,n,o)}}return function(){return e.apply(t,arguments)}}},"./node_modules/core-js-pure/internals/function-bind.js":function(e,t,r){"use strict";var n=r("./node_modules/core-js-pure/internals/a-function.js"),o=r("./node_modules/core-js-pure/internals/is-object.js"),s=[].slice,i={},a=function(e,t,r){if(!(t in i)){for(var n=[],o=0;od;d++)if((h=j(e[d]))&&h instanceof u)return h;return new u(!1)}c=l.call(e)}for(p=c.next;!(m=p.call(c)).done;){try{h=j(m.value)}catch(e){throw A(c),e}if("object"==typeof h&&h&&h instanceof u)return h}return new u(!1)}},"./node_modules/core-js-pure/internals/iterator-close.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/an-object.js");e.exports=function(e){var t=e.return;if(void 0!==t)return n(t.call(e)).value}},"./node_modules/core-js-pure/internals/iterators-core.js":function(e,t,r){"use strict";var n,o,s,i=r("./node_modules/core-js-pure/internals/fails.js"),a=r("./node_modules/core-js-pure/internals/object-get-prototype-of.js"),A=r("./node_modules/core-js-pure/internals/create-non-enumerable-property.js"),u=r("./node_modules/core-js-pure/internals/has.js"),c=r("./node_modules/core-js-pure/internals/well-known-symbol.js"),l=r("./node_modules/core-js-pure/internals/is-pure.js"),d=c("iterator"),f=!1;[].keys&&("next"in(s=[].keys())?(o=a(a(s)))!==Object.prototype&&(n=o):f=!0);var h=null==n||i((function(){var e={};return n[d].call(e)!==e}));h&&(n={}),l&&!h||u(n,d)||A(n,d,(function(){return this})),e.exports={IteratorPrototype:n,BUGGY_SAFARI_ITERATORS:f}},"./node_modules/core-js-pure/internals/iterators.js":function(e){e.exports={}},"./node_modules/core-js-pure/internals/microtask.js":function(e,t,r){var n,o,s,i,a,A,u,c,l=r("./node_modules/core-js-pure/internals/global.js"),d=r("./node_modules/core-js-pure/internals/object-get-own-property-descriptor.js").f,f=r("./node_modules/core-js-pure/internals/task.js").set,h=r("./node_modules/core-js-pure/internals/engine-is-ios.js"),p=r("./node_modules/core-js-pure/internals/engine-is-webos-webkit.js"),m=r("./node_modules/core-js-pure/internals/engine-is-node.js"),g=l.MutationObserver||l.WebKitMutationObserver,y=l.document,v=l.process,w=l.Promise,b=d(l,"queueMicrotask"),B=b&&b.value;B||(n=function(){var e,t;for(m&&(e=v.domain)&&e.exit();o;){t=o.fn,o=o.next;try{t()}catch(e){throw o?i():s=void 0,e}}s=void 0,e&&e.enter()},h||m||p||!g||!y?w&&w.resolve?((u=w.resolve(void 0)).constructor=w,c=u.then,i=function(){c.call(u,n)}):i=m?function(){v.nextTick(n)}:function(){f.call(l,n)}:(a=!0,A=y.createTextNode(""),new g(n).observe(A,{characterData:!0}),i=function(){A.data=a=!a})),e.exports=B||function(e){var t={fn:e,next:void 0};s&&(s.next=t),o||(o=t,i()),s=t}},"./node_modules/core-js-pure/internals/native-promise-constructor.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/global.js");e.exports=n.Promise},"./node_modules/core-js-pure/internals/native-symbol.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/engine-v8-version.js"),o=r("./node_modules/core-js-pure/internals/fails.js");e.exports=!!Object.getOwnPropertySymbols&&!o((function(){var e=Symbol();return!String(e)||!(Object(e)instanceof Symbol)||!Symbol.sham&&n&&n<41}))},"./node_modules/core-js-pure/internals/native-weak-map.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/global.js"),o=r("./node_modules/core-js-pure/internals/inspect-source.js"),s=n.WeakMap;e.exports="function"==typeof s&&/native code/.test(o(s))},"./node_modules/core-js-pure/internals/new-promise-capability.js":function(e,t,r){"use strict";var n=r("./node_modules/core-js-pure/internals/a-function.js"),o=function(e){var t,r;this.promise=new e((function(e,n){if(void 0!==t||void 0!==r)throw TypeError("Bad Promise constructor");t=e,r=n})),this.resolve=n(t),this.reject=n(r)};e.exports.f=function(e){return new o(e)}},"./node_modules/core-js-pure/internals/not-a-regexp.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/is-regexp.js");e.exports=function(e){if(n(e))throw TypeError("The method doesn't accept regular expressions");return e}},"./node_modules/core-js-pure/internals/number-parse-float.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/global.js"),o=r("./node_modules/core-js-pure/internals/string-trim.js").trim,s=r("./node_modules/core-js-pure/internals/whitespaces.js"),i=n.parseFloat,a=1/i(s+"-0")!=-1/0;e.exports=a?function(e){var t=o(String(e)),r=i(t);return 0===r&&"-"==t.charAt(0)?-0:r}:i},"./node_modules/core-js-pure/internals/number-parse-int.js":function(e,t,r){var n=r("./node_modules/core-js-pure/internals/global.js"),o=r("./node_modules/core-js-pure/internals/string-trim.js").trim,s=r("./node_modules/core-js-pure/internals/whitespaces.js"),i=n.parseInt,a=/^[+-]?0[Xx]/,A=8!==i(s+"08")||22!==i(s+"0x16");e.exports=A?function(e,t){var r=o(String(e));return i(r,t>>>0||(a.test(r)?16:10))}:i},"./node_modules/core-js-pure/internals/object-create.js":function(e,t,r){var n,o=r("./node_modules/core-js-pure/internals/an-object.js"),s=r("./node_modules/core-js-pure/internals/object-define-properties.js"),i=r("./node_modules/core-js-pure/internals/enum-bug-keys.js"),a=r("./node_modules/core-js-pure/internals/hidden-keys.js"),A=r("./node_modules/core-js-pure/internals/html.js"),u=r("./node_modules/core-js-pure/internals/document-create-element.js"),c=r("./node_modules/core-js-pure/internals/shared-key.js")("IE_PROTO"),l=function(){},d=function(e){return"\n + \n + \n" + ) + .to_string() +} + +fn online_js_cdn() -> String { + // tex-mml-chtml conflicts with tex-svg when generating Latex Titles + r##" + + + + "## + .to_string() +} diff --git a/plotly_static/src/webdriver.rs b/plotly_static/src/webdriver.rs new file mode 100644 index 00000000..095c6e0e --- /dev/null +++ b/plotly_static/src/webdriver.rs @@ -0,0 +1,669 @@ +//! WebDriver management module for plotly_static. +//! +//! This module provides WebDriver process management, including: +//! - Automatic detection of existing WebDriver sessions +//! - Process spawning and lifecycle management +//! - Connection reuse and cleanup +//! - Support for both spawned and external WebDriver instances +//! +//! The module is designed to work safely in parallel environments. +use std::io::prelude::*; +use std::io::BufReader; +use std::path::PathBuf; +use std::process::{Child, Command, Stdio}; +use std::sync::{Arc, Mutex}; +use std::thread; +#[cfg(any(test, feature = "debug"))] +use std::{println as info, println as error, println as debug, println as warn, println as trace}; + +use anyhow::{anyhow, Result}; +#[cfg(not(any(test, feature = "debug")))] +use log::{debug, error, info, trace, warn}; + +/// Environment variable for specifying the WebDriver binary path +const WEBDRIVER_PATH_ENV: &str = "WEBDRIVER_PATH"; + +#[cfg(feature = "geckodriver")] +const WEBDRIVER_BIN: &str = "geckodriver"; + +#[cfg(feature = "chromedriver")] +const WEBDRIVER_BIN: &str = "chromedriver"; + +/// Default WebDriver port +pub(crate) const WEBDRIVER_PORT: u32 = 4444; +/// Default WebDriver URL +pub(crate) const WEBDRIVER_URL: &str = "http://localhost"; + +#[cfg(all(feature = "chromedriver", not(target_os = "windows")))] +pub(crate) fn chrome_default_caps() -> Vec<&'static str> { + vec![ + "--headless", + "--no-sandbox", + "--disable-gpu-sandbox", + "--disable-dev-shm-usage", + "--disable-extensions", + "--disable-background-networking", + "--disable-sync", + "--disable-translate", + "--disable-background-timer-throttling", + "--disable-renderer-backgrounding", + "--disable-features=VizDisplayCompositor", + "--memory-pressure-off", + // macOS-specific flags from choreographer + "--enable-unsafe-swiftshader", + "--use-mock-keychain", + "--password-store=basic", + "--disable-web-security", + "--disable-breakpad", + "--no-first-run", + "--no-default-browser-check", + // Additional flags for better PDF generation support + "--disable-backgrounding-occluded-windows", + "--disable-ipc-flooding-protection", + "--enable-logging", + "--v=1", + ] +} + +#[cfg(all(feature = "chromedriver", target_os = "windows"))] +pub(crate) fn chrome_default_caps() -> Vec<&'static str> { + vec![ + "--headless=new", + "--no-sandbox", + "--disable-dev-shm-usage", + "--disable-breakpad", + "--no-first-run", + "--no-default-browser-check", + // Stability flags to prevent renderer crashes + "--disable-background-networking", + "--disable-sync", + "--disable-translate", + "--disable-background-timer-throttling", + "--disable-renderer-backgrounding", + "--disable-backgrounding-occluded-windows", + "--disable-ipc-flooding-protection", + "--disable-extensions", + // Minimal flags for Windows headless operation without disabling GPU + "--hide-scrollbars", + "--mute-audio", + "--use-angle=swiftshader", + "--disable-software-rasterizer", + ] +} + +#[cfg(feature = "geckodriver")] +pub(crate) fn firefox_default_caps() -> Vec<&'static str> { + vec![ + "-headless", // Essential for headless operation (single dash for Firefox) + "--no-remote", // Prevents connecting to existing Firefox instances + ] +} + +/// Internal WebDriver state +#[derive(Debug)] +struct WdInner { + webdriver_port: u32, + driver_path: Option, + webdriver_child: Option, + is_external: bool, /* Marker for whether this WebDriver was spawned by us or connected to + * existing */ +} + +/// WebDriver management struct +/// +/// This struct provides WebDriver process management with the following +/// features: +/// - Automatic detection of existing WebDriver sessions +/// - Process spawning and lifecycle management +/// - Connection reuse and cleanup +/// - Support for both spawned and external WebDriver instances +/// - Thread-safe operations using Arc> for internal state +#[derive(Debug)] +pub struct WebDriver { + inner: Arc>, +} + +impl WebDriver { + /// Creates a new WebDriver instance for spawning. + /// + /// This method creates a WebDriver instance that will later spawn a new + /// process. It looks for the WebDriver binary in the following order: + /// 1. `WEBDRIVER_PATH` environment variable (should point to full + /// executable path) + /// 2. Build-time downloaded path (if `webdriver_download` feature is + /// enabled) + /// + /// Returns a `Result` where: + /// - `Ok(webdriver)` - Successfully created the WebDriver instance + /// - `Err(e)` - Failed to create the instance (e.g., binary not found) + pub(crate) fn new(port: u32) -> Result { + let full_path = Self::get_webdriver_path()?; + + Ok(Self { + inner: Arc::new(Mutex::new(WdInner { + webdriver_port: port, + driver_path: Some(full_path), + webdriver_child: None, + is_external: false, // mark it as spawned by us + })), + }) + } + + /// Spawn a new WebDriver instance, connecting to existing if available. + /// + /// This method provides WebDriver management: + /// 1. First checks if a WebDriver is already running on the specified port + /// 2. If found, connects to the existing session + /// 3. If not found, spawns a process of the current WebDriver instance + /// + /// This approach allows for efficient resource usage and supports both + /// scenarios where WebDriver is managed externally or needs to be spawned. + /// + /// Returns a `Result` where: + /// - `Ok(webdriver)` - Successfully created or connected to WebDriver + /// - `Err(e)` - Failed to create or connect to WebDriver + pub(crate) fn connect_or_spawn(port: u32) -> Result { + match Self::try_connect(port) { + Some(active_instance) => Ok(active_instance), + None => Self::spawn(port) + .map_err(|e| anyhow!("Failed to spawn new WebDriver on port {}: {}", port, e)), + } + } + + /// Try connecting to an existing WebDriver instance + pub(crate) fn try_connect(port: u32) -> Option { + if !Self::is_webdriver_running(port) { + return None; + } + info!("WebDriver already running on port {port}, connecting to existing session"); + Self::no_spawn_instance(port).ok() + } + + /// Create a new WebDriver instance without a path to the webdriver binary, + /// as it is used for connecting to an existing WebDriver session and not + /// spawning a new process. + pub(crate) fn no_spawn_instance(port: u32) -> Result { + Ok(Self { + inner: Arc::new(Mutex::new(WdInner { + webdriver_port: port, + driver_path: None, + webdriver_child: None, + is_external: true, // Mark as external since we didn't spawn it + })), + }) + } + + /// Spawns the WebDriver process + /// + /// This method starts the WebDriver process in a background thread and + /// captures its output for logging. The process is spawned with the + /// specified port and appropriate I/O redirection. + /// + /// The spawned process will be automatically managed and can be stopped + /// using the `stop()` method. + pub(crate) fn spawn(port: u32) -> Result { + debug!("No WebDriver running on port {port}, creating new instance and spawning"); + + let mut wd = Self::new(port)?; + wd.spawn_webdriver()?; + + if Self::is_webdriver_running(port) { + info!("Successfully created and started WebDriver on port {port}"); + Ok(wd) + } else { + let diagnostics = wd.get_diagnostics(); + error!( + "WebDriver failed to start properly on port {port}. Diagnostics:\n{diagnostics}" + ); + Err(anyhow!( + "WebDriver failed to start properly on port {}", + port + )) + } + } + + pub(crate) fn spawn_webdriver(&mut self) -> Result<()> { + let port = self.inner.lock().unwrap().webdriver_port; + let driver_path = self.inner.lock().unwrap().driver_path.clone(); + + info!("Spawning {WEBDRIVER_BIN} on port {port} with path: {driver_path:?}"); + + if Self::is_webdriver_running(port) { + warn!("WebDriver already running on port {port}, attempting to connect instead"); + return Ok(()); + } + + self.validate_spawn_prerequisites()?; + + let mut child = self.spawn_process(&driver_path, port)?; + + self.setup_output_monitoring(&mut child, port); + + self.store_child_process(child); + + self.wait_for_ready(port) + } + + fn validate_spawn_prerequisites(&self) -> Result<()> { + let inner = self + .inner + .lock() + .map_err(|e| anyhow!("Failed to acquire lock: {}", e))?; + + // Check if driver path exists + let driver_path = inner.driver_path.as_ref().ok_or_else(|| { + error!( + "WebDriver diagnostics after missing driver path:\n{}", + self.get_diagnostics() + ); + anyhow!("No driver path available for spawning") + })?; + + // Check if binary exists + if !driver_path.exists() { + error!( + "WebDriver diagnostics after missing binary:\n{}", + self.get_diagnostics() + ); + return Err(anyhow!("WebDriver binary does not exist: {driver_path:?}")); + } + + Ok(()) + } + + fn spawn_process(&self, driver_path: &Option, port: u32) -> Result { + let driver_path = driver_path.as_ref().unwrap(); // Safe unwrap since we validated above + + let mut command = Self::create_command(driver_path, port); + Self::log_command(&command); + + match command.spawn() { + Ok(child) => Ok(child), + Err(e) => { + #[cfg(not(target_os = "windows"))] + { + Err(self.handle_spawn_error(e, &command, "standard method")) + } + #[cfg(target_os = "windows")] + { + self.spawn_with_fallback(driver_path, port, e) + } + } + } + } + + /// Windows-specific fallback spawn method when CREATE_NO_WINDOW fails + #[cfg(target_os = "windows")] + fn spawn_with_fallback( + &self, + driver_path: &PathBuf, + port: u32, + original_error: std::io::Error, + ) -> Result { + // If CREATE_NO_WINDOW fails, try without any special flags + error!("Failed to spawn with CREATE_NO_WINDOW: {original_error}"); + error!("Trying without special creation flags..."); + + let mut fallback_command = Self::standard_command(driver_path, port); + Self::log_command(&fallback_command); + + match fallback_command.spawn() { + Ok(child) => { + info!("Successfully spawned WebDriver without special creation flags"); + Ok(child) + } + Err(fallback_e) => { + error!("Original error: {original_error}"); + error!("Fallback error: {fallback_e}"); + Err(self.handle_spawn_error(fallback_e, &fallback_command, "fallback method")) + } + } + } + + /// Creates a command with standard configuration (no Windows flags) + fn standard_command(driver_path: &PathBuf, port: u32) -> Command { + let mut command = Command::new(driver_path); + command.arg(format!("--port={port}")); + + // Add verbose flag only for chromedriver + #[cfg(feature = "chromedriver")] + command.arg("--verbose"); + + command + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + command + } + + /// Creates a command with Windows-specific flags + #[cfg(target_os = "windows")] + fn create_command(driver_path: &PathBuf, port: u32) -> Command { + use std::os::windows::process::CommandExt; + + let mut command = Self::standard_command(driver_path, port); + // Try with CREATE_NO_WINDOW for headless operation + command.creation_flags(0x08000000); // CREATE_NO_WINDOW flag + command + } + + #[cfg(not(target_os = "windows"))] + fn create_command(driver_path: &PathBuf, port: u32) -> Command { + Self::standard_command(driver_path, port) + } + + /// Logs command execution details + fn log_command(command: &Command) { + info!( + "Executing command: {:?} {:?}", + command.get_program(), + command.get_args() + ); + } + + /// Handles spawn errors with appropriate logging and diagnostics + fn handle_spawn_error( + &self, + e: std::io::Error, + command: &Command, + attempt: &str, + ) -> anyhow::Error { + error!("Failed to spawn '{WEBDRIVER_BIN}' with {attempt}: {e}"); + error!( + "Command was: {:?} {:?}", + command.get_program(), + command.get_args() + ); + + #[cfg(target_os = "windows")] + if attempt == "CREATE_NO_WINDOW" { + error!("Windows: Check if antivirus is blocking the process"); + error!("Windows: Check if the binary has proper permissions"); + } + + error!( + "WebDriver diagnostics after spawn failure:\n{}", + self.get_diagnostics() + ); + + anyhow!("Failed to spawn '{WEBDRIVER_BIN}': {}", e) + } + + fn setup_output_monitoring(&self, child: &mut Child, port: u32) { + // Monitor stderr + if let Some(stderr) = child.stderr.take() { + let port_for_logging = port; + thread::spawn(move || { + info!("Starting stderr monitoring for WebDriver on port {port_for_logging}"); + let stderr_lines = BufReader::new(stderr).lines(); + for line in stderr_lines.map_while(Result::ok) { + trace!("WebDriver[{port_for_logging}] stderr: {line}"); + } + info!("Stderr monitoring ended for WebDriver on port {port_for_logging}"); + }); + } + + // Monitor stdout + if let Some(stdout) = child.stdout.take() { + let port_for_logging = port; + thread::spawn(move || { + info!("Starting stdout monitoring for WebDriver on port {port_for_logging}"); + let stdout_lines = BufReader::new(stdout).lines(); + for line in stdout_lines.map_while(Result::ok) { + trace!("WebDriver[{port_for_logging}] stdout: {line}"); + } + info!("Stdout monitoring ended for WebDriver on port {port_for_logging}"); + }); + } + } + + fn store_child_process(&mut self, child: Child) { + let mut inner = self.inner.lock().unwrap(); + inner.webdriver_child = Some(child); + info!("WebDriver process stored, waiting for it to become ready..."); + } + + fn wait_for_ready(&self, port: u32) -> Result<()> { + let start_time = std::time::Instant::now(); + let timeout_duration = if cfg!(target_os = "windows") { + std::time::Duration::from_secs(60) + } else { + std::time::Duration::from_secs(30) + }; + + while start_time.elapsed() < timeout_duration { + if Self::is_webdriver_running(port) { + info!( + "WebDriver is ready on port {} after {:?}", + port, + start_time.elapsed() + ); + return Ok(()); + } + + // Check if process is still alive + if let Some(child) = self.inner.lock().unwrap().webdriver_child.as_mut() { + if let Ok(Some(_)) = child.try_wait() { + error!("WebDriver process exited before becoming ready on port {port}"); + return Err(anyhow!( + "WebDriver process exited before becoming ready on port {}", + port + )); + } + } + + std::thread::sleep(std::time::Duration::from_millis(100)); + } + + error!("WebDriver failed to become ready on port {port} within {timeout_duration:?}"); + Err(anyhow!( + "WebDriver failed to become ready on port {} within {:?}", + port, + timeout_duration + )) + } + + /// Stops the WebDriver process. + /// + /// This method manages WebDriver process termination: + /// - Only terminates processes that were spawned by this instance + /// - Leaves externally managed WebDriver sessions running + /// - Logs warnings when attempting to stop external sessions + /// + /// Returns `Ok(())` on success, or an error if the process termination + /// fails. + pub fn stop(&mut self) -> Result<()> { + let mut inner = self + .inner + .lock() + .map_err(|e| anyhow!("Failed to acquire lock: {}", e))?; + + // Only stop the process if we spawned it (not if it's external) + if !inner.is_external { + if let Some(child) = inner.webdriver_child.as_mut() { + info!("Stopping '{WEBDRIVER_BIN}' (PID: {})", child.id()); + let _ = child.kill(); + let _ = child.wait(); + } + } else { + warn!( + "Not stopping external WebDriver on port {} as it was not spawned by us", + inner.webdriver_port + ); + } + Ok(()) + } + + /// Get diagnostic information about the WebDriver process. + /// + /// This method provides detailed information about the WebDriver process + /// for debugging purposes. + /// + /// Returns a string with diagnostic information. + pub(crate) fn get_diagnostics(&self) -> String { + let mut inner = self.inner.lock().unwrap(); + let mut diagnostics = String::new(); + + diagnostics.push_str("WebDriver Diagnostics:\n"); + diagnostics.push_str(&format!(" Port: {}\n", inner.webdriver_port)); + diagnostics.push_str(&format!(" Driver Path: {:?}\n", inner.driver_path)); + diagnostics.push_str(&format!(" Is External: {}\n", inner.is_external)); + + if let Some(child) = inner.webdriver_child.as_mut() { + diagnostics.push_str(&format!(" Process ID: {}\n", child.id())); + + // Check if process is still running + match child.try_wait() { + Ok(None) => diagnostics.push_str(" Process Status: Running\n"), + Ok(Some(status)) => { + diagnostics.push_str(&format!(" Process Status: Exited with {status:?}\n")) + } + Err(e) => { + diagnostics.push_str(&format!(" Process Status: Error checking status: {e}\n")) + } + } + } else { + diagnostics.push_str(" Process ID: None (no child process)\n"); + } + + // Check if WebDriver is responding + let is_running = Self::is_webdriver_running(inner.webdriver_port); + diagnostics.push_str(&format!(" WebDriver Responding: {is_running}\n")); + + // Check port availability + let url = format!("{WEBDRIVER_URL}:{}/status", inner.webdriver_port); + diagnostics.push_str(&format!(" Status URL: {url}\n")); + + #[cfg(target_os = "windows")] + { + diagnostics.push_str(" Platform: Windows\n"); + // Check if port is in use using Windows-specific commands + if let Ok(output) = std::process::Command::new("netstat").args(["-an"]).output() { + let netstat_output = String::from_utf8_lossy(&output.stdout); + if netstat_output.contains(&format!(":{}", inner.webdriver_port)) { + diagnostics.push_str(&format!( + " Port {} appears to be in use (netstat)\n", + inner.webdriver_port + )); + } else { + diagnostics.push_str(&format!( + " Port {} appears to be free (netstat)\n", + inner.webdriver_port + )); + } + } + + // Check if chromedriver is in PATH (Windows-specific) + #[cfg(all(target_os = "windows", feature = "chromedriver"))] + { + if let Ok(output) = std::process::Command::new("where") + .arg("chromedriver") + .output() + { + let where_output = String::from_utf8_lossy(&output.stdout); + diagnostics + .push_str(&format!(" Chromedriver in PATH: {}", where_output.trim())); + } else { + diagnostics.push_str(" Chromedriver not found in PATH\n"); + } + } + + // Check if geckodriver is in PATH (Windows-specific) + #[cfg(all(target_os = "windows", feature = "geckodriver"))] + { + if let Ok(output) = std::process::Command::new("where") + .arg("geckodriver") + .output() + { + let where_output = String::from_utf8_lossy(&output.stdout); + diagnostics + .push_str(&format!(" Geckodriver in PATH: {}", where_output.trim())); + } else { + diagnostics.push_str(" Geckodriver not found in PATH\n"); + } + } + + // Check Windows Defender status (Windows-specific) + #[cfg(target_os = "windows")] + { + if let Ok(output) = std::process::Command::new("powershell") + .args([ + "-Command", + "Get-MpComputerStatus | Select-Object RealTimeProtectionEnabled", + ]) + .output() + { + let defender_output = String::from_utf8_lossy(&output.stdout); + diagnostics.push_str(&format!( + " Windows Defender Real-time Protection: {}", + defender_output.trim() + )); + } + } + } + + diagnostics + } + + /// Check if a WebDriver is already running on the specified port. + /// + /// This method performs a WebDriver standard-compliant check by: + /// 1. Making an HTTP GET request to `/status` endpoint + /// 2. Checking for HTTP 200 response + /// 3. Verifying the response contains "ready" indicating the service is + /// ready + /// + /// Returns `true` if WebDriver is running and ready, `false` otherwise. + pub(crate) fn is_webdriver_running(port: u32) -> bool { + let url = format!("{WEBDRIVER_URL}:{port}/status"); + + // Add timeout to prevent hanging + let client = reqwest::blocking::Client::builder() + .timeout(std::time::Duration::from_secs(5)) + .build() + .unwrap_or_else(|_| reqwest::blocking::Client::new()); + + client + .get(&url) + .send() + .ok() + .filter(|response| response.status().as_u16() == 200) + .and_then(|response| response.text().ok()) + .map(|text| text.contains("ready")) + .unwrap_or(false) + } + + fn get_webdriver_path() -> Result { + use std::env; + + let path = match env::var(WEBDRIVER_PATH_ENV) { + Ok(runtime_env) => runtime_env, + Err(runtime_env_err) => match option_env!("WEBDRIVER_DOWNLOAD_PATH") { + Some(compile_time_path) => compile_time_path.to_string(), + None => { + debug!("{WEBDRIVER_PATH_ENV}: {runtime_env_err}"); + warn!("Use the plotly_static's `webdriver_download` feature to automatically download, install and use the the chosen WebDriver for supported platforms. Or manually set {WEBDRIVER_PATH_ENV} to point to the WebDriver binary."); + return Err(anyhow!( + "WebDriver binary not available. Set {} environment variable or use the webdriver_download feature", + WEBDRIVER_PATH_ENV + )); + } + }, + }; + + Self::full_path(&path).map_err(|e| anyhow!("Invalid WebDriver path '{}': {}", path, e)) + } + + fn full_path(path: &str) -> Result { + let p = PathBuf::from(path); + if !p.exists() { + Err(anyhow!( + "WebDriver executable not found at provided path: '{}'", + p.display() + )) + } else { + Ok(p) + } + } +} From cddff80b48499b2242021da9d3f4ab0b9e4df33a Mon Sep 17 00:00:00 2001 From: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> Date: Mon, 7 Jul 2025 22:31:01 +0200 Subject: [PATCH 2/2] add Typos check job and fix typos Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- .github/workflows/book.yml | 4 ++++ .github/workflows/{ci.yml => build.yml} | 2 +- .github/workflows/typos.yml | 20 ++++++++++++++++++++ CHANGELOG.md | 8 ++++---- README.md | 4 ++-- _typos.toml | 10 ++++++++++ plotly/src/common/mod.rs | 6 +++--- plotly/src/plot.rs | 2 +- plotly/src/traces/scatter3d.rs | 2 +- 9 files changed, 46 insertions(+), 12 deletions(-) rename .github/workflows/{ci.yml => build.yml} (99%) create mode 100644 .github/workflows/typos.yml create mode 100644 _typos.toml diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index 07a6ed7d..16870084 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -6,6 +6,10 @@ on: tags: - '[0-9]+.[0-9]+.[0-9]+' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: RUST_BACKTRACE: full diff --git a/.github/workflows/ci.yml b/.github/workflows/build.yml similarity index 99% rename from .github/workflows/ci.yml rename to .github/workflows/build.yml index 153fc4dc..58403644 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: CI +name: Build&Test on: workflow_dispatch: diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml new file mode 100644 index 00000000..0458ff1f --- /dev/null +++ b/.github/workflows/typos.yml @@ -0,0 +1,20 @@ +name: Typos + +on: + workflow_dispatch: + pull_request: + branches: [ main ] + push: + branches: [ main ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + typos: + name: Check for typos + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: crate-ci/typos@master \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 027d822a..94785f5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,14 +6,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [0.13.0] - 2025-xx-xx ### Changed -- [[#277](https://github.com/plotly/plotly.rs/pull/277)] Removed `wasm` feature flag and put evrything behind target specific dependencies. Added `.cargo/config.toml` for configuration flags needed by `getrandom` version 0.3 on `wasm` targets. +- [[#277](https://github.com/plotly/plotly.rs/pull/277)] Removed `wasm` feature flag and put everything behind target specific dependencies. Added `.cargo/config.toml` for configuration flags needed by `getrandom` version 0.3 on `wasm` targets. - [[#281](https://github.com/plotly/plotly.rs/pull/281)] Update to askama 0.13.0 - [[#287](https://github.com/plotly/plotly.rs/pull/287)] Added functionality for callbacks (using wasm) - [[#289](https://github.com/plotly/plotly.rs/pull/289)] Fixes Kaleido static export for MacOS targets by removing `--disable-gpu` flag for MacOS - [[#291](https://github.com/plotly/plotly.rs/pull/291)] Remove `--disable-gpu` flag for Kaleido static-image generation for all targets. - [[#299](https://github.com/plotly/plotly.rs/pull/299)] Added customdata field to HeatMap - [[#303](https://github.com/plotly/plotly.rs/pull/303)] Split layout mod.rs into modules -- [[#304](https://github.com/plotly/plotly.rs/pull/304)] Refactored examples to allow fo generation of full html files +- [[#304](https://github.com/plotly/plotly.rs/pull/304)] Refactored examples to allow for generation of full html files - [[#320](https://github.com/plotly/plotly.rs/pull/320)] Make offline_js_sources function `pub` - [[#321](https://github.com/plotly/plotly.rs/pull/321)] Make 'online_cdn_js' function also `pub` for consistenccy @@ -86,7 +86,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - [[#154](https://github.com/plotly/plotly.rs/pull/154)] Improve ergonomics of `Title` and `LegendGroupTitle` structs: `new` method now takes no arguments as per other structs, whilst a new `with_text()` constructor is added for convenience. Where other structs contain a `Title` (and `LegendGroupTitle`), users can now call the `title()` (and `legend_group_title()`) method with anything that `impl`s `Into`, viz. `String`, `&String`, `&str` and `Title`. - [[#157](https://github.com/plotly/plotly.rs/pull/157)] Fix `HeatMap`'s setters for correctly setting `zmin`, `zmax` and `zmin` independent of `Z` input type. - [[#159](https://github.com/plotly/plotly.rs/pull/159)] Make `heat_map` module public to expose `Smoothing enum`. -- [[#161](https://github.com/plotly/plotly.rs/pull/161)] Added `Axis` `scaleanchor` settter. +- [[#161](https://github.com/plotly/plotly.rs/pull/161)] Added `Axis` `scaleanchor` setter. - [[#163](https://github.com/plotly/plotly.rs/pull/163)] Added `DensityMapbox`. - [[#166](https://github.com/plotly/plotly.rs/pull/166)] Added subplot example with multiple titles. - [[#178](https://github.com/plotly/plotly.rs/pull/178)] Fix setter for `Axis::matches` to take string arg. @@ -119,7 +119,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [0.8.1] - 2022-09-25 ### Added -- Button support (i.e. [updatemenus](https://plotly.com/javascript/reference/layout/updatemenus/)) contibuted by [@sreenathkrishnan](https://github.com/sreenathkrishnan). Details and examples in this well written PR [#99](https://github.com/plotly/plotly.rs/pull/99). +- Button support (i.e. [updatemenus](https://plotly.com/javascript/reference/layout/updatemenus/)) contributed by [@sreenathkrishnan](https://github.com/sreenathkrishnan). Details and examples in this well written PR [#99](https://github.com/plotly/plotly.rs/pull/99). - Internally, there is now a `plotly-derive` crate which defines a `FieldSetter` procedural macro. This massively cuts down the amount of code duplication by generating the setter methods based on the struct fields. Again thanks to @sreenathkrishnan for this effort. ## [0.8.0] - 2022-08-26 diff --git a/README.md b/README.md index 243b5724..3cdcdb34 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ Alternatively, enable only the `kaleido` feature and manually install Kaleido. # Cargo.toml [dependencies] -plotly = { version = "0.12", features = ["kaleido"] } +plotly = { version = "0.13", features = ["kaleido"] } ``` With the feature enabled, plots can be saved as any of `png`, `jpeg`, `webp`, `svg`, `pdf` and `eps`. Note that the plot will be a static image, i.e. they will be non-interactive. @@ -167,7 +167,7 @@ plot.write_image("out.png", ImageFormat::PNG, 800, 600, 1.0); ## Usage Within a Wasm Environment -`Plotly.rs` can be used with a Wasm-based frontend framework. The needed dependencies are automatically enabled on `wasm32` targets. Note that the `kaleido` feature is not supported in Wasm enviroments and will throw a compilation error if enabled. +`Plotly.rs` can be used with a Wasm-based frontend framework. The needed dependencies are automatically enabled on `wasm32` targets. Note that the `kaleido` feature is not supported in Wasm environments and will throw a compilation error if enabled. First, make sure that you have the Plotly JavaScript library in your base HTML template: diff --git a/_typos.toml b/_typos.toml new file mode 100644 index 00000000..a89d386f --- /dev/null +++ b/_typos.toml @@ -0,0 +1,10 @@ +[files] +extend-exclude = [ + "**/*.js", + "**/*min.js", +] + + +[default.extend-words] +# Don't correct the scene projection type "Winkel tripel" +tripel = "tripel" \ No newline at end of file diff --git a/plotly/src/common/mod.rs b/plotly/src/common/mod.rs index 6bf70cd6..cd866c3a 100644 --- a/plotly/src/common/mod.rs +++ b/plotly/src/common/mod.rs @@ -1060,7 +1060,7 @@ pub enum PatternShape { #[serde(rename = "")] None, #[serde(rename = "-")] - HorizonalLine, + HorizontalLine, #[serde(rename = "|")] VerticalLine, #[serde(rename = "/")] @@ -2263,7 +2263,7 @@ mod tests { #[test] fn serialize_pattern_shape() { assert_eq!(to_value(PatternShape::None).unwrap(), json!("")); - assert_eq!(to_value(PatternShape::HorizonalLine).unwrap(), json!("-")); + assert_eq!(to_value(PatternShape::HorizontalLine).unwrap(), json!("-")); assert_eq!(to_value(PatternShape::VerticalLine).unwrap(), json!("|")); assert_eq!( to_value(PatternShape::RightDiagonalLine).unwrap(), @@ -2294,7 +2294,7 @@ mod tests { fn serialize_pattern() { let pattern = Pattern::new() .shape_array(vec![ - PatternShape::HorizonalLine, + PatternShape::HorizontalLine, PatternShape::VerticalLine, ]) .fill_mode(PatternFillMode::Overlay) diff --git a/plotly/src/plot.rs b/plotly/src/plot.rs index e17f575f..9a45f43e 100644 --- a/plotly/src/plot.rs +++ b/plotly/src/plot.rs @@ -791,7 +791,7 @@ impl Plot { } #[cfg(target_family = "wasm")] - /// Convert a `Plot` to a native Javasript `js_sys::Object`. + /// Convert a `Plot` to a native JavaScript `js_sys::Object`. pub fn to_js_object(&self) -> wasm_bindgen_futures::js_sys::Object { use wasm_bindgen_futures::js_sys; use wasm_bindgen_futures::wasm_bindgen::JsCast; diff --git a/plotly/src/traces/scatter3d.rs b/plotly/src/traces/scatter3d.rs index 762c30d9..27baa0e4 100644 --- a/plotly/src/traces/scatter3d.rs +++ b/plotly/src/traces/scatter3d.rs @@ -155,7 +155,7 @@ where #[serde(rename = "texttemplate")] text_template: Option<Dim<String>>, /// Sets hover text elements associated with each (x, y, z) triplet. The - /// same text will be associated with all datas points. To be seen, the + /// same text will be associated with all data points. To be seen, the /// trace `hover_info` must contain a "Text" flag. #[serde(rename = "hovertext")] hover_text: Option<Dim<String>>,