Skip to content

Commit

Permalink
PDF support (#97)
Browse files Browse the repository at this point in the history
* Add vl-convert-pdf crate that builds on svg2pdf to add text overlays

* Add PDF functions to converter, add vl2pdf and vg2pdf CLI subcommands

* Compute metrics on base PDF fonts, choose and scale

* Python PDF conversion functions

* skip text without fill

* Handle multiple spans within single text chunk

* Comments, add basic right-to-left support

* Add pdf scale to CLI

* Add Python PDF tests

* Add VlConvert as PDF creator

* Update thirdparty_rust.yaml

* Add show-warnings for vl2pdf

* Update README.md

* update to svg2pdf 0.7.0

* Fix chunk coordinate conversion

* Add README and example for vl-convert-pdf
  • Loading branch information
jonmmease authored Sep 13, 2023
1 parent 2ecec47 commit 416b592
Show file tree
Hide file tree
Showing 25 changed files with 1,342 additions and 19 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ jobs:
python-version: ${{ matrix.options[1] }}
- name: install Python dependencies
run: |
python -m pip install pytest maturin scikit-image
python -m pip install pytest maturin scikit-image pypdfium2
- name: Build development wheel
run: |
maturin develop -m vl-convert-python/Cargo.toml
Expand Down
46 changes: 46 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"vl-convert",
"vl-convert-python",
"vl-convert-vendor",
"vl-convert-pdf",
]

[profile.release]
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ Commands:
vl2vg Convert a Vega-Lite specification to a Vega specification
vl2svg Convert a Vega-Lite specification to an SVG image
vl2png Convert a Vega-Lite specification to an PNG image
vl2jpeg Convert a Vega-Lite specification to an JPEG image
vl2pdf Convert a Vega-Lite specification to a PDF image
vg2svg Convert a Vega specification to an SVG image
vg2png Convert a Vega specification to an PNG image
vg2jpeg Convert a Vega specification to an JPEG image
vg2pdf Convert a Vega specification to an PDF image
ls-themes List available themes
cat-theme Print the config JSON for a theme
help Print this message or the help of the given subcommand(s)
Expand Down Expand Up @@ -99,6 +103,9 @@ The Vega JavaScript library supports exporting chart specifications directly to

VlConvert generates PNG images by first exporting charts to SVG as described above, then converting the SVG image to a PNG image using the `resvg` crate.

## Vega(-Lite) to PDF
VlConvert generates PDF images by first exporting charts to SVG as described above, then converting them to PDF with a combination of the `svg2pdf` crate and custom text layout and font embedding logic. Font embedding currently supports TrueType fonts only.

## Limitations
### PNG Performance
VlConvert relies on the [`resvg`](https://github.com/RazrFalcon/resvg) Rust library for rendering PNG images from the SVG produced by Vega. resvg is a very accurate implementation of SVG rendering, but it is not GPU accelerated and can be somewhat slow when asked to render charts with many individual marks (e.g. large scatter plots). For a single pane scatter plot, the performance is on the order of 1 second per 1000 points.
Expand Down
26 changes: 25 additions & 1 deletion thirdparty_rust.yaml

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions vl-convert-pdf/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "vl-convert-pdf"
version = "0.13.1"
edition = "2021"
license = "BSD-3-Clause"
readme = "README.md"
homepage = "https://github.com/jonmmease/vl-convert"
repository = "https://github.com/jonmmease/vl-convert"

[dependencies]
svg2pdf = "0.7.0"
pdf-writer = "0.8.1"
usvg = "0.35.0"
anyhow = "1.0.71"
ttf-parser = "0.19.1"
subsetter = "0.1.1"
miniz_oxide = "0.7.1"
siphasher = "0.3.10"
itertools = "0.11.0"
unicode-bidi = "0.3.13"
11 changes: 11 additions & 0 deletions vl-convert-pdf/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Copyright 2023 Jon Mease

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 changes: 11 additions & 0 deletions vl-convert-pdf/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## vl-convert-pdf
This crate builds on top of the excellent [svg2pdf](https://github.com/typst/svg2pdf) project (created by the [typst](https://typst.app/) team) to convert SVG images to PDF files with embedded text. svg2pdf supports converting text into geometric paths using the [usvg](https://github.com/RazrFalcon/resvg) library, but it doesn't yet support embedding text (which is required for text selection, text copying, screen readers, etc.).

This project uses svg2pdf to handle everything in the SVG image except text, and then adds an embedded text layer on top using the [pdf-writer](https://github.com/typst/pdf-writer) library (also created by the typst team). The text embedding logic handles TrueType fonts and is heavily inspired by the implementation in the [typst](https://github.com/typst/typst) typesetting project.

In the future, it would be great if the text embedding logic in typst could be extracted and used by svg2pdf (making this crate unnecessary), but in the meantime this crate will maintain an independent implementation of text embedding.

Many thanks to the typst team for their support in https://github.com/typst/svg2pdf/issues/21.

## Example
See examples/pdf_conversion.rs for example usage
21 changes: 21 additions & 0 deletions vl-convert-pdf/examples/pdf_conversion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use std::fs;
use usvg::fontdb::Database;
use usvg::TreeParsing;
use vl_convert_pdf::svg_to_pdf;

fn main() {
let tree = usvg::Tree::from_str(r#"
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<text id="text1" x="100" y="100" text-anchor="middle" font-family="Arial" font-size="20" fill="black">
Hello, World!
</text>
<rect id="frame" x="1" y="1" width="198" height="198" fill="none" stroke="black"/>
</svg>
"#, &Default::default()).unwrap();

let mut font_db = Database::new();
font_db.load_system_fonts();

let pdf_bytes = svg_to_pdf(&tree, &font_db, 1.0).unwrap();
fs::write("target/hello.pdf".to_string(), pdf_bytes).unwrap();
}
Loading

0 comments on commit 416b592

Please sign in to comment.