[
  {
    "path": ".circleci/config.yml",
    "content": "version: 2.1\n\norbs:\n  win: circleci/windows@2.2.0\n\njobs:\n  build-stable:\n    docker:\n      - image: cimg/rust:1.85.1\n    steps:\n      - checkout\n      - run: cargo --version\n      - run: cargo build --workspace\n      - run: cargo test\n      - run:\n          name: Install tools\n          command: |\n            rustup component add rustfmt clippy\n      - run:\n          name: Check formatting\n          command: |\n            cargo fmt --all -- --check --color=auto\n      - run:\n          name: Clippy\n          command: |\n            cargo clippy --all-features\n  build-css:\n    docker:\n      - image: cimg/rust:1.90\n    steps:\n      - checkout\n      - run: cargo --version\n      - run: cargo build --features=css,css_ext,xml --workspace\n      - run: cargo test --features=css,css_ext,xml\n  build-1-85:\n    docker:\n      - image: cimg/rust:1.85\n    steps:\n      - checkout\n      - run: cargo --version\n      - run: cargo build --features=css\n      - run: cargo test --features=css\n  build-windows:\n    executor:\n      name: win/default\n      size: medium\n      shell: bash.exe\n    environment:\n      PATHk\n    steps:\n      - checkout\n      - run:\n          name: Install Rust\n          command: |\n            curl https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe --output rustup-init.exe\n            ./rustup-init.exe -y\n      - run:\n          name: Update PATH and cargo config\n          command: |\n            echo \"[net]\" >> $USERPROFILE/.cargo/config\n            echo \"git-fetch-with-cli = true\" >> $USERPROFILE/.cargo/config\n            echo 'export PATH=$USERPROFILE/.cargo/bin:$PATH' >> $BASH_ENV\n      - run:\n          name: Build\n          command: |\n            cargo build\n      - run:\n          name: Tests\n          command: |\n            cargo test\n\nworkflows:\n  version: 2\n  build:\n    jobs:\n      - \"build-stable\"\n      - \"build-css\"\n      - \"build-1-85\"\n      - \"build-windows\"\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n- package-ecosystem: \"cargo\"\n  directory: \"/\"\n  schedule:\n    interval: \"weekly\"\n    day: \"friday\"\n  rebase-strategy: \"disabled\"\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  pull_request:\n  push:\n    branches:\n      - main\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  test-action:\n    name: Check semver compatibility\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v2\n\n      - name: Install stable toolchain\n        uses: actions-rs/toolchain@v1\n        with:\n          toolchain: stable\n          profile: minimal\n          override: true\n\n      - name: Check semver\n        uses: obi1kenobi/cargo-semver-checks-action@v2\n        with:\n          version-tag-prefix: ''\n"
  },
  {
    "path": ".github/workflows/jekyll-gh-pages.yml",
    "content": "# Sample workflow for building and deploying a Jekyll site to GitHub Pages\nname: Build and deploy demo site\n\non:\n  # Allows you to run this workflow manually from the Actions tab\n  workflow_dispatch:\n\n# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages\npermissions:\n  contents: read\n  pages: write\n  id-token: write\n\n# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.\n# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.\nconcurrency:\n  group: \"pages\"\n  cancel-in-progress: false\n\njobs:\n  # Build job:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Setup Pages\n        uses: actions/configure-pages@v5\n      - name: Install trunk\n        run: cargo install trunk --version=0.21.13\n      - name: Install WASM rust target\n        run: rustup target add wasm32-unknown-unknown\n      - name: Build WASM module\n        run: trunk build\n        working-directory: ./html2text-web-demo\n      - name: Copy WASM assets\n        run: cp html2text-web-demo/dist/html2text-web-demo{.js,_bg.wasm} ./pages/assets/\n      - name: Build with Jekyll\n        uses: actions/jekyll-build-pages@v1\n        with:\n          source: ./pages\n          destination: ./_site\n      - name: Upload artifact\n        uses: actions/upload-pages-artifact@v3\n\n  # Deployment job\n  deploy:\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n    runs-on: ubuntu-latest\n    needs: build\n    steps:\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@v4\n\n"
  },
  {
    "path": ".gitignore",
    "content": "target\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nPossible log types:\n\n- `[added]` for new features.\n- `[changed]` for changes in existing functionality.\n- `[deprecated]` for once-stable features removed in upcoming releases.\n- `[removed]` for deprecated features removed in this release.\n- `[fixed]` for any bug fixes.\n- `[security]` to invite users to upgrade in case of vulnerabilities.\n\n### 0.17.1\n\n- [added] Add support for XHTML (for the cases where it doesn't quite behave\n  like HTML).\n\n### 0.17.0\n\n- [changed] Split `html2text` example into `html2text-cli` crate\n- [fixed] A possible panic when syntax-highlighting\n- [changed] Update html5ever to 0.39.0\n\n### 0.16.7\n\n- [added] Support `<b>` tags as bold (thanks amir)\n- [changed] Update html5ever to 0.38.0 (thanks mtorromeo)\n\n### 0.16.6\n\n- [changed] Update html5ever and tendril dependencies.\n\n### 0.16.5\n\n- [fixed] Fix a subtract with underflow with rowspans and empty rows (thanks\n  mdierksen)\n\n### 0.16.4\n\n- [fixed] Further fix for RcDom::serialize() when there is a `<doctype>`.\n\n### 0.16.3\n\n- [fixed] RcDom::serialize() panicked.\n- [changed] Bumped html5ever dependency\n- [fixed] Fixed a subtraction underflow in the `html2term` example.\n\n### 0.16.2\n\n- [fixed] Removed spurious `dbg!()` accidentally left in.\n\n### 0.16.1\n\n- [added] Add `Config::empty_img_mode()` to configure how images with no alt text\n  are handled.\n\n### 0.16.0\n\n- [changed] Updated MSRV to 1.85.\n- [fixed] Fix a panic in debug mode (subtraction underflow) with some table/rowspan\n  edge cases (thanks mtorromeo)\n\n### 0.15.5\n\n- [fixed] Fix an assertion and some missing styles with rowspan cells in rich mode.\n\n### 0.15.4\n\n- [added] Support handling `rowspan` in tables.\n\n### 0.15.3\n\n- [fixed] Parse `<noscript>` tags as if scripting is disabled (thanks craigchiang)\n- [fixed] Treat inline `<svg>` as images (thanks craigchiang)\n\n### 0.15.2\n\n- [fixed] Fix whitespace-only tables being shown as \"─\" (thanks fuelen)\n- [fixed] Fix wrapping with non-breaking spaces, zero-width spaces, and\n  some similar equivalents.\n\n### 0.15.1\n\n- [added] CSS: Support basic attribute selectors (`div[attr=\"bar\"]`).\n- [changed] Various improvements to syntax highlighting:\n  - It uses the priority of the `x-syntax` rule.\n  - Now supported on non-`<pre>` elements.\n  - No longer strips contained tags when highlighting\n  - Compatible with `display: x-raw-dom` extension (e.g. to colour the HTML)\n- [fixed] With `pad_block_width` enabled, do a better job of padding blocks.\n  In particular, the padding gets the block's background colour (when CSS etc.\n  are being used).\n\n### 0.15.0\n\n- [added] Syntax highlighting support for `<pre>` blocks\n  (`Config::register_highlighter` and CSS `x-syntax: foo`)\n- [changed] CSS extensions (until now `display: x-raw-dom`, and only if the\n  `css_ext` Cargo feature is enabled) are now only available in agent and user CSS.\n  This is a breaking change, but is not likely to affect many users.\n\n### 0.14.4\n\n- [added] `RcDom::serialize`, and expose a few more of the `RcDom` types.\n- [added] Online [demo page](https://jugglerchris.github.io/rust-html2text/)\n\n### 0.14.3\n\n- [changed] Updated dependencies, including html5ever 0.31.\n\n### 0.14.2\n\n- [fixed] An issue with multiple verions of markup5ever being included.\n  (thanks anna-hope)\n\n### 0.14.1\n\n- [fixed] An issue with `FragmentStart`s being lost (thanks toiletbril)\n- [fixed] An infinite loop if tabs inside `<pre>` wrapped past the width\n  (thanks nshp)\n\n### 0.14.0\n\n- [changed] Various small refactors (thanks sftse)\n- [changed] `Config::rich()` no longer includes decorations around `<em>` etc. - \n  use `Config::rich().do_decorate()` to get the old behaviour.\n- [fixed] Remove unnecessary empty lines at the start of lists (thanks russellbanks)\n- [added] New CSS support: `::before`/`::after` and `content: \"string\"`, which is now\n  used for simple decorations.  With CSS enabled, this allows for customising\n  the display of `<em>foo</em>` without writing a decorator.\n- [added] Add support for `<h5>` and `<h6>` (thanks noahbaculi)\n- [changed] Link footnotes are now configurable independently of the decorator, and on\n  by default for `config::plain()` but can be enabled or disabled with\n  `config.link_footnotes(true/false)`.  The footnote references (e.g. `[1]`) are added\n  in the main renderer, and the actual footnotes are written in a default implementation\n  of `TextDecorator::finalise()` so can be customised.\n\n### 0.13.6\n\n- [fixed] Fixed issue parsing CSS rules with known rules but unknown values,\n  which caused parsing to stop instead of just skipping the unkown rule.\n\n### 0.13.5\n\n- [added] CSS support for `:nth-child()` (not yet with the `of foo`).\n- [added] Non-standard `display: x-raw-dom` for debugging (with `css_ext`\n  feature flag).\n- [fixed] An issue which could (apparently rarely) miss out some output depending on wrapping\n- [fixed] CSS parsing stopped when it hit an at-rule.\n- [added] Add `--show-css` option to `html2text` example for debugging what rules were parsed.\n- [added] Add poor-man's inspect mode to `html2term` - `I` to enable/disable, and arrows to navigate\n  around the DOM.  Implemented using `:nth-child` and `x-raw-dom`.\n\n### 0.13.4\n\n- [fixed] Fix a debug assertion from a double-counted length increment\n  (thanks JadedBlueeyes).\n\n### 0.13.3\n\n- [fixed] Handle some obsolete `bgcolor=...` attributes.\n- [added] html2text example has `--show-render` to help debugging render issues.\n- [changed] Some error handling and other tidyups (thanks sftse)\n\n### 0.13.2\n\n- [fixed] Fixed errors when building with Rust 1.72.\n\n### 0.13.1\n\n- [added] html2text now has --show-dom\n- [fixed] Support background CSS property (for colour)\n- [fixed] Some edge cases with CSS styles on whitespace\n\n### 0.13.0\n\n- [added] Support CSS white-space: pre-wrap (and normal, pre).\n\n### 0.13.0-alpha.2\n\n- [changed] Updated html5ever and markup5ever crate versions.  This has meant\n  updating the MSRV, which is now set to 1.72.\n- [fixed] Add `Config::no_link_wrapping()` (thanks JadeBlueEyes)\n- [fixed] Fix panic with empty table inside a list (thanks sftse)\n- [changed] Top level convenience functions (`from_read` etc.) now return\n  `Result<..>` instead of panicking (thanks sftse)\n- [fixed] Fix panic with very large HTML `colspan` (thanks pycui)\n- [changed] CSS updates:\n  - Separate user agent, author, and user CSS layers\n  - Improve the style precedence between layers and implement specificity.\n\n### 0.13.0-alpha.1\n\n- [fixed] Table rows with colours would disappear. (thanks tkapias)\n\n### 0.13.0-alpha.0\n\n- [changed] Replaced LightningCSS with a smaller CSS parser.  There is a chance\n  that some CSS edge cases which no longer work; if so this would be a bug.\n- [removed] Some previously `pub` items and methods which are either internal\n  implementation details or considered redundant have been removed or made\n  private (thanks sftse).  Please open an issue for anything removed that was\n  being used.\n\n  Of note, `RenderTree::render_plain()` and `RenderTree::render_rich()` have\n  been removed.  Replace code like:\n\n  ```rust\n  let text = html2text::parse(html)?\n      .render_plain(80)?\n      .into_string()?;\n  ```\n  with:\n  ```rust\n  let text = html2text::config::plain()\n      .render_to_string(html2text::parse(html)?)?\n  ```\n- [changed] Some names moved out of `text_renderer` module, so some `use` statements\n  may need updating.\n- [changed] Replace some `unwrap()` with improved patterns (thanks sftse).\n- [changed] Updated some dependencies\n\n### 0.12.5\n\n- [changed] Updated some dependencies\n- [added] The `html2text` example now has `--ignore-css-colour`, which ignores CSS\n  colour information but still uses `display: none`, for example.\n- [added] The `html2text` example now has `--only-css` option, to not use\n  default colours when CSS colours are being used.\n- [fixed] Make the dummy `dashmap` depenency optional so it's not included\n  unnecessarily when CSS isn't enabled (thanks xmakro)\n\n### 0.12.4\n\n- [changed] Update the previous `max-height: 0` to also look at `height: 0` and require\n  `overflow: hidden` as well.\n  This helps with a hack some e-mail senders use for e-mail previews.  (thanks tkapias)\n\n### 0.12.3\n\n- [changed] Treat `max-height: 0` as if it's `display: none` when CSS is enabled.\n  This helps with a hack some e-mail senders use for e-mail previews.  (thanks tkapias)\n\n### 0.12.2\n\n- [changed] Bump version of lightningcss dependency to fix build failures.\n\n### 0.12.1\n\n- [fixed] Fix a case where Err(TooNarrow) was returned unnecessarily. (thanks sftse)\n- [added] Add new rendering options `Config::raw_mode()` and\n  `Config::no_table_borders()` (thanks sftse)\n- [changed] Formatting, clippy and other tidy-ups (thanks sftse)\n- [changed] Cargo fmt now enforced in CI\n\n### 0.12.0\n\n- [changed] Updated termion dev-dependency\n- [added] Support `<sup>` HTML elements\n- [added] Export `RcDom` publically.  It was already returned by a pub function.\n- [added] Update handling of width overflow:\n          With `Config::allow_width_overflow()`, prefer returning output wider\n          than requested, instead of returning `Err(TooNarrow)`.\n          `Config::min_wrap_width()` sets the minimum text wrap width (default\n          3).  The minimum width (before overflow or `TooNarrow`) is now\n          handled more cleanly.\n- [added] CSS: use color/bgcolor attributes on elements.\n\n### 0.11.0\n\n- [fixed] CSS: rules marked !important were ignored.\n- [changed] html\\_trace feature now uses the `log` crate.\n- [changed] Bumped MSRV to 1.63 (matching Debian stable) due to some dependencies.\n\n### 0.10.3\n\n- [fixed] A panic on some unlucky text wrapping coincidences.\n- [fixed] Use dep:backtrace in Cargo.toml to avoid implicit feature.\n\n### 0.10.2\n\n- [fixed] CSS: Ignore transparent colours.\n\n### 0.10.1\n\n- [fixed] `max_width` was not working with some render methods.\n\n### 0.10.0\n\n- [added] Simple support for `<i>`, `<ins>`, and `<del>` (thanks sgtatham)\n- [added] Added background-color support\n- [fixed] CSS support didn't work in some places, such as `<td>` elements.\n- [added] Add support for `style` attributes.\n- [added] Styles apply to table borders\n- [changed] Update some dependencies\n- [fixed] Fix a few places which caused excess blank lines or empty tables\n\n### 0.9.4\n\n- [changed] Updated the termion dev-dependency to 2.0.\n\n### 0.9.3\n\n- [changed] Added cargo categories and update to 2021 edition.\n\n### 0.9.2\n\n- [fixed] CSS didn't work inside `<ul>` or `<ol>`.\n- [added] Add methods to get and use the intermediate HTML DOM and RenderTree\n  from Config.\n- [fixed] Removed some clones which are no longer necessary now that Box<FnOnce>\n  works.\n\n### 0.9.1\n\n- [fixed] Various documentation issues (thanks sgtatham)\n- [changed] CSS color rules now work for elements other than span.\n\n### 0.9.0\n\n- [changed] `Config::add_css` now returns `Result` instead of panicking on\n  CSS parse errors.  Errors from parsing document CSS are ignored.\n- [added] Support `<font color=...>` when CSS is enabled.\n- [added] `Config::max_wrap_width()` to wrap text to a norrower width than\n  the overal size available.\n- [added] Add --wrap-width and --css options to html2text example.\n\n### 0.8.0\n\n- [added] CSS: Support more extensive selectors\n- [changed] CSS handling defaults to off; use `Config::use_doc_css()`\n  or `Config::add_css` to use CSS.\n\n### 0.7.1\n\n- [added] Now recognised CSS `display:none`\n- [added] Can now add extra CSS rules via `Config::add_css`.\n- [changed] StyleData::coloured is no longer public.\n\n### 0.7.0\n\n- [changed] Remove some noisy stderr output when encoutering control chars\n  (thanks sftse)\n- [added] A builder-based config API.\n- [changed] Updated MSRV to 1.60\n- [fixed] Fixed #88: panic when a width of zero passed in (thanks bingen13)\n- [fixed] Fixed #90: Fixed a divide-by-zero panic with colspan=0 (thanks mtorromeo)\n- [added] Add very basic CSS colour support (under the css feature flag)\n- [changed] Removed ansi\\_colours feature (from\\_read\\_coloured is always available)\n- [changed] Overhauled error handling.  Internally (and in the lower level\n  API) errors (mainly \"TooNarrow\") are passed around with `Result`.  Fixed\n  some panics and infinite loops.  (Thanks WIZeaz for fuzzing)\n\n### 0.6.0\n\n- [changed] Improve layout of tables thanks to sftse:\n  - Table column size estimates have been improved when the source HTML has a lot\n    of unnecessary whitespace.\n  - Move the URL footnotes out to the top level, also improving layout of tables\n    containing links.\n- [changed] Some APIs have slightly changed as part of the table improvements,\n  though most users should not be affeted.\n\n### 0.5.1\n\n- [fixed] Some tables were rendered too wide.\n\n### 0.5.0\n\n- [changed] Rich Image annotations now include the src attirbute (thanks spencerwi).\n\n### 0.4.5\n\n- [fixed] Preserve empty lines in pre blocks (thanks kpagacz).\n\n### 0.4.4\n\n- [fixed] Fix some panics when enumerated lists are in tables (thanks sfts).\n- [fixed] Impove table size estimation to include links.\n\n### 0.4.3\n\n- [changed] MSRV is now 1.56.\n- [fixed] Fix some panics when very large widths are used with tables.\n\n### 0.4.2\n\n- [changed] Moved the rcdom module directly into src/\n\n### 0.4.1 (unpublished)\n\n- [changed] rcdom now vendored as a module.\n\n### 0.4.0 (unpublished)\n\n- [changed] Update html5ever to v0.26.\n- [changed] MSRV is now 1.49.\n\n### 0.3.1\n\n- [changed] Update the build badges to reflect the updated CI configuration.\n\n### 0.3.0\n\n- [added] New experimental `from_read_coloured()` (under `ansi_colours` feature).\n- [added] Add `into_tagged_strings` and `tagged_strings` methods to `TaggedLine`\n  (thanks Robin Krahl)\n- [added] Add `width` method to `TaggedString` (thanks Robin Krahl)\n- [changed] Keep annotations in `TextRenderer::into_lines` (thanks Robin Krahl)\n- [fixed] Add colon to reference style link (thanks zakaluka)\n- [added] Allow text decorators to customise block prefix strings (thanks SardineFish)\n- [fixed] Fixed some problems rendering some complicated tables, including a panic\n  and near-infinite loops.\n- [changed] Tables which are too wide to possibly render in the given width are now\n  arranged vertically instead (with `///`) lines.\n- [changed] A number of small table rendering improvements.\n- [changed] MSRV is now 1.41.\n\n### 0.2.1\n\n- [added] New entry points - split HTML parsing from rendering the output,\n  thanks Robin Krahl.\n- [fixed] Decorators weren't being used for preformatted text.\n\n### 0.2.0\n\n- [added] Support `<s>` strikeout text.\n\n### 0.1.14 (2020-08-07)\n\n- [fixed] A table with an `id` attribute on `<tbody>` would be hidden.\n\n### 0.1.13 (2020-07-21)\n\n- [changed] Run cargo fmt (thanks crunchyjesus)\n- [added] CHANGELOG.md\n- [fixed] Some text near a fragment start (`id=\"foo\"` attribute) could be\n  lost if it needed breaking across lines.\n- [added] Experimentally add dependabot configuration.\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"html2text\"\ndescription = \"Render HTML as plain text.\"\nreadme = \"README.md\"\ndocumentation = \"https://docs.rs/html2text/\"\ncategories = [\"text-processing\"]\nkeywords = [\"html\", \"text\"]\nversion.workspace = true\nrepository.workspace = true\nlicense.workspace = true\nedition.workspace = true\nrust-version.workspace = true\n\n[workspace]\nmembers = [\n  \".\",\n  \"html2text-cli\",\n]\n\n[workspace.package]\nversion = \"0.17.1\"\nrepository = \"https://github.com/jugglerchris/rust-html2text/\"\nlicense = \"MIT\"\nauthors = [\"Chris Emerson <github@mail.nosreme.org>\"]\nedition = \"2024\"\nrust-version = \"1.85\"\n\n[dependencies]\nhtml5ever = \"0.39.0\"\ntendril = \"0.5\"\nunicode-width = \"0.2\"\nbacktrace = { version = \"0.3\", optional=true }\nthiserror = \"2.0.0\"\nlog = { version = \"0.4.20\", optional = true }\nnom = { version = \"8.0.0\", optional = true }\nxml5ever = { version = \"0.39.0\", optional = true }\n\n[features]\nhtml_trace = [\"dep:log\"]\nhtml_trace_bt = [\"html_trace\", \"dep:backtrace\"]\ndefault = []\ncss = [ \"dep:nom\" ]\ncss_ext = [\"css\"]\nxml = [\"dep:xml5ever\"]\n\n[[example]]\nname = \"html2term\"\npath = \"examples/html2term.rs\"\n\n[dev-dependencies]\nenv_logger = \"0.11.6\"\nargparse = \"0.2.2\"\nlog = \"0.4.20\"\n\n[target.'cfg(unix)'.dev-dependencies]\ntermion = \"4.0\"\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2016 Chris Emerson\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[![jugglerchris](https://circleci.com/gh/jugglerchris/rust-html2text.svg?branch=master&style=svg)](https://app.circleci.com/pipelines/github/jugglerchris/rust-html2text?filter=all)\n\n# html2text\n\nhtml2text is a [Rust](http://www.rust-lang.org/) crate which converts HTML to\nplain text (as in Rust `String`) or text spans with annotations like colours,\ne.g. optionally using CSS.  See [the online demo](https://jugglerchris.github.io/rust-html2text/)\nfor examples of the output.\n\n`cargo install html2text-cli` will install the `html2text` CLI wrapper.\n\nIt makes use of the [Servo project](https://github.com/servo/servo)'s HTML\nparser, [html5ever](https://github.com/servo/html5ever/), using the DOM to\ngenerate text (which can optionally include annotations for some features such\nas hyperlinks).\n\nThe project aims to do a reasonable job of rendering reasonable HTML in a\nterminal or other places where HTML needs to be converted to text (for\nexample the text/plain fallback in HTML e-mails).\n\nWith feature flags enabled (see below) some CSS/colour support is available.\n\n## Examples\n\nThe simple functions like `from_read()` return formatted text (in various\nformats including plain text).\n\n```rust\nuse html2text::from_read;\nlet html = b\"\n       <ul>\n         <li>Item one</li>\n         <li>Item two</li>\n         <li>Item three</li>\n       </ul>\";\nassert_eq!(from_read(&html[..], 20).unwrap(),\n           \"\\\n* Item one\n* Item two\n* Item three\n\");\n```\n\nA lower level API gives a bit more control.  This give the same result (except for\nreturning errors as Result instead of panicking):\n\n```rust\nuse html2text::config;\n\nlet html = b\"\n       <ul>\n         <li>Item one</li>\n         <li>Item two</li>\n         <li>Item three</li>\n       </ul>\";\n\nassert_eq!(\n    config::plain()\n           .string_from_read(&html[..], 20)\n           .unwrap(),\n    \"\\\n* Item one\n* Item two\n* Item three\n\");\n```\n\nA couple of simple demonstration programs are included as examples:\n\n### html2text\n\nThe simplest example uses `from_read` to convert HTML on stdin into plain\ntext:\n\n```sh\n$ cargo run --example html2text < foo.html\n[...]\n```\n\n### html2term\n\nA very simple example of using the rich interface (`from_read_rich`) for a\nslightly interactive console HTML viewer is provided as `html2term`.\n\n```sh\n$ cargo run --example html2term foo.html\n[...]\n```\n\nNote that this example takes the HTML file as a parameter so that it can\nread keys from stdin.\n\n## Cargo Features\n\n|Feature| Description|\n|-------|------------|\n|css    | Limited handling of CSS, adding Coloured nodes to the render tree. |\n|css\\_ext| Some CSS extensions (see below for details) |\n|xml | Support XHTML (for cases where a document may be parsed differently than as HTML). |\n|html\\_trace| Add verbose internal logging (not recommended) |\n|html\\_trace\\_bt| Add backtraces to the verbose internal logging |\n\n### CSS support\n\nWhen the `css` feature is enabled, some simple CSS handling is available.\n\nStyle rules are taken from:\n* If `Config::use_doc_css()` is called, then style from the document:\n  * `<style>` elements\n  * Inline `style` attributes (`<div style=\"...\">`)\n  * `<font color=...>`\n* Independently of `use_doc_css`, extra rules can be added with `Config::add_css(...)`\n\nThe following CSS features are implemented:\n* Basic selector matching (including child and descendents, classes and element\n  types).\n* Attribute selectors including `[foo]` and `[foo=\"bar\"]`\n* `nth-child` pseudo-class.\n* CSS colors (`color`/`background-color`) will add\n  `Coloured(...)`/`BgColoured(...)` nodes to the render tree.\n* Rules with `display: none` will cause matching elements to be removed from\n  the render tree.  The same is true if `overflow: hidden` or `overflow-y: hidden` and\n  the `height` or `max-height` are 0.\n* `content: \"text\"` inside a `::before`/`::after`\n* `white-space` values \"normal\", \"pre\", and \"prewrap\"\n\nThe CSS handling is expected to improve in future (PRs welcome), but not to a full-\nblown browser style system, which would be overkill for terminal output.\n\nThere are two ways to make use of the colours:\n* Use `from_read_rich()` or one of its variants.  One of the annotations you may get\n  back is `Colour(..)`.\n* Use `from_read_coloured()`.  This is similar to `from_read()`, but you provide\n  a function to add terminal colours (or other styling) based on the same\n  RichAnnotations.  See examples/html2text.rs for an example using termion.\n\n### CSS extensions (`css\\_ext` Cargo feature)\n\nThe following CSS extensions are implemented:\n\n* `x-syntax: foo;`\n  - Syntax-highlight with language \"foo\".  A highlighter needs to be registered\n    for that language with `config::register_highlighter()` - see the\n    `html2text-cli` for an example using `syntect`.\n* `display: x-raw-dom;`\n  - Show the HTML elements instead of rendering them.  (Useful for debugging, along\n    with something like `:nth-child(...)` to select a particular node)\n\n### XML/XHTML support\n\nIn some rare cases, parsing an XHTML document as HTML behaves differently.  For example:\n\n```xml\n<?xml version=\"1.0\"?>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<body>\n <h1/>\n <p>Not Heading</p>\n</body>\n</html>\n\n```\n\nHTML does not have the self-closing `<h1/>` form, so this would parse as an\nunclosed `<h1>` element, containing the following `<p>`, but in XHTML this is parsed as an empty `<h1>` followed by a `<p>`.\n\nWith the `xml` Cargo feature enabled, by default documents are parsed as XHTML\nif they start with the `<?xml?>` declaration and HTML otherwise.  This can be\nconfigured with `Config::xml_mode()`.\n"
  },
  {
    "path": "benches/tables.rs",
    "content": "#![feature(test)]\nextern crate html2text;\nextern crate test;\n\nuse ::test::Bencher;\n\nuse html2text::from_read;\n\nfn make_html(content: &str) -> String {\n    String::from(\"<html>\") + content + \"</html>\"\n}\n\nfn make_tab(cell: &str, rows: usize, cols: usize) -> String {\n    let mut result = String::from(\"<table>\");\n    for _ in 0..rows {\n        result.push_str(\"<tr>\");\n        for _ in 0..cols {\n            result.push_str(\"<td>\");\n            result.push_str(cell);\n            result.push_str(\"</td>\");\n        }\n        result.push_str(\"</tr>\");\n    }\n    result\n}\n\n#[bench]\nfn bench_empty(b: &mut Bencher) {\n    b.iter(|| from_read(make_html(\"\").as_bytes(), 80));\n}\n\n#[bench]\nfn bench_tab_1_1(b: &mut Bencher) {\n    b.iter(|| from_read(make_html(&make_tab(\"cell\", 1, 1)).as_bytes(), 80));\n}\n#[bench]\nfn bench_tab_2_2(b: &mut Bencher) {\n    b.iter(|| from_read(make_html(&make_tab(\"cell\", 2, 2)).as_bytes(), 80));\n}\n#[bench]\nfn bench_tab_3_3(b: &mut Bencher) {\n    b.iter(|| from_read(make_html(&make_tab(\"cell\", 3, 3)).as_bytes(), 80));\n}\n#[bench]\nfn bench_tab_4_4(b: &mut Bencher) {\n    b.iter(|| from_read(make_html(&make_tab(\"cell\", 4, 4)).as_bytes(), 80));\n}\n#[bench]\nfn bench_tab_5_5(b: &mut Bencher) {\n    b.iter(|| from_read(make_html(&make_tab(\"cell\", 5, 5)).as_bytes(), 80));\n}\n#[bench]\nfn bench_tab_6_6(b: &mut Bencher) {\n    b.iter(|| from_read(make_html(&make_tab(\"cell\", 6, 6)).as_bytes(), 80));\n}\n// Try a table with `depth` nested tables each with `rows` rows and `cols` columns.\nfn bench_tab_depth(b: &mut Bencher, content: &str, depth: usize, rows: usize, cols: usize) {\n    let mut t = String::from(content);\n    for _ in 0..depth {\n        t = make_tab(&t, rows, cols);\n    }\n    let html = make_html(&t);\n    b.iter(|| from_read(html.as_bytes(), 80));\n}\n#[bench]\nfn bench_tab_2_1_depth_2(b: &mut Bencher) {\n    bench_tab_depth(b, \"cell\", 2, 2, 1);\n}\n#[bench]\nfn bench_tab_3_1_depth_2(b: &mut Bencher) {\n    bench_tab_depth(b, \"cell\", 2, 3, 1);\n}\n#[bench]\nfn bench_tab_4_1_depth_2(b: &mut Bencher) {\n    bench_tab_depth(b, \"cell\", 2, 4, 1);\n}\n#[bench]\nfn bench_tab_1_2_depth_2(b: &mut Bencher) {\n    bench_tab_depth(b, \"cell\", 2, 1, 2);\n}\n#[bench]\nfn bench_tab_1_3_depth_2(b: &mut Bencher) {\n    bench_tab_depth(b, \"cell\", 2, 1, 3);\n}\n#[bench]\nfn bench_tab_1_4_depth_2(b: &mut Bencher) {\n    bench_tab_depth(b, \"cell\", 2, 1, 4);\n}\n#[bench]\nfn bench_tab_2_depth_2(b: &mut Bencher) {\n    bench_tab_depth(b, \"cell\", 2, 2, 2);\n}\n/*\n#[bench]\nfn bench_tab_2_depth_3(b: &mut Bencher) {\n    bench_tab_depth(b, \"cell\", 3, 2, 2);\n}\n#[bench]\nfn bench_tab_2_depth_4(b: &mut Bencher) {\n    bench_tab_depth(b, \"cell\", 4, 2, 2);\n}\n#[bench]\nfn bench_tab_2_depth_5(b: &mut Bencher) {\n    bench_tab_depth(b, \"cell\", 5, 2, 2);\n}\n*/\n"
  },
  {
    "path": "examples/html2term.rs",
    "content": "#[cfg(unix)]\nextern crate argparse;\n#[cfg(unix)]\nextern crate unicode_width;\n#[cfg(unix)]\nmod top {\n    #[cfg(feature = \"css\")]\n    use argparse::StoreFalse;\n    use argparse::{ArgumentParser, Store};\n    use html2text::render::{RichAnnotation, TaggedLine, TaggedLineElement};\n    use std::collections::HashMap;\n    use std::io::{self, Write};\n    use termion::cursor::Goto;\n    use termion::event::Key;\n    use termion::input::TermRead;\n    use termion::raw::IntoRawMode;\n    use termion::screen::IntoAlternateScreen;\n    use unicode_width::UnicodeWidthStr;\n\n    fn to_style(tag: &[RichAnnotation]) -> String {\n        use termion::color::*;\n        let mut style = String::new();\n\n        for ann in tag {\n            match *ann {\n                RichAnnotation::Default => (),\n                RichAnnotation::Link(_) => {\n                    style.push_str(&format!(\"{}\", termion::style::Underline));\n                }\n                RichAnnotation::Image(_) => {\n                    style.push_str(&format!(\"{}\", Fg(LightBlue)));\n                }\n                RichAnnotation::Emphasis => {\n                    style.push_str(&format!(\"{}\", Fg(LightGreen)));\n                }\n                RichAnnotation::Strong => {\n                    style.push_str(&format!(\"{}\", Fg(LightGreen)));\n                }\n                RichAnnotation::Strikeout => (),\n                RichAnnotation::Code => {\n                    style.push_str(&format!(\"{}\", Fg(LightYellow)));\n                }\n                RichAnnotation::Preformat(is_cont) => {\n                    if is_cont {\n                        style.push_str(&format!(\"{}\", Fg(LightMagenta)));\n                    } else {\n                        style.push_str(&format!(\"{}\", Fg(Magenta)));\n                    }\n                }\n                RichAnnotation::Colour(col) => {\n                    style.push_str(&format!(\"{}\", Fg(Rgb(col.r, col.g, col.b))));\n                }\n                RichAnnotation::BgColour(col) => {\n                    style.push_str(&format!(\"{}\", Bg(Rgb(col.r, col.g, col.b))));\n                }\n                _ => todo!(),\n            }\n        }\n        style\n    }\n\n    struct LinkMap {\n        lines: Vec<Vec<Option<String>>>, // lines[y][x] => Some(URL) or None\n    }\n\n    impl LinkMap {\n        pub fn link_at(&self, x: usize, y: usize) -> Option<&str> {\n            if let Some(linevec) = self.lines.get(y) {\n                if let Some(Some(text)) = linevec.get(x) {\n                    return Some(text);\n                }\n            }\n            None\n        }\n    }\n\n    fn link_from_tag(tag: &[RichAnnotation]) -> Option<String> {\n        let mut link = None;\n        for annotation in tag {\n            if let RichAnnotation::Link(text) = annotation {\n                link = Some(text.clone());\n            }\n        }\n        link\n    }\n\n    fn find_links(lines: &[TaggedLine<Vec<RichAnnotation>>]) -> LinkMap {\n        let mut map = Vec::new();\n        for line in lines {\n            let mut linevec = Vec::new();\n\n            for ts in line.tagged_strings() {\n                let link = link_from_tag(&ts.tag);\n                for _ in 0..UnicodeWidthStr::width(ts.s.as_str()) {\n                    linevec.push(link.clone());\n                }\n            }\n\n            map.push(linevec);\n        }\n        LinkMap { lines: map }\n    }\n\n    struct FragMap {\n        start_xy: HashMap<String, (usize, usize)>,\n    }\n\n    fn find_frags(lines: &[TaggedLine<Vec<RichAnnotation>>]) -> FragMap {\n        use self::TaggedLineElement::*;\n\n        let mut map = HashMap::new();\n        for (y, line) in lines.iter().enumerate() {\n            let mut x = 0;\n            for tli in line.iter() {\n                match tli {\n                    FragmentStart(fragname) => {\n                        map.insert(fragname.to_string(), (x, y));\n                    }\n                    Str(ts) => {\n                        x += UnicodeWidthStr::width(ts.s.as_str());\n                    }\n                }\n            }\n        }\n        FragMap { start_xy: map }\n    }\n\n    struct Options {\n        #[cfg(feature = \"css\")]\n        use_css: bool,\n    }\n\n    impl Options {\n        fn new() -> Options {\n            Options {\n                #[cfg(feature = \"css\")]\n                use_css: true,\n            }\n        }\n    }\n\n    pub fn main() {\n        let mut filename = String::new();\n        #[allow(unused_mut)]\n        let mut options = Options::new();\n        {\n            let mut ap = ArgumentParser::new();\n            ap.refer(&mut filename)\n                .add_argument(\"filename\", Store, \"Set HTML filename\");\n            #[cfg(feature = \"css\")]\n            ap.refer(&mut options.use_css)\n                .add_option(&[\"--no-css\"], StoreFalse, \"Disable CSS\");\n            ap.parse_args_or_exit();\n        }\n\n        let (width, height) = termion::terminal_size().unwrap();\n        let (width, height) = (width as usize, height as usize);\n\n        let mut file = std::fs::File::open(filename).expect(\"Tried to open file\");\n\n        let dom = html2text::config::plain()\n            .parse_html(&mut file)\n            .expect(\"Failed to parse HTML\");\n\n        let mut keys = io::stdin().keys();\n\n        // top_y is the (0-based) index of the document line shown at\n        // the top of the visible screen.\n        let mut top_y = 0;\n        // doc_x and doc_y are the logical (0-based) x and y of the\n        // cursor position within the document.\n        let mut doc_x = 0;\n        let mut doc_y = 0;\n\n        let mut screen = io::stdout()\n            .into_raw_mode()\n            .unwrap()\n            .into_alternate_screen()\n            .unwrap();\n\n        let mut annotated = rerender(&dom, &[], width, &options);\n\n        let link_map = find_links(&annotated);\n        let frag_map = find_frags(&annotated);\n\n        let mut inspect_path = vec![];\n\n        loop {\n            // max_y is the largest (0-based) index of a real document line.\n            let max_y = annotated.len().saturating_sub(1);\n\n            // Sanity-check the current screen position. max_y should\n            // be small enough that no blank lines beyond the end of\n            // the document are visible on screen (except when the\n            // document is shorter than a screenful); large enough\n            // that the cursor isn't off the bottom of the visible\n            // screen; and small enough that the cursor isn't off the\n            // top.\n            if max_y >= height - 1 {\n                top_y = std::cmp::min(top_y, max_y - (height - 1));\n            }\n            if doc_y >= height - 1 {\n                top_y = std::cmp::max(top_y, doc_y - (height - 1));\n            }\n            top_y = std::cmp::min(top_y, doc_y);\n\n            let opt_url = link_map.link_at(doc_x, doc_y);\n            let mut vis_y_limit = std::cmp::min(top_y + height, max_y + 1);\n            if !inspect_path.is_empty() {\n                vis_y_limit -= 1;\n            }\n            write!(screen, \"{}\", termion::clear::All).unwrap();\n            for (i, line) in annotated[top_y..vis_y_limit].iter().enumerate() {\n                write!(screen, \"{}\", Goto(1, i as u16 + 1)).unwrap();\n                for ts in line.tagged_strings() {\n                    let style = to_style(&ts.tag);\n                    let link = link_from_tag(&ts.tag);\n                    match (opt_url, link) {\n                        (Some(ref t1), Some(ref t2)) if t1 == t2 => {\n                            write!(screen, \"{}\", termion::style::Invert).unwrap();\n                        }\n                        _ => (),\n                    }\n                    write!(screen, \"{}{}{}\", style, ts.s, termion::style::Reset).unwrap();\n                }\n            }\n            if !inspect_path.is_empty() {\n                let mut pth = String::from(\"top \");\n                let mut node = dom.document.clone();\n\n                for &idx in &inspect_path {\n                    node = node.nth_child(idx).unwrap();\n                    pth.push_str(&format!(\"> {}\", node.element_name().unwrap()));\n                }\n                write!(\n                    screen,\n                    \"{}{}{:?}\",\n                    Goto(1, vis_y_limit as u16),\n                    pth,\n                    &inspect_path\n                )\n                .unwrap();\n            }\n\n            // 1-based screen coordinates\n            let cursor_x = (doc_x + 1) as u16;\n            let cursor_y = (doc_y - top_y + 1) as u16;\n            write!(screen, \"{}\", Goto(cursor_x, cursor_y)).unwrap();\n\n            screen.flush().unwrap();\n            if let Some(Ok(k)) = keys.next() {\n                match k {\n                    Key::Char('q') => break,\n                    Key::Char('j') | Key::Down => {\n                        if inspect_path.is_empty() {\n                            if doc_y < max_y {\n                                doc_y += 1;\n                            }\n                        } else {\n                            *inspect_path.last_mut().unwrap() += 1;\n                            if dom.get_node_by_path(&inspect_path).is_none() {\n                                // No next node - undo.\n                                *inspect_path.last_mut().unwrap() -= 1;\n                            } else {\n                                annotated = rerender(&dom, &inspect_path, width, &options);\n                            }\n                        }\n                    }\n                    Key::Char('k') | Key::Up => {\n                        if inspect_path.is_empty() {\n                            doc_y = doc_y.saturating_sub(1);\n                        } else if *inspect_path.last().unwrap() > 1 {\n                            *inspect_path.last_mut().unwrap() -= 1;\n                            annotated = rerender(&dom, &inspect_path, width, &options);\n                        }\n                    }\n                    Key::Char('h') | Key::Left => {\n                        if inspect_path.is_empty() {\n                            doc_x = doc_x.saturating_sub(1);\n                        } else if inspect_path.len() > 1 {\n                            inspect_path.pop();\n                            annotated = rerender(&dom, &inspect_path, width, &options);\n                        }\n                    }\n                    Key::Char('l') | Key::Right => {\n                        if inspect_path.is_empty() {\n                            if doc_x + 1 < width {\n                                doc_x += 1;\n                            }\n                        } else {\n                            inspect_path.push(1);\n                            if dom.get_node_by_path(&inspect_path).is_none() {\n                                inspect_path.pop();\n                            } else {\n                                annotated = rerender(&dom, &inspect_path, width, &options);\n                            }\n                        }\n                    }\n                    Key::Char(' ') | Key::PageDown => {\n                        // Ideally, move both the cursor and the top\n                        // visible line down by a whole page\n                        doc_y += height;\n                        top_y += height;\n\n                        // But bound the cursor within the document\n                        doc_y = std::cmp::min(doc_y, max_y);\n\n                        // And the standard bounds checking for top_y\n                        // will take care of the rest of the special\n                        // cases.\n                    }\n                    Key::PageUp => {\n                        // Ideally, move both the cursor and the top\n                        // visible line up by a whole page. But bound\n                        // both at zero.\n                        doc_y = std::cmp::max(doc_y, height) - height;\n                        top_y = std::cmp::max(top_y, height) - height;\n                    }\n                    Key::Home => {\n                        doc_y = 0;\n                    }\n                    Key::End => {\n                        doc_y = max_y;\n                    }\n                    Key::Char('\\t') => {}\n                    Key::Char('\\r') | Key::Char('\\n') => {\n                        if let Some(url) = opt_url {\n                            if let Some(u) = url.strip_prefix('#') {\n                                let start = frag_map.start_xy.get(u);\n                                if let Some((x, y)) = start {\n                                    doc_x = *x;\n                                    doc_y = *y;\n                                }\n                            }\n                        }\n                    }\n                    #[cfg(feature = \"css_ext\")]\n                    Key::Char('I') => {\n                        // Enter/leave inspect mode\n                        if inspect_path.is_empty() {\n                            inspect_path.push(1);\n                        } else {\n                            inspect_path.clear();\n                        }\n                        annotated = rerender(&dom, &inspect_path, width, &options);\n                    }\n                    _ => {}\n                }\n            }\n        }\n    }\n\n    fn rerender(\n        dom: &html2text::RcDom,\n        inspect_path: &[usize],\n        width: usize,\n        #[allow(unused)] options: &Options,\n    ) -> Vec<TaggedLine<Vec<RichAnnotation>>> {\n        let config = html2text::config::rich();\n        #[cfg(feature = \"css\")]\n        let config = if options.use_css {\n            config\n                .use_doc_css()\n                .add_agent_css(\n                    r#\"\n                    img {\n                        color: #77f;\n                    }\n                \"#,\n                )\n                .unwrap()\n        } else {\n            config\n        };\n        if inspect_path.is_empty() {\n            let render_tree = config\n                .dom_to_render_tree(dom)\n                .expect(\"Failed to build render tree\");\n            config\n                .render_to_lines(render_tree, width)\n                .expect(\"Failed to render\")\n        } else {\n            #[cfg(feature = \"css_ext\")]\n            {\n                let mut path_selector = String::new();\n                for &idx in &inspect_path[1..] {\n                    path_selector.push_str(&format!(\" > :nth-child({})\", idx));\n                }\n                let config = config\n                    .add_agent_css(\n                        &(format!(\n                            r#\"\n                    html {} {{\n                        color: white !important;\n                        background-color: black !important;\n                        display: x-raw-dom;\n                    }}\n                \"#,\n                            path_selector\n                        )),\n                    )\n                    .expect(\"Invalid CSS\");\n                let render_tree = config\n                    .dom_to_render_tree(dom)\n                    .expect(\"Failed to build render tree\");\n                config\n                    .render_to_lines(render_tree, width)\n                    .expect(\"Failed to render\")\n            }\n            #[cfg(not(feature = \"css_ext\"))]\n            unreachable!()\n        }\n    }\n}\n\n#[cfg(not(unix))]\nmod top {\n    pub fn main() {}\n}\n\nfn main() {\n    top::main()\n}\n"
  },
  {
    "path": "html2text-cli/Cargo.toml",
    "content": "[package]\nname = \"html2text-cli\"\ndescription = \"Render HTML as plain text.\"\nreadme = \"README.md\"\ncategories = [\"text-processing\"]\nkeywords = [\"html\", \"text\"]\ndefault-run = \"html2text\"\nversion.workspace = true\nrepository.workspace = true\nlicense.workspace = true\nedition.workspace = true\nrust-version.workspace = true\n\n[[bin]]\nname = \"html2text\"\npath = \"src/main.rs\"\n\n[dependencies]\nhtml2text = { version = \"0.17.0\", path = \"..\" }\nargparse = \"0.2.2\"\nlog = \"0.4.20\"\nsyntect = \"5.2.0\"\n\n[target.'cfg(unix)'.dependencies]\ntermion = \"4.0\"\n\n[features]\ndefault = [\"css\"]\ncss = [\"html2text/css\"]\ncss_ext = [\"html2text/css_ext\"]\nhtml_trace = [\"html2text/html_trace\"]\nhtml_trace_btr = [\"html2text/html_trace_bt\"]\nxml = [\"html2text/xml\"]\n"
  },
  {
    "path": "html2text-cli/README.md",
    "content": "# html2text\n\nA command-line tool to convert HTML into text in various forms.  Supports some\nCSS including colours with default features.\n\nThe default, which gives text with ASCII annotations similar to (but not\nexactly) markdown for things like `<strong>` elements and lists, and table\nformatting. It defaults to reading from standard input and writing to standard\noutput.\n\n```sh\nhtml2text foo.html\n```\n\nFor a more fancy terminal output, including ANSI colours, syntax highlighting\nusing syntect (`css_ext` cargo feature required) and added CSS rules:\n\n```sh\nhtml2text --colour --css --syntax --agent-css 'pre.rust { x-syntax: rs; }' < file_with_rust_code.html\n```\n"
  },
  {
    "path": "html2text-cli/src/main.rs",
    "content": "extern crate argparse;\nextern crate html2text;\nuse argparse::{ArgumentParser, Store, StoreOption, StoreTrue};\nuse html2text::config::{self, Config};\nuse html2text::render::{TextDecorator, TrivialDecorator};\nuse log::trace;\nuse std::io;\nuse std::io::Write;\n\n#[cfg(unix)]\nuse html2text::render::RichAnnotation;\n#[cfg(unix)]\nfn default_colour_map(\n    annotations: &[RichAnnotation],\n    s: &str,\n    use_css_colours: bool,\n    no_default_colours: bool,\n) -> String {\n    use RichAnnotation::*;\n    use termion::color::*;\n    // Explicit CSS colours override any other colours\n    let mut have_explicit_colour = no_default_colours;\n    let mut start = Vec::new();\n    let mut finish = Vec::new();\n    trace!(\"default_colour_map: str={s}, annotations={annotations:?}\");\n    for annotation in annotations.iter() {\n        match annotation {\n            Default => {}\n            Link(_) => {\n                start.push(format!(\"{}\", termion::style::Underline));\n                finish.push(format!(\"{}\", termion::style::Reset));\n            }\n            Image(_) => {\n                if !have_explicit_colour {\n                    start.push(format!(\"{}\", Fg(Blue)));\n                    finish.push(format!(\"{}\", Fg(Reset)));\n                }\n            }\n            Emphasis => {\n                start.push(format!(\"{}\", termion::style::Bold));\n                finish.push(format!(\"{}\", termion::style::Reset));\n            }\n            Strong => {\n                if !have_explicit_colour {\n                    start.push(format!(\"{}\", Fg(LightYellow)));\n                    finish.push(format!(\"{}\", Fg(Reset)));\n                }\n            }\n            Strikeout => {\n                if !have_explicit_colour {\n                    start.push(format!(\"{}\", Fg(LightBlack)));\n                    finish.push(format!(\"{}\", Fg(Reset)));\n                }\n            }\n            Code => {\n                if !have_explicit_colour {\n                    start.push(format!(\"{}\", Fg(Blue)));\n                    finish.push(format!(\"{}\", Fg(Reset)));\n                }\n            }\n            Preformat(_) => {\n                if !have_explicit_colour {\n                    start.push(format!(\"{}\", Fg(Blue)));\n                    finish.push(format!(\"{}\", Fg(Reset)));\n                }\n            }\n            Colour(c) => {\n                if use_css_colours {\n                    start.push(format!(\"{}\", Fg(Rgb(c.r, c.g, c.b))));\n                    finish.push(format!(\"{}\", Fg(Reset)));\n                    have_explicit_colour = true;\n                }\n            }\n            BgColour(c) => {\n                if use_css_colours {\n                    start.push(format!(\"{}\", Bg(Rgb(c.r, c.g, c.b))));\n                    finish.push(format!(\"{}\", Bg(Reset)));\n                }\n            }\n            _ => {}\n        }\n    }\n    // Reverse the finish sequences\n    finish.reverse();\n    let mut result = start.join(\"\");\n    result.push_str(s);\n    for s in finish {\n        result.push_str(&s);\n    }\n    trace!(\"default_colour_map: output={result}\");\n    result\n}\n\n#[cfg(feature = \"css_ext\")]\nfn do_syntect_highlight<'t>(text: &'t str, language: &str) -> Vec<(html2text::TextStyle, &'t str)> {\n    use html2text::{Colour, TextStyle};\n    use syntect::{\n        easy::HighlightLines, highlighting::ThemeSet, parsing::SyntaxSet, util::LinesWithEndings,\n    };\n\n    let ps = SyntaxSet::load_defaults_newlines();\n    let ts = ThemeSet::load_defaults();\n\n    let syntax = ps.find_syntax_by_extension(&language).unwrap();\n    let mut h = HighlightLines::new(syntax, &ts.themes[\"Solarized (dark)\"]);\n\n    let mut results = Vec::new();\n    for line in LinesWithEndings::from(&text) {\n        let ranges: Vec<(syntect::highlighting::Style, &str)> =\n            h.highlight_line(line, &ps).unwrap();\n\n        fn convert(c: syntect::highlighting::Color) -> Colour {\n            Colour {\n                r: c.r,\n                g: c.g,\n                b: c.b,\n            }\n        }\n        for (sty, text) in ranges {\n            results.push((\n                TextStyle::colours(convert(sty.foreground), convert(sty.background)),\n                text,\n            ));\n        }\n    }\n    results\n}\n\nfn update_config<T: TextDecorator>(mut config: Config<T>, flags: &Flags) -> Config<T> {\n    if let Some(wrap_width) = flags.wrap_width {\n        config = config.max_wrap_width(wrap_width);\n    }\n    #[cfg(feature = \"css\")]\n    if flags.use_css {\n        config = config.use_doc_css();\n    }\n    #[cfg(feature = \"css\")]\n    if !flags.agent_css.is_empty() {\n        config = config.add_agent_css(&flags.agent_css).expect(\"Invalid CSS\");\n    }\n    #[cfg(feature = \"css_ext\")]\n    if flags.syntax_highlight {\n        config = config\n            .register_highlighter(\"rs\", Box::new(|text| do_syntect_highlight(text, \"rs\")))\n            .register_highlighter(\"html\", Box::new(|text| do_syntect_highlight(text, \"html\")));\n    }\n    match (flags.link_footnotes, flags.no_link_footnotes) {\n        (true, true) => {\n            eprintln!(\"Error: can't specify both --link-footnotes and --no-link-footnotes\");\n            std::process::exit(1);\n        }\n        (true, false) => config = config.link_footnotes(true),\n        (false, true) => config = config.link_footnotes(false),\n        (false, false) => {}\n    };\n    if flags.pad_width {\n        config = config.pad_block_width();\n    }\n    config\n}\n\nfn translate<R>(input: R, flags: Flags, literal: bool) -> String\nwhere\n    R: io::Read,\n{\n    #[cfg(unix)]\n    {\n        if flags.use_colour {\n            let conf = config::rich();\n            let conf = update_config(conf, &flags);\n            #[cfg(feature = \"css_ext\")]\n            let conf = if flags.show_dom {\n                conf.add_agent_css(\"body { display: x-raw-dom !important; }\")\n                    .unwrap()\n            } else {\n                conf\n            };\n            #[cfg(feature = \"css\")]\n            let use_css_colours = !flags.ignore_css_colours;\n            #[cfg(not(feature = \"css\"))]\n            let use_css_colours = false;\n            #[cfg(feature = \"css\")]\n            let use_only_css = flags.use_only_css;\n            #[cfg(not(feature = \"css\"))]\n            let use_only_css = false;\n            return conf\n                .coloured(input, flags.width, move |anns, s| {\n                    default_colour_map(anns, s, use_css_colours, use_only_css)\n                })\n                .unwrap();\n        }\n    }\n    #[cfg(feature = \"css\")]\n    {\n        if flags.show_css {\n            let conf = config::plain();\n            let conf = update_config(conf, &flags);\n            let dom = conf.parse_html(input).unwrap();\n            return html2text::dom_to_parsed_style(&dom).expect(\"Parsing CSS\");\n        }\n    }\n    if flags.show_dom {\n        let conf = config::plain();\n        let conf = update_config(conf, &flags);\n        let dom = conf.parse_html(input).unwrap();\n        dom.as_dom_string()\n    } else if flags.show_render {\n        let conf = config::plain();\n        let conf = update_config(conf, &flags);\n        let dom = conf.parse_html(input).unwrap();\n        let rendertree = conf.dom_to_render_tree(&dom).unwrap();\n        rendertree.to_string()\n    } else if literal {\n        let conf = config::with_decorator(TrivialDecorator::new());\n        let conf = update_config(conf, &flags);\n        conf.string_from_read(input, flags.width).unwrap()\n    } else {\n        let conf = config::plain();\n        let conf = update_config(conf, &flags);\n        conf.string_from_read(input, flags.width).unwrap()\n    }\n}\n\n#[derive(Debug)]\nstruct Flags {\n    width: usize,\n    wrap_width: Option<usize>,\n    #[allow(unused)]\n    use_colour: bool,\n    #[cfg(feature = \"css\")]\n    use_css: bool,\n    #[cfg(feature = \"css\")]\n    ignore_css_colours: bool,\n    #[cfg(feature = \"css\")]\n    use_only_css: bool,\n    show_dom: bool,\n    show_render: bool,\n    #[cfg(feature = \"css\")]\n    show_css: bool,\n    pad_width: bool,\n    link_footnotes: bool,\n    no_link_footnotes: bool,\n    #[cfg(feature = \"css_ext\")]\n    syntax_highlight: bool,\n    #[cfg(feature = \"css\")]\n    agent_css: String,\n}\n\nfn main() {\n    #[cfg(feature = \"html_trace\")]\n    env_logger::init();\n\n    let mut infile: Option<String> = None;\n    let mut outfile: Option<String> = None;\n    let mut flags = Flags {\n        width: 80,\n        wrap_width: None,\n        use_colour: false,\n        #[cfg(feature = \"css\")]\n        use_css: false,\n        #[cfg(feature = \"css\")]\n        ignore_css_colours: false,\n        #[cfg(feature = \"css\")]\n        use_only_css: false,\n        show_dom: false,\n        show_render: false,\n        #[cfg(feature = \"css\")]\n        show_css: false,\n        #[cfg(feature = \"css\")]\n        agent_css: Default::default(),\n        pad_width: false,\n        link_footnotes: false,\n        no_link_footnotes: false,\n        #[cfg(feature = \"css_ext\")]\n        syntax_highlight: false,\n    };\n    let mut literal: bool = false;\n\n    {\n        let mut ap = ArgumentParser::new();\n        ap.refer(&mut infile).add_argument(\n            \"infile\",\n            StoreOption,\n            \"Input HTML file (default is standard input)\",\n        );\n        ap.refer(&mut flags.width).add_option(\n            &[\"-w\", \"--width\"],\n            Store,\n            \"Column width to format to (default is 80)\",\n        );\n        ap.refer(&mut flags.wrap_width).add_option(\n            &[\"-W\", \"--wrap-width\"],\n            StoreOption,\n            \"Maximum text wrap width (default same as width)\",\n        );\n        ap.refer(&mut outfile).add_option(\n            &[\"-o\", \"--output\"],\n            StoreOption,\n            \"Output file (default is standard output)\",\n        );\n        ap.refer(&mut literal).add_option(\n            &[\"-L\", \"--literal\"],\n            StoreTrue,\n            \"Output only literal text (no decorations)\",\n        );\n        ap.refer(&mut flags.pad_width).add_option(\n            &[\"--pad-width\"],\n            StoreTrue,\n            \"Pad blocks to their full width\",\n        );\n        ap.refer(&mut flags.link_footnotes).add_option(\n            &[\"--link-footnotes\"],\n            StoreTrue,\n            \"Enable link footnotes\",\n        );\n        ap.refer(&mut flags.no_link_footnotes).add_option(\n            &[\"--no-link-footnotes\"],\n            StoreTrue,\n            \"Enable link footnotes\",\n        );\n        #[cfg(unix)]\n        ap.refer(&mut flags.use_colour).add_option(\n            &[\"--colour\"],\n            StoreTrue,\n            \"Use ANSI terminal colours\",\n        );\n        #[cfg(feature = \"css\")]\n        ap.refer(&mut flags.use_css)\n            .add_option(&[\"--css\"], StoreTrue, \"Enable CSS\");\n        #[cfg(feature = \"css\")]\n        ap.refer(&mut flags.ignore_css_colours)\n            .add_option(&[\"--ignore-css-colour\"], StoreTrue, \"With --css, ignore CSS colour information (still hides elements with e.g. display: none)\");\n        #[cfg(feature = \"css\")]\n        ap.refer(&mut flags.use_only_css).add_option(\n            &[\"--only-css\"],\n            StoreTrue,\n            \"Don't use default non-CSS colours\",\n        );\n        ap.refer(&mut flags.show_dom).add_option(\n            &[\"--show-dom\"],\n            StoreTrue,\n            \"Show the parsed HTML DOM instead of rendered output\",\n        );\n        ap.refer(&mut flags.show_render).add_option(\n            &[\"--show-render\"],\n            StoreTrue,\n            \"Show the computed render tree instead of the rendered output\",\n        );\n        #[cfg(feature = \"css\")]\n        ap.refer(&mut flags.show_css).add_option(\n            &[\"--show-css\"],\n            StoreTrue,\n            \"Show the parsed CSS instead of rendered output\",\n        );\n        #[cfg(feature = \"css\")]\n        ap.refer(&mut flags.agent_css).add_option(\n            &[\"--agent-css\"],\n            Store,\n            \"Add some CSS rules (to the agent spreadsheet)\",\n        );\n        #[cfg(feature = \"css_ext\")]\n        ap.refer(&mut flags.syntax_highlight).add_option(\n            &[\"--syntax\"],\n            StoreTrue,\n            \"Enable syntax highlighting of <pre> blocks.\",\n        );\n        ap.parse_args_or_exit();\n    }\n\n    let data = match infile {\n        None => {\n            let stdin = io::stdin();\n\n            translate(&mut stdin.lock(), flags, literal)\n        }\n        Some(name) => {\n            let mut file = std::fs::File::open(name).expect(\"Tried to open file\");\n            translate(&mut file, flags, literal)\n        }\n    };\n\n    match outfile {\n        None => {\n            print!(\"{}\", data);\n        }\n        Some(name) => {\n            let mut file = std::fs::File::create(name).expect(\"Tried to create file\");\n            write!(file, \"{}\", data).unwrap();\n        }\n    };\n}\n"
  },
  {
    "path": "html2text-web-demo/.cargo/config.toml",
    "content": "[build]\ntarget = \"wasm32-wasip2\"\n"
  },
  {
    "path": "html2text-web-demo/.gitignore",
    "content": "dist\n"
  },
  {
    "path": "html2text-web-demo/Cargo.toml",
    "content": "[package]\nname = \"html2text-web-demo\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\nhtml2text = { path = \"..\", features = [\"css\"] }\nratzilla = \"0.0.6\"\nwasm-bindgen = \"0.2.100\"\n\n[lib]\ncrate-type = [\"cdylib\", \"rlib\"]\n"
  },
  {
    "path": "html2text-web-demo/Trunk.toml",
    "content": "trunk-version = \"^0.21.13\"\n\n[build]\npublic_url = \"/rust-html2text/\"\nrelease = true\nfilehash = false\ninject_scripts = true #false\noffline = false #true\nfrozen = true\nminify = \"on_release\"\n"
  },
  {
    "path": "html2text-web-demo/index.html",
    "content": "<!doctype html>\n<html>\n    <head>\n<style>\nbody {\n    margin: 0;\n}\npre {\n    margin: 0;\n}\n#main, #lib {\n    background-color: black;\n}\n#input_html {\n    width: 100%;\n    height: 300px;\n}\n</style>\n<script>\nfunction update_html() {\n    const text = document.getElementById(\"input_html\").value;\n    const css = document.getElementById(\"conf_css\").checked;\n    const colour = document.getElementById(\"conf_colour\").checked;\n\n    let conf = wasmBindings.Config.new();\n    console.log(\"CSS:\", css);\n    if (css) {\n        conf.use_css();\n    }\n    if (colour) {\n        conf.use_colour();\n    }\n\n    wasmBindings.format_html(conf, text);\n}\nwindow.addEventListener(\"TrunkApplicationStarted\", update_html);\n</script>\n    </head>\n    <body>\n        <h1>Html2text demo</h1>\n        <input type=\"checkbox\" id=\"conf_css\" checked=true onchange=\"update_html()\">CSS</input>\n        <br>\n        <input type=\"checkbox\" id=\"conf_colour\" checked=true onchange=\"update_html()\">Colour</input>\n        <br>\n        <textarea id=\"input_html\" onchange=\"update_html()\" oninput=\"update_html()\">&lt;html&gt;\n&lt;style&gt;\n.green {\n    color: #4f4;\n}\n&lt;/style&gt;\n&lt;body&gt;\n  &lt;h1&gt;Hi there&lt;/h1&gt;\n  &lt;p&gt;This is some simple text&lt;/p&gt;\n  &lt;ol&gt;\n    &lt;li&gt;Item one&lt;/li&gt;\n    &lt;li&gt;&lt;s&gt;Item two&lt/s&gt&lt;/li&gt;\n    &lt;li class=\"green\"&gt;Item three&lt;/li&gt;\n  &lt;/ol&gt;\n&lt;table&gt;\n    &lt;tr&gt;&lt;th&gt;Heading 1&lt;/th&gt;&lt;th&gt;Heading 2&lt;/th&gt;&lt;th&gt;Heading 3&lt;/th&gt;&lt;/tr&gt;\n    &lt;tr&gt;&lt;td&gt;Data 1&lt;/td&gt;&lt;td&gt;Data 2&lt;/td&gt;&lt;td&gt;Data 3&lt;/td&gt;&lt;/tr&gt;\n    &lt;tr&gt;&lt;td colspan=3&gt;Hello there&lt;/td&gt;&lt;/tr&gt;\n&lt;/table&gt;\n&lt;/body&gt;&lt;/html&gt;\n        </textarea>\n        <br>\n        <button onclick=\"update_html()\">Update</button>\n        <div id=\"lib\"></div>\n    </body>\n</html>\n"
  },
  {
    "path": "html2text-web-demo/src/lib.rs",
    "content": "use wasm_bindgen::prelude::wasm_bindgen;\n\nuse ratzilla::ratatui::{\n    style::{Color, Style, Stylize},\n    text::{Text, Line, Span},\n    widgets::{Block, Paragraph},\n    Frame,\n    Terminal,\n};\n\nuse html2text::{\n    config::ImageRenderMode,\n    render::TextDecorator,\n};\nuse ratzilla::DomBackend;\n\n#[derive(Default)]\n#[wasm_bindgen]\npub struct Config {\n    css: bool,\n    colour: bool,\n    user_css: Option<String>,\n    agent_css: Option<String>,\n    pad_block_width: bool,\n    wrap_width: Option<usize>,\n    allow_overflow: bool,\n    min_wrap_width: Option<usize>,\n    raw_mode: bool,\n    no_borders: bool,\n    no_link_wrap: bool,\n    unicode_so: bool,\n    do_decorate: bool,\n    link_footnotes: bool,\n    image_mode: ImageRenderMode,\n}\n\n#[wasm_bindgen]\nimpl Config {\n    pub fn new() -> Self {\n        Config {\n            ..Default::default()\n        }\n    }\n\n    pub fn use_colour(&mut self) {\n        self.colour = true;\n    }\n\n    pub fn use_css(&mut self) {\n        self.css = true;\n    }\n\n    pub fn add_user_css(&mut self, css: String) {\n        if css.trim().is_empty() {\n            self.user_css = None;\n        } else {\n            self.user_css = Some(css);\n        }\n    }\n\n    pub fn add_agent_css(&mut self, css: String) {\n        if css.trim().is_empty() {\n            self.agent_css = None;\n        } else {\n            self.agent_css = Some(css);\n        }\n    }\n\n    pub fn pad_block_width(&mut self) {\n        self.pad_block_width = true;\n    }\n\n    pub fn max_wrap_width(&mut self, width: usize) {\n        self.wrap_width = Some(width);\n    }\n\n    pub fn allow_overflow(&mut self) {\n        self.allow_overflow = true;\n    }\n\n    pub fn min_wrap_width(&mut self, width: usize) {\n        self.min_wrap_width = Some(width);\n    }\n    pub fn raw_mode(&mut self) {\n        self.raw_mode = true;\n    }\n    pub fn no_borders(&mut self) {\n        self.no_borders = true;\n    }\n    pub fn no_link_wrap(&mut self) {\n        self.no_link_wrap = true;\n    }\n    pub fn unicode_so(&mut self) {\n        self.unicode_so = true;\n    }\n    pub fn do_decorate(&mut self) {\n        self.do_decorate = true;\n    }\n    pub fn link_footnotes(&mut self, value: bool) {\n        self.link_footnotes = value;\n    }\n\n    pub fn image_mode(&mut self, value: &str) {\n        match value {\n            \"ignore\" => self.image_mode = ImageRenderMode::IgnoreEmpty,\n            \"always\" => self.image_mode = ImageRenderMode::ShowAlways,\n            \"replace\" => self.image_mode = ImageRenderMode::Replace(\"XX\"),\n            \"filename\" =>  self.image_mode = ImageRenderMode::Filename,\n            _ => self.image_mode = ImageRenderMode::IgnoreEmpty,\n        }\n    }\n\n    fn update_conf<D: TextDecorator>(&self, conf: html2text::config::Config<D>) -> Result<html2text::config::Config<D>, String> {\n        let mut conf = if self.css {\n            conf.use_doc_css()\n        } else {\n            conf\n        };\n        if let Some(user_css) = &self.user_css {\n            conf = conf.add_css(user_css).map_err(|e| format!(\"{}\", e))?;\n        }\n        if let Some(agent_css) = &self.agent_css {\n            conf = conf.add_agent_css(agent_css).map_err(|e| format!(\"{}\", e))?;\n        }\n        if self.pad_block_width {\n            conf = conf.pad_block_width();\n        }\n        if let Some(width) = self.wrap_width {\n            conf = conf.max_wrap_width(width);\n        }\n        if self.allow_overflow {\n            conf = conf.allow_width_overflow();\n        }\n        if let Some(width) = self.min_wrap_width {\n            conf = conf.min_wrap_width(width);\n        }\n        if self.raw_mode {\n            conf = conf.raw_mode(true);\n        }\n        if self.no_borders {\n            conf = conf.no_table_borders();\n        }\n        if self.no_link_wrap {\n            conf = conf.no_link_wrapping();\n        }\n        if self.unicode_so {\n            conf = conf.unicode_strikeout(true);\n        }\n        if self.do_decorate {\n            conf = conf.do_decorate();\n        }\n        conf = conf.link_footnotes(self.link_footnotes);\n        if self.image_mode != ImageRenderMode::IgnoreEmpty {\n            conf = conf.empty_img_mode(self.image_mode);\n        }\n        Ok(conf\n            .unicode_strikeout(false))\n    }\n}\n\nfn do_render_colour(f: &mut Frame, config: &Config, input: &[u8]) -> Result<(), String> {\n    let area = f.area();\n\n    let conf = config.update_conf(html2text::config::rich())?;\n\n    let lines = conf.lines_from_read(input, area.width as usize - 2).unwrap();\n    let mut out = Text::default();\n    for line in lines {\n        let mut term_line = Line::default();\n        for piece in line.tagged_strings() {\n            let span = Span::from(dbg!(piece.s.clone()));\n            let mut style = Style::new();\n            for attr in &piece.tag {\n                use html2text::render::RichAnnotation::*;\n                match attr {\n                    Default | Link(_) | Image(_) | Code | Preformat(_) => {}\n                    Emphasis => {\n                        style = style.italic();\n                    }\n                    Strong => {\n                        style = style.bold();\n                    }\n                    Strikeout => {\n                        style = style.crossed_out();\n                    }\n                    Colour(col) => {\n                        style = style.fg(Color::Rgb(col.r, col.g, col.b));\n                    }\n                    BgColour(col) => {\n                        style = style.bg(Color::Rgb(col.r, col.g, col.b));\n                    }\n                    _ => {}\n                }\n            }\n            term_line.push_span(span.style(style));\n        }\n        out.push_line(term_line);\n    }\n    f.render_widget(\n        Paragraph::new(out).block(Block::bordered().title(\"HTML\").border_style(Color::Yellow)),\n        f.area());\n    Ok(())\n}\n\n#[wasm_bindgen]\npub fn format_html(config: Config, input: &str) -> Result<(), String> {\n    let backend = DomBackend::new_by_id(\"lib\").unwrap();\n    let mut terminal = Terminal::new(backend).unwrap();\n\n    let inp = input.to_string();\n    terminal.draw(move |f| {\n        if config.colour {\n            do_render_colour(f, &config, inp.as_bytes()).unwrap();\n        } else {\n            let area = f.area();\n\n            let conf = config.update_conf(html2text::config::plain()).unwrap();\n            let output = conf.string_from_read(inp.as_bytes(), area.width as usize).unwrap();\n\n            f.render_widget(\n                Paragraph::new(output),\n                f.area());\n        }\n    }).map_err(|e| format!(\"{e}\"))?;\n    Ok(())\n}\n"
  },
  {
    "path": "pages/.gitignore",
    "content": "_site\n"
  },
  {
    "path": "pages/_config.yml",
    "content": "lsi: false\nsafe: true\nsource: .\nincremental: false\nbaseurl: \"/rust-html2text\"\ngist:\n  noscript: false\n\ntheme: minima\n\ngithub_username: jugglerchris\n"
  },
  {
    "path": "pages/_includes/head.html",
    "content": "<head>\n  <meta charset=\"utf-8\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n  {%- seo -%}\n  <link rel=\"stylesheet\" href=\"{{ \"/assets/main.css\" | relative_url }}\">\n  {%- feed_meta -%}\n  {%- if jekyll.environment == 'production' and site.google_analytics -%}\n    {%- include google-analytics.html -%}\n  {%- endif -%}\n  {%- if page.h2t_wasm -%}\n  <link rel=modulepreload src=\"{{\"/assets/html2text-web-demo.js\" | relative_url }}\">\n  <link rel=preload src=\"{{\"/assets/html2text-web-demo-bg.wasm\" | relative_url }}\" as=\"fetch\" type=\"application/wasm\" crossorigin=\"\">\n  <link rel=\"stylesheet\" href=\"{{ \"/assets/demo.css\" | relative_url }}\">\n  {%- endif -%}\n  {%- if page.h2t_js -%}\n  <script src=\"{{ page.h2t_js | relative_url }}\">\n  </script>\n  {%- endif -%}\n</head>\n"
  },
  {
    "path": "pages/assets/demo-main.js",
    "content": "const img_modes = {\n    \"ignore\": \"IgnoreEmpty\",\n    \"always\": \"ShowAlways\",\n    \"replace\": \"Replace(\\\"XX\\\")\",\n    \"filename\": \"Filename\",\n};\nconst controls = [\n    // Element id, (conf, value) -> \"rust code\"\n    [\"conf_css\", (conf, value) => { conf.use_css(); return \".use_doc_css()\"; }],\n    [\"conf_user_css\", (conf, value) => { conf.add_user_css(value); return `.add_css(r#\"{value}\"#)`; }],\n    [\"conf_agent_css\", (conf, value) => { conf.add_agent_css(value); return `.add_agent_css(r#\"{value}\"#)`; }],\n    [\"conf_pad_block_width\", (conf, value) => { conf.bad_block_width(); return `.pad_block_width()`; }],\n    [\"conf_wrap_width\", (conf, value) => { conf.max_wrap_width(value); return `.max_wrap_width({value})`; }],\n    [\"conf_allow_overflow\", (conf, value) => { conf.allow_overflow(); return `.allow_width_overflow()`; }],\n    [\"conf_min_wrap_width\", (conf, value) => { conf.min_wrap_width(value); return `.min_wrap_width({value})`; }],\n    [\"conf_raw\", (conf, value) => { conf.raw_mode(); return `.raw_mode(true)`; }],\n    [\"conf_no_borders\", (conf, value) => { conf.no_borders(); return `.no_table_borders(true)`; }],\n    [\"conf_no_link_wrap\", (conf, value) => { conf.no_link_wrap(); return `.no_link_wrapping(true)`; }],\n    [\"conf_unicode_so\", (conf, value) => { conf.unicode_so(); return `.unicode_strikeout(true)`; }],\n    [\"conf_do_decorate\", (conf, value) => { conf.do_decorate(); return `.do_decorate(true)`; }],\n    [\"conf_link_footnotes\", (conf, value) => { conf.link_footnotes(value); return `.link_footnotes({value})`; }],\n    [\"conf_img_mode\", (conf, value) => { conf.image_mode(value); return '.empty_img_mode(ImageRenderMode::' + img_modes[value] + \")\"; }],\n\n];\nfunction update_html() {\n    const text = document.getElementById(\"input_html\").value;\n    const colour = document.getElementById(\"conf_colour\").checked;\n\n    const raw = document.getElementById(\"conf_raw\").checked;\n    const no_borders = document.getElementById(\"conf_no_borders\").checked;\n    const no_link_wrap = document.getElementById(\"conf_no_link_wrap\").checked;\n    const unicode_so = document.getElementById(\"conf_unicode_so\").checked;\n    const do_decorate = document.getElementById(\"conf_do_decorate\").checked;\n    const link_footnotes = document.getElementById(\"conf_link_footnotes\").checked;\n\n    let rust_code = \"\";\n\n    let conf = wasmBindings.Config.new();\n    if (colour) {\n        rust_code += \"let config = html2text::config::rich()\";\n        conf.use_colour();\n    } else {\n        rust_code += \"let config = html2text::config::plain()\";\n    }\n    for (const conf_desc of controls) {\n        const elt_id = conf_desc[0];\n        const handler = conf_desc[1];\n\n        const elt = document.getElementById(elt_id);\n        if (elt.type == \"checkbox\") {\n            if (elt.checked) {\n                let codefrag = handler(conf, elt.checked);\n                if (codefrag) {\n                    rust_code += \"\\n    \" + codefrag;\n                }\n            }\n        } else {\n            if (elt.value) {\n                let codefrag = handler(conf, elt.value);\n                if (codefrag) {\n                    rust_code += \"\\n    \" + codefrag;\n                }\n            }\n        }\n    }\n\n    rust_code += \";\\n\";\n    if (colour) {\n        rust_code += `\nlet lines = conf.lines_from_read(input, width);\nfor line in lines {\n    for ts in line.tagged_strings() {\n        // examine tags for each text span for colours etc.\n    }\n}\n`;\n    } else {\n        rust_code += `\nlet text = conf.string_from_read(input, width);\n`;\n    }\n\n    let tn = document.createTextNode(rust_code);\n    document.getElementById(\"rust-code\").replaceChildren(tn);\n    wasmBindings.format_html(conf, text);\n}\n\nfunction start() {\n    const confItems = document.querySelectorAll(\"input\");\n    confItems.forEach((elt) => {\n        elt.addEventListener(\"change\", update_html);\n    });\n    const selectItems = document.querySelectorAll(\"select\");\n    selectItems.forEach((elt) => {\n        elt.addEventListener(\"change\", update_html);\n    });\n    // Do the first render\n    update_html();\n}\nwindow.addEventListener(\"TrunkApplicationStarted\", start);\n"
  },
  {
    "path": "pages/assets/demo.css",
    "content": "#lib {\n    background-color: black;\n    height: 30em;\n    overflow: scroll;\n}\n#input_html {\n    height: 300px;\n    width: 95%;\n    overflow: scroll;\n}\n\n#lib pre {\n    margin: 0;\n    padding: 0;\n    overflow: hidden;\n    background-color: black;\n    border: 0px;\n}\n\n.warning {\n    color: red;\n}\n.warning::before {\n    content: \"⚠️\";\n}\n\ndiv.wrapper {\n    max-width: 100%;\n}\n@media screen and (min-width: 1000px) {\n    #h2tmain {\n        display: grid;\n        gap: 10px;\n        grid-template-columns: 1fr 1fr;\n    }\n    #lib_container {\n        grid-column: 1;\n        min-width: 45%;\n    }\n    #input_container {\n        grid-column: 1;\n        grid-row-start: 2;\n        min-width: 45%;\n    }\n    #configtable {\n        grid-column: 2;\n        grid-row-start: 1;\n        grid-row-end: 3;\n        min-width: 45%;\n    }\n    #rust-code-pre {\n        grid-column: 2;\n        min-width: 45%;\n    }\n\n}\n"
  },
  {
    "path": "pages/index.markdown",
    "content": "---\n# Feel free to add content and custom Front Matter to this file.\n# To modify the layout, see https://jekyllrb.com/docs/themes/#overriding-theme-defaults\n\ntitle: html2text API demo\nlayout: home\n\n# Local additions\nh2t_wasm: true\nh2t_js: \"/assets/demo-main.js\"\n---\n\n<noscript>\n<h2 class=\"warning\">This demo page requires javascript (and WASM) to work.</h2>\n</noscript>\n\nAn online demonstration of the\n[`html2text`](https://github.com/jugglerchris/rust-html2text) Rust crate. Edit\nthe HTML below and see how `html2text` converts it for text or terminal\ndisplay.\n\nThis demo uses `html2text` compiled to WASM, which can run in any modern\nbrowser, with [ratzilla](https://github.com/orhun/ratzilla) for the web-based\nterminal output.\n\n<div id=\"h2tmain\" markdown=\"1\">\n\n<div id=\"lib_container\" markdown=\"1\">\n\n## Output\n\nThe html2text output is updated here:\n\n<div id=\"lib\"></div>\n\n</div>\n\n<div id=\"input_container\">\n<h2>Input HTML</h2>\n<p>Edit the HTML here - the output will update live.</p>\n<textarea id=\"input_html\" onchange=\"update_html()\" oninput=\"update_html()\">\n<html>\n<style>\n.green {\n    color: #4f4;\n}\n</style>\n<body>\n  <h1>Hi there</h1>\n  <p>This is some simple text with a <a href=\"https://github.com/jugglerchris/html2text/\">link to github</a></p>\n  <ol>\n    <li>Item one</li>\n    <li><s>Item two</s></li>\n    <li class=\"green\">Item three</li>\n  </ol>\n<table>\n    <tr><th>Heading 1</th><th>Heading 2</th><th>Heading 3</th></tr>\n    <tr><td>Data 1</td><td>Data 2</td><td>Data 3</td></tr>\n    <tr><td colspan=3>Hello there</td></tr>\n</table>\n</body></html>\n</textarea>\n</div>\n<div id=\"configtable\" markdown=\"1\">\n\n## Configuration\n\nThe following are the configuration settings (accessible via [`html2text::config`](https://docs.rs/html2text/latest/html2text/config/struct.Config.html)).\n\n| <input type=\"checkbox\" id=\"conf_colour\" checked=true>Use Rich output | The [`rich`](https://docs.rs/html2text/latest/html2text/config/fn.rich.html) mode returns spans with attributes (like hyperlinks, emphasis, or colours).  When disabled ([`plain`](https://docs.rs/html2text/latest/html2text/config/fn.plain.html)), the output is a plain `String` (possibly with formatting depending on other settings, e.g. table borders or `**markdown-style**` characters added).  Rich output adds extra information (annotations) to allow, for example, using terminal colours and other features for a nicer TUI.  |\n| <input type=\"checkbox\" id=\"conf_css\" checked=true>use_doc_css | Parse CSS from the HTML document (css) |\n| <input type=\"text\" id=\"conf_user_css\">User CSS | Add user stylesheet rules (css) |\n| <input type=\"text\" id=\"conf_agent_css\">Agent CSS | Add browser stylesheet rules (css) |\n| <input type=\"checkbox\" id=\"conf_pad_block_width\">Pad block width | Pad blocks to the width with spaces |\n| <input type=\"number\" id=\"conf_wrap_width\">Text wrap width | Wrap text to this width even if overall width is wider |\n| <input type=\"checkbox\" id=\"conf_allow_overflow\">Allow width overflow | Allow text to be too wide in extreme cases instead of returning an error |\n| <input type=\"number\" id=\"conf_min_wrap_width\">Minimum wrap width | Set the minimum number of columns to use for text blocks. |\n| <input type=\"checkbox\" id=\"conf_raw\">Raw mode | Render contents of tables as if they were just text. Implies `no_table_borders` |\n| <input type=\"checkbox\" id=\"conf_no_borders\">Don't render table borders | Tables are shown without borders |\n| <input type=\"checkbox\" id=\"conf_no_link_wrap\">Don't wrap URLs at the end | Some terminals handle long URLs better if not pre-wrapped |\n| <input type=\"checkbox\" id=\"conf_unicode_so\">Use Unicode combining characters for strikeout | This allows crossed out text without terminal codes, but some environments don't render them correctly (e.g. offset). |\n| <input type=\"checkbox\" id=\"conf_do_decorate\">Add markdown-like decoration | Add characters, e.g. `*` around `<em>` text even with plain decorators. |\n| <input type=\"checkbox\" id=\"conf_link_footnotes\">URL footnotes | Add numbered list of URLs at the end of the output |\n| <select name=\"imgmode\" id=\"conf_img_mode\"><option value=\"\">--Select mode--</option><option value=\"ignore\">IgnoreEmpty</option><option value=\"always\">ShowAlways</option><option value=\"replace\">Replace(...)</option><option value=\"filename\">Filename</option></select> | Configure how images with no `alt` text are handled |\n\n</div>\n\n<div id=\"rust-code-pre\" markdown=\"1\">\n\n## Rust API configuration\n\nThe code below shows how to use the currently selected settings in the Rust API.\n\n<pre><code id=\"rust-code\"></code></pre>\n</div>\n\n<script type=\"module\">\nimport init, * as bindings from '/rust-html2text/assets/html2text-web-demo.js';\nconst wasm = await init({ module_or_path: '/rust-html2text/assets/html2text-web-demo_bg.wasm' });\n\nwindow.wasmBindings = bindings;\n\ndispatchEvent(new CustomEvent(\"TrunkApplicationStarted\", {detail: {wasm}}));\n\n</script>\n"
  },
  {
    "path": "rust.yml",
    "content": "name: Rust\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v2\n    - name: Build\n      run: cargo build --verbose\n    - name: Run tests\n      run: cargo test --verbose\n"
  },
  {
    "path": "src/ansi_colours.rs",
    "content": "//! Convenience helper for producing coloured terminal output.\n//!\n//! This optional helper applies terminal colours (or other effects which\n//! can be achieved using inline characters sent to the terminal such as\n//! underlining in some terminals).\n\nuse crate::RichAnnotation;\nuse std::io;\n\n/// Reads HTML from `input`, and returns text wrapped to `width` columns.\n///\n/// The text is returned as a `Vec<TaggedLine<_>>`; the annotations are vectors\n/// of `RichAnnotation`.  The \"outer\" annotation comes first in the `Vec`.\n///\n/// The function `colour_map` is given a slice of `RichAnnotation` and should\n/// return a pair of static strings which should be inserted before/after a text\n/// span with that annotation; for example a string which sets text colour\n/// and a string which sets the colour back to the default.\npub fn from_read_coloured<R, FMap>(\n    input: R,\n    width: usize,\n    colour_map: FMap,\n) -> Result<String, crate::Error>\nwhere\n    R: io::Read,\n    FMap: Fn(&[RichAnnotation], &str) -> String,\n{\n    super::config::rich().coloured(input, width, colour_map)\n}\n"
  },
  {
    "path": "src/css/parser.rs",
    "content": "//! Parsing for the subset of CSS used in html2text.\n\nuse std::{borrow::Cow, ops::Deref, str::FromStr};\n\nuse nom::{\n    AsChar, IResult, Parser,\n    branch::alt,\n    bytes::complete::{tag, take_until},\n    character::complete::{self, digit0, digit1},\n    combinator::{fail, map, opt, recognize},\n    error::ErrorKind,\n    multi::{many0, many1, separated_list0},\n};\n\n#[derive(Debug, PartialEq)]\npub(crate) enum Colour {\n    Rgb(u8, u8, u8),\n}\n\nimpl From<Colour> for crate::Colour {\n    fn from(value: Colour) -> Self {\n        match value {\n            Colour::Rgb(r, g, b) => crate::Colour { r, g, b },\n        }\n    }\n}\n\n#[derive(Debug, PartialEq)]\npub(crate) enum LengthUnit {\n    // Absolute units\n    In,\n    Cm,\n    Mm,\n    Pt,\n    Pc,\n    Px,\n    // Relative units\n    Em,\n    Ex,\n}\n\n#[derive(Debug, PartialEq)]\npub(crate) enum Height {\n    #[allow(unused)]\n    Auto,\n    // If the length is 0, the unit will be Px\n    Length(f32, LengthUnit),\n}\n\n#[derive(Debug, PartialEq)]\npub(crate) enum Overflow {\n    Visible,\n    Hidden,\n    Scroll,\n    Auto,\n}\n\n#[non_exhaustive]\n#[derive(Debug, PartialEq)]\npub(crate) enum Display {\n    None,\n    Other,\n    #[cfg(feature = \"css_ext\")]\n    RawDom,\n}\n\n#[derive(Debug, PartialEq)]\n#[non_exhaustive]\npub(crate) enum Decl {\n    Color {\n        value: Colour,\n    },\n    BackgroundColor {\n        value: Colour,\n    },\n    Height {\n        value: Height,\n    },\n    MaxHeight {\n        value: Height,\n    },\n    Overflow {\n        value: Overflow,\n    },\n    OverflowY {\n        value: Overflow,\n    },\n    Display {\n        value: Display,\n    },\n    WhiteSpace {\n        value: WhiteSpace,\n    },\n    Content {\n        text: String,\n    },\n    #[cfg(feature = \"css_ext\")]\n    XSyntax {\n        language: String,\n    },\n    Unknown {\n        name: PropertyName,\n        //        value: Vec<Token>,\n    },\n}\n\n// Tokens as defined in the CSS Syntax Module Level 3\n#[allow(unused)]\n#[derive(Debug, PartialEq)]\nenum Token<'s> {\n    /// Plain identifier\n    Ident(Cow<'s, str>),\n    /// Start of a function call: <ident>(\n    Function(Cow<'s, str>),\n    /// @<ident>\n    AtKeyword(Cow<'s, str>),\n    /// #abcd12\n    Hash(Cow<'s, str>),\n    /// Quoted (double or single) string\n    String(Cow<'s, str>),\n    /// <bad-string-token>\n    BadString(Cow<'s, str>),\n    /// URL\n    Url(Cow<'s, str>),\n    /// <bad-url-token>\n    BadUrl(Cow<'s, str>),\n    /// Delim character\n    Delim(char),\n    /// Number\n    Number(Cow<'s, str>),\n    /// Dimension (number, unit)\n    Dimension(Cow<'s, str>, Cow<'s, str>),\n    /// Percentage\n    Percentage(Cow<'s, str>),\n    /// Whitespace\n    //Whitespace(Cow<'s, str>),\n    /// CDO (<!--)\n    #[allow(clippy::upper_case_acronyms)]\n    CDO,\n    /// CDC (-->)\n    #[allow(clippy::upper_case_acronyms)]\n    CDC,\n    /// Colon\n    Colon,\n    /// Semicolon\n    Semicolon,\n    /// Comma\n    Comma,\n    /// [-token\n    OpenSquare,\n    /// ]-token\n    CloseSquare,\n    /// (-token\n    OpenRound,\n    /// )-token\n    CloseRound,\n    /// {-token\n    OpenBrace,\n    /// }-token\n    CloseBrace,\n}\n\n// A raw, uninterpreted declaration value.\n#[derive(Debug, PartialEq)]\nstruct RawValue<'s> {\n    tokens: Vec<Token<'s>>,\n    important: bool,\n}\n\n#[derive(Debug, PartialEq)]\npub(crate) struct Declaration {\n    pub data: Decl,\n    pub important: Importance,\n}\n\nuse crate::css::styles_from_properties;\n\nuse super::{PseudoElement, Selector, SelectorComponent, StyleDecl, WhiteSpace, types::Importance};\n\n#[derive(Debug, PartialEq)]\npub(crate) struct RuleSet {\n    pub selectors: Vec<Selector>,\n    pub declarations: Vec<Declaration>,\n}\n\n#[derive(Debug, PartialEq)]\npub(crate) struct PropertyName(String);\n\nfn match_comment(text: &str) -> IResult<&str, ()> {\n    let (rest, _) = tag(\"/*\")(text)?;\n    let (rest, _) = take_until(\"*/\")(rest)?;\n    map(tag(\"*/\"), |_t| ()).parse(rest)\n}\n\nfn match_whitespace_item(text: &str) -> IResult<&str, ()> {\n    alt((map(complete::one_of(\" \\t\\r\\n\\x0c\"), |_c| ()), match_comment)).parse(text)\n}\n\nfn skip_optional_whitespace(text: &str) -> IResult<&str, ()> {\n    map(many0(match_whitespace_item), |_res| ()).parse(text)\n}\n\nfn nmstart_char(s: &str) -> IResult<&str, char> {\n    let mut iter = s.chars();\n    match iter.next() {\n        Some(c) => match c {\n            '_' | 'a'..='z' | 'A'..='Z' => Ok((iter.as_str(), c.to_ascii_lowercase())),\n            _ => IResult::Err(nom::Err::Error(nom::error::Error::new(s, ErrorKind::Fail))),\n        },\n        None => fail().parse(s),\n    }\n}\n\nfn is_ident_start(c: char) -> bool {\n    matches!(c, 'a'..='z' | 'A'..='Z' | '_' | '\\u{0081}'..)\n}\n\nfn is_digit(c: char) -> bool {\n    c.is_ascii_digit()\n}\n\nfn nmchar_char(s: &str) -> IResult<&str, char> {\n    let mut iter = s.chars();\n    match iter.next() {\n        Some(c) => match c {\n            '_' | 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' => {\n                Ok((iter.as_str(), c.to_ascii_lowercase()))\n            }\n            _ => IResult::Err(nom::Err::Error(nom::error::Error::new(s, ErrorKind::Fail))),\n        },\n        None => fail().parse(s),\n    }\n}\n\nfn ident_escape(s: &str) -> IResult<&str, char> {\n    let (rest, _) = tag(\"\\\\\")(s)?;\n    let mut chars = rest.char_indices();\n\n    match chars.next() {\n        None => {\n            // EOF: return replacement char\n            Ok((rest, '\\u{fffd}'))\n        }\n        Some((i, c)) if c.is_hex_digit() => {\n            // Option 1: up to 6 hex digits.\n            let start_idx = i;\n            let mut end_idx = i + 1;\n            for (nexti, nextc) in chars {\n                if nextc.is_hex_digit() && nexti - start_idx < 6 {\n                    continue;\n                } else {\n                    end_idx = nexti;\n                    break;\n                }\n            }\n            let val = u32::from_str_radix(&rest[start_idx..end_idx], 16).unwrap();\n            Ok((&rest[end_idx..], char::from_u32(val).unwrap_or('\\u{fffd}')))\n        }\n        Some((_i, c)) => {\n            let bytes = c.len_utf8();\n            Ok((&rest[bytes..], c))\n        }\n    }\n}\n\nfn nmstart(text: &str) -> IResult<&str, char> {\n    alt((nmstart_char, ident_escape)).parse(text)\n}\n\nfn nmchar(text: &str) -> IResult<&str, char> {\n    alt((nmchar_char, ident_escape)).parse(text)\n}\n\nfn parse_ident(text: &str) -> IResult<&str, String> {\n    let (rest, _) = skip_optional_whitespace(text)?;\n    let mut name = Vec::new();\n    let (rest, dash) = opt(tag(\"-\")).parse(rest)?;\n    if dash.is_some() {\n        name.push('-');\n    }\n\n    let (rest, start) = nmstart(rest)?;\n    name.push(start);\n\n    let (rest, chars) = many0(nmchar).parse(rest)?;\n    name.extend(chars);\n    Ok((rest, name.into_iter().collect()))\n}\n\nfn parse_identstring(text: &str) -> IResult<&str, String> {\n    let (rest, _) = skip_optional_whitespace(text)?;\n\n    let (rest, name) = many1(nmchar).parse(rest)?;\n    Ok((rest, name.into_iter().collect()))\n}\n\nfn parse_property_name(text: &str) -> IResult<&str, PropertyName> {\n    parse_ident(text).map(|(r, s)| (r, PropertyName(s)))\n}\n\n// For now ignore whitespace\nfn parse_token(text: &str) -> IResult<&str, Token<'_>> {\n    let (rest, _) = skip_optional_whitespace(text)?;\n    let mut chars = rest.chars();\n    match chars.next() {\n        None => fail().parse(rest),\n        Some('\"') | Some('\\'') => parse_string_token(rest),\n        Some('#') => match parse_identstring(&rest[1..]) {\n            Ok((rest, id)) => Ok((rest, Token::Hash(id.into()))),\n            Err(_) => Ok((rest, Token::Delim('#'))),\n        },\n        Some(';') => Ok((&rest[1..], Token::Semicolon)),\n        Some('(') => Ok((&rest[1..], Token::OpenRound)),\n        Some(')') => Ok((&rest[1..], Token::CloseRound)),\n        Some('+') => match parse_numeric_token(&rest[1..]) {\n            Ok(result) => Ok(result),\n            Err(_) => Ok((&rest[1..], Token::Delim('+'))),\n        },\n        Some(',') => Ok((&rest[1..], Token::Comma)),\n        Some('-') => {\n            if let Ok((rest_n, tok)) = parse_numeric_token(rest) {\n                return Ok((rest_n, tok));\n            }\n            if let Some(rest_cdc) = rest.strip_prefix(\"-->\") {\n                return Ok((rest_cdc, Token::CDC));\n            }\n            if let Ok((rest_id, token)) = parse_ident_like(rest) {\n                return Ok((rest_id, token));\n            }\n            Ok((&rest[1..], Token::Delim('-')))\n        }\n        Some('.') => {\n            if let Ok((rest_n, tok)) = parse_numeric_token(rest) {\n                return Ok((rest_n, tok));\n            }\n            Ok((&rest[1..], Token::Delim('.')))\n        }\n        Some(':') => Ok((&rest[1..], Token::Colon)),\n        Some('<') => {\n            if let Some(rest_cdo) = rest.strip_prefix(\"<!--\") {\n                return Ok((rest_cdo, Token::CDO));\n            }\n            Ok((&rest[1..], Token::Delim('<')))\n        }\n        Some('@') => {\n            if let Ok((rest_id, id)) = parse_ident(rest) {\n                return Ok((rest_id, Token::AtKeyword(id.into())));\n            }\n            Ok((&rest[1..], Token::Delim('@')))\n        }\n        Some('[') => Ok((&rest[1..], Token::OpenSquare)),\n        Some('\\\\') => {\n            if let Ok((rest_id, token)) = parse_ident_like(rest) {\n                Ok((rest_id, token))\n            } else {\n                Ok((&rest[1..], Token::Delim('\\\\')))\n            }\n        }\n        Some(']') => Ok((&rest[1..], Token::CloseSquare)),\n        Some('{') => Ok((&rest[1..], Token::OpenBrace)),\n        Some('}') => Ok((&rest[1..], Token::CloseBrace)),\n        Some(c) if is_ident_start(c) => parse_ident_like(rest),\n        Some(c) if is_digit(c) => parse_numeric_token(rest),\n        Some('!') => Ok((&rest[1..], Token::Delim('!'))),\n        Some(c) => {\n            let num_bytes = c.len_utf8();\n            Ok((&rest[num_bytes..], Token::Delim(c)))\n        }\n    }\n}\n\nfn parse_token_not_semicolon(text: &str) -> IResult<&str, Token<'_>> {\n    let (rest, token) = parse_token(text)?;\n    if token == Token::Semicolon {\n        fail().parse(text)\n    } else {\n        Ok((rest, token))\n    }\n}\n\nfn parse_value(text: &str) -> IResult<&str, RawValue<'_>> {\n    let (rest, mut tokens) = many0(parse_token_not_semicolon).parse(text)?;\n    let mut important = false;\n    if let [.., Token::Delim('!'), Token::Ident(x)] = &tokens[..] {\n        if x == \"important\" {\n            tokens.pop();\n            tokens.pop();\n            important = true;\n        }\n    }\n    Ok((rest, RawValue { tokens, important }))\n}\n\npub(crate) fn parse_color_attribute(\n    text: &str,\n) -> Result<Colour, nom::Err<nom::error::Error<&'static str>>> {\n    let (_rest, value) = parse_value(text).map_err(|_| empty_fail())?;\n    parse_color(&value.tokens).or_else(|e| parse_faulty_color(e, text))\n}\n\nfn parse_color_part(text: &str, index: std::ops::Range<usize>) -> Option<u8> {\n    u8::from_str_radix(text.get(index)?, 16).ok()\n}\n\n// Both Firefox and Chromium accept \"00aabb\" as a bgcolor - I'm not sure this has ever been legal,\n// but regrettably I've had e-mails which were unreadable without doing this.\nfn parse_faulty_color(\n    e: nom::Err<nom::error::Error<&'static str>>,\n    text: &str,\n) -> Result<Colour, nom::Err<nom::error::Error<&'static str>>> {\n    let text = text.trim();\n    let r = parse_color_part(text, 0..2);\n    let g = parse_color_part(text, 2..4);\n    let b = parse_color_part(text, 4..6);\n    if let (Some(r), Some(g), Some(b)) = (r, g, b) {\n        return Ok(Colour::Rgb(r, g, b));\n    }\n    Err(e)\n}\n\npub(crate) fn parse_declaration(text: &str) -> IResult<&str, Option<Declaration>> {\n    let (rest, (prop, _ws1, _colon, _ws2, value)) = (\n        parse_property_name,\n        skip_optional_whitespace,\n        tag(\":\"),\n        skip_optional_whitespace,\n        parse_value,\n    )\n        .parse(text)?;\n    let decl = match prop.0.as_str() {\n        \"background-color\" => {\n            if let Ok(value) = parse_color(&value.tokens) {\n                Decl::BackgroundColor { value }\n            } else {\n                Decl::Unknown { name: prop }\n            }\n        }\n        \"background\" => match parse_background_color(&value) {\n            Ok(Some(value)) => Decl::BackgroundColor { value },\n            _ => Decl::Unknown { name: prop },\n        },\n        \"color\" => {\n            if let Ok(value) = parse_color(&value.tokens) {\n                Decl::Color { value }\n            } else {\n                Decl::Unknown { name: prop }\n            }\n        }\n        \"height\" => {\n            if let Ok(value) = parse_height(&value) {\n                Decl::Height { value }\n            } else {\n                Decl::Unknown { name: prop }\n            }\n        }\n        \"max-height\" => {\n            if let Ok(value) = parse_height(&value) {\n                Decl::MaxHeight { value }\n            } else {\n                Decl::Unknown { name: prop }\n            }\n        }\n        \"overflow\" => {\n            if let Ok(value) = parse_overflow(&value) {\n                Decl::Overflow { value }\n            } else {\n                Decl::Unknown { name: prop }\n            }\n        }\n        \"overflow-y\" => {\n            if let Ok(value) = parse_overflow(&value) {\n                Decl::OverflowY { value }\n            } else {\n                Decl::Unknown { name: prop }\n            }\n        }\n        \"display\" => {\n            if let Ok(value) = parse_display(&value) {\n                Decl::Display { value }\n            } else {\n                Decl::Unknown { name: prop }\n            }\n        }\n        \"white-space\" => {\n            if let Ok(value) = parse_white_space(&value) {\n                Decl::WhiteSpace { value }\n            } else {\n                Decl::Unknown { name: prop }\n            }\n        }\n        \"content\" => {\n            if let Ok(value) = parse_content(&value) {\n                Decl::Content { text: value }\n            } else {\n                Decl::Unknown { name: prop }\n            }\n        }\n        #[cfg(feature = \"css_ext\")]\n        \"x-syntax\" => {\n            if let Ok(value) = parse_syntax(&value) {\n                Decl::XSyntax { language: value }\n            } else {\n                Decl::Unknown { name: prop }\n            }\n        }\n        _ => Decl::Unknown {\n            name: prop,\n            //            value: /*value*/\"\".into(),\n        },\n    };\n    Ok((\n        rest,\n        Some(Declaration {\n            data: decl,\n            important: if value.important {\n                Importance::Important\n            } else {\n                Importance::Default\n            },\n        }),\n    ))\n}\n\nfn empty_fail() -> nom::Err<nom::error::Error<&'static str>> {\n    nom::Err::Error(nom::error::Error::new(\"\", ErrorKind::Fail))\n}\n\nfn parse_color(tokens: &[Token]) -> Result<Colour, nom::Err<nom::error::Error<&'static str>>> {\n    let fail_error = empty_fail();\n    if tokens.is_empty() {\n        return Err(fail_error);\n    }\n    match tokens {\n        [Token::Ident(c)] => {\n            let colour = match c.deref() {\n                \"aqua\" => Colour::Rgb(0, 0xff, 0xff),\n                \"black\" => Colour::Rgb(0, 0, 0),\n                \"blue\" => Colour::Rgb(0, 0, 0xff),\n                \"fuchsia\" => Colour::Rgb(0xff, 0, 0xff),\n                \"gray\" => Colour::Rgb(0x80, 0x80, 0x80),\n                \"green\" => Colour::Rgb(0, 0x80, 0),\n                \"lime\" => Colour::Rgb(0, 0xff, 0),\n                \"maroon\" => Colour::Rgb(0x80, 0, 0),\n                \"navy\" => Colour::Rgb(0, 0, 0x80),\n                \"olive\" => Colour::Rgb(0x80, 0x80, 0),\n                \"orange\" => Colour::Rgb(0xff, 0xa5, 0),\n                \"purple\" => Colour::Rgb(0x80, 0, 0x80),\n                \"red\" => Colour::Rgb(0xff, 0, 0),\n                \"silver\" => Colour::Rgb(0xc0, 0xc0, 0xc0),\n                \"teal\" => Colour::Rgb(0, 0x80, 0x80),\n                \"white\" => Colour::Rgb(0xff, 0xff, 0xff),\n                \"yellow\" => Colour::Rgb(0xff, 0xff, 0),\n                _ => {\n                    return Err(empty_fail());\n                }\n            };\n            Ok(colour)\n        }\n        [Token::Function(name), rgb_args @ .., Token::CloseRound] => {\n            use Token::*;\n            match name.deref() {\n                \"rgb\" => match rgb_args {\n                    [Number(r), Comma, Number(g), Comma, Number(b)] => {\n                        let r = r.parse().map_err(|_e| empty_fail())?;\n                        let g = g.parse().map_err(|_e| empty_fail())?;\n                        let b = b.parse().map_err(|_e| empty_fail())?;\n                        Ok(Colour::Rgb(r, g, b))\n                    }\n                    _ => Err(empty_fail()),\n                },\n                _ => Err(empty_fail()),\n            }\n        }\n        [Token::Hash(s)] => {\n            if s.len() == 3 {\n                let v = u32::from_str_radix(s, 16).map_err(|_| fail_error)?;\n                let r = ((v >> 8) & 0xf) as u8 * 0x11;\n                let g = ((v >> 4) & 0xf) as u8 * 0x11;\n                let b = (v & 0xf) as u8 * 0x11;\n                Ok(Colour::Rgb(r, g, b))\n            } else if s.len() == 6 {\n                let v = u32::from_str_radix(s, 16).map_err(|_| fail_error)?;\n                let r = ((v >> 16) & 0xff) as u8;\n                let g = ((v >> 8) & 0xff) as u8;\n                let b = (v & 0xff) as u8;\n                Ok(Colour::Rgb(r, g, b))\n            } else {\n                Err(fail_error)\n            }\n        }\n        _ => Err(fail_error),\n    }\n}\n\n// Parse background: value, extracting only the colour (if present).\nfn parse_background_color(\n    value: &RawValue,\n) -> Result<Option<Colour>, nom::Err<nom::error::Error<&'static str>>> {\n    let tokens = if let Some(last) = value.tokens.rsplit(|tok| *tok == Token::Comma).next() {\n        last\n    } else {\n        return Err(empty_fail());\n    };\n\n    match parse_color(tokens) {\n        Ok(col) => Ok(Some(col)),\n        Err(_) => Ok(None),\n    }\n}\n\nfn parse_integer(text: &str) -> IResult<&str, f32> {\n    let (rest, digits) = digit1(text)?;\n    Ok((rest, <f32 as FromStr>::from_str(digits).unwrap()))\n}\n\nfn parse_decimal(text: &str) -> IResult<&str, f32> {\n    let (rest, valstr) = recognize((digit0, tag(\".\"), digit1)).parse(text)?;\n    Ok((rest, <f32 as FromStr>::from_str(valstr).unwrap()))\n}\n\nfn parse_number(text: &str) -> IResult<&str, f32> {\n    let (rest, _) = skip_optional_whitespace(text)?;\n    let (rest, (sign, val)) = (\n        opt(alt((tag(\"-\"), tag(\"+\")))),\n        alt((parse_integer, parse_decimal)),\n    )\n        .parse(rest)?;\n    Ok((\n        rest,\n        match sign {\n            Some(\"-\") => -val,\n            None | Some(\"+\") => val,\n            _ => unreachable!(),\n        },\n    ))\n}\n\nfn parse_numeric_token(text: &str) -> IResult<&str, Token<'_>> {\n    let (rest, num) = recognize(parse_number).parse(text)?;\n    let match_pct: IResult<_, _> = tag(\"%\")(rest);\n    if let Ok((rest_p, _)) = match_pct {\n        return Ok((rest_p, Token::Percentage(num.into())));\n    }\n    match parse_ident(rest) {\n        Ok((rest_id, dim)) => Ok((rest_id, Token::Dimension(num.into(), dim.into()))),\n        Err(_) => Ok((rest, Token::Number(num.into()))),\n    }\n}\n\nfn parse_ident_like(text: &str) -> IResult<&str, Token<'_>> {\n    let (rest, ident) = parse_ident(text)?;\n    // If the next character is '(', then it's a function token\n    let match_bracket: IResult<_, _> = tag(\"(\")(rest);\n    match match_bracket {\n        Ok((rest_f, _)) => Ok((rest_f, Token::Function(ident.into()))),\n        Err(_) => Ok((rest, Token::Ident(ident.into()))),\n    }\n}\n\nfn parse_string_token(text: &str) -> IResult<&str, Token<'_>> {\n    let mut chars = text.char_indices();\n    let mut s = String::new();\n    let end_char = chars.next().unwrap().1;\n    if !(end_char == '\"' || end_char == '\\'') {\n        return fail().parse(text);\n    }\n\n    loop {\n        match chars.next() {\n            None => return Ok((\"\", Token::String(s.into()))),\n            Some((i, c)) if c == end_char => {\n                return Ok((&text[i + 1..], Token::String(s.into())));\n            }\n            Some((i, '\\n')) => {\n                return Ok((&text[i..], Token::BadString(s.into())));\n            }\n            Some((i, '\\\\')) => {\n                match chars.next() {\n                    None => {\n                        // Backslash at end\n                        return Ok((&text[i + 1..], Token::String(s.into())));\n                    }\n                    Some((_i, '\\n')) => {} // Eat the newline\n                    Some((_i, c)) => {\n                        s.push(c);\n                    }\n                }\n            }\n            Some((_, c)) => {\n                s.push(c);\n            }\n        }\n    }\n}\n\n// Parse a string as in `parse_string_token`, but fail on\n// bad strings.\nfn parse_quoted_string(text: &str) -> IResult<&str, String> {\n    let (rest, result) = parse_string_token(text)?;\n\n    match result {\n        Token::String(s) => Ok((rest, s.to_string())),\n        _ => fail().parse(\"Invalid string\"),\n    }\n}\n\n/*\nfn parse_unit(text: &str) -> IResult<&str, LengthUnit> {\n    let (rest, word) = alpha0(text)?;\n    Ok((rest, match word {\n        _ => {\n            return fail().parse(text);\n        }\n    }))\n}\n*/\n\nfn parse_height(value: &RawValue) -> Result<Height, nom::Err<nom::error::Error<&'static str>>> {\n    match value.tokens[..] {\n        [Token::Dimension(ref n, ref unit)] => {\n            let (_, num) = parse_number(n).map_err(|_e| empty_fail())?;\n            let unit = match unit.deref() {\n                \"in\" => LengthUnit::In,\n                \"cm\" => LengthUnit::Cm,\n                \"mm\" => LengthUnit::Mm,\n                \"pt\" => LengthUnit::Pt,\n                \"pc\" => LengthUnit::Pc,\n                \"px\" => LengthUnit::Px,\n                \"em\" => LengthUnit::Em,\n                \"ex\" => LengthUnit::Ex,\n                _ => {\n                    return Err(empty_fail());\n                }\n            };\n            Ok(Height::Length(num, unit))\n        }\n        [Token::Number(ref n)] => {\n            let (_, n) = parse_number(n).map_err(|_e| empty_fail())?;\n            if n == 0.0 {\n                Ok(Height::Length(0.0, LengthUnit::Px))\n            } else {\n                Err(empty_fail())\n            }\n        }\n        _ => Err(empty_fail()),\n    }\n}\n\nfn parse_overflow(value: &RawValue) -> Result<Overflow, nom::Err<nom::error::Error<&'static str>>> {\n    for tok in &value.tokens {\n        if let Token::Ident(word) = tok {\n            match word.deref() {\n                \"visible\" => {\n                    return Ok(Overflow::Visible);\n                }\n                \"hidden\" => {\n                    return Ok(Overflow::Hidden);\n                }\n                \"scroll\" => {\n                    return Ok(Overflow::Scroll);\n                }\n                \"auto\" => {\n                    return Ok(Overflow::Auto);\n                }\n                _ => {}\n            }\n        }\n    }\n    Err(empty_fail())\n}\n\nfn parse_display(value: &RawValue) -> Result<Display, nom::Err<nom::error::Error<&'static str>>> {\n    for tok in &value.tokens {\n        if let Token::Ident(word) = tok {\n            #[allow(clippy::single_match)]\n            match word.deref() {\n                \"none\" => return Ok(Display::None),\n                #[cfg(feature = \"css_ext\")]\n                \"x-raw-dom\" => return Ok(Display::RawDom),\n                _ => (),\n            }\n        }\n    }\n    Ok(Display::Other)\n}\n\n#[cfg(feature = \"css_ext\")]\nfn parse_syntax(value: &RawValue) -> Result<String, nom::Err<nom::error::Error<&'static str>>> {\n    if let Some(Token::Ident(word)) = value.tokens.first() {\n        return Ok(word.to_string());\n    }\n    Err(empty_fail())\n}\n\nfn parse_white_space(\n    value: &RawValue,\n) -> Result<WhiteSpace, nom::Err<nom::error::Error<&'static str>>> {\n    for tok in &value.tokens {\n        if let Token::Ident(word) = tok {\n            match word.deref() {\n                \"normal\" => return Ok(WhiteSpace::Normal),\n                \"pre\" => return Ok(WhiteSpace::Pre),\n                \"pre-wrap\" => return Ok(WhiteSpace::PreWrap),\n                _ => (),\n            }\n        }\n    }\n    Ok(WhiteSpace::Normal)\n}\n\n// Parse content - currently only support a single string.\nfn parse_content(value: &RawValue) -> Result<String, nom::Err<nom::error::Error<&'static str>>> {\n    let mut result = String::new();\n    for tok in &value.tokens {\n        if let Token::String(word) = tok {\n            result.push_str(word);\n        } else {\n            return Err(empty_fail());\n        }\n    }\n    Ok(result)\n}\n\npub(crate) fn parse_rules(text: &str) -> IResult<&str, Vec<Declaration>> {\n    separated_list0((tag(\";\"), skip_optional_whitespace), parse_declaration)\n        .parse(text)\n        .map(|(rest, v)| (rest, v.into_iter().flatten().collect()))\n}\n\nfn parse_class(text: &str) -> IResult<&str, SelectorComponent> {\n    let (rest, _) = tag(\".\")(text)?;\n    let (rest, classname) = parse_ident(rest)?;\n    Ok((rest, SelectorComponent::Class(classname)))\n}\n\nfn parse_attr(text: &str) -> IResult<&str, SelectorComponent> {\n    alt((\n        map((tag(\"[\"), parse_ident, tag(\"]\")), |(_, name, _)| {\n            SelectorComponent::Attr {\n                name,\n                value: None,\n                op: super::AttrOperator::Present,\n            }\n        }),\n        map(\n            (\n                tag(\"[\"),\n                parse_ident,\n                tag(\"=\"),\n                parse_quoted_string,\n                tag(\"]\"),\n            ),\n            |(_, name, _, value, _)| SelectorComponent::Attr {\n                name,\n                value: Some(value),\n                op: super::AttrOperator::Equal,\n            },\n        ),\n        map(\n            (tag(\"[\"), parse_ident, tag(\"=\"), parse_ident, tag(\"]\")),\n            |(_, name, _, value, _)| SelectorComponent::Attr {\n                name,\n                value: Some(value),\n                op: super::AttrOperator::Equal,\n            },\n        ),\n    ))\n    .parse(text)\n}\n\n#[derive(Eq, PartialEq, Copy, Clone)]\nenum Sign {\n    Plus,\n    Neg,\n}\n\nimpl Sign {\n    fn val(&self) -> i32 {\n        match self {\n            Sign::Plus => 1,\n            Sign::Neg => -1,\n        }\n    }\n}\n\nfn opt_sign(text: &str) -> IResult<&str, Sign> {\n    match text.chars().next() {\n        Some('-') => Ok((&text[1..], Sign::Neg)),\n        Some('+') => Ok((&text[1..], Sign::Plus)),\n        _ => Ok((text, Sign::Plus)),\n    }\n}\nfn sign(text: &str) -> IResult<&str, Sign> {\n    match text.chars().next() {\n        Some('-') => Ok((&text[1..], Sign::Neg)),\n        Some('+') => Ok((&text[1..], Sign::Plus)),\n        _ => fail().parse(text),\n    }\n}\n\nfn parse_nth_child_args(text: &str) -> IResult<&str, SelectorComponent> {\n    let (rest, _) = tag(\"(\")(text)?;\n    let (rest, _) = skip_optional_whitespace(rest)?;\n\n    let (rest, (a, b)) = alt((\n        map(tag(\"even\"), |_| (2, 0)),\n        map(tag(\"odd\"), |_| (2, 1)),\n        // The case where both a and b are specified\n        map(\n            (\n                opt_sign,\n                opt(digit1),\n                tag(\"n\"),\n                skip_optional_whitespace,\n                sign,\n                digit1,\n            ),\n            |(a_sign, a_opt_val, _, _, b_sign, b_val)| {\n                let a =\n                    <i32 as FromStr>::from_str(a_opt_val.unwrap_or(\"1\")).unwrap() * a_sign.val();\n                let b = <i32 as FromStr>::from_str(b_val).unwrap() * b_sign.val();\n                (a, b)\n            },\n        ),\n        // Just a\n        map(\n            (opt_sign, opt(digit1), tag(\"n\")),\n            |(a_sign, a_opt_val, _)| {\n                let a =\n                    <i32 as FromStr>::from_str(a_opt_val.unwrap_or(\"1\")).unwrap() * a_sign.val();\n                (a, 0)\n            },\n        ),\n        // Just b\n        map((opt_sign, digit1), |(b_sign, b_val)| {\n            let b = <i32 as FromStr>::from_str(b_val).unwrap() * b_sign.val();\n            (0, b)\n        }),\n    ))\n    .parse(rest)?;\n\n    let (rest, _) = (skip_optional_whitespace, tag(\")\")).parse(rest)?;\n\n    let sel = Selector {\n        components: vec![SelectorComponent::Star],\n        ..Default::default()\n    };\n    Ok((rest, SelectorComponent::NthChild { a, b, sel }))\n}\n\nfn parse_pseudo_class(text: &str) -> IResult<&str, SelectorComponent> {\n    let (rest, _) = tag(\":\")(text)?;\n    let (rest, pseudo_classname) = parse_ident(rest)?;\n    match pseudo_classname.as_str() {\n        \"nth-child\" => {\n            let (rest, component) = parse_nth_child_args(rest)?;\n            Ok((rest, component))\n        }\n        _ => fail().parse(text),\n    }\n}\n\nfn parse_hash(text: &str) -> IResult<&str, SelectorComponent> {\n    let (rest, _) = tag(\"#\")(text)?;\n    let (rest, word) = parse_identstring(rest)?;\n    Ok((rest, SelectorComponent::Hash(word)))\n}\n\n// Match some (not zero) whitespace\nfn parse_ws(text: &str) -> IResult<&str, ()> {\n    map(many1(match_whitespace_item), |_| ()).parse(text)\n}\n\nfn parse_simple_selector_component(text: &str) -> IResult<&str, SelectorComponent> {\n    alt((\n        map(\n            (skip_optional_whitespace, tag(\">\"), skip_optional_whitespace),\n            |_| SelectorComponent::CombChild,\n        ),\n        map(\n            (skip_optional_whitespace, tag(\"*\"), skip_optional_whitespace),\n            |_| SelectorComponent::Star,\n        ),\n        map(parse_ws, |_| SelectorComponent::CombDescendant),\n        parse_class,\n        parse_attr,\n        parse_hash,\n        map(parse_ident, SelectorComponent::Element),\n        parse_pseudo_class,\n    ))\n    .parse(text)\n}\n\nfn parse_selector_with_element(text: &str) -> IResult<&str, Vec<SelectorComponent>> {\n    let (rest, ident) = parse_ident(text)?;\n    let (rest, extras) = many0(parse_simple_selector_component).parse(rest)?;\n    let mut result = vec![SelectorComponent::Element(ident)];\n    result.extend(extras);\n    Ok((rest, result))\n}\n\nfn parse_selector_without_element(text: &str) -> IResult<&str, Vec<SelectorComponent>> {\n    many1(parse_simple_selector_component).parse(text)\n}\n\npub(crate) fn parse_pseudo_element(text: &str) -> IResult<&str, Option<PseudoElement>> {\n    opt(alt((\n        map(tag(\"::before\"), |_| PseudoElement::Before),\n        map(tag(\"::after\"), |_| PseudoElement::After),\n    )))\n    .parse(text)\n}\n\npub(crate) fn parse_selector(text: &str) -> IResult<&str, Selector> {\n    let (rest, mut components) = alt((\n        parse_selector_with_element,\n        parse_selector_without_element,\n        fail(),\n    ))\n    .parse(text)?;\n    // Reverse.  Also remove any leading/trailing CombDescendant, as leading/trailing whitespace\n    // shouldn't count as a descendent combinator.\n    if let Some(&SelectorComponent::CombDescendant) = components.last() {\n        components.pop();\n    }\n    components.reverse();\n    if let Some(&SelectorComponent::CombDescendant) = components.last() {\n        components.pop();\n    }\n\n    let (rest, pseudo_element) = parse_pseudo_element(rest)?;\n    Ok((\n        rest,\n        Selector {\n            components,\n            pseudo_element,\n        },\n    ))\n}\n\nfn parse_ruleset(text: &str) -> IResult<&str, RuleSet> {\n    let (rest, _) = skip_optional_whitespace(text)?;\n    let (rest, selectors) =\n        separated_list0((tag(\",\"), skip_optional_whitespace), parse_selector).parse(rest)?;\n    let (rest, (_ws1, _bra, _ws2, declarations, _ws3, _optsemi, _ws4, _ket, _ws5)) = (\n        skip_optional_whitespace,\n        tag(\"{\"),\n        skip_optional_whitespace,\n        parse_rules,\n        skip_optional_whitespace,\n        opt(tag(\";\")),\n        skip_optional_whitespace,\n        tag(\"}\"),\n        skip_optional_whitespace,\n    )\n        .parse(rest)?;\n    Ok((\n        rest,\n        RuleSet {\n            selectors,\n            declarations,\n        },\n    ))\n}\n\nfn skip_to_end_of_statement(text: &str) -> IResult<&str, ()> {\n    let mut rest = text;\n\n    let mut bra_stack = vec![];\n    loop {\n        let (remain, tok) = match parse_token(rest) {\n            Ok(res) => res,\n            Err(_) => return Ok((rest, ())),\n        };\n        match &tok {\n            Token::Ident(..)\n            | Token::AtKeyword(_)\n            | Token::Hash(_)\n            | Token::String(_)\n            | Token::BadString(_)\n            | Token::Url(_)\n            | Token::BadUrl(_)\n            | Token::Delim(_)\n            | Token::Number(_)\n            | Token::Dimension(_, _)\n            | Token::Percentage(_)\n            | Token::Colon\n            | Token::Comma => (),\n\n            Token::Function(_) | Token::OpenRound => {\n                bra_stack.push(Token::CloseRound);\n            }\n            Token::CDO => {\n                bra_stack.push(Token::CDC);\n            }\n            Token::OpenSquare => {\n                bra_stack.push(Token::CloseSquare);\n            }\n            Token::OpenBrace => {\n                bra_stack.push(Token::CloseBrace);\n            }\n            Token::Semicolon => {\n                if bra_stack.is_empty() {\n                    return Ok((remain, ()));\n                }\n            }\n            Token::CloseBrace if bra_stack.is_empty() => {\n                // The stack is empty, so don't include the closing brace.\n                return Ok((rest, ()));\n            }\n            // Standard closing brackets\n            Token::CDC | Token::CloseSquare | Token::CloseRound | Token::CloseBrace => {\n                if bra_stack.last() == Some(&tok) {\n                    bra_stack.pop();\n\n                    if tok == Token::CloseBrace && bra_stack.is_empty() {\n                        // The rule lasted until the end of the next block;\n                        // eat this closing brace.\n                        return Ok((remain, ()));\n                    }\n                } else {\n                    // Unbalanced brackets\n                    return fail().parse(rest);\n                }\n            }\n        }\n        rest = remain;\n    }\n}\n\nfn parse_at_rule(text: &str) -> IResult<&str, ()> {\n    let (rest, _) = (\n        skip_optional_whitespace,\n        tag(\"@\"),\n        skip_optional_whitespace,\n        parse_ident,\n    )\n        .parse(text)?;\n\n    skip_to_end_of_statement(rest)\n}\n\nfn parse_statement(text: &str) -> IResult<&str, Option<RuleSet>> {\n    alt((map(parse_ruleset, Some), map(parse_at_rule, |_| None))).parse(text)\n}\n\npub(crate) fn parse_stylesheet(text: &str) -> IResult<&str, Vec<RuleSet>> {\n    let (rest, items) = many0(parse_statement).parse(text)?;\n    Ok((rest, items.into_iter().flatten().collect()))\n}\n\npub(crate) fn parse_style_attribute(text: &str) -> crate::Result<Vec<StyleDecl>> {\n    html_trace_quiet!(\"Parsing inline style: {text}\");\n    let (_rest, decls) = parse_rules(text).map_err(|_| crate::Error::CssParseError)?;\n\n    let styles = styles_from_properties(&decls, false);\n    html_trace_quiet!(\"Parsed inline style: {:?}\", styles);\n    Ok(styles)\n}\n\n#[cfg(test)]\nmod test {\n    use crate::css::{\n        AttrOperator, PseudoElement, SelectorComponent,\n        parser::{Height, Importance, LengthUnit, RuleSet, Selector},\n    };\n\n    use super::{Colour, Decl, Declaration, Overflow, PropertyName};\n\n    #[test]\n    fn test_parse_decl() {\n        assert_eq!(\n            super::parse_declaration(\"foo:bar;\"),\n            Ok((\n                \";\",\n                Some(Declaration {\n                    data: Decl::Unknown {\n                        name: PropertyName(\"foo\".into()),\n                        //                value: \"bar\".into()\n                    },\n                    important: Importance::Default,\n                })\n            ))\n        );\n    }\n\n    #[test]\n    fn test_parse_overflow() {\n        assert_eq!(\n            super::parse_rules(\"overflow: hidden; overflow-y: scroll\"),\n            Ok((\n                \"\",\n                vec![\n                    Declaration {\n                        data: Decl::Overflow {\n                            value: Overflow::Hidden\n                        },\n                        important: Importance::Default,\n                    },\n                    Declaration {\n                        data: Decl::OverflowY {\n                            value: Overflow::Scroll\n                        },\n                        important: Importance::Default,\n                    },\n                ]\n            ))\n        );\n    }\n\n    #[test]\n    fn test_parse_color() {\n        assert_eq!(\n            super::parse_rules(\"color: #123; color: #abcdef\"),\n            Ok((\n                \"\",\n                vec![\n                    Declaration {\n                        data: Decl::Color {\n                            value: Colour::Rgb(0x11, 0x22, 0x33)\n                        },\n                        important: Importance::Default,\n                    },\n                    Declaration {\n                        data: Decl::Color {\n                            value: Colour::Rgb(0xab, 0xcd, 0xef)\n                        },\n                        important: Importance::Default,\n                    },\n                ]\n            ))\n        );\n        assert_eq!(\n            super::parse_rules(\"color: inherit\"),\n            Ok((\n                \"\",\n                vec![Declaration {\n                    data: Decl::Unknown {\n                        name: PropertyName(\"color\".into()),\n                    },\n                    important: Importance::Default,\n                },]\n            ))\n        );\n    }\n\n    #[test]\n    fn test_parse_height() {\n        assert_eq!(\n            super::parse_rules(\"height: 0; max-height: 100cm\"),\n            Ok((\n                \"\",\n                vec![\n                    Declaration {\n                        data: Decl::Height {\n                            value: Height::Length(0.0, LengthUnit::Px),\n                        },\n                        important: Importance::Default,\n                    },\n                    Declaration {\n                        data: Decl::MaxHeight {\n                            value: Height::Length(100.0, LengthUnit::Cm),\n                        },\n                        important: Importance::Default,\n                    },\n                ]\n            ))\n        );\n    }\n\n    #[test]\n    fn test_parse_empty_ss() {\n        assert_eq!(super::parse_stylesheet(\"\"), Ok((\"\", vec![])));\n    }\n\n    #[test]\n    fn test_parse_ss_col() {\n        assert_eq!(\n            super::parse_stylesheet(\n                \"\n            foo {\n                color: #112233;\n            }\n            \"\n            ),\n            Ok((\n                \"\",\n                vec![RuleSet {\n                    selectors: vec![Selector {\n                        components: vec![SelectorComponent::Element(\"foo\".into()),],\n                        ..Default::default()\n                    },],\n                    declarations: vec![Declaration {\n                        data: Decl::Color {\n                            value: Colour::Rgb(0x11, 0x22, 0x33)\n                        },\n                        important: Importance::Default,\n                    },],\n                }]\n            ))\n        );\n    }\n\n    #[test]\n    fn test_parse_class() {\n        assert_eq!(\n            super::parse_stylesheet(\n                \"\n            .foo {\n                color: #112233;\n                background-color: #332211 !important;\n            }\n            \"\n            ),\n            Ok((\n                \"\",\n                vec![RuleSet {\n                    selectors: vec![Selector {\n                        components: vec![SelectorComponent::Class(\"foo\".into()),],\n                        ..Default::default()\n                    },],\n                    declarations: vec![\n                        Declaration {\n                            data: Decl::Color {\n                                value: Colour::Rgb(0x11, 0x22, 0x33)\n                            },\n                            important: Importance::Default,\n                        },\n                        Declaration {\n                            data: Decl::BackgroundColor {\n                                value: Colour::Rgb(0x33, 0x22, 0x11)\n                            },\n                            important: Importance::Important,\n                        },\n                    ],\n                }]\n            ))\n        );\n    }\n\n    #[test]\n    fn test_parse_at_rules() {\n        assert_eq!(\n            super::parse_stylesheet(\n                \"\n            @media paper {\n            }\n\n            @blah asldfkjasfda;\n\n            @nested { lkasjfd alkdsjfa sldkfjas ( alksjdasfd ) [ alskdjfalskdf] }\n\n            @keyframes foo {\n                0% { transform: translateY(0); }\n               50% { opacity:0.8; }\n              100% { }\n            }\n\n\n            .foo {\n                color: #112233;\n                background-color: #332211 !important;\n            }\n            \"\n            ),\n            Ok((\n                \"\",\n                vec![RuleSet {\n                    selectors: vec![Selector {\n                        components: vec![SelectorComponent::Class(\"foo\".into()),],\n                        ..Default::default()\n                    },],\n                    declarations: vec![\n                        Declaration {\n                            data: Decl::Color {\n                                value: Colour::Rgb(0x11, 0x22, 0x33)\n                            },\n                            important: Importance::Default,\n                        },\n                        Declaration {\n                            data: Decl::BackgroundColor {\n                                value: Colour::Rgb(0x33, 0x22, 0x11)\n                            },\n                            important: Importance::Important,\n                        },\n                    ],\n                }]\n            ))\n        );\n    }\n\n    #[test]\n    fn test_parse_named_colour() {\n        assert_eq!(\n            super::parse_declaration(\"color: white\"),\n            Ok((\n                \"\",\n                Some(Declaration {\n                    data: Decl::Color {\n                        value: Colour::Rgb(0xff, 0xff, 0xff)\n                    },\n                    important: Importance::Default,\n                })\n            ))\n        );\n    }\n\n    #[test]\n    fn test_parse_colour_func() {\n        assert_eq!(\n            super::parse_declaration(\"color: rgb(1, 2, 3)\"),\n            Ok((\n                \"\",\n                Some(Declaration {\n                    data: Decl::Color {\n                        value: Colour::Rgb(1, 2, 3)\n                    },\n                    important: Importance::Default,\n                })\n            ))\n        );\n    }\n\n    #[test]\n    fn test_parse_multi_selector() {\n        assert_eq!(\n            super::parse_stylesheet(\n                \"\n.foo a         .foo-bar2 { color: inherit; }\n.foo a.foo-bar .foo-bar2 { color: #112233; }\n            \"\n            ),\n            Ok((\n                \"\",\n                vec![\n                    RuleSet {\n                        selectors: vec![Selector {\n                            components: vec![\n                                SelectorComponent::Class(\"foo-bar2\".into()),\n                                SelectorComponent::CombDescendant,\n                                SelectorComponent::Element(\"a\".into()),\n                                SelectorComponent::CombDescendant,\n                                SelectorComponent::Class(\"foo\".into()),\n                            ],\n                            ..Default::default()\n                        },],\n                        declarations: vec![Declaration {\n                            data: Decl::Unknown {\n                                name: PropertyName(\"color\".into()),\n                            },\n                            important: Importance::Default,\n                        },],\n                    },\n                    RuleSet {\n                        selectors: vec![Selector {\n                            components: vec![\n                                SelectorComponent::Class(\"foo-bar2\".into()),\n                                SelectorComponent::CombDescendant,\n                                SelectorComponent::Class(\"foo-bar\".into()),\n                                SelectorComponent::Element(\"a\".into()),\n                                SelectorComponent::CombDescendant,\n                                SelectorComponent::Class(\"foo\".into()),\n                            ],\n                            ..Default::default()\n                        },],\n                        declarations: vec![Declaration {\n                            data: Decl::Color {\n                                value: Colour::Rgb(0x11, 0x22, 0x33)\n                            },\n                            important: Importance::Default,\n                        },],\n                    }\n                ]\n            ))\n        );\n    }\n\n    #[test]\n    fn test_parse_comma_selector() {\n        assert_eq!(\n            super::parse_stylesheet(\n                \"\n.foo a, p  { color: #112233; }\n            \"\n            ),\n            Ok((\n                \"\",\n                vec![RuleSet {\n                    selectors: vec![\n                        Selector {\n                            components: vec![\n                                SelectorComponent::Element(\"a\".into()),\n                                SelectorComponent::CombDescendant,\n                                SelectorComponent::Class(\"foo\".into()),\n                            ],\n                            ..Default::default()\n                        },\n                        Selector {\n                            components: vec![SelectorComponent::Element(\"p\".into()),],\n                            ..Default::default()\n                        },\n                    ],\n                    declarations: vec![Declaration {\n                        data: Decl::Color {\n                            value: Colour::Rgb(0x11, 0x22, 0x33)\n                        },\n                        important: Importance::Default,\n                    },],\n                },]\n            ))\n        );\n    }\n\n    #[test]\n    fn test_parse_before_after() {\n        assert_eq!(\n            super::parse_stylesheet(\n                \"\n.foo a::before, p::after  { color: #112233; }\n            \"\n            ),\n            Ok((\n                \"\",\n                vec![RuleSet {\n                    selectors: vec![\n                        Selector {\n                            components: vec![\n                                SelectorComponent::Element(\"a\".into()),\n                                SelectorComponent::CombDescendant,\n                                SelectorComponent::Class(\"foo\".into()),\n                            ],\n                            pseudo_element: Some(PseudoElement::Before),\n                        },\n                        Selector {\n                            components: vec![SelectorComponent::Element(\"p\".into()),],\n                            pseudo_element: Some(PseudoElement::After),\n                        },\n                    ],\n                    declarations: vec![Declaration {\n                        data: Decl::Color {\n                            value: Colour::Rgb(0x11, 0x22, 0x33)\n                        },\n                        important: Importance::Default,\n                    },],\n                },]\n            ))\n        );\n    }\n\n    #[test]\n    fn test_parse_content() {\n        assert_eq!(\n            super::parse_stylesheet(\n                r#\"\n.foo a::before, p::after  { content: \"blah*#\"; }\n            \"#\n            ),\n            Ok((\n                \"\",\n                vec![RuleSet {\n                    selectors: vec![\n                        Selector {\n                            components: vec![\n                                SelectorComponent::Element(\"a\".into()),\n                                SelectorComponent::CombDescendant,\n                                SelectorComponent::Class(\"foo\".into()),\n                            ],\n                            pseudo_element: Some(PseudoElement::Before),\n                        },\n                        Selector {\n                            components: vec![SelectorComponent::Element(\"p\".into()),],\n                            pseudo_element: Some(PseudoElement::After),\n                        },\n                    ],\n                    declarations: vec![Declaration {\n                        data: Decl::Content {\n                            text: \"blah*#\".into()\n                        },\n                        important: Importance::Default,\n                    },],\n                },]\n            ))\n        );\n    }\n\n    #[test]\n    fn test_background() {\n        assert_eq!(\n            super::parse_declaration(\"background: white\"),\n            Ok((\n                \"\",\n                Some(Declaration {\n                    data: Decl::BackgroundColor {\n                        value: Colour::Rgb(0xff, 0xff, 0xff)\n                    },\n                    important: Importance::Default,\n                })\n            ))\n        );\n        assert_eq!(\n            super::parse_declaration(\"background: url('blah'), white\"),\n            Ok((\n                \"\",\n                Some(Declaration {\n                    data: Decl::BackgroundColor {\n                        value: Colour::Rgb(0xff, 0xff, 0xff)\n                    },\n                    important: Importance::Default,\n                })\n            ))\n        );\n        assert_eq!(\n            super::parse_declaration(\"background: url('blah'), foo\"),\n            Ok((\n                \"\",\n                Some(Declaration {\n                    data: Decl::Unknown {\n                        name: PropertyName(\"background\".into()),\n                    },\n                    important: Importance::Default,\n                })\n            ))\n        );\n    }\n\n    #[test]\n    fn test_nth_child() {\n        use SelectorComponent::NthChild;\n        let (_, sel_all) = super::parse_selector(\"*\").unwrap();\n        assert_eq!(\n            super::parse_selector(\":nth-child(even)\").unwrap(),\n            (\n                \"\",\n                Selector {\n                    components: vec![NthChild {\n                        a: 2,\n                        b: 0,\n                        sel: sel_all.clone()\n                    }],\n                    ..Default::default()\n                }\n            )\n        );\n        assert_eq!(\n            super::parse_selector(\":nth-child(odd)\").unwrap(),\n            (\n                \"\",\n                Selector {\n                    components: vec![NthChild {\n                        a: 2,\n                        b: 1,\n                        sel: sel_all.clone()\n                    }],\n                    ..Default::default()\n                }\n            )\n        );\n        assert_eq!(\n            super::parse_selector(\":nth-child(17)\").unwrap(),\n            (\n                \"\",\n                Selector {\n                    components: vec![NthChild {\n                        a: 0,\n                        b: 17,\n                        sel: sel_all.clone()\n                    }],\n                    ..Default::default()\n                }\n            )\n        );\n        assert_eq!(\n            super::parse_selector(\":nth-child(17n)\").unwrap(),\n            (\n                \"\",\n                Selector {\n                    components: vec![NthChild {\n                        a: 17,\n                        b: 0,\n                        sel: sel_all.clone()\n                    }],\n                    ..Default::default()\n                }\n            )\n        );\n        assert_eq!(\n            super::parse_selector(\":nth-child(10n-1)\").unwrap(),\n            (\n                \"\",\n                Selector {\n                    components: vec![NthChild {\n                        a: 10,\n                        b: -1,\n                        sel: sel_all.clone()\n                    }],\n                    ..Default::default()\n                }\n            )\n        );\n        assert_eq!(\n            super::parse_selector(\":nth-child(10n+9)\").unwrap(),\n            (\n                \"\",\n                Selector {\n                    components: vec![NthChild {\n                        a: 10,\n                        b: 9,\n                        sel: sel_all.clone()\n                    }],\n                    ..Default::default()\n                }\n            )\n        );\n        assert_eq!(\n            super::parse_selector(\":nth-child(-n+3)\").unwrap(),\n            (\n                \"\",\n                Selector {\n                    components: vec![NthChild {\n                        a: -1,\n                        b: 3,\n                        sel: sel_all.clone()\n                    }],\n                    ..Default::default()\n                }\n            )\n        );\n        assert_eq!(\n            super::parse_selector(\":nth-child(n)\").unwrap(),\n            (\n                \"\",\n                Selector {\n                    components: vec![NthChild {\n                        a: 1,\n                        b: 0,\n                        sel: sel_all.clone()\n                    }],\n                    ..Default::default()\n                }\n            )\n        );\n        assert_eq!(\n            super::parse_selector(\":nth-child(+n)\").unwrap(),\n            (\n                \"\",\n                Selector {\n                    components: vec![NthChild {\n                        a: 1,\n                        b: 0,\n                        sel: sel_all.clone()\n                    }],\n                    ..Default::default()\n                }\n            )\n        );\n        assert_eq!(\n            super::parse_selector(\":nth-child(-n)\").unwrap(),\n            (\n                \"\",\n                Selector {\n                    components: vec![NthChild {\n                        a: -1,\n                        b: 0,\n                        sel: sel_all.clone()\n                    }],\n                    ..Default::default()\n                }\n            )\n        );\n    }\n    #[test]\n    fn test_attr() {\n        use AttrOperator::*;\n        use SelectorComponent::{Attr, Class, Element};\n        assert_eq!(\n            super::parse_selector(\"[foo]\").unwrap(),\n            (\n                \"\",\n                Selector {\n                    components: vec![Attr {\n                        name: \"foo\".into(),\n                        value: None,\n                        op: Present,\n                    }],\n                    ..Default::default()\n                }\n            )\n        );\n        assert_eq!(\n            super::parse_selector(\"[foo=bar]\").unwrap(),\n            (\n                \"\",\n                Selector {\n                    components: vec![Attr {\n                        name: \"foo\".into(),\n                        value: Some(\"bar\".into()),\n                        op: Equal,\n                    }],\n                    ..Default::default()\n                }\n            )\n        );\n        assert_eq!(\n            super::parse_selector(\"[foo='some string']\").unwrap(),\n            (\n                \"\",\n                Selector {\n                    components: vec![Attr {\n                        name: \"foo\".into(),\n                        value: Some(\"some string\".into()),\n                        op: Equal,\n                    }],\n                    ..Default::default()\n                }\n            )\n        );\n        assert_eq!(\n            super::parse_selector(\"x.y[foo='some string']\").unwrap(),\n            (\n                \"\",\n                Selector {\n                    components: vec![\n                        Attr {\n                            name: \"foo\".into(),\n                            value: Some(\"some string\".into()),\n                            op: Equal,\n                        },\n                        Class(\"y\".into()),\n                        Element(\"x\".into()),\n                    ],\n                    ..Default::default()\n                }\n            )\n        );\n    }\n}\n"
  },
  {
    "path": "src/css/types.rs",
    "content": "#[derive(Copy, Clone, PartialEq, Eq, Debug)]\npub(crate) enum Importance {\n    Default,\n    Important,\n}\n"
  },
  {
    "path": "src/css.rs",
    "content": "//! Some basic CSS support.\nuse std::ops::Deref;\nuse std::rc::Rc;\n\n#[cfg(feature = \"css\")]\nmod parser;\npub(crate) mod types;\n\n#[cfg(feature = \"css\")]\nuse crate::{Colour, Result, WhiteSpace};\n#[cfg(feature = \"css\")]\nuse parser::parse_style_attribute;\n\nuse types::Importance;\n\nuse crate::{\n    ComputedStyle, Specificity, StyleOrigin,\n    markup5ever_rcdom::{\n        Handle,\n        NodeData::{self, Comment, Document, Element},\n    },\n};\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// Attribute seletor operations\npub(crate) enum AttrOperator {\n    #[allow(unused)]\n    Present, // foo[href]\n    #[allow(unused)]\n    Equal, // foo[href=\"foo\"]\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n#[allow(unused)]\npub(crate) enum SelectorComponent {\n    Class(String),\n    Element(String),\n    Hash(String),\n    Star,\n    CombChild,\n    CombDescendant,\n    NthChild {\n        /* An + B [of sel] */\n        a: i32,\n        b: i32,\n        sel: Selector,\n    },\n    Attr {\n        name: String,\n        value: Option<String>,\n        op: AttrOperator,\n        // TODO: other comparisions like $=\n        // TODO: case sensitivity flags\n    },\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub(crate) enum PseudoElement {\n    Before,\n    After,\n}\n\nimpl std::fmt::Display for SelectorComponent {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            SelectorComponent::Class(name) => write!(f, \".{}\", name),\n            SelectorComponent::Element(name) => write!(f, \"{}\", name),\n            SelectorComponent::Hash(val) => write!(f, \"#{}\", val),\n            SelectorComponent::Star => write!(f, \" * \"),\n            SelectorComponent::CombChild => write!(f, \" > \"),\n            SelectorComponent::CombDescendant => write!(f, \" \"),\n            SelectorComponent::NthChild { a, b, .. } => write!(f, \":nth-child({}n+{})\", a, b),\n            SelectorComponent::Attr { name, value, op } => match op {\n                AttrOperator::Present => write!(f, \"[{name}]\"),\n                AttrOperator::Equal => write!(\n                    f,\n                    \"[{name} = \\\"{}\\\"]\",\n                    value\n                        .as_ref()\n                        .expect(\"Missing value for attribute equality comparison\")\n                ),\n            },\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Default)]\npub(crate) struct Selector {\n    // List of components, right first so we match from the leaf.\n    pub(crate) components: Vec<SelectorComponent>,\n    pub(crate) pseudo_element: Option<PseudoElement>,\n}\n\nimpl std::fmt::Display for Selector {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        for comp in self.components.iter().rev() {\n            comp.fmt(f)?;\n        }\n        match self.pseudo_element {\n            Some(PseudoElement::Before) => write!(f, \"::before\")?,\n            Some(PseudoElement::After) => write!(f, \"::after\")?,\n            None => (),\n        }\n        Ok(())\n    }\n}\n\nimpl Selector {\n    fn do_matches(comps: &[SelectorComponent], node: &Handle) -> bool {\n        match comps.first() {\n            None => true,\n            Some(comp) => match comp {\n                SelectorComponent::Class(class) => match &node.data {\n                    Document\n                    | NodeData::Doctype { .. }\n                    | NodeData::Text { .. }\n                    | Comment { .. }\n                    | NodeData::ProcessingInstruction { .. } => false,\n                    Element { attrs, .. } => {\n                        let attrs = attrs.borrow();\n                        for attr in attrs.iter() {\n                            if &attr.name.local == \"class\" {\n                                for cls in attr.value.split_whitespace() {\n                                    if cls == class {\n                                        return Self::do_matches(&comps[1..], node);\n                                    }\n                                }\n                            }\n                        }\n                        false\n                    }\n                },\n                SelectorComponent::Attr { name, value, op } => match &node.data {\n                    Document\n                    | NodeData::Doctype { .. }\n                    | NodeData::Text { .. }\n                    | Comment { .. }\n                    | NodeData::ProcessingInstruction { .. } => false,\n                    Element { attrs, .. } => {\n                        let attrs = attrs.borrow();\n                        for attr in attrs.iter() {\n                            if &attr.name.local == name {\n                                match op {\n                                    AttrOperator::Present => {\n                                        return Self::do_matches(&comps[1..], node);\n                                    }\n                                    AttrOperator::Equal => {\n                                        if &*attr.value\n                                            == value\n                                                .as_ref()\n                                                .expect(\"No value in Attr equality comparison\")\n                                        {\n                                            return Self::do_matches(&comps[1..], node);\n                                        } else {\n                                            return false;\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                        false\n                    }\n                },\n                SelectorComponent::Hash(hash) => {\n                    if let Element { attrs, .. } = &node.data {\n                        let attrs = attrs.borrow();\n                        for attr in attrs.iter() {\n                            if &attr.name.local == \"id\" && &*attr.value == hash {\n                                return Self::do_matches(&comps[1..], node);\n                            }\n                        }\n                    }\n                    false\n                }\n                SelectorComponent::Element(name) => match &node.data {\n                    Element { name: eltname, .. } if name == eltname.expanded().local.deref() => {\n                        Self::do_matches(&comps[1..], node)\n                    }\n                    _ => false,\n                },\n                SelectorComponent::Star => Self::do_matches(&comps[1..], node),\n                SelectorComponent::CombChild => match node.get_parent() {\n                    Some(parent) => Self::do_matches(&comps[1..], &parent),\n                    _ => false,\n                },\n                SelectorComponent::CombDescendant => match node.get_parent() {\n                    Some(parent) => {\n                        Self::do_matches(&comps[1..], &parent) || Self::do_matches(comps, &parent)\n                    }\n                    _ => false,\n                },\n                SelectorComponent::NthChild { a, b, sel } => {\n                    let parent = match node.get_parent() {\n                        Some(parent) => parent,\n                        _ => {\n                            return false;\n                        }\n                    };\n                    let mut idx = 0i32;\n                    for child in parent.children.borrow().iter() {\n                        if let Element { .. } = child.data {\n                            if sel.matches(child) {\n                                idx += 1;\n                                if Rc::ptr_eq(child, node) {\n                                    break;\n                                }\n                            } else if Rc::ptr_eq(child, node) {\n                                return false;\n                            }\n                        }\n                    }\n                    if idx == 0 {\n                        // The child wasn't found(?)\n                        return false;\n                    }\n                    /* The selector matches if idx == a*n + b, where\n                     * n >= 0\n                     */\n                    let idx_offset = idx - b;\n                    if *a == 0 {\n                        return idx_offset == 0 && Self::do_matches(&comps[1..], node);\n                    }\n                    if (idx_offset % a) != 0 {\n                        // Not a multiple\n                        return false;\n                    }\n                    let n = idx_offset / a;\n                    n >= 0 && Self::do_matches(&comps[1..], node)\n                }\n            },\n        }\n    }\n    fn matches(&self, node: &Handle) -> bool {\n        Self::do_matches(&self.components, node)\n    }\n    fn specificity(&self) -> Specificity {\n        let mut result: Specificity = Default::default();\n\n        for component in &self.components {\n            match component {\n                SelectorComponent::Class(_) | SelectorComponent::Attr { .. } => {\n                    result.class += 1;\n                }\n                SelectorComponent::Element(_) => {\n                    result.typ += 1;\n                }\n                SelectorComponent::Hash(_) => {\n                    result.id += 1;\n                }\n                SelectorComponent::Star => {}\n                SelectorComponent::CombChild => {}\n                SelectorComponent::CombDescendant => {}\n                SelectorComponent::NthChild { sel, .. } => {\n                    result.class += 1;\n                    result += &sel.specificity();\n                }\n            }\n        }\n\n        result\n    }\n}\n\n#[cfg(feature = \"css\")]\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub(crate) enum Display {\n    /// display: none\n    None,\n    #[cfg(feature = \"css_ext\")]\n    /// Show node as HTML DOM\n    ExtRawDom,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub(crate) struct PseudoContent {\n    /// content: \"foo\"\n    pub(crate) text: String,\n}\n\n#[cfg(feature = \"css_ext\")]\n#[derive(Debug, Clone, PartialEq, Eq)]\npub(crate) struct SyntaxInfo {\n    /// Highlight language\n    pub(crate) language: String,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub(crate) enum Style {\n    #[cfg(feature = \"css\")]\n    Colour(Colour),\n    #[cfg(feature = \"css\")]\n    BgColour(Colour),\n    #[cfg(feature = \"css\")]\n    Display(Display),\n    #[cfg(feature = \"css\")]\n    WhiteSpace(WhiteSpace),\n    Content(PseudoContent),\n    #[cfg(feature = \"css_ext\")]\n    Syntax(SyntaxInfo),\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub(crate) struct StyleDecl {\n    pub(crate) style: Style,\n    pub(crate) importance: Importance,\n}\n\nimpl std::fmt::Display for StyleDecl {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match &self.style {\n            #[cfg(feature = \"css\")]\n            Style::Colour(col) => write!(f, \"color: {}\", col)?,\n            #[cfg(feature = \"css\")]\n            Style::BgColour(col) => write!(f, \"background-color: {}\", col)?,\n            #[cfg(feature = \"css\")]\n            Style::Display(Display::None) => write!(f, \"display: none\")?,\n            #[cfg(feature = \"css_ext\")]\n            Style::Display(Display::ExtRawDom) => write!(f, \"display: x-raw-dom\")?,\n            #[cfg(feature = \"css\")]\n            Style::WhiteSpace(ws) => match ws {\n                WhiteSpace::Normal => write!(f, \"white-space: normal\")?,\n                WhiteSpace::Pre => write!(f, \"white-space: pre\")?,\n                WhiteSpace::PreWrap => write!(f, \"white-space: pre-wrap\")?,\n            },\n            Style::Content(content) => write!(f, \"content: \\\"{}\\\"\", content.text)?,\n            #[cfg(feature = \"css_ext\")]\n            Style::Syntax(syntax_info) => write!(f, \"x-syntax: {}\", syntax_info.language)?,\n        }\n        match self.importance {\n            Importance::Default => (),\n            Importance::Important => write!(f, \" !important\")?,\n        }\n        Ok(())\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub(crate) struct Ruleset {\n    pub(crate) selector: Selector,\n    pub(crate) styles: Vec<StyleDecl>,\n}\n\nimpl std::fmt::Display for Ruleset {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        writeln!(f, \"  {} {{\", self.selector)?;\n        for decl in &self.styles {\n            writeln!(f, \"    {}\", decl)?;\n        }\n        writeln!(f, \"  }}\")?;\n        Ok(())\n    }\n}\n\n/// Stylesheet data which can be used while building the render tree.\n#[derive(Clone, Default, Debug, PartialEq, Eq)]\npub(crate) struct StyleData {\n    agent_rules: Vec<Ruleset>,\n    user_rules: Vec<Ruleset>,\n    author_rules: Vec<Ruleset>,\n}\n\n#[cfg(feature = \"css\")]\nfn styles_from_properties(\n    decls: &[parser::Declaration],\n    _allow_extensions: bool,\n) -> Vec<StyleDecl> {\n    let mut styles = Vec::new();\n    html_trace_quiet!(\"styles:from_properties2: {decls:?}\");\n    let mut overflow_hidden = false;\n    let mut height_zero = false;\n    for decl in decls {\n        html_trace_quiet!(\"styles:from_properties2: {decl:?}\");\n        match &decl.data {\n            parser::Decl::Unknown { .. } => {}\n            parser::Decl::Color {\n                value: parser::Colour::Rgb(r, g, b),\n            } => {\n                styles.push(StyleDecl {\n                    style: Style::Colour(Colour {\n                        r: *r,\n                        g: *g,\n                        b: *b,\n                    }),\n                    importance: decl.important,\n                });\n            }\n            parser::Decl::BackgroundColor {\n                value: parser::Colour::Rgb(r, g, b),\n            } => {\n                styles.push(StyleDecl {\n                    style: Style::BgColour(Colour {\n                        r: *r,\n                        g: *g,\n                        b: *b,\n                    }),\n                    importance: decl.important,\n                });\n            }\n            parser::Decl::Height { value } => match value {\n                parser::Height::Auto => (),\n                parser::Height::Length(l, _) => {\n                    if *l == 0.0 {\n                        height_zero = true;\n                    }\n                }\n            },\n            parser::Decl::MaxHeight { value } => match value {\n                parser::Height::Auto => (),\n                parser::Height::Length(l, _) => {\n                    if *l == 0.0 {\n                        height_zero = true;\n                    }\n                }\n            },\n            parser::Decl::Overflow {\n                value: parser::Overflow::Hidden,\n            }\n            | parser::Decl::OverflowY {\n                value: parser::Overflow::Hidden,\n            } => {\n                overflow_hidden = true;\n            }\n            parser::Decl::Overflow { .. } | parser::Decl::OverflowY { .. } => {}\n            parser::Decl::Display { value } => match value {\n                parser::Display::None => {\n                    styles.push(StyleDecl {\n                        style: Style::Display(Display::None),\n                        importance: decl.important,\n                    });\n                }\n                #[cfg(feature = \"css_ext\")]\n                parser::Display::RawDom => {\n                    if !_allow_extensions {\n                        continue;\n                    }\n                    styles.push(StyleDecl {\n                        style: Style::Display(Display::ExtRawDom),\n                        importance: decl.important,\n                    });\n                }\n                _ => (),\n            },\n            parser::Decl::WhiteSpace { value } => {\n                styles.push(StyleDecl {\n                    style: Style::WhiteSpace(*value),\n                    importance: decl.important,\n                });\n            }\n            parser::Decl::Content { text } => {\n                styles.push(StyleDecl {\n                    style: Style::Content(PseudoContent { text: text.clone() }),\n                    importance: decl.important,\n                });\n            }\n            #[cfg(feature = \"css_ext\")]\n            parser::Decl::XSyntax { language } => {\n                if !_allow_extensions {\n                    continue;\n                }\n                styles.push(StyleDecl {\n                    style: Style::Syntax(SyntaxInfo {\n                        language: language.clone(),\n                    }),\n                    importance: decl.important,\n                });\n            } /*\n              _ => {\n                  html_trace_quiet!(\"CSS: Unhandled property {:?}\", decl);\n              }\n              */\n        }\n    }\n    // If the height is set to zero and overflow hidden, treat as display: none\n    if height_zero && overflow_hidden {\n        styles.push(StyleDecl {\n            style: Style::Display(Display::None),\n            importance: Importance::Default,\n        });\n    }\n    styles\n}\n\nimpl StyleData {\n    #[cfg(feature = \"css\")]\n    /// Add some CSS source to be included.  The source will be parsed\n    /// and the relevant and supported features extracted.\n    fn do_add_css(css: &str, rules: &mut Vec<Ruleset>, allow_extensions: bool) -> Result<()> {\n        let (_, ss) = parser::parse_stylesheet(css).map_err(|_| crate::Error::CssParseError)?;\n\n        for rule in ss {\n            let styles = styles_from_properties(&rule.declarations, allow_extensions);\n            if !styles.is_empty() {\n                for selector in rule.selectors {\n                    let ruleset = Ruleset {\n                        selector,\n                        styles: styles.clone(),\n                    };\n                    html_trace_quiet!(\"Adding ruleset {ruleset:?}\");\n                    rules.push(ruleset);\n                }\n            }\n        }\n        Ok(())\n    }\n\n    pub(crate) fn add_agent_rules(&mut self, rules: &[Ruleset]) {\n        for rule in rules {\n            self.agent_rules.push(rule.clone());\n        }\n    }\n\n    #[cfg(feature = \"css\")]\n    /// Add some CSS source to be included as part of the user agent (\"browser\") CSS rules.\n    pub fn add_agent_css(&mut self, css: &str) -> Result<()> {\n        Self::do_add_css(css, &mut self.agent_rules, true)\n    }\n\n    #[cfg(feature = \"css\")]\n    /// Add some CSS source to be included as part of the user CSS rules.\n    pub fn add_user_css(&mut self, css: &str) -> Result<()> {\n        Self::do_add_css(css, &mut self.user_rules, true)\n    }\n\n    #[cfg(feature = \"css\")]\n    /// Add some CSS source to be included as part of the document/author CSS rules.\n    pub fn add_author_css(&mut self, css: &str) -> Result<()> {\n        Self::do_add_css(css, &mut self.author_rules, false)\n    }\n\n    #[cfg(feature = \"css\")]\n    /// Merge style data from other into this one.\n    /// Data on other takes precedence.\n    pub fn merge(&mut self, other: Self) {\n        self.agent_rules.extend(other.agent_rules);\n        self.user_rules.extend(other.user_rules);\n        self.author_rules.extend(other.author_rules);\n    }\n\n    pub(crate) fn computed_style(\n        &self,\n        parent_style: &ComputedStyle,\n        handle: &Handle,\n        _use_doc_css: bool,\n    ) -> ComputedStyle {\n        let mut result = parent_style.inherit();\n\n        for (origin, ruleset) in [\n            (StyleOrigin::Agent, &self.agent_rules),\n            (StyleOrigin::User, &self.user_rules),\n            (StyleOrigin::Author, &self.author_rules),\n        ] {\n            for rule in ruleset {\n                if rule.selector.matches(handle) {\n                    for style in rule.styles.iter() {\n                        Self::merge_computed_style(\n                            &mut result,\n                            style.importance == Importance::Important,\n                            origin,\n                            rule.selector.specificity(),\n                            rule.selector.pseudo_element.as_ref(),\n                            style,\n                        );\n                    }\n                }\n            }\n        }\n\n        #[cfg(feature = \"css\")]\n        if _use_doc_css {\n            // Now look for a style attribute\n            if let Element { attrs, .. } = &handle.data {\n                let borrowed = attrs.borrow();\n                for attr in borrowed.iter() {\n                    if &attr.name.local == \"style\" {\n                        let rules = parse_style_attribute(&attr.value).unwrap_or_default();\n                        for style in rules {\n                            Self::merge_computed_style(\n                                &mut result,\n                                false,\n                                StyleOrigin::Author,\n                                Specificity::inline(),\n                                None,\n                                &style,\n                            );\n                        }\n                    } else if &*attr.name.local == \"color\" {\n                        if let Ok(colour) = parser::parse_color_attribute(&attr.value) {\n                            Self::merge_computed_style(\n                                &mut result,\n                                false,\n                                StyleOrigin::Author,\n                                Specificity::inline(),\n                                None,\n                                &StyleDecl {\n                                    style: Style::Colour(colour.into()),\n                                    importance: Importance::Default,\n                                },\n                            );\n                        }\n                    } else if &*attr.name.local == \"bgcolor\" {\n                        if let Ok(colour) = parser::parse_color_attribute(&attr.value) {\n                            Self::merge_computed_style(\n                                &mut result,\n                                false,\n                                StyleOrigin::Author,\n                                Specificity::inline(),\n                                None,\n                                &StyleDecl {\n                                    style: Style::BgColour(colour.into()),\n                                    importance: Importance::Default,\n                                },\n                            );\n                        }\n                    }\n                }\n            }\n        }\n\n        result\n    }\n\n    fn merge_computed_style(\n        result: &mut ComputedStyle,\n        important: bool,\n        origin: StyleOrigin,\n        specificity: Specificity,\n        pseudo_selectors: Option<&PseudoElement>,\n        style: &StyleDecl,\n    ) {\n        let result_target = match pseudo_selectors {\n            None => result,\n            Some(PseudoElement::Before) => {\n                // TODO: ideally we should inherit from the parent; however we haven't finished\n                // computing the parent yet.\n                result.content_before.get_or_insert_with(Default::default)\n            }\n            Some(PseudoElement::After) => result.content_after.get_or_insert_with(Default::default),\n        };\n        // The increasing priority is:\n        // * agent\n        // * user\n        // * author\n        // * author !important\n        // * user !important\n        // * agent !important\n        // Since we view in the order agent, user, author, we always want to\n        // replace the value if we haven't yet seen an !important rule, and\n        // never afterwards.\n        match style.style {\n            #[cfg(feature = \"css\")]\n            Style::Colour(col) => {\n                result_target\n                    .colour\n                    .maybe_update(important, origin, specificity, col);\n            }\n            #[cfg(feature = \"css\")]\n            Style::BgColour(col) => {\n                result_target\n                    .bg_colour\n                    .maybe_update(important, origin, specificity, col);\n            }\n            #[cfg(feature = \"css\")]\n            Style::Display(disp) => {\n                // We don't have a \"not DisplayNone\" - we might need to fix this.\n                result_target\n                    .display\n                    .maybe_update(important, origin, specificity, disp);\n            }\n            #[cfg(feature = \"css\")]\n            Style::WhiteSpace(ws) => {\n                result_target\n                    .white_space\n                    .maybe_update(important, origin, specificity, ws);\n            }\n            Style::Content(ref content) => {\n                result_target\n                    .content\n                    .maybe_update(important, origin, specificity, content.clone());\n            }\n            #[cfg(feature = \"css_ext\")]\n            Style::Syntax(ref syntax_info) => {\n                result_target.syntax.maybe_update(\n                    important,\n                    origin,\n                    specificity,\n                    syntax_info.clone(),\n                );\n            }\n        }\n    }\n}\n\nimpl std::fmt::Display for StyleData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        if !self.agent_rules.is_empty() {\n            writeln!(f, \"Agent rules:\")?;\n            for ruleset in &self.agent_rules {\n                ruleset.fmt(f)?;\n            }\n        }\n        if !self.user_rules.is_empty() {\n            writeln!(f, \"User rules:\")?;\n            for ruleset in &self.user_rules {\n                ruleset.fmt(f)?;\n            }\n        }\n        if !self.author_rules.is_empty() {\n            writeln!(f, \"Author rules:\")?;\n            for ruleset in &self.author_rules {\n                ruleset.fmt(f)?;\n            }\n        }\n        Ok(())\n    }\n}\n\n#[cfg(feature = \"css\")]\npub(crate) mod dom_extract {\n    use std::io::Write;\n\n    use crate::{expanded_name, local_name, ns};\n\n    use crate::{\n        Result, TreeMapResult,\n        markup5ever_rcdom::{\n            Handle,\n            NodeData::{self, Comment, Document, Element},\n        },\n        tree_map_reduce,\n    };\n\n    use super::StyleData;\n\n    fn pending<F>(handle: Handle, f: F) -> TreeMapResult<'static, (), Handle, Vec<String>>\n    where\n        F: Fn(&mut (), Vec<Vec<String>>) -> Result<Option<Vec<String>>> + 'static,\n    {\n        TreeMapResult::PendingChildren {\n            children: handle.children.borrow().clone(),\n            cons: Box::new(f),\n            prefn: None,\n            postfn: None,\n        }\n    }\n\n    fn combine_vecs(vecs: Vec<Vec<String>>) -> Vec<String> {\n        let mut it = vecs.into_iter();\n        let first = it.next();\n        match first {\n            None => Vec::new(),\n            Some(mut first) => {\n                for v in it {\n                    first.extend(v.into_iter());\n                }\n                first\n            }\n        }\n    }\n\n    fn extract_style_nodes<T: Write>(\n        handle: Handle,\n        _err_out: &mut T,\n    ) -> TreeMapResult<'static, (), Handle, Vec<String>> {\n        use TreeMapResult::*;\n\n        match handle.clone().data {\n            Document => pending(handle, |&mut (), cs| Ok(Some(combine_vecs(cs)))),\n            Comment { .. } => Nothing,\n            Element { ref name, .. } => {\n                match name.expanded() {\n                    expanded_name!(html \"style\") => {\n                        let mut result = String::new();\n                        // Assume just a flat text node\n                        for child in handle.children.borrow().iter() {\n                            if let NodeData::Text { ref contents } = child.data {\n                                result += &contents.borrow();\n                            }\n                        }\n                        Finished(vec![result])\n                    }\n                    _ => pending(handle, |_, cs| Ok(Some(combine_vecs(cs)))),\n                }\n            }\n            NodeData::Text {\n                contents: ref _tstr,\n            } => Nothing,\n            _ => {\n                // NodeData doesn't have a Debug impl.\n                Nothing\n            }\n        }\n    }\n\n    /// Extract stylesheet data from document.\n    pub(crate) fn dom_to_stylesheet<T: Write>(\n        handle: Handle,\n        err_out: &mut T,\n    ) -> Result<StyleData> {\n        let styles = tree_map_reduce(&mut (), handle, |_, handle| {\n            Ok(extract_style_nodes(handle, err_out))\n        })?;\n\n        let mut result = StyleData::default();\n        if let Some(styles) = styles {\n            for css in styles {\n                // Ignore CSS parse errors.\n                let _ = result.add_author_css(&css);\n            }\n        }\n        Ok(result)\n    }\n}\n\n#[cfg(feature = \"css\")]\n#[cfg(test)]\nmod tests {\n    use crate::Specificity;\n\n    use super::parser::parse_selector;\n\n    #[test]\n    fn test_specificity() {\n        let sel_id1 = parse_selector(\"#foo\").unwrap().1;\n        assert_eq!(\n            sel_id1.specificity(),\n            Specificity {\n                id: 1,\n                ..Default::default()\n            }\n        );\n\n        let sel_cl3 = parse_selector(\".foo .bar .baz\").unwrap().1;\n        assert_eq!(\n            sel_cl3.specificity(),\n            Specificity {\n                class: 3,\n                ..Default::default()\n            }\n        );\n\n        assert!(sel_id1.specificity() > sel_cl3.specificity());\n    }\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "//! Convert HTML to text formats.\n//!\n//! This crate renders HTML into a text format, wrapped to a specified width.\n//! This can either be plain text or with extra annotations to (for example)\n//! show in a terminal which supports colours.\n//!\n//! # Examples\n//!\n//! ```rust\n//! # use html2text::from_read;\n//! let html = b\"\n//!        <ul>\n//!          <li>Item one</li>\n//!          <li>Item two</li>\n//!          <li>Item three</li>\n//!        </ul>\";\n//! assert_eq!(from_read(&html[..], 20).unwrap(),\n//!            \"\\\n//! * Item one\n//! * Item two\n//! * Item three\n//! \");\n//! ```\n//! A couple of simple demonstration programs are included as examples:\n//!\n//! ### html2text\n//!\n//! The simplest example uses `from_read` to convert HTML on stdin into plain\n//! text:\n//!\n//! ```sh\n//! $ cargo run --example html2text < foo.html\n//! [...]\n//! ```\n//!\n//! ### html2term\n//!\n//! A very simple example of using the rich interface (`from_read_rich`) for a\n//! slightly interactive console HTML viewer is provided as `html2term`.\n//!\n//! ```sh\n//! $ cargo run --example html2term foo.html\n//! [...]\n//! ```\n//!\n//! Note that this example takes the HTML file as a parameter so that it can\n//! read keys from stdin.\n//!\n\n#![deny(missing_docs)]\n\n// Check code in README.md\n#[cfg(doctest)]\n#[doc = include_str!(\"../README.md\")]\nstruct ReadMe;\n\n#[macro_use]\nmod macros;\n\npub mod css;\npub mod render;\n\n/// Extra methods on chars for dealing with special cases with wrapping and whitespace.\ntrait WhitespaceExt {\n    /// Returns whether this character always takes space. This is true for non-whitespace and\n    /// non-breaking spaces.\n    fn always_takes_space(&self) -> bool;\n\n    /// Returns true if a word before this character is allowed. This includes most whitespace\n    /// (but not non-breaking space).\n    fn is_wordbreak_point(&self) -> bool;\n}\n\nimpl WhitespaceExt for char {\n    fn always_takes_space(&self) -> bool {\n        match *self {\n            '\\u{A0}' => true,\n            c if !c.is_whitespace() => true,\n            _ => false,\n        }\n    }\n\n    fn is_wordbreak_point(&self) -> bool {\n        match *self {\n            '\\u{00A0}' => false,\n            '\\u{200b}' => true,\n            c if c.is_whitespace() => true,\n            _ => false,\n        }\n    }\n}\n\n/// Extra methods for strings\ntrait StrExt {\n    /// Trims leading/trailing whitespace expect for hard spaces.\n    fn trim_collapsible_ws(&self) -> &str;\n}\n\nimpl StrExt for str {\n    fn trim_collapsible_ws(&self) -> &str {\n        self.trim_matches(|c: char| !c.always_takes_space())\n    }\n}\n\n#[cfg(feature = \"css_ext\")]\n/// Text style information.\n#[derive(Clone, Debug)]\n#[non_exhaustive]\npub struct TextStyle {\n    /// The foreground colour\n    pub fg_colour: Colour,\n    /// The background colour, or None.\n    pub bg_colour: Option<Colour>,\n}\n\n#[cfg(feature = \"css_ext\")]\nimpl TextStyle {\n    /// Create a TextStyle from foreground and background colours.\n    pub fn colours(fg_colour: Colour, bg_colour: Colour) -> Self {\n        TextStyle {\n            fg_colour,\n            bg_colour: Some(bg_colour),\n        }\n    }\n\n    /// Create a TextStyle using only a foreground colour.\n    pub fn foreground(fg_colour: Colour) -> Self {\n        TextStyle {\n            fg_colour,\n            bg_colour: None,\n        }\n    }\n}\n\n#[cfg(feature = \"css_ext\")]\n/// Syntax highlighter function.\n///\n/// Takes a string corresponding to some text to be highlighted, and returns\n/// spans with sub-strs of that text with associated colours.\npub type SyntaxHighlighter = Box<dyn for<'a> Fn(&'a str) -> Vec<(TextStyle, &'a str)>>;\n\nuse markup5ever_rcdom::Node;\nuse render::text_renderer::{\n    RenderLine, RenderOptions, RichAnnotation, SubRenderer, TaggedLine, TextRenderer,\n};\nuse render::{Renderer, TextDecorator, TrivialDecorator};\n\nuse html5ever::driver::ParseOpts;\nuse html5ever::parse_document;\nuse html5ever::tree_builder::TreeBuilderOpts;\nmod markup5ever_rcdom;\npub use html5ever::{expanded_name, local_name, namespace_url, ns};\npub use markup5ever_rcdom::{\n    Handle,\n    NodeData::{Comment, Document, Element},\n    RcDom,\n};\n\nuse std::cell::{Cell, RefCell};\nuse std::cmp::{max, min};\nuse std::collections::{BTreeSet, HashMap};\n#[cfg(feature = \"css_ext\")]\nuse std::ops::Range;\nuse std::rc::Rc;\nuse unicode_width::UnicodeWidthStr;\n\nuse std::io;\nuse std::io::Write;\nuse std::iter::{once, repeat};\n\n#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]\npub(crate) enum WhiteSpace {\n    #[default]\n    Normal,\n    // NoWrap,\n    Pre,\n    #[allow(unused)]\n    PreWrap,\n    // PreLine,\n    // BreakSpaces,\n}\n\nimpl WhiteSpace {\n    pub fn preserve_whitespace(&self) -> bool {\n        match self {\n            WhiteSpace::Normal => false,\n            WhiteSpace::Pre | WhiteSpace::PreWrap => true,\n        }\n    }\n    #[allow(unused)]\n    pub fn do_wrap(&self) -> bool {\n        match self {\n            WhiteSpace::Normal | WhiteSpace::PreWrap => true,\n            WhiteSpace::Pre => false,\n        }\n    }\n}\n\n/// An RGB colour value\n#[derive(Copy, Clone, Debug, PartialEq, Eq)]\npub struct Colour {\n    /// Red value\n    pub r: u8,\n    /// Green value\n    pub g: u8,\n    /// Blue value\n    pub b: u8,\n}\n\nimpl std::fmt::Display for Colour {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"#{:02x}{:02x}{:02x}\", self.r, self.g, self.b)\n    }\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, PartialOrd)]\npub(crate) enum StyleOrigin {\n    #[default]\n    None,\n    Agent,\n    #[allow(unused)]\n    User,\n    #[allow(unused)]\n    Author,\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]\npub(crate) struct Specificity {\n    inline: bool,\n    id: u16,\n    class: u16,\n    typ: u16,\n}\n\nimpl Specificity {\n    #[cfg(feature = \"css\")]\n    fn inline() -> Self {\n        Specificity {\n            inline: true,\n            id: 0,\n            class: 0,\n            typ: 0,\n        }\n    }\n}\n\nimpl std::ops::Add<&Specificity> for &Specificity {\n    type Output = Specificity;\n\n    fn add(self, rhs: &Specificity) -> Self::Output {\n        Specificity {\n            inline: self.inline || rhs.inline,\n            id: self.id + rhs.id,\n            class: self.class + rhs.class,\n            typ: self.typ + rhs.typ,\n        }\n    }\n}\n\nimpl std::ops::AddAssign<&Specificity> for Specificity {\n    fn add_assign(&mut self, rhs: &Specificity) {\n        self.inline = self.inline || rhs.inline;\n        self.id += rhs.id;\n        self.class += rhs.class;\n        self.typ += rhs.typ;\n    }\n}\n\nimpl PartialOrd for Specificity {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        match self.inline.partial_cmp(&other.inline) {\n            Some(core::cmp::Ordering::Equal) => {}\n            ord => return ord,\n        }\n        match self.id.partial_cmp(&other.id) {\n            Some(core::cmp::Ordering::Equal) => {}\n            ord => return ord,\n        }\n        match self.class.partial_cmp(&other.class) {\n            Some(core::cmp::Ordering::Equal) => {}\n            ord => return ord,\n        }\n        self.typ.partial_cmp(&other.typ)\n    }\n}\n\n#[derive(Clone, Copy, Debug)]\npub(crate) struct WithSpec<T> {\n    val: Option<T>,\n    origin: StyleOrigin,\n    specificity: Specificity,\n    important: bool,\n}\nimpl<T: Clone> WithSpec<T> {\n    pub(crate) fn maybe_update(\n        &mut self,\n        important: bool,\n        origin: StyleOrigin,\n        specificity: Specificity,\n        val: T,\n    ) {\n        if self.val.is_some() {\n            // We already have a value, so need to check.\n            if self.important && !important {\n                // important takes priority over not important.\n                return;\n            }\n            // importance is the same.  Next is checking the origin.\n            {\n                use StyleOrigin::*;\n                match (self.origin, origin) {\n                    (Agent, Agent) | (User, User) | (Author, Author) => {\n                        // They're the same so continue the comparison\n                    }\n                    (mine, theirs) => {\n                        if (important && theirs > mine) || (!important && mine > theirs) {\n                            return;\n                        }\n                    }\n                }\n            }\n            // We're now from the same origin an importance\n            if specificity < self.specificity {\n                return;\n            }\n        }\n        self.val = Some(val);\n        self.origin = origin;\n        self.specificity = specificity;\n        self.important = important;\n    }\n\n    pub fn val(&self) -> Option<&T> {\n        self.val.as_ref()\n    }\n}\n\nimpl<T> Default for WithSpec<T> {\n    fn default() -> Self {\n        WithSpec {\n            val: None,\n            origin: StyleOrigin::None,\n            specificity: Default::default(),\n            important: false,\n        }\n    }\n}\n\n#[derive(Debug, Clone, Default)]\npub(crate) struct ComputedStyle {\n    #[cfg(feature = \"css\")]\n    /// The computed foreground colour, if any\n    pub(crate) colour: WithSpec<Colour>,\n    #[cfg(feature = \"css\")]\n    /// The computed background colour, if any\n    pub(crate) bg_colour: WithSpec<Colour>,\n    #[cfg(feature = \"css\")]\n    /// If set, indicates whether `display: none` or something equivalent applies\n    pub(crate) display: WithSpec<css::Display>,\n    /// The CSS white-space property\n    pub(crate) white_space: WithSpec<WhiteSpace>,\n    /// The CSS content property\n    pub(crate) content: WithSpec<css::PseudoContent>,\n    #[cfg(feature = \"css_ext\")]\n    pub(crate) syntax: WithSpec<css::SyntaxInfo>,\n\n    /// The CSS content property for ::before\n    pub(crate) content_before: Option<Box<ComputedStyle>>,\n    /// The CSS content property for ::after\n    pub(crate) content_after: Option<Box<ComputedStyle>>,\n\n    /// A non-CSS flag indicating we're inside a <pre>.\n    pub(crate) internal_pre: bool,\n}\n\nimpl ComputedStyle {\n    /// Return the style data inherited by children.\n    pub(crate) fn inherit(&self) -> Self {\n        // TODO: clear fields that shouldn't be inherited\n        self.clone()\n    }\n}\n\n/// Errors from reading or rendering HTML\n#[derive(thiserror::Error, Debug)]\n#[non_exhaustive]\npub enum Error {\n    /// The output width was too narrow to render to.\n    #[error(\"Output width not wide enough.\")]\n    TooNarrow,\n    /// CSS parse error\n    #[error(\"Invalid CSS\")]\n    CssParseError,\n    /// An general error was encountered.\n    #[error(\"Unknown failure\")]\n    Fail,\n    /// An I/O error\n    #[error(\"I/O error\")]\n    IoError(#[from] io::Error),\n}\n\nimpl PartialEq for Error {\n    fn eq(&self, other: &Error) -> bool {\n        use Error::*;\n        match (self, other) {\n            (TooNarrow, TooNarrow) => true,\n            #[cfg(feature = \"css\")]\n            (CssParseError, CssParseError) => true,\n            (Fail, Fail) => true,\n            _ => false,\n        }\n    }\n}\n\nimpl Eq for Error {}\n\ntype Result<T> = std::result::Result<T, Error>;\n\nconst MIN_WIDTH: usize = 3;\n\n/// Size information/estimate\n#[derive(Debug, Copy, Clone, Default)]\nstruct SizeEstimate {\n    size: usize,      // Rough overall size\n    min_width: usize, // The narrowest possible\n\n    // The use is specific to the node type.\n    prefix_size: usize,\n}\n\nimpl SizeEstimate {\n    /// Combine two estimates into one (add size and take the largest\n    /// min width)\n    fn add(self, other: SizeEstimate) -> SizeEstimate {\n        let min_width = max(self.min_width, other.min_width);\n        SizeEstimate {\n            size: self.size + other.size,\n            min_width,\n            prefix_size: 0,\n        }\n    }\n    /// Combine two estimates into one which need to be side by side.\n    /// The min widths are added.\n    fn add_hor(self, other: SizeEstimate) -> SizeEstimate {\n        SizeEstimate {\n            size: self.size + other.size,\n            min_width: self.min_width + other.min_width,\n            prefix_size: 0,\n        }\n    }\n\n    /// Combine two estimates into one (take max of each)\n    fn max(self, other: SizeEstimate) -> SizeEstimate {\n        SizeEstimate {\n            size: max(self.size, other.size),\n            min_width: max(self.min_width, other.min_width),\n            prefix_size: 0,\n        }\n    }\n}\n\n#[derive(Clone, Debug)]\n/// Render tree table cell\nstruct RenderTableCell {\n    colspan: usize,\n    rowspan: usize,\n    content: Vec<RenderNode>,\n    size_estimate: Cell<Option<SizeEstimate>>,\n    col_width: Option<usize>, // Actual width to use\n    x_pos: Option<usize>,     // X location\n    style: ComputedStyle,\n    is_dummy: bool,\n}\n\nimpl RenderTableCell {\n    /// Calculate or return the estimate size of the cell\n    fn get_size_estimate(&self) -> SizeEstimate {\n        let Some(size) = self.size_estimate.get() else {\n            let size = self\n                .content\n                .iter()\n                .map(|node| node.get_size_estimate())\n                .fold(Default::default(), SizeEstimate::add);\n            self.size_estimate.set(Some(size));\n            return size;\n        };\n        size\n    }\n\n    /// Make a placeholder cell to cover for a cell above with\n    /// larger rowspan.\n    pub fn dummy(colspan: usize) -> Self {\n        RenderTableCell {\n            colspan,\n            rowspan: 1,\n            content: Default::default(),\n            size_estimate: Cell::new(Some(SizeEstimate::default())),\n            col_width: None,\n            x_pos: None,\n            style: Default::default(),\n            is_dummy: true,\n        }\n    }\n}\n\n#[derive(Clone, Debug)]\n/// Render tree table row\nstruct RenderTableRow {\n    cells: Vec<RenderTableCell>,\n    col_sizes: Option<Vec<usize>>,\n    style: ComputedStyle,\n}\n\nimpl RenderTableRow {\n    /// Return a mutable iterator over the cells.\n    fn cells(&self) -> std::slice::Iter<'_, RenderTableCell> {\n        self.cells.iter()\n    }\n    /// Return a mutable iterator over the cells.\n    fn cells_mut(&mut self) -> std::slice::IterMut<'_, RenderTableCell> {\n        self.cells.iter_mut()\n    }\n    /// Return an iterator which returns cells by values (removing\n    /// them from the row).\n    fn cells_drain(&mut self) -> impl Iterator<Item = RenderTableCell> + use<> {\n        std::mem::take(&mut self.cells).into_iter()\n    }\n    /// Count the number of cells in the row.\n    /// Takes into account colspan.\n    fn num_cells(&self) -> usize {\n        self.cells.iter().map(|cell| cell.colspan.max(1)).sum()\n    }\n\n    /// Return the contained cells as RenderNodes, annotated with their\n    /// widths if available.  Skips cells with no width allocated.\n    fn into_cells(self, vertical: bool) -> Vec<RenderNode> {\n        let mut result = Vec::new();\n        let mut colno = 0;\n        let col_sizes = self.col_sizes.unwrap();\n        let mut x_pos = 0;\n        for mut cell in self.cells {\n            let colspan = cell.colspan;\n            let col_width = if vertical {\n                col_sizes[colno]\n            } else {\n                col_sizes[colno..colno + cell.colspan].iter().sum::<usize>()\n            };\n            // Skip any zero-width columns\n            if col_width > 0 {\n                let this_col_width = col_width + cell.colspan - 1;\n                cell.col_width = Some(this_col_width);\n                cell.x_pos = Some(x_pos);\n                x_pos += this_col_width + 1;\n                let style = cell.style.clone();\n                result.push(RenderNode::new_styled(\n                    RenderNodeInfo::TableCell(cell),\n                    style,\n                ));\n            }\n            colno += colspan;\n        }\n        result\n    }\n}\n\n#[derive(Clone, Debug)]\n/// A representation of a table render tree with metadata.\nstruct RenderTable {\n    rows: Vec<RenderTableRow>,\n    num_columns: usize,\n    size_estimate: Cell<Option<SizeEstimate>>,\n}\n\nimpl RenderTable {\n    /// Create a new RenderTable with the given rows\n    fn new(mut rows: Vec<RenderTableRow>) -> RenderTable {\n        // We later on want to allocate a vector sized by the column count,\n        // but occasionally we see something like colspan=\"1000000000\".  We\n        // handle this by remapping the column ids to the smallest values\n        // possible.\n        //\n        // Tables with no explicit colspan will be unchanged, but if there\n        // are multiple columns each covered by a single <td> on every row,\n        // they will be collapsed into a single column.  For example:\n        //\n        //    <td><td colspan=1000><td>\n        //    <td colspan=1000><td><td>\n        //\n        //  becomes the equivalent:\n        //    <td><td colspan=2><td>\n        //    <td colspan=2><td><td>\n\n        // This will include 0 and the index after the last colspan.\n        let mut col_positions = BTreeSet::new();\n        // Cells which have a rowspan > 1 from previous rows.\n        // Each element is (rows_left, colpos, colspan)\n        // Before each row, the overhangs are in reverse order so that\n        // they can be popped off.\n        let mut overhang_cells: Vec<(usize, usize, usize)> = Vec::new();\n        let mut next_overhang_cells = Vec::new();\n        col_positions.insert(0);\n        for row in &mut rows {\n            let mut col = 0;\n            let mut new_cells = Vec::new();\n\n            for cell in row.cells_drain() {\n                while let Some(hanging) = overhang_cells.last() {\n                    if hanging.1 <= col {\n                        new_cells.push(RenderTableCell::dummy(hanging.2));\n                        col += hanging.2;\n                        col_positions.insert(col);\n                        let mut used = overhang_cells.pop().unwrap();\n                        if used.0 > 1 {\n                            used.0 -= 1;\n                            next_overhang_cells.push(used);\n                        }\n                    } else {\n                        break;\n                    }\n                }\n                if cell.rowspan > 1 {\n                    next_overhang_cells.push((cell.rowspan - 1, col, cell.colspan));\n                }\n                col += cell.colspan;\n                col_positions.insert(col);\n                new_cells.push(cell);\n            }\n            // Handle remaining overhanging cells\n            while let Some(mut hanging) = overhang_cells.pop() {\n                new_cells.push(RenderTableCell::dummy(hanging.2));\n                col += hanging.2;\n                col_positions.insert(col);\n                if hanging.0 > 1 {\n                    hanging.0 -= 1;\n                    next_overhang_cells.push(hanging);\n                }\n            }\n\n            row.cells = new_cells;\n            overhang_cells = std::mem::take(&mut next_overhang_cells);\n            overhang_cells.reverse();\n        }\n\n        let colmap: HashMap<_, _> = col_positions\n            .into_iter()\n            .enumerate()\n            .map(|(i, pos)| (pos, i))\n            .collect();\n\n        for row in &mut rows {\n            let mut pos = 0;\n            let mut mapped_pos = 0;\n            for cell in row.cells_mut() {\n                let nextpos = pos + cell.colspan.max(1);\n                let next_mapped_pos = *colmap.get(&nextpos).unwrap();\n                cell.colspan = next_mapped_pos - mapped_pos;\n                pos = nextpos;\n                mapped_pos = next_mapped_pos;\n            }\n        }\n\n        let num_columns = rows.iter().map(|r| r.num_cells()).max().unwrap_or(0);\n        RenderTable {\n            rows,\n            num_columns,\n            size_estimate: Cell::new(None),\n        }\n    }\n\n    /// Return an iterator over the rows.\n    fn rows(&self) -> std::slice::Iter<'_, RenderTableRow> {\n        self.rows.iter()\n    }\n\n    /// Consume this and return a `Vec<RenderNode>` containing the children;\n    /// the children know the column sizes required.\n    fn into_rows(self, col_sizes: Vec<usize>, vert: bool) -> Vec<RenderNode> {\n        self.rows\n            .into_iter()\n            .map(|mut tr| {\n                tr.col_sizes = Some(col_sizes.clone());\n                let style = tr.style.clone();\n                RenderNode::new_styled(RenderNodeInfo::TableRow(tr, vert), style)\n            })\n            .collect()\n    }\n\n    fn calc_size_estimate(&self, _context: &HtmlContext) -> SizeEstimate {\n        if self.num_columns == 0 {\n            let result = SizeEstimate {\n                size: 0,\n                min_width: 0,\n                prefix_size: 0,\n            };\n            self.size_estimate.set(Some(result));\n            return result;\n        }\n        let mut sizes: Vec<SizeEstimate> = vec![Default::default(); self.num_columns];\n\n        // For now, a simple estimate based on adding up sub-parts.\n        for row in self.rows() {\n            let mut colno = 0usize;\n            for cell in row.cells() {\n                let cellsize = cell.get_size_estimate();\n                for colnum in 0..cell.colspan {\n                    sizes[colno + colnum].size += cellsize.size / cell.colspan;\n                    sizes[colno + colnum].min_width = max(\n                        sizes[colno + colnum].min_width,\n                        cellsize.min_width / cell.colspan,\n                    );\n                }\n                colno += cell.colspan;\n            }\n        }\n        let size = sizes.iter().map(|s| s.size).sum::<usize>() + self.num_columns.saturating_sub(1);\n        let min_width = sizes.iter().map(|s| s.min_width).sum::<usize>() + self.num_columns - 1;\n        let result = SizeEstimate {\n            size,\n            min_width,\n            prefix_size: 0,\n        };\n        self.size_estimate.set(Some(result));\n        result\n    }\n}\n\n/// The node-specific information distilled from the DOM.\n#[derive(Clone, Debug)]\n#[non_exhaustive]\nenum RenderNodeInfo {\n    /// Some text.\n    Text(String),\n    /// A group of nodes collected together.\n    Container(Vec<RenderNode>),\n    /// A link with contained nodes\n    Link(String, Vec<RenderNode>),\n    /// An emphasised region\n    Em(Vec<RenderNode>),\n    /// A strong region\n    Strong(Vec<RenderNode>),\n    /// A struck out region\n    Strikeout(Vec<RenderNode>),\n    /// A code region\n    Code(Vec<RenderNode>),\n    /// An image (src, title)\n    Img(String, String),\n    /// An inline SVG (title)\n    Svg(String),\n    /// A block element with children\n    Block(Vec<RenderNode>),\n    /// A header (h1, h2, ...) with children\n    Header(usize, Vec<RenderNode>),\n    /// A Div element with children\n    Div(Vec<RenderNode>),\n    /// A blockquote\n    BlockQuote(Vec<RenderNode>),\n    /// An unordered list\n    Ul(Vec<RenderNode>),\n    /// An ordered list\n    Ol(i64, Vec<RenderNode>),\n    /// A description list (containing Dt or Dd)\n    Dl(Vec<RenderNode>),\n    /// A term (from a `<dt>`)\n    Dt(Vec<RenderNode>),\n    /// A definition (from a `<dl>`)\n    Dd(Vec<RenderNode>),\n    /// A line break\n    Break,\n    /// A table\n    Table(RenderTable),\n    /// A set of table rows (from either `<thead>` or `<tbody>`\n    TableBody(Vec<RenderTableRow>),\n    /// Table row (must only appear within a table body)\n    /// If the boolean is true, then the cells are drawn vertically\n    /// instead of horizontally (because of space).\n    TableRow(RenderTableRow, bool),\n    /// Table cell (must only appear within a table row)\n    TableCell(RenderTableCell),\n    /// Start of a named HTML fragment\n    FragStart(String),\n    /// A list item\n    ListItem(Vec<RenderNode>),\n    /// Superscript text\n    Sup(Vec<RenderNode>),\n}\n\n/// Common fields from a node.\n#[derive(Clone, Debug)]\nstruct RenderNode {\n    size_estimate: Cell<Option<SizeEstimate>>,\n    info: RenderNodeInfo,\n    style: ComputedStyle,\n}\n\nimpl RenderNode {\n    /// Create a node from the RenderNodeInfo.\n    fn new(info: RenderNodeInfo) -> RenderNode {\n        RenderNode {\n            size_estimate: Cell::new(None),\n            info,\n            style: Default::default(),\n        }\n    }\n\n    /// Create a node from the RenderNodeInfo.\n    fn new_styled(info: RenderNodeInfo, style: ComputedStyle) -> RenderNode {\n        RenderNode {\n            size_estimate: Cell::new(None),\n            info,\n            style,\n        }\n    }\n\n    /// Get a size estimate\n    fn get_size_estimate(&self) -> SizeEstimate {\n        self.size_estimate.get().unwrap()\n    }\n\n    /// Calculate the size of this node.\n    fn calc_size_estimate<D: TextDecorator>(\n        &self,\n        context: &HtmlContext,\n        decorator: &D,\n    ) -> SizeEstimate {\n        // If it's already calculated, then just return the answer.\n        if let Some(s) = self.size_estimate.get() {\n            return s;\n        };\n\n        use RenderNodeInfo::*;\n\n        let recurse = |node: &RenderNode| node.calc_size_estimate(context, decorator);\n\n        // Otherwise, make an estimate.\n        let estimate = match self.info {\n            Text(ref t) | Img(_, ref t) | Svg(ref t) => {\n                use unicode_width::UnicodeWidthChar;\n                let mut len = 0;\n                let mut in_whitespace = false;\n                for c in t.trim_collapsible_ws().chars() {\n                    let is_collapsible_ws = !c.always_takes_space();\n                    if !is_collapsible_ws {\n                        len += UnicodeWidthChar::width(c).unwrap_or(0);\n                        // Count the preceding whitespace as one.\n                        if in_whitespace {\n                            len += 1;\n                        }\n                    }\n                    in_whitespace = is_collapsible_ws;\n                }\n                // Add one for preceding whitespace, unless the node is otherwise empty.\n                if let Some(true) = t.chars().next().map(|c| !c.always_takes_space()) {\n                    if len > 0 {\n                        len += 1;\n                    }\n                }\n                if let Img(_, _) = self.info {\n                    len += 2;\n                }\n                SizeEstimate {\n                    size: len,\n                    min_width: len.min(context.min_wrap_width),\n                    prefix_size: 0,\n                }\n            }\n\n            Container(ref v) | Em(ref v) | Strong(ref v) | Strikeout(ref v) | Code(ref v)\n            | Block(ref v) | Div(ref v) | Dl(ref v) | Dt(ref v) | ListItem(ref v) | Sup(ref v) => v\n                .iter()\n                .map(recurse)\n                .fold(Default::default(), SizeEstimate::add),\n            Link(ref _target, ref v) => v\n                .iter()\n                .map(recurse)\n                .fold(Default::default(), SizeEstimate::add)\n                .add(SizeEstimate {\n                    size: 5,\n                    min_width: 5,\n                    prefix_size: 0,\n                }),\n            Dd(ref v) | BlockQuote(ref v) | Ul(ref v) => {\n                let prefix = match self.info {\n                    Dd(_) => \"  \".into(),\n                    BlockQuote(_) => decorator.quote_prefix(),\n                    Ul(_) => decorator.unordered_item_prefix(),\n                    _ => unreachable!(),\n                };\n                let prefix_width = UnicodeWidthStr::width(prefix.as_str());\n                let mut size = v\n                    .iter()\n                    .map(recurse)\n                    .fold(Default::default(), SizeEstimate::add)\n                    .add_hor(SizeEstimate {\n                        size: prefix_width,\n                        min_width: prefix_width,\n                        prefix_size: 0,\n                    });\n                size.prefix_size = prefix_width;\n                size\n            }\n            Ol(i, ref v) => {\n                let prefix_size = calc_ol_prefix_size(i, v.len(), decorator);\n                let mut result = v\n                    .iter()\n                    .map(recurse)\n                    .fold(Default::default(), SizeEstimate::add)\n                    .add_hor(SizeEstimate {\n                        size: prefix_size,\n                        min_width: prefix_size,\n                        prefix_size: 0,\n                    });\n                result.prefix_size = prefix_size;\n                result\n            }\n            Header(level, ref v) => {\n                let prefix_size = decorator.header_prefix(level).len();\n                let mut size = v\n                    .iter()\n                    .map(recurse)\n                    .fold(Default::default(), SizeEstimate::add)\n                    .add_hor(SizeEstimate {\n                        size: prefix_size,\n                        min_width: prefix_size,\n                        prefix_size: 0,\n                    });\n                size.prefix_size = prefix_size;\n                size\n            }\n            Break => SizeEstimate {\n                size: 1,\n                min_width: 1,\n                prefix_size: 0,\n            },\n            Table(ref t) => t.calc_size_estimate(context),\n            TableRow(..) | TableBody(_) | TableCell(_) => unimplemented!(),\n            FragStart(_) => Default::default(),\n        };\n        self.size_estimate.set(Some(estimate));\n        estimate\n    }\n\n    /// Return true if this node is definitely empty.  This is used to quickly\n    /// remove e.g. links with no anchor text in most cases, but can't recurse\n    /// and look more deeply.\n    fn is_shallow_empty(&self) -> bool {\n        use RenderNodeInfo::*;\n\n        // Otherwise, make an estimate.\n        match self.info {\n            Text(ref t) | Img(_, ref t) | Svg(ref t) => {\n                let len = t.trim().len();\n                len == 0\n            }\n\n            Container(ref v)\n            | Link(_, ref v)\n            | Em(ref v)\n            | Strong(ref v)\n            | Strikeout(ref v)\n            | Code(ref v)\n            | Block(ref v)\n            | ListItem(ref v)\n            | Div(ref v)\n            | BlockQuote(ref v)\n            | Dl(ref v)\n            | Dt(ref v)\n            | Dd(ref v)\n            | Ul(ref v)\n            | Ol(_, ref v)\n            | Sup(ref v) => v.is_empty(),\n            Header(_level, ref v) => v.is_empty(),\n            Break => true,\n            Table(ref _t) => false,\n            TableRow(..) | TableBody(_) | TableCell(_) => false,\n            FragStart(_) => true,\n        }\n    }\n\n    fn write_container(\n        &self,\n        name: &str,\n        items: &[RenderNode],\n        f: &mut std::fmt::Formatter,\n        indent: usize,\n    ) -> std::prelude::v1::Result<(), std::fmt::Error> {\n        writeln!(f, \"{:indent$}{name}:\", \"\")?;\n        for item in items {\n            item.write_self(f, indent + 1)?;\n        }\n        Ok(())\n    }\n    fn write_style(\n        f: &mut std::fmt::Formatter,\n        indent: usize,\n        style: &ComputedStyle,\n    ) -> std::result::Result<(), std::fmt::Error> {\n        use std::fmt::Write;\n        let mut stylestr = String::new();\n\n        #[cfg(feature = \"css\")]\n        {\n            if let Some(col) = style.colour.val() {\n                write!(&mut stylestr, \" colour={:?}\", col)?;\n            }\n            if let Some(col) = style.bg_colour.val() {\n                write!(&mut stylestr, \" bg_colour={:?}\", col)?;\n            }\n            if let Some(val) = style.display.val() {\n                write!(&mut stylestr, \" disp={:?}\", val)?;\n            }\n        }\n        if let Some(ws) = style.white_space.val() {\n            write!(&mut stylestr, \" white_space={:?}\", ws)?;\n        }\n        if style.internal_pre {\n            write!(&mut stylestr, \" internal_pre\")?;\n        }\n        if !stylestr.is_empty() {\n            writeln!(f, \"{:indent$}[Style:{stylestr}\", \"\")?;\n        }\n        Ok(())\n    }\n    fn write_self(\n        &self,\n        f: &mut std::fmt::Formatter,\n        indent: usize,\n    ) -> std::prelude::v1::Result<(), std::fmt::Error> {\n        Self::write_style(f, indent, &self.style)?;\n\n        match &self.info {\n            RenderNodeInfo::Text(s) => writeln!(f, \"{:indent$}{s:?}\", \"\")?,\n            RenderNodeInfo::Container(v) => {\n                self.write_container(\"Container\", v, f, indent)?;\n            }\n            RenderNodeInfo::Link(targ, v) => {\n                self.write_container(&format!(\"Link({})\", targ), v, f, indent)?;\n            }\n            RenderNodeInfo::Em(v) => {\n                self.write_container(\"Em\", v, f, indent)?;\n            }\n            RenderNodeInfo::Strong(v) => {\n                self.write_container(\"Strong\", v, f, indent)?;\n            }\n            RenderNodeInfo::Strikeout(v) => {\n                self.write_container(\"Strikeout\", v, f, indent)?;\n            }\n            RenderNodeInfo::Code(v) => {\n                self.write_container(\"Code\", v, f, indent)?;\n            }\n            RenderNodeInfo::Img(src, title) => {\n                writeln!(f, \"{:indent$}Img src={:?} title={:?}:\", \"\", src, title)?;\n            }\n            RenderNodeInfo::Svg(title) => {\n                writeln!(f, \"{:indent$}Svg title={:?}:\", \"\", title)?;\n            }\n            RenderNodeInfo::Block(v) => {\n                self.write_container(\"Block\", v, f, indent)?;\n            }\n            RenderNodeInfo::Header(depth, v) => {\n                self.write_container(&format!(\"Header({})\", depth), v, f, indent)?;\n            }\n            RenderNodeInfo::Div(v) => {\n                self.write_container(\"Div\", v, f, indent)?;\n            }\n            RenderNodeInfo::BlockQuote(v) => {\n                self.write_container(\"BlockQuote\", v, f, indent)?;\n            }\n            RenderNodeInfo::Ul(v) => {\n                self.write_container(\"Ul\", v, f, indent)?;\n            }\n            RenderNodeInfo::Ol(start, v) => {\n                self.write_container(&format!(\"Ol({})\", start), v, f, indent)?;\n            }\n            RenderNodeInfo::Dl(v) => {\n                self.write_container(\"Dl\", v, f, indent)?;\n            }\n            RenderNodeInfo::Dt(v) => {\n                self.write_container(\"Dt\", v, f, indent)?;\n            }\n            RenderNodeInfo::Dd(v) => {\n                self.write_container(\"Dd\", v, f, indent)?;\n            }\n            RenderNodeInfo::Break => {\n                writeln!(f, \"{:indent$}Break\", \"\", indent = indent)?;\n            }\n            RenderNodeInfo::Table(rows) => {\n                writeln!(f, \"{:indent$}Table ({} cols):\", \"\", rows.num_columns)?;\n                for rtr in &rows.rows {\n                    Self::write_style(f, indent + 1, &rtr.style)?;\n                    writeln!(\n                        f,\n                        \"{:width$}Row ({} cells):\",\n                        \"\",\n                        rtr.cells.len(),\n                        width = indent + 1\n                    )?;\n                    for cell in &rtr.cells {\n                        Self::write_style(f, indent + 2, &cell.style)?;\n                        writeln!(\n                            f,\n                            \"{:width$}Cell colspan={} width={:?}:\",\n                            \"\",\n                            cell.colspan,\n                            cell.col_width,\n                            width = indent + 2\n                        )?;\n                        for node in &cell.content {\n                            node.write_self(f, indent + 3)?;\n                        }\n                    }\n                }\n            }\n            RenderNodeInfo::TableBody(_) => todo!(),\n            RenderNodeInfo::TableRow(_, _) => todo!(),\n            RenderNodeInfo::TableCell(_) => todo!(),\n            RenderNodeInfo::FragStart(frag) => {\n                writeln!(f, \"{:indent$}FragStart({}):\", \"\", frag)?;\n            }\n            RenderNodeInfo::ListItem(v) => {\n                self.write_container(\"ListItem\", v, f, indent)?;\n            }\n            RenderNodeInfo::Sup(v) => {\n                self.write_container(\"Sup\", v, f, indent)?;\n            }\n        }\n        Ok(())\n    }\n}\n\nfn precalc_size_estimate<'a, D: TextDecorator>(\n    node: &'a RenderNode,\n    context: &mut HtmlContext,\n    decorator: &'a D,\n) -> TreeMapResult<'a, HtmlContext, &'a RenderNode, ()> {\n    use RenderNodeInfo::*;\n    if node.size_estimate.get().is_some() {\n        return TreeMapResult::Nothing;\n    }\n    match node.info {\n        Text(_) | Img(_, _) | Svg(_) | Break | FragStart(_) => {\n            let _ = node.calc_size_estimate(context, decorator);\n            TreeMapResult::Nothing\n        }\n\n        Container(ref v)\n        | Link(_, ref v)\n        | Em(ref v)\n        | Strong(ref v)\n        | Strikeout(ref v)\n        | Code(ref v)\n        | Block(ref v)\n        | ListItem(ref v)\n        | Div(ref v)\n        | BlockQuote(ref v)\n        | Ul(ref v)\n        | Ol(_, ref v)\n        | Dl(ref v)\n        | Dt(ref v)\n        | Dd(ref v)\n        | Sup(ref v)\n        | Header(_, ref v) => TreeMapResult::PendingChildren {\n            children: v.iter().collect(),\n            cons: Box::new(move |context, _cs| {\n                node.calc_size_estimate(context, decorator);\n                Ok(None)\n            }),\n            prefn: None,\n            postfn: None,\n        },\n        Table(ref t) => {\n            /* Return all the indirect children which are RenderNodes. */\n            let mut children = Vec::new();\n            for row in &t.rows {\n                for cell in &row.cells {\n                    children.extend(cell.content.iter());\n                }\n            }\n            TreeMapResult::PendingChildren {\n                children,\n                cons: Box::new(move |context, _cs| {\n                    node.calc_size_estimate(context, decorator);\n                    Ok(None)\n                }),\n                prefn: None,\n                postfn: None,\n            }\n        }\n        TableRow(..) | TableBody(_) | TableCell(_) => unimplemented!(),\n    }\n}\n\n/// Convert a table into a RenderNode\nfn table_to_render_tree<'a, T: Write>(\n    input: RenderInput,\n    computed: ComputedStyle,\n    _err_out: &mut T,\n) -> TreeMapResult<'a, HtmlContext, RenderInput, RenderNode> {\n    pending(input, move |_, rowset| {\n        let mut rows = vec![];\n        for bodynode in rowset {\n            if let RenderNodeInfo::TableBody(body) = bodynode.info {\n                rows.extend(body);\n            } else {\n                html_trace!(\"Found in table: {:?}\", bodynode.info);\n            }\n        }\n        if rows.is_empty() {\n            None\n        } else {\n            Some(RenderNode::new_styled(\n                RenderNodeInfo::Table(RenderTable::new(rows)),\n                computed,\n            ))\n        }\n    })\n}\n\n/// Add rows from a thead or tbody.\nfn tbody_to_render_tree<'a, T: Write>(\n    input: RenderInput,\n    computed: ComputedStyle,\n    _err_out: &mut T,\n) -> TreeMapResult<'a, HtmlContext, RenderInput, RenderNode> {\n    pending_noempty(input, move |_, rowchildren| {\n        let mut rows = rowchildren\n            .into_iter()\n            .flat_map(|rownode| {\n                if let RenderNodeInfo::TableRow(row, _) = rownode.info {\n                    Some(row)\n                } else {\n                    html_trace!(\"  [[tbody child: {:?}]]\", rownode);\n                    None\n                }\n            })\n            .collect::<Vec<_>>();\n\n        // Handle colspan=0 by replacing it.\n        // Get a list of (has_zero_colspan, sum_colspan)\n        let num_columns = rows\n            .iter()\n            .map(|row| {\n                row.cells()\n                    // Treat the column as having colspan 1 for initial counting.\n                    .map(|cell| (cell.colspan == 0, cell.colspan.max(1)))\n                    .fold((false, 0), |a, b| (a.0 || b.0, a.1 + b.1))\n            })\n            .collect::<Vec<_>>();\n\n        let max_columns = num_columns.iter().map(|(_, span)| span).max().unwrap_or(&1);\n\n        for (i, &(has_zero, num_cols)) in num_columns.iter().enumerate() {\n            // Note this won't be sensible if more than one column has colspan=0,\n            // but that's not very well defined anyway.\n            if has_zero {\n                for cell in rows[i].cells_mut() {\n                    if cell.colspan == 0 {\n                        // +1 because we said it had 1 to start with\n                        cell.colspan = max_columns - num_cols + 1;\n                    }\n                }\n            }\n        }\n\n        Some(RenderNode::new_styled(\n            RenderNodeInfo::TableBody(rows),\n            computed,\n        ))\n    })\n}\n\n/// Convert a table row to a RenderTableRow\nfn tr_to_render_tree<'a, T: Write>(\n    input: RenderInput,\n    computed: ComputedStyle,\n    _err_out: &mut T,\n) -> TreeMapResult<'a, HtmlContext, RenderInput, RenderNode> {\n    pending(input, move |_, cellnodes| {\n        let cells = cellnodes\n            .into_iter()\n            .flat_map(|cellnode| {\n                if let RenderNodeInfo::TableCell(cell) = cellnode.info {\n                    Some(cell)\n                } else {\n                    html_trace!(\"  [[tr child: {:?}]]\", cellnode);\n                    None\n                }\n            })\n            .collect();\n        let style = computed.clone();\n        Some(RenderNode::new_styled(\n            RenderNodeInfo::TableRow(\n                RenderTableRow {\n                    cells,\n                    col_sizes: None,\n                    style,\n                },\n                false,\n            ),\n            computed,\n        ))\n    })\n}\n\n/// Convert a single table cell to a render node.\nfn td_to_render_tree<'a, T: Write>(\n    input: RenderInput,\n    computed: ComputedStyle,\n    _err_out: &mut T,\n) -> TreeMapResult<'a, HtmlContext, RenderInput, RenderNode> {\n    let mut colspan = 1;\n    let mut rowspan = 1;\n    if let Element { ref attrs, .. } = input.handle.data {\n        for attr in attrs.borrow().iter() {\n            if &attr.name.local == \"colspan\" {\n                let v: &str = &attr.value;\n                colspan = v.parse().unwrap_or(1);\n            }\n            if &attr.name.local == \"rowspan\" {\n                let v: &str = &attr.value;\n                rowspan = v.parse().unwrap_or(1);\n            }\n        }\n    }\n    pending(input, move |_, children| {\n        let style = computed.clone();\n        Some(RenderNode::new_styled(\n            RenderNodeInfo::TableCell(RenderTableCell {\n                colspan,\n                rowspan,\n                content: children,\n                size_estimate: Cell::new(None),\n                col_width: None,\n                x_pos: None,\n                style,\n                is_dummy: false,\n            }),\n            computed,\n        ))\n    })\n}\n\n/// A reducer which combines results from mapping children into\n/// the result for the current node.  Takes a context and a\n/// vector of results and returns a new result (or nothing).\ntype ResultReducer<'a, C, R> = dyn FnOnce(&mut C, Vec<R>) -> Result<Option<R>> + 'a;\n\n/// A closure to call before processing a child node.\ntype ChildPreFn<C, N> = dyn Fn(&mut C, &N) -> Result<()>;\n\n/// A closure to call after processing a child node,\n/// before adding the result to the processed results\n/// vector.\ntype ChildPostFn<C, R> = dyn Fn(&mut C, &R) -> Result<()>;\n\n/// The result of trying to render one node.\nenum TreeMapResult<'a, C, N, R> {\n    /// A completed result.\n    Finished(R),\n    /// Deferred completion - can be turned into a result\n    /// once the vector of children are processed.\n    PendingChildren {\n        children: Vec<N>,\n        cons: Box<ResultReducer<'a, C, R>>,\n        prefn: Option<Box<ChildPreFn<C, N>>>,\n        postfn: Option<Box<ChildPostFn<C, R>>>,\n    },\n    /// Nothing (e.g. a comment or other ignored element).\n    Nothing,\n}\n\nfn tree_map_reduce<'a, C, N, R, M>(\n    context: &mut C,\n    top: N,\n    mut process_node: M,\n) -> Result<Option<R>>\nwhere\n    M: FnMut(&mut C, N) -> Result<TreeMapResult<'a, C, N, R>>,\n{\n    /// A node partially decoded, waiting for its children to\n    /// be processed.\n    struct PendingNode<'a, C, R, N> {\n        /// How to make the node once finished\n        construct: Box<ResultReducer<'a, C, R>>,\n        /// Called before processing each child\n        prefn: Option<Box<ChildPreFn<C, N>>>,\n        /// Called after processing each child\n        postfn: Option<Box<ChildPostFn<C, R>>>,\n        /// Children already processed\n        children: Vec<R>,\n        /// Iterator of child nodes not yet processed\n        to_process: std::vec::IntoIter<N>,\n    }\n\n    let mut last = PendingNode {\n        // We only expect one child, which we'll just return.\n        construct: Box::new(|_, mut cs| Ok(cs.pop())),\n        prefn: None,\n        postfn: None,\n        children: Vec::new(),\n        to_process: vec![top].into_iter(),\n    };\n    let mut pending_stack = Vec::new();\n    loop {\n        // Get the next child node to process\n        while let Some(h) = last.to_process.next() {\n            if let Some(f) = &last.prefn {\n                f(context, &h)?;\n            }\n            match process_node(context, h)? {\n                TreeMapResult::Finished(result) => {\n                    if let Some(f) = &last.postfn {\n                        f(context, &result)?;\n                    }\n                    last.children.push(result);\n                }\n                TreeMapResult::PendingChildren {\n                    children,\n                    cons,\n                    prefn,\n                    postfn,\n                } => {\n                    pending_stack.push(last);\n                    last = PendingNode {\n                        construct: cons,\n                        prefn,\n                        postfn,\n                        children: Vec::new(),\n                        to_process: children.into_iter(),\n                    };\n                }\n                TreeMapResult::Nothing => {}\n            };\n        }\n        // No more children, so finally construct the parent.\n        if let Some(mut parent) = pending_stack.pop() {\n            if let Some(node) = (last.construct)(context, last.children)? {\n                if let Some(f) = &parent.postfn {\n                    f(context, &node)?;\n                }\n                parent.children.push(node);\n            }\n            last = parent;\n            continue;\n        }\n        // Finished the whole stack!\n        break Ok((last.construct)(context, last.children)?);\n    }\n}\n\n#[cfg(feature = \"css_ext\")]\n#[derive(Clone, Default)]\nstruct HighlighterMap {\n    map: HashMap<String, Rc<SyntaxHighlighter>>,\n}\n\n#[cfg(feature = \"css_ext\")]\nimpl HighlighterMap {\n    pub fn get(&self, name: &str) -> Option<Rc<SyntaxHighlighter>> {\n        self.map.get(name).cloned()\n    }\n\n    fn insert(&mut self, name: impl Into<String>, f: Rc<SyntaxHighlighter>) {\n        self.map.insert(name.into(), f);\n    }\n}\n\n#[cfg(feature = \"css_ext\")]\nimpl std::fmt::Debug for HighlighterMap {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"HighlighterMap\")\n            .field(\"map\", &self.map.keys().collect::<Vec<_>>())\n            .finish()\n    }\n}\n\n#[cfg(feature = \"css_ext\")]\nimpl PartialEq for HighlighterMap {\n    fn eq(&self, _other: &Self) -> bool {\n        todo!()\n    }\n}\n\n#[cfg(feature = \"css_ext\")]\nimpl Eq for HighlighterMap {}\n\n#[derive(Debug, PartialEq, Eq)]\nstruct HtmlContext {\n    style_data: css::StyleData,\n    #[cfg(feature = \"css\")]\n    use_doc_css: bool,\n\n    max_wrap_width: Option<usize>,\n    pad_block_width: bool,\n    allow_width_overflow: bool,\n    min_wrap_width: usize,\n    raw: bool,\n    draw_borders: bool,\n    wrap_links: bool,\n    include_link_footnotes: bool,\n    use_unicode_strikeout: bool,\n    image_mode: config::ImageRenderMode,\n\n    #[cfg(feature = \"xml\")]\n    xml_mode: config::XmlMode,\n\n    #[cfg(feature = \"css_ext\")]\n    syntax_highlighters: HighlighterMap,\n}\n\n// Input to render tree conversion.\nstruct RenderInput {\n    handle: Handle,\n    parent_style: Rc<ComputedStyle>,\n    // Overlay styles from syntax highlighting.\n    #[cfg(feature = \"css_ext\")]\n    extra_styles: RefCell<Vec<(Range<usize>, TextStyle)>>,\n    // Map from node to the length of enclosed text nodes.\n    node_lengths: Rc<RefCell<HashMap<*const Node, usize>>>,\n}\n\nimpl RenderInput {\n    fn new(handle: Handle, parent_style: Rc<ComputedStyle>) -> Self {\n        RenderInput {\n            handle,\n            parent_style,\n            #[cfg(feature = \"css_ext\")]\n            extra_styles: Default::default(),\n            node_lengths: Default::default(),\n        }\n    }\n\n    #[cfg(feature = \"css_ext\")]\n    fn set_syntax_info(&self, full_text: &str, highlighted: Vec<(TextStyle, &str)>) {\n        let mut node_styles = Vec::new();\n\n        // Turn the returned strings into offsets into full_text.  We assume\n        // we can maintain relative offsets as we step through the tree rendering.\n        for (style, s) in highlighted {\n            fn get_offset(full: &str, sub: &str) -> Option<Range<usize>> {\n                // This looks scary, but if we get this wrong the worst case is\n                // that we end up panicking when using the offsets.\n                let full_start = full.as_ptr() as usize;\n                let full_end = full_start + full.len();\n                let sub_start = sub.as_ptr() as usize;\n                let sub_end = sub_start + sub.len();\n\n                if sub_start >= full_start && sub_end <= full_end {\n                    Some((sub_start - full_start)..(sub_end - full_start))\n                } else {\n                    None\n                }\n            }\n\n            if let Some(offset_range) = get_offset(full_text, s) {\n                node_styles.push((offset_range, style));\n            } // else we ignore the highlight.\n        }\n        node_styles.sort_by_key(|r| (r.0.start, r.0.end));\n        *self.extra_styles.borrow_mut() = node_styles;\n    }\n\n    // Return the children in the right form\n    #[allow(clippy::mut_range_bound)]\n    fn children(&self) -> Vec<RenderInput> {\n        #[cfg(feature = \"css_ext\")]\n        if !self.extra_styles.borrow().is_empty() {\n            let mut offset = 0;\n            let mut result = Vec::new();\n            let mut start_style_index = 0;\n            let node_lengths = self.node_lengths.borrow();\n            let extra_styles = self.extra_styles.borrow();\n            for child in &*self.handle.children.borrow() {\n                let end_offset = offset + node_lengths.get(&Rc::as_ptr(child)).unwrap();\n                let mut child_extra_styles = Vec::new();\n                for es_idx in start_style_index..extra_styles.len() {\n                    let mut style_range = extra_styles[es_idx].0.clone();\n                    if style_range.start >= end_offset {\n                        // We've gone too far.\n                        break;\n                    }\n                    if style_range.end <= offset {\n                        // We don't need to look at this again\n                        // Note this is here to restart this loop in a different place\n                        // in the next run of the outer loop; hence allowing\n                        // clippy::mut_range_bound on the function.\n                        start_style_index = es_idx;\n                    } else {\n                        // This piece must overlap!\n                        // Clip the range to this node.\n                        style_range.start = style_range.start.max(offset) - offset;\n                        style_range.end = style_range.end.min(end_offset) - offset;\n\n                        child_extra_styles.push((style_range, extra_styles[es_idx].1.clone()));\n                    }\n                }\n                result.push(RenderInput {\n                    handle: Rc::clone(child),\n                    parent_style: Rc::clone(&self.parent_style),\n                    extra_styles: RefCell::new(child_extra_styles),\n                    node_lengths: self.node_lengths.clone(),\n                });\n                offset = end_offset;\n            }\n            return result;\n        }\n\n        // Simple case, and we might not have the node lengths.\n        self.handle\n            .children\n            .borrow()\n            .iter()\n            .map(|child| RenderInput {\n                handle: child.clone(),\n                parent_style: Rc::clone(&self.parent_style),\n                #[cfg(feature = \"css_ext\")]\n                extra_styles: Default::default(),\n                node_lengths: self.node_lengths.clone(),\n            })\n            .collect()\n    }\n\n    #[cfg(feature = \"css_ext\")]\n    fn do_extract_text(\n        out: &mut String,\n        handle: &Handle,\n        length_map: &mut HashMap<*const Node, usize>,\n    ) {\n        match handle.data {\n            markup5ever_rcdom::NodeData::Text { contents: ref tstr } => {\n                let s: &str = &tstr.borrow();\n                out.push_str(s);\n                length_map.entry(Rc::as_ptr(handle)).or_insert(s.len());\n            }\n            _ => {\n                for child in handle.children.borrow().iter() {\n                    let len_before = out.len();\n                    RenderInput::do_extract_text(out, child, length_map);\n                    let len_after = out.len();\n                    length_map\n                        .entry(Rc::as_ptr(child))\n                        .or_insert(len_after - len_before);\n                }\n            }\n        }\n    }\n\n    #[cfg(feature = \"css_ext\")]\n    /// Return a full String, and a list of where substrings came from:\n    ///\n    fn extract_raw_text(&self) -> String {\n        let mut result = String::new();\n        RenderInput::do_extract_text(\n            &mut result,\n            &self.handle,\n            &mut self.node_lengths.borrow_mut(),\n        );\n        result\n    }\n}\n\nfn dom_to_render_tree_with_context<T: Write>(\n    handle: Handle,\n    err_out: &mut T,\n    context: &mut HtmlContext,\n) -> Result<Option<RenderNode>> {\n    html_trace!(\"### dom_to_render_tree: HTML: {:?}\", handle);\n    #[cfg(feature = \"css\")]\n    if context.use_doc_css {\n        let mut doc_style_data = css::dom_extract::dom_to_stylesheet(handle.clone(), err_out)?;\n        doc_style_data.merge(std::mem::take(&mut context.style_data));\n        context.style_data = doc_style_data;\n    }\n\n    let parent_style = Default::default();\n    let result = tree_map_reduce(\n        context,\n        RenderInput::new(handle, parent_style),\n        |context, input| process_dom_node(input, err_out, context),\n    );\n\n    html_trace!(\"### dom_to_render_tree: out= {:#?}\", result);\n    result\n}\n\n#[cfg(feature = \"css\")]\n/// Return a string representation of the CSS rules parsed from\n/// the DOM document.\npub fn dom_to_parsed_style(dom: &RcDom) -> Result<String> {\n    let handle = dom.document.clone();\n    let doc_style_data = css::dom_extract::dom_to_stylesheet(handle, &mut std::io::sink())?;\n    Ok(doc_style_data.to_string())\n}\n\nfn pending<F>(\n    input: RenderInput,\n    f: F,\n) -> TreeMapResult<'static, HtmlContext, RenderInput, RenderNode>\nwhere\n    F: FnOnce(&mut HtmlContext, Vec<RenderNode>) -> Option<RenderNode> + 'static,\n{\n    TreeMapResult::PendingChildren {\n        children: input.children(),\n        cons: Box::new(move |ctx, children| Ok(f(ctx, children))),\n        prefn: None,\n        postfn: None,\n    }\n}\n\nfn pending_noempty<F>(\n    input: RenderInput,\n    f: F,\n) -> TreeMapResult<'static, HtmlContext, RenderInput, RenderNode>\nwhere\n    F: FnOnce(&mut HtmlContext, Vec<RenderNode>) -> Option<RenderNode> + 'static,\n{\n    let handle = &input.handle;\n    let style = &input.parent_style;\n    TreeMapResult::PendingChildren {\n        children: handle\n            .children\n            .borrow()\n            .iter()\n            .map(|child| RenderInput::new(child.clone(), Rc::clone(style)))\n            .collect(),\n        cons: Box::new(move |ctx, children| {\n            if children.is_empty() {\n                Ok(None)\n            } else {\n                Ok(f(ctx, children))\n            }\n        }),\n        prefn: None,\n        postfn: None,\n    }\n}\n\n#[derive(Copy, Clone, Eq, PartialEq, Debug)]\nenum ChildPosition {\n    Start,\n    End,\n}\n\n/// Prepend or append a FragmentStart (or analogous) marker to an existing\n/// RenderNode.\nfn insert_child(\n    new_child: RenderNode,\n    mut orig: RenderNode,\n    position: ChildPosition,\n) -> RenderNode {\n    use RenderNodeInfo::*;\n    html_trace!(\"insert_child({:?}, {:?}, {:?})\", new_child, orig, position);\n\n    match orig.info {\n        // For block elements such as Block and Div, we need to insert\n        // the node at the front of their children array, otherwise\n        // the renderer is liable to drop the fragment start marker\n        // _before_ the new line indicating the end of the previous\n        // paragraph.\n        //\n        // For Container, we do the same thing just to make the data\n        // less pointlessly nested.\n        Block(ref mut children)\n        | ListItem(ref mut children)\n        | Dd(ref mut children)\n        | Dt(ref mut children)\n        | Dl(ref mut children)\n        | Div(ref mut children)\n        | BlockQuote(ref mut children)\n        | Container(ref mut children)\n        | TableCell(RenderTableCell {\n            content: ref mut children,\n            ..\n        }) => {\n            match position {\n                ChildPosition::Start => children.insert(0, new_child),\n                ChildPosition::End => children.push(new_child),\n            }\n            // Now return orig, but we do that outside the match so\n            // that we've given back the borrowed ref 'children'.\n        }\n\n        // For table rows and tables, push down if there's any content.\n        TableRow(ref mut rrow, _) => {\n            // If the row is empty, then there isn't really anything\n            // to attach the fragment start to.\n            if let Some(cell) = rrow.cells.first_mut() {\n                match position {\n                    ChildPosition::Start => cell.content.insert(0, new_child),\n                    ChildPosition::End => cell.content.push(new_child),\n                }\n            }\n        }\n\n        TableBody(ref mut rows) | Table(RenderTable { ref mut rows, .. }) => {\n            // If the row is empty, then there isn't really anything\n            // to attach the fragment start to.\n            if let Some(rrow) = rows.first_mut() {\n                if let Some(cell) = rrow.cells.first_mut() {\n                    match position {\n                        ChildPosition::Start => cell.content.insert(0, new_child),\n                        ChildPosition::End => cell.content.push(new_child),\n                    }\n                }\n            }\n        }\n\n        // For anything else, just make a new Container with the\n        // new_child node and the original one.\n        _ => {\n            let result = match position {\n                ChildPosition::Start => RenderNode::new(Container(vec![new_child, orig])),\n                ChildPosition::End => RenderNode::new(Container(vec![orig, new_child])),\n            };\n            html_trace!(\"insert_child() -> {:?}\", result);\n            return result;\n        }\n    }\n    html_trace!(\"insert_child() -> {:?}\", &orig);\n    orig\n}\n\nfn process_dom_node<T: Write>(\n    input: RenderInput,\n    err_out: &mut T,\n    #[allow(unused)] // Used with css feature\n    context: &mut HtmlContext,\n) -> Result<TreeMapResult<'static, HtmlContext, RenderInput, RenderNode>> {\n    use RenderNodeInfo::*;\n    use TreeMapResult::*;\n\n    Ok(match input.handle.clone().data {\n        Document => pending(input, |_context, cs| Some(RenderNode::new(Container(cs)))),\n        Comment { .. } => Nothing,\n        Element {\n            ref name,\n            ref attrs,\n            ..\n        } => {\n            let mut frag_from_name_attr = false;\n\n            let RenderInput {\n                ref handle,\n                ref parent_style,\n                ..\n            } = input;\n\n            #[cfg(feature = \"css\")]\n            let use_doc_css = context.use_doc_css;\n            #[cfg(not(feature = \"css\"))]\n            let use_doc_css = false;\n\n            let computed = {\n                let computed = context\n                    .style_data\n                    .computed_style(parent_style, handle, use_doc_css);\n                #[cfg(feature = \"css\")]\n                match computed.display.val() {\n                    Some(css::Display::None) => return Ok(Nothing),\n                    #[cfg(feature = \"css_ext\")]\n                    Some(css::Display::ExtRawDom) => {\n                        use html5ever::interface::{NodeOrText, TreeSink};\n                        use html5ever::{LocalName, QualName};\n                        let mut html_bytes: Vec<u8> = Default::default();\n                        handle.serialize(&mut html_bytes)?;\n\n                        // Make a new DOM object so that we can easily create new\n                        // nodes.  They will be independent.\n                        let dom = RcDom::default();\n\n                        // We'll enclose it in a `<pre>`, so that we have an element in the right\n                        // shape to process.\n                        let html_string = String::from_utf8_lossy(&html_bytes).into_owned();\n                        let pre_node = dom.create_element(\n                            QualName::new(None, ns!(html), LocalName::from(\"pre\")),\n                            vec![],\n                            Default::default(),\n                        );\n                        dom.append(&pre_node, NodeOrText::AppendText(html_string.into()));\n\n                        // Remove the RawDom setting; we don't want to be recursively converting to\n                        // raw DOM.\n                        let mut my_computed = computed;\n                        my_computed.display = Default::default();\n                        // Preformat it\n                        my_computed.white_space.maybe_update(\n                            false,\n                            StyleOrigin::Agent,\n                            Default::default(),\n                            WhiteSpace::Pre,\n                        );\n                        my_computed.internal_pre = true;\n\n                        let new_input = RenderInput {\n                            handle: pre_node,\n                            parent_style: Rc::new(my_computed.clone()),\n                            extra_styles: Default::default(),\n                            node_lengths: Default::default(),\n                        };\n\n                        if let Some(syntax_info) = my_computed.syntax.val() {\n                            if let Some(highlighter) =\n                                context.syntax_highlighters.get(&syntax_info.language)\n                            {\n                                // Do the highlighting here.\n                                let text = new_input.extract_raw_text();\n                                let highlighted = highlighter(&text);\n                                new_input.set_syntax_info(&text, highlighted);\n                            }\n                        }\n                        return Ok(pending(new_input, move |_, cs| {\n                            Some(RenderNode::new_styled(Container(cs), my_computed))\n                        }));\n                    }\n                    _ => (),\n                }\n                #[cfg(feature = \"css_ext\")]\n                if let Some(syntax_info) = computed.syntax.val() {\n                    if let Some(highlighter) =\n                        context.syntax_highlighters.get(&syntax_info.language)\n                    {\n                        let extracted_text = input.extract_raw_text();\n                        let highlighted = highlighter(&extracted_text);\n                        input.set_syntax_info(&extracted_text, highlighted);\n                    }\n                }\n\n                computed\n            };\n\n            let computed_before = computed.content_before.clone();\n            let computed_after = computed.content_after.clone();\n\n            let result = match name.expanded() {\n                expanded_name!(html \"html\") | expanded_name!(html \"body\") => {\n                    /* process children, but don't add anything */\n                    pending(input, move |_, cs| {\n                        Some(RenderNode::new_styled(Container(cs), computed))\n                    })\n                }\n                expanded_name!(html \"link\")\n                | expanded_name!(html \"meta\")\n                | expanded_name!(html \"hr\")\n                | expanded_name!(html \"script\")\n                | expanded_name!(html \"style\")\n                | expanded_name!(html \"head\") => {\n                    /* Ignore the head and its children */\n                    Nothing\n                }\n                expanded_name!(html \"span\") => {\n                    /* process children, but don't add anything */\n                    pending_noempty(input, move |_, cs| {\n                        Some(RenderNode::new_styled(Container(cs), computed))\n                    })\n                }\n                expanded_name!(html \"a\") => {\n                    let borrowed = attrs.borrow();\n                    let mut target = None;\n                    frag_from_name_attr = true;\n                    for attr in borrowed.iter() {\n                        if &attr.name.local == \"href\" {\n                            target = Some(&*attr.value);\n                            break;\n                        }\n                    }\n                    PendingChildren {\n                        children: input.children(),\n                        cons: if let Some(href) = target {\n                            let href: String = href.into();\n                            Box::new(move |_, cs: Vec<RenderNode>| {\n                                if cs.iter().any(|c| !c.is_shallow_empty()) {\n                                    Ok(Some(RenderNode::new_styled(Link(href, cs), computed)))\n                                } else {\n                                    Ok(None)\n                                }\n                            })\n                        } else {\n                            Box::new(move |_, cs| {\n                                Ok(Some(RenderNode::new_styled(Container(cs), computed)))\n                            })\n                        },\n                        prefn: None,\n                        postfn: None,\n                    }\n                }\n                expanded_name!(html \"em\")\n                | expanded_name!(html \"i\")\n                | expanded_name!(html \"ins\") => pending(input, move |_, cs| {\n                    Some(RenderNode::new_styled(Em(cs), computed))\n                }),\n                expanded_name!(html \"strong\") | expanded_name!(html \"b\") => {\n                    pending(input, move |_, cs| {\n                        Some(RenderNode::new_styled(Strong(cs), computed))\n                    })\n                }\n                expanded_name!(html \"s\") | expanded_name!(html \"del\") => {\n                    pending(input, move |_, cs| {\n                        Some(RenderNode::new_styled(Strikeout(cs), computed))\n                    })\n                }\n                expanded_name!(html \"code\") => pending(input, move |_, cs| {\n                    Some(RenderNode::new_styled(Code(cs), computed))\n                }),\n                expanded_name!(html \"img\") => {\n                    let borrowed = attrs.borrow();\n                    let mut title = None;\n                    let mut src = None;\n                    for attr in borrowed.iter() {\n                        if &attr.name.local == \"alt\" && !attr.value.is_empty() {\n                            title = Some(&*attr.value);\n                        }\n                        if &attr.name.local == \"src\" && !attr.value.is_empty() {\n                            src = Some(&*attr.value);\n                        }\n                        if title.is_some() && src.is_some() {\n                            break;\n                        }\n                    }\n                    // Ignore `<img>` without src.\n                    if let Some(src) = src {\n                        Finished(RenderNode::new_styled(\n                            Img(src.into(), title.unwrap_or(\"\").into()),\n                            computed,\n                        ))\n                    } else {\n                        Nothing\n                    }\n                }\n                expanded_name!(svg \"svg\") => {\n                    // Inline SVG: look for a <title> child for the title.\n                    let mut title = None;\n\n                    for node in input.handle.children.borrow().iter() {\n                        if let markup5ever_rcdom::NodeData::Element { ref name, .. } = node.data {\n                            if matches!(name.expanded(), expanded_name!(svg \"title\")) {\n                                let mut title_str = String::new();\n                                for subnode in node.children.borrow().iter() {\n                                    if let markup5ever_rcdom::NodeData::Text { ref contents } =\n                                        subnode.data\n                                    {\n                                        title_str.push_str(&contents.borrow());\n                                    }\n                                }\n                                title = Some(title_str);\n                            } else {\n                                // The first item has to be <title>\n                                break;\n                            }\n                        }\n                    }\n\n                    Finished(RenderNode::new_styled(\n                        Svg(title.unwrap_or_else(String::new)),\n                        computed,\n                    ))\n                }\n                expanded_name!(html \"h1\")\n                | expanded_name!(html \"h2\")\n                | expanded_name!(html \"h3\")\n                | expanded_name!(html \"h4\")\n                | expanded_name!(html \"h5\")\n                | expanded_name!(html \"h6\") => {\n                    let level: usize = name.local[1..].parse().unwrap();\n                    pending(input, move |_, cs| {\n                        Some(RenderNode::new_styled(Header(level, cs), computed))\n                    })\n                }\n                expanded_name!(html \"p\") => pending_noempty(input, move |_, cs| {\n                    Some(RenderNode::new_styled(Block(cs), computed))\n                }),\n                expanded_name!(html \"li\") => pending(input, move |_, cs| {\n                    Some(RenderNode::new_styled(ListItem(cs), computed))\n                }),\n                expanded_name!(html \"sup\") => pending(input, move |_, cs| {\n                    Some(RenderNode::new_styled(Sup(cs), computed))\n                }),\n                expanded_name!(html \"div\") => pending_noempty(input, move |_, cs| {\n                    Some(RenderNode::new_styled(Div(cs), computed))\n                }),\n                expanded_name!(html \"pre\") => pending(input, move |_, cs| {\n                    let mut computed = computed;\n                    computed.white_space.maybe_update(\n                        false,\n                        StyleOrigin::Agent,\n                        Default::default(),\n                        WhiteSpace::Pre,\n                    );\n                    computed.internal_pre = true;\n                    Some(RenderNode::new_styled(Block(cs), computed))\n                }),\n                expanded_name!(html \"br\") => Finished(RenderNode::new_styled(Break, computed)),\n                expanded_name!(html \"wbr\") => {\n                    Finished(RenderNode::new_styled(Text(\"\\u{200b}\".into()), computed))\n                }\n                expanded_name!(html \"table\") => table_to_render_tree(input, computed, err_out),\n                expanded_name!(html \"thead\") | expanded_name!(html \"tbody\") => {\n                    tbody_to_render_tree(input, computed, err_out)\n                }\n                expanded_name!(html \"tr\") => tr_to_render_tree(input, computed, err_out),\n                expanded_name!(html \"th\") | expanded_name!(html \"td\") => {\n                    td_to_render_tree(input, computed, err_out)\n                }\n                expanded_name!(html \"blockquote\") => pending_noempty(input, move |_, cs| {\n                    Some(RenderNode::new_styled(BlockQuote(cs), computed))\n                }),\n                expanded_name!(html \"ul\") => pending_noempty(input, move |_, cs| {\n                    Some(RenderNode::new_styled(Ul(cs), computed))\n                }),\n                expanded_name!(html \"ol\") => {\n                    let borrowed = attrs.borrow();\n                    let mut start = 1;\n                    for attr in borrowed.iter() {\n                        if &attr.name.local == \"start\" {\n                            start = attr.value.parse().ok().unwrap_or(1);\n                            break;\n                        }\n                    }\n\n                    pending_noempty(input, move |_, cs| {\n                        // There can be extra nodes which aren't ListItem (like whitespace text\n                        // nodes).  We need to filter those out to avoid messing up the rendering.\n                        let cs = cs\n                            .into_iter()\n                            .filter(|n| matches!(n.info, RenderNodeInfo::ListItem(..)))\n                            .collect();\n                        Some(RenderNode::new_styled(Ol(start, cs), computed))\n                    })\n                }\n                expanded_name!(html \"dl\") => {\n                    pending_noempty(input, move |_, cs| {\n                        // There can be extra nodes which aren't Dt or Dd (like whitespace text\n                        // nodes).  We need to filter those out to avoid messing up the rendering.\n                        let cs = cs\n                            .into_iter()\n                            .filter(|n| {\n                                matches!(n.info, RenderNodeInfo::Dt(..) | RenderNodeInfo::Dd(..))\n                            })\n                            .collect();\n                        Some(RenderNode::new_styled(Dl(cs), computed))\n                    })\n                }\n                expanded_name!(html \"dt\") => pending(input, move |_, cs| {\n                    Some(RenderNode::new_styled(Dt(cs), computed))\n                }),\n                expanded_name!(html \"dd\") => pending(input, move |_, cs| {\n                    Some(RenderNode::new_styled(Dd(cs), computed))\n                }),\n                _ => {\n                    html_trace!(\"Unhandled element: {:?}\\n\", name.local);\n                    pending_noempty(input, move |_, cs| {\n                        Some(RenderNode::new_styled(Container(cs), computed))\n                    })\n                }\n            };\n\n            let mut fragment = None;\n            let borrowed = attrs.borrow();\n            for attr in borrowed.iter() {\n                if &attr.name.local == \"id\" || (frag_from_name_attr && &attr.name.local == \"name\") {\n                    fragment = Some(attr.value.to_string());\n                    break;\n                }\n            }\n\n            let result = if computed_before.is_some() || computed_after.is_some() {\n                let wrap_nodes = move |mut node: RenderNode| {\n                    if let Some(ref content) = computed_before {\n                        if let Some(pseudo_content) = content.content.val() {\n                            node = insert_child(\n                                RenderNode::new(Text(pseudo_content.text.clone())),\n                                node,\n                                ChildPosition::Start,\n                            );\n                        }\n                    }\n                    if let Some(ref content) = computed_after {\n                        if let Some(pseudo_content) = content.content.val() {\n                            node = insert_child(\n                                RenderNode::new(Text(pseudo_content.text.clone())),\n                                node,\n                                ChildPosition::End,\n                            );\n                        }\n                    }\n                    node\n                };\n                // Insert extra content nodes\n                match result {\n                    Finished(node) => Finished(wrap_nodes(node)),\n                    // Do we need to wrap a Nothing?\n                    Nothing => Nothing,\n                    PendingChildren {\n                        children,\n                        cons,\n                        prefn,\n                        postfn,\n                    } => PendingChildren {\n                        children,\n                        prefn,\n                        postfn,\n                        cons: Box::new(move |ctx, ch| match cons(ctx, ch)? {\n                            None => Ok(None),\n                            Some(node) => Ok(Some(wrap_nodes(node))),\n                        }),\n                    },\n                }\n            } else {\n                result\n            };\n\n            let Some(fragname) = fragment else {\n                return Ok(result);\n            };\n            match result {\n                Finished(node) => Finished(insert_child(\n                    RenderNode::new(FragStart(fragname)),\n                    node,\n                    ChildPosition::Start,\n                )),\n                Nothing => Finished(RenderNode::new(FragStart(fragname))),\n                PendingChildren {\n                    children,\n                    cons,\n                    prefn,\n                    postfn,\n                } => PendingChildren {\n                    children,\n                    prefn,\n                    postfn,\n                    cons: Box::new(move |ctx, ch| {\n                        let fragnode = RenderNode::new(FragStart(fragname));\n                        match cons(ctx, ch)? {\n                            None => Ok(Some(fragnode)),\n                            Some(node) => {\n                                Ok(Some(insert_child(fragnode, node, ChildPosition::Start)))\n                            }\n                        }\n                    }),\n                },\n            }\n        }\n        markup5ever_rcdom::NodeData::Text { contents: ref tstr } => {\n            #[cfg(feature = \"css_ext\")]\n            if !input.extra_styles.borrow().is_empty() {\n                let mut nodes = Vec::new();\n                let mut offset = 0;\n                for part in &*input.extra_styles.borrow() {\n                    let (start, end) = (part.0.start, part.0.end);\n                    if start > offset {\n                        // Handle the unstyled bit at the start\n                        nodes.push(RenderNode::new(Text((tstr.borrow()[offset..start]).into())));\n                    }\n                    let mut cstyle = input.parent_style.inherit();\n                    cstyle.colour.maybe_update(\n                        // TODO: use the right specificity\n                        cstyle.syntax.important,\n                        cstyle.syntax.origin,\n                        cstyle.syntax.specificity,\n                        part.1.fg_colour,\n                    );\n                    if let Some(bgcol) = part.1.bg_colour {\n                        cstyle.bg_colour.maybe_update(\n                            // TODO: use the right specificity\n                            cstyle.syntax.important,\n                            cstyle.syntax.origin,\n                            cstyle.syntax.specificity,\n                            bgcol,\n                        );\n                    }\n                    // Now the styled part\n                    nodes.push(RenderNode::new_styled(\n                        Text((tstr.borrow()[start..end]).into()),\n                        cstyle,\n                    ));\n                    offset = end;\n                }\n                // the final bit\n                if offset < tstr.borrow().len() {\n                    nodes.push(RenderNode::new(Text((tstr.borrow()[offset..]).into())));\n                }\n                if nodes.len() == 1 {\n                    return Ok(Finished(nodes.pop().unwrap()));\n                } else {\n                    return Ok(Finished(RenderNode::new(RenderNodeInfo::Container(nodes))));\n                }\n            }\n\n            Finished(RenderNode::new(Text((&*tstr.borrow()).into())))\n        }\n        _ => {\n            // NodeData doesn't have a Debug impl.\n            writeln!(err_out, \"Unhandled node type.\").unwrap();\n            Nothing\n        }\n    })\n}\n\nfn render_tree_to_string<T: Write, D: TextDecorator>(\n    context: &mut HtmlContext,\n    renderer: SubRenderer<D>,\n    decorator: &D,\n    tree: RenderNode,\n    err_out: &mut T,\n) -> Result<SubRenderer<D>> {\n    /* Phase 1: get size estimates. */\n    // can't actually error, but Ok-wrap to satisfy tree_map_reduce signature\n    tree_map_reduce(context, &tree, |context, node| {\n        Ok(precalc_size_estimate(node, context, decorator))\n    })?;\n    /* Phase 2: actually render. */\n    let mut renderer = TextRenderer::new(renderer);\n    tree_map_reduce(&mut renderer, tree, |renderer, node| {\n        Ok(do_render_node(renderer, node, err_out)?)\n    })?;\n    let (mut renderer, links) = renderer.into_inner();\n    let lines = renderer.finalise(links);\n    // And add the links\n    if !lines.is_empty() {\n        renderer.start_block()?;\n        renderer.fmt_links(lines);\n    }\n    Ok(renderer)\n}\n\nfn pending2<\n    D: TextDecorator,\n    F: FnOnce(\n            &mut TextRenderer<D>,\n            Vec<Option<SubRenderer<D>>>,\n        ) -> Result<Option<Option<SubRenderer<D>>>>\n        + 'static,\n>(\n    children: Vec<RenderNode>,\n    f: F,\n) -> TreeMapResult<'static, TextRenderer<D>, RenderNode, Option<SubRenderer<D>>> {\n    TreeMapResult::PendingChildren {\n        children,\n        cons: Box::new(f),\n        prefn: None,\n        postfn: None,\n    }\n}\n\n/// Keep track of what style state has been applied to a renderer so that we\n/// can undo it.\n#[derive(Default)]\nstruct PushedStyleInfo {\n    colour: bool,\n    bgcolour: bool,\n    white_space: bool,\n    preformat: bool,\n}\n\nimpl PushedStyleInfo {\n    fn apply<D: TextDecorator>(render: &mut TextRenderer<D>, style: &ComputedStyle) -> Self {\n        #[allow(unused_mut)]\n        let mut result: PushedStyleInfo = Default::default();\n        #[cfg(feature = \"css\")]\n        if let Some(col) = style.colour.val() {\n            render.push_colour(*col);\n            result.colour = true;\n        }\n        #[cfg(feature = \"css\")]\n        if let Some(col) = style.bg_colour.val() {\n            render.push_bgcolour(*col);\n            result.bgcolour = true;\n        }\n        if let Some(ws) = style.white_space.val() {\n            if let WhiteSpace::Pre | WhiteSpace::PreWrap = ws {\n                render.push_ws(*ws);\n                result.white_space = true;\n            }\n        }\n        if style.internal_pre {\n            render.push_preformat();\n            result.preformat = true;\n        }\n        result\n    }\n    fn unwind<D: TextDecorator>(self, renderer: &mut TextRenderer<D>) {\n        if self.bgcolour {\n            renderer.pop_bgcolour();\n        }\n        if self.colour {\n            renderer.pop_colour();\n        }\n        if self.white_space {\n            renderer.pop_ws();\n        }\n        if self.preformat {\n            renderer.pop_preformat();\n        }\n    }\n}\n\nfn do_render_node<T: Write, D: TextDecorator>(\n    renderer: &mut TextRenderer<D>,\n    tree: RenderNode,\n    err_out: &mut T,\n) -> render::Result<TreeMapResult<'static, TextRenderer<D>, RenderNode, Option<SubRenderer<D>>>> {\n    html_trace!(\"do_render_node({:?}\", tree);\n    use RenderNodeInfo::*;\n    use TreeMapResult::*;\n\n    let size_estimate = tree.size_estimate.get().unwrap_or_default();\n\n    let pushed_style = PushedStyleInfo::apply(renderer, &tree.style);\n\n    Ok(match tree.info {\n        Text(ref tstr) => {\n            renderer.add_inline_text(tstr)?;\n            pushed_style.unwind(renderer);\n            Finished(None)\n        }\n        Container(children) => pending2(children, |renderer, _| {\n            pushed_style.unwind(renderer);\n            Ok(Some(None))\n        }),\n        Link(href, children) => {\n            renderer.start_link(&href)?;\n            pending2(children, move |renderer: &mut TextRenderer<D>, _| {\n                renderer.end_link()?;\n                pushed_style.unwind(renderer);\n                Ok(Some(None))\n            })\n        }\n        Em(children) => {\n            renderer.start_emphasis()?;\n            pending2(children, |renderer: &mut TextRenderer<D>, _| {\n                renderer.end_emphasis()?;\n                pushed_style.unwind(renderer);\n                Ok(Some(None))\n            })\n        }\n        Strong(children) => {\n            renderer.start_strong()?;\n            pending2(children, |renderer: &mut TextRenderer<D>, _| {\n                renderer.end_strong()?;\n                pushed_style.unwind(renderer);\n                Ok(Some(None))\n            })\n        }\n        Strikeout(children) => {\n            renderer.start_strikeout()?;\n            pending2(children, |renderer: &mut TextRenderer<D>, _| {\n                renderer.end_strikeout()?;\n                pushed_style.unwind(renderer);\n                Ok(Some(None))\n            })\n        }\n        Code(children) => {\n            renderer.start_code()?;\n            pending2(children, |renderer: &mut TextRenderer<D>, _| {\n                renderer.end_code()?;\n                pushed_style.unwind(renderer);\n                Ok(Some(None))\n            })\n        }\n        Img(src, title) => {\n            renderer.add_image(&src, &title)?;\n            pushed_style.unwind(renderer);\n            Finished(None)\n        }\n        Svg(title) => {\n            renderer.add_image(\"\", &title)?;\n            pushed_style.unwind(renderer);\n            Finished(None)\n        }\n        Block(children) | ListItem(children) => {\n            renderer.start_block()?;\n            pending2(children, |renderer: &mut TextRenderer<D>, _| {\n                renderer.end_block();\n                pushed_style.unwind(renderer);\n                Ok(Some(None))\n            })\n        }\n        Header(level, children) => {\n            let prefix = renderer.header_prefix(level);\n            let prefix_size = size_estimate.prefix_size;\n            debug_assert!(prefix.len() == prefix_size);\n            let min_width = size_estimate.min_width;\n            let inner_width = min_width.saturating_sub(prefix_size);\n            let sub_builder =\n                renderer.new_sub_renderer(renderer.width_minus(prefix_size, inner_width)?)?;\n            renderer.push(sub_builder);\n            pending2(children, move |renderer: &mut TextRenderer<D>, _| {\n                let sub_builder = renderer.pop();\n\n                renderer.start_block()?;\n                renderer.append_subrender(sub_builder, repeat(&prefix[..]))?;\n                renderer.end_block();\n                pushed_style.unwind(renderer);\n                Ok(Some(None))\n            })\n        }\n        Div(children) => {\n            renderer.new_line()?;\n            pending2(children, |renderer: &mut TextRenderer<D>, _| {\n                renderer.new_line()?;\n                pushed_style.unwind(renderer);\n                Ok(Some(None))\n            })\n        }\n        BlockQuote(children) => {\n            let prefix = renderer.quote_prefix();\n            debug_assert!(size_estimate.prefix_size == prefix.len());\n            let inner_width = size_estimate.min_width - prefix.len();\n            let sub_builder =\n                renderer.new_sub_renderer(renderer.width_minus(prefix.len(), inner_width)?)?;\n            renderer.push(sub_builder);\n            pending2(children, move |renderer: &mut TextRenderer<D>, _| {\n                let sub_builder = renderer.pop();\n\n                renderer.start_block()?;\n                renderer.append_subrender(sub_builder, repeat(&prefix[..]))?;\n                renderer.end_block();\n                pushed_style.unwind(renderer);\n                Ok(Some(None))\n            })\n        }\n        Ul(items) => {\n            let prefix = renderer.unordered_item_prefix();\n            let prefix_len = prefix.len();\n\n            TreeMapResult::PendingChildren {\n                children: items,\n                cons: Box::new(|renderer, _| {\n                    pushed_style.unwind(renderer);\n                    Ok(Some(None))\n                }),\n                prefn: Some(Box::new(move |renderer: &mut TextRenderer<D>, _| {\n                    let inner_width = size_estimate.min_width - prefix_len;\n                    let sub_builder = renderer\n                        .new_sub_renderer(renderer.width_minus(prefix_len, inner_width)?)?;\n                    renderer.push(sub_builder);\n                    Ok(())\n                })),\n                postfn: Some(Box::new(move |renderer: &mut TextRenderer<D>, _| {\n                    let sub_builder = renderer.pop();\n\n                    let indent = \" \".repeat(prefix.len());\n\n                    renderer.append_subrender(\n                        sub_builder,\n                        once(&prefix[..]).chain(repeat(&indent[..])),\n                    )?;\n                    Ok(())\n                })),\n            }\n        }\n        Ol(start, items) => {\n            let num_items = items.len();\n\n            // The prefix width could be at either end if the start is negative.\n            let min_number = start;\n            // Assumption: num_items can't overflow isize.\n            let max_number = start + (num_items as i64) - 1;\n            let prefix_width_min = renderer.ordered_item_prefix(min_number).len();\n            let prefix_width_max = renderer.ordered_item_prefix(max_number).len();\n            let prefix_width = max(prefix_width_min, prefix_width_max);\n            let prefixn = format!(\"{: <width$}\", \"\", width = prefix_width);\n            let i: Cell<_> = Cell::new(start);\n\n            TreeMapResult::PendingChildren {\n                children: items,\n                cons: Box::new(|renderer, _| {\n                    pushed_style.unwind(renderer);\n                    Ok(Some(None))\n                }),\n                prefn: Some(Box::new(move |renderer: &mut TextRenderer<D>, _| {\n                    let inner_min = size_estimate.min_width - size_estimate.prefix_size;\n                    let sub_builder = renderer\n                        .new_sub_renderer(renderer.width_minus(prefix_width, inner_min)?)?;\n                    renderer.push(sub_builder);\n                    Ok(())\n                })),\n                postfn: Some(Box::new(move |renderer: &mut TextRenderer<D>, _| {\n                    let sub_builder = renderer.pop();\n                    let prefix1 = renderer.ordered_item_prefix(i.get());\n                    let prefix1 = format!(\"{: <width$}\", prefix1, width = prefix_width);\n\n                    renderer.append_subrender(\n                        sub_builder,\n                        once(prefix1.as_str()).chain(repeat(prefixn.as_str())),\n                    )?;\n                    i.set(i.get() + 1);\n                    Ok(())\n                })),\n            }\n        }\n        Dl(items) => {\n            renderer.start_block()?;\n\n            TreeMapResult::PendingChildren {\n                children: items,\n                cons: Box::new(|renderer, _| {\n                    pushed_style.unwind(renderer);\n                    Ok(Some(None))\n                }),\n                prefn: None,\n                postfn: None,\n            }\n        }\n        Dt(children) => {\n            renderer.new_line()?;\n            renderer.start_emphasis()?;\n            pending2(children, |renderer: &mut TextRenderer<D>, _| {\n                renderer.end_emphasis()?;\n                pushed_style.unwind(renderer);\n                Ok(Some(None))\n            })\n        }\n        Dd(children) => {\n            let inner_min = size_estimate.min_width - 2;\n            let sub_builder = renderer.new_sub_renderer(renderer.width_minus(2, inner_min)?)?;\n            renderer.push(sub_builder);\n            pending2(children, |renderer: &mut TextRenderer<D>, _| {\n                let sub_builder = renderer.pop();\n                renderer.append_subrender(sub_builder, repeat(\"  \"))?;\n                pushed_style.unwind(renderer);\n                Ok(Some(None))\n            })\n        }\n        Break => {\n            renderer.new_line_hard()?;\n            pushed_style.unwind(renderer);\n            Finished(None)\n        }\n        Table(tab) => render_table_tree(renderer, tab, err_out)?,\n        TableRow(row, false) => render_table_row(renderer, row, pushed_style, err_out),\n        TableRow(row, true) => render_table_row_vert(renderer, row, pushed_style, err_out),\n        TableBody(_) => unimplemented!(\"Unexpected TableBody while rendering\"),\n        TableCell(cell) => render_table_cell(renderer, cell, pushed_style, err_out),\n        FragStart(fragname) => {\n            renderer.record_frag_start(&fragname);\n            pushed_style.unwind(renderer);\n            Finished(None)\n        }\n        Sup(children) => {\n            // Special case for digit-only superscripts - use superscript\n            // characters.\n            fn sup_digits(children: &[RenderNode]) -> Option<String> {\n                let [node] = children else {\n                    return None;\n                };\n                if let Text(s) = &node.info {\n                    if s.chars().all(|d| d.is_ascii_digit()) {\n                        // It's just a string of digits - replace by superscript characters.\n                        const SUPERSCRIPTS: [char; 10] =\n                            ['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];\n                        return Some(\n                            s.bytes()\n                                .map(|b| SUPERSCRIPTS[(b - b'0') as usize])\n                                .collect(),\n                        );\n                    }\n                }\n                None\n            }\n            if let Some(digitstr) = sup_digits(&children) {\n                renderer.add_inline_text(&digitstr)?;\n                pushed_style.unwind(renderer);\n                Finished(None)\n            } else {\n                renderer.start_superscript()?;\n                pending2(children, |renderer: &mut TextRenderer<D>, _| {\n                    renderer.end_superscript()?;\n                    pushed_style.unwind(renderer);\n                    Ok(Some(None))\n                })\n            }\n        }\n    })\n}\n\nfn render_table_tree<T: Write, D: TextDecorator>(\n    renderer: &mut TextRenderer<D>,\n    table: RenderTable,\n    _err_out: &mut T,\n) -> render::Result<TreeMapResult<'static, TextRenderer<D>, RenderNode, Option<SubRenderer<D>>>> {\n    /* Now lay out the table. */\n    let num_columns = table.num_columns;\n\n    /* Heuristic: scale the column widths according to how much content there is. */\n    let mut col_sizes: Vec<SizeEstimate> = vec![Default::default(); num_columns];\n\n    for row in table.rows() {\n        let mut colno = 0;\n        for cell in row.cells() {\n            // FIXME: get_size_estimate is still recursive.\n            let mut estimate = cell.get_size_estimate();\n\n            // If the cell has a colspan>1, then spread its size between the\n            // columns.\n            estimate.size /= cell.colspan;\n            estimate.min_width /= cell.colspan;\n            for i in 0..cell.colspan {\n                col_sizes[colno + i] = (col_sizes[colno + i]).max(estimate);\n            }\n            colno += cell.colspan;\n        }\n    }\n    let tot_size: usize = col_sizes.iter().map(|est| est.size).sum();\n    let min_size: usize = col_sizes.iter().map(|est| est.min_width).sum::<usize>()\n        + col_sizes.len().saturating_sub(1);\n    let width = renderer.width();\n\n    let vert_row = renderer.options.raw || (min_size > width || width == 0);\n\n    let mut col_widths: Vec<usize> = if !vert_row {\n        col_sizes\n            .iter()\n            .map(|sz| {\n                if sz.size == 0 {\n                    0\n                } else {\n                    min(\n                        sz.size,\n                        if usize::MAX / width <= sz.size {\n                            // The provided width is too large to multiply by width,\n                            // so do it the other way around.\n                            max((width / tot_size) * sz.size, sz.min_width)\n                        } else {\n                            max(sz.size * width / tot_size, sz.min_width)\n                        },\n                    )\n                }\n            })\n            .collect()\n    } else {\n        col_sizes.iter().map(|_| width).collect()\n    };\n\n    if !vert_row {\n        let num_cols = col_widths.len();\n        if num_cols > 0 {\n            loop {\n                let cur_width = col_widths.iter().sum::<usize>() + num_cols - 1;\n                if cur_width <= width {\n                    break;\n                }\n                let (i, _) = col_widths\n                    .iter()\n                    .enumerate()\n                    .max_by_key(|&(colno, width)| {\n                        (\n                            width.saturating_sub(col_sizes[colno].min_width),\n                            width,\n                            usize::MAX - colno,\n                        )\n                    })\n                    .unwrap();\n                col_widths[i] -= 1;\n            }\n        }\n    }\n\n    let table_width = if vert_row {\n        width\n    } else {\n        col_widths.iter().cloned().sum::<usize>()\n            + col_widths\n                .iter()\n                .filter(|&w| w > &0)\n                .count()\n                .saturating_sub(1)\n    };\n\n    renderer.start_table()?;\n\n    if table_width != 0 && renderer.options.draw_borders {\n        renderer.add_horizontal_border_width(table_width)?;\n    }\n\n    Ok(TreeMapResult::PendingChildren {\n        children: table.into_rows(col_widths, vert_row),\n        cons: Box::new(|_, _| Ok(Some(None))),\n        prefn: None,\n        postfn: None,\n    })\n}\n\nfn render_table_row<T: Write, D: TextDecorator>(\n    _renderer: &mut TextRenderer<D>,\n    row: RenderTableRow,\n    pushed_style: PushedStyleInfo,\n    _err_out: &mut T,\n) -> TreeMapResult<'static, TextRenderer<D>, RenderNode, Option<SubRenderer<D>>> {\n    let rowspans: Vec<usize> = row.cells().map(|cell| cell.rowspan).collect();\n    let have_overhang = row.cells().any(|cell| cell.is_dummy);\n    TreeMapResult::PendingChildren {\n        children: row.into_cells(false),\n        cons: Box::new(move |builders, children| {\n            let children: Vec<_> = children.into_iter().map(Option::unwrap).collect();\n            if have_overhang || children.iter().any(|c| !c.empty()) {\n                builders.append_columns_with_borders(\n                    children.into_iter().zip(rowspans.into_iter()),\n                    true,\n                )?;\n            }\n            pushed_style.unwind(builders);\n            Ok(Some(None))\n        }),\n        prefn: Some(Box::new(|renderer: &mut TextRenderer<D>, node| {\n            if let RenderNodeInfo::TableCell(ref cell) = node.info {\n                let sub_builder = renderer.new_sub_renderer(cell.col_width.unwrap())?;\n                renderer.push(sub_builder);\n                Ok(())\n            } else {\n                panic!()\n            }\n        })),\n        postfn: Some(Box::new(|_renderer: &mut TextRenderer<D>, _| Ok(()))),\n    }\n}\n\nfn render_table_row_vert<T: Write, D: TextDecorator>(\n    _renderer: &mut TextRenderer<D>,\n    row: RenderTableRow,\n    pushed_style: PushedStyleInfo,\n    _err_out: &mut T,\n) -> TreeMapResult<'static, TextRenderer<D>, RenderNode, Option<SubRenderer<D>>> {\n    TreeMapResult::PendingChildren {\n        children: row.into_cells(true),\n        cons: Box::new(|builders, children| {\n            let children: Vec<_> = children.into_iter().map(Option::unwrap).collect();\n            builders.append_vert_row(children)?;\n            pushed_style.unwind(builders);\n            Ok(Some(None))\n        }),\n        prefn: Some(Box::new(|renderer: &mut TextRenderer<D>, node| {\n            if let RenderNodeInfo::TableCell(ref cell) = node.info {\n                let sub_builder = renderer.new_sub_renderer(cell.col_width.unwrap())?;\n                renderer.push(sub_builder);\n                Ok(())\n            } else {\n                Err(Error::Fail)\n            }\n        })),\n        postfn: Some(Box::new(|_renderer: &mut TextRenderer<D>, _| Ok(()))),\n    }\n}\n\nfn render_table_cell<T: Write, D: TextDecorator>(\n    _renderer: &mut TextRenderer<D>,\n    cell: RenderTableCell,\n    pushed_style: PushedStyleInfo,\n    _err_out: &mut T,\n) -> TreeMapResult<'static, TextRenderer<D>, RenderNode, Option<SubRenderer<D>>> {\n    pending2(cell.content, |renderer: &mut TextRenderer<D>, _| {\n        pushed_style.unwind(renderer);\n        let sub_builder = renderer.pop();\n\n        Ok(Some(Some(sub_builder)))\n    })\n}\n\npub mod config {\n    //! Configure the HTML to text translation using the `Config` type, which can be\n    //! constructed using one of the functions in this module.\n    use std::io;\n\n    use super::Error;\n    use crate::css::types::Importance;\n    use crate::css::{Ruleset, Selector, SelectorComponent, Style, StyleData};\n    #[cfg(feature = \"css_ext\")]\n    use crate::{HighlighterMap, SyntaxHighlighter};\n    use crate::{\n        HtmlContext, MIN_WIDTH, RenderTree, Result,\n        css::{PseudoContent, PseudoElement, StyleDecl},\n        render::text_renderer::{\n            PlainDecorator, RichAnnotation, RichDecorator, TaggedLine, TextDecorator,\n        },\n    };\n\n    /// Specify how images with missing or empty alt text are handled\n    #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]\n    #[non_exhaustive]\n    pub enum ImageRenderMode {\n        /// Ignore `<img>` without alt, or `<svg>` without `<title>`.\n        #[default]\n        IgnoreEmpty,\n        /// Always process images (will be handled by the decorator)\n        ShowAlways,\n        /// Use a fixed replacement text (e.g. emoji)\n        Replace(&'static str),\n        /// Replace with the last component of the link filename if any\n        Filename,\n    }\n\n    #[cfg(feature = \"xml\")]\n    /// Specify HTML vs XHTML handling\n    #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]\n    #[non_exhaustive]\n    pub enum XmlMode {\n        /// Treat as HTML unless the document starts with an XML declaration\n        /// (`<?xml ...?>`).\n        #[default]\n        Auto,\n        /// Always treat as HTML\n        Html,\n        /// Always treat as XHTML\n        Xhtml,\n    }\n\n    /// Configure the HTML processing.\n    pub struct Config<D: TextDecorator> {\n        decorator: D,\n\n        max_wrap_width: Option<usize>,\n\n        style: StyleData,\n        #[cfg(feature = \"css\")]\n        use_doc_css: bool,\n\n        pad_block_width: bool,\n\n        allow_width_overflow: bool,\n        min_wrap_width: usize,\n        raw: bool,\n        draw_borders: bool,\n        wrap_links: bool,\n        include_link_footnotes: bool,\n        use_unicode_strikeout: bool,\n        image_mode: ImageRenderMode,\n\n        #[cfg(feature = \"xml\")]\n        xml_mode: XmlMode,\n\n        #[cfg(feature = \"css_ext\")]\n        syntax_highlighters: HighlighterMap,\n    }\n\n    impl<D: TextDecorator> Config<D> {\n        /// Make the HtmlContext from self.\n        pub(crate) fn make_context(&self) -> HtmlContext {\n            HtmlContext {\n                style_data: self.style.clone(),\n                #[cfg(feature = \"css\")]\n                use_doc_css: self.use_doc_css,\n\n                max_wrap_width: self.max_wrap_width,\n                pad_block_width: self.pad_block_width,\n                allow_width_overflow: self.allow_width_overflow,\n                min_wrap_width: self.min_wrap_width,\n                raw: self.raw,\n                draw_borders: self.draw_borders,\n                wrap_links: self.wrap_links,\n                include_link_footnotes: self.include_link_footnotes,\n                use_unicode_strikeout: self.use_unicode_strikeout,\n                image_mode: self.image_mode,\n\n                #[cfg(feature = \"xml\")]\n                xml_mode: self.xml_mode,\n\n                #[cfg(feature = \"css_ext\")]\n                syntax_highlighters: self.syntax_highlighters.clone(),\n            }\n        }\n        /// Parse with context.\n        pub(crate) fn do_parse<R>(&self, context: &mut HtmlContext, input: R) -> Result<RenderTree>\n        where\n            R: io::Read,\n        {\n            #[cfg(feature = \"xml\")]\n            let dom = {\n                match context.xml_mode {\n                    XmlMode::Html => self.parse_html(input)?,\n                    XmlMode::Xhtml => self.parse_xml(input)?,\n                    XmlMode::Auto => {\n                        const XML_CHECK: &[u8] = b\"<?xml\";\n                        let mut input = input;\n                        let mut firstbuf = [0u8; XML_CHECK.len()];\n                        let bytes_read = input.read(&mut firstbuf)?;\n                        let first_slice = &firstbuf[..bytes_read];\n                        if bytes_read == XML_CHECK.len() && &firstbuf == XML_CHECK {\n                            self.parse_xml(std::io::Read::chain(first_slice, input))?\n                        } else {\n                            self.parse_html(std::io::Read::chain(first_slice, input))?\n                        }\n                    }\n                }\n            };\n\n            #[cfg(not(feature = \"xml\"))]\n            let dom = self.parse_html(input)?;\n\n            let render_tree = super::dom_to_render_tree_with_context(\n                dom.document.clone(),\n                &mut io::sink(),\n                context,\n            )?\n            .ok_or(Error::Fail)?;\n            Ok(RenderTree(render_tree))\n        }\n\n        /// Parse the HTML into a DOM structure.\n        pub fn parse_html<R: io::Read>(&self, mut input: R) -> Result<super::RcDom> {\n            use html5ever::tendril::TendrilSink;\n            let opts = super::ParseOpts {\n                tree_builder: super::TreeBuilderOpts {\n                    scripting_enabled: false,\n                    ..Default::default()\n                },\n                ..Default::default()\n            };\n            Ok(super::parse_document(super::RcDom::default(), opts)\n                .from_utf8()\n                .read_from(&mut input)?)\n        }\n\n        #[cfg(feature = \"xml\")]\n        /// Parse document as XML into a DOM structure.\n        pub fn parse_xml<R: io::Read>(&self, mut input: R) -> Result<super::RcDom> {\n            use ::xml5ever::{driver::parse_document, tendril::TendrilSink};\n            let opts = Default::default();\n            Ok(parse_document(super::RcDom::default(), opts)\n                .from_utf8()\n                .read_from(&mut input)?)\n        }\n\n        /// Convert an HTML DOM into a RenderTree.\n        pub fn dom_to_render_tree(&self, dom: &super::RcDom) -> Result<RenderTree> {\n            Ok(RenderTree(\n                super::dom_to_render_tree_with_context(\n                    dom.document.clone(),\n                    &mut io::sink(),\n                    &mut self.make_context(),\n                )?\n                .ok_or(Error::Fail)?,\n            ))\n        }\n\n        /// Render an existing RenderTree into a string.\n        pub fn render_to_string(&self, render_tree: RenderTree, width: usize) -> Result<String> {\n            let s = render_tree\n                .render_with_context(\n                    &mut self.make_context(),\n                    width,\n                    self.decorator.make_subblock_decorator(),\n                )?\n                .into_string()?;\n            Ok(s)\n        }\n\n        /// Take an existing RenderTree, and returns text wrapped to `width` columns.\n        /// The text is returned as a `Vec<TaggedLine<_>>`; the annotations are vectors\n        /// of the provided text decorator's `Annotation`.  The \"outer\" annotation comes first in\n        /// the `Vec`.\n        pub fn render_to_lines(\n            &self,\n            render_tree: RenderTree,\n            width: usize,\n        ) -> Result<Vec<TaggedLine<Vec<D::Annotation>>>> {\n            render_tree\n                .render_with_context(\n                    &mut self.make_context(),\n                    width,\n                    self.decorator.make_subblock_decorator(),\n                )?\n                .into_lines()\n        }\n\n        /// Reads HTML from `input`, and returns a `String` with text wrapped to\n        /// `width` columns.\n        pub fn string_from_read<R: std::io::Read>(self, input: R, width: usize) -> Result<String> {\n            let mut context = self.make_context();\n            let s = self\n                .do_parse(&mut context, input)?\n                .render_with_context(&mut context, width, self.decorator)?\n                .into_string()?;\n            Ok(s)\n        }\n\n        /// Reads HTML from `input`, and returns text wrapped to `width` columns.\n        /// The text is returned as a `Vec<TaggedLine<_>>`; the annotations are vectors\n        /// of the provided text decorator's `Annotation`.  The \"outer\" annotation comes first in\n        /// the `Vec`.\n        pub fn lines_from_read<R: std::io::Read>(\n            self,\n            input: R,\n            width: usize,\n        ) -> Result<Vec<TaggedLine<Vec<D::Annotation>>>> {\n            let mut context = self.make_context();\n            self.do_parse(&mut context, input)?\n                .render_with_context(&mut context, width, self.decorator)?\n                .into_lines()\n        }\n\n        #[cfg(feature = \"css\")]\n        /// Add some CSS rules which will be used (if supported) with any\n        /// HTML processed.\n        pub fn add_css(mut self, css: &str) -> Result<Self> {\n            self.style.add_user_css(css)?;\n            Ok(self)\n        }\n\n        #[cfg(feature = \"css\")]\n        /// Add some agent CSS rules which will be used (if supported) with any\n        /// HTML processed.\n        pub fn add_agent_css(mut self, css: &str) -> Result<Self> {\n            self.style.add_agent_css(css)?;\n            Ok(self)\n        }\n\n        #[cfg(feature = \"css\")]\n        /// Parse CSS from any \\<style\\> elements and use supported rules.\n        pub fn use_doc_css(mut self) -> Self {\n            self.use_doc_css = true;\n            self\n        }\n\n        /// Pad lines out to the full render width.\n        pub fn pad_block_width(mut self) -> Self {\n            self.pad_block_width = true;\n            self\n        }\n\n        /// Set the maximum text wrap width.\n        /// When set, paragraphs will be wrapped to that width even if there\n        /// is more total width available for rendering.\n        pub fn max_wrap_width(mut self, wrap_width: usize) -> Self {\n            self.max_wrap_width = Some(wrap_width);\n            self\n        }\n\n        /// Allow the output to be wider than the max width.  When enabled,\n        /// then output wider than the specified width will be returned\n        /// instead of returning `Err(TooNarrow)` if the output wouldn't\n        /// otherwise fit.\n        pub fn allow_width_overflow(mut self) -> Self {\n            self.allow_width_overflow = true;\n            self\n        }\n\n        /// Set the minimum width for text wrapping.  The default is 3.\n        /// Blocks of text will be forced to have at least this width\n        /// (unless the text inside is less than that).  Increasing this\n        /// can increase the chance that the width will overflow, leading\n        /// to a TooNarrow error unless `allow_width_overflow()` is set.\n        pub fn min_wrap_width(mut self, min_wrap_width: usize) -> Self {\n            self.min_wrap_width = min_wrap_width;\n            self\n        }\n\n        /// Raw extraction, ensures text in table cells ends up rendered together\n        /// This traverses tables as if they had a single column and every cell is its own row.\n        /// Implies `no_table_borders()`\n        pub fn raw_mode(mut self, raw: bool) -> Self {\n            self.raw = raw;\n            self.draw_borders = false;\n            self\n        }\n\n        /// Do not render table borders\n        pub fn no_table_borders(mut self) -> Self {\n            self.draw_borders = false;\n            self\n        }\n        /// Do not wrap links\n        pub fn no_link_wrapping(mut self) -> Self {\n            self.wrap_links = false;\n            self\n        }\n\n        /// Select whether to use Unicode combining characters to strike out text.\n        pub fn unicode_strikeout(mut self, use_unicode: bool) -> Self {\n            self.use_unicode_strikeout = use_unicode;\n            self\n        }\n\n        /// Make a simple \"contains\" type rule for an element.\n        fn make_surround_rule(element: &str, after: bool, content: &str) -> Ruleset {\n            Ruleset {\n                selector: Selector {\n                    components: vec![SelectorComponent::Element(element.into())],\n                    pseudo_element: Some(if after {\n                        PseudoElement::After\n                    } else {\n                        PseudoElement::Before\n                    }),\n                },\n                styles: vec![StyleDecl {\n                    style: Style::Content(PseudoContent {\n                        text: content.into(),\n                    }),\n                    importance: Importance::Default,\n                }],\n            }\n        }\n\n        /// Decorate <em> etc. similarly to markdown\n        pub fn do_decorate(mut self) -> Self {\n            self.style.add_agent_rules(&[\n                Self::make_surround_rule(\"em\", false, \"*\"),\n                Self::make_surround_rule(\"em\", true, \"*\"),\n                Self::make_surround_rule(\"dt\", false, \"*\"),\n                Self::make_surround_rule(\"dt\", true, \"*\"),\n                Self::make_surround_rule(\"strong\", false, \"**\"),\n                Self::make_surround_rule(\"strong\", true, \"**\"),\n                Self::make_surround_rule(\"b\", false, \"**\"),\n                Self::make_surround_rule(\"b\", true, \"**\"),\n                Self::make_surround_rule(\"code\", false, \"`\"),\n                Self::make_surround_rule(\"code\", true, \"`\"),\n            ]);\n            self\n        }\n\n        /// Add footnotes for hyperlinks\n        pub fn link_footnotes(mut self, include_footnotes: bool) -> Self {\n            self.include_link_footnotes = include_footnotes;\n            self\n        }\n\n        /// Configure how images with no alt text are handled.\n        pub fn empty_img_mode(mut self, img_mode: ImageRenderMode) -> Self {\n            self.image_mode = img_mode;\n            self\n        }\n\n        #[cfg(feature = \"xml\")]\n        /// Configure the HTML vs XHTML parsing mode.\n        pub fn xml_mode(mut self, xml_mode: XmlMode) -> Self {\n            self.xml_mode = xml_mode;\n            self\n        }\n\n        #[cfg(feature = \"css_ext\")]\n        /// Register a named syntax highlighter.\n        ///\n        /// The highlighter will be used when a `<pre>` element\n        /// is styled with `x-syntax: name`\n        pub fn register_highlighter(\n            mut self,\n            name: impl Into<String>,\n            f: SyntaxHighlighter,\n        ) -> Self {\n            use std::rc::Rc;\n\n            self.syntax_highlighters.insert(name.into(), Rc::new(f));\n            self\n        }\n    }\n\n    impl Config<RichDecorator> {\n        /// Return coloured text.  `colour_map` is a function which takes\n        /// a list of `RichAnnotation` and some text, and returns the text\n        /// with any terminal escapes desired to indicate those annotations\n        /// (such as colour).\n        pub fn coloured<R, FMap>(self, input: R, width: usize, colour_map: FMap) -> Result<String>\n        where\n            R: std::io::Read,\n            FMap: Fn(&[RichAnnotation], &str) -> String,\n        {\n            let mut context = self.make_context();\n            let render_tree = self.do_parse(&mut context, input)?;\n            self.render_coloured(render_tree, width, colour_map)\n        }\n\n        /// Return coloured text from a RenderTree.  `colour_map` is a function which takes a list\n        /// of `RichAnnotation` and some text, and returns the text with any terminal escapes\n        /// desired to indicate those annotations (such as colour).\n        pub fn render_coloured<FMap>(\n            &self,\n            render_tree: RenderTree,\n            width: usize,\n            colour_map: FMap,\n        ) -> Result<String>\n        where\n            FMap: Fn(&[RichAnnotation], &str) -> String,\n        {\n            let lines = self.render_to_lines(render_tree, width)?;\n\n            let mut result = String::new();\n            for line in lines {\n                for ts in line.tagged_strings() {\n                    result.push_str(&colour_map(&ts.tag, &ts.s));\n                }\n                result.push('\\n');\n            }\n            Ok(result)\n        }\n    }\n\n    /// Return a Config initialized with a `RichDecorator`.\n    pub fn rich() -> Config<RichDecorator> {\n        with_decorator(RichDecorator::new())\n    }\n\n    /// Return a Config initialized with a `PlainDecorator`.\n    pub fn plain() -> Config<PlainDecorator> {\n        with_decorator(PlainDecorator::new())\n            .do_decorate()\n            .link_footnotes(true)\n    }\n\n    /// Return a Config initialized with a `PlainDecorator`.\n    pub fn plain_no_decorate() -> Config<PlainDecorator> {\n        with_decorator(PlainDecorator::new())\n    }\n\n    /// Return a Config initialized with a custom decorator.\n    pub fn with_decorator<D: TextDecorator>(decorator: D) -> Config<D> {\n        Config {\n            decorator,\n            style: Default::default(),\n            #[cfg(feature = \"css\")]\n            use_doc_css: false,\n            max_wrap_width: None,\n            pad_block_width: false,\n            allow_width_overflow: false,\n            min_wrap_width: MIN_WIDTH,\n            raw: false,\n            draw_borders: true,\n            wrap_links: true,\n            include_link_footnotes: false,\n            use_unicode_strikeout: true,\n            image_mode: ImageRenderMode::IgnoreEmpty,\n            #[cfg(feature = \"xml\")]\n            xml_mode: XmlMode::Auto,\n            #[cfg(feature = \"css_ext\")]\n            syntax_highlighters: Default::default(),\n        }\n    }\n}\n\n/// The structure of an HTML document that can be rendered using a [`TextDecorator`][].\n///\n/// [`TextDecorator`]: render/text_renderer/trait.TextDecorator.html\n\n#[derive(Clone, Debug)]\npub struct RenderTree(RenderNode);\n\nimpl std::fmt::Display for RenderTree {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        writeln!(f, \"Render tree:\")?;\n        self.0.write_self(f, 1)\n    }\n}\n\nimpl RenderTree {\n    /// Render this document using the given `decorator` and wrap it to `width` columns.\n    fn render_with_context<D: TextDecorator>(\n        self,\n        context: &mut HtmlContext,\n        width: usize,\n        decorator: D,\n    ) -> Result<RenderedText<D>> {\n        if width == 0 {\n            return Err(Error::TooNarrow);\n        }\n        let render_options = RenderOptions {\n            wrap_width: context.max_wrap_width,\n            pad_block_width: context.pad_block_width,\n            allow_width_overflow: context.allow_width_overflow,\n            raw: context.raw,\n            draw_borders: context.draw_borders,\n            wrap_links: context.wrap_links,\n            include_link_footnotes: context.include_link_footnotes,\n            use_unicode_strikeout: context.use_unicode_strikeout,\n            img_mode: context.image_mode,\n        };\n        let test_decorator = decorator.make_subblock_decorator();\n        let builder = SubRenderer::new(width, render_options, decorator);\n        let builder =\n            render_tree_to_string(context, builder, &test_decorator, self.0, &mut io::sink())?;\n        Ok(RenderedText(builder))\n    }\n}\n\n/// A rendered HTML document.\nstruct RenderedText<D: TextDecorator>(SubRenderer<D>);\n\nimpl<D: TextDecorator> RenderedText<D> {\n    /// Convert the rendered HTML document to a string.\n    fn into_string(self) -> render::Result<String> {\n        self.0.into_string()\n    }\n\n    /// Convert the rendered HTML document to a vector of lines with the annotations created by the\n    /// decorator.\n    fn into_lines(self) -> Result<Vec<TaggedLine<Vec<D::Annotation>>>> {\n        Ok(self\n            .0\n            .into_lines()?\n            .into_iter()\n            .map(RenderLine::into_tagged_line)\n            .collect())\n    }\n}\n\n/// Reads and parses HTML from `input` and prepares a render tree.\npub fn parse(input: impl io::Read) -> Result<RenderTree> {\n    let cfg = config::with_decorator(TrivialDecorator::new());\n    cfg.do_parse(&mut cfg.make_context(), input)\n}\n\n/// Reads HTML from `input`, decorates it using `decorator`, and\n/// returns a `String` with text wrapped to `width` columns.\npub fn from_read_with_decorator<R, D>(input: R, width: usize, decorator: D) -> Result<String>\nwhere\n    R: io::Read,\n    D: TextDecorator,\n{\n    config::with_decorator(decorator).string_from_read(input, width)\n}\n\n/// Reads HTML from `input`, and returns a `String` with text wrapped to\n/// `width` columns.\npub fn from_read<R>(input: R, width: usize) -> Result<String>\nwhere\n    R: io::Read,\n{\n    config::plain().string_from_read(input, width)\n}\n\n/// Reads HTML from `input`, and returns text wrapped to `width` columns.\n///\n/// The text is returned as a `Vec<TaggedLine<_>>`; the annotations are vectors\n/// of `RichAnnotation`.  The \"outer\" annotation comes first in the `Vec`.\npub fn from_read_rich<R>(input: R, width: usize) -> Result<Vec<TaggedLine<Vec<RichAnnotation>>>>\nwhere\n    R: io::Read,\n{\n    config::rich().lines_from_read(input, width)\n}\n\nmod ansi_colours;\n\npub use ansi_colours::from_read_coloured;\n\n#[cfg(test)]\nmod tests;\n\nfn calc_ol_prefix_size<D: TextDecorator>(start: i64, num_items: usize, decorator: &D) -> usize {\n    // The prefix width could be at either end if the start is negative.\n    let min_number = start;\n    // Assumption: num_items can't overflow isize.\n    let max_number = start + (num_items as i64) - 1;\n\n    // This assumes that the decorator gives the same width as default.\n    let prefix_width_min = decorator.ordered_item_prefix(min_number).len();\n    let prefix_width_max = decorator.ordered_item_prefix(max_number).len();\n    max(prefix_width_min, prefix_width_max)\n}\n"
  },
  {
    "path": "src/macros.rs",
    "content": "#[cfg(feature = \"html_trace_bt\")]\nextern crate backtrace;\n\n/* This is to work around a false positive for the clippy warning\n * `match_on_same_arms`.\n * See https://github.com/Manishearth/rust-clippy/issues/1390\n */\n#[cfg(not(feature = \"html_trace\"))]\n#[inline(always)]\npub fn nop() {}\n\n#[cfg(feature = \"html_trace\")]\n#[macro_export]\n#[doc(hidden)]\nmacro_rules! html_trace {\n    ($fmt:expr) => {\n         #[cfg(feature = \"html_trace_bt\")]\n         {\n             let bt = ::backtrace::Backtrace::new();\n             log::info!( concat!($fmt, \" at {:?}\"), bt );\n         }\n         #[cfg(not(feature = \"html_trace_bt\"))]\n         {\n             log::info!($fmt);\n         }\n    };\n    ($fmt:expr, $( $args:expr ),*) => {\n         #[cfg(feature = \"html_trace_bt\")]\n         {\n             let bt = ::backtrace::Backtrace::new();\n             log::info!( concat!($fmt, \" at {:?}\"), $( $args ),* , bt );\n         }\n         #[cfg(not(feature = \"html_trace_bt\"))]\n         {\n             log::info!($fmt, $( $args ),*);\n         }\n    };\n}\n#[cfg(not(feature = \"html_trace\"))]\n#[macro_export]\n#[doc(hidden)]\nmacro_rules! html_trace {\n    ($fmt:expr_2021) => {\n        $crate::macros::nop();\n    };\n    ($fmt:expr_2021, $( $args:expr_2021 ),*) => {\n        $crate::macros::nop();\n    };\n}\n\n#[cfg(feature = \"html_trace\")]\n#[macro_export]\n#[doc(hidden)]\nmacro_rules! html_trace_quiet {\n    ($fmt:expr) => {\n         log::trace!( $fmt );\n    };\n    ($fmt:expr, $( $args:expr ),*) => {\n         log::trace!( $fmt, $( $args ),* );\n    };\n}\n\n#[cfg(not(feature = \"html_trace\"))]\n#[macro_export]\n#[doc(hidden)]\nmacro_rules! html_trace_quiet {\n    ($fmt:expr_2021) => {\n        $crate::macros::nop();\n    };\n    ($fmt:expr_2021, $( $args:expr_2021 ),*) => {\n        $crate::macros::nop();\n    };\n}\n"
  },
  {
    "path": "src/markup5ever_rcdom.rs",
    "content": "// Copyright 2014-2017 The html5ever Project Developers. See the\n// COPYRIGHT file at the top-level directory of this distribution.\n//\n// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or\n// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license\n// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your\n// option. This file may not be copied, modified, or distributed\n// except according to those terms.\n\n//! A simple reference-counted DOM.\n//!\n//! This is sufficient as a static parse tree, but don't build a\n//! web browser using it. :)\n//!\n//! A DOM is a [tree structure] with ordered children that can be represented in an XML-like\n//! format. For example, the following graph\n//!\n//! ```text\n//! div\n//!  +- \"text node\"\n//!  +- span\n//! ```\n//! in HTML would be serialized as\n//!\n//! ```html\n//! <div>text node<span></span></div>\n//! ```\n//!\n//! See the [document object model article on wikipedia][dom wiki] for more information.\n//!\n//! This implementation stores the information associated with each node once, and then hands out\n//! refs to children. The nodes themselves are reference-counted to avoid copying - you can create\n//! a new ref and then a node will outlive the document. Nodes own their children, but only have\n//! weak references to their parents.\n//!\n//! [tree structure]: https://en.wikipedia.org/wiki/Tree_(data_structure)\n//! [dom wiki]: https://en.wikipedia.org/wiki/Document_Object_Model\n\nextern crate tendril;\n\nuse std::borrow::Cow;\nuse std::cell::{Cell, RefCell};\nuse std::collections::{HashSet, VecDeque};\nuse std::fmt;\nuse std::io;\nuse std::mem;\nuse std::rc::{Rc, Weak};\n\nuse html5ever::interface::ElemName;\nuse tendril::StrTendril;\n\nuse html5ever::Attribute;\nuse html5ever::ExpandedName;\nuse html5ever::QualName;\nuse html5ever::interface::tree_builder;\nuse html5ever::interface::tree_builder::{ElementFlags, NodeOrText, QuirksMode, TreeSink};\nuse html5ever::serialize::TraversalScope;\nuse html5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode};\nuse html5ever::serialize::{Serialize, Serializer};\n\n/// The different kinds of nodes in the DOM.\n#[derive(Debug, Clone)]\npub enum NodeData {\n    /// The `Document` itself - the root node of a HTML document.\n    Document,\n\n    /// A `DOCTYPE` with name, public id, and system id. See\n    /// [document type declaration on wikipedia][dtd wiki].\n    ///\n    /// [dtd wiki]: https://en.wikipedia.org/wiki/Document_type_declaration\n    Doctype {\n        name: StrTendril,\n        public_id: StrTendril,\n        system_id: StrTendril,\n    },\n\n    /// A text node.\n    Text { contents: RefCell<StrTendril> },\n\n    /// A comment.\n    Comment {\n        /// The comment text.\n        contents: StrTendril,\n    },\n\n    /// An element with attributes.\n    Element {\n        /// The qualified element name\n        name: QualName,\n        /// The element's attributes.\n        attrs: RefCell<Vec<Attribute>>,\n\n        /// For HTML \\<template\\> elements, the [template contents].\n        ///\n        /// [template contents]: https://html.spec.whatwg.org/multipage/#template-contents\n        template_contents: RefCell<Option<Handle>>,\n\n        /// Whether the node is a [HTML integration point].\n        ///\n        /// [HTML integration point]: https://html.spec.whatwg.org/multipage/#html-integration-point\n        mathml_annotation_xml_integration_point: bool,\n    },\n\n    /// A Processing instruction.\n    ProcessingInstruction {\n        target: StrTendril,\n        contents: StrTendril,\n    },\n}\n\n/// A DOM node.\npub struct Node {\n    /// Parent node.\n    pub parent: Cell<Option<WeakHandle>>,\n    /// Child nodes of this node.\n    pub children: RefCell<Vec<Handle>>,\n    /// Represents this node's data.\n    pub data: NodeData,\n}\n\nimpl Node {\n    /// Create a new node from its contents\n    pub fn new(data: NodeData) -> Rc<Self> {\n        Rc::new(Node {\n            data,\n            parent: Cell::new(None),\n            children: RefCell::new(Vec::new()),\n        })\n    }\n\n    pub fn get_parent(&self) -> Option<Rc<Self>> {\n        match self.parent.take() {\n            Some(parent) => {\n                let parent_handle = parent.upgrade();\n                self.parent.set(Some(parent));\n                parent_handle\n            }\n            _ => None,\n        }\n    }\n\n    /// Return the nth child element of this node, or None.\n    pub fn nth_child(&self, idx: usize) -> Option<Rc<Self>> {\n        let mut element_idx = 0;\n        for child in self.children.borrow().iter() {\n            if let NodeData::Element { .. } = child.data {\n                element_idx += 1;\n                if element_idx == idx {\n                    return Some(child.clone());\n                }\n            }\n        }\n        None\n    }\n\n    /// Return the element type (if an element)\n    pub fn element_name(&self) -> Option<String> {\n        if let NodeData::Element { ref name, .. } = self.data {\n            Some(format!(\"{}\", name.local_name()))\n        } else {\n            None\n        }\n    }\n\n    /// Serialise the node to a writable.\n    pub fn serialize(self: &Rc<Self>, writer: impl io::Write) -> io::Result<()> {\n        html5ever::serialize(\n            writer,\n            &SerializableHandle(self.clone()),\n            html5ever::serialize::SerializeOpts {\n                scripting_enabled: true,\n                traversal_scope: html5ever::serialize::TraversalScope::IncludeNode,\n                create_missing_parent: false,\n            },\n        )\n    }\n}\n\nimpl Drop for Node {\n    fn drop(&mut self) {\n        let mut nodes = mem::take(&mut *self.children.borrow_mut());\n        while let Some(node) = nodes.pop() {\n            let children = mem::take(&mut *node.children.borrow_mut());\n            nodes.extend(children.into_iter());\n            if let NodeData::Element {\n                ref template_contents,\n                ..\n            } = node.data\n            {\n                if let Some(template_contents) = template_contents.borrow_mut().take() {\n                    nodes.push(template_contents);\n                }\n            }\n        }\n    }\n}\n\nimpl fmt::Debug for Node {\n    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n        fmt.debug_struct(\"Node\")\n            .field(\"data\", &self.data)\n            .field(\"children\", &self.children)\n            .finish()\n    }\n}\n\n/// Reference to a DOM node.\npub type Handle = Rc<Node>;\n\n/// Weak reference to a DOM node, used for parent pointers.\npub type WeakHandle = Weak<Node>;\n\n/// Append a parentless node to another nodes' children\nfn append(new_parent: &Handle, child: Handle) {\n    let previous_parent = child.parent.replace(Some(Rc::downgrade(new_parent)));\n    // Invariant: child cannot have existing parent\n    assert!(previous_parent.is_none());\n    new_parent.children.borrow_mut().push(child);\n}\n\n/// If the node has a parent, get it and this node's position in its children\nfn get_parent_and_index(target: &Handle) -> Option<(Handle, usize)> {\n    let weak = target.parent.take()?;\n    let parent = weak.upgrade().expect(\"dangling weak pointer\");\n    target.parent.set(Some(weak));\n    let i = match parent\n        .children\n        .borrow()\n        .iter()\n        .enumerate()\n        .find(|&(_, child)| Rc::ptr_eq(child, target))\n    {\n        Some((i, _)) => i,\n        None => panic!(\"have parent but couldn't find in parent's children!\"),\n    };\n    Some((parent, i))\n}\n\nfn append_to_existing_text(prev: &Handle, text: &str) -> bool {\n    match prev.data {\n        NodeData::Text { ref contents } => {\n            contents.borrow_mut().push_slice(text);\n            true\n        }\n        _ => false,\n    }\n}\n\nfn remove_from_parent(target: &Handle) {\n    if let Some((parent, i)) = get_parent_and_index(target) {\n        parent.children.borrow_mut().remove(i);\n        target.parent.set(None);\n    }\n}\n\n/// The DOM itself; the result of parsing.\npub struct RcDom {\n    /// The `Document` itself.\n    pub document: Handle,\n\n    /// Errors that occurred during parsing.\n    pub errors: RefCell<Vec<Cow<'static, str>>>,\n\n    /// The document's quirks mode.\n    pub quirks_mode: Cell<QuirksMode>,\n}\n\nimpl RcDom {\n    fn add_node_to_string(s: &mut String, node: &Handle, indent: usize) {\n        use std::fmt::Write as _;\n        match &node.data {\n            NodeData::Document => {\n                for child in &*node.children.borrow() {\n                    Self::add_node_to_string(s, child, indent);\n                }\n            }\n            NodeData::Doctype { .. } => {\n                writeln!(s, \"{0:indent$}<doctype>\", \"\", indent = indent).unwrap();\n            }\n            NodeData::Text { contents } => {\n                let borrowed = contents.borrow();\n                let text = borrowed.to_string();\n                if !text.trim().is_empty() {\n                    writeln!(s, \"{0:indent$}Text:{1}\", \"\", text, indent = indent).unwrap();\n                }\n            }\n            NodeData::Comment { .. } => (),\n            NodeData::Element { name, .. } => {\n                writeln!(s, \"{0:indent$}<{1}>\", \"\", name.local, indent = indent).unwrap();\n                for child in &*node.children.borrow() {\n                    Self::add_node_to_string(s, child, indent + 1);\n                }\n                writeln!(s, \"{0:indent$}</{1}>\", \"\", name.local, indent = indent).unwrap();\n            }\n            NodeData::ProcessingInstruction { .. } => {}\n        }\n    }\n\n    /// A low-quality debug DOM rendering.\n    pub fn as_dom_string(&self) -> String {\n        let mut s = String::new();\n        Self::add_node_to_string(&mut s, &self.document, 0);\n        s\n    }\n\n    /// A low-quality debug DOM rendering of an individual node\n    pub fn node_as_dom_string(node: &Handle) -> String {\n        let mut s = String::new();\n        Self::add_node_to_string(&mut s, node, 0);\n        s\n    }\n\n    /// Serialise the DOM to a writable.\n    pub fn serialize(&self, mut writer: impl io::Write) -> io::Result<()> {\n        let doc = &self.document;\n        let children = doc.children.borrow();\n        for child in &*children {\n            html5ever::serialize(\n                &mut writer,\n                &SerializableHandle(child.clone()),\n                html5ever::serialize::SerializeOpts {\n                    scripting_enabled: true,\n                    traversal_scope: html5ever::serialize::TraversalScope::IncludeNode,\n                    create_missing_parent: false,\n                },\n            )?;\n        }\n        Ok(())\n    }\n\n    /// Find the node at a child path starting from the root element.  At each level, 1 is the\n    /// first child element, and only elements are counted.\n    pub fn get_node_by_path(&self, path: &[usize]) -> Option<Handle> {\n        let mut node = self.document.clone();\n        for idx in path {\n            node = node.nth_child(*idx)?;\n        }\n        Some(node)\n    }\n}\n\nimpl TreeSink for RcDom {\n    type Output = Self;\n\n    type ElemName<'a> = ExpandedName<'a>;\n    fn finish(self) -> Self {\n        self\n    }\n\n    type Handle = Handle;\n\n    fn parse_error(&self, msg: Cow<'static, str>) {\n        self.errors.borrow_mut().push(msg);\n    }\n\n    fn get_document(&self) -> Handle {\n        self.document.clone()\n    }\n\n    fn get_template_contents(&self, target: &Handle) -> Handle {\n        if let NodeData::Element {\n            ref template_contents,\n            ..\n        } = target.data\n        {\n            template_contents\n                .borrow()\n                .as_ref()\n                .expect(\"not a template element!\")\n                .clone()\n        } else {\n            panic!(\"not a template element!\")\n        }\n    }\n\n    fn set_quirks_mode(&self, mode: QuirksMode) {\n        self.quirks_mode.set(mode);\n    }\n\n    fn same_node(&self, x: &Handle, y: &Handle) -> bool {\n        Rc::ptr_eq(x, y)\n    }\n\n    fn elem_name<'a>(&self, target: &'a Handle) -> ExpandedName<'a> {\n        match target.data {\n            NodeData::Element { ref name, .. } => name.expanded(),\n            _ => panic!(\"not an element!\"),\n        }\n    }\n\n    fn create_element(&self, name: QualName, attrs: Vec<Attribute>, flags: ElementFlags) -> Handle {\n        Node::new(NodeData::Element {\n            name,\n            attrs: RefCell::new(attrs),\n            template_contents: RefCell::new(if flags.template {\n                Some(Node::new(NodeData::Document))\n            } else {\n                None\n            }),\n            mathml_annotation_xml_integration_point: flags.mathml_annotation_xml_integration_point,\n        })\n    }\n\n    fn create_comment(&self, text: StrTendril) -> Handle {\n        Node::new(NodeData::Comment { contents: text })\n    }\n\n    fn create_pi(&self, target: StrTendril, data: StrTendril) -> Handle {\n        Node::new(NodeData::ProcessingInstruction {\n            target,\n            contents: data,\n        })\n    }\n\n    fn append(&self, parent: &Handle, child: NodeOrText<Handle>) {\n        // Append to an existing Text node if we have one.\n        if let NodeOrText::AppendText(text) = &child {\n            if let Some(h) = parent.children.borrow().last() {\n                if append_to_existing_text(h, text) {\n                    return;\n                }\n            }\n        }\n\n        append(\n            parent,\n            match child {\n                NodeOrText::AppendText(text) => Node::new(NodeData::Text {\n                    contents: RefCell::new(text),\n                }),\n                NodeOrText::AppendNode(node) => node,\n            },\n        );\n    }\n\n    fn append_before_sibling(&self, sibling: &Handle, child: NodeOrText<Handle>) {\n        let (parent, i) = get_parent_and_index(sibling)\n            .expect(\"append_before_sibling called on node without parent\");\n\n        let child = match (child, i) {\n            // No previous node.\n            (NodeOrText::AppendText(text), 0) => Node::new(NodeData::Text {\n                contents: RefCell::new(text),\n            }),\n\n            // Look for a text node before the insertion point.\n            (NodeOrText::AppendText(text), i) => {\n                let children = parent.children.borrow();\n                let prev = &children[i - 1];\n                if append_to_existing_text(prev, &text) {\n                    return;\n                }\n                Node::new(NodeData::Text {\n                    contents: RefCell::new(text),\n                })\n            }\n\n            // The tree builder promises we won't have a text node after\n            // the insertion point.\n\n            // Any other kind of node.\n            (NodeOrText::AppendNode(node), _) => node,\n        };\n\n        remove_from_parent(&child);\n\n        child.parent.set(Some(Rc::downgrade(&parent)));\n        parent.children.borrow_mut().insert(i, child);\n    }\n\n    fn append_based_on_parent_node(\n        &self,\n        element: &Self::Handle,\n        prev_element: &Self::Handle,\n        child: NodeOrText<Self::Handle>,\n    ) {\n        let parent = element.parent.take();\n        let has_parent = parent.is_some();\n        element.parent.set(parent);\n\n        if has_parent {\n            self.append_before_sibling(element, child);\n        } else {\n            self.append(prev_element, child);\n        }\n    }\n\n    fn append_doctype_to_document(\n        &self,\n        name: StrTendril,\n        public_id: StrTendril,\n        system_id: StrTendril,\n    ) {\n        append(\n            &self.document,\n            Node::new(NodeData::Doctype {\n                name,\n                public_id,\n                system_id,\n            }),\n        );\n    }\n\n    fn add_attrs_if_missing(&self, target: &Handle, attrs: Vec<Attribute>) {\n        let mut existing = if let NodeData::Element { ref attrs, .. } = target.data {\n            attrs.borrow_mut()\n        } else {\n            panic!(\"not an element\")\n        };\n\n        let existing_names = existing\n            .iter()\n            .map(|e| e.name.clone())\n            .collect::<HashSet<_>>();\n        existing.extend(\n            attrs\n                .into_iter()\n                .filter(|attr| !existing_names.contains(&attr.name)),\n        );\n    }\n\n    fn remove_from_parent(&self, target: &Handle) {\n        remove_from_parent(target);\n    }\n\n    fn reparent_children(&self, node: &Handle, new_parent: &Handle) {\n        let mut children = node.children.borrow_mut();\n        let mut new_children = new_parent.children.borrow_mut();\n        for child in children.iter() {\n            let previous_parent = child.parent.replace(Some(Rc::downgrade(new_parent)));\n            assert!(Rc::ptr_eq(\n                node,\n                &previous_parent.unwrap().upgrade().expect(\"dangling weak\")\n            ))\n        }\n        new_children.extend(mem::take(&mut *children));\n    }\n\n    fn is_mathml_annotation_xml_integration_point(&self, target: &Handle) -> bool {\n        if let NodeData::Element {\n            mathml_annotation_xml_integration_point,\n            ..\n        } = target.data\n        {\n            mathml_annotation_xml_integration_point\n        } else {\n            panic!(\"not an element!\")\n        }\n    }\n}\n\nimpl Default for RcDom {\n    fn default() -> RcDom {\n        RcDom {\n            document: Node::new(NodeData::Document),\n            errors: vec![].into(),\n            quirks_mode: tree_builder::NoQuirks.into(),\n        }\n    }\n}\n\nenum SerializeOp {\n    Open(Handle),\n    Close(QualName),\n}\n\npub struct SerializableHandle(Handle);\n\nimpl From<Handle> for SerializableHandle {\n    fn from(h: Handle) -> SerializableHandle {\n        SerializableHandle(h)\n    }\n}\n\nimpl Serialize for SerializableHandle {\n    fn serialize<S>(&self, serializer: &mut S, traversal_scope: TraversalScope) -> io::Result<()>\n    where\n        S: Serializer,\n    {\n        let mut ops = VecDeque::new();\n        match traversal_scope {\n            IncludeNode => ops.push_back(SerializeOp::Open(self.0.clone())),\n            ChildrenOnly(_) => ops.extend(\n                self.0\n                    .children\n                    .borrow()\n                    .iter()\n                    .map(|h| SerializeOp::Open(h.clone())),\n            ),\n        }\n\n        while let Some(op) = ops.pop_front() {\n            match op {\n                SerializeOp::Open(handle) => match handle.data {\n                    NodeData::Element {\n                        ref name,\n                        ref attrs,\n                        ..\n                    } => {\n                        serializer.start_elem(\n                            name.clone(),\n                            attrs.borrow().iter().map(|at| (&at.name, &at.value[..])),\n                        )?;\n\n                        ops.reserve(1 + handle.children.borrow().len());\n                        ops.push_front(SerializeOp::Close(name.clone()));\n\n                        for child in handle.children.borrow().iter().rev() {\n                            ops.push_front(SerializeOp::Open(child.clone()));\n                        }\n                    }\n\n                    NodeData::Doctype { ref name, .. } => serializer.write_doctype(name)?,\n\n                    NodeData::Text { ref contents } => serializer.write_text(&contents.borrow())?,\n\n                    NodeData::Comment { ref contents } => serializer.write_comment(contents)?,\n\n                    NodeData::ProcessingInstruction {\n                        ref target,\n                        ref contents,\n                    } => serializer.write_processing_instruction(target, contents)?,\n\n                    NodeData::Document => panic!(\"Can't serialize Document node itself\"),\n                },\n\n                SerializeOp::Close(name) => {\n                    serializer.end_elem(name)?;\n                }\n            }\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/render/mod.rs",
    "content": "//! Module containing the `Renderer` interface for constructing a\n//! particular text output.\n\nuse crate::Colour;\nuse crate::WhiteSpace;\n\npub(crate) mod text_renderer;\n\npub use text_renderer::{\n    PlainDecorator, RichAnnotation, RichDecorator, TaggedLine, TaggedLineElement, TextDecorator,\n    TrivialDecorator,\n};\n\npub(crate) type Result<T> = std::result::Result<T, TooNarrow>;\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub(crate) struct TooNarrow;\n\nimpl From<TooNarrow> for crate::Error {\n    fn from(_: TooNarrow) -> crate::Error {\n        crate::Error::TooNarrow\n    }\n}\n\n/// A type which is a backend for HTML to text rendering.\npub(crate) trait Renderer {\n    /// Add an empty line to the output (ie between blocks).\n    fn add_empty_line(&mut self) -> Result<()>;\n\n    /// Create a sub-renderer for nested blocks.\n    fn new_sub_renderer(&self, width: usize) -> Result<Self>\n    where\n        Self: Sized;\n\n    /// Start a new block.\n    fn start_block(&mut self) -> Result<()>;\n\n    /// Start a new table.\n    fn start_table(&mut self) -> Result<()>;\n\n    /// Mark the end of a block.\n    fn end_block(&mut self);\n\n    /// Start a new line, if necessary (but don't add a new line).\n    fn new_line(&mut self) -> Result<()>;\n\n    /// Start a new line.\n    fn new_line_hard(&mut self) -> Result<()>;\n\n    /// Add a horizontal table border.\n    fn add_horizontal_border(&mut self) -> Result<()>;\n\n    /// Add a horizontal border which is not the full width\n    fn add_horizontal_border_width(\n        &mut self,\n        #[allow(unused_variables)] width: usize,\n    ) -> Result<()> {\n        self.add_horizontal_border()\n    }\n\n    /// Begin a preformatted block.  This indicates we are inside a <pre> element.\n    /// The whitespace/wrapping behaviour is treated separately with `push_ws`.\n    fn push_preformat(&mut self);\n\n    /// End a preformatted block.\n    fn pop_preformat(&mut self);\n\n    /// Update the white-space CSS setting.\n    fn push_ws(&mut self, ws: WhiteSpace);\n\n    /// End the current white-space setting.\n    fn pop_ws(&mut self);\n\n    /// Add some inline text (which should be wrapped at the\n    /// appropriate width) to the current block.\n    fn add_inline_text(&mut self, text: &str) -> Result<()>;\n\n    /// Return the current width in character cells\n    fn width(&self) -> usize;\n\n    /// Add a new block from a sub renderer, and prefix every line by the\n    /// corresponding text from each iteration of prefixes.\n    fn append_subrender<'a, I>(&mut self, other: Self, prefixes: I) -> Result<()>\n    where\n        I: Iterator<Item = &'a str>;\n\n    /// Append a set of sub renderers joined left-to-right with a vertical line,\n    /// and add a horizontal line below.\n    /// If collapse is true, then merge top/bottom borders of the subrenderer\n    /// with the surrounding one.\n    fn append_columns_with_borders<I>(&mut self, cols: I, collapse: bool) -> Result<()>\n    where\n        I: IntoIterator<Item = (Self, usize)>,\n        Self: Sized;\n\n    /// Append a set of sub renderers joined vertically with lines, for tables\n    /// which would otherwise be too wide for the screen.\n    fn append_vert_row<I>(&mut self, cols: I) -> Result<()>\n    where\n        I: IntoIterator<Item = Self>,\n        Self: Sized;\n\n    /// Returns true if this renderer has no content.\n    fn empty(&self) -> bool;\n\n    /// Start a hyperlink\n    /// TODO: return sub-builder or similar to make misuse\n    /// of start/link harder?\n    fn start_link(&mut self, target: &str) -> Result<()>;\n\n    /// Finish a hyperlink started earlier.\n    fn end_link(&mut self) -> Result<()>;\n\n    /// Start an emphasised region\n    fn start_emphasis(&mut self) -> Result<()>;\n\n    /// Finish emphasised text started earlier.\n    fn end_emphasis(&mut self) -> Result<()>;\n\n    /// Start a strong region\n    fn start_strong(&mut self) -> Result<()>;\n\n    /// Finish strong text started earlier.\n    fn end_strong(&mut self) -> Result<()>;\n\n    /// Start a strikeout region\n    fn start_strikeout(&mut self) -> Result<()>;\n\n    /// Finish strikeout text started earlier.\n    fn end_strikeout(&mut self) -> Result<()>;\n\n    /// Start a code region\n    fn start_code(&mut self) -> Result<()>;\n\n    /// End a code region\n    fn end_code(&mut self) -> Result<()>;\n\n    /// Add an image\n    fn add_image(&mut self, src: &str, title: &str) -> Result<()>;\n\n    /// Get prefix string of header in specific level.\n    fn header_prefix(&mut self, level: usize) -> String;\n\n    /// Get prefix string of quoted block.\n    fn quote_prefix(&mut self) -> String;\n\n    /// Get prefix string of unordered list item.\n    fn unordered_item_prefix(&mut self) -> String;\n\n    /// Get prefix string of ith ordered list item.\n    fn ordered_item_prefix(&mut self, i: i64) -> String;\n\n    /// Record the start of a named HTML fragment\n    fn record_frag_start(&mut self, fragname: &str);\n\n    #[allow(unused)]\n    /// Push a new foreground colour\n    fn push_colour(&mut self, colour: Colour);\n\n    #[allow(unused)]\n    /// Pop the last foreground colour\n    fn pop_colour(&mut self);\n\n    #[allow(unused)]\n    /// Push a new background colour\n    fn push_bgcolour(&mut self, colour: Colour);\n\n    #[allow(unused)]\n    /// Pop the last background colour\n    fn pop_bgcolour(&mut self);\n\n    /// Start a section of superscript text.\n    fn start_superscript(&mut self) -> Result<()>;\n\n    /// End a section of superscript text.\n    fn end_superscript(&mut self) -> Result<()>;\n}\n"
  },
  {
    "path": "src/render/text_renderer.rs",
    "content": "//! Implementations of the `Renderer` trait.\n//!\n//! This module implements helpers and concrete types for rendering from HTML\n//! into different text formats.\n\nuse crate::Colour;\nuse crate::WhiteSpace;\nuse crate::WhitespaceExt as _;\nuse crate::config::ImageRenderMode;\n\nuse super::Renderer;\nuse super::Result;\nuse super::TooNarrow;\nuse std::collections::VecDeque;\nuse std::mem;\nuse std::ops::Deref;\nuse std::ops::DerefMut;\nuse std::vec;\nuse std::{collections::LinkedList, fmt::Debug};\nuse unicode_width::{UnicodeWidthChar, UnicodeWidthStr};\n\n/// Context to use during tree parsing.\n/// This mainly gives access to a Renderer, but needs to be able to push\n/// new ones on for nested structures.\n#[derive(Debug)]\npub(crate) struct TextRenderer<D: TextDecorator> {\n    subrender: Vec<SubRenderer<D>>,\n    links: Vec<String>,\n}\n\nimpl<D: TextDecorator> Deref for TextRenderer<D> {\n    type Target = SubRenderer<D>;\n    fn deref(&self) -> &Self::Target {\n        self.subrender.last().expect(\"Underflow in renderer stack\")\n    }\n}\nimpl<D: TextDecorator> DerefMut for TextRenderer<D> {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        self.subrender\n            .last_mut()\n            .expect(\"Underflow in renderer stack\")\n    }\n}\n\nimpl<D: TextDecorator> TextRenderer<D> {\n    /// Construct new stack of renderer\n    pub fn new(subrenderer: SubRenderer<D>) -> TextRenderer<D> {\n        TextRenderer {\n            subrender: vec![subrenderer],\n            links: Vec::new(),\n        }\n    }\n\n    // hack overloads start_link method otherwise coming from the Renderer trait\n    // impl on SubRenderer\n    /// Add link to global link collection\n    pub fn start_link(&mut self, target: &str) -> Result<()> {\n        self.links.push(target.to_string());\n        self.subrender.last_mut().unwrap().start_link(target)?;\n        Ok(())\n    }\n\n    pub fn end_link(&mut self) -> Result<()> {\n        self.subrender.last_mut().unwrap().end_link()?;\n\n        if self.options.include_link_footnotes {\n            let footnote_num = self.links.len();\n            self.add_inline_text(&format!(\"[{}]\", footnote_num))?;\n        }\n        Ok(())\n    }\n\n    /// Push a new builder onto the stack\n    pub fn push(&mut self, builder: SubRenderer<D>) {\n        self.subrender.push(builder);\n    }\n\n    /// Pop off the top builder and return it.\n    /// Panics if empty\n    pub fn pop(&mut self) -> SubRenderer<D> {\n        self.subrender\n            .pop()\n            .expect(\"Attempt to pop a subrender from empty stack\")\n    }\n\n    /// Pop off the only builder and return it.\n    /// panics if there aren't exactly 1 available.\n    pub fn into_inner(mut self) -> (SubRenderer<D>, Vec<String>) {\n        assert_eq!(self.subrender.len(), 1);\n        (\n            self.subrender\n                .pop()\n                .expect(\"Attempt to pop a subrenderer from an empty stack\"),\n            self.links,\n        )\n    }\n}\n\n/// A wrapper around a String with extra metadata.\n#[derive(Debug, Clone, PartialEq)]\npub struct TaggedString<T> {\n    /// The wrapped text.\n    pub s: String,\n\n    /// The metadata.\n    pub tag: T,\n}\n\nimpl<T> TaggedString<T> {\n    /// Returns the tagged string’s display width in columns.\n    ///\n    /// See [`unicode_width::UnicodeWidthStr::width`][] for more information.\n    ///\n    /// [`unicode_width::UnicodeWidthStr::width`]: https://docs.rs/unicode-width/latest/unicode_width/trait.UnicodeWidthStr.html\n    pub fn width(&self) -> usize {\n        self.s.width()\n    }\n}\n\nimpl<T> TaggedString<T> {\n    /// Converts to a TaggedString with a different tag type, given a\n    /// function that transforms the tag.\n    pub fn map_tag<U>(self, f: &impl Fn(T) -> U) -> TaggedString<U> {\n        TaggedString {\n            s: self.s,\n            tag: f(self.tag),\n        }\n    }\n}\n\n/// An element of a line of tagged text: either a TaggedString or a\n/// marker appearing in between document characters.\n#[derive(Clone, Debug, PartialEq)]\npub enum TaggedLineElement<T> {\n    /// A string with tag information attached.\n    Str(TaggedString<T>),\n\n    /// A zero-width marker indicating the start of a named HTML fragment.\n    FragmentStart(String),\n}\n\nimpl<T> TaggedLineElement<T> {\n    /// Return true if this element is non-empty.\n    /// FragmentStart is considered empty.\n    fn has_content(&self) -> bool {\n        match self {\n            TaggedLineElement::Str(_) => true,\n            TaggedLineElement::FragmentStart(_) => false,\n        }\n    }\n\n    /// Converts to a TaggedLineElement with a different tag type, given a\n    /// function that transforms the tag.\n    pub fn map_tag<U>(self, f: &impl Fn(T) -> U) -> TaggedLineElement<U> {\n        match self {\n            TaggedLineElement::Str(ts) => TaggedLineElement::Str(ts.map_tag(f)),\n            TaggedLineElement::FragmentStart(s) => TaggedLineElement::FragmentStart(s.clone()),\n        }\n    }\n\n    /// Return the text width of this element.\n    pub fn width(&self) -> usize {\n        match self {\n            TaggedLineElement::Str(tagged_string) => tagged_string.width(),\n            TaggedLineElement::FragmentStart(_) => 0usize,\n        }\n    }\n\n    /// Return the text content of this element.\n    pub fn as_str(&self) -> &str {\n        match self {\n            TaggedLineElement::Str(tagged_string) => &tagged_string.s,\n            TaggedLineElement::FragmentStart(_) => \"\",\n        }\n    }\n}\n\n/// A line of tagged text (composed of a set of `TaggedString`s).\n#[derive(Debug, Clone, PartialEq)]\npub struct TaggedLine<T> {\n    v: Vec<TaggedLineElement<T>>,\n    len: usize,\n}\n\nimpl<T: Debug + Eq + PartialEq + Clone + Default> Default for TaggedLine<T> {\n    fn default() -> Self {\n        TaggedLine::new()\n    }\n}\n\nimpl<T: Debug + Eq + PartialEq + Clone + Default> TaggedLine<T> {\n    /// Create an empty `TaggedLine`.\n    pub fn new() -> TaggedLine<T> {\n        TaggedLine {\n            v: Vec::new(),\n            len: 0,\n        }\n    }\n\n    /// Create a new TaggedLine from a string and tag.\n    pub fn from_string(s: String, tag: &T) -> TaggedLine<T> {\n        let len = UnicodeWidthStr::width(s.as_str());\n        TaggedLine {\n            v: vec![TaggedLineElement::Str(TaggedString {\n                s,\n                tag: tag.clone(),\n            })],\n            len,\n        }\n    }\n\n    /// Join the line into a String, ignoring the tags and markers.\n    #[allow(clippy::inherent_to_string)]\n    fn to_string(&self) -> String {\n        let mut s = String::new();\n        for tle in &self.v {\n            if let TaggedLineElement::Str(ts) = tle {\n                s.push_str(&ts.s);\n            }\n        }\n        s\n    }\n\n    /// Return true if the line is non-empty\n    fn is_empty(&self) -> bool {\n        for elt in &self.v {\n            if elt.has_content() {\n                return false;\n            }\n        }\n        true\n    }\n\n    /// Add a new tagged string fragment to the line\n    fn push_str(&mut self, ts: TaggedString<T>) {\n        use self::TaggedLineElement::Str;\n\n        if !ts.s.is_empty() {\n            self.len += UnicodeWidthStr::width(ts.s.as_str());\n            if let Some(Str(ts_prev)) = self.v.last_mut() {\n                if ts_prev.tag == ts.tag {\n                    ts_prev.s.push_str(&ts.s);\n                    return;\n                }\n            }\n            self.v.push(Str(ts));\n        }\n    }\n\n    /// Add a new general TaggedLineElement to the line\n    fn push(&mut self, tle: TaggedLineElement<T>) {\n        use self::TaggedLineElement::Str;\n\n        if let Str(ts) = tle {\n            self.push_str(ts);\n        } else {\n            self.v.push(tle);\n        }\n    }\n\n    /// Push some whitespace\n    fn push_ws(&mut self, len: usize, tag: &T) {\n        use self::TaggedLineElement::Str;\n        self.push(Str(TaggedString {\n            s: \" \".repeat(len),\n            tag: tag.clone(),\n        }));\n    }\n\n    /// Add a new fragment to the start of the line\n    fn insert_front(&mut self, ts: TaggedString<T>) {\n        use self::TaggedLineElement::Str;\n\n        self.len += UnicodeWidthStr::width(ts.s.as_str());\n\n        if let Some(Str(ts1)) = self.v.get_mut(0) {\n            if ts1.tag == ts.tag {\n                // Combine into one TaggedString\n                ts1.s.insert_str(0, &ts.s);\n                return;\n            }\n        }\n        self.v.insert(0, Str(ts));\n    }\n\n    /// Add text with a particular tag to self\n    fn push_char(&mut self, c: char, tag: &T) {\n        use self::TaggedLineElement::Str;\n\n        self.len += UnicodeWidthChar::width(c).unwrap_or(0);\n        if let Some(Str(ts_prev)) = self.v.last_mut() {\n            if ts_prev.tag == *tag {\n                ts_prev.s.push(c);\n                return;\n            }\n        }\n        let mut s = String::new();\n        s.push(c);\n        self.v.push(Str(TaggedString {\n            s,\n            tag: tag.clone(),\n        }));\n    }\n\n    /// Drain tl and use to extend self.\n    fn consume(&mut self, tl: &mut TaggedLine<T>) {\n        for ts in tl.v.drain(..) {\n            self.push(ts);\n        }\n    }\n\n    /// Converts to a TaggedLine with a different tag type, given a\n    /// function that transforms the tag.\n    pub fn map_tag<U>(self, f: &impl Fn(T) -> U) -> TaggedLine<U> {\n        TaggedLine {\n            v: self.v.into_iter().map(|e| e.map_tag(f)).collect(),\n            len: self.len,\n        }\n    }\n\n    /// Remove the contained items\n    fn remove_items(&mut self) -> impl Iterator<Item = TaggedLineElement<T>> + use<T> {\n        self.len = 0;\n        std::mem::take(&mut self.v).into_iter()\n    }\n\n    /// Iterator over the chars in this line.\n    pub fn chars(&self) -> impl Iterator<Item = char> + '_ {\n        use self::TaggedLineElement::Str;\n\n        self.v.iter().flat_map(|tle| {\n            if let Str(ts) = tle {\n                ts.s.chars()\n            } else {\n                \"\".chars()\n            }\n        })\n    }\n\n    /// Iterator over TaggedLineElements\n    pub fn iter(&self) -> impl Iterator<Item = &TaggedLineElement<T>> + '_ {\n        self.v.iter()\n    }\n\n    /// Iterator over the tagged strings in this line, ignoring any fragments.\n    pub fn tagged_strings(&self) -> impl Iterator<Item = &TaggedString<T>> {\n        self.v.iter().filter_map(|tle| match tle {\n            TaggedLineElement::Str(ts) => Some(ts),\n            _ => None,\n        })\n    }\n\n    /// Converts the tagged line into an iterator over the tagged strings in this line, ignoring\n    /// any fragments.\n    fn into_tagged_strings(self) -> impl Iterator<Item = TaggedString<T>> {\n        self.v.into_iter().filter_map(|tle| match tle {\n            TaggedLineElement::Str(ts) => Some(ts),\n            _ => None,\n        })\n    }\n\n    /// Return the width of the line in cells\n    fn width(&self) -> usize {\n        let result = self.tagged_strings().map(TaggedString::width).sum();\n        debug_assert_eq!(self.len, result);\n        result\n    }\n\n    /// Pad this line to width with spaces (or if already at least this wide, do\n    /// nothing).\n    fn pad_to(&mut self, width: usize, tag: &T) {\n        let my_width = self.width();\n        if width > my_width {\n            self.push_ws(width - my_width, tag);\n        }\n    }\n\n    /// Remove leading whitespace.\n    fn remove_leading_whitespace(&mut self) {\n        let mut pieces_to_remove = 0;\n        let mut width_removed = 0;\n        for element in &mut self.v {\n            if let TaggedLineElement::Str(piece) = element {\n                let trimmed = piece.s.trim_start();\n                let tlen = trimmed.len();\n                let toffset = piece.s.len() - tlen;\n                if toffset == 0 {\n                    // No more leading whitespace\n                    break;\n                }\n                if tlen == 0 {\n                    // The piece would be empty.\n                    pieces_to_remove += 1;\n                    width_removed += piece.width();\n                } else {\n                    // Shrink it to fit.\n                    let orig_width = piece.width();\n                    piece.s.replace_range(0..toffset, \"\");\n                    let new_width = piece.width();\n                    width_removed += orig_width - new_width;\n                    break;\n                }\n            } else {\n                // Don't do anything to fragments\n                break;\n            }\n        }\n        if pieces_to_remove > 0 {\n            self.v = self.v.split_off(pieces_to_remove);\n        }\n        self.len -= width_removed;\n    }\n\n    /// Remove tralling spaces\n    fn remove_trailing_spaces(&mut self) {\n        while let Some(TaggedLineElement::Str(piece)) = self.v.last_mut() {\n            let trimmed = piece.s.trim_end_matches(' ');\n            let tlen = trimmed.len();\n            if tlen == 0 {\n                // Remove the whole last element.\n                self.len -= piece.width();\n                self.v.pop();\n            } else if tlen == piece.s.len() {\n                // We're done.\n                break;\n            } else {\n                self.len -= piece.width() - trimmed.width();\n                piece.s.replace_range(tlen.., \"\");\n                break;\n            }\n        }\n    }\n}\n\nimpl<T> IntoIterator for TaggedLine<T> {\n    type Item = TaggedLineElement<T>;\n    type IntoIter = <Vec<TaggedLineElement<T>> as IntoIterator>::IntoIter;\n\n    fn into_iter(self) -> Self::IntoIter {\n        self.v.into_iter()\n    }\n}\n\n/// A type that extends an arbitrary tag with a WhiteSpace, and\n/// provides a conversion back to the original tag.\n#[derive(Debug, Clone, PartialEq, Eq, Default)]\nstruct WithWhiteSpace<T>(T, WhiteSpace);\n\n/// A type to build up wrapped text, allowing extra metadata for\n/// spans.\n#[derive(Debug, Clone)]\nstruct WrappedBlock<T> {\n    width: usize,\n    text: Vec<TaggedLine<T>>,\n    line: TaggedLine<WithWhiteSpace<T>>,\n    spacetag: Option<WithWhiteSpace<T>>, // Tag for the whitespace before the current word\n    word: TaggedLine<WithWhiteSpace<T>>, // The current word\n    wordlen: usize,\n    wslen: usize,\n    pre_wrapped: bool, // If true, we've been forced to wrap a <pre> line.\n    pad_blocks: bool,\n    allow_overflow: bool,\n    default_tag: T,\n}\n\nimpl<T: Clone + Eq + Debug + Default> WrappedBlock<T> {\n    pub fn new(\n        width: usize,\n        pad_blocks: bool,\n        allow_overflow: bool,\n        default_tag: T,\n    ) -> WrappedBlock<T> {\n        WrappedBlock {\n            width,\n            text: Vec::new(),\n            line: TaggedLine::new(),\n            spacetag: None,\n            word: TaggedLine::new(),\n            wordlen: 0,\n            wslen: 0,\n            pre_wrapped: false,\n            pad_blocks,\n            allow_overflow,\n            default_tag,\n        }\n    }\n\n    fn flush_word(&mut self) -> Result<()> {\n        use self::TaggedLineElement::Str;\n\n        /* Finish the word. */\n        html_trace_quiet!(\n            \"flush_word: word={:?}, linelen={}\",\n            self.word,\n            self.line.len\n        );\n\n        if !self.word.is_empty() {\n            let ws_mode = self\n                .word\n                .v\n                .iter()\n                .find_map(|e| match e {\n                    TaggedLineElement::Str(ts) => Some(ts.tag.1),\n                    _ => None,\n                })\n                .unwrap_or(WhiteSpace::Normal);\n            self.pre_wrapped = false;\n            let space_in_line = self.width - self.line.len;\n            let space_needed = self.wslen + self.wordlen;\n            if space_needed <= space_in_line {\n                html_trace!(\"Got enough space\");\n                if self.wslen > 0 {\n                    self.line.push(Str(TaggedString {\n                        s: \" \".repeat(self.wslen),\n                        tag: self.spacetag.take().unwrap(),\n                    }));\n                    self.wslen = 0;\n                }\n\n                self.line.consume(&mut self.word);\n                html_trace!(\"linelen increased by wordlen to {}\", self.line.len);\n            } else {\n                html_trace!(\"Not enough space\");\n                match self.spacetag {\n                    Some(WithWhiteSpace(_, WhiteSpace::Pre)) => {\n                        // We're not word-wrapping, so output any portion that still\n                        // fits.\n                        if self.wslen >= space_in_line {\n                            // Skip the whitespace\n                            self.wslen -= space_in_line;\n                        } else if self.wslen > 0 {\n                            self.line\n                                .push_ws(self.wslen, &self.spacetag.take().unwrap());\n                            self.wslen = 0;\n                        }\n                    }\n                    Some(WithWhiteSpace(_, WhiteSpace::Normal)) => {\n                        // We're word-wrapping, so discard any whitespace.\n                        self.spacetag = None;\n                        self.wslen = 0;\n                    }\n                    Some(WithWhiteSpace(_, WhiteSpace::PreWrap)) => {\n                        // Preserving whitespace except at wrap points.\n                        self.spacetag = None;\n                        self.wslen = 0;\n\n                        // Remove whitespace at the start of the word.\n                        self.word.remove_leading_whitespace();\n                        // And remove spaces at the end of the line.\n                        self.line.remove_trailing_spaces();\n                    }\n                    None => (),\n                }\n                /* Start a new line */\n                self.flush_line();\n\n                if ws_mode == WhiteSpace::Pre {\n                    self.pre_wrapped = true;\n                }\n\n                // Write any remaining whitespace\n                while self.wslen > 0 {\n                    let to_copy = self.wslen.min(self.width);\n                    self.line.push_ws(to_copy, self.spacetag.as_ref().unwrap());\n                    if to_copy == self.width {\n                        self.flush_line();\n                    }\n                    self.wslen -= to_copy;\n                }\n                self.spacetag = None;\n\n                // At this point, either:\n                // We're word-wrapping, and at the start of the line or\n                // We're preformatted, and may have some whitespace at the start of the\n                // line.  In either case we just keep outputing the word directly, hard\n                // wrapping if needed.\n                self.flush_word_hard_wrap()?;\n            }\n        }\n        self.wordlen = 0;\n        Ok(())\n    }\n\n    // Write the current word out, hard-wrapped.  (This may originally be pre-formatted,\n    // or be a word which just doesn't fit on the line.)\n    fn flush_word_hard_wrap(&mut self) -> Result<()> {\n        use self::TaggedLineElement::Str;\n\n        let mut lineleft = self.width - self.line.len;\n        for element in self.word.remove_items() {\n            if let Str(piece) = element {\n                let w = piece.width();\n                let mut wpos = 0; // Width of already-copied pieces\n                let mut bpos = 0; // Byte position of already-copied pieces\n                //\n                while w - wpos > lineleft {\n                    let mut split_idx = 0;\n                    for (idx, c) in piece.s[bpos..].char_indices() {\n                        let c_w = UnicodeWidthChar::width(c).unwrap();\n                        if c_w <= lineleft {\n                            lineleft -= c_w;\n                            wpos += c_w;\n                        } else {\n                            // Check if we've made no progress, for example\n                            // if the first character is 2 cells wide and we\n                            // only have a width of 1.\n                            if idx == 0 && self.line.width() == 0 {\n                                if self.allow_overflow {\n                                    split_idx = c.len_utf8();\n                                    wpos += c_w;\n                                    break;\n                                } else {\n                                    return Err(TooNarrow);\n                                }\n                            }\n                            split_idx = idx;\n                            break;\n                        }\n                    }\n                    self.line.push(Str(TaggedString {\n                        s: piece.s[bpos..bpos + split_idx].into(),\n                        tag: piece.tag.clone(),\n                    }));\n                    bpos += split_idx;\n                    self.force_flush_line();\n                    lineleft = self.width;\n                }\n                if bpos == 0 {\n                    self.line.push(Str(piece));\n                    lineleft -= w;\n                } else if bpos < piece.s.len() {\n                    self.line.push(Str(TaggedString {\n                        s: piece.s[bpos..].into(),\n                        tag: piece.tag,\n                    }));\n                    lineleft -= w.saturating_sub(wpos);\n                }\n            }\n        }\n        Ok(())\n    }\n\n    fn flush_line(&mut self) {\n        if !self.line.is_empty() {\n            self.force_flush_line();\n        }\n    }\n\n    fn force_flush_line(&mut self) {\n        let mut tmp_line = TaggedLine::new();\n        mem::swap(&mut tmp_line, &mut self.line);\n        if self.pad_blocks {\n            tmp_line.pad_to(\n                self.width,\n                &WithWhiteSpace(self.default_tag.clone(), WhiteSpace::Normal),\n            );\n        }\n        self.text\n            .push(tmp_line.map_tag(&|ww: WithWhiteSpace<T>| ww.0));\n    }\n\n    fn flush(&mut self) -> Result<()> {\n        self.flush_word()?;\n        self.flush_line();\n        Ok(())\n    }\n\n    /*\n    /// Consume self and return a vector of lines.\n    pub fn into_untagged_lines(mut self) -> Vec<String> {\n        self.flush();\n\n        let mut result = Vec::new();\n        for line in self.text.into_iter() {\n            let mut line_s = String::new();\n            for TaggedString{ s, .. } in line.into_iter() {\n                line_s.push_str(&s);\n            }\n            result.push(line_s);\n        }\n        result\n    }\n    */\n\n    /// If there are any pending fragment starts, return them.\n    pub fn take_trailing_fragments(&mut self) -> Vec<TaggedLineElement<T>> {\n        if self.word.is_empty() {\n            std::mem::take(&mut self.word)\n                .v\n                .into_iter()\n                .map(|e| e.map_tag(&|ww: WithWhiteSpace<T>| ww.0))\n                .collect()\n        } else {\n            Default::default()\n        }\n    }\n\n    /// Consume self and return vector of lines including annotations.\n    pub fn into_lines(mut self) -> Result<Vec<TaggedLine<T>>> {\n        self.flush()?;\n\n        Ok(self.text)\n    }\n\n    fn add_text(\n        &mut self,\n        text: &str,\n        ws_mode: WhiteSpace,\n        main_tag: &T,\n        wrap_tag: &T,\n    ) -> Result<()> {\n        html_trace!(\"WrappedBlock::add_text({}), {:?}\", text, main_tag);\n        // We walk character by character.\n        // 1. First, build up whitespace columns in self.wslen\n        //    - In normal mode self.wslen will always be 0 or 1\n        //    - If wslen > 0, then self.spacetag will always be set.\n        // 2. Next build up a word (non-whitespace).\n        // 2a. If the word gets too long for the line\n        // 2b. If we get to more whitespace, output the first whitespace and the word\n        //     and continue.\n        //\n        // In \"pre\" mode, we treat whitespace other than newlines as word characters.\n        let mut tag = if self.pre_wrapped { wrap_tag } else { main_tag };\n        for c in text.chars() {\n            html_trace!(\n                \"c = {:?} word={:?} linelen={} wslen={} line={:?}\",\n                c,\n                self.word,\n                self.line.len,\n                self.wslen,\n                self.line\n            );\n            if c.is_wordbreak_point() && self.wordlen > 0 && ws_mode != WhiteSpace::Pre {\n                self.flush_word()?;\n            }\n\n            if c == '\\u{200b}' {\n                // This is a zero-width space.  As we're doing the formatting, we can just omit it.\n                continue;\n            } else if !c.always_takes_space() {\n                if ws_mode.preserve_whitespace() {\n                    match c {\n                        '\\n' => {\n                            self.flush_word()?;\n                            self.force_flush_line();\n                            self.wslen = 0;\n                            self.spacetag = None;\n                            self.pre_wrapped = false;\n                            // Hard new line, so back to main tag.\n                            tag = main_tag;\n                        }\n                        '\\t' => {\n                            // Flush the word.\n                            self.flush_word()?;\n\n                            let tab_stop = 8;\n                            let mut pos = self.line.len + self.wordlen + self.wslen;\n                            let mut at_least_one_space = false;\n                            while pos % tab_stop != 0 || !at_least_one_space {\n                                if pos >= self.width {\n                                    self.flush_line();\n                                    pos = 0;\n                                } else {\n                                    self.line\n                                        .push_char(' ', &WithWhiteSpace(tag.clone(), ws_mode));\n                                    pos += 1;\n                                    at_least_one_space = true;\n                                }\n                            }\n                        }\n                        _ => {\n                            if let Some(cwidth) = UnicodeWidthChar::width(c) {\n                                if self.word.is_empty() && char::is_whitespace(c) {\n                                    self.wslen += cwidth;\n                                    self.spacetag = Some(WithWhiteSpace(tag.clone(), ws_mode));\n                                } else {\n                                    // Add the character.\n                                    self.word\n                                        .push_char(c, &WithWhiteSpace(tag.clone(), ws_mode));\n                                    self.wordlen += cwidth;\n                                }\n                            }\n                        }\n                    }\n                } else {\n                    // If not preserving whitespace, everything is collapsed,\n                    // and the line won't start with whitespace.\n                    if self.line.len > 0 && self.wslen == 0 {\n                        self.spacetag = Some(WithWhiteSpace(tag.clone(), ws_mode));\n                        self.wslen = 1;\n                    }\n                }\n            } else {\n                // Non-whitespace character: add to the current word.\n                if let Some(cwidth) = UnicodeWidthChar::width(c) {\n                    self.wordlen += cwidth;\n                    // Special case: detect wrapping preformatted line to switch\n                    // the tag.\n                    if ws_mode == WhiteSpace::Pre\n                        && (self.line.len + self.wslen + self.wordlen > self.width)\n                    {\n                        self.pre_wrapped = true;\n                        tag = wrap_tag;\n                    }\n                    self.word\n                        .push_char(c, &WithWhiteSpace(tag.clone(), ws_mode));\n                }\n            }\n        }\n        Ok(())\n    }\n\n    fn add_element(&mut self, elt: TaggedLineElement<T>) {\n        self.word\n            .push(elt.map_tag(&|t| WithWhiteSpace(t, WhiteSpace::Normal))); // FIXME\n    }\n\n    fn text_len(&self) -> usize {\n        self.text.len() + self.line.len + self.wordlen\n    }\n\n    fn is_empty(&self) -> bool {\n        self.text_len() == 0\n    }\n}\n\n/// Allow decorating/styling text.\n///\n/// Decorating refers to adding extra text around the rendered version\n/// of some elements, such as surrounding emphasised text with `*` like\n/// in markdown: `Some *bold* text`.  The decorations are formatted and\n/// wrapped along with the rest of the rendered text.  This is suitable\n/// for rendering HTML to an environment where terminal attributes are\n/// not available.\n///\n/// In addition, instances of `TextDecorator` can also return annotations\n/// of an associated type `Annotation` which will be associated with spans of\n/// text.  This can be anything from `()` as for `PlainDecorator` or a more\n/// featured type such as `RichAnnotation`.  The annotated spans (`TaggedLine`)\n/// can be used by application code to add e.g. terminal colours or underlines.\npub trait TextDecorator {\n    /// An annotation which can be added to text, and which will\n    /// be attached to spans of text.\n    type Annotation: Eq + PartialEq + Debug + Clone + Default;\n\n    /// Return an annotation and rendering prefix for a link.\n    fn decorate_link_start(&mut self, url: &str) -> (String, Self::Annotation);\n\n    /// Return a suffix for after a link.\n    fn decorate_link_end(&mut self) -> String;\n\n    /// Return an annotation and rendering prefix for em\n    fn decorate_em_start(&self) -> (String, Self::Annotation);\n\n    /// Return a suffix for after an em.\n    fn decorate_em_end(&self) -> String;\n\n    /// Return an annotation and rendering prefix for strong\n    fn decorate_strong_start(&self) -> (String, Self::Annotation);\n\n    /// Return a suffix for after a strong.\n    fn decorate_strong_end(&self) -> String;\n\n    /// Return an annotation and rendering prefix for strikeout\n    fn decorate_strikeout_start(&self) -> (String, Self::Annotation);\n\n    /// Return a suffix for after a strikeout.\n    fn decorate_strikeout_end(&self) -> String;\n\n    /// Return an annotation and rendering prefix for code\n    fn decorate_code_start(&self) -> (String, Self::Annotation);\n\n    /// Return a suffix for after a code.\n    fn decorate_code_end(&self) -> String;\n\n    /// Return an annotation for the initial part of a preformatted line\n    fn decorate_preformat_first(&self) -> Self::Annotation;\n\n    /// Return an annotation for a continuation line when a preformatted\n    /// line doesn't fit.\n    fn decorate_preformat_cont(&self) -> Self::Annotation;\n\n    /// Return an annotation and rendering prefix for a link.\n    fn decorate_image(&mut self, src: &str, title: &str) -> (String, Self::Annotation);\n\n    /// Return prefix string of header in specific level.\n    fn header_prefix(&self, level: usize) -> String;\n\n    /// Return prefix string of quoted block.\n    fn quote_prefix(&self) -> String;\n\n    /// Return prefix string of unordered list item.\n    fn unordered_item_prefix(&self) -> String;\n\n    /// Return prefix string of ith ordered list item.\n    fn ordered_item_prefix(&self, i: i64) -> String;\n\n    /// Return a new decorator of the same type which can be used\n    /// for sub blocks.\n    fn make_subblock_decorator(&self) -> Self;\n\n    /// Return an annotation corresponding to adding colour, or none.\n    fn push_colour(&mut self, _: Colour) -> Option<Self::Annotation> {\n        None\n    }\n\n    /// Pop the last colour pushed if we pushed one.\n    fn pop_colour(&mut self) -> bool {\n        false\n    }\n\n    /// Return an annotation corresponding to adding background colour, or none.\n    fn push_bgcolour(&mut self, _: Colour) -> Option<Self::Annotation> {\n        None\n    }\n\n    /// Pop the last background colour pushed if we pushed one.\n    fn pop_bgcolour(&mut self) -> bool {\n        false\n    }\n\n    /// Return an annotation and rendering prefix for superscript text\n    fn decorate_superscript_start(&self) -> (String, Self::Annotation) {\n        (\"^{\".into(), Default::default())\n    }\n\n    /// Return a suffix for after a superscript.\n    fn decorate_superscript_end(&self) -> String {\n        \"}\".into()\n    }\n\n    /// Finish with a document, and return extra lines to add to the rendered text.\n    /// The urls are in the correct order for footnotes; if footnote references were\n    /// not included then this list will be empty.\n    fn finalise(&mut self, urls: Vec<String>) -> Vec<TaggedLine<Self::Annotation>> {\n        urls.into_iter()\n            .enumerate()\n            .map(|(idx, s)| {\n                TaggedLine::from_string(format!(\"[{}]: {}\", idx + 1, s), &Default::default())\n            })\n            .collect()\n    }\n}\n\n/// A space on a horizontal row.\n#[derive(Copy, Clone, Debug)]\nenum BorderSegHoriz {\n    /// Pure horizontal line\n    Horiz,\n    /// Joined with a line above\n    JoinAbove,\n    /// Joins with a line below\n    JoinBelow,\n    /// Joins both ways\n    JoinCross,\n    /// Vertical bar\n    Vert,\n    /// Horizontal line, but separating two table cells from a row\n    /// which wouldn't fit next to each other.\n    HorizVert,\n    /// Vertical bar with join to the left\n    JoinLeft,\n    /// Vertical bar with join to the right\n    JoinRight,\n    /// Top left corner\n    CornerTL,\n    /// Top right corner\n    CornerTR,\n    /// Bottom left corner\n    CornerBL,\n    /// Bottom right corner\n    CornerBR,\n}\n\nimpl BorderSegHoriz {\n    /// Chop any join to the left of this cell.\n    pub fn chop_left(&mut self) {\n        use BorderSegHoriz::*;\n        match *self {\n            Horiz | HorizVert => {}\n            JoinBelow => {\n                *self = CornerTL;\n            }\n            JoinAbove => {\n                *self = CornerBL;\n            }\n            JoinCross => {\n                *self = JoinRight;\n            }\n            Vert => {}\n            JoinLeft => {\n                *self = Vert;\n            }\n            JoinRight => {}\n            CornerTL => {}\n            CornerTR => {\n                *self = Vert;\n            }\n            CornerBL => {}\n            CornerBR => {\n                *self = Vert;\n            }\n        }\n    }\n\n    /// Chop any join to the right of this cell.\n    pub fn chop_right(&mut self) {\n        use BorderSegHoriz::*;\n        match *self {\n            Horiz | HorizVert => {}\n            JoinBelow => {\n                *self = CornerTR;\n            }\n            JoinAbove => {\n                *self = CornerBR;\n            }\n            JoinCross => {\n                *self = JoinLeft;\n            }\n            Vert => {}\n            JoinLeft => {}\n            JoinRight => {\n                *self = Vert;\n            }\n            CornerTL => {\n                *self = Vert;\n            }\n            CornerTR => {}\n            CornerBL => {\n                *self = Vert;\n            }\n            CornerBR => {}\n        }\n    }\n}\n/// A dividing line between table rows which tracks intersections\n/// with vertical lines.\n///\n/// It may also have actual text, when cells span multiple rows\n/// and extend through the row.\n#[derive(Clone, Debug)]\npub(crate) struct BorderHoriz<T> {\n    /// The segments for the line.\n    segments: Vec<BorderSegHoriz>,\n    /// The tag associated with the lines\n    tag: T,\n    /// Parts of text which punch a hole through the line,\n    /// with their positions.\n    holes: Vec<(usize, TaggedLineElement<T>)>,\n}\n\nimpl<T: Clone> BorderHoriz<T> {\n    /// Create a new blank border line.\n    pub fn new(width: usize, tag: T) -> Self {\n        BorderHoriz {\n            segments: vec![BorderSegHoriz::Horiz; width],\n            tag,\n            holes: Default::default(),\n        }\n    }\n\n    /// Create a new blank border line.\n    fn new_type(width: usize, linetype: BorderSegHoriz, tag: T) -> Self {\n        BorderHoriz {\n            segments: vec![linetype; width],\n            tag,\n            holes: Default::default(),\n        }\n    }\n\n    /// Stretch the line to at least the specified width\n    fn stretch_to(&mut self, width: usize) {\n        use self::BorderSegHoriz::*;\n        while width > self.segments.len() {\n            self.segments.push(Horiz);\n        }\n    }\n\n    /// Make a join to a line above at the xth cell\n    fn join_above(&mut self, x: usize) {\n        use self::BorderSegHoriz::*;\n        self.stretch_to(x + 1);\n        let prev = self.segments[x];\n        self.segments[x] = match prev {\n            Horiz | JoinAbove => JoinAbove,\n            JoinBelow | JoinCross => JoinCross,\n            Vert => Vert,\n            JoinLeft => JoinLeft,\n            JoinRight => JoinRight,\n            CornerTL => JoinRight,\n            CornerTR => JoinLeft,\n            CornerBL => CornerBL,\n            CornerBR => CornerBR,\n            HorizVert => HorizVert,\n        }\n    }\n\n    /// Make a join to a line below at the xth cell\n    fn join_below(&mut self, x: usize) {\n        use self::BorderSegHoriz::*;\n        self.stretch_to(x + 1);\n        let prev = self.segments[x];\n        self.segments[x] = match prev {\n            Horiz | JoinBelow => JoinBelow,\n            JoinAbove | JoinCross => JoinCross,\n            Vert => Vert,\n            JoinLeft => JoinLeft,\n            JoinRight => JoinRight,\n            CornerTL => CornerTL,\n            CornerTR => CornerTR,\n            CornerBL => JoinRight,\n            CornerBR => JoinLeft,\n            HorizVert => HorizVert,\n        }\n    }\n\n    /// Merge a (possibly partial) border line below into this one.\n    fn merge_from_below(&mut self, other: &BorderHoriz<T>, pos: usize) {\n        use self::BorderSegHoriz::*;\n        for (idx, seg) in other.segments.iter().enumerate() {\n            match *seg {\n                Horiz | Vert | JoinLeft | JoinRight | CornerTL | CornerTR | HorizVert => (),\n                JoinAbove | JoinBelow | JoinCross | CornerBL | CornerBR => {\n                    self.join_below(idx + pos);\n                }\n            }\n        }\n    }\n\n    /// Merge a (possibly partial) border line above into this one.\n    fn merge_from_above(&mut self, other: &BorderHoriz<T>, pos: usize) {\n        use self::BorderSegHoriz::*;\n        for (idx, seg) in other.segments.iter().enumerate() {\n            match *seg {\n                Horiz | Vert | JoinLeft | JoinRight | CornerBL | CornerBR | HorizVert => (),\n                JoinAbove | JoinBelow | JoinCross | CornerTL | CornerTR => {\n                    self.join_above(idx + pos);\n                }\n            }\n        }\n    }\n\n    /// Return a string of spaces and vertical lines which would match\n    /// just above this line.\n    fn to_vertical_lines_above(&self) -> String {\n        use self::BorderSegHoriz::*;\n        self.segments\n            .iter()\n            .map(|seg| match *seg {\n                Horiz | JoinBelow | Vert | JoinLeft | JoinRight | CornerTL | CornerTR\n                | HorizVert => ' ',\n                JoinAbove | JoinCross | CornerBL | CornerBR => '│',\n            })\n            .collect()\n    }\n\n    /// Add a chunk of text on top of the line.\n    fn add_text_span(&mut self, pos: usize, t: TaggedLineElement<T>)\n    where\n        T: Debug,\n    {\n        // Adjust the line pieces on either side.\n        if pos > 0 {\n            if let Some(seg) = self.segments.get_mut(pos - 1) {\n                seg.chop_right()\n            }\n        }\n        let rpos = pos + t.width();\n        if let Some(seg) = self.segments.get_mut(rpos) {\n            seg.chop_left()\n        }\n        self.holes.push((pos, t));\n    }\n\n    fn seg_to_char(seg: BorderSegHoriz) -> char {\n        match seg {\n            BorderSegHoriz::Horiz => '─',\n            BorderSegHoriz::Vert => '│',\n            BorderSegHoriz::HorizVert => '/',\n            BorderSegHoriz::JoinAbove => '┴',\n            BorderSegHoriz::JoinBelow => '┬',\n            BorderSegHoriz::JoinCross => '┼',\n            BorderSegHoriz::JoinLeft => '┤',\n            BorderSegHoriz::JoinRight => '├',\n            BorderSegHoriz::CornerTL => '┌',\n            BorderSegHoriz::CornerTR => '┐',\n            BorderSegHoriz::CornerBL => '└',\n            BorderSegHoriz::CornerBR => '┘',\n        }\n    }\n\n    /// Turn into a string with drawing characters\n    #[allow(clippy::inherent_to_string)]\n    fn to_string(&self) -> String {\n        let mut result = String::new();\n        let mut pos = 0usize;\n\n        for (holepos, hole) in &self.holes {\n            for seg in &self.segments[pos..*holepos] {\n                result.push(Self::seg_to_char(*seg));\n            }\n            pos = *holepos;\n            result.push_str(hole.as_str());\n            pos += hole.width();\n        }\n        if pos < self.segments.len() {\n            for seg in &self.segments[pos..] {\n                result.push(Self::seg_to_char(*seg));\n            }\n        }\n        result\n    }\n\n    fn extend_to(&mut self, len: usize) {\n        while self.segments.len() < len {\n            self.segments.push(BorderSegHoriz::Horiz);\n        }\n    }\n}\n\nimpl<T: Clone + Debug + Eq + Default> BorderHoriz<T> {\n    fn into_tagged_line(self) -> TaggedLine<T> {\n        use self::TaggedLineElement::Str;\n        let mut pos = 0usize;\n        let mut result = TaggedLine::new();\n        let tag = self.tag.clone();\n\n        for (holepos, hole) in self.holes {\n            if holepos > pos {\n                let mut s = String::new();\n                for seg in &self.segments[pos..holepos] {\n                    s.push(Self::seg_to_char(*seg));\n                }\n                result.push(Str(TaggedString {\n                    s,\n                    tag: tag.clone(),\n                }));\n                pos = holepos;\n            }\n            pos += hole.width();\n            result.push(hole);\n        }\n        if pos < self.segments.len() {\n            let mut s = String::new();\n            for seg in &self.segments[pos..] {\n                s.push(Self::seg_to_char(*seg));\n            }\n            result.push(Str(TaggedString {\n                s,\n                tag: tag.clone(),\n            }));\n        }\n        result\n    }\n}\n\n/// A line, which can either be text or a line.\n#[derive(Clone, Debug)]\npub(crate) enum RenderLine<T> {\n    /// Some rendered text\n    Text(TaggedLine<T>),\n    /// A table border line\n    Line(BorderHoriz<T>),\n}\n\nimpl<T: PartialEq + Eq + Clone + Debug + Default> RenderLine<T> {\n    /// Turn the rendered line into a String\n    #[allow(clippy::inherent_to_string)]\n    fn to_string(&self) -> String {\n        match self {\n            RenderLine::Text(tagged) => tagged.to_string(),\n            RenderLine::Line(border) => border.to_string(),\n        }\n    }\n\n    /// Convert into a `TaggedLine<T>`, if necessary squashing the\n    /// BorderHoriz into one.\n    pub fn into_tagged_line(self) -> TaggedLine<T> {\n        match self {\n            RenderLine::Text(tagged) => tagged,\n            RenderLine::Line(border) => border.into_tagged_line(),\n        }\n    }\n\n    /// Return whether this line has any text content\n    /// Borders do not count as text.\n    fn has_content(&self) -> bool {\n        match self {\n            RenderLine::Text(line) => !line.is_empty(),\n            RenderLine::Line(bord) => bord.holes.is_empty(),\n        }\n    }\n}\n\n/// A renderer which just outputs plain text with\n/// annotations depending on a decorator.\npub(crate) struct SubRenderer<D: TextDecorator> {\n    /// Text width\n    width: usize,\n    /// Rendering options\n    pub options: RenderOptions,\n    /// The currently generated lines\n    lines: LinkedList<RenderLine<Vec<D::Annotation>>>,\n    /// FragmentStart items which have not yet been output.\n    pending_frags: Vec<TaggedLineElement<Vec<D::Annotation>>>,\n    /// True at the end of a block, meaning we should add\n    /// a blank line if any other text is added.\n    at_block_end: bool,\n    wrapping: Option<WrappedBlock<Vec<D::Annotation>>>,\n    decorator: D,\n    ann_stack: Vec<D::Annotation>,\n    text_filter_stack: Vec<fn(&str) -> Option<String>>,\n    /// The depth of `<pre>` block stacking.\n    pre_depth: usize,\n    /// The current stack of whitespace wrapping setting\n    ws_stack: Vec<WhiteSpace>,\n\n    /// Parts of table cells which overhang from the last row\n    /// (because of rowspan)\n    overhang_cells: Vec<LineSet<D::Annotation>>,\n}\n\nimpl<D: TextDecorator> std::fmt::Debug for SubRenderer<D> {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        f.debug_struct(\"SubRenderer\")\n            .field(\"width\", &self.width)\n            .field(\"lines\", &self.lines)\n            //.field(\"decorator\", &self.decorator)\n            .field(\"ann_stack\", &self.ann_stack)\n            .field(\"ws_stack\", &self.ws_stack)\n            .field(\"wrapping\", &self.wrapping)\n            .finish()\n    }\n}\n\nimpl<D: TextDecorator> std::fmt::Display for SubRenderer<D> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        writeln!(f, \"SubRenderer(width={})\", self.width)?;\n        writeln!(f, \" Lines: {}\", self.lines.len())?;\n        for line in &self.lines {\n            match line {\n                RenderLine::Text(tagged_line) => {\n                    writeln!(f, \"  {}\", tagged_line.to_string())?;\n                }\n                RenderLine::Line(_) => {\n                    writeln!(f, \"  <<<border>>>\")?;\n                }\n            }\n        }\n        if let Some(wrapping) = &self.wrapping {\n            writeln!(f, \" WrappedBlock text:\")?;\n            for line in &wrapping.text {\n                writeln!(f, \"  {}\", line.to_string())?;\n            }\n            writeln!(f, \" WrappedBlock cur line:\")?;\n            writeln!(f, \"  {}\", wrapping.line.to_string())?;\n            writeln!(f, \" WrappedBlock Word: [[{}]]\", wrapping.word.to_string())?;\n        }\n        writeln!(f)\n    }\n}\n\n/// Rendering options.\n#[derive(Clone)]\n#[non_exhaustive]\npub(crate) struct RenderOptions {\n    /// The maximum text wrap width.  If set, paragraphs of text will only be wrapped\n    /// to that width or less, though the overall width can be larger (e.g. for indented\n    /// blocks or side-by-side table cells).\n    pub wrap_width: Option<usize>,\n\n    /// If true, then allow the output to be wider than specified instead of returning\n    /// `Err(TooNarrow)`.\n    pub allow_width_overflow: bool,\n\n    /// Whether to always pad lines out to the full width.\n    /// This may give a better output when the parent block\n    /// has a background colour set.\n    pub pad_block_width: bool,\n\n    /// Raw extraction, ensures text in table cells ends up rendered together\n    /// This traverses tables as if they had a single column and every cell is its own row.\n    pub raw: bool,\n\n    /// Whether to draw table borders\n    pub draw_borders: bool,\n\n    /// Whether to wrap links as normal text\n    pub wrap_links: bool,\n\n    /// Whether to include footnotes for hyperlinks\n    pub include_link_footnotes: bool,\n\n    /// Whether to use Unicode combining characters for crossing text out.\n    pub use_unicode_strikeout: bool,\n\n    /// Image rendering mode\n    pub img_mode: ImageRenderMode,\n}\n\nimpl Default for RenderOptions {\n    fn default() -> Self {\n        Self {\n            wrap_width: Default::default(),\n            allow_width_overflow: Default::default(),\n            pad_block_width: Default::default(),\n            raw: false,\n            draw_borders: true,\n            wrap_links: true,\n            include_link_footnotes: false,\n            use_unicode_strikeout: true,\n            img_mode: Default::default(),\n        }\n    }\n}\n\nfn get_wrapping_or_insert<'w, D: TextDecorator>(\n    wrapping: &'w mut Option<WrappedBlock<Vec<D::Annotation>>>,\n    options: &RenderOptions,\n    width: usize,\n    default_tag: &[D::Annotation],\n) -> &'w mut WrappedBlock<Vec<D::Annotation>> {\n    wrapping.get_or_insert_with(|| {\n        let wwidth = match options.wrap_width {\n            Some(ww) => ww.min(width),\n            None => width,\n        };\n        WrappedBlock::new(\n            wwidth,\n            options.pad_block_width,\n            options.allow_width_overflow,\n            default_tag.to_owned(),\n        )\n    })\n}\n\nimpl<D: TextDecorator> SubRenderer<D> {\n    /// Render links as lines\n    pub fn finalise(&mut self, links: Vec<String>) -> Vec<TaggedLine<D::Annotation>> {\n        if self.options.include_link_footnotes {\n            self.decorator.finalise(links)\n        } else {\n            self.decorator.finalise(Vec::new())\n        }\n    }\n\n    /// Construct a new empty SubRenderer.\n    pub fn new(width: usize, options: RenderOptions, decorator: D) -> SubRenderer<D> {\n        html_trace!(\"new({})\", width);\n        SubRenderer {\n            width,\n            options,\n            lines: LinkedList::new(),\n            at_block_end: false,\n            wrapping: None,\n            decorator,\n            ann_stack: Vec::new(),\n            ws_stack: Vec::new(),\n            pre_depth: 0,\n            text_filter_stack: Vec::new(),\n            pending_frags: Default::default(),\n            overhang_cells: Default::default(),\n        }\n    }\n\n    /// Append a line to the output.\n    /// Any pending fragments will be prepended to a non-border line.\n    fn add_line(&mut self, mut line: RenderLine<Vec<D::Annotation>>) {\n        if !self.pending_frags.is_empty() {\n            match line {\n                RenderLine::Text(tagged_line) => {\n                    let mut tl = TaggedLine::new();\n                    for frag in std::mem::take(&mut self.pending_frags) {\n                        tl.push(frag);\n                    }\n                    for part in tagged_line.into_iter() {\n                        tl.push(part);\n                    }\n                    if self.options.pad_block_width {\n                        tl.pad_to(self.width, &self.ann_stack);\n                    }\n                    line = RenderLine::Text(tl);\n                }\n                RenderLine::Line(..) => (),\n            }\n        }\n        if self.options.pad_block_width {\n            match &mut line {\n                RenderLine::Text(tl) => {\n                    tl.pad_to(self.width, &self.ann_stack);\n                }\n                RenderLine::Line(..) => (),\n            }\n        }\n        self.lines.push_back(line);\n    }\n\n    /// Append multiple lines to the output\n    /// Any pending fragments will be prepended to a non-border line.\n    fn extend_lines(&mut self, lines: impl IntoIterator<Item = RenderLine<Vec<D::Annotation>>>) {\n        for line in lines {\n            self.add_line(line);\n        }\n    }\n\n    /// Flushes the current wrapped block into the lines.\n    fn flush_wrapping(&mut self) -> Result<()> {\n        if let Some(mut w) = self.wrapping.take() {\n            let frags = w.take_trailing_fragments();\n            self.extend_lines(w.into_lines()?.into_iter().map(RenderLine::Text));\n\n            self.pending_frags.extend(frags);\n        }\n        Ok(())\n    }\n\n    /// Flush the wrapping text and border.  Only one should have\n    /// anything to do.\n    fn flush_all(&mut self) -> Result<()> {\n        self.flush_wrapping()?;\n        Ok(())\n    }\n\n    /// Consumes this renderer and return a multiline `String` with the result.\n    pub fn into_string(mut self) -> Result<String> {\n        let mut result = String::new();\n        self.flush_wrapping()?;\n        for line in &self.lines {\n            result.push_str(&line.to_string());\n            result.push('\\n');\n        }\n        html_trace!(\"into_string({}, {:?})\", self.width, result);\n        Ok(result)\n    }\n\n    #[cfg(feature = \"html_trace\")]\n    /// Returns a string of the current builder contents (for testing).\n    #[allow(clippy::inherent_to_string_shadow_display)]\n    fn to_string(&self) -> String {\n        let mut result = String::new();\n        for line in &self.lines {\n            result += &line.to_string();\n            result.push('\\n');\n        }\n        result\n    }\n\n    /// Wrap links to width\n    pub fn fmt_links(&mut self, mut links: Vec<TaggedLine<D::Annotation>>) {\n        for line in links.drain(..) {\n            /* Hard wrap */\n            let mut pos = 0;\n            let mut wrapped_line = TaggedLine::new();\n            for ts in line.into_tagged_strings() {\n                // FIXME: should we percent-escape?  This is probably\n                // an invalid URL to start with.\n                let s = ts.s.replace('\\n', \" \");\n                let tag = vec![ts.tag];\n\n                let width = s.width();\n                if self.options.wrap_links && pos + width > self.width {\n                    // split the string and start a new line\n                    let mut buf = String::new();\n                    for c in s.chars() {\n                        let c_width = UnicodeWidthChar::width(c).unwrap_or(0);\n                        if pos + c_width > self.width {\n                            if !buf.is_empty() {\n                                wrapped_line.push_str(TaggedString {\n                                    s: buf,\n                                    tag: tag.clone(),\n                                });\n                                buf = String::new();\n                            }\n\n                            self.add_line(RenderLine::Text(wrapped_line));\n                            wrapped_line = TaggedLine::new();\n                            pos = 0;\n                        }\n                        pos += c_width;\n                        buf.push(c);\n                    }\n                    wrapped_line.push_str(TaggedString { s: buf, tag });\n                } else {\n                    wrapped_line.push_str(TaggedString {\n                        s: s.to_owned(),\n                        tag,\n                    });\n                    pos += width;\n                }\n            }\n            self.add_line(RenderLine::Text(wrapped_line));\n        }\n    }\n\n    /// Returns a `Vec` of `TaggedLine`s with the rendered text.\n    pub fn into_lines(mut self) -> Result<LinkedList<RenderLine<Vec<D::Annotation>>>> {\n        self.flush_wrapping()?;\n        Ok(self.lines)\n    }\n\n    fn add_horizontal_line(&mut self, line: BorderHoriz<Vec<D::Annotation>>) -> Result<()> {\n        self.flush_wrapping()?;\n        self.add_line(RenderLine::Line(line));\n        Ok(())\n    }\n\n    pub fn width_minus(&self, prefix_len: usize, min_width: usize) -> Result<usize> {\n        let new_width = self.width.saturating_sub(prefix_len);\n        if new_width < min_width && !self.options.allow_width_overflow {\n            return Err(TooNarrow);\n        }\n        Ok(new_width.max(min_width))\n    }\n\n    fn ws_mode(&self) -> WhiteSpace {\n        self.ws_stack.last().cloned().unwrap_or(WhiteSpace::Normal)\n    }\n}\n\nfn filter_text_strikeout(s: &str) -> Option<String> {\n    let mut result = String::new();\n    for c in s.chars() {\n        result.push(c);\n        if UnicodeWidthChar::width(c).unwrap_or(0) > 0 {\n            // This is a character with width (not a combining or other character)\n            // so add a strikethrough combiner.\n            result.push('\\u{336}');\n        }\n    }\n    Some(result)\n}\n\n// State for a cell we're outputting to the lines.\n#[derive(Default, Debug)]\nstruct LineSet<A> {\n    // The cell's horizontal character position\n    pos: usize,\n    // The cell's width\n    width: usize,\n    // The cell's rowspan\n    rowspan: usize,\n    // The remaining lines.\n    lines: VecDeque<RenderLine<Vec<A>>>,\n}\n\nimpl<A: PartialEq + Eq + Clone + Debug + Default> std::fmt::Display for LineSet<A> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let lines = self.lines.iter().map(|l| l.to_string()).collect::<Vec<_>>();\n        f.debug_struct(\"LineSet\")\n            .field(\"pos\", &self.pos)\n            .field(\"width\", &self.width)\n            .field(\"rowspan\", &self.rowspan)\n            .field(\"lines\", &lines)\n            .finish()\n    }\n}\nimpl<A> LineSet<A> {\n    fn cell_height(&self) -> usize {\n        let tot_lines = self.lines.len();\n        if self.rowspan == 1 {\n            tot_lines\n        } else {\n            // Divide the height by the rowspan\n            // We are only counting the space between the horizontal borders,\n            // so want subtract those before dividing.  However we also want\n            // to round up, so it turns out that we would add the same value\n            // back, which neatly cancels out.\n            tot_lines / self.rowspan\n        }\n    }\n}\n\nimpl<D: TextDecorator> Renderer for SubRenderer<D> {\n    fn add_empty_line(&mut self) -> Result<()> {\n        html_trace!(\"add_empty_line()\");\n        self.flush_all()?;\n        self.add_line(RenderLine::Text(TaggedLine::new()));\n        html_trace_quiet!(\"add_empty_line: at_block_end <- false\");\n        self.at_block_end = false;\n        html_trace_quiet!(\"add_empty_line: new lines: {:?}\", self.lines);\n        Ok(())\n    }\n\n    fn new_sub_renderer(&self, width: usize) -> Result<Self> {\n        let mut result = SubRenderer::new(\n            width,\n            self.options.clone(),\n            self.decorator.make_subblock_decorator(),\n        );\n        // Copy the annotation stack\n        result.ann_stack = self.ann_stack.clone();\n        Ok(result)\n    }\n\n    fn start_block(&mut self) -> Result<()> {\n        html_trace!(\"start_block({})\", self.width);\n        self.flush_all()?;\n        if self.lines.iter().any(|l| l.has_content()) {\n            self.add_empty_line()?;\n        }\n        html_trace_quiet!(\"start_block; at_block_end <- false\");\n        self.at_block_end = false;\n        Ok(())\n    }\n\n    fn start_table(&mut self) -> Result<()> {\n        //debug_assert!(self.overhang_cells.is_empty());\n        self.start_block()\n    }\n\n    fn new_line(&mut self) -> Result<()> {\n        self.flush_all()\n    }\n\n    fn new_line_hard(&mut self) -> Result<()> {\n        match &self.wrapping {\n            None => self.add_empty_line(),\n            Some(wrapping) => {\n                if wrapping.wordlen == 0 && wrapping.line.len == 0 {\n                    self.add_empty_line()\n                } else {\n                    self.flush_all()\n                }\n            }\n        }\n    }\n\n    fn add_horizontal_border(&mut self) -> Result<()> {\n        self.flush_wrapping()?;\n        self.add_line(RenderLine::Line(BorderHoriz::new(\n            self.width,\n            self.ann_stack.clone(),\n        )));\n        Ok(())\n    }\n\n    fn add_horizontal_border_width(&mut self, width: usize) -> Result<()> {\n        self.flush_wrapping()?;\n        self.add_line(RenderLine::Line(BorderHoriz::new(\n            width,\n            self.ann_stack.clone(),\n        )));\n        Ok(())\n    }\n\n    fn push_ws(&mut self, ws: WhiteSpace) {\n        self.ws_stack.push(ws);\n    }\n\n    fn pop_ws(&mut self) {\n        self.ws_stack.pop();\n    }\n\n    fn push_preformat(&mut self) {\n        self.pre_depth += 1;\n    }\n\n    fn pop_preformat(&mut self) {\n        debug_assert!(self.pre_depth > 0);\n        self.pre_depth -= 1;\n    }\n\n    fn end_block(&mut self) {\n        self.at_block_end = true;\n    }\n\n    fn add_inline_text(&mut self, text: &str) -> Result<()> {\n        html_trace!(\"add_inline_text({}, {})\", self.width, text);\n        if !self.ws_mode().preserve_whitespace()\n            && self.at_block_end\n            && text.chars().all(char::is_whitespace)\n        {\n            // Ignore whitespace between blocks.\n            return Ok(());\n        }\n        if self.at_block_end {\n            self.start_block()?;\n        }\n        let mut s = None;\n        // Do any filtering of the text\n        for filter in &self.text_filter_stack {\n            let srctext = s.as_deref().unwrap_or(text);\n            if let Some(filtered) = filter(srctext) {\n                s = Some(filtered);\n            }\n        }\n        let filtered_text = s.as_deref().unwrap_or(text);\n        let ws_mode = self.ws_mode();\n        let wrapping = get_wrapping_or_insert::<D>(\n            &mut self.wrapping,\n            &self.options,\n            self.width,\n            &self.ann_stack,\n        );\n        let mut pre_tag_start;\n        let mut pre_tag_cont;\n\n        let main_tag;\n        let cont_tag;\n        if self.pre_depth > 0 {\n            pre_tag_start = self.ann_stack.clone();\n            pre_tag_cont = self.ann_stack.clone();\n            pre_tag_start.push(self.decorator.decorate_preformat_first());\n            pre_tag_cont.push(self.decorator.decorate_preformat_cont());\n            main_tag = &pre_tag_start;\n            cont_tag = &pre_tag_cont;\n        } else {\n            main_tag = &self.ann_stack;\n            cont_tag = &self.ann_stack;\n        }\n        wrapping.add_text(filtered_text, ws_mode, main_tag, cont_tag)?;\n        Ok(())\n    }\n\n    fn width(&self) -> usize {\n        self.width\n    }\n\n    fn append_subrender<'a, I>(&mut self, other: Self, prefixes: I) -> Result<()>\n    where\n        I: Iterator<Item = &'a str>,\n    {\n        use self::TaggedLineElement::Str;\n\n        self.flush_wrapping()?;\n        let tag = self.ann_stack.clone();\n\n        self.extend_lines(\n            other\n                .into_lines()?\n                .into_iter()\n                .zip(prefixes)\n                .map(|(line, prefix)| match line {\n                    RenderLine::Text(mut tline) => {\n                        if !prefix.is_empty() {\n                            tline.insert_front(TaggedString {\n                                s: prefix.to_string(),\n                                tag: tag.clone(),\n                            });\n                        }\n                        RenderLine::Text(tline)\n                    }\n                    RenderLine::Line(l) => {\n                        let mut tline = TaggedLine::new();\n                        tline.push(Str(TaggedString {\n                            s: prefix.to_string(),\n                            tag: tag.clone(),\n                        }));\n                        tline.push(Str(TaggedString {\n                            s: l.to_string(),\n                            tag: tag.clone(),\n                        }));\n                        RenderLine::Text(tline)\n                    }\n                }),\n        );\n\n        Ok(())\n    }\n\n    fn append_columns_with_borders<I>(&mut self, cols: I, collapse: bool) -> Result<()>\n    where\n        I: IntoIterator<Item = (Self, usize)>,\n        Self: Sized,\n    {\n        use self::TaggedLineElement::Str;\n        html_trace!(\"append_columns_with_borders(collapse={})\", collapse);\n        html_trace!(\"self=<<<\\n{}>>>\", self.to_string());\n\n        self.flush_wrapping()?;\n\n        let mut tot_width = 0;\n\n        let mut line_sets = cols\n            .into_iter()\n            .map(|(sub_r, rowspan)| {\n                let width = sub_r.width;\n                let pos = tot_width;\n                tot_width += width + 1;\n                html_trace!(\"Adding column:\\n{}\", sub_r.to_string());\n                Ok(LineSet {\n                    pos,\n                    width,\n                    rowspan,\n                    lines: sub_r\n                        .into_lines()?\n                        .into_iter()\n                        .map(|mut line| {\n                            match line {\n                                RenderLine::Text(ref mut tline) => {\n                                    tline.pad_to(width, &self.ann_stack);\n                                }\n                                RenderLine::Line(ref mut border) => {\n                                    border.stretch_to(width);\n                                }\n                            }\n                            line\n                        })\n                        .collect(),\n                })\n            })\n            .collect::<Result<Vec<LineSet<_>>>>()?;\n\n        {\n            // Character position from line sets.\n            let mut lidx = 0;\n            let mut lnextpos = 0;\n            for ls in std::mem::take(&mut self.overhang_cells) {\n                while lidx < line_sets.len() && line_sets[lidx].pos < ls.pos {\n                    let lpos = line_sets[lidx].pos;\n                    lnextpos = lpos + line_sets[lidx].width + 1;\n                    lidx += 1;\n                }\n                if lidx >= line_sets.len() {\n                    // This row doesn't extend this far.\n                    if lnextpos < ls.pos {\n                        // We need padding\n                        line_sets.push(LineSet {\n                            pos: lnextpos,\n                            width: ls.pos.saturating_sub(lnextpos + 1),\n                            rowspan: 1,\n                            lines: Default::default(),\n                        });\n                    }\n                    if ls.pos + ls.width > tot_width {\n                        // +1 because we subtract one later\n                        tot_width = ls.pos + ls.width + 1;\n                    }\n                    line_sets.push(ls);\n                } else {\n                    //debug_assert_eq!(ls.width, line_sets[lidx].width);\n                    line_sets[lidx] = ls;\n                }\n            }\n        }\n\n        // If we haven't added any columns, tot_width will be 0; otherwise\n        // it will be one too high for a final unneeded border.\n        tot_width = tot_width.saturating_sub(1);\n\n        let mut next_border = BorderHoriz::new(tot_width, self.ann_stack.clone());\n\n        // Join the vertical lines to all the borders\n        if let Some(RenderLine::Line(prev_border)) = self.lines.back_mut() {\n            let mut pos = 0;\n            html_trace!(\"Merging with last line:\\n{}\", prev_border.to_string());\n            for ls in &line_sets[..line_sets.len().saturating_sub(1)] {\n                let w = ls.width;\n                html_trace!(\"pos={}, w={}\", pos, w);\n                prev_border.join_below(pos + w);\n                next_border.join_above(pos + w);\n                pos += w + 1;\n            }\n            if let Some(ls) = line_sets.last() {\n                // Stretch the previous border if this row is wider.\n                prev_border.extend_to(pos + ls.width);\n            }\n        }\n\n        // If we're collapsing bottom borders, then the bottom border of a\n        // nested table is being merged into the bottom border of the\n        // containing cell.  If that cell happens not to be the tallest\n        // cell in the row, then we need to extend any vertical lines\n        // to the bottom.  We'll remember what to do when we update the\n        // containing border.\n        let mut column_padding = vec![None; line_sets.len()];\n\n        // If we're collapsing borders, do so.\n        if collapse {\n            html_trace!(\"Collapsing borders.\");\n            /* Collapse any top border */\n            let mut pos = 0;\n            for ls in &mut line_sets {\n                let w = ls.width;\n                let sublines = &mut ls.lines;\n                let starts_border = matches!(sublines.front(), Some(RenderLine::Line(_)));\n                if starts_border {\n                    html_trace!(\"Starts border\");\n                    if let &mut RenderLine::Line(ref mut prev_border) =\n                        self.lines.back_mut().expect(\"No previous line\")\n                    {\n                        if let Some(RenderLine::Line(line)) = sublines.pop_front() {\n                            html_trace!(\n                                \"prev border:\\n{}\\n, pos={}, line:\\n{}\",\n                                prev_border.to_string(),\n                                pos,\n                                line.to_string()\n                            );\n                            prev_border.merge_from_below(&line, pos);\n                        }\n                    } else {\n                        unreachable!();\n                    }\n                }\n                pos += w + 1;\n            }\n\n            /* Collapse any bottom border */\n            let mut pos = 0;\n            for (col_no, ls) in line_sets.iter_mut().enumerate() {\n                let w = ls.width;\n                let sublines = &mut ls.lines;\n                if let Some(RenderLine::Line(line)) = sublines.back() {\n                    html_trace!(\"Ends border\");\n                    next_border.merge_from_above(line, pos);\n                    column_padding[col_no] = Some(line.to_vertical_lines_above());\n                    sublines.pop_back();\n                }\n                pos += w + 1;\n            }\n        }\n\n        let cell_height = line_sets\n            .iter()\n            .map(|ls| ls.cell_height())\n            .max()\n            .unwrap_or(0);\n        let spaces: String = (0..tot_width).map(|_| ' ').collect();\n        // line_sets can be empty; if so last_cellno can safely be 0 as\n        // we won't use it.\n        let last_cellno = line_sets.len().saturating_sub(1);\n        for i in 0..cell_height {\n            let mut line = TaggedLine::new();\n            for (cellno, ls) in line_sets.iter_mut().enumerate() {\n                match ls.lines.get_mut(i) {\n                    Some(RenderLine::Text(tline)) => line.consume(tline),\n                    Some(RenderLine::Line(bord)) => line.push(Str(TaggedString {\n                        s: bord.to_string(),\n                        tag: self.ann_stack.clone(),\n                    })),\n                    None => line.push(Str(TaggedString {\n                        s: column_padding[cellno]\n                            .clone()\n                            .unwrap_or_else(|| spaces[0..ls.width].to_string()),\n                        tag: self.ann_stack.clone(),\n                    })),\n                }\n                if cellno != last_cellno {\n                    line.push_char(\n                        if self.options.draw_borders {\n                            '│'\n                        } else {\n                            ' '\n                        },\n                        &self.ann_stack,\n                    );\n                }\n            }\n            self.add_line(RenderLine::Text(line));\n        }\n        // Handle overhanging cells\n        {\n            let mut pos = 0;\n            for mut ls in line_sets.into_iter() {\n                if ls.rowspan > 1 {\n                    if let Some(l) = ls.lines.get(cell_height) {\n                        let mut tmppos = pos;\n                        for ts in l.clone().into_tagged_line() {\n                            let w = ts.width();\n                            next_border.add_text_span(tmppos, ts);\n                            tmppos += w;\n                        }\n                    } else {\n                        next_border.add_text_span(\n                            pos,\n                            TaggedLineElement::Str(TaggedString {\n                                s: \" \".repeat(ls.width),\n                                tag: next_border.tag.clone(),\n                            }),\n                        );\n                    }\n                    {\n                        // TODO: use VecDeque::truncate_front() when available.\n                        let new_len = ls.lines.len().saturating_sub(cell_height + 1);\n                        while ls.lines.len() > new_len {\n                            ls.lines.pop_front();\n                        }\n                    }\n                    self.overhang_cells.push(LineSet {\n                        pos: ls.pos,\n                        width: ls.width,\n                        rowspan: ls.rowspan - 1,\n                        lines: ls.lines,\n                    });\n                }\n                pos += ls.width + 1;\n            }\n        }\n        if self.options.draw_borders {\n            self.add_line(RenderLine::Line(next_border));\n        }\n        Ok(())\n    }\n\n    fn append_vert_row<I>(&mut self, cols: I) -> Result<()>\n    where\n        I: IntoIterator<Item = Self>,\n        Self: Sized,\n    {\n        html_trace!(\"append_vert_row()\");\n        html_trace!(\"self=\\n{}\", self.to_string());\n\n        self.flush_wrapping()?;\n\n        let width = self.width();\n\n        let mut first = true;\n        for col in cols {\n            if first {\n                first = false;\n            } else if self.options.draw_borders {\n                let border =\n                    BorderHoriz::new_type(width, BorderSegHoriz::HorizVert, self.ann_stack.clone());\n                self.add_horizontal_line(border)?;\n            }\n            self.append_subrender(col, std::iter::repeat(\"\"))?;\n        }\n        if self.options.draw_borders {\n            self.add_horizontal_border()?;\n        }\n        Ok(())\n    }\n\n    fn empty(&self) -> bool {\n        self.lines.is_empty()\n            && if let Some(wrapping) = &self.wrapping {\n                wrapping.is_empty()\n            } else {\n                true\n            }\n    }\n\n    fn start_link(&mut self, target: &str) -> Result<()> {\n        let (s, annotation) = self.decorator.decorate_link_start(target);\n        self.ann_stack.push(annotation);\n        self.add_inline_text(&s)\n    }\n    fn end_link(&mut self) -> Result<()> {\n        let s = self.decorator.decorate_link_end();\n        self.add_inline_text(&s)?;\n        self.ann_stack.pop();\n        Ok(())\n    }\n    fn start_emphasis(&mut self) -> Result<()> {\n        let (s, annotation) = self.decorator.decorate_em_start();\n        self.ann_stack.push(annotation);\n        self.add_inline_text(&s)\n    }\n    fn end_emphasis(&mut self) -> Result<()> {\n        let s = self.decorator.decorate_em_end();\n        self.add_inline_text(&s)?;\n        self.ann_stack.pop();\n        Ok(())\n    }\n    fn start_strong(&mut self) -> Result<()> {\n        let (s, annotation) = self.decorator.decorate_strong_start();\n        self.ann_stack.push(annotation);\n        self.add_inline_text(&s)\n    }\n    fn end_strong(&mut self) -> Result<()> {\n        let s = self.decorator.decorate_strong_end();\n        self.add_inline_text(&s)?;\n        self.ann_stack.pop();\n        Ok(())\n    }\n    fn start_strikeout(&mut self) -> Result<()> {\n        let (s, annotation) = self.decorator.decorate_strikeout_start();\n        self.ann_stack.push(annotation);\n        self.add_inline_text(&s)?;\n        if self.options.use_unicode_strikeout {\n            self.text_filter_stack.push(filter_text_strikeout);\n        }\n        Ok(())\n    }\n    fn end_strikeout(&mut self) -> Result<()> {\n        if self.options.use_unicode_strikeout {\n            self.text_filter_stack\n                .pop()\n                .expect(\"end_strikeout() called without a corresponding start_strokeout()\");\n        }\n        let s = self.decorator.decorate_strikeout_end();\n        self.add_inline_text(&s)?;\n        self.ann_stack.pop();\n        Ok(())\n    }\n    fn start_code(&mut self) -> Result<()> {\n        let (s, annotation) = self.decorator.decorate_code_start();\n        self.ann_stack.push(annotation);\n        self.add_inline_text(&s)?;\n        Ok(())\n    }\n    fn end_code(&mut self) -> Result<()> {\n        let s = self.decorator.decorate_code_end();\n        self.add_inline_text(&s)?;\n        self.ann_stack.pop();\n        Ok(())\n    }\n    fn add_image(&mut self, src: &str, title: &str) -> Result<()> {\n        let (s, tag) = match (title, self.options.img_mode) {\n            (\"\", ImageRenderMode::IgnoreEmpty) => {\n                return Ok(());\n            }\n            (\"\", ImageRenderMode::Filename) => {\n                let slash = src.rfind('/');\n                let sub_title = match slash {\n                    Some(pos) => &src[pos + 1..],\n                    None => src,\n                };\n                self.decorator.decorate_image(src, sub_title)\n            }\n            (\"\", ImageRenderMode::ShowAlways) => self.decorator.decorate_image(src, title),\n            (\"\", ImageRenderMode::Replace(s)) => self.decorator.decorate_image(src, s),\n            (title, _) => self.decorator.decorate_image(src, title),\n        };\n        self.ann_stack.push(tag);\n        self.add_inline_text(&s)?;\n        self.ann_stack.pop();\n        Ok(())\n    }\n\n    fn header_prefix(&mut self, level: usize) -> String {\n        self.decorator.header_prefix(level)\n    }\n\n    fn quote_prefix(&mut self) -> String {\n        self.decorator.quote_prefix()\n    }\n\n    fn unordered_item_prefix(&mut self) -> String {\n        self.decorator.unordered_item_prefix()\n    }\n\n    fn ordered_item_prefix(&mut self, i: i64) -> String {\n        self.decorator.ordered_item_prefix(i)\n    }\n\n    fn record_frag_start(&mut self, fragname: &str) {\n        use self::TaggedLineElement::FragmentStart;\n\n        get_wrapping_or_insert::<D>(\n            &mut self.wrapping,\n            &self.options,\n            self.width,\n            &self.ann_stack,\n        )\n        .add_element(FragmentStart(fragname.to_string()));\n    }\n\n    #[allow(unused)]\n    fn push_colour(&mut self, colour: Colour) {\n        if let Some(ann) = self.decorator.push_colour(colour) {\n            self.ann_stack.push(ann);\n        }\n    }\n\n    #[allow(unused)]\n    fn pop_colour(&mut self) {\n        if self.decorator.pop_colour() {\n            self.ann_stack.pop();\n        }\n    }\n\n    fn push_bgcolour(&mut self, colour: Colour) {\n        if let Some(ann) = self.decorator.push_bgcolour(colour) {\n            self.ann_stack.push(ann);\n        }\n    }\n\n    fn pop_bgcolour(&mut self) {\n        if self.decorator.pop_bgcolour() {\n            self.ann_stack.pop();\n        }\n    }\n\n    fn start_superscript(&mut self) -> Result<()> {\n        let (s, annotation) = self.decorator.decorate_superscript_start();\n        self.ann_stack.push(annotation);\n        self.add_inline_text(&s)?;\n        Ok(())\n    }\n    fn end_superscript(&mut self) -> Result<()> {\n        let s = self.decorator.decorate_superscript_end();\n        self.add_inline_text(&s)?;\n        self.ann_stack.pop();\n        Ok(())\n    }\n}\n\n/// A decorator for use with `SubRenderer` which outputs plain UTF-8 text\n/// with no annotations.  Markup is rendered as text characters or footnotes.\n#[derive(Clone, Debug)]\npub struct PlainDecorator {}\n\nimpl PlainDecorator {\n    /// Create a new `PlainDecorator`.\n    #[allow(clippy::new_without_default)]\n    pub fn new() -> PlainDecorator {\n        PlainDecorator {}\n    }\n}\n\nimpl TextDecorator for PlainDecorator {\n    type Annotation = ();\n\n    fn decorate_link_start(&mut self, _url: &str) -> (String, Self::Annotation) {\n        (\"[\".to_string(), ())\n    }\n\n    fn decorate_link_end(&mut self) -> String {\n        \"]\".to_string()\n    }\n\n    fn decorate_em_start(&self) -> (String, Self::Annotation) {\n        (\"\".to_string(), ())\n    }\n\n    fn decorate_em_end(&self) -> String {\n        \"\".to_string()\n    }\n\n    fn decorate_strong_start(&self) -> (String, Self::Annotation) {\n        (\"\".to_string(), ())\n    }\n\n    fn decorate_strong_end(&self) -> String {\n        \"\".to_string()\n    }\n\n    fn decorate_strikeout_start(&self) -> (String, Self::Annotation) {\n        (\"\".to_string(), ())\n    }\n\n    fn decorate_strikeout_end(&self) -> String {\n        \"\".to_string()\n    }\n\n    fn decorate_code_start(&self) -> (String, Self::Annotation) {\n        (\"\".to_string(), ())\n    }\n\n    fn decorate_code_end(&self) -> String {\n        \"\".to_string()\n    }\n\n    fn decorate_preformat_first(&self) -> Self::Annotation {}\n    fn decorate_preformat_cont(&self) -> Self::Annotation {}\n\n    fn decorate_image(&mut self, _src: &str, title: &str) -> (String, Self::Annotation) {\n        (format!(\"[{}]\", title), ())\n    }\n\n    fn header_prefix(&self, level: usize) -> String {\n        \"#\".repeat(level) + \" \"\n    }\n\n    fn quote_prefix(&self) -> String {\n        \"> \".to_string()\n    }\n\n    fn unordered_item_prefix(&self) -> String {\n        \"* \".to_string()\n    }\n\n    fn ordered_item_prefix(&self, i: i64) -> String {\n        format!(\"{}. \", i)\n    }\n\n    fn make_subblock_decorator(&self) -> Self {\n        self.clone()\n    }\n}\n\n/// A decorator for use with `SubRenderer` which outputs plain UTF-8 text\n/// with no annotations or markup, emitting only the literal text.\n#[derive(Clone, Debug)]\npub struct TrivialDecorator {}\n\nimpl TrivialDecorator {\n    /// Create a new `TrivialDecorator`.\n    #[allow(clippy::new_without_default)]\n    pub fn new() -> TrivialDecorator {\n        TrivialDecorator {}\n    }\n}\n\nimpl TextDecorator for TrivialDecorator {\n    type Annotation = ();\n\n    fn decorate_link_start(&mut self, _url: &str) -> (String, Self::Annotation) {\n        (\"\".to_string(), ())\n    }\n\n    fn decorate_link_end(&mut self) -> String {\n        \"\".to_string()\n    }\n\n    fn decorate_em_start(&self) -> (String, Self::Annotation) {\n        (\"\".to_string(), ())\n    }\n\n    fn decorate_em_end(&self) -> String {\n        \"\".to_string()\n    }\n\n    fn decorate_strong_start(&self) -> (String, Self::Annotation) {\n        (\"\".to_string(), ())\n    }\n\n    fn decorate_strong_end(&self) -> String {\n        \"\".to_string()\n    }\n\n    fn decorate_strikeout_start(&self) -> (String, Self::Annotation) {\n        (\"\".to_string(), ())\n    }\n\n    fn decorate_strikeout_end(&self) -> String {\n        \"\".to_string()\n    }\n\n    fn decorate_code_start(&self) -> (String, Self::Annotation) {\n        (\"\".to_string(), ())\n    }\n\n    fn decorate_code_end(&self) -> String {\n        \"\".to_string()\n    }\n\n    fn decorate_preformat_first(&self) -> Self::Annotation {}\n    fn decorate_preformat_cont(&self) -> Self::Annotation {}\n\n    fn decorate_image(&mut self, _src: &str, title: &str) -> (String, Self::Annotation) {\n        // FIXME: this should surely be the alt text, not the title text\n        (title.to_string(), ())\n    }\n\n    fn header_prefix(&self, _level: usize) -> String {\n        \"\".to_string()\n    }\n\n    fn quote_prefix(&self) -> String {\n        \"\".to_string()\n    }\n\n    fn unordered_item_prefix(&self) -> String {\n        \"\".to_string()\n    }\n\n    fn ordered_item_prefix(&self, _i: i64) -> String {\n        \"\".to_string()\n    }\n\n    fn make_subblock_decorator(&self) -> Self {\n        TrivialDecorator::new()\n    }\n}\n\n/// A decorator to generate rich text (styled) rather than\n/// pure text output.\n#[derive(Clone, Debug)]\npub struct RichDecorator {}\n\n/// Annotation type for \"rich\" text.  Text is associated with a set of\n/// these.\n#[derive(PartialEq, Eq, Clone, Debug, Default)]\n#[non_exhaustive]\npub enum RichAnnotation {\n    /// Normal text.\n    #[default]\n    Default,\n    /// A link with the target.\n    Link(String),\n    /// An image with its src (this tag is attached to the title text)\n    Image(String),\n    /// Emphasised text, which might be rendered in bold or another colour.\n    Emphasis,\n    /// Strong text, which might be rendered in bold or another colour.\n    Strong,\n    /// Stikeout text\n    Strikeout,\n    /// Code\n    Code,\n    /// Preformatted; true if a continuation line for an overly-long line.\n    Preformat(bool),\n    /// Colour information\n    Colour(crate::Colour),\n    /// Background Colour information\n    BgColour(crate::Colour),\n}\n\nimpl RichDecorator {\n    /// Create a new `RichDecorator` with the default settings.\n    #[allow(clippy::new_without_default)]\n    pub fn new() -> RichDecorator {\n        RichDecorator {}\n    }\n}\n\nimpl TextDecorator for RichDecorator {\n    type Annotation = RichAnnotation;\n\n    fn decorate_link_start(&mut self, url: &str) -> (String, Self::Annotation) {\n        (\"\".to_string(), RichAnnotation::Link(url.to_string()))\n    }\n\n    fn decorate_link_end(&mut self) -> String {\n        \"\".to_string()\n    }\n\n    fn decorate_em_start(&self) -> (String, Self::Annotation) {\n        (\"\".to_string(), RichAnnotation::Emphasis)\n    }\n\n    fn decorate_em_end(&self) -> String {\n        \"\".to_string()\n    }\n\n    fn decorate_strong_start(&self) -> (String, Self::Annotation) {\n        (\"\".to_string(), RichAnnotation::Strong)\n    }\n\n    fn decorate_strong_end(&self) -> String {\n        \"\".to_string()\n    }\n\n    fn decorate_strikeout_start(&self) -> (String, Self::Annotation) {\n        (\"\".to_string(), RichAnnotation::Strikeout)\n    }\n\n    fn decorate_strikeout_end(&self) -> String {\n        \"\".to_string()\n    }\n\n    fn decorate_code_start(&self) -> (String, Self::Annotation) {\n        (\"\".to_string(), RichAnnotation::Code)\n    }\n\n    fn decorate_code_end(&self) -> String {\n        \"\".to_string()\n    }\n\n    fn decorate_preformat_first(&self) -> Self::Annotation {\n        RichAnnotation::Preformat(false)\n    }\n\n    fn decorate_preformat_cont(&self) -> Self::Annotation {\n        RichAnnotation::Preformat(true)\n    }\n\n    fn decorate_image(&mut self, src: &str, title: &str) -> (String, Self::Annotation) {\n        (title.to_string(), RichAnnotation::Image(src.to_string()))\n    }\n\n    fn header_prefix(&self, level: usize) -> String {\n        \"#\".repeat(level) + \" \"\n    }\n\n    fn quote_prefix(&self) -> String {\n        \"> \".to_string()\n    }\n\n    fn unordered_item_prefix(&self) -> String {\n        \"* \".to_string()\n    }\n\n    fn ordered_item_prefix(&self, i: i64) -> String {\n        format!(\"{}. \", i)\n    }\n\n    fn make_subblock_decorator(&self) -> Self {\n        self.clone()\n    }\n\n    fn push_colour(&mut self, colour: Colour) -> Option<Self::Annotation> {\n        Some(RichAnnotation::Colour(colour))\n    }\n\n    fn pop_colour(&mut self) -> bool {\n        true\n    }\n\n    fn push_bgcolour(&mut self, colour: Colour) -> Option<Self::Annotation> {\n        Some(RichAnnotation::BgColour(colour))\n    }\n\n    fn pop_bgcolour(&mut self) -> bool {\n        true\n    }\n}\n"
  },
  {
    "path": "src/tests.rs",
    "content": "use std::str;\n\nuse crate::config::Config;\nuse crate::render::TaggedLineElement;\nuse crate::render::text_renderer::{PlainDecorator, TaggedString};\nuse crate::{Error, config};\n\nuse super::render::text_renderer::{RichAnnotation, RichDecorator, TaggedLine, TrivialDecorator};\nuse super::{TextDecorator, from_read, from_read_with_decorator, parse};\n\n/// Like assert_eq!(), but prints out the results normally as well\nmacro_rules! assert_eq_str {\n    ($a:expr_2021, $b:expr_2021) => {\n        if $a != $b {\n            println!(\"<<<\\n{}===\\n{}>>>\", $a, $b);\n            assert_eq!($a, $b);\n        }\n    };\n}\n#[track_caller]\nfn test_html(input: &[u8], expected: &str, width: usize) {\n    let output = from_read(input, width).unwrap();\n    assert_eq_str!(output, expected);\n}\n#[track_caller]\nfn test_html_conf<F>(input: &[u8], expected: &str, width: usize, conf: F)\nwhere\n    F: Fn(Config<PlainDecorator>) -> Config<PlainDecorator>,\n{\n    let result = conf(config::plain())\n        .string_from_read(input, width)\n        .unwrap();\n    assert_eq_str!(result, expected);\n}\n#[track_caller]\nfn test_html_conf_dec<D: TextDecorator, F>(\n    decorator: D,\n    input: &[u8],\n    expected: &str,\n    width: usize,\n    conf: F,\n) where\n    F: Fn(Config<D>) -> Config<D>,\n{\n    let result = conf(config::with_decorator(decorator))\n        .string_from_read(input, width)\n        .unwrap();\n    assert_eq_str!(result, expected);\n}\n#[track_caller]\nfn test_html_maxwrap(input: &[u8], expected: &str, width: usize, wrap_width: usize) {\n    test_html_conf(input, expected, width, |conf| {\n        conf.max_wrap_width(wrap_width)\n    })\n}\n#[cfg(feature = \"css\")]\nfn test_html_css(input: &[u8], expected: &str, width: usize) {\n    let result = config::plain()\n        .use_doc_css()\n        .string_from_read(input, width)\n        .unwrap();\n    assert_eq_str!(result, expected);\n}\n#[cfg(feature = \"css\")]\nfn test_colour_map(annotations: &[RichAnnotation], s: &str) -> String {\n    let mut tags = (\"\", \"\");\n    let mut bgtags = (\"\", \"\");\n    for ann in annotations {\n        match ann {\n            RichAnnotation::Colour(c) => match c {\n                crate::Colour {\n                    r: 0xff,\n                    g: 0,\n                    b: 0,\n                } => {\n                    tags = (\"<R>\", \"</R>\");\n                }\n                crate::Colour {\n                    r: 0xff,\n                    g: 0xff,\n                    b: 0xff,\n                } => {\n                    tags = (\"<W>\", \"</W>\");\n                }\n                crate::Colour {\n                    r: 0,\n                    g: 0xff,\n                    b: 0,\n                } => {\n                    tags = (\"<G>\", \"</G>\");\n                }\n                crate::Colour {\n                    r: 0,\n                    g: 0,\n                    b: 0xff,\n                } => {\n                    tags = (\"<B>\", \"</B>\");\n                }\n                _ => {\n                    tags = (\"<?>\", \"</?>\");\n                }\n            },\n            RichAnnotation::BgColour(c) => match c {\n                crate::Colour {\n                    r: 0xff,\n                    g: 0,\n                    b: 0,\n                } => {\n                    bgtags = (\"<r>\", \"</r>\");\n                }\n                crate::Colour {\n                    r: 0,\n                    g: 0xff,\n                    b: 0,\n                } => {\n                    bgtags = (\"<g>\", \"</g>\");\n                }\n                _ => {\n                    bgtags = (\"<.>\", \"</.>\");\n                }\n            },\n            _ => (),\n        }\n    }\n    format!(\"{}{}{}{}{}\", bgtags.0, tags.0, s, tags.1, bgtags.1)\n}\n\n#[cfg(feature = \"css\")]\n#[track_caller]\nfn test_html_coloured_conf<F>(input: &[u8], expected: &str, width: usize, conf: F)\nwhere\n    F: Fn(Config<RichDecorator>) -> Config<RichDecorator>,\n{\n    let result = conf(config::rich().use_doc_css())\n        .coloured(input, width, test_colour_map)\n        .unwrap();\n    assert_eq_str!(result, expected);\n}\n#[cfg(feature = \"css\")]\n#[track_caller]\nfn test_html_coloured(input: &[u8], expected: &str, width: usize) {\n    test_html_coloured_conf(input, expected, width, |c| c)\n}\n#[track_caller]\nfn test_html_err_conf<F>(input: &[u8], expected: Error, width: usize, conf: F)\nwhere\n    F: Fn(Config<PlainDecorator>) -> Config<PlainDecorator>,\n{\n    let result = conf(config::plain()).string_from_read(input, width);\n    match result {\n        Err(e) => {\n            assert_eq!(e, expected);\n        }\n        Ok(text) => {\n            panic!(\"Expected error, got: [[{}]]\", text);\n        }\n    }\n}\nfn test_html_err(input: &[u8], expected: Error, width: usize) {\n    test_html_err_conf(input, expected, width, |c| c)\n}\n\n#[cfg(feature = \"css\")]\n#[track_caller]\nfn test_html_style(input: &[u8], style: &str, expected: &str, width: usize) {\n    let result = config::plain()\n        .add_css(style)\n        .unwrap()\n        .string_from_read(input, width)\n        .unwrap();\n    assert_eq_str!(result, expected);\n}\n\n#[track_caller]\nfn test_html_decorator<D>(input: &[u8], expected: &str, width: usize, decorator: D)\nwhere\n    D: TextDecorator,\n{\n    let output = from_read_with_decorator(input, width, decorator).unwrap();\n    assert_eq_str!(output, expected);\n}\n\n#[cfg(feature = \"css_ext\")]\n#[track_caller]\nfn test_html_conf_rendertree<F>(input: &[u8], conf: F, expected: crate::RenderTree)\nwhere\n    F: Fn(Config<RichDecorator>) -> Config<RichDecorator>,\n{\n    let conf = conf(config::rich());\n    let dom = conf.parse_html(input).expect(\"Failed to parse HTML\");\n    let rt = conf\n        .dom_to_render_tree(&dom)\n        .expect(\"Failed to get render tree\");\n    assert_eq_str!(rt.to_string(), expected.to_string());\n}\n\n#[cfg(feature = \"xml\")]\n#[track_caller]\nfn test_xml(input: &[u8], expected: &str, width: usize) {\n    let conf = config::plain();\n    let dom = conf.parse_xml(input).expect(\"Failed to parse XHTML\");\n    let rt = conf.dom_to_render_tree(&dom).expect(\"To Render Tree\");\n    let output = conf.render_to_string(rt, width).expect(\"Render to string\");\n\n    assert_eq_str!(output, expected);\n}\n\n#[test]\nfn test_table() {\n    test_html(\n        br##\"\n   <table>\n     <tr>\n       <td>1</td>\n       <td>2</td>\n       <td>3</td>\n     </tr>\n   </table>\n\"##,\n        r#\"─┬─┬─\n1│2│3\n─┴─┴─\n\"#,\n        12,\n    );\n}\n\n#[test]\nfn test_table2() {\n    test_html(\n        br##\"\n   <table>\n     <tr>\n       <td>1</td>\n       <td>2</td>\n       <td>3</td>\n     </tr>\n     <tr>\n       <td>4</td>\n       <td>5</td>\n       <td>6</td>\n     </tr>\n   </table>\n\"##,\n        r#\"─┬─┬─\n1│2│3\n─┼─┼─\n4│5│6\n─┴─┴─\n\"#,\n        12,\n    );\n}\n\n#[test]\nfn test_thead() {\n    test_html(\n        br##\"\n   <table>\n     <thead>\n       <tr>\n         <th>Col1</th>\n         <th>Col2</th>\n         <th>Col3</th>\n       </tr>\n     </thead>\n     <tbody>\n       <tr>\n         <td>1</td>\n         <td>2</td>\n         <td>3</td>\n       </tr>\n     </tbody>\n   </table>\n\"##,\n        r#\"────┬────┬────\nCol1│Col2│Col3\n────┼────┼────\n1   │2   │3   \n────┴────┴────\n\"#,\n        15,\n    );\n}\n\n#[test]\nfn test_colspan() {\n    test_html(\n        br##\"\n   <table>\n     <tr>\n       <td>1</td>\n       <td>2</td>\n       <td>3</td>\n     </tr>\n     <tr>\n       <td colspan=\"2\">12</td>\n       <td>3</td>\n     </tr>\n     <tr>\n       <td>1</td>\n       <td colspan=\"2\">23</td>\n     </tr>\n   </table>\n\"##,\n        r#\"─┬─┬─\n1│2│3\n─┴─┼─\n12 │3\n─┬─┴─\n1│23 \n─┴───\n\"#,\n        12,\n    );\n}\n\n#[test]\nfn test_colspan_zero() {\n    test_html(\n        br##\"\n   <table>\n     <tr>\n       <td>1</td>\n       <td>2</td>\n       <td>3</td>\n     </tr>\n     <tr>\n       <td colspan=\"2\">12</td>\n       <td>3</td>\n     </tr>\n     <tr>\n       <td>1</td>\n       <td colspan=\"0\">23</td>\n     </tr>\n   </table>\n\"##,\n        r#\"─┬─┬─\n1│2│3\n─┴─┼─\n12 │3\n─┬─┴─\n1│23 \n─┴───\n\"#,\n        12,\n    );\n}\n\n#[test]\nfn test_colspan_large() {\n    test_html(\n        br##\"\n   <table>\n     <tr>\n       <td>1</td>\n       <td>2</td>\n       <td>3</td>\n     </tr>\n     <tr>\n       <td colspan=\"2\">12</td>\n       <td>3</td>\n     </tr>\n     <tr>\n       <td>1</td>\n       <td colspan=\"99\">23</td>\n     </tr>\n   </table>\n\"##,\n        r#\"─┬─┬─\n1│2│3\n─┴─┼─\n12 │3\n─┬─┴──\n1│23  \n─┴────\n\"#,\n        12,\n    );\n}\n\n#[test]\nfn test_colspan_larger() {\n    test_html(\n        br##\"\n   <table>\n     <tr>\n       <td colspan=\"50\">1</td>\n       <td colspan=\"50\">2</td>\n       <td colspan=\"50\">3</td>\n     </tr>\n     <tr>\n       <td colspan=\"100\">12</td>\n       <td colspan=\"50\">3</td>\n     </tr>\n     <tr>\n       <td colspan=\"50\">1</td>\n       <td colspan=\"100\">23</td>\n     </tr>\n   </table>\n\"##,\n        r#\"─┬─┬─\n1│2│3\n─┴─┼─\n12 │3\n─┬─┴─\n1│23 \n─┴───\n\"#,\n        12,\n    );\n}\n\n#[test]\nfn test_para() {\n    test_html(&b\"<p>Hello</p>\"[..], \"Hello\\n\", 10);\n}\n\n#[test]\nfn test_para2() {\n    test_html(&b\"<p>Hello, world!</p>\"[..], \"Hello, world!\\n\", 20);\n}\n\n#[test]\nfn test_blockquote() {\n    test_html(\n        &br#\"<p>Hello</p>\n    <blockquote>One, two, three</blockquote>\n    <p>foo</p>\n\"#[..],\n        r#\"Hello\n\n> One, two,\n> three\n\nfoo\n\"#,\n        12,\n    );\n}\n\n#[test]\nfn test_ul() {\n    test_html(\n        br#\"\n        <ul>\n          <li>Item one</li>\n          <li>Item two</li>\n          <li>Item three</li>\n        </ul>\n     \"#,\n        r#\"* Item one\n* Item two\n* Item\n  three\n\"#,\n        10,\n    );\n}\n\n#[test]\nfn test_ol1() {\n    test_html(\n        br#\"\n        <ol>\n          <li>Item one</li>\n          <li>Item two</li>\n          <li>Item three</li>\n        </ol>\n     \"#,\n        r#\"1. Item one\n2. Item two\n3. Item\n   three\n\"#,\n        11,\n    );\n}\n\n#[test]\nfn test_ol2() {\n    test_html(\n        br#\"\n        <ol>\n          <li>Item one</li>\n          <li>Item two</li>\n          <li>Item three</li>\n          <li>Item four</li>\n          <li>Item five</li>\n          <li>Item six</li>\n          <li>Item seven</li>\n          <li>Item eight</li>\n          <li>Item nine</li>\n          <li>Item ten</li>\n        </ol>\n     \"#,\n        r#\"1.  Item one\n2.  Item two\n3.  Item three\n4.  Item four\n5.  Item five\n6.  Item six\n7.  Item seven\n8.  Item eight\n9.  Item nine\n10. Item ten\n\"#,\n        20,\n    );\n}\n\n#[test]\nfn test_ol_start() {\n    test_html(\n        br#\"\n        <ol start=\"3\">\n          <li>Item three</li>\n          <li>Item four</li>\n        </ol>\n     \"#,\n        r#\"3. Item three\n4. Item four\n\"#,\n        20,\n    );\n}\n\n#[test]\nfn test_ol_start_9() {\n    test_html(\n        br#\"\n        <ol start=\"9\">\n          <li>Item nine</li>\n          <li>Item ten</li>\n        </ol>\n     \"#,\n        r#\"9.  Item nine\n10. Item ten\n\"#,\n        20,\n    );\n}\n\n#[test]\nfn test_ol_start_neg() {\n    test_html(\n        br#\"\n        <ol start=\"-1\">\n          <li>Item minus one</li>\n          <li>Item zero</li>\n          <li>Item one</li>\n        </ol>\n     \"#,\n        r#\"-1. Item minus one\n0.  Item zero\n1.  Item one\n\"#,\n        20,\n    );\n}\n\n#[test]\nfn test_strip_nl() {\n    test_html(\n        br#\"\n        <p>\n           One\n           Two\n           Three\n        </p>\n     \"#,\n        \"One Two Three\\n\",\n        40,\n    );\n}\n#[test]\nfn test_strip_nl2() {\n    test_html(\n        br#\"\n        <p>\n           One\n           <span>\n               Two\n           </span>\n           Three\n        </p>\n     \"#,\n        \"One Two Three\\n\",\n        40,\n    );\n}\n#[test]\nfn test_strip_nl_tbl() {\n    test_html(\n        br#\"\n       <table>\n         <tr>\n            <td>\n               One\n               <span>\n                   Two\n               </span>\n               Three\n            </td>\n          </tr>\n        </table>\n     \"#,\n        r\"──────────────\nOne Two Three \n──────────────\n\",\n        20,\n    );\n}\n#[test]\nfn test_unknown_element() {\n    test_html(\n        br#\"\n       <foo>\n       <table>\n         <tr>\n            <td>\n               One\n               <span><yyy>\n                   Two\n               </yyy></span>\n               Three\n            </td>\n          </tr>\n        </table>\n        </foo>\n     \"#,\n        r\"──────────────\nOne Two Three \n──────────────\n\",\n        20,\n    );\n}\n#[test]\nfn test_strip_nl_tbl_p() {\n    test_html(\n        br#\"\n       <table>\n         <tr>\n            <td><p>\n               One\n               <span>\n                   Two\n               </span>\n               Three\n            </p></td>\n          </tr>\n        </table>\n     \"#,\n        r\"──────────────\nOne Two Three \n──────────────\n\",\n        20,\n    );\n}\n#[test]\nfn test_pre() {\n    test_html(\n        br#\"\n       <pre>foo\nbar\nwib   asdf;\n</pre>\n<p>Hello</p>\n     \"#,\n        r\"foo\nbar\nwib   asdf;\n\nHello\n\",\n        20,\n    );\n}\n\n#[test]\nfn test_link() {\n    test_html(\n        br#\"\n       <p>Hello, <a href=\"http://www.example.com/\">world</a></p>\"#,\n        r\"Hello, [world][1]\n\n[1]: http://www.example.com/\n\",\n        80,\n    );\n}\n#[test]\nfn test_link2() {\n    test_html(\n        br#\"\n       <p>Hello, <a href=\"http://www.example.com/\">world</a>!</p>\"#,\n        r\"Hello, [world][1]!\n\n[1]: http://www.example.com/\n\",\n        80,\n    );\n}\n\n#[test]\nfn test_link3() {\n    test_html(\n        br#\"\n       <p>Hello, <a href=\"http://www.example.com/\">w</a>orld</p>\"#,\n        r\"Hello, [w][1]orld\n\n[1]: http://www.example.com/\n\",\n        80,\n    );\n}\n\n#[test]\nfn test_link_wrap() {\n    test_html(\n        br#\"\n       <a href=\"http://www.example.com/\">Hello</a>\"#,\n        r\"[Hello][1]\n\n[1]: http:\n//www.exam\nple.com/\n\",\n        10,\n    );\n}\n\n#[test]\nfn test_links_footnotes() {\n    // Default plain includes footnotes\n    test_html(\n        br#\"\n       <p>Hello, <a href=\"http://www.example.com/\">world</a></p>\"#,\n        r\"Hello, [world][1]\n\n[1]: http://www.example.com/\n\",\n        80,\n    );\n\n    // Can disable footnotes\n    test_html_conf(\n        br#\"\n       <p>Hello, <a href=\"http://www.example.com/\">world</a></p>\"#,\n        r\"Hello, [world]\n\",\n        80,\n        |conf| conf.link_footnotes(false),\n    );\n}\n\n#[test]\nfn test_links_footnotes_trivial() {\n    // Trivial decorate does footnotes if enabled\n    test_html_conf_dec(\n        TrivialDecorator::new(),\n        br#\"\n       <p>Hello, <a href=\"http://www.example.com/\">world</a></p>\"#,\n        r\"Hello, world[1]\n\n[1]: http://www.example.com/\n\",\n        80,\n        |conf| conf.link_footnotes(true),\n    );\n\n    // But by default doesn't\n    test_html_conf_dec(\n        TrivialDecorator::new(),\n        br#\"\n       <p>Hello, <a href=\"http://www.example.com/\">world</a></p>\"#,\n        r\"Hello, world\n\",\n        80,\n        |conf| conf,\n    );\n}\n\n#[test]\nfn test_links_footnotes_rich() {\n    // Rich decorator  does include footnotes if enabled\n    test_html_conf_dec(\n        RichDecorator::new(),\n        br#\"\n       <p>Hello, <a href=\"http://www.example.com/\">world</a></p>\"#,\n        r\"Hello, world[1]\n\n[1]: http://www.example.com/\n\",\n        80,\n        |conf| conf.link_footnotes(true),\n    );\n\n    // But by default doesn't\n    test_html_conf_dec(\n        RichDecorator::new(),\n        br#\"\n       <p>Hello, <a href=\"http://www.example.com/\">world</a></p>\"#,\n        r\"Hello, world\n\",\n        80,\n        |conf| conf,\n    );\n}\n\n#[test]\nfn test_wrap() {\n    test_html(\n        br\"<p>Hello, world.  Superlongwordreally</p>\",\n        r#\"Hello,\nworld.\nSuperlon\ngwordrea\nlly\n\"#,\n        8,\n    );\n}\n\n#[test]\nfn test_wrap2() {\n    test_html(\n        br\"<p>Hello, world.  This is a long sentence with a\nfew words, which we want to be wrapped correctly.</p>\",\n        r#\"Hello, world. This\nis a long sentence\nwith a few words,\nwhich we want to be\nwrapped correctly.\n\"#,\n        20,\n    );\n}\n\n#[test]\nfn test_wrap3() {\n    test_html(\n        br#\"<p><a href=\"dest\">http://example.org/blah/</a> one two three\"#,\n        r#\"[http://example.org/blah/\n][1] one two three\n\n[1]: dest\n\"#,\n        25,\n    );\n}\n\n#[test]\nfn test_wrap4() {\n    test_html(\n        br#\"<table><tr><td colspan=\"2\"><p>Hello, this should be wrapped.</p></table>\"#,\n        r#\"──────────\nHello,    \nthis      \nshould be \nwrapped.  \n──────────\n\"#,\n        10,\n    );\n}\n\n#[test]\nfn test_wrap_max() {\n    test_html_maxwrap(\n        br#\"\n        <p>This is a bit of text to wrap<p>\n        <ul>\n          <li>This is a bit of text to wrap too</li>\n          </li>\n        </ul>\"#,\n        r#\"This is a\nbit of\ntext to\nwrap\n\n* This is a\n  bit of\n  text to\n  wrap too\n\"#,\n        20,\n        10,\n    )\n}\n\n#[test]\nfn test_wrap_max2() {\n    test_html_maxwrap(\n        br#\"\n        <p>plain para at the full screen width</p>\n        <ul>\n          <li>bullet point uses same width so its margin is 2 chars further right\n\n          <ul><li>nested bullets in turn move 2 chars right each time\n             <ul><li>result: you never get text squashed too narrow</li></ul>\n          </li></ul>\n        </li></ul>\"#,\n        r#\"plain para at the\nfull screen width\n* bullet point uses\n  same width so its\n  margin is 2 chars\n  further right\n  * nested bullets in\n    turn move 2 chars\n    right each time\n    * result: you never\n      get text squashed\n      too narrow\n\"#,\n        80,\n        17,\n    );\n}\n\n#[test]\nfn test_wrap_nbsp_unicode() {\n    // Use nbsp unicode character\n    test_html(\n        b\"<p>Don't break a line between the words Foo\\xc2\\xa0and\\xc2\\xa0Bar</p>\",\n        \"Don't break a line between the words\nFoo\\u{a0}and\\u{a0}Bar\n\",\n        40,\n    );\n}\n\n#[test]\nfn test_wrap_nbsp_ent() {\n    // Use `&nbsp;` HTML entity\n    test_html(\n        br#\"<p>Don't break a line between the words Foo&nbsp;and&nbsp;Bar</p>\"#,\n        \"Don't break a line between the words\nFoo\\u{a0}and\\u{a0}Bar\n\",\n        40,\n    );\n}\n\n#[test]\nfn test_dowrap_unicode() {\n    // Use Unicode nbsp\n    test_html(\n        b\"<p>Do break a line somewhere in foo\\xe2\\x80\\x8bbar\\xe2\\x80\\x8bfoo\\xe2\\x80\\x8bbar\\xe2\\x80\\x8bfoo\\xe2\\x80\\x8bbar\\xe2\\x80\\x8bfoo\\xe2\\x80\\x8bbar\\xe2\\x80\\x8bfoo\\xe2\\x80\\x8bbar\\xe2\\x80\\x8bfoo\\xe2\\x80\\x8bbar</p>\",\n        r#\"Do break a line somewhere in foobarfoo\nbarfoobarfoobarfoobarfoobar\n\"#,\n        40,\n    );\n}\n\n#[test]\nfn test_dowrap_wbr() {\n    // Use `<wbr>` HTML element\n    test_html(\n        b\"<p>Do break a line somewhere in foo<wbr>bar<wbr>foo<wbr>bar<wbr>foo<wbr>bar<wbr>foo<wbr>bar<wbr>foo<wbr>bar<wbr>foo<wbr>bar</p>\",\n        r#\"Do break a line somewhere in foobarfoo\nbarfoobarfoobarfoobarfoobar\n\"#,\n        40,\n    );\n}\n\n#[test]\nfn test_nested_ul() {\n    test_html(\n        br\"\n    <ul>\n      <li>Item 1</li>\n      <li>Item 2\n      <ul>\n        <li>SubItem 2.1</li>\n        <li>SubItem 2.2\n          <ul>\n            <li>Sub Item 2.2.1</li>\n          </ul>\n        </li>\n      </ul>\n    </ul>\",\n        r#\"* Item 1\n* Item 2\n  * SubItem 2.1\n  * SubItem 2.2\n    * Sub Item 2.2.1\n\"#,\n        80,\n    );\n}\n\n#[test]\nfn test_nested_ol() {\n    test_html(\n        br\"\n    <ol>\n      <li>Item 1</li>\n      <li>Item 2\n      <ol>\n        <li>SubItem 2.1</li>\n        <li>SubItem 2.2\n          <ol>\n            <li>Sub Item 2.2.1</li>\n          </ol>\n        </li>\n      </ol>\n    </ol>\",\n        r#\"1. Item 1\n2. Item 2\n   1. SubItem 2.1\n   2. SubItem 2.2\n      1. Sub Item 2.2.1\n\"#,\n        80,\n    );\n}\n\n#[test]\nfn test_wrap_word_boundaries() {\n    test_html(br#\"Hello there boo\"#, \"Hello there boo\\n\", 20);\n    test_html(br#\"Hello there boo\"#, \"Hello there boo\\n\", 15);\n    test_html(br#\"Hello there boo\"#, \"Hello there\\nboo\\n\", 14);\n    test_html(br#\"Hello there boo\"#, \"Hello there\\nboo\\n\", 13);\n    test_html(br#\"Hello there boo\"#, \"Hello there\\nboo\\n\", 12);\n    test_html(br#\"Hello there boo\"#, \"Hello there\\nboo\\n\", 11);\n    test_html(br#\"Hello there boo\"#, \"Hello\\nthere boo\\n\", 10);\n    test_html(br#\"Hello there boo\"#, \"Hello\\nthere\\nboo\\n\", 6);\n    test_html(br#\"Hello there boo\"#, \"Hello\\nthere\\nboo\\n\", 5);\n    test_html(br#\"Hello there boo\"#, \"Hell\\no\\nther\\ne\\nboo\\n\", 4);\n    test_html(\n        br#\"Hello there boo\"#,\n        \"H\\ne\\nl\\nl\\no\\nt\\nh\\ne\\nr\\ne\\nb\\no\\no\\n\",\n        1,\n    );\n    test_html(br#\"Hello <em>there</em> boo\"#, \"Hello *there* boo\\n\", 20);\n    test_html(br#\"Hello <em>there</em> boo\"#, \"Hello *there*\\nboo\\n\", 15);\n    test_html(br#\"Hello <em>there</em> boo\"#, \"Hello *there*\\nboo\\n\", 14);\n    test_html(br#\"Hello <em>there</em> boo\"#, \"Hello *there*\\nboo\\n\", 13);\n    test_html(br#\"Hello <em>there</em> boo\"#, \"Hello\\n*there* boo\\n\", 12);\n    test_html(br#\"Hello <em>there</em> boo\"#, \"Hello\\n*there* boo\\n\", 11);\n    test_html(br#\"Hello <em>there</em> boo\"#, \"Hello\\n*there*\\nboo\\n\", 10);\n    test_html(br#\"Hello <em>there</em> boo\"#, \"Hello\\n*there\\n* boo\\n\", 6);\n    test_html(br#\"Hello <em>there</em> boo\"#, \"Hello\\n*ther\\ne*\\nboo\\n\", 5);\n    test_html(\n        br#\"Hello <em>there</em> boo\"#,\n        \"Hell\\no\\n*the\\nre*\\nboo\\n\",\n        4,\n    );\n    test_html(\n        br#\"Hello <em>there</em> boo\"#,\n        \"H\\ne\\nl\\nl\\no\\n*\\nt\\nh\\ne\\nr\\ne\\n*\\nb\\no\\no\\n\",\n        1,\n    );\n}\n\n#[test]\nfn test_div() {\n    test_html(\n        br\"<p>Hello</p><div>Div</div>\",\n        r#\"Hello\n\nDiv\n\"#,\n        20,\n    );\n    test_html(\n        br\"<p>Hello</p><div>Div</div><div>Div2</div>\",\n        r#\"Hello\n\nDiv\nDiv2\n\"#,\n        20,\n    );\n}\n\n#[test]\nfn test_img_alt() {\n    test_html(\n        br\"<p>Hello <img src='foo.jpg' alt='world'></p>\",\n        \"Hello [world]\\n\",\n        80,\n    );\n}\n\n#[test]\nfn test_img_noalt() {\n    test_html(br\"<p>Hello x<img src='foo.jpg'>y</p>\", \"Hello xy\\n\", 80);\n    test_html(\n        br\"<p>Hello x<img src='foo.jpg' alt=''>y</p>\",\n        \"Hello xy\\n\",\n        80,\n    );\n    test_html_conf(\n        br\"<p>Hello x<img src='foo.jpg' alt=''>y</p>\",\n        \"Hello x[foo.jpg]y\\n\",\n        80,\n        |conf| conf.empty_img_mode(config::ImageRenderMode::Filename),\n    );\n    test_html_conf(\n        br\"<p>Hello x<img src='http://www.example.com/path/foo.jpg' alt=''>y</p>\",\n        \"Hello x[foo.jpg]y\\n\",\n        80,\n        |conf| conf.empty_img_mode(config::ImageRenderMode::Filename),\n    );\n    test_html_conf(\n        br\"<p>Hello x<img src='foo.jpg'>y</p>\",\n        \"Hello x[]y\\n\",\n        80,\n        |conf| conf.empty_img_mode(config::ImageRenderMode::ShowAlways),\n    );\n    test_html_conf(\n        br\"<p>Hello x<img src='foo.jpg'>y</p>\",\n        \"Hello x[XYZ]y\\n\",\n        80,\n        |conf| conf.empty_img_mode(config::ImageRenderMode::Replace(\"XYZ\")),\n    );\n}\n\n#[test]\nfn test_img_nosrc() {\n    test_html(br\"<p>Hello x<img alt='myalt'>y</p>\", \"Hello xy\\n\", 80);\n}\n\n#[test]\nfn test_svg() {\n    test_html(\n        br\"<p>Hello <svg><title>world</title></svg></p>\",\n        \"Hello [world]\\n\",\n        80,\n    );\n    test_html(\n        br\"<p>Hello<svg><style>blah</style></svg></p>\",\n        \"Hello\\n\",\n        80,\n    );\n    test_html(br\"<p>Hello<svg></svg></p>\", \"Hello\\n\", 80);\n}\n\n#[test]\nfn test_noscript() {\n    test_html(\n        br\"<p>Hello</p>\n        <noscript><p><strong>There</strong></p></noscript>\",\n        \"Hello\\n\\n**There**\\n\",\n        80,\n    );\n}\n\n#[test]\nfn test_br() {\n    test_html(br\"<p>Hello<br/>World</p>\", \"Hello\\nWorld\\n\", 20);\n}\n\n#[test]\nfn test_br2() {\n    test_html(br\"<p>Hello<br/><br/>World</p>\", \"Hello\\n\\nWorld\\n\", 20);\n}\n\n#[test]\nfn test_br3() {\n    test_html(br\"<p>Hello<br/> <br/>World</p>\", \"Hello\\n\\nWorld\\n\", 20);\n}\n\n#[test]\nfn test_subblock() {\n    test_html(\n        br#\"<div>\n     <div>Here's a <a href=\"https://example.com/\">link</a>.</div>\n     <div><ul>\n     <li>Bullet</li>\n     <li>Bullet</li>\n     <li>Bullet</li>\n     </ul></div>\n     </div>\"#,\n        r\"Here's a [link][1].\n* Bullet\n* Bullet\n* Bullet\n\n[1]: https://example.com/\n\",\n        80,\n    );\n}\n\n#[test]\nfn test_controlchar() {\n    test_html(\"Foo\\u{0080}Bar\".as_bytes(), \"FooBar\\n\", 80);\n    test_html(\"Foo\\u{0080}Bar\".as_bytes(), \"FooB\\nar\\n\", 4);\n    test_html(\"FooBa\\u{0080}r\".as_bytes(), \"FooB\\nar\\n\", 4);\n}\n\n#[test]\nfn test_nested_table_1() {\n    test_html(\n        br##\"\n   <table>\n     <tr>\n       <td>\n          <table><tr><td>1</td><td>2</td><td>3</td></tr></table>\n       </td>\n       <td>\n          <table><tr><td>4</td><td>5</td><td>6</td></tr></table>\n       </td>\n       <td>\n          <table><tr><td>7</td><td>8</td><td>9</td></tr></table>\n       </td>\n     </tr>\n     <tr>\n       <td>\n          <table><tr><td>1</td><td>2</td><td>3</td></tr></table>\n       </td>\n       <td>\n          <table><tr><td>4</td><td>5</td><td>6</td></tr></table>\n       </td>\n       <td>\n          <table><tr><td>7</td><td>8</td><td>9</td></tr></table>\n       </td>\n     </tr>\n     <tr>\n       <td>\n          <table><tr><td>1</td><td>2</td><td>3</td></tr></table>\n       </td>\n       <td>\n          <table><tr><td>4</td><td>5</td><td>6</td></tr></table>\n       </td>\n       <td>\n          <table><tr><td>7</td><td>8</td><td>9</td></tr></table>\n       </td>\n     </tr>\n   </table>\n\"##,\n        r#\"─┬─┬─┬─┬─┬─┬─┬─┬─\n1│2│3│4│5│6│7│8│9\n─┼─┼─┼─┼─┼─┼─┼─┼─\n1│2│3│4│5│6│7│8│9\n─┼─┼─┼─┼─┼─┼─┼─┼─\n1│2│3│4│5│6│7│8│9\n─┴─┴─┴─┴─┴─┴─┴─┴─\n\"#,\n        21,\n    );\n}\n\n#[test]\nfn test_nested_table_2() {\n    test_html(\n        br##\"\n   <table>\n     <tr>\n       <td>\n          <table>\n             <tr><td>1</td><td>a</td></tr>\n             <tr><td>2</td><td>b</td></tr>\n          </table>\n       </td>\n       <td><pre>one\ntwo\nthree\nfour\nfive\n</pre>\n       </td>\n     </tr>\n   </table>\n\"##,\n        r#\"─┬─┬───────\n1│a│one    \n─┼─│two    \n2│b│three  \n │ │four   \n │ │five   \n─┴─┴───────\n\"#,\n        11,\n    );\n}\n\n#[test]\nfn test_h1() {\n    test_html(\n        br##\"\n   <h1>Hi</h1>\n   <p>foo</p>\n\"##,\n        r#\"# Hi\n\nfoo\n\"#,\n        21,\n    );\n}\n\n#[test]\nfn test_h3() {\n    test_html(\n        br##\"\n   <h3>Hi</h3>\n   <p>foo</p>\n\"##,\n        r#\"### Hi\n\nfoo\n\"#,\n        21,\n    );\n}\n\n// General test that spacing is preserved\n#[test]\nfn test_pre2() {\n    test_html(\n        br##\"<pre>Hello  sp\nworld</pre>\"##,\n        r#\"Hello  sp\nworld\n\"#,\n        21,\n    );\n}\n\n#[test]\nfn test_multi_pre() {\n    test_html(\n        br##\"<head><style><!--\npre\n\t{\n\tmargin:0cm;\n\tmargin-bottom:.0001pt;\n\t}\n--></style></head>\n<body>\n    <pre>&nbsp;</pre>\n    <pre>Senior Security Engineer</pre>\n    <pre>&nbsp;</pre>\n    <pre>Singapore Washington DC</pre>\n    <pre>&nbsp;</pre>\n    <pre>email: x.y@z.com</pre>\n    <pre>&nbsp;</pre>\n    <pre>mobile:33 999999</pre>\n    <pre>phone: 33 999999</pre>\n</body>\n\"##,\n        \"\\u{a0}\n\nSenior Security Engineer\n\n\\u{a0}\n\nSingapore Washington DC\n\n\\u{a0}\n\nemail: x.y@z.com\n\n\\u{a0}\n\nmobile:33 999999\n\nphone: 33 999999\n\",\n        80,\n    );\n}\n\n// Check that spans work correctly inside <pre>\n#[test]\nfn test_pre_span() {\n    test_html(\n        br##\"\n<pre>Hello <span>$</span>sp\n<span>Hi</span> <span>$</span><span>foo</span>\n<span>Hi</span> <span>foo</span><span>, </span><span>bar</span>\n</pre>\"##,\n        r#\"Hello $sp\nHi $foo\nHi foo, bar\n\"#,\n        21,\n    );\n}\n\n// Check tab behaviour\n#[test]\nfn test_pre_tab() {\n    test_html(b\"<pre>\\tworld</pre>\", \"        world\\n\", 40);\n    test_html(b\"<pre>H\\tworld</pre>\", \"H       world\\n\", 40);\n    test_html(b\"<pre>He\\tworld</pre>\", \"He      world\\n\", 40);\n    test_html(b\"<pre>Hel\\tworld</pre>\", \"Hel     world\\n\", 40);\n    test_html(b\"<pre>Hell\\tworld</pre>\", \"Hell    world\\n\", 40);\n    test_html(b\"<pre>Hello\\tworld</pre>\", \"Hello   world\\n\", 40);\n    test_html(b\"<pre>Helloo\\tworld</pre>\", \"Helloo  world\\n\", 40);\n    test_html(b\"<pre>Hellooo\\tworld</pre>\", \"Hellooo world\\n\", 40);\n    test_html(b\"<pre>Helloooo\\tworld</pre>\", \"Helloooo        world\\n\", 40);\n}\n\n#[test]\nfn test_pre_tab2() {\n    // Note hard tab characters below.\n    test_html(\n        br#\"<pre>\tt0\nx\tt1\nxx\tt2\nxxx\tt3\nxxxx\tt4\nxxxxx\tt5\nxxxxxx\tt6\nxxxxxxx\tt7\nxxxxxxxx\tt8\nxxxxxxxxx\tt9</pre>\"#,\n        r\"        t0\nx       t1\nxx      t2\nxxx     t3\nxxxx    t4\nxxxxx   t5\nxxxxxx  t6\nxxxxxxx t7\nxxxxxxxx        t8\nxxxxxxxxx       t9\n\",\n        40,\n    );\n}\n\n// Check for edge cases hitting the width\n#[test]\nfn test_pre_tab3() {\n    // Note hard tab characters below.\n    test_html(\n        br#\"<pre>\tt\nx\tt\nxx\tt\nxxx\tt\nxxxx\tt\nxxxxx\tt\nxxxxxx\tt\nxxxxxxx\tt\nxxxxxxxx\tt\nxxxxxxxxx\tt</pre>\"#,\n        r\"        t\nx       t\nxx      t\nxxx     t\nxxxx    t\nxxxxx   t\nxxxxxx  t\nxxxxxxx t\nxxxxxxxx  \nt\nxxxxxxxxx \nt\n\",\n        10,\n    );\n    test_html(\n        br#\"<pre>\tt\nx\tt\nxx\tt\nxxx\tt\nxxxx\tt\nxxxxx\tt\nxxxxxx\tt\nxxxxxxx\tt\nxxxxxxxx\tt\nxxxxxxxxx\tt</pre>\"#,\n        r\"        t\nx       t\nxx      t\nxxx     t\nxxxx    t\nxxxxx   t\nxxxxxx  t\nxxxxxxx t\nxxxxxxxx \nt\nxxxxxxxxx\n        t\n\",\n        9,\n    );\n    test_html(\n        br#\"<pre>\tt\nx\tt\nxx\tt\nxxx\tt\nxxxx\tt\nxxxxx\tt\nxxxxxx\tt\nxxxxxxx\tt\nxxxxxxxx\tt\nxxxxxxxxx\tt</pre>\"#,\n        r\"        \nt\nx       \nt\nxx      \nt\nxxx     \nt\nxxxx    \nt\nxxxxx   \nt\nxxxxxx  \nt\nxxxxxxx \nt\nxxxxxxxx\n        \nt\nxxxxxxxx\nx       \nt\n\",\n        8,\n    );\n    test_html(\n        br#\"<pre>\tt\nx\tt\nxx\tt\nxxx\tt\nxxxx\tt\nxxxxx\tt\nxxxxxx\tt\nxxxxxxx\tt\nxxxxxxxx\tt\nxxxxxxxxx\tt</pre>\"#,\n        r\"       \nt\nx      \nt\nxx     \nt\nxxx    \nt\nxxxx   \nt\nxxxxx  \nt\nxxxxxx \nt\nxxxxxxx\n       \nt\nxxxxxxx\nx      \nt\nxxxxxxx\nxx     \nt\n\",\n        7,\n    );\n}\n\n#[test]\nfn test_em_strong() {\n    test_html(\n        br##\"\n   <p>Hi <em>em</em> <strong>strong</strong></p>\n\"##,\n        r#\"Hi *em* **strong**\n\"#,\n        21,\n    );\n}\n\n#[test]\nfn test_b_tag() {\n    test_html(\n        br##\"\n   <p>Hi <b>bold</b></p>\n\"##,\n        r#\"Hi **bold**\n\"#,\n        21,\n    );\n}\n\n#[test]\nfn test_nbsp_indent() {\n    test_html(\n        br##\"\n   <div>Top</div>\n   <div>&nbsp;Indented</div>\n   <div>&nbsp;&nbsp;Indented again</div>\n\"##,\n        \"Top\n\\u{a0}Indented\n\\u{a0}\\u{a0}Indented again\n\",\n        21,\n    );\n}\n\n// Some of the tracing output can overflow the stack when tracing some values.\n#[cfg(not(feature = \"html_trace\"))]\n#[test]\nfn test_deeply_nested() {\n    use ::std::iter::repeat;\n    let html = repeat(\"<foo>\").take(1000).collect::<Vec<_>>().concat();\n    test_html(html.as_bytes(), \"\", 10);\n}\n\n// Some of the tracing output can overflow the stack when tracing some values.\n#[cfg(not(feature = \"html_trace\"))]\n#[test]\nfn test_deeply_nested_table() {\n    use ::std::iter::repeat;\n    let rpt = 1000;\n    let html = repeat(\"<table><tr><td>hi</td><td>\")\n        .take(rpt)\n        .collect::<Vec<_>>()\n        .concat()\n        + &repeat(\"</td></tr></table>\")\n            .take(rpt)\n            .collect::<Vec<_>>()\n            .concat();\n\n    let result = repeat(\n        r#\"──────────\nhi\n//////////\n\"#,\n    )\n    .take(rpt - 3)\n    .collect::<Vec<_>>()\n    .concat()\n        + r#\"──┬──┬───\nhi│hi│hi \n──┴──┴───\n\"# + &\"──────────\\n\".repeat(rpt - 3);\n    test_html(html.as_bytes(), &result, 10);\n}\n\n#[test]\nfn test_table_no_id() {\n    let html = r#\"<html><body><table>\n        <tr>\n            <td>hi, world</td>\n        </tr>\n    </table></body></html>\"#;\n    test_html(\n        html.as_bytes(),\n        r#\"─────────\nhi, world\n─────────\n\"#,\n        10,\n    );\n}\n\n#[test]\nfn test_table_cell_id() {\n    let html = r#\"<html><body><table>\n        <tr>\n            <td id=\"bodyCell\">hi, world</td>\n        </tr>\n    </table></body></html>\"#;\n    test_html(\n        html.as_bytes(),\n        r#\"─────────\nhi, world\n─────────\n\"#,\n        10,\n    );\n}\n\n#[test]\nfn test_table_row_id() {\n    let html = r#\"<html><body><table>\n        <tr id=\"bodyrow\">\n            <td>hi, world</td>\n        </tr>\n    </table></body></html>\"#;\n    test_html(\n        html.as_bytes(),\n        r#\"─────────\nhi, world\n─────────\n\"#,\n        10,\n    );\n}\n\n#[test]\nfn test_table_table_id() {\n    let html = r#\"<html><body><table id=\"bodytable\">\n        <tr>\n            <td>hi, world</td>\n        </tr>\n    </table></body></html>\"#;\n    test_html(\n        html.as_bytes(),\n        r#\"─────────\nhi, world\n─────────\n\"#,\n        10,\n    );\n}\n\n#[test]\nfn test_table_tbody_id() {\n    let html = r#\"<html><body><table>\n      <tbody id=\"tb\">\n        <tr>\n            <td>hi, world</td>\n        </tr>\n      </tbody>\n    </table></body></html>\"#;\n    test_html(\n        html.as_bytes(),\n        r#\"─────────\nhi, world\n─────────\n\"#,\n        10,\n    );\n}\n\n#[test]\nfn test_header_width() {\n    //0 size\n    test_html_err(\n        br##\"\n        <h2>\n            <table>\n                        <h3>Anything</h3>\n            </table>\n        </h2>\n\"##,\n        Error::TooNarrow,\n        7,\n    );\n    //Underflow\n    test_html_err(\n        br##\"\n        <h2>\n            <table>\n                <h3>Anything</h3>\n            </table>\n        </h2>\n\"##,\n        Error::TooNarrow,\n        5,\n    );\n}\n\n#[test]\nfn test_trivial_decorator() {\n    test_html_decorator(\n        br#\"<div>\n     <div>Here's a <a href=\"https://example.com/\">link</a>.</div>\n     <div><ul>\n     <li>Bullet</li>\n     <li>Bullet</li>\n     <li>Bullet</li>\n     </ul></div>\n     </div>\"#,\n        r\"Here's a link.\nBullet\nBullet\nBullet\n\",\n        80,\n        TrivialDecorator::new(),\n    );\n}\n\n#[test]\nfn test_issue_16() {\n    test_html(b\"<ul><li><!----></li></ul>\", \"\", 10);\n}\n\n#[test]\nfn test_pre_br() {\n    test_html(\n        b\"<pre>Foo<br>Bar</pre>\",\n        r#\"Foo\nBar\n\"#,\n        10,\n    );\n}\n\n#[test]\nfn test_pre_emptyline() {\n    test_html(br#\"<pre>X<span id=\"i\"> </span></pre>\"#, \"X \\n\", 10);\n}\n\n#[test]\nfn test_link_id_longline() {\n    test_html(\n        br#\"<a href=\"foo\" id=\"i\">quitelongline</a>\"#,\n        r#\"[quitelong\nline][1]\n\n[1]: foo\n\"#,\n        10,\n    );\n}\n\n#[test]\nfn test_dl() {\n    test_html(\n        br#\"<dl><dt>Foo</dt><dd>Definition of foo</dd></dl>\"#,\n        r#\"*Foo*\n  Definition of foo\n\"#,\n        40,\n    );\n}\n\n#[test]\nfn test_s() {\n    test_html(\n        br#\"Hi <s>you</s>thee!\"#,\n        \"Hi y\\u{336}o\\u{336}u\\u{336}thee!\\n\",\n        40,\n    );\n}\n\n#[test]\nfn test_multi_parse() {\n    let html: &[u8] = b\"one two three four five six seven eight nine ten eleven twelve thirteen \\\n                        fourteen fifteen sixteen seventeen\";\n    let tree = parse(html).unwrap();\n    assert_eq!(\n        \"one two three four five six seven eight nine ten eleven twelve thirteen fourteen\\n\\\n         fifteen sixteen seventeen\\n\",\n        config::plain().render_to_string(tree.clone(), 80).unwrap()\n    );\n    assert_eq!(\n        \"one two three four five six seven eight nine ten eleven twelve\\n\\\n         thirteen fourteen fifteen sixteen seventeen\\n\",\n        config::plain().render_to_string(tree.clone(), 70).unwrap()\n    );\n    assert_eq!(\n        \"one two three four five six seven eight nine ten\\n\\\n         eleven twelve thirteen fourteen fifteen sixteen\\n\\\n         seventeen\\n\",\n        config::plain().render_to_string(tree.clone(), 50).unwrap()\n    );\n}\n\n#[test]\nfn test_read_rich() {\n    let html: &[u8] = b\"<strong>bold</strong>\";\n    let lines = config::rich()\n        .render_to_lines(parse(html).unwrap(), 80)\n        .unwrap();\n    let tag = vec![RichAnnotation::Strong];\n    let line = TaggedLine::from_string(\"bold\".to_owned(), &tag);\n    assert_eq!(vec![line], lines);\n}\n\n#[test]\nfn test_read_rich_nodecorate() {\n    let html: &[u8] = b\"<strong>bold</strong>\";\n    let lines = config::rich()\n        .render_to_lines(parse(html).unwrap(), 80)\n        .unwrap();\n    let tag = vec![RichAnnotation::Strong];\n    let line = TaggedLine::from_string(\"bold\".to_owned(), &tag);\n    assert_eq!(vec![line], lines);\n}\n\n#[test]\nfn test_read_custom() {\n    let html: &[u8] = b\"<strong>bold</strong>\";\n    let lines = config::with_decorator(TrivialDecorator::new())\n        .render_to_lines(parse(html).unwrap(), 80)\n        .unwrap();\n    let tag = vec![()];\n    let line = TaggedLine::from_string(\"bold\".to_owned(), &tag);\n    assert_eq!(vec![line], lines);\n}\n\n#[test]\nfn test_pre_rich() {\n    use RichAnnotation::*;\n    assert_eq!(\n        config::rich()\n            .render_to_lines(parse(&b\"<pre>test</pre>\"[..]).unwrap(), 100)\n            .unwrap(),\n        [TaggedLine::from_string(\n            \"test\".into(),\n            &vec![Preformat(false)]\n        )]\n    );\n\n    assert_eq!(\n        config::rich()\n            .render_to_lines(crate::parse(\"<pre>testlong</pre>\".as_bytes()).unwrap(), 4)\n            .unwrap(),\n        [\n            TaggedLine::from_string(\"test\".into(), &vec![Preformat(false)]),\n            TaggedLine::from_string(\"long\".into(), &vec![Preformat(true)])\n        ]\n    );\n\n    // The similar html with <p> and white-space: pre should not have the Preformat\n    // tags.\n    assert_eq!(\n        config::rich()\n            .render_to_lines(\n                crate::parse(r#\"<p style=\"white-space: pre\">testlong</p>\"#.as_bytes()).unwrap(),\n                4\n            )\n            .unwrap(),\n        [\n            TaggedLine::from_string(\"test\".into(), &vec![]),\n            TaggedLine::from_string(\"long\".into(), &vec![])\n        ]\n    );\n}\n\n#[test]\nfn test_finalise() {\n    use crate::render::text_renderer::{TaggedLine, TextDecorator};\n\n    #[derive(Clone, Debug)]\n    struct TestDecorator;\n\n    impl TextDecorator for TestDecorator {\n        type Annotation = bool;\n\n        fn decorate_link_start(&mut self, _url: &str) -> (String, Self::Annotation) {\n            Default::default()\n        }\n\n        fn decorate_link_end(&mut self) -> String {\n            Default::default()\n        }\n\n        fn decorate_em_start(&self) -> (String, Self::Annotation) {\n            Default::default()\n        }\n\n        fn decorate_em_end(&self) -> String {\n            Default::default()\n        }\n\n        fn decorate_strong_start(&self) -> (String, Self::Annotation) {\n            Default::default()\n        }\n\n        fn decorate_strong_end(&self) -> String {\n            Default::default()\n        }\n\n        fn decorate_strikeout_start(&self) -> (String, Self::Annotation) {\n            Default::default()\n        }\n\n        fn decorate_strikeout_end(&self) -> String {\n            Default::default()\n        }\n\n        fn decorate_code_start(&self) -> (String, Self::Annotation) {\n            Default::default()\n        }\n\n        fn decorate_code_end(&self) -> String {\n            Default::default()\n        }\n\n        fn decorate_preformat_first(&self) -> Self::Annotation {\n            Default::default()\n        }\n\n        fn decorate_preformat_cont(&self) -> Self::Annotation {\n            Default::default()\n        }\n\n        fn decorate_image(&mut self, _src: &str, _title: &str) -> (String, Self::Annotation) {\n            Default::default()\n        }\n\n        fn header_prefix(&self, level: usize) -> String {\n            \"#\".repeat(level) + \" \"\n        }\n\n        fn quote_prefix(&self) -> String {\n            \"> \".to_string()\n        }\n\n        fn unordered_item_prefix(&self) -> String {\n            \"* \".to_string()\n        }\n\n        fn ordered_item_prefix(&self, i: i64) -> String {\n            format!(\"{}. \", i)\n        }\n\n        fn finalise(&mut self, _links: Vec<String>) -> Vec<TaggedLine<bool>> {\n            vec![TaggedLine::from_string(String::new(), &true)]\n        }\n\n        fn make_subblock_decorator(&self) -> Self {\n            TestDecorator\n        }\n    }\n    assert_eq!(\n        config::with_decorator(TestDecorator)\n            .lines_from_read(\"test\".as_bytes(), 80)\n            .unwrap(),\n        vec![\n            TaggedLine::from_string(\"test\".to_owned(), &Vec::new()),\n            TaggedLine::new(),\n            TaggedLine::new(),\n        ]\n    );\n}\n\n#[test]\nfn test_empty_rows() {\n    test_html(\n        br##\"\n   <table>\n     <tr>\n       <td>1</td>\n       <td>2</td>\n       <td>3</td>\n     </tr>\n     <tr><td></td><td></td><td></td></tr>\n     <tr>\n       <td>4</td>\n       <td>5</td>\n       <td>6</td>\n     </tr>\n   </table>\n\"##,\n        r#\"─┬─┬─\n1│2│3\n─┼─┼─\n4│5│6\n─┴─┴─\n\"#,\n        12,\n    );\n}\n\n#[test]\nfn test_empty_cols() {\n    test_html(\n        br##\"\n   <table>\n     <tr>\n       <td></td>\n       <td>1</td>\n       <td></td>\n       <td>2</td>\n       <td></td>\n     </tr>\n     <tr>\n       <td></td>\n       <td>3</td>\n       <td></td>\n       <td>4</td>\n       <td></td>\n     </tr>\n     <tr>\n       <td></td>\n       <td>5</td>\n       <td></td>\n       <td>6</td>\n       <td></td>\n     </tr>\n   </table>\n\"##,\n        r#\"─┬─\n1│2\n─┼─\n3│4\n─┼─\n5│6\n─┴─\n\"#,\n        12,\n    );\n}\n\n#[test]\nfn test_empty_table() {\n    test_html(\n        br##\"\n   <table></table>\n\"##,\n        r#\"\"#,\n        12,\n    );\n}\n\n#[test]\nfn test_table_empty_single_row() {\n    test_html(\n        br##\"\n   <table><tr></tr></table>\n\"##,\n        r#\"\"#,\n        12,\n    );\n}\n\n#[test]\nfn test_table_empty_single_row_empty_cell() {\n    test_html(\n        br##\"\n   <table><tr><td></td></tr></table>\n\"##,\n        r#\"\"#,\n        12,\n    );\n}\n\n#[test]\nfn test_table_empty_single_row_ws_cell() {\n    test_html(\n        br##\"\n   <table><tr><td> </td></tr></table>\n\"##,\n        r#\"\"#,\n        12,\n    );\n    test_html(\n        br##\"\n   <table><tr><td>\n</td></tr></table>\n\"##,\n        r#\"\"#,\n        12,\n    );\n}\n\n#[test]\nfn test_renderer_zero_width() {\n    test_html_err(\n        br##\"<ul><li><table><tr><td>x</td></tr></table></li></ul>\n\"##,\n        Error::TooNarrow,\n        2,\n    );\n}\n\n#[test]\nfn test_ul_tiny_table() {\n    test_html(\n        br##\"<ul><li><table><tr><td>x</td></tr></table></li></ul>\n\"##,\n        r#\"* ─\n  x\n  ─\n\"#,\n        12,\n    );\n}\n\n#[test]\nfn test_issue_54_oob() {\n    test_html(\n        br##\"\n<html>\n<body>\n    <table>\n        <tr>\n            <td>\n                <table>\n                    <tr>\n                        <td>&nbsp;</td>\n                        <td>\n                            <table>\n                                <tr>\n                                    <td>Blah blah blah\n                                    </td>\n                                </tr>\n                            </table>\n                        </td>\n                        <td>&nbsp;</td>\n                    </tr>\n                </table>\n            </td>\n        </tr>\n    </table>\n</body>\n\"##,\n        \"─┬──────┬─\n\\u{a0}│Blah  │\\u{a0}\n │blah  │ \n │blah  │ \n─┴──────┴─\n\",\n        10,\n    );\n}\n\n#[test]\nfn test_table_vertical_rows() {\n    test_html(\n        br##\"\n<table>\n    <tr>\n        <td>wid</td>\n        <td>kin</td>\n        <td>der</td>\n    </tr>\n</table>\n\"##,\n        \"─────\nwid\n/////\nkin\n/////\nder\n─────\n\",\n        5,\n    );\n}\n\nconst MULTILINE_CELLS: &[u8] = b\"<table><tr>\n    <td><ol><li></li></ol></td>\n    <td><ol><li>\n        Aliquam erat volutpat.  Nunc eleifend leo vitae magna.  In id erat non orci commodo lobortis.\n    </li>\n    <li>\n        Aliquam erat volutpat.\n    </li>\n    <li></li>\n    </ol></td>\n    <td><ol><li>\n        Lorem ipsum dolor sit amet, consectetuer adipiscing elit.  Donec hendrerit tempor tellus.\n    </li></ol></td>\n</tr>\n</table>\";\n\n#[test]\nfn test_table_without_borders() {\n    let expected = \"Aliquam erat volutpat. Nunc eleifend leo     Lorem ipsum dolor sit amet,       \nvitae magna. In id erat non orci commodo     consectetuer adipiscing elit.     \nlobortis.                                    Donec hendrerit tempor tellus.    \nAliquam erat volutpat.                                                         \\n\";\n    test_html_conf_dec(\n        TrivialDecorator::new(),\n        MULTILINE_CELLS,\n        expected,\n        80,\n        |c| c.no_table_borders(),\n    );\n}\n\n#[test]\nfn test_table_raw_mode() {\n    let expected = \"Aliquam erat volutpat. Nunc eleifend leo vitae magna. In id erat non orci\ncommodo lobortis.\nAliquam erat volutpat.\nLorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec hendrerit tempor\ntellus.\\n\";\n    test_html_conf_dec(\n        TrivialDecorator::new(),\n        MULTILINE_CELLS,\n        expected,\n        80,\n        |c| c.raw_mode(true),\n    );\n}\n\n#[test]\nfn test_unicode() {\n    test_html(\n        \"<table>\n      <td>နတမစ</td>\n      <td>နတမစ</td>\n      <td>aaa</td>\n</table>\"\n            .as_bytes(),\n        \"────┬────┬───\nနတမစ│နတမစ│aaa\n────┴────┴───\n\",\n        15,\n    );\n}\n\n#[test]\nfn test_list_in_table() {\n    test_html(\n        b\"<table>\n<td><ol>\n<li>0</li>\n<li>1</li>\n<li>2</li>\n<li>3</li>\n<li>4</li>\n<li>5</li>\n<li>6</li>\n<li>7</li>\n<li>8</li>\n<li>9</li>\n<li>10</li>\n</ol></td>\n</table>\",\n        \"──────\n1.  0 \n2.  1 \n3.  2 \n4.  3 \n5.  4 \n6.  5 \n7.  6 \n8.  7 \n9.  8 \n10. 9 \n11. 10\n──────\n\",\n        6,\n    );\n}\n\n#[test]\nfn test_max_width() {\n    let html = r#\"<table><td><p>3,266</p>\"#;\n    let decorator = crate::render::text_renderer::PlainDecorator::new();\n    let text = from_read_with_decorator(html.as_bytes(), usize::MAX, decorator.clone()).unwrap();\n    println!(\"{}\", text);\n}\n\n#[test]\nfn test_preserving_empty_newlines_in_pre_blocks() {\n    let html = r#\"<pre>\nTest.\n\n\nEnd.\n</pre>\"#;\n    let decorator = crate::render::text_renderer::TrivialDecorator::new();\n    let text = from_read_with_decorator(html.as_bytes(), 20, decorator.clone()).unwrap();\n    assert_eq!(text, \"Test.\\n\\n\\nEnd.\\n\");\n}\n\n#[test]\nfn test_links_outside_table() {\n    let html = r#\"\n<table>\n<tbody>\n<tr><td><a href=\"https://example.com/verylonglinks\"><img src=\"http://www.twitter.com/img/icon_twitter.png\" alt=\"Twitter\"></a></td>\n    <td><a href=\"http://www.facebook.com/pages\"><img src=\"http://www.facebook.com/icon_facebook.png\" alt=\"Facebook\"></a></td>\n        </tr>\n        </tbody>\n        </table>\n\"#;\n    let text = from_read(html.as_bytes(), 80).unwrap();\n    assert_eq!(\n        text,\n        \"──────────────┬───────────────\n[[Twitter]][1]│[[Facebook]][2]\n──────────────┴───────────────\n\n[1]: https://example.com/verylonglinks\n[2]: http://www.facebook.com/pages\n\"\n    );\n}\n\n#[test]\nfn test_narrow_width_nested() {\n    use crate::Error;\n    // Check different things which cause narrowing\n    for html in [\n        r#\"<h1>Hi</h1>\"#,\n        r#\"<blockquote>Hi</blockquote>\"#,\n        r#\"<ul><li>Hi</li></ul>\"#,\n        r#\"<ol><li>Hi</li></ul>\"#,\n        r#\"<dl><dt>Foo</dt><dd>Definition of foo</dd></dl>\"#,\n    ] {\n        let result = config::plain().string_from_read(html.as_bytes(), 1);\n        if let Err(Error::TooNarrow) = result {\n            // ok\n        } else {\n            panic!(\"Expected too narrow, got: {:?}\", result);\n        }\n    }\n}\n\n#[test]\nfn test_issue_93_x() {\n    let data = [\n        60, 116, 97, 98, 108, 101, 62, 60, 116, 114, 62, 60, 116, 100, 62, 120, 105, 60, 48, 62, 0,\n        0, 0, 60, 116, 97, 98, 108, 101, 62, 58, 58, 58, 62, 58, 62, 62, 62, 58, 60, 112, 32, 32,\n        32, 32, 32, 32, 32, 71, 87, 85, 78, 16, 16, 62, 60, 15, 16, 16, 16, 16, 16, 16, 15, 38, 16,\n        16, 16, 15, 1, 16, 16, 16, 16, 16, 16, 162, 111, 107, 99, 91, 112, 57, 64, 94, 100, 60,\n        111, 108, 47, 62, 127, 60, 108, 73, 62, 125, 109, 121, 102, 99, 122, 110, 102, 114, 98, 60,\n        97, 32, 104, 114, 101, 102, 61, 98, 111, 103, 32, 105, 100, 61, 100, 62, 60, 111, 15, 15,\n        15, 15, 15, 15, 15, 39, 15, 15, 15, 106, 102, 59, 99, 32, 32, 32, 86, 102, 122, 110, 104,\n        93, 108, 71, 114, 117, 110, 100, 96, 121, 57, 60, 107, 116, 109, 247, 62, 60, 32, 60, 122,\n        98, 99, 98, 97, 32, 119, 127, 127, 62, 60, 112, 62, 121, 116, 60, 47, 116, 100, 62, 62, 60,\n        111, 98, 62, 123, 110, 109, 97, 101, 105, 119, 60, 112, 101, 101, 122, 102, 63, 120, 97,\n        62, 60, 101, 62, 60, 120, 109, 112, 32, 28, 52, 55, 50, 50, 49, 52, 185, 150, 99, 62, 255,\n        112, 76, 85, 60, 112, 62, 73, 100, 116, 116, 60, 75, 50, 73, 116, 120, 110, 127, 255, 118,\n        32, 42, 40, 49, 33, 112, 32, 36, 107, 57, 60, 5, 163, 62, 49, 55, 32, 33, 118, 99, 63, 60,\n        109, 107, 43, 119, 100, 62, 60, 104, 58, 101, 163, 163, 163, 163, 220, 220, 220, 220, 220,\n        220, 220, 220, 220, 220, 220, 220, 1, 107, 117, 107, 108, 44, 102, 58, 60, 116, 101, 97,\n        106, 98, 59, 60, 115, 109, 52, 58, 115, 98, 62, 232, 110, 114, 32, 60, 117, 93, 120, 112,\n        119, 111, 59, 98, 120, 61, 206, 19, 61, 206, 19, 59, 1, 110, 102, 60, 115, 0, 242, 64, 203,\n        8, 111, 50, 59, 121, 122, 32, 42, 35, 32, 37, 101, 120, 104, 121, 0, 242, 59, 63, 121, 231,\n        130, 130, 130, 170, 170, 1, 32, 0, 0, 0, 28, 134, 200, 90, 119, 48, 60, 111, 108, 118, 119,\n        116, 113, 59, 100, 60, 117, 43, 110, 99, 9, 216, 157, 137, 216, 157, 246, 167, 62, 60, 104,\n        61, 43, 28, 134, 200, 105, 119, 48, 60, 122, 110, 0, 242, 61, 61, 114, 231, 130, 130, 130,\n        170, 170, 170, 233, 222, 222, 162, 163, 163, 163, 163, 163, 163, 163, 85, 100, 116, 99, 61,\n        60, 163, 163, 163, 163, 163, 220, 220, 1, 109, 112, 105, 10, 59, 105, 220, 215, 10, 59,\n        122, 100, 100, 121, 97, 43, 43, 43, 102, 122, 100, 60, 62, 114, 116, 122, 115, 61, 60, 115,\n        101, 62, 215, 215, 215, 215, 215, 98, 59, 60, 109, 120, 57, 60, 97, 102, 113, 229, 43, 43,\n        43, 43, 43, 43, 43, 43, 43, 35, 43, 43, 101, 58, 60, 116, 98, 101, 107, 98, 43, 43, 43, 43,\n        43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43,\n        43, 43, 43, 43, 43, 98, 99, 62, 60, 112, 102, 59, 124, 107, 111, 97, 98, 108, 118, 60, 116,\n        102, 101, 104, 97, 62, 60, 255, 127, 46, 60, 116, 101, 62, 60, 105, 102, 63, 116, 116, 60,\n        47, 116, 101, 62, 62, 60, 115, 98, 62, 123, 109, 108, 97, 100, 119, 118, 60, 111, 99, 97,\n        103, 99, 62, 60, 255, 127, 46, 60, 103, 99, 62, 60, 116, 98, 63, 60, 101, 62, 60, 109, 109,\n        231, 130, 130, 130, 213, 213, 213, 233, 222, 222, 59, 101, 103, 58, 60, 100, 111, 61, 65,\n        114, 104, 60, 47, 101, 109, 62, 60, 99, 99, 172, 97, 97, 58, 60, 119, 99, 64, 126, 118,\n        104, 100, 100, 107, 105, 60, 120, 98, 255, 255, 255, 0, 60, 255, 127, 46, 60, 113, 127,\n    ];\n    config::with_decorator(TrivialDecorator::new())\n        .string_from_read(&data[..], 1)\n        .unwrap_err();\n}\n\n#[test]\nfn test_superscript() {\n    test_html(br#\"Exponential x<sup>y</sup>\"#, \"Exponential x^{y}\\n\", 80);\n    test_html(br#\"Exponential 2<sup>32</sup>\"#, \"Exponential 2³²\\n\", 80);\n}\n\n#[test]\nfn test_header_overflow() {\n    let html_hdr = br#\"<blockquote><h3>Foo</h3></blockquote>\"#;\n    test_html(html_hdr, \"> ### Foo\\n\", 20);\n    test_html_conf(html_hdr, \"> ### F\\n> ### o\\n> ### o\\n\", 7, |c| {\n        c.min_wrap_width(1)\n    });\n    test_html_err_conf(html_hdr, Error::TooNarrow, 6, |c| c.min_wrap_width(1));\n    test_html_err_conf(html_hdr, Error::TooNarrow, 7, |c| c.min_wrap_width(3));\n    test_html_conf(html_hdr, \"> ### F\\n> ### o\\n> ### o\\n\", 6, |c| {\n        c.min_wrap_width(1).allow_width_overflow()\n    });\n    test_html_conf(html_hdr, \"> ### Foo\\n\", 7, |c| {\n        c.min_wrap_width(3).allow_width_overflow()\n    });\n}\n\n#[test]\nfn test_blockquote_overflow() {\n    let html_hdr = br#\"<blockquote><blockquote>Foo</blockquote></blockquote>\"#;\n    test_html(html_hdr, \"> > Foo\\n\", 20);\n    test_html_conf(html_hdr, \"> > F\\n> > o\\n> > o\\n\", 5, |c| {\n        c.min_wrap_width(1)\n    });\n    test_html_err_conf(html_hdr, Error::TooNarrow, 3, |c| c.min_wrap_width(1));\n    test_html_err_conf(html_hdr, Error::TooNarrow, 4, |c| c.min_wrap_width(3));\n    test_html_conf(html_hdr, \"> > F\\n> > o\\n> > o\\n\", 3, |c| {\n        c.min_wrap_width(1).allow_width_overflow()\n    });\n    test_html_conf(html_hdr, \"> > Foo\\n\", 4, |c| {\n        c.min_wrap_width(3).allow_width_overflow()\n    });\n}\n\n#[test]\nfn test_ul_overflow() {\n    let html_hdr = br#\"<ul><li><ul><li>Foo</li></ul></li></ul>\"#;\n    test_html(html_hdr, \"* * Foo\\n\", 20);\n    test_html_conf(html_hdr, \"* * F\\n    o\\n    o\\n\", 5, |c| {\n        c.min_wrap_width(1)\n    });\n    test_html_err_conf(html_hdr, Error::TooNarrow, 3, |c| c.min_wrap_width(1));\n    test_html_err_conf(html_hdr, Error::TooNarrow, 4, |c| c.min_wrap_width(3));\n    test_html_conf(html_hdr, \"* * F\\n    o\\n    o\\n\", 3, |c| {\n        c.min_wrap_width(1).allow_width_overflow()\n    });\n    test_html_conf(html_hdr, \"* * Foo\\n\", 4, |c| {\n        c.min_wrap_width(3).allow_width_overflow()\n    });\n}\n\n#[test]\nfn test_ol_overflow() {\n    let html_hdr = br#\"<ol><li><ol><li>Foo</li></ol></li></ol>\"#;\n    test_html(html_hdr, \"1. 1. Foo\\n\", 20);\n    test_html_conf(html_hdr, \"1. 1. F\\n      o\\n      o\\n\", 7, |c| {\n        c.min_wrap_width(1)\n    });\n    test_html_err_conf(html_hdr, Error::TooNarrow, 5, |c| c.min_wrap_width(1));\n    test_html_err_conf(html_hdr, Error::TooNarrow, 6, |c| c.min_wrap_width(3));\n    test_html_conf(html_hdr, \"1. 1. F\\n      o\\n      o\\n\", 5, |c| {\n        c.min_wrap_width(1).allow_width_overflow()\n    });\n    test_html_conf(html_hdr, \"1. 1. Foo\\n\", 6, |c| {\n        c.min_wrap_width(3).allow_width_overflow()\n    });\n}\n\n#[test]\nfn test_dd_overflow() {\n    let html_hdr = br#\"<blockquote><dl><dt>Foo</dt><dd>Hello</dd></dl></blockquote>\"#;\n    test_html(html_hdr, \"> *Foo*\\n>   Hello\\n\", 20);\n    test_html_conf(\n        html_hdr,\n        \"> *Fo\\n> o*\\n>   H\\n>   e\\n>   l\\n>   l\\n>   o\\n\",\n        5,\n        |c| c.min_wrap_width(1),\n    );\n    test_html_err_conf(html_hdr, Error::TooNarrow, 3, |c| c.min_wrap_width(1));\n    test_html_err_conf(html_hdr, Error::TooNarrow, 4, |c| c.min_wrap_width(3));\n    test_html_conf(html_hdr, \"> *Foo*\\n>   Hel\\n>   lo\\n\", 4, |c| {\n        c.min_wrap_width(3).allow_width_overflow()\n    });\n}\n\n#[test]\nfn test_overflow_wide_char() {\n    // The smiley is a width-2 character.\n    let html = \"😃\".as_bytes();\n    test_html_err_conf(html, Error::TooNarrow, 1, |c| c.min_wrap_width(1));\n    test_html_conf(html, \"😃\\n\", 1, |c| {\n        c.min_wrap_width(1).allow_width_overflow()\n    });\n}\n\n#[test]\nfn test_table_too_narrow() {\n    let tbl = \"<table><tr>\n    <td><ol><li></li></ol></td>\n    <td>\n        <ol><li>Aliquam erat volutpat. Lorem ipsum dolor sit amet,</li></ol>\n    </td>\n    <td>\n        <ol><li>Lorem ipsum dolor sit</li></ol>\n    </td>\n</tr></table>\"\n        .as_bytes();\n    from_read(tbl, 80).unwrap();\n}\n\n#[test]\nfn test_empty_table_in_list() {\n    test_html(\n        b\"\n<ul>\n  <table>\n    <tr></tr>\n  </table>\n</ul>\",\n        \"\",\n        80,\n    );\n}\n\n#[test]\nfn test_silly_colspan() {\n    test_html(\n        br#\"\n  <table>\n    <tr>\n      <td colspan=\"9007199254740991\">foo</td.\n    </tr>\n  </table>\n\"#,\n        r#\"───\nfoo\n───\n\"#,\n        80,\n    );\n}\n\n#[test]\nfn test_rowspan1() {\n    test_html(\n        br#\"<table>\n    <tr><th>H0</th><th>H1</th><th>H2</th><th>H3</th><th>H4</th></tr>\n    <tr><td>X</td><td rowspan=2>Boo\n            Foo</td><td colspan=3>oh no</td></tr>\n    <tr><td>X123213</td><td rowspan=1 colspan=2>O</td><td rowspan=2>one two</td></tr>\n    <tr><td>Y</td><td colspan=3>Hmm oh</td></tr>\n</table>\"#,\n        r#\"────┬────┬──┬──┬────\nH0  │H1  │H2│H3│H4  \n────┼────┼──┴──┴────\nX   │Boo │oh no     \n────┤Foo ├─────┬────\nX123│    │O    │one \n213 │    │     │two \n────┼────┴─────┤    \nY   │Hmm oh    │    \n────┴──────────┴────\n\"#,\n        20,\n    );\n}\n\n#[test]\nfn test_rowspan2_emptyrow() {\n    test_html(\n        br#\"<table>\n    <tr><th>H0</th><th>H1</th><th>H2</th><th>H3</th><th>H4</th></tr>\n    <tr><td>X</td><td rowspan=3>Boo\n            Foo</td><td colspan=3>oh no</td></tr>\n    <tr><td>X123213</td><td rowspan=2 colspan=2>O</td><td rowspan=3>one two</td></tr>\n    <tr></tr>\n    <tr><td>Y</td><td colspan=3>Hmm oh</td></tr>\n</table>\"#,\n        r#\"────┬────┬──┬──┬────\nH0  │H1  │H2│H3│H4  \n────┼────┼──┴──┴────\nX   │Boo │oh no     \n────┤Foo ├─────┬────\nX123│    │O    │one \n213 │    │     │two \n────┤    │     │    \n────┼────┴─────┤    \nY   │Hmm oh    │    \n────┴──────────┴────\n\"#,\n        20,\n    );\n}\n\n#[test]\nfn test_rowspan3_shortrow() {\n    test_html(\n        br#\"<table>\n    <tr><th>H0</th><th>H1</th><th>H2</th><th>H3</th><th>H4</th></tr>\n    <tr><td>foo</td></tr>\n    <tr><td>X</td><td rowspan=2>B\n            F b b o t the</td><td colspan=3>oh no</td></tr>\n    <tr><td>Y</td><td rowspan=1 colspan=2>O</td><td rowspan=2>one two thr fou fiv</td></tr>\n    <tr><td>W</td><td colspan=2>Hmm oh</td></tr>\n</table>\"#,\n        r#\"───┬────┬──┬──┬─────\nH0 │H1  │H2│H3│H4   \n───┴────┴──┴──┴─────\nfoo\n───┬────┬───────────\nX  │B F │oh no      \n   │b b │           \n───┤o t ├─────┬─────\nY  │the │O    │one  \n   │    │     │two  \n───┼────┴──┬──┤thr  \nW  │Hmm oh │  │fou  \n   │       │  │fiv  \n───┴───────┴──┴─────\n\"#,\n        20,\n    );\n}\n\n#[test]\nfn test_rowspan_underflow() {\n    test_html(\n        br#\"<table>\n  <tr>\n    <td></td>\n    <td rowspan=\"2\"></td>\n  </tr>\n  <tr>\n    <td></td>\n  </tr>\n</table>\n        \"#,\n        \"\\n\",\n        20,\n    );\n}\n\n#[test]\nfn test_issue_187() {\n    let html = br#\"<div><table><tbody><tr><td><div><table><tbody><tr><td><div><pre>na na na na na na na na na na na na na na na</p></div></td></tr>/<tbody></table></div></td></tr>/<tbody></table></div>\"#;\n    let _ = crate::config::plain().string_from_read(&html[..], 17);\n}\n\nfn get_lines(html: &[u8], width: usize) -> Vec<TaggedLine<Vec<()>>> {\n    config::plain().lines_from_read(html, width).unwrap()\n}\n\n#[test]\nfn frag_simple() {\n    use TaggedLineElement::*;\n    assert_eq!(\n        get_lines(br#\"<p id=\"my_id\">Hi</p>\"#, 10)\n            .into_iter()\n            .map(|line| line.into_iter().collect::<Vec<_>>())\n            .collect::<Vec<_>>(),\n        vec![vec![\n            FragmentStart(\"my_id\".into()),\n            Str(TaggedString {\n                s: \"Hi\".into(),\n                tag: Default::default(),\n            })\n        ],]\n    );\n}\n\n#[test]\nfn frag_list() {\n    use TaggedLineElement::*;\n    assert_eq!(\n        get_lines(\n            br#\"<ul id=\"my_id\">\n            <li>One</li>\n            <li>Two</li>\n        </ul>\"#,\n            10\n        )\n        .into_iter()\n        .map(|line| line.into_iter().collect::<Vec<_>>())\n        .collect::<Vec<_>>(),\n        vec![\n            vec![\n                FragmentStart(\"my_id\".into()),\n                Str(TaggedString {\n                    s: \"* One\".into(),\n                    tag: Default::default(),\n                })\n            ],\n            vec![Str(TaggedString {\n                s: \"* Two\".into(),\n                tag: Default::default(),\n            })],\n        ]\n    );\n}\n\n#[test]\nfn test_serialise_full() {\n    let dom = config::plain().parse_html(&b\"<p>Hello</p>\"[..]).unwrap();\n    let mut result = Vec::new();\n    dom.serialize(&mut result).unwrap();\n    let s = str::from_utf8(&result).unwrap();\n    assert_eq!(s, \"<html><head></head><body><p>Hello</p></body></html>\");\n}\n\n#[cfg(feature = \"css\")]\nmod css_tests {\n    use super::{\n        test_html_coloured, test_html_coloured_conf, test_html_conf, test_html_css, test_html_style,\n    };\n\n    #[test]\n    fn test_disp_none() {\n        test_html_css(\n            br#\"\n          <style>\n              .hide { display: none; }\n          </style>\n        <p>Hello</p>\n        <p class=\"hide\">Ignore</p>\n        <p>There</p>\"#,\n            r#\"Hello\n\nThere\n\"#,\n            20,\n        );\n\n        // Same as above, but style supplied separately.\n        test_html_style(\n            br#\"\n        <p>Hello</p>\n        <p class=\"hide\">Ignore</p>\n        <p>There</p>\"#,\n            \" .hide { display: none; }\",\n            r#\"Hello\n\nThere\n\"#,\n            20,\n        );\n    }\n\n    #[test]\n    fn test_selector_elementname() {\n        test_html_css(\n            br#\"\n          <style>\n              div { display: none; }\n          </style>\n        <p>Hello</p>\n        <div>Ignore</div>\n        <p>There</p>\"#,\n            r#\"Hello\n\nThere\n\"#,\n            20,\n        );\n    }\n\n    #[test]\n    fn test_selector_aoc() {\n        test_html_css(\n            br#\"\n          <style>\n              .someclass > * > span > span {\n                  display: none;\n              }\n          </style>\n        <p>Hello</p>\n        <div class=\"someclass\">Ok\n        <p>\n         <span>Span1<span>Span2</span></span>\n        </p>\n        <div>\n         <span>Span1<span>Span2</span></span>\n        </div>\n        </div>\n        <p>There</p>\"#,\n            r#\"Hello\n\nOk\n\nSpan1\n\nSpan1\n\nThere\n\"#,\n            20,\n        );\n    }\n\n    #[test]\n    fn test_coloured_a() {\n        test_html_coloured(\n            br##\"\n          <style>\n              .red {\n                  color:#FF0000;\n              }\n          </style>\n        <p>Test <a class=\"red\" href=\"foo\">red</a></p>\n        \"##,\n            r#\"Test <R>red</R>\n\"#,\n            20,\n        );\n    }\n\n    #[test]\n    fn test_bgcoloured() {\n        test_html_coloured(\n            br##\"\n          <style>\n              .red {\n                  color:#FF0000;\n                  background-color:#00FF00;\n              }\n          </style>\n        <p>Test <span class=\"red\">red</span></p>\n        \"##,\n            r#\"Test <g><R>red</R></g>\n\"#,\n            20,\n        );\n    }\n\n    #[test]\n    fn test_bgcoloured2() {\n        test_html_coloured(\n            br##\"\n          <style>\n              .red {\n                  color:#FF0000;\n                  background-color:#00FF00;\n              }\n          </style>\n        <p>Test <span class=\"red\">red</span> and <span style=\"color: #00ff00\">green</span></p>\n        \"##,\n            r#\"Test <g><R>red</R></g> and <G>green</G>\n\"#,\n            20,\n        );\n    }\n\n    #[test]\n    fn test_bgcoloured3() {\n        test_html_coloured(\n            br##\"\n          <style>\n              .but {\n                  background-color:#00FF00;\n              }\n          </style>\n        <p>Test <span class=\"but\">Two words</span> bg</p>\n        \"##,\n            r#\"Test <g>Two words</g> bg\n\"#,\n            20,\n        );\n    }\n\n    #[test]\n    fn test_coloured_element() {\n        test_html_coloured(\n            br##\"\n          <style>\n              .red {\n                  color:#FF0000;\n              }\n          </style>\n        <p>Test <blah class=\"red\" href=\"foo\">red</blah></p>\n        \"##,\n            r#\"Test <R>red</R>\n\"#,\n            20,\n        );\n    }\n\n    #[test]\n    fn test_color_attr() {\n        test_html_coloured(\n            br##\"\n        <p>Test <font color=\"red\">red</font></p>\n        \"##,\n            r#\"Test <R>red</R>\n\"#,\n            20,\n        );\n    }\n\n    #[test]\n    fn test_css_lists() {\n        test_html_coloured(\n            br##\"\n          <style>\n              .red {\n                  color:#FF0000;\n              }\n          </style>\n        <ul>\n          <li class=\"red\">Line one</li>\n          <li>Line <span class=\"red\">two</span></li>\n        </ul>\n        \"##,\n            r#\"* <R>Line one</R>\n* Line <R>two</R>\n\"#,\n            20,\n        );\n        test_html_coloured(\n            br##\"\n          <style>\n              .red {\n                  color:#FF0000;\n              }\n          </style>\n        <ol>\n          <li class=\"red\">Line one</li>\n          <li>Line <span class=\"red\">two</span></li>\n        </ul>\n        \"##,\n            r#\"1. <R>Line one</R>\n2. Line <R>two</R>\n\"#,\n            20,\n        );\n    }\n\n    #[test]\n    fn test_coloured_multi() {\n        use super::test_colour_map;\n        let config = crate::config::rich().use_doc_css();\n        let dom = config\n            .parse_html(\n                &br##\"\n          <style>\n              .red {\n                  color:#FF0000;\n              }\n          </style>\n        <p>Test paragraph with <span class=\"red\">red</span> text</p>\n        \"##[..],\n            )\n            .unwrap();\n        let rt = config.dom_to_render_tree(&dom).unwrap();\n        assert_eq!(\n            config\n                .render_coloured(rt.clone(), 10, test_colour_map)\n                .unwrap(),\n            r#\"Test\nparagraph\nwith <R>red</R>\ntext\n\"#\n        );\n        assert_eq!(\n            config\n                .render_coloured(rt.clone(), 100, test_colour_map)\n                .unwrap(),\n            r#\"Test paragraph with <R>red</R> text\n\"#\n        );\n    }\n\n    #[test]\n    fn test_coloured_important() {\n        use super::test_colour_map;\n        let config = crate::config::rich().use_doc_css();\n        let dom = config\n            .parse_html(\n                &br##\"\n          <style>\n              .red {\n                  color:#FF0000 !important;\n              }\n          </style>\n        <p>Test paragraph with <span class=\"red\">red</span> text</p>\n        \"##[..],\n            )\n            .unwrap();\n        let rt = config.dom_to_render_tree(&dom).unwrap();\n        assert_eq!(\n            config\n                .render_coloured(rt.clone(), 10, test_colour_map)\n                .unwrap(),\n            r#\"Test\nparagraph\nwith <R>red</R>\ntext\n\"#\n        );\n        assert_eq!(\n            config\n                .render_coloured(rt.clone(), 100, test_colour_map)\n                .unwrap(),\n            r#\"Test paragraph with <R>red</R> text\n\"#\n        );\n    }\n\n    #[test]\n    fn test_wrap_word_boundaries() {\n        let html = br#\"<head><style>em { color: white; }</style></head>\n            <body>\n                Hello *<em>there</em>* boo\"#;\n        test_html_coloured(html, \"Hello *<W>there</W>* boo\\n\", 20);\n        test_html_coloured(html, \"Hello *<W>there</W>*\\nboo\\n\", 15);\n        test_html_coloured(html, \"Hello *<W>there</W>*\\nboo\\n\", 14);\n        test_html_coloured(html, \"Hello *<W>there</W>*\\nboo\\n\", 13);\n        test_html_coloured(html, \"Hello\\n*<W>there</W>* boo\\n\", 12);\n        test_html_coloured(html, \"Hello\\n*<W>there</W>* boo\\n\", 11);\n        test_html_coloured(html, \"Hello\\n*<W>there</W>*\\nboo\\n\", 10);\n        test_html_coloured(html, \"Hello\\n*<W>there</W>*\\nboo\\n\", 7);\n        test_html_coloured(html, \"Hello\\n*<W>there</W>\\n* boo\\n\", 6);\n        test_html_coloured(html, \"Hello\\n*<W>ther</W>\\n<W>e</W>*\\nboo\\n\", 5);\n        test_html_coloured(html, \"Hell\\no\\n*<W>the</W>\\n<W>re</W>*\\nboo\\n\", 4);\n        test_html_coloured(\n            html,\n            \"H\\ne\\nl\\nl\\no\\n*\\n<W>t</W>\\n<W>h</W>\\n<W>e</W>\\n<W>r</W>\\n<W>e</W>\\n*\\nb\\no\\no\\n\",\n            1,\n        );\n    }\n\n    #[test]\n    fn test_max_height_0() {\n        test_html_css(\n            br#\"\n        <div style=\"max-height: 0; overflow-y: hidden\">This should be hidden</div>\n        <p>Hello</p>\"#,\n            r#\"Hello\n\"#,\n            20,\n        );\n    }\n\n    #[test]\n    fn test_height_0() {\n        test_html_css(\n            br#\"\n        <div style=\"height: 0; overflow: hidden\">This should be hidden</div>\n        <p>Hello</p>\"#,\n            r#\"Hello\n\"#,\n            20,\n        );\n    }\n\n    #[test]\n    fn test_selector_hash() {\n        test_html_coloured(\n            br#\"<head><style>\n        #foo {\n            color: #f00;\n        }\n        p#bar {\n            color: #0f0;\n        }\n        div#baz {\n            color: #00f;\n        }\n        *#qux {\n            color: #fff;\n        }\n        </style></head><body>\n\n        <p id=\"foo\">Foo</p>\n        <p id=\"bar\">Bar</p>\n        <p id=\"baz\">Baz</p>\n        <p id=\"qux\">Qux</p>\n        \"#,\n            r#\"<R>Foo</R>\n\n<G>Bar</G>\n\nBaz\n\n<W>Qux</W>\n\"#,\n            20,\n        );\n    }\n\n    #[test]\n    fn test_selector_child_desc() {\n        test_html_coloured(\n            br#\"<head><style>\n        p.d span { /* descendent */\n            color: #f00;\n        }\n        p.c > span { /* child */\n            color: #0f0;\n        }\n        </style></head><body>\n\n        <p class=\"d\">X<span>C</span><dummy><span>D</span></dummy>Y</p>\n        <p class=\"c\"><span>C</span><dummy><span>D</span></dummy></p>\n        \"#,\n            r#\"X<R>CD</R>Y\n\n<G>C</G>D\n\"#,\n            20,\n        );\n    }\n\n    #[test]\n    fn test_colour_row() {\n        test_html_coloured(\n            br#\"<head><style>\n        tr.r {\n            color: #f00;\n        }\n        </style></head><body>\n        <table>\n         <tr>\n           <td>Row</td><td>One</td>\n         </tr>\n         <tr class=\"r\">\n           <td>Row</td><td>Two</td>\n         </tr>\n         <tr>\n           <td>Row</td><td>Three</td>\n         </tr>\n        </table>\n        \"#,\n            r#\"───┬─────\nRow│One  \n───┼─────\n<R>Row│Two  </R>\n<R>───┼─────</R>\nRow│Three\n───┴─────\n\"#,\n            20,\n        );\n    }\n\n    #[test]\n    fn test_css_levels() {\n        test_html_coloured_conf(\n            br#\"\n        <head><style>\n            .doc_red { color: #f00; }\n            .doc_red_imp { color: #f00 !important; }\n        </style></head>\n        <body>\n            <p class=\"doc_red\">Doc red</p>\n            <p class=\"agent_green\">Agent green</p>\n            <p class=\"user_blue\">User blue</p>\n            <p class=\"doc_red agent_green\">Doc vs agent</p>\n            <p class=\"agent_green user_blue\">Agent vs user</p>\n            <p class=\"user_blue doc_red\">User vs doc</p>\n            <p class=\"doc_red agent_green_imp\">Doc vs agent!</p>\n            <p class=\"agent_green_imp user_blue\">Agent! vs user</p>\n            <p class=\"user_blue_imp doc_red\">User! vs doc</p>\n            <p class=\"doc_red_imp agent_green_imp\">Doc! vs agent!</p>\n            <p class=\"agent_green_imp user_blue_imp\">Agent! vs user!</p>\n            <p class=\"user_blue_imp doc_red_imp\">User! vs doc!</p>\n        </body>\"#,\n            r#\"<R>Doc red</R>\n\n<G>Agent green</G>\n\n<B>User blue</B>\n\n<R>Doc vs agent</R>\n\n<B>Agent vs user</B>\n\n<R>User vs doc</R>\n\n<G>Doc vs agent!</G>\n\n<G>Agent! vs user</G>\n\n<B>User! vs doc</B>\n\n<G>Doc! vs agent!</G>\n\n<G>Agent! vs user!</G>\n\n<B>User! vs doc!</B>\n\"#,\n            80,\n            |conf| {\n                conf.add_agent_css(\n                    r#\"\n                .agent_green { color: #0f0; }\n                .agent_green_imp { color: #0f0 !important; }\n                \"#,\n                )\n                .unwrap()\n                .add_css(\n                    r#\"\n                .user_blue { color: #00f; }\n                .user_blue_imp { color: #00f !important; }\n                \"#,\n                )\n                .unwrap()\n            },\n        );\n    }\n\n    #[test]\n    fn test_pre_wrap() {\n        test_html_conf(\n            br#\"<p class=\"prewrap\">Hi\n a\n  b\n   x  longword\nc  d  e\n</p>\"#,\n            r#\"Hi\n a\n  b\n   x\nlongword\nc  d  e\n\"#,\n            10,\n            |conf| {\n                conf.add_css(r#\".prewrap { white-space: pre-wrap; }\"#)\n                    .unwrap()\n            },\n        );\n\n        test_html_conf(\n            br#\"<p class=\"prewrap\">Test wrapping of some normal text in pre-wrap mode</p>\"#,\n            r#\"Test wrapping of\nsome normal text\nin pre-wrap mode\n\"#,\n            17,\n            |conf| {\n                conf.add_css(r#\".prewrap { white-space: pre-wrap; }\"#)\n                    .unwrap()\n            },\n        );\n\n        test_html_conf(\n            br#\"<p class=\"prewrap\">This  para  has  double  spacing  which  should  survive  except  at  line  breaks</p>\"#,\n            r#\"This  para  has  double  spacing\nwhich  should  survive  except\nat  line  breaks\n\"#,\n            33,\n            |conf| {\n                conf.add_css(r#\".prewrap { white-space: pre-wrap; }\"#)\n                    .unwrap()\n            },\n        );\n\n        test_html_conf(\n            br#\"<p class=\"prewrap\">The last line shouldn't have a space at the start</p>\"#,\n            r#\"The last line shouldn't\nhave a space at the\nstart\n\"#,\n            23,\n            |conf| {\n                conf.add_css(r#\".prewrap { white-space: pre-wrap; }\"#)\n                    .unwrap()\n            },\n        );\n    }\n\n    #[test]\n    fn test_nth_child() {\n        test_html_coloured(\n            br#\"\n          <style>\n              li:nth-child(even) {\n                  color: #f00;\n              }\n          </style>\n          <body><ul>\n              <li>One</li>\n              <li>Two</li>\n              <li>Three</li>\n              <li>Four</li>\n              <li>Five</li>\n          </ul>\"#,\n            r#\"* One\n* <R>Two</R>\n* Three\n* <R>Four</R>\n* Five\n\"#,\n            20,\n        );\n        test_html_coloured(\n            br#\"\n          <style>\n              li:nth-child(odd) {\n                  color: #f00;\n              }\n          </style>\n          <body><ul>\n              <li>One</li>\n              <li>Two</li>\n              <li>Three</li>\n              <li>Four</li>\n              <li>Five</li>\n          </ul>\"#,\n            r#\"* <R>One</R>\n* Two\n* <R>Three</R>\n* Four\n* <R>Five</R>\n\"#,\n            20,\n        );\n        test_html_coloured(\n            br#\"\n          <style>\n              li:nth-child(-n+3) {\n                  color: #f00;\n              }\n          </style>\n          <body><ul>\n              <li>One</li>\n              <li>Two</li>\n              <li>Three</li>\n              <li>Four</li>\n              <li>Five</li>\n          </ul>\"#,\n            r#\"* <R>One</R>\n* <R>Two</R>\n* <R>Three</R>\n* Four\n* Five\n\"#,\n            20,\n        );\n        test_html_coloured(\n            br#\"\n          <style>\n              li:nth-child(2) {\n                  color: #f00;\n              }\n          </style>\n          <body><ul>\n              <li>One</li>\n              <li>Two</li>\n              <li>Three</li>\n              <li>Four</li>\n              <li>Five</li>\n          </ul>\"#,\n            r#\"* One\n* <R>Two</R>\n* Three\n* Four\n* Five\n\"#,\n            20,\n        );\n        test_html_coloured(\n            br#\"\n          <style>\n              li:nth-child(n+3):nth-child(-n+5) {\n                  color: #f00;\n              }\n          </style>\n          <body><ul>\n              <li>One</li>\n              <li>Two</li>\n              <li>Three</li>\n              <li>Four</li>\n              <li>Five</li>\n              <li>Six</li>\n              <li>Seven</li>\n              <li>Eight</li>\n              <li>Nine</li>\n              <li>Ten</li>\n          </ul>\"#,\n            r#\"* One\n* Two\n* <R>Three</R>\n* <R>Four</R>\n* <R>Five</R>\n* Six\n* Seven\n* Eight\n* Nine\n* Ten\n\"#,\n            20,\n        );\n    }\n\n    #[test]\n    fn test_before_after() {\n        test_html_coloured(\n            br#\"\n        <style>\n          span.bracketed::before {\n              content: \"[\";\n          }\n          span.bracketed::after {\n              content: \"]\";\n          }\n        </style>\n        <body>\n        <p>Hello <span class=\"bracketed\">world</span>!</p>\n        </body>\"#,\n            r#\"Hello [world]!\n\"#,\n            80,\n        );\n    }\n\n    #[test]\n    fn test_wrap_nbsp_style() {\n        test_html_css(\n            br#\"<p>Don't break a line between the words <span style=\"white-space: pre\">Foo and Bar</span></p>\"#,\n            r#\"Don't break a line between the words\nFoo and Bar\n\"#,\n40,\n        );\n    }\n}\n\n#[test]\nfn test_issue_252() {\n    test_html(\n        b\"<table><td rowspan=8><tr><tr>\",\n        r\"\n\n\",\n        10,\n    );\n}\n\n#[test]\n#[cfg(feature = \"xml\")]\nfn test_xml1() {\n    use crate::config::XmlMode;\n\n    let doc = br#\"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n<title>Testing, testing</title>\n</head>\n<body>\n<h1/>\n<p>Not Heading</p>\n</body>\n</html>\"#;\n\n    // Parsing XHTML as HTML - expect wrong output.\n    test_html_conf(\n        doc,\n        r\"# Not Heading\n\",\n        20,\n        |conf| conf.xml_mode(XmlMode::Html),\n    );\n    // Parsing with default settings - detects XML correctly\n    test_html(\n        doc,\n        r\"Not Heading\n\",\n        20,\n    );\n    // Parsing XHTML as XHTML - expect correct output.\n    test_xml(\n        doc,\n        r\"Not Heading\n\",\n        20,\n    );\n    // Parsing XHTML as XHTML - using config and explicit Xml mode.\n    test_html_conf(\n        doc,\n        r\"Not Heading\n\",\n        20,\n        |conf| conf.xml_mode(XmlMode::Xhtml),\n    );\n}\n\n#[cfg(feature = \"css_ext\")]\nmod css_ext_tests {\n    use super::test_html_conf_rendertree;\n    use crate::{\n        Colour, ComputedStyle, RenderNode, RenderNodeInfo, RenderTree, StyleOrigin, TextStyle,\n        WhiteSpace,\n    };\n\n    // Text node\n    fn t(s: &str) -> RenderNode {\n        RenderNode::new(RenderNodeInfo::Text(s.into()))\n    }\n    // Text node\n    fn t_s(s: &str, st: ComputedStyle) -> RenderNode {\n        RenderNode::new_styled(RenderNodeInfo::Text(s.into()), st)\n    }\n    // Em node containing text\n    fn em(ns: &[RenderNode]) -> RenderNode {\n        RenderNode::new(RenderNodeInfo::Em(ns.into()))\n    }\n    // Container\n    fn c(ns: &[RenderNode]) -> RenderNode {\n        RenderNode::new(RenderNodeInfo::Container(ns.into()))\n    }\n    /*\n    // Container\n    fn c_s(ns: &[RenderNode], st: ComputedStyle) -> RenderNode {\n        RenderNode::new_styled(RenderNodeInfo::Container(ns.into()), st)\n    }\n    // Block\n    fn b(ns: &[RenderNode]) -> RenderNode {\n        RenderNode::new(RenderNodeInfo::Block(ns.into()))\n    }\n    */\n    // Container\n    fn b_s(ns: &[RenderNode], st: ComputedStyle) -> RenderNode {\n        RenderNode::new_styled(RenderNodeInfo::Block(ns.into()), st)\n    }\n    /*\n    // Pre\n    fn pre(text: &str) -> RenderNode {\n        let st: ComputedStyle = st().pre();\n\n        RenderNode::new_styled(RenderNodeInfo::Container(vec![t_s(text, st.inherit())]), st)\n    }\n    */\n    fn d<T: Default>() -> T {\n        Default::default()\n    }\n\n    fn st() -> ComputedStyle {\n        d()\n    }\n\n    trait TestAddStyle {\n        fn fg(self, r: u8, g: u8, b: u8) -> Self;\n        fn pre(self) -> Self;\n    }\n    impl TestAddStyle for ComputedStyle {\n        fn fg(mut self, r: u8, g: u8, b: u8) -> Self {\n            self.colour.maybe_update(\n                false,               // important\n                StyleOrigin::Author, // origin\n                Default::default(),  // specificity\n                Colour { r, g, b },\n            );\n            self\n        }\n        fn pre(mut self) -> Self {\n            self.white_space.maybe_update(\n                false,               // important\n                StyleOrigin::Author, // origin\n                Default::default(),  // specificity\n                WhiteSpace::Pre,\n            );\n            self.internal_pre = true;\n            self\n        }\n    }\n\n    #[test]\n    fn render_1() {\n        test_html_conf_rendertree(\n            br#\"<p style=\"color: red\">Hi</p>\"#,\n            |conf| conf.use_doc_css(),\n            RenderTree(c(&[c(&[c(&[b_s(&[t(\"Hi\")], st().fg(255, 0, 0))])])])),\n        );\n    }\n\n    fn syntax_all_blue(text: &str) -> Vec<(TextStyle, &str)> {\n        vec![(TextStyle::foreground(Colour { r: 0, g: 0, b: 255 }), text)]\n    }\n    #[test]\n    fn syntax_pre_em() {\n        test_html_conf_rendertree(\n            br#\"<pre>Hi <em>there</em></pre>\"#,\n            |conf| {\n                conf.use_doc_css()\n                    .register_highlighter(\"syn\", Box::new(syntax_all_blue))\n                    .add_agent_css(\"pre { x-syntax: syn; }\")\n                    .expect(\"CSS parsing\")\n            },\n            RenderTree(c(&[c(&[c(&[b_s(\n                &[\n                    t_s(\"Hi \", st().fg(0, 0, 255)),\n                    em(&[t_s(\"there\", st().fg(0, 0, 255))]),\n                ],\n                st().pre(),\n            )])])])),\n        );\n    }\n}\n"
  }
]