Skip to content

feat(markdown): emit asset:N hrefs for embedded images - #70

Open
alexiskowalski wants to merge 17 commits into
firecrawl:mainfrom
alexiskowalski:feat/markdown-asset-refs-63
Open

feat(markdown): emit asset:N hrefs for embedded images#70
alexiskowalski wants to merge 17 commits into
firecrawl:mainfrom
alexiskowalski:feat/markdown-asset-refs-63

Conversation

@alexiskowalski

Copy link
Copy Markdown

Summary

Fixes #63: embedded assets already appear as Inline::Image { source: Asset(id) } in the document model, but Markdown dropped them to alt text only (or nothing when alt was empty), so to_markdown_bytes() lost positional image markers.

Changes

  • Render ImageSource::Asset as ![alt](asset:N), where N is the index into Document.assets.
  • Keep ImageSource::Unavailable as alt-text-only (no bytes to reference).
  • Document the asset:N scheme in the README.
  • Update Markdown snapshots that previously collapsed embedded images to bare alt text.

Callers can rewrite asset:N after writing asset files out, or keep using the document model for the bytes. This is complementary to approaches that inject resolved URLs.

Test plan

  • Unit tests for asset href rendering (with and without alt).
  • Snapshot updates for docx/odt/rtf/doc/epub fixtures that embed pictures.
  • cargo test --locked render::markdown
  • cargo test --locked --test snapshots
  • CI

@mirabilitum

Copy link
Copy Markdown

cargo test --locked --test snapshots fails with this PR as-is — five snapshots are missing or incorrect (ole, odp, pptx-order, rich's OLE line, and epub has the wrong content: one image + text tiny dot, not two images). This patch fixes all five; git apply from the repo root:

diff --git a/tests/snapshots/snapshots__docx__handmade-ole.docx.snap b/tests/snapshots/snapshots__docx__handmade-ole.docx.snap
index c9626e8..b9e155d 100644
--- a/tests/snapshots/snapshots__docx__handmade-ole.docx.snap
+++ b/tests/snapshots/snapshots__docx__handmade-ole.docx.snap
@@ -4,4 +4,4 @@ expression: output
 ---
 Before the object
 
-Embedded object: Excel.Sheet.12
+![Embedded object: Excel.Sheet.12](asset:0)
diff --git a/tests/snapshots/snapshots__docx__handmade-rich.docx.snap b/tests/snapshots/snapshots__docx__handmade-rich.docx.snap
index 38580ca..9114d99 100644
--- a/tests/snapshots/snapshots__docx__handmade-rich.docx.snap
+++ b/tests/snapshots/snapshots__docx__handmade-rich.docx.snap
@@ -17,6 +17,6 @@ Rich objects follow.
 
 ![tiny dot image](asset:0)
 
-Embedded object: Excel.Sheet.12
+![Embedded object: Excel.Sheet.12](asset:1)
 
 After the objects.
diff --git a/tests/snapshots/snapshots__epub__book.epub.snap b/tests/snapshots/snapshots__epub__book.epub.snap
index c0dd0de..96bbaa9 100644
--- a/tests/snapshots/snapshots__epub__book.epub.snap
+++ b/tests/snapshots/snapshots__epub__book.epub.snap
@@ -26,7 +26,7 @@ A list of things:
 
 See [Chapter Two](#epub-text-ch002-xhtml-chapter-two) for the table, or jump straight to [the marked paragraph](#epub-text-ch002-xhtml-markpoint).
 
-![tiny dot](asset:0) ![tiny dot](asset:0)
+![tiny dot](asset:0) tiny dot
 
 <a id="epub-text-ch002-xhtml-chapter-two"></a>
 
diff --git a/tests/snapshots/snapshots__odp__pres.odp.snap b/tests/snapshots/snapshots__odp__pres.odp.snap
index d46d347..e70d94b 100644
--- a/tests/snapshots/snapshots__odp__pres.odp.snap
+++ b/tests/snapshots/snapshots__odp__pres.odp.snap
@@ -17,6 +17,8 @@ Numbers Slide
 | --- | --- |
 | North | 42 |
 
+![](asset:0)
+
 Grouped shapes below.
 
 Inside a group shape.
diff --git a/tests/snapshots/snapshots__pptx__handmade-order.pptx.snap b/tests/snapshots/snapshots__pptx__handmade-order.pptx.snap
index 782ed77..652da2e 100644
--- a/tests/snapshots/snapshots__pptx__handmade-order.pptx.snap
+++ b/tests/snapshots/snapshots__pptx__handmade-order.pptx.snap
@@ -8,4 +8,4 @@ Kicker before the title
 
 Body after the title
 
-Quarterly numbers
+![Quarterly numbers](asset:0)

With it applied, cargo test --locked is green (210 unit + 8 snapshot tests).

@alexiskowalski

Copy link
Copy Markdown
Author

Thanks @mirabilitum — applied your snapshot patch for ole / rich / epub / odp / pptx-order. cargo test --locked --test snapshots should be green now.

@cubic-dev-ai cubic-dev-ai 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.

1 issue found and verified against the latest diff

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="README.md">

<violation number="1" location="README.md:132">
P3: The new sentence says every image and embedded object becomes an `![alt](asset:N)` ref, but `ImageSource::Unavailable` images still render as alt-text-only (and as nothing when alt is empty). Qualify the claim, e.g. "images that carry embedded bytes become ...".</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread README.md
- **One output for every format.** Each format parses into a shared document model and renders through a single Markdown serializer, so escaping, tables, heading anchors, and footnotes behave identically whether the input was a `.doc` from 2003 or a `.pptx` from yesterday.
- **Full document structure.** Headings with anchors, bold/italic/strikethrough, inline code and code blocks, links and internal cross-references, bulleted/numbered/nested/task lists with the source's own numbering, tables with merged cells and header rows, block quotes, footnotes and endnotes, and speaker notes.
- **Embedded assets.** Images and embedded objects render as their alt text in the Markdown, and the raw bytes stay available on the document model, tagged with their media type. Images with an external URL become ordinary Markdown images.
- **Embedded assets.** Images and embedded objects become Markdown image refs of the form `![alt](asset:N)`, where `N` indexes `Document.assets` (raw bytes + media type). Rewrite those hrefs after writing the files out, or consume the document model directly. Images with an external URL become ordinary Markdown images.

@cubic-dev-ai cubic-dev-ai Bot Aug 28, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The new sentence says every image and embedded object becomes an ![alt](asset:N) ref, but ImageSource::Unavailable images still render as alt-text-only (and as nothing when alt is empty). Qualify the claim, e.g. "images that carry embedded bytes become ...".

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At README.md, line 132:

<comment>The new sentence says every image and embedded object becomes an `![alt](asset:N)` ref, but `ImageSource::Unavailable` images still render as alt-text-only (and as nothing when alt is empty). Qualify the claim, e.g. "images that carry embedded bytes become ...".</comment>

<file context>
@@ -129,7 +129,7 @@ let document = anydoc::to_document(&bytes, None)?;
 - **One output for every format.** Each format parses into a shared document model and renders through a single Markdown serializer, so escaping, tables, heading anchors, and footnotes behave identically whether the input was a `.doc` from 2003 or a `.pptx` from yesterday.
 - **Full document structure.** Headings with anchors, bold/italic/strikethrough, inline code and code blocks, links and internal cross-references, bulleted/numbered/nested/task lists with the source's own numbering, tables with merged cells and header rows, block quotes, footnotes and endnotes, and speaker notes.
-- **Embedded assets.** Images and embedded objects render as their alt text in the Markdown, and the raw bytes stay available on the document model, tagged with their media type. Images with an external URL become ordinary Markdown images.
+- **Embedded assets.** Images and embedded objects become Markdown image refs of the form `![alt](asset:N)`, where `N` indexes `Document.assets` (raw bytes + media type). Rewrite those hrefs after writing the files out, or consume the document model directly. Images with an external URL become ordinary Markdown images.
 - **Content-based format detection.** The format is read from the bytes themselves (PDF header, RTF open group, OLE stream names, ZIP package mimetype), so mislabeled files still convert correctly.
 - **Fast.** Pure Rust, no ML models, no external services. Median conversion time is under 5ms per document.
</file context>
Suggested change
- **Embedded assets.** Images and embedded objects become Markdown image refs of the form `![alt](asset:N)`, where `N` indexes `Document.assets` (raw bytes + media type). Rewrite those hrefs after writing the files out, or consume the document model directly. Images with an external URL become ordinary Markdown images.
- **Embedded assets.** Images and embedded objects with embedded bytes become Markdown image refs of the form `![alt](asset:N)`, where `N` indexes `Document.assets` (raw bytes + media type). Rewrite those hrefs after writing the files out, or consume the document model directly. Images with an external URL become ordinary Markdown images.
Fix with cubic

@alexiskowalski
alexiskowalski force-pushed the feat/markdown-asset-refs-63 branch from fca038c to 4a45add Compare August 28, 2026 12:34
Signed-off-by: alexiskowalski <alexiskowalski@users.noreply.github.com>
Signed-off-by: alexiskowalski <alexiskowalski@users.noreply.github.com>
Signed-off-by: alexiskowalski <alexiskowalski@users.noreply.github.com>
Signed-off-by: alexiskowalski <alexiskowalski@users.noreply.github.com>
Signed-off-by: alexiskowalski <alexiskowalski@users.noreply.github.com>
Signed-off-by: alexiskowalski <alexiskowalski@users.noreply.github.com>
Signed-off-by: alexiskowalski <alexiskowalski@users.noreply.github.com>
Signed-off-by: alexiskowalski <alexiskowalski@users.noreply.github.com>
Signed-off-by: alexiskowalski <alexiskowalski@users.noreply.github.com>
Signed-off-by: alexiskowalski <alexiskowalski@users.noreply.github.com>
Signed-off-by: alexiskowalski <alexiskowalski@users.noreply.github.com>
Signed-off-by: alexiskowalski <alexiskowalski@users.noreply.github.com>
Signed-off-by: alexiskowalski <alexiskowalski@users.noreply.github.com>
Signed-off-by: alexiskowalski <alexiskowalski@users.noreply.github.com>
Signed-off-by: alexiskowalski <alexiskowalski@users.noreply.github.com>
Signed-off-by: alexiskowalski <alexiskowalski@users.noreply.github.com>
Signed-off-by: alexiskowalski <alexiskowalski@users.noreply.github.com>
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.

Expose embedded image assets in markdown output

2 participants