diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f12b119..5256d0f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,8 +13,9 @@ repos: - id: check-toml - id: check-yaml - id: end-of-file-fixer - exclude: \.changes/.*\.md + exclude: server/.* - id: trailing-whitespace + exclude: server/.* - repo: https://github.com/python-jsonschema/check-jsonschema rev: 0.22.0 @@ -28,12 +29,14 @@ repos: hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] + exclude: server/.*\.py - repo: https://github.com/psf/black rev: 23.3.0 hooks: - id: black language_version: python3.10 + exclude: server/.*\.py - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.1.1 @@ -43,6 +46,7 @@ repos: - types-docutils - types-requests - typing-extensions + exclude: server/.*\.py - repo: https://github.com/pre-commit/pre-commit rev: v3.2.2 diff --git a/resvg_py.pyi b/resvg_py.pyi index 6e7dffa..3a033ef 100644 --- a/resvg_py.pyi +++ b/resvg_py.pyi @@ -4,14 +4,23 @@ import os from numpy import ndarray +class ShapeRendering: + OptimizeSpeed: ShapeRendering + CrispEdges: ShapeRendering + GeometricPrecision: ShapeRendering + class SVGOptions: def __init__( self: SVGOptions, *, dpi: float = 96.0, + font_family: str = "Times New Roman", + font_size: float = 12.0, + languages: list[str] | None = None, + shape_rendering: ShapeRendering, + resources_dir: os.PathLike | None = None, default_width: float = 100.0, default_height: float = 100.0, - resources_dir: os.PathLike | None = None, ) -> None: ... class Resvg: diff --git a/src/lib.rs b/src/lib.rs index c47e1f8..cce4a68 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,60 +1,11 @@ +mod options; + use pyo3::prelude::*; use pyo3::types::PyBytes; use resvg::tiny_skia::Pixmap; use usvg::{Size, Tree, TreeParsing}; -/// SVG parsing and rendering options. -/// -/// TODO(edgarmondragon): Add more options. -#[derive(Clone)] -#[pyclass] -pub struct SVGOptions { - /// Target DPI. - /// - /// Impacts units conversion. - /// - /// Default: 96.0 - dpi: f64, - - /// Directory that will be used during relative paths resolving. - /// - /// Expected to be the same as the directory that contains the SVG file, - /// but can be set to any. - /// - /// Default: `None - resources_dir: Option, - - /// Default viewport width to assume if there is no `viewBox` attribute and - /// the `width` is relative. - /// - /// Default: 100.0 - default_width: f64, - - /// Default viewport height to assume if there is no `viewBox` attribute and - /// the `height` is relative. - /// - /// Default: 100.0 - default_height: f64, -} - -#[pymethods] -impl SVGOptions { - #[new] - #[pyo3(signature = (*, dpi = 96.0, default_width = 100.0, default_height = 100.0, resources_dir = None))] - fn new( - dpi: f64, - default_width: f64, - default_height: f64, - resources_dir: Option, - ) -> Self { - Self { - dpi, - default_width, - default_height, - resources_dir, - } - } -} +use crate::options::{SVGOptions, ShapeRendering}; /// A Python class for rendering SVGs. #[pyclass] @@ -86,9 +37,15 @@ impl Resvg { let options = if let Some(options) = &self.options { usvg::Options { dpi: options.dpi, - default_size: Size::new(options.default_width, options.default_height).unwrap(), + font_family: options.font_family.clone(), + font_size: options.font_size, + languages: options.languages.clone().unwrap_or_default(), + shape_rendering: options.shape_rendering.clone().into(), + text_rendering: options.text_rendering.clone().into(), + image_rendering: options.image_rendering.clone().into(), resources_dir: options.resources_dir.clone(), - ..usvg::Options::default() + default_size: Size::new(options.default_width, options.default_height).unwrap(), + image_href_resolver: usvg::ImageHrefResolver::default(), } } else { usvg::Options::default() @@ -140,6 +97,7 @@ impl RenderedImage { /// Python bindings for resvg. #[pymodule] fn resvg_py(_py: Python, m: &PyModule) -> PyResult<()> { + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/src/options.rs b/src/options.rs new file mode 100644 index 0000000..2118fee --- /dev/null +++ b/src/options.rs @@ -0,0 +1,186 @@ +use pyo3::prelude::*; + +/// A shape rendering method. +/// +/// `shape-rendering` attribute in the SVG. +#[derive(Clone)] +#[pyclass] +pub enum ShapeRendering { + OptimizeSpeed, + CrispEdges, + GeometricPrecision, +} + +impl From for usvg::ShapeRendering { + fn from(shape_rendering: ShapeRendering) -> Self { + match shape_rendering { + ShapeRendering::OptimizeSpeed => Self::OptimizeSpeed, + ShapeRendering::CrispEdges => Self::CrispEdges, + ShapeRendering::GeometricPrecision => Self::GeometricPrecision, + } + } +} + +/// Specifies the default text rendering method. +/// +/// Will be used when an SVG element's `text-rendering` property is set to `auto`. +/// +/// Default: OptimizeLegibility +#[derive(Clone)] +#[pyclass] +pub enum TextRendering { + OptimizeSpeed, + OptimizeLegibility, + GeometricPrecision, +} + +impl From for usvg::TextRendering { + fn from(text_rendering: TextRendering) -> Self { + match text_rendering { + TextRendering::OptimizeSpeed => Self::OptimizeSpeed, + TextRendering::OptimizeLegibility => Self::OptimizeLegibility, + TextRendering::GeometricPrecision => Self::GeometricPrecision, + } + } +} + +/// An image rendering method. +/// +/// `image-rendering` attribute in the SVG. +#[derive(Clone)] +#[pyclass] +pub enum ImageRendering { + OptimizeQuality, + OptimizeSpeed, +} + +impl From for usvg::ImageRendering { + fn from(image_rendering: ImageRendering) -> Self { + match image_rendering { + ImageRendering::OptimizeQuality => Self::OptimizeQuality, + ImageRendering::OptimizeSpeed => Self::OptimizeSpeed, + } + } +} + +/// SVG parsing and rendering options. +/// +/// TODO(edgarmondragon): Add more options. +#[derive(Clone)] +#[pyclass] +pub struct SVGOptions { + /// Target DPI. + /// + /// Impacts units conversion. + /// + /// Default: 96.0 + pub dpi: f64, + + /// A default font family. + /// + /// Will be used when no `font-family` attribute is set in the SVG. + /// + /// Default: Times New Roman + pub font_family: String, + + /// A default font size. + /// + /// Will be used when no `font-size` attribute is set in the SVG. + /// + /// Default: 12 + pub font_size: f64, + + /// Languages to use when resolving `systemLanguage` conditional attributes. + /// + /// Format: en, en-US. + /// + /// Default: `[en]` + pub languages: Option>, + + /// Specifies the default shape rendering method. + /// + /// Will be used when an SVG element's `shape-rendering` property is set to `auto`. + /// + /// Default: GeometricPrecision + pub shape_rendering: ShapeRendering, + + /// Specifies the default text rendering method. + /// + /// Will be used when an SVG element's `text-rendering` property is set to `auto`. + /// + /// Default: OptimizeLegibility + pub text_rendering: TextRendering, + + /// Specifies the default image rendering method. + /// + /// Will be used when an SVG element's `image-rendering` property is set to `auto`. + /// + /// Default: OptimizeQuality + pub image_rendering: ImageRendering, + + /// Directory that will be used during relative paths resolving. + /// + /// Expected to be the same as the directory that contains the SVG file, + /// but can be set to any. + /// + /// Default: `None + pub resources_dir: Option, + + /// Default viewport width to assume if there is no `viewBox` attribute and + /// the `width` is relative. + /// + /// Default: 100.0 + pub default_width: f64, + + /// Default viewport height to assume if there is no `viewBox` attribute and + /// the `height` is relative. + /// + /// Default: 100.0 + pub default_height: f64, +} + + +#[pymethods] +impl SVGOptions { + #[new] + #[pyo3( + signature = ( + *, + dpi = 96.0, + font_family = "Times New Roman".to_string(), + font_size = 12.0, + languages = None, + shape_rendering = ShapeRendering::GeometricPrecision, + text_rendering = TextRendering::OptimizeLegibility, + image_rendering = ImageRendering::OptimizeQuality, + resources_dir = None, + default_width = 100.0, + default_height = 100.0, + ) + )] + fn new( + dpi: f64, + font_family: String, + font_size: f64, + languages: Option>, + shape_rendering: ShapeRendering, + text_rendering: TextRendering, + image_rendering: ImageRendering, + resources_dir: Option, + default_width: f64, + default_height: f64, + ) -> Self { + Self { + dpi, + font_family, + font_size, + languages, + shape_rendering, + text_rendering, + image_rendering, + resources_dir, + default_width, + default_height, + } + } +} diff --git a/tests/test_render.py b/tests/test_render.py index 030ae3a..a6c9aa1 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -14,13 +14,22 @@ @pytest.mark.parametrize( - "svg_file", + ("svg_file", "options"), [ - pytest.param("resources/examples/svg/octocat.svg", id="octocat"), + pytest.param( + "resources/examples/svg/octocat.svg", + resvg_py.SVGOptions( + shape_rendering=resvg_py.ShapeRendering.GeometricPrecision, + ), + id="octocat", + ), ], ) -def test_render(snapshot: Snapshot, svg_file: str) -> None: - options = resvg_py.SVGOptions() +def test_render( + snapshot: Snapshot, + svg_file: str, + options: resvg_py.SVGOptions, +) -> None: r = resvg_py.Resvg(options) with Path(svg_file).open("r") as input_file: