From a156cda1797584fa7f202e1a763fbbe3baaa0bf6 Mon Sep 17 00:00:00 2001 From: Martin Haug Date: Mon, 6 Dec 2021 18:09:14 +0100 Subject: [PATCH] Add docs and CI --- .github/dependabot.yml | 6 ++ .github/workflows/rust.yml | 35 ++++++++++++ Cargo.toml | 8 ++- README.md | 63 +++++++++++++++++++++ src/lib.rs | 109 ++++++++++++++++++++++++++++++++++++- tests/example.svg | 14 +++++ 6 files changed, 232 insertions(+), 3 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/rust.yml create mode 100644 README.md create mode 100644 tests/example.svg diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..573ceb71 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "cargo" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" \ No newline at end of file diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 00000000..e361f7a9 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,35 @@ +name: Continuous integration + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + ci: + runs-on: ubuntu-latest + strategy: + matrix: + rust: [stable] + + steps: + - name: Checkout source code + uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + + - name: Build + uses: actions-rs/cargo@v1 + with: + command: build + + - name: Test + uses: actions-rs/cargo@v1 + with: + command: test diff --git a/Cargo.toml b/Cargo.toml index 466efe81..6e12e244 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,14 @@ [package] name = "svg2pdf" version = "0.1.0" +authors = ["Martin Haug "] edition = "2018" +description = "Convert SVG files to PDFs." +repository = "https://github.com/typst/svg2pdf" +readme = "README.md" license = "MIT OR Apache-2.0" +categories = ["encoding", "graphics", "multimedia"] +keywords = ["svg", "pdf", "vector-graphics", "conversion"] [features] default = ["png", "jpeg", "text", "system-fonts"] @@ -14,5 +20,5 @@ system-fonts = ["usvg/system-fonts", "usvg/memmap-fonts"] [dependencies] image = { version = "0.23", default-features = false, optional = true } miniz_oxide = "0.4" -pdf-writer = { git = "https://github.com/typst/pdf-writer", rev = "e1ec200" } +pdf-writer = "0.4.1" usvg = { version = "0.19", default-features = false } diff --git a/README.md b/README.md new file mode 100644 index 00000000..f3c96459 --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# svg2pdf + +[![Build status](https://github.com/typst/svg2pdf/workflows/Continuous%20integration/badge.svg)](https://github.com/typst/svg2pdf/actions) +[![Current crates.io release](https://img.shields.io/crates/v/svg2pdf)](https://crates.io/crates/svg2pdf) +[![Documentation](https://img.shields.io/badge/docs.rs-svg2pdf-66c2a5?labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K)](https://docs.rs/svg2pdf/) + +Convert SVG files to PDFs. + +This crate allows to convert static (i.e. non-interactive) SVG files to +either standalone PDF files or Form XObjects that can be embedded in another +PDF file and used just like images. + +The conversion will translate the SVG content to PDF without rasterizing it, +so no quality is lost. + +## Example +This example reads an SVG file and writes the corresponding PDF back to the disk. + +```rust +let svg = std::fs::read_to_string("tests/example.svg").unwrap(); + +// This can only fail if the SVG is malformed. This one is not. +let pdf = svg2pdf::convert_str(&svg, svg2pdf::Options::default()).unwrap(); + +// ... and now you have a Vec which you could write to a file or +// transmit over the network! +std::fs::write("target/example.pdf", pdf).unwrap(); +``` + +## Supported features +- Path drawing with fills and strokes +- Gradients +- Patterns +- Clip paths +- Masks +- Transformation matrices +- Respecting the `keepAspectRatio` attribute +- Raster images and nested SVGs + +Filters are not currently supported and embedded raster images are not color +managed. Instead, they use PDF's `DeviceRGB` color space. + +## Contributing +We are looking forward to receiving your bugs and feature requests in the Issues +tab. We would also be very happy to accept PRs for bug fixes, features, or +refactorings! + +If you want to contribute but are uncertain where to start, yo could look into +filters like `feBlend` and `feColorMatrix` that can be implemented with +transparency groups and color spaces, respectively. We'd be happy to assist you +with your PR's, so feel free to post Work in Progress PRs if marked as such. +Please be kind to the maintainers and other contributors. If you feel that there +are any problems, please feel free to reach out to us privately. + +Thanks to each and every prospective contributor for the effort you (plan to) +invest in this project and for adopting it! + +## License +svg2pdf is licensed under a MIT / Apache 2.0 dual license. + +Users and consumers of the library may choose which of those licenses they want +to apply whereas contributors have to accept that their code is in compliance +and distributed under the terms of both of these licenses. diff --git a/src/lib.rs b/src/lib.rs index 57569366..4513e0e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,39 @@ -//! Convert SVG files to PDFs. +/*! Convert SVG files to PDFs. + +This crate allows to convert static (i.e. non-interactive) SVG files to +either standalone PDF files or Form XObjects that can be embedded in another +PDF file and used just like images. + +The conversion will translate the SVG content to PDF without rasterizing it, +so no quality is lost. + +## Example +This example reads an SVG file and writes the corresponding PDF back to the disk. + +```rust +let svg = std::fs::read_to_string("tests/example.svg").unwrap(); + +// This can only fail if the SVG is malformed. This one is not. +let pdf = svg2pdf::convert_str(&svg, svg2pdf::Options::default()).unwrap(); + +// ... and now you have a Vec which you could write to a file or +// transmit over the network! +std::fs::write("target/example.pdf", pdf).unwrap(); +``` + +## Supported features +- Path drawing with fills and strokes +- Gradients +- Patterns +- Clip paths +- Masks +- Transformation matrices +- Respecting the `keepAspectRatio` attribute +- Raster images and nested SVGs + +Filters are not currently supported and embedded raster images are not color +managed. Instead, they use PDF's `DeviceRGB` color space. +*/ use std::collections::HashMap; @@ -258,12 +293,82 @@ pub fn convert_tree(tree: &Tree, options: Options) -> Vec { /// /// The resulting object can be used by registering a name and the `id` with a /// page's [`/XObject`](pdf_writer::writers::Resources::x_objects) resources -/// dictionary and then invoking the [`/Do`](pdf_writer::Content::x_object) +/// dictionary and then invoking the [`Do`](pdf_writer::Content::x_object) /// operator with the name in the page's content stream. /// /// As the conversion process may need to create multiple indirect objects in /// the PDF, this function allocates consecutive IDs starting at `id` for its /// objects and returns the next available ID for your future writing. +/// +/// ## Example +/// Write a PDF file with some text and an SVG graphic. +/// +/// ```rust +/// use svg2pdf; +/// use pdf_writer::{Content, Finish, Name, PdfWriter, Rect, Ref, Str}; +/// +/// // Allocate the indirect reference IDs and names. +/// let catalog_id = Ref::new(1); +/// let page_tree_id = Ref::new(2); +/// let page_id = Ref::new(3); +/// let font_id = Ref::new(4); +/// let content_id = Ref::new(5); +/// let svg_id = Ref::new(6); +/// let font_name = Name(b"F1"); +/// let svg_name = Name(b"S1"); +/// +/// // Start writing a PDF. +/// let mut writer = PdfWriter::new(); +/// writer.catalog(catalog_id).pages(page_tree_id); +/// writer.pages(page_tree_id).kids([page_id]).count(1); +/// +/// // Set up a simple A4 page. +/// let mut page = writer.page(page_id); +/// page.media_box(Rect::new(0.0, 0.0, 595.0, 842.0)); +/// page.parent(page_tree_id); +/// page.contents(content_id); +/// +/// // Add the font and, more importantly, the SVG to the resource dictionary +/// // so that it can be referenced in the content stream. +/// let mut resources = page.resources(); +/// resources.x_objects().pair(svg_name, svg_id); +/// resources.fonts().pair(font_name, font_id); +/// resources.finish(); +/// page.finish(); +/// +/// // Set a predefined font, so we do not have to load anything extra. +/// writer.type1_font(font_id).base_font(Name(b"Helvetica")); +/// +/// // Let's add an SVG graphic to this file. +/// // We need to load its source first and manually parse it into a usvg Tree. +/// let svg = std::fs::read_to_string("tests/example.svg").unwrap(); +/// let tree = usvg::Tree::from_str(&svg, &usvg::Options::default().to_ref()).unwrap(); +/// +/// // Then, we will write it to the page as the 6th indirect object. +/// // +/// // This call allocates some indirect object reference IDs for itself. If we +/// // wanted to write some more indirect objects afterwards, we could use the +/// // return value as the next unused reference ID. +/// svg2pdf::convert_tree_into(&tree, svg2pdf::Options::default(), &mut writer, svg_id); +/// +/// // Write a content stream with some text and our SVG. +/// let mut content = Content::new(); +/// content +/// .begin_text() +/// .set_font(font_name, 16.0) +/// .next_line(108.0, 734.0) +/// .show(Str(b"Look at my wonderful vector graphic!")) +/// .end_text(); +/// +/// // Add our graphic. +/// content +/// .transform([300.0, 0.0, 0.0, 300.0, 147.5, 385.0]) +/// .x_object(svg_name); +/// +/// // Write the file to the disk. +/// writer.stream(content_id, &content.finish()); +/// std::fs::write("target/embedded.pdf", writer.finish()).unwrap(); +/// ``` pub fn convert_tree_into( tree: &Tree, options: Options, diff --git a/tests/example.svg b/tests/example.svg new file mode 100644 index 00000000..4edf8584 --- /dev/null +++ b/tests/example.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + \ No newline at end of file