Skip to content

Commit 2eb9ece

Browse files
committed
chore: release v0.6.3 (image + link resolution fixes)
Issue #3 follow-up: the 0.6.1 SVG fix never ran for the reported document, because the image loader rejected absolute paths (and relative paths escaping the doc's directory) before reading a byte. Local images now load from any readable path — display-only and safe, since the bytes must decode as an image and only ever reach the screen as pixels; remote fetching stays opt-in. - Fixed: absolute/out-of-tree, percent-encoded, and file:// image paths load; the same policy applies to followed .md links - Fixed: failed images state why ("image not found" / "cannot decode image") instead of a bare placeholder indistinguishable from a rendering bug - Added: linked images, image galleries, raw HTML <img>, data: URIs, ~/ expansion, ?raw=true / #fragment suffixes, EXIF orientation - Added: resvg "raster-images" so <image> elements inside SVGs render - Bump version 0.6.2 -> 0.6.3, refresh Cargo.lock, update CHANGELOG Also ships the 0.6.2 panic fixes: 0.6.2 was bumped in-tree but never tagged, so this is the first published build to carry them.
1 parent 74dc59c commit 2eb9ece

9 files changed

Lines changed: 813 additions & 115 deletions

File tree

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
11
# Changelog
22

3+
## 0.6.3 — 2026-07-26
4+
5+
Also carries the 0.6.2 robustness fixes below: 0.6.2 was tagged in the source
6+
tree but never published, so this is the first build to ship them.
7+
8+
### Fixed
9+
- **Images referenced by absolute path now render** ([#3](https://github.com/borghei/ink/issues/3) follow-up). The 0.6.1 SVG fix never got a chance to run for the reported document: `![](/tmp/sample.svg)` is an absolute path, and the image loader rejected every absolute path (and every relative path escaping the document's directory) before reading a single byte — which is also why a PNG referenced the same way didn't show. Local images now load from any readable path, like kitty's `icat`. This is display-only and safe: image bytes must decode and only ever reach the screen as pixels, never as text, and remote fetching stays opt-in via `--remote-images`.
10+
- **The same root cause, fixed everywhere it appeared:**
11+
- Percent-encoded destinations resolve: `![](my%20pic.png)` finds `my pic.png` (the encoding Obsidian/Notion exports and standard markdown produce). Applies to images and followed links; a file literally named with `%` still wins.
12+
- `file://` image URLs load as local paths instead of silently failing.
13+
- Following a `.md` link works for absolute and out-of-tree paths, matching the image policy.
14+
- **Failed images now say why.** A missing file shows `🖼 … (image not found)`, an undecodable one `(cannot decode image)`, instead of a bare placeholder indistinguishable from a rendering bug — the reason #3 took two rounds to diagnose.
15+
16+
### Added
17+
- **Linked images render.** `[![alt](shot.png)](https://…)` — the badge/linked-screenshot pattern — now renders the image as a block; the caption carries the link so hint-mode and open still reach it.
18+
- **Image galleries render.** A paragraph holding several images in a row renders each one, instead of degrading all of them to inline placeholders.
19+
- **Raw HTML `<img>` renders.** `<p align="center"><img src="logo.png" …></p>` — the README way to size/center a logo — now shows the image (alt text as caption) instead of dim raw markup. A mid-sentence inline `<img>` shows the standard `🖼` placeholder instead of vanishing.
20+
- **`data:` URI images render.** Base64 or percent-encoded payloads, as produced by notebook and HTML exports.
21+
- **`~/pics/x.png` resolves** to the home directory, for images and followed links.
22+
- **`pic.png?raw=true` / `pic.png#gh-light-mode-only` resolve.** GitHub-habit query/fragment suffixes are stripped as a fallback; a file literally named with the suffix still wins.
23+
- **EXIF orientation is honored.** Phone photos no longer render sideways.
24+
- **SVGs with embedded raster images render** (resvg's `raster-images` feature was off, leaving `<image href="…">` elements blank; relative hrefs now resolve against the SVG's own directory).
25+
326
## 0.6.2 — 2026-07-24
427

528
### Fixed (robustness)

Cargo.lock

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ink-md"
3-
version = "0.6.2"
3+
version = "0.6.3"
44
edition = "2021"
55
authors = ["Amin Borghei <borghei.mohammadamin@gmail.com>"]
66
description = "The most advanced terminal markdown reader"
@@ -60,8 +60,12 @@ unicode-width = "0.2"
6060
# Image decoding for inline image rendering
6161
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "gif", "webp", "bmp", "ico", "tiff", "tga", "qoi", "pnm", "hdr", "exr", "ff"] }
6262

63-
# SVG rasterization (vector → pixels) for inline SVG images
64-
resvg = { version = "0.47", default-features = false, features = ["text", "system-fonts", "memmap-fonts"] }
63+
# SVG rasterization (vector → pixels) for inline SVG images; raster-images
64+
# renders <image> elements embedded in SVGs (otherwise they come out blank)
65+
resvg = { version = "0.47", default-features = false, features = ["text", "system-fonts", "memmap-fonts", "raster-images"] }
66+
67+
# data: URI images embedded in documents (notebook/HTML exports)
68+
base64 = "0.22"
6569

6670
# Cross-platform file watching for --watch (FSEvents on macOS, inotify on Linux, ReadDirectoryChangesW on Windows)
6771
notify = "8"

src/app.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -729,12 +729,10 @@ fn run_inner(
729729
let base = std::path::Path::new(&tabs[active_tab].filename)
730730
.parent()
731731
.unwrap_or(std::path::Path::new("."));
732-
// Contain link targets: stay inside the current doc's
733-
// directory tree or the directory ink was launched in.
734-
let cwd = std::env::current_dir().unwrap_or_default();
735-
let Some(target) =
736-
crate::sanitize::resolve_within(base, &[base, &cwd], &link_path)
737-
else {
732+
// Same trust model as images: following a link renders
733+
// a local .md file to the local user, so any readable
734+
// path is fair game (absolute included).
735+
let Some(target) = crate::sanitize::resolve_local(base, &link_path) else {
738736
continue;
739737
};
740738
if let Ok(src) = std::fs::read_to_string(&target) {
@@ -847,7 +845,7 @@ fn collect_link_hints(lines: &[crate::layout::StyledLine]) -> Vec<LinkHint> {
847845
}
848846

849847
/// Act on a chosen link: open web/mail URLs in the default handler; follow a
850-
/// relative `.md` link in-place (with containment + nav history).
848+
/// local `.md` link in-place (any readable path, same trust model as images).
851849
#[allow(clippy::too_many_arguments)]
852850
fn open_link(
853851
url: &str,
@@ -870,16 +868,17 @@ fn open_link(
870868
return;
871869
}
872870
}
873-
// Relative markdown link: follow in-place if it stays in-tree.
871+
// Local markdown link: follow in-place. Same trust model as images —
872+
// rendering a local .md file to the local user, so any readable path is
873+
// fair game (absolute included).
874874
if !(url.ends_with(".md") || url.ends_with(".markdown")) {
875875
return;
876876
}
877877
let base = std::path::Path::new(&tabs[active_tab].filename)
878878
.parent()
879879
.unwrap_or(std::path::Path::new("."))
880880
.to_path_buf();
881-
let cwd = std::env::current_dir().unwrap_or_default();
882-
let Some(target) = crate::sanitize::resolve_within(&base, &[&base, &cwd], url) else {
881+
let Some(target) = crate::sanitize::resolve_local(&base, url) else {
883882
return;
884883
};
885884
if let Ok(src) = std::fs::read_to_string(&target) {

0 commit comments

Comments
 (0)