svg2pdf

repository·main·Indexed 19 days ago

https://github.com/typst/svg2pdf

A tool and library for converting SVG files into PDF documents. It provides a CLI for conversion with controls for DPI, raster scaling, and text rendering (embedding fonts or converting text to paths). As a library, it allows converting usvg::Tree objects into standalone PDF byte buffers or integrating them into existing pdf-writer workflows as XObjects. It supports paths, gradients, patterns, clip paths, and masks, with automatic conversion of quadratic Bézier curves to cubic Bézier curves for PDF compatibility.

Tokens
6.4K
Snippets
25
Records
30
Agent score
64%

What's inside svg2pdf

  1. Supported and Unsupported SVG features

    main

    Supported

    • Paths (simple and complex fills)
    • Gradients
    • Patterns
    • Clip paths and Masks
    • Transformations
    • Viewbox
    • Text
    • Raster images and nested SVGs

    Unsupported

    • spreadMethod attribute in gradients
    • Color management for raster images (uses PDF's DeviceRGB space)
    • Many SVG2 features
  2. How SVG clip paths are rendered to PDF

    main

    The svg2pdf engine handles SVG clip paths by distinguishing between 'simple' and 'complex' paths to balance PDF compatibility and rendering accuracy.

    Simple Clip Paths

    A clip path is considered simple if it meets these criteria:

    • It contains no nested clip paths (except for the root clip path itself).
    • All shapes within the clip path use the FillRule::NonZero rule, OR it uses the FillRule::EvenOdd rule with exactly one shape.

    Simple clip paths are rendered using native PDF clip path operators (clip_nonzero() or clip_even_odd()). This is efficient and widely supported.

    Complex Clip Paths

    If a clip path is too complex for native PDF operators (e.g., nested clip paths with varying transforms or multiple EvenOdd shapes), the engine falls back to using soft masks (Alpha masks).

    Note: While soft masks ensure conformance with the SVG specification, they are more computationally expensive and may occasionally encounter rendering issues in certain browsers like Safari.

    Implementation Logic Summary

    1. Check Complexity: Evaluate the ClipPath root group.
    2. Branching:
      • If Simple: Flatten the path segments, apply the base transform, and use native PDF clipping.
      • If Complex: Create a Form XObject containing the clipped content and return a graphics state reference that applies it as a MaskType::Alpha soft mask.
  3. How SVG path rendering handles paint order

    main

    The svg2pdf engine manages the complexity of SVG styles by explicitly separating the filling and stroking phases of a path. This is necessary to support:

    • Different Paint Orders: Respecting PaintOrder::FillAndStroke vs PaintOrder::StrokeAndFill.
    • Advanced Fills/Strokes: Correctly applying opacities to patterns, linear gradients, or radial gradients.
    • Opacity Management: Ensuring that a stroke opacity of 0.5 applied to a pattern affects the whole pattern correctly rather than just the individual elements within the pattern.
  4. Configure page DPI with `PageOptions`

    main

    The PageOptions struct controls the physical properties of the resulting PDF page. Currently, it only supports setting the DPI (Dots Per Inch).

    OptionTypeDefaultDescription
    dpif3272.0The DPI assumed for the conversion. This affects the scaling of the SVG content to fit the PDF coordinate system.
    let page_options = PageOptions {
        dpi: 300.0,
    };
  5. Configure PDF conversion with `ConversionOptions`

    main

    The ConversionOptions struct allows you to fine-tune how the SVG is translated to PDF content.

    OptionTypeDefaultDescription
    compressbooltrueWhether to use FlateDecode compression on content streams.
    raster_scalef321.5Scaling factor for rasterized effects (like filters). Higher values improve quality but increase file size.
    embed_textbooltrueIf true, text is selectable. If false, text is converted to vector paths.
    pdfaboolfalseIf true, ensures content streams do not contain elements forbidden by PDF/A-2b.
    let options = ConversionOptions {
        compress: true,
        raster_scale: 2.0,
        embed_text: true,
        pdfa: false,
    };
  6. Manage shared resources with ResourceContainer

    main

    The ResourceContainer struct is used to manage and deduplicate shared resources (such as fonts, images, and color spaces) during the SVG to PDF conversion process. It tracks resources by their PDF Ref and assigns them unique names within the PDF structure.

    Key Behaviors

    • Deduplication: If you attempt to add the same Ref multiple times, the container will return the same name previously assigned, ensuring that identical objects are not duplicated in the output PDF.
    • Resource Types: It supports several resource categories including XObject, Shading, Pattern, GraphicsState, Font (requires text feature), and ColorSpace.
    • Finalization: The finish method transfers all collected pending resources into a provided pdf_writer::writers::Resources object and configures the necessary ProcSet (including Pdf, Text, ImageColor, and ImageGrayscale).
    use pdf_writer::types::Ref;
    use svg2pdf::util::resources::ResourceContainer;
    
    let mut container = ResourceContainer::new();
    
    // Add resources using their PDF references
    let xobject_name = container.add_x_object(some_ref);
    let font_name = container.add_font(another_ref);
    
    // Finalize the resources into a PDF writer's Resources object
    // mut resources: pdf_writer::writers::Resources
    container.finish(&mut resources);
  7. Create a soft mask for gradient opacities

    main

    Use create_shading_soft_mask to generate a luminosity-based soft mask that renders the opacity of gradient stops as grayscale shading.

    This is required when a gradient's stops have an opacity value less than 1.0. If all stops are fully opaque, the function returns None and no mask is created.

    Note: This function relies on the bbox (bounding box) of the element to define the mask area.

    create_shading_soft_mask(
        paint: &Paint,
        chunk: &mut Chunk,
        ctx: &mut Context,
        bbox: Rect,
    ) -> Option<Ref
  8. Render an SVG mask using `render`

    main

    The render function converts an SVG Mask into a PDF content stream. It creates a mask object reference, adds it to the ResourceContainer as a graphics state, and applies that state to the current PDF content stream using set_parameters.

    pub fn render(
        group: &Group,
        mask: &Mask,
        chunk: &mut Chunk,
        content: &mut Content,
        ctx: &mut Context,
        rc: &mut ResourceContainer,
    ) -> Result<()>
  9. Create a shading pattern from a Paint object

    main

    Use create_shading_pattern to convert a usvg::Paint (specifically linear or radial gradients) into a PDF shading pattern object.

    Note: This function ignores stop opacities. If your gradient contains stops with transparency (opacity < 1.0), you must also call create_shading_soft_mask to handle the transparency correctly.

    create_shading_pattern(
        paint: &Paint,
        chunk: &mut Chunk,
        ctx: &mut Context,
        accumulated_transform: &Transform,
    ) -> Ref
  10. Convert an SVG tree to a PDF XObject with `tree_to_xobject`

    main

    Use tree_to_xobject to convert a usvg::Tree into a PDF XObject (similar to an image). This is useful for embedding SVG content as a reusable, sized object within a PDF.

    Key behaviors:

    • It calculates the bounding box based on the tree size.
    • It applies a scaling matrix to fit the content into a 1x1 coordinate space.
    • If ctx.options.compress is enabled, it applies the Filter::FlateDecode to the XObject.
    • It manages resource allocation and finishing via the provided Context and a new ResourceContainer.
    let x_ref = tree_to_xobject(
        tree,  // &usvg::Tree
        chunk, // &mut pdf_writer::Chunk
        ctx,   // &mut Context
    )?;
    // Returns a pdf_writer::Ref to the created XObject
  11. Finalize and compress PDF content

    main

    The finish_content method handles the finalization of a pdf_writer::Content object. It automatically respects the compress setting within your ConversionOptions:

    • If options.compress is true, the content is deflated (compressed) before being returned.
    • If options.compress is false, the raw content is returned as a Vec<u8>.
    // 'ctx' is your Context instance
    // 'content' is a pdf_writer::Content object
    let final_bytes = ctx.finish_content(content);
  12. Create a PDF mask object with `create`

    main

    The create function generates a new PDF mask object (XObject) from an SVG Mask.

    Key behaviors:

    • It handles nested masks recursively.
    • It applies a clip path to the mask's bounding rectangle to ensure compatibility with browsers (like Firefox) that may misinterpret bounding boxes under certain transforms.
    • It applies FlateDecode compression if ctx.options.compress is enabled.
    • It configures the XObject with standard transparency settings (non-isolated, no knockout) and sets the color space to the context's sRGB reference.
    • It returns a Ref to a new graphics state that uses this mask as a soft_mask with the appropriate PDF mask subtype.
    pub fn create(
        parent: &Group,
        mask: &Mask,
        chunk: &mut Chunk,
        ctx: &mut Context,
    ) -> Result<Ref>