forked from jess/Acord
More work in implementing image rendering.
This commit is contained in:
parent
9c7053d2dc
commit
9c0a74c1ce
|
|
@ -25,6 +25,7 @@ toml = "0.8"
|
||||||
zip = { version = "2", default-features = false, features = ["deflate"] }
|
zip = { version = "2", default-features = false, features = ["deflate"] }
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
arboard = "3"
|
arboard = "3"
|
||||||
|
ureq = "3"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
cbindgen = "0.29"
|
cbindgen = "0.29"
|
||||||
|
|
|
||||||
|
|
@ -609,7 +609,6 @@ impl EditorState {
|
||||||
self.eval_results.retain(|r| !ids.contains(&r.anchor.block_id));
|
self.eval_results.retain(|r| !ids.contains(&r.anchor.block_id));
|
||||||
self.computed_tables.retain(|t| !ids.contains(&t.anchor.block_id));
|
self.computed_tables.retain(|t| !ids.contains(&t.anchor.block_id));
|
||||||
self.computed_trees.retain(|t| !ids.contains(&t.anchor.block_id));
|
self.computed_trees.retain(|t| !ids.contains(&t.anchor.block_id));
|
||||||
self.computed_images.retain(|img| !ids.contains(&img.anchor.block_id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Map a line number in concatenated module source back to a per-block anchor.
|
/// Map a line number in concatenated module source back to a per-block anchor.
|
||||||
|
|
@ -4424,16 +4423,27 @@ fn parse_image_ref(line: &str) -> Option<(String, String)> {
|
||||||
Some((alt, src))
|
Some((alt, src))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load an image from a local filesystem path into an `ImageCacheEntry`.
|
/// Load an image into an `ImageCacheEntry`. Accepts:
|
||||||
/// Returns `None` on any failure (missing file, corrupt image, etc.).
|
/// - `http://` / `https://` URLs (5s timeout, blocking — guarded by the
|
||||||
|
/// per-source cache so the stall only happens on first load).
|
||||||
|
/// - `~/`-prefixed paths (expanded against the home directory).
|
||||||
|
/// - Absolute or relative filesystem paths.
|
||||||
fn load_image_from_path(src: &str) -> Option<ImageCacheEntry> {
|
fn load_image_from_path(src: &str) -> Option<ImageCacheEntry> {
|
||||||
// Expand ~ to home directory.
|
let bytes = if src.starts_with("http://") || src.starts_with("https://") {
|
||||||
let path = if src.starts_with("~/") {
|
let agent: ureq::Agent = ureq::Agent::config_builder()
|
||||||
dirs::home_dir()?.join(&src[2..])
|
.timeout_global(Some(std::time::Duration::from_secs(5)))
|
||||||
|
.build()
|
||||||
|
.into();
|
||||||
|
let mut resp = agent.get(src).call().ok()?;
|
||||||
|
resp.body_mut().read_to_vec().ok()?
|
||||||
} else {
|
} else {
|
||||||
std::path::PathBuf::from(src)
|
let path = if src.starts_with("~/") {
|
||||||
|
dirs::home_dir()?.join(&src[2..])
|
||||||
|
} else {
|
||||||
|
std::path::PathBuf::from(src)
|
||||||
|
};
|
||||||
|
std::fs::read(&path).ok()?
|
||||||
};
|
};
|
||||||
let bytes = std::fs::read(&path).ok()?;
|
|
||||||
let reader = image::ImageReader::new(std::io::Cursor::new(&bytes))
|
let reader = image::ImageReader::new(std::io::Cursor::new(&bytes))
|
||||||
.with_guessed_format()
|
.with_guessed_format()
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
@ -4445,3 +4455,30 @@ fn load_image_from_path(src: &str) -> Option<ImageCacheEntry> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Encode a clipboard image (RGBA from `arboard`) to PNG and write it into
|
||||||
|
/// `~/.acord/cache/images/{hash}.png`. Returns the absolute path as a
|
||||||
|
/// String suitable for embedding in a `` markdown reference.
|
||||||
|
/// Content-addressed: re-pasting the same pixels reuses the same file.
|
||||||
|
pub fn write_clipboard_image_to_cache(img: &arboard::ImageData) -> Option<String> {
|
||||||
|
let dir = dirs::home_dir()?.join(".acord").join("cache").join("images");
|
||||||
|
std::fs::create_dir_all(&dir).ok()?;
|
||||||
|
|
||||||
|
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
img.width.hash(&mut hasher);
|
||||||
|
img.height.hash(&mut hasher);
|
||||||
|
img.bytes.hash(&mut hasher);
|
||||||
|
let name = format!("{:016x}.png", hasher.finish());
|
||||||
|
let path = dir.join(&name);
|
||||||
|
|
||||||
|
if !path.exists() {
|
||||||
|
let buf = image::RgbaImage::from_raw(
|
||||||
|
img.width as u32,
|
||||||
|
img.height as u32,
|
||||||
|
img.bytes.to_vec(),
|
||||||
|
)?;
|
||||||
|
buf.save_with_format(&path, image::ImageFormat::Png).ok()?;
|
||||||
|
}
|
||||||
|
Some(path.to_string_lossy().into_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,20 @@ struct AcordClipboard {
|
||||||
impl clipboard::Clipboard for AcordClipboard {
|
impl clipboard::Clipboard for AcordClipboard {
|
||||||
fn read(&self, _kind: clipboard::Kind) -> Option<String> {
|
fn read(&self, _kind: clipboard::Kind) -> Option<String> {
|
||||||
// arboard uses NSPasteboard on macOS, Win32 on Windows — no subprocess.
|
// arboard uses NSPasteboard on macOS, Win32 on Windows — no subprocess.
|
||||||
|
// Image-first: if the pasteboard holds a bitmap, encode it to PNG in
|
||||||
|
// the on-disk image cache and yield a markdown reference. Wrapping
|
||||||
|
// newlines guarantee the `` lands as the only thing on its line
|
||||||
|
// so `parse_image_ref` will pick it up.
|
||||||
|
let mut board = self.board.borrow_mut();
|
||||||
|
if let Ok(img) = board.get_image() {
|
||||||
|
if let Some(path) = crate::editor::write_clipboard_image_to_cache(&img) {
|
||||||
|
return Some(format!("\n\n", path));
|
||||||
|
}
|
||||||
|
}
|
||||||
// Line-ending normalisation: web pages and cross-platform apps keep
|
// Line-ending normalisation: web pages and cross-platform apps keep
|
||||||
// `\r\n` in the pasteboard; collapse to `\n` so iced's buffer and
|
// `\r\n` in the pasteboard; collapse to `\n` so iced's buffer and
|
||||||
// our gutter line counter agree.
|
// our gutter line counter agree.
|
||||||
self.board.borrow_mut()
|
board.get_text()
|
||||||
.get_text()
|
|
||||||
.ok()
|
.ok()
|
||||||
.map(|s| s.replace("\r\n", "\n").replace('\r', "\n"))
|
.map(|s| s.replace("\r\n", "\n").replace('\r', "\n"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue