Support remote image URLs in Typst compiler
This commit is contained in:
@@ -65,6 +65,12 @@ Uploaded images can be referenced natively using the `#image` function in Typst.
|
|||||||
#image("logo.png", width: 50%)
|
#image("logo.png", width: 50%)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can also reference remote images directly by their `http://` or `https://` URL — TypstDrive fetches them at compile time.
|
||||||
|
|
||||||
|
```typst
|
||||||
|
#image("https://example.com/logo.png", width: 50%)
|
||||||
|
```
|
||||||
|
|
||||||
## Self-Hosting
|
## Self-Hosting
|
||||||
|
|
||||||
TypstDrive is completely self-hostable. A Docker image packages both the Rust backend and the SvelteKit frontend into a single container.
|
TypstDrive is completely self-hostable. A Docker image packages both the Rust backend and the SvelteKit frontend into a single container.
|
||||||
|
|||||||
@@ -37,3 +37,4 @@ tempfile = "3.27.0"
|
|||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
|
ureq = "2.12"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use chrono::Datelike;
|
use chrono::Datelike;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
use typst::diag::{FileError, FileResult};
|
use typst::diag::{FileError, FileResult};
|
||||||
use typst::foundations::{Bytes, Datetime, Duration};
|
use typst::foundations::{Bytes, Datetime, Duration};
|
||||||
@@ -22,10 +23,44 @@ pub struct MemoryWorld {
|
|||||||
|
|
||||||
const LOCAL_NAMESPACE: &str = "typstdrive";
|
const LOCAL_NAMESPACE: &str = "typstdrive";
|
||||||
|
|
||||||
|
const REMOTE_FETCH_TIMEOUT_SECS: u64 = 15;
|
||||||
|
const REMOTE_MAX_BYTES: u64 = 50 * 1024 * 1024;
|
||||||
|
|
||||||
fn normalize_path(path: &str) -> String {
|
fn normalize_path(path: &str) -> String {
|
||||||
path.trim_start_matches('/').replace('\\', "/")
|
path.trim_start_matches('/').replace('\\', "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn remote_url(path: &str) -> Option<String> {
|
||||||
|
for scheme in ["https", "http"] {
|
||||||
|
let prefix = format!("{scheme}:/");
|
||||||
|
if let Some(rest) = path.strip_prefix(&prefix) {
|
||||||
|
let host_and_path = rest.trim_start_matches('/');
|
||||||
|
return Some(format!("{scheme}://{host_and_path}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_remote(url: &str) -> FileResult<Vec<u8>> {
|
||||||
|
let agent = ureq::AgentBuilder::new()
|
||||||
|
.timeout(std::time::Duration::from_secs(REMOTE_FETCH_TIMEOUT_SECS))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let response = agent
|
||||||
|
.get(url)
|
||||||
|
.call()
|
||||||
|
.map_err(|e| FileError::Other(Some(format!("failed to fetch {url}: {e}").into())))?;
|
||||||
|
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
response
|
||||||
|
.into_reader()
|
||||||
|
.take(REMOTE_MAX_BYTES)
|
||||||
|
.read_to_end(&mut bytes)
|
||||||
|
.map_err(|e| FileError::Other(Some(format!("failed to read {url}: {e}").into())))?;
|
||||||
|
|
||||||
|
Ok(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
impl MemoryWorld {
|
impl MemoryWorld {
|
||||||
pub fn new_project(
|
pub fn new_project(
|
||||||
entrypoint: String,
|
entrypoint: String,
|
||||||
@@ -103,6 +138,10 @@ impl MemoryWorld {
|
|||||||
return root.load(id.vpath()).map(|bytes| bytes.to_vec());
|
return root.load(id.vpath()).map(|bytes| bytes.to_vec());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(url) = remote_url(&path) {
|
||||||
|
return fetch_remote(&url);
|
||||||
|
}
|
||||||
|
|
||||||
self.files
|
self.files
|
||||||
.get(&path)
|
.get(&path)
|
||||||
.cloned()
|
.cloned()
|
||||||
|
|||||||
Reference in New Issue
Block a user