Skip to content

feat(cli): add PNG output with converter-style format selection#12

Merged
twangodev merged 4 commits into
mainfrom
feat/png-output
May 26, 2026
Merged

feat(cli): add PNG output with converter-style format selection#12
twangodev merged 4 commits into
mainfrom
feat/png-output

Conversation

@twangodev

@twangodev twangodev commented May 26, 2026

Copy link
Copy Markdown
Owner

Adds PNG output to sdocx-cli, with the output format chosen ffmpeg-style — inferred from the -o extension and overridable with a -f/--format flag — by rasterizing the existing SVG render through resvg.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added --format flag to specify output as SVG or PNG
    • Output format automatically detected from file extension
    • PNG export capability now supported for documents
  • Tests

    • Enhanced test coverage for format detection and PNG conversion

Review Change Stack

twangodev added 3 commits May 25, 2026 16:35
Wires resolve_format, Format::ext, and svg_to_png into main() via a new
write_page helper; adds --format/-f flag to Cli; removes all transient
#[allow(dead_code)] attrs; adds renders_sample_to_valid_png end-to-end test.
Copilot AI review requested due to automatic review settings May 26, 2026 01:37
@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@twangodev, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 44 minutes and 12 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 67852930-d4d3-486e-a07e-a4a871f9e825

📥 Commits

Reviewing files that changed from the base of the PR and between ba61a6b and 929341e.

📒 Files selected for processing (1)
  • crates/sdocx-cli/src/main.rs
📝 Walkthrough

Walkthrough

Added optional --format flag to CLI for explicit SVG/PNG output selection with format inference from file extension (.svg/.png) and SVG default. Integrated resvg dependency and refactored output writing into format-aware write_page function. Both single-page and multi-page rendering now resolve and respect the selected format throughout.

Changes

SVG/PNG Format Selection and Output

Layer / File(s) Summary
Format abstraction, conversion, and dependency
crates/sdocx-cli/Cargo.toml, crates/sdocx-cli/src/main.rs
Added resvg 0.47.0 dependency. Introduced Format enum (Svg, Png) with resolve_format helper implementing flag-first precedence (explicit flag > extension inference > SVG default), Format::ext() for mapping formats to extensions, and svg_to_png for byte conversion via resvg rendering.
CLI --format argument wiring
crates/sdocx-cli/src/main.rs
Added optional --format CLI argument using ValueEnum to expose Svg/Png selection, enabling explicit format override of extension-based inference.
Format-aware output writing
crates/sdocx-cli/src/main.rs
Refactored output into write_page(path, svg, format) that branches on format: writes SVG text directly or converts SVG to PNG bytes before writing, with error handling for both paths.
Main function format resolution and output
crates/sdocx-cli/src/main.rs
Updated main to resolve format via resolve_format, compute output base path extension from the resolved format, and route both single-page and multi-page rendering through write_page with the selected format.
Format resolution and PNG conversion tests
crates/sdocx-cli/src/main.rs
Added tests for resolve_format precedence (flag overrides extension, extension inferred when no flag, SVG is default), extension inference error handling, svg_to_png PNG header validation (signature and IHDR dimensions), and sample document rendering to PNG.

Sequence Diagram

sequenceDiagram
  participant User as User / CLI
  participant Resolve as resolve_format()
  participant Ext as Format::ext()
  participant Write as write_page()
  participant Conv as svg_to_png()
  participant FS as File System

  User->>Resolve: --format flag + output path
  Resolve->>Resolve: Check explicit flag first
  alt Flag provided
    Resolve->>Resolve: Use flag value
  else No flag
    Resolve->>Resolve: Infer from output extension
    Resolve->>Resolve: Default to Svg if unknown
  end
  Resolve-->>User: Resolved Format (Svg/Png)
  User->>Ext: Get file extension
  Ext-->>User: .svg or .png
  User->>Write: (path, svg_text, format)
  alt Format::Svg
    Write->>FS: Write SVG text directly
  else Format::Png
    Write->>Conv: svg_to_png(svg_text)
    Conv-->>Write: PNG bytes
    Write->>FS: Write PNG bytes
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 With formats now in bloom, both SVG and PNG delight,
The CLI waves its magic flag, making output shine so bright,
From pennant to picture, resvg weaves conversion's thread,
Each layer flows together—resolve, write, render instead! 🎨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding PNG output capability with converter-style format selection based on file extension or explicit flag.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/png-output

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds PNG output support to sdocx-cli, selecting output format ffmpeg-style (infer from -o extension, override with -f/--format) by rasterizing the existing SVG render via resvg.

Changes:

  • Introduces Format + --format flag and extension-based format inference (resolve_format).
  • Adds SVG→PNG rasterization (svg_to_png) and routes output writing through write_page.
  • Adds unit tests covering format resolution and basic PNG validity, and adds the resvg dependency.

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 3 comments.

File Description
crates/sdocx-cli/src/main.rs Adds format selection, SVG→PNG rendering, output writing logic, and related tests.
crates/sdocx-cli/Cargo.toml Adds resvg dependency needed for PNG output.
Cargo.lock Locks new transitive dependencies introduced by resvg.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/sdocx-cli/src/main.rs Outdated
Comment on lines +26 to +28
match output.and_then(|p| p.extension()).and_then(|e| e.to_str()) {
Some("svg") => Ok(Format::Svg),
Some("png") => Ok(Format::Png),
Comment on lines +45 to +48
fn svg_to_png(svg: &str) -> Result<Vec<u8>, String> {
let mut opt = resvg::usvg::Options::default();
// Load system fonts so <text> elements render instead of being silently dropped.
opt.fontdb_mut().load_system_fonts();

#[test]
fn renders_sample_to_valid_png() {
let doc = sdocx::parse("../../samples/handwritten.sdocx").expect("parse sample");

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/sdocx-cli/src/main.rs (1)

645-669: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize output extension to the resolved format.

Line 645 keeps the user-provided extension unchanged, so --format can produce content/extension mismatches (e.g., SVG bytes in .png files). This breaks converter-style expectations and can confuse downstream tools.

Proposed fix
-    let output_base = cli
-        .output
-        .unwrap_or_else(|| cli.path.with_extension(format.ext()));
+    let output_base = cli
+        .output
+        .unwrap_or_else(|| cli.path.with_extension(format.ext()))
+        .with_extension(format.ext());
@@
-            let ext = output_base
-                .extension()
-                .and_then(|e| e.to_str())
-                .unwrap_or(format.ext());
-            let path = output_base.with_file_name(format!("{stem}_page{i}.{ext}"));
+            let path = output_base.with_file_name(format!("{stem}_page{i}.{}", format.ext()));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sdocx-cli/src/main.rs` around lines 645 - 669, The output path
currently preserves the user-provided extension (output_base) which can mismatch
the resolved format; change code that constructs output paths to normalize the
extension to the resolved format.ext(): when computing output_base (from
cli.output or cli.path) call with_extension(format.ext()) so single-page
write_page gets the correct extension, and in the multi-page loop derive ext
from format.ext() (not output_base.extension()) when building path for each page
(see symbols: output_base, cli.output, cli.path, format.ext(), write_page,
render_page_svg). Ensure all generated file names use the format.ext()
extension.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/sdocx-cli/src/main.rs`:
- Around line 26-31: The extension matching in main.rs currently only accepts
lowercase "svg"/"png"; update the match on output.and_then(|p|
p.extension()).and_then(|e| e.to_str()) to be case-insensitive by either mapping
the extension string to a canonical form (e.g., call to_ascii_lowercase() on the
&str before matching) or by using conditional arms with eq_ignore_ascii_case
(e.g., Some(ext) if ext.eq_ignore_ascii_case("svg") => Ok(Format::Svg), similar
for "png"); leave the error branch to report the original extension value if
present. Ensure the match still returns Ok(Format::Svg)/Ok(Format::Png) and
Err(...) on unknown values.

---

Outside diff comments:
In `@crates/sdocx-cli/src/main.rs`:
- Around line 645-669: The output path currently preserves the user-provided
extension (output_base) which can mismatch the resolved format; change code that
constructs output paths to normalize the extension to the resolved format.ext():
when computing output_base (from cli.output or cli.path) call
with_extension(format.ext()) so single-page write_page gets the correct
extension, and in the multi-page loop derive ext from format.ext() (not
output_base.extension()) when building path for each page (see symbols:
output_base, cli.output, cli.path, format.ext(), write_page, render_page_svg).
Ensure all generated file names use the format.ext() extension.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9c644553-3e2c-4129-bc1a-29c98de1925c

📥 Commits

Reviewing files that changed from the base of the PR and between 4e8243d and ba61a6b.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • crates/sdocx-cli/Cargo.toml
  • crates/sdocx-cli/src/main.rs

Comment thread crates/sdocx-cli/src/main.rs Outdated
@twangodev twangodev merged commit e36b644 into main May 26, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants