Add text selection (semi-functional); save as PDF; fix width issues (different vscale/wscale)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,4 +2,5 @@
|
|||||||
/.direnv
|
/.direnv
|
||||||
*.mgc
|
*.mgc
|
||||||
*.typ
|
*.typ
|
||||||
|
*.pdf
|
||||||
/result
|
/result
|
||||||
|
|||||||
170
Cargo.lock
generated
170
Cargo.lock
generated
@@ -628,6 +628,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
|
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
|
"libz-rs-sys",
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -637,6 +638,15 @@ version = "0.9.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "float-cmp"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
@@ -977,6 +987,18 @@ dependencies = [
|
|||||||
"zune-jpeg",
|
"zune-jpeg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hayro-write"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc05d8b4bc878b9aee48d980ecb25ed08f1dd9fad6da5ab4d9b7c56ec03a0cf6"
|
||||||
|
dependencies = [
|
||||||
|
"flate2",
|
||||||
|
"hayro-syntax",
|
||||||
|
"log",
|
||||||
|
"pdf-writer",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -1278,6 +1300,12 @@ version = "0.13.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
|
checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "imagesize"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.12.0"
|
version = "2.12.0"
|
||||||
@@ -1290,6 +1318,12 @@ dependencies = [
|
|||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "infer"
|
||||||
|
version = "0.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
@@ -1314,6 +1348,52 @@ dependencies = [
|
|||||||
"mutate_once",
|
"mutate_once",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "krilla"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "199be5f63da6e19b71051fd5276258a8e55449ac48e2e7492c68238f38ca9f3b"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"bumpalo",
|
||||||
|
"comemo",
|
||||||
|
"flate2",
|
||||||
|
"float-cmp 0.10.0",
|
||||||
|
"gif",
|
||||||
|
"hayro-write",
|
||||||
|
"image-webp",
|
||||||
|
"imagesize 0.14.0",
|
||||||
|
"once_cell",
|
||||||
|
"pdf-writer",
|
||||||
|
"png 0.17.16",
|
||||||
|
"rayon",
|
||||||
|
"rustc-hash",
|
||||||
|
"rustybuzz",
|
||||||
|
"siphasher",
|
||||||
|
"skrifa",
|
||||||
|
"smallvec",
|
||||||
|
"subsetter",
|
||||||
|
"tiny-skia-path",
|
||||||
|
"xmp-writer",
|
||||||
|
"yoke 0.8.0",
|
||||||
|
"zune-jpeg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "krilla-svg"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d3eec075c9507dfdbfb4b9bc3b2aeac074ed422b61bcfd93517616d6b3d19c3"
|
||||||
|
dependencies = [
|
||||||
|
"flate2",
|
||||||
|
"fontdb",
|
||||||
|
"krilla",
|
||||||
|
"png 0.17.16",
|
||||||
|
"resvg",
|
||||||
|
"tiny-skia",
|
||||||
|
"usvg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kurbo"
|
name = "kurbo"
|
||||||
version = "0.11.3"
|
version = "0.11.3"
|
||||||
@@ -1369,6 +1449,15 @@ dependencies = [
|
|||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libz-rs-sys"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "840db8cf39d9ec4dd794376f38acc40d0fc65eec2a8f484f7fd375b84602becd"
|
||||||
|
dependencies = [
|
||||||
|
"zlib-rs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linked-hash-map"
|
name = "linked-hash-map"
|
||||||
version = "0.5.6"
|
version = "0.5.6"
|
||||||
@@ -1657,6 +1746,18 @@ version = "1.0.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pdf-writer"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92a79477295a713c2ed425aa82a8b5d20cec3fdee203706cbe6f3854880c1c81"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"ryu",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
@@ -2315,7 +2416,7 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"float-cmp",
|
"float-cmp 0.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2339,6 +2440,18 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subsetter"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cb6895a12ac5599bb6057362f00e8a3cf1daab4df33f553a55690a44e4fed8d0"
|
||||||
|
dependencies = [
|
||||||
|
"kurbo 0.12.0",
|
||||||
|
"rustc-hash",
|
||||||
|
"skrifa",
|
||||||
|
"write-fonts",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "svgtypes"
|
name = "svgtypes"
|
||||||
version = "0.15.3"
|
version = "0.15.3"
|
||||||
@@ -2888,6 +3001,32 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typst-pdf"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "99160d071220909c1993d7530abe94c0bceffd6e10be7bb629eae2f73d596f26"
|
||||||
|
dependencies = [
|
||||||
|
"az",
|
||||||
|
"bytemuck",
|
||||||
|
"comemo",
|
||||||
|
"ecow",
|
||||||
|
"image",
|
||||||
|
"indexmap",
|
||||||
|
"infer",
|
||||||
|
"krilla",
|
||||||
|
"krilla-svg",
|
||||||
|
"rustc-hash",
|
||||||
|
"serde",
|
||||||
|
"smallvec",
|
||||||
|
"typst-assets",
|
||||||
|
"typst-library",
|
||||||
|
"typst-macros",
|
||||||
|
"typst-syntax",
|
||||||
|
"typst-timing",
|
||||||
|
"typst-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-realize"
|
name = "typst-realize"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
@@ -3158,7 +3297,7 @@ dependencies = [
|
|||||||
"data-url",
|
"data-url",
|
||||||
"flate2",
|
"flate2",
|
||||||
"fontdb",
|
"fontdb",
|
||||||
"imagesize",
|
"imagesize 0.13.0",
|
||||||
"kurbo 0.11.3",
|
"kurbo 0.11.3",
|
||||||
"log",
|
"log",
|
||||||
"pico-args",
|
"pico-args",
|
||||||
@@ -3462,6 +3601,19 @@ version = "0.46.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "write-fonts"
|
||||||
|
version = "0.43.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "886614b5ce857341226aa091f3c285e450683894acaaa7887f366c361efef79d"
|
||||||
|
dependencies = [
|
||||||
|
"font-types",
|
||||||
|
"indexmap",
|
||||||
|
"kurbo 0.12.0",
|
||||||
|
"log",
|
||||||
|
"read-fonts",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "writeable"
|
name = "writeable"
|
||||||
version = "0.5.5"
|
version = "0.5.5"
|
||||||
@@ -3496,6 +3648,12 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xmp-writer"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce9e2f4a404d9ebffc0a9832cf4f50907220ba3d7fffa9099261a5cab52f2dd7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yaml-rust"
|
name = "yaml-rust"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
@@ -3562,8 +3720,10 @@ dependencies = [
|
|||||||
"codespan-reporting",
|
"codespan-reporting",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
"system-deps",
|
"system-deps",
|
||||||
|
"ttf-parser",
|
||||||
"typst",
|
"typst",
|
||||||
"typst-kit",
|
"typst-kit",
|
||||||
|
"typst-pdf",
|
||||||
"typst-render",
|
"typst-render",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3676,6 +3836,12 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zlib-rs"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zune-core"
|
name = "zune-core"
|
||||||
version = "0.4.12"
|
version = "0.4.12"
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ typst-render = "0.14.0"
|
|||||||
cairo-rs = "0.21.2"
|
cairo-rs = "0.21.2"
|
||||||
typst-kit = { version = "0.14.0", features = ["embed-fonts", "vendor-openssl"] }
|
typst-kit = { version = "0.14.0", features = ["embed-fonts", "vendor-openssl"] }
|
||||||
codespan-reporting = "0.13.1"
|
codespan-reporting = "0.13.1"
|
||||||
|
typst-pdf = "0.14.0"
|
||||||
|
ttf-parser = "0.25.1"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
bindgen = "0.72.1"
|
bindgen = "0.72.1"
|
||||||
|
|||||||
247
src/lib.rs
247
src/lib.rs
@@ -1,20 +1,28 @@
|
|||||||
use std::{
|
use std::{
|
||||||
ffi::{CStr, CString, OsStr, c_void},
|
cmp::{max, min},
|
||||||
|
ffi::{CStr, OsStr, c_void},
|
||||||
|
fs, iter,
|
||||||
mem::MaybeUninit,
|
mem::MaybeUninit,
|
||||||
os::unix::ffi::OsStrExt,
|
os::unix::ffi::OsStrExt,
|
||||||
path::Path,
|
path::Path,
|
||||||
slice,
|
slice,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ::typst::layout::{Frame, FrameItem, GroupItem, Page, Point, Transform};
|
use ::typst::{
|
||||||
|
layout::{Abs, FrameItem, Page, Point, Rect},
|
||||||
|
text::{Glyph, TextItem},
|
||||||
|
};
|
||||||
use cairo::{Format, ImageSurface};
|
use cairo::{Format, ImageSurface};
|
||||||
|
|
||||||
use crate::zathura::{
|
use crate::{
|
||||||
ZathuraResult, cairo_t, girara_list_append, girara_list_new_with_free, girara_list_t,
|
typst::FrameItemIterator,
|
||||||
zathura_document_get_data, zathura_document_get_path, zathura_document_s,
|
zathura::{
|
||||||
zathura_document_set_data, zathura_document_set_number_of_pages, zathura_page_get_document,
|
ZathuraResult, cairo_t, girara_list_append, girara_list_new_with_free, girara_list_t,
|
||||||
zathura_page_get_index, zathura_page_set_data, zathura_page_set_height, zathura_page_set_width,
|
zathura_document_get_data, zathura_document_get_path, zathura_document_s,
|
||||||
zathura_page_t, zathura_rectangle_s,
|
zathura_document_set_data, zathura_document_set_number_of_pages, zathura_page_get_document,
|
||||||
|
zathura_page_get_index, zathura_page_set_data, zathura_page_set_height,
|
||||||
|
zathura_page_set_width, zathura_page_t, zathura_rectangle_s,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
mod typst;
|
mod typst;
|
||||||
@@ -35,7 +43,7 @@ pub static zathura_plugin_6_7: zathura::zathura_plugin_definition_s =
|
|||||||
document_open: Some(document_open),
|
document_open: Some(document_open),
|
||||||
document_free: Some(document_free),
|
document_free: Some(document_free),
|
||||||
document_index_generate: None,
|
document_index_generate: None,
|
||||||
document_save_as: None,
|
document_save_as: Some(document_save_as),
|
||||||
document_attachments_get: None,
|
document_attachments_get: None,
|
||||||
document_attachment_save: None,
|
document_attachment_save: None,
|
||||||
document_get_information: None,
|
document_get_information: None,
|
||||||
@@ -82,6 +90,24 @@ unsafe extern "C" fn document_free(_: *mut zathura_document_s, data: *mut c_void
|
|||||||
ZathuraResult::OK
|
ZathuraResult::OK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn document_save_as(
|
||||||
|
_: *mut zathura_document_s,
|
||||||
|
data: *mut c_void,
|
||||||
|
dst: *const i8,
|
||||||
|
) -> ZathuraResult {
|
||||||
|
let typst_doc: &TypstDocument = unsafe { &*(data as *mut _) };
|
||||||
|
let dst = Path::new(OsStr::from_bytes(unsafe { CStr::from_ptr(dst) }.to_bytes()));
|
||||||
|
|
||||||
|
let Ok(bytes) = typst_pdf::pdf(&typst_doc.doc, &Default::default()) else {
|
||||||
|
return ZathuraResult::Unknown;
|
||||||
|
};
|
||||||
|
let Ok(()) = fs::write(dst, bytes) else {
|
||||||
|
return ZathuraResult::InvalidArguments;
|
||||||
|
};
|
||||||
|
|
||||||
|
ZathuraResult::OK
|
||||||
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn page_init(page: *mut zathura_page_t) -> ZathuraResult {
|
unsafe extern "C" fn page_init(page: *mut zathura_page_t) -> ZathuraResult {
|
||||||
let pageno = unsafe { zathura_page_get_index(page) };
|
let pageno = unsafe { zathura_page_get_index(page) };
|
||||||
let doc = unsafe { zathura_page_get_document(page) };
|
let doc = unsafe { zathura_page_get_document(page) };
|
||||||
@@ -122,12 +148,14 @@ unsafe extern "C" fn page_render_cairo(
|
|||||||
let scale = {
|
let scale = {
|
||||||
let wscale = surface.width() as f32 / typst_page.frame.width().to_pt() as f32;
|
let wscale = surface.width() as f32 / typst_page.frame.width().to_pt() as f32;
|
||||||
let vscale = surface.height() as f32 / typst_page.frame.height().to_pt() as f32;
|
let vscale = surface.height() as f32 / typst_page.frame.height().to_pt() as f32;
|
||||||
if wscale < vscale { wscale } else { vscale } // min, but floats aren't Ord
|
if vscale > wscale { vscale } else { wscale }
|
||||||
};
|
};
|
||||||
|
|
||||||
let format = surface.format();
|
let format = surface.format();
|
||||||
|
|
||||||
let pixels_rgba = typst_render::render(typst_page, scale).take();
|
let pixels_rgba = typst_render::render(typst_page, scale);
|
||||||
|
let render_width = pixels_rgba.width();
|
||||||
|
let pixels_rgba = pixels_rgba.take();
|
||||||
|
|
||||||
let (pixels_rgba, _) = pixels_rgba.as_chunks::<4>(); // This will always be exact anyway
|
let (pixels_rgba, _) = pixels_rgba.as_chunks::<4>(); // This will always be exact anyway
|
||||||
|
|
||||||
@@ -143,10 +171,14 @@ unsafe extern "C" fn page_render_cairo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pixels_rgba
|
pixels_rgba
|
||||||
.iter()
|
.chunks_exact(render_width as _)
|
||||||
.zip(buf.chunks_exact_mut(4))
|
.zip(buf.chunks_mut(surface.stride() as _))
|
||||||
.for_each(|(&[r, g, b, a], dst)| {
|
.for_each(|(row, dst)| {
|
||||||
dst.copy_from_slice(&u32::from_be_bytes([a, r, g, b]).to_ne_bytes());
|
row.iter()
|
||||||
|
.zip(dst.chunks_exact_mut(4))
|
||||||
|
.for_each(|(&[r, g, b, a], dst)| {
|
||||||
|
dst.copy_from_slice(&u32::from_be_bytes([a, r, g, b]).to_ne_bytes());
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
surface.mark_dirty();
|
surface.mark_dirty();
|
||||||
@@ -154,26 +186,94 @@ unsafe extern "C" fn page_render_cairo(
|
|||||||
ZathuraResult::OK
|
ZathuraResult::OK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_intersection(a: Rect, b: Rect) -> Option<Rect> {
|
||||||
|
Some(Rect::new(
|
||||||
|
Point::new(max(a.min.x, b.min.x), max(a.min.y, b.min.y)),
|
||||||
|
Point::new(min(a.max.x, b.max.x), min(a.max.y, b.max.y)),
|
||||||
|
))
|
||||||
|
.filter(|r| r.size().all(|c| c.to_raw() >= 0.))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rect_union(a: Rect, b: Rect) -> Rect {
|
||||||
|
Rect::new(
|
||||||
|
Point::new(min(a.min.x, b.min.x), min(a.min.y, b.min.y)),
|
||||||
|
Point::new(max(a.max.x, b.max.x), max(a.max.y, b.max.y)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rect_norm(a: Rect) -> Rect {
|
||||||
|
Rect::new(
|
||||||
|
Point::new(min(a.min.x, a.max.x), min(a.min.y, a.max.y)),
|
||||||
|
Point::new(max(a.min.x, a.max.x), max(a.min.y, a.max.y)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selected_glyphs(
|
||||||
|
selection: Rect,
|
||||||
|
point: Point,
|
||||||
|
item: &TextItem,
|
||||||
|
) -> Option<impl Iterator<Item = (Point, Rect, &Glyph)>> {
|
||||||
|
let bbox = rect_norm(item.bbox());
|
||||||
|
if bbox.size().any(|d| d.to_raw().is_infinite()) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let inter = rect_intersection(
|
||||||
|
Rect::new(point + bbox.min, point + bbox.max),
|
||||||
|
rect_norm(selection),
|
||||||
|
)?;
|
||||||
|
Some(
|
||||||
|
item.glyphs
|
||||||
|
.iter()
|
||||||
|
.scan(point, move |cursor, glyph| {
|
||||||
|
Some((*cursor, {
|
||||||
|
*cursor +=
|
||||||
|
Point::new(glyph.x_advance.at(item.size), glyph.y_advance.at(item.size));
|
||||||
|
glyph
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.filter_map(move |(point, glyph)| {
|
||||||
|
dbg!(&item.text[glyph.range()]);
|
||||||
|
let bbox = item
|
||||||
|
.font
|
||||||
|
.ttf()
|
||||||
|
.glyph_bounding_box(ttf_parser::GlyphId(glyph.id))?;
|
||||||
|
let base =
|
||||||
|
point + Point::new(glyph.x_offset.at(item.size), glyph.y_offset.at(item.size));
|
||||||
|
let bbox = rect_norm(Rect::new(
|
||||||
|
base + Point::new(
|
||||||
|
item.font.to_em(bbox.x_min).at(item.size),
|
||||||
|
item.font.to_em(bbox.y_min).at(item.size),
|
||||||
|
),
|
||||||
|
base + Point::new(
|
||||||
|
item.font.to_em(bbox.x_max).at(item.size),
|
||||||
|
item.font.to_em(bbox.y_max).at(item.size),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
rect_intersection(inter, bbox).map(|_| (point, bbox, glyph))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn page_get_text(
|
unsafe extern "C" fn page_get_text(
|
||||||
_page: *mut zathura_page_t,
|
_page: *mut zathura_page_t,
|
||||||
data: *mut c_void,
|
data: *mut c_void,
|
||||||
rect: zathura_rectangle_s,
|
rect: zathura_rectangle_s,
|
||||||
res: *mut ZathuraResult,
|
res: *mut ZathuraResult,
|
||||||
) -> *mut i8 {
|
) -> *mut i8 {
|
||||||
let mut res_ = ZathuraResult::Unknown;
|
let res = if res.is_null() {
|
||||||
let res = unsafe {
|
&mut ZathuraResult::OK
|
||||||
&mut *(if res.is_null() { &raw mut res_ } else { res } as *mut MaybeUninit<ZathuraResult>)
|
} else {
|
||||||
}
|
unsafe { &mut *(res as *mut MaybeUninit<ZathuraResult>) }.write(ZathuraResult::Unknown)
|
||||||
.write(ZathuraResult::Unknown);
|
};
|
||||||
let typst_page: &Page = unsafe { &*(data as *mut _) };
|
let typst_page: &Page = unsafe { &*(data as *mut _) };
|
||||||
|
|
||||||
|
let rect = Rect::from(rect);
|
||||||
let text = FrameItemIterator::new(&typst_page.frame)
|
let text = FrameItemIterator::new(&typst_page.frame)
|
||||||
.filter(|(Point { x, y }, _)| {
|
.filter_map(|(point, item)| {
|
||||||
(rect.x1..=rect.x2).contains(&x.to_pt()) && (rect.y1..=rect.y2).contains(&y.to_pt())
|
|
||||||
})
|
|
||||||
.filter_map(|(_, item)| {
|
|
||||||
if let FrameItem::Text(item) = item {
|
if let FrameItem::Text(item) = item {
|
||||||
Some(item.text.to_string())
|
let mut ranges =
|
||||||
|
selected_glyphs(rect, point, item)?.map(|(_, _, glyph)| glyph.range());
|
||||||
|
Some(item.text[ranges.next()?.start..ranges.last()?.end].to_owned())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -199,33 +299,26 @@ unsafe extern "C" fn page_get_selection(
|
|||||||
rect: zathura_rectangle_s,
|
rect: zathura_rectangle_s,
|
||||||
res: *mut ZathuraResult,
|
res: *mut ZathuraResult,
|
||||||
) -> *mut girara_list_t {
|
) -> *mut girara_list_t {
|
||||||
let mut res_ = ZathuraResult::Unknown;
|
let res = if res.is_null() {
|
||||||
let res = unsafe {
|
&mut ZathuraResult::OK
|
||||||
&mut *(if res.is_null() { &raw mut res_ } else { res } as *mut MaybeUninit<ZathuraResult>)
|
} else {
|
||||||
}
|
unsafe { &mut *(res as *mut MaybeUninit<ZathuraResult>) }.write(ZathuraResult::Unknown)
|
||||||
.write(ZathuraResult::Unknown);
|
|
||||||
let typst_page: &Page = unsafe { &*(data as *mut _) };
|
|
||||||
let min = |x, y| if x < y { x } else { y };
|
|
||||||
let max = |x, y| if x > y { x } else { y };
|
|
||||||
|
|
||||||
let rect = zathura_rectangle_s {
|
|
||||||
x1: min(rect.x1, rect.x2),
|
|
||||||
y1: min(rect.y1, rect.y2),
|
|
||||||
x2: max(rect.x1, rect.x2),
|
|
||||||
y2: max(rect.y1, rect.y2),
|
|
||||||
};
|
};
|
||||||
|
let typst_page: &Page = unsafe { &*(data as *mut _) };
|
||||||
|
|
||||||
let rects = FrameItemIterator::new(&typst_page.frame)
|
let rect = Rect::from(rect);
|
||||||
.filter(|(Point { x, y }, _)| {
|
|
||||||
(rect.x1..=rect.x2).contains(&x.to_pt()) && (rect.y1..=rect.y2).contains(&y.to_pt())
|
let rects = FrameItemIterator::new(&typst_page.frame).filter_map(|(point, item)| {
|
||||||
})
|
if let FrameItem::Text(item) = item {
|
||||||
.filter_map(|(_, item)| {
|
let bbox = selected_glyphs(rect, point, item)?.fold(
|
||||||
if let FrameItem::Text(item) = item {
|
Rect::new(Point::splat(Abs::inf()), Point::splat(-Abs::inf())),
|
||||||
Some(item.bbox())
|
|acc, (_, bbox, _)| rect_union(acc, bbox),
|
||||||
} else {
|
);
|
||||||
None
|
Some(bbox)
|
||||||
}
|
} else {
|
||||||
});
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
unsafe extern "C" fn drop_rect(data: *mut c_void) {
|
unsafe extern "C" fn drop_rect(data: *mut c_void) {
|
||||||
drop(unsafe { Box::from_raw(data as *mut zathura_rectangle_s) });
|
drop(unsafe { Box::from_raw(data as *mut zathura_rectangle_s) });
|
||||||
@@ -251,61 +344,7 @@ unsafe extern "C" fn page_get_selection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FrameItemIterator<'a> {
|
|
||||||
transform: Transform,
|
|
||||||
recur: Option<Box<FrameItemIterator<'a>>>,
|
|
||||||
current: slice::Iter<'a, (Point, FrameItem)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> FrameItemIterator<'a> {
|
|
||||||
fn new(root: &'a Frame) -> Self {
|
|
||||||
Self::new_at(Default::default(), root)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_at(transform: Transform, root: &'a Frame) -> Self {
|
|
||||||
Self {
|
|
||||||
transform,
|
|
||||||
recur: None,
|
|
||||||
current: root.items(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Iterator for FrameItemIterator<'a> {
|
|
||||||
type Item = (Point, &'a FrameItem);
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
if let Some((position, item)) = self.recur.as_mut().and_then(|r| r.next()) {
|
|
||||||
return Some((position.transform(self.transform), item));
|
|
||||||
}
|
|
||||||
self.recur = None;
|
|
||||||
let (position, item) = self.current.next()?;
|
|
||||||
if let FrameItem::Group(GroupItem {
|
|
||||||
frame, transform, ..
|
|
||||||
}) = item
|
|
||||||
{
|
|
||||||
self.recur = Some(Box::new(Self::new_at(
|
|
||||||
transform
|
|
||||||
.invert()
|
|
||||||
.unwrap()
|
|
||||||
.post_concat(Transform::translate(position.x, position.y)),
|
|
||||||
frame,
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
Some((position.transform(self.transform), item))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: render warnings
|
// TODO: render warnings
|
||||||
// TODO: PDF as attachment
|
// TODO: PDF as attachment
|
||||||
// TODO: link/... handling
|
// TODO: link/... handling
|
||||||
// TODO: better caching
|
// TODO: better caching
|
||||||
// TODO: nixify compilation of magicdb (file -Cm ~/.magic:/nix/store/vi7ya34k19nid2m0dmkljqip5572g0bi-file-5.45/share/misc/magic.mgc)
|
|
||||||
/*
|
|
||||||
0 string/cW /*\ doc:\ typst\ */ typst text document
|
|
||||||
!:ext typ
|
|
||||||
!:mime text/vnd.typst
|
|
||||||
0 string #import typst text document
|
|
||||||
!:ext typ
|
|
||||||
!:mime text/vnd.typst
|
|
||||||
*/
|
|
||||||
|
|||||||
60
src/typst.rs
60
src/typst.rs
@@ -1,10 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, hash_map::Entry},
|
collections::{hash_map::Entry, HashMap}, hash::Hash, io::{self, ErrorKind}, iter, path::{Path, PathBuf}, slice, sync::{LazyLock, Mutex}
|
||||||
hash::Hash,
|
|
||||||
io::{self, ErrorKind},
|
|
||||||
iter,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
sync::{LazyLock, Mutex},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use codespan_reporting::{
|
use codespan_reporting::{
|
||||||
@@ -13,12 +8,7 @@ use codespan_reporting::{
|
|||||||
term::{Config, DisplayStyle, emit_to_write_style, termcolor::Ansi},
|
term::{Config, DisplayStyle, emit_to_write_style, termcolor::Ansi},
|
||||||
};
|
};
|
||||||
use typst::{
|
use typst::{
|
||||||
Library, LibraryExt, WorldExt,
|
diag::{FileError, FileResult, SourceDiagnostic, Warned}, foundations::{Bytes, Datetime}, layout::{Frame, FrameItem, GroupItem, Point, Transform}, syntax::{FileId, Lines, Source, Span, VirtualPath}, text::{Font, FontBook}, utils::LazyHash, Library, LibraryExt, WorldExt
|
||||||
diag::{FileError, FileResult, SourceDiagnostic, Warned},
|
|
||||||
foundations::{Bytes, Datetime},
|
|
||||||
syntax::{FileId, Lines, Source, Span, VirtualPath},
|
|
||||||
text::{Font, FontBook},
|
|
||||||
utils::LazyHash,
|
|
||||||
};
|
};
|
||||||
use typst_kit::{
|
use typst_kit::{
|
||||||
download::{Downloader, ProgressSink},
|
download::{Downloader, ProgressSink},
|
||||||
@@ -293,3 +283,49 @@ fn diagnostics(world: &World, errors: impl IntoIterator<Item = SourceDiagnostic>
|
|||||||
fn label(world: &World, span: Span) -> Option<Label<FileId>> {
|
fn label(world: &World, span: Span) -> Option<Label<FileId>> {
|
||||||
Some(Label::primary(span.id()?, world.range(span)?))
|
Some(Label::primary(span.id()?, world.range(span)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct FrameItemIterator<'a> {
|
||||||
|
transform: Transform,
|
||||||
|
recur: Option<Box<FrameItemIterator<'a>>>,
|
||||||
|
current: slice::Iter<'a, (Point, FrameItem)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FrameItemIterator<'a> {
|
||||||
|
pub fn new(root: &'a Frame) -> Self {
|
||||||
|
Self::new_at(Default::default(), root)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_at(transform: Transform, root: &'a Frame) -> Self {
|
||||||
|
Self {
|
||||||
|
transform,
|
||||||
|
recur: None,
|
||||||
|
current: root.items(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for FrameItemIterator<'a> {
|
||||||
|
type Item = (Point, &'a FrameItem);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if let Some((position, item)) = self.recur.as_mut().and_then(|r| r.next()) {
|
||||||
|
return Some((position.transform(self.transform), item));
|
||||||
|
}
|
||||||
|
self.recur = None;
|
||||||
|
let (position, item) = self.current.next()?;
|
||||||
|
if let FrameItem::Group(GroupItem {
|
||||||
|
frame, transform, ..
|
||||||
|
}) = item
|
||||||
|
{
|
||||||
|
self.recur = Some(Box::new(Self::new_at(
|
||||||
|
transform
|
||||||
|
.invert()
|
||||||
|
.unwrap()
|
||||||
|
.post_concat(Transform::translate(position.x, position.y)),
|
||||||
|
frame,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Some((position.transform(self.transform), item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#![allow(warnings)]
|
#![allow(warnings)]
|
||||||
pub use cairo::ffi::*;
|
pub use cairo::ffi::*;
|
||||||
|
use typst::layout::{Abs, Point, Rect};
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||||
|
|
||||||
@@ -21,3 +22,26 @@ pub enum ZathuraResult {
|
|||||||
/// The provided password is invalid
|
/// The provided password is invalid
|
||||||
InvalidPassword = 5,
|
InvalidPassword = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<zathura_rectangle_s> for Rect {
|
||||||
|
fn from(rect: zathura_rectangle_s) -> Self {
|
||||||
|
fn min(a: f64, b: f64) -> f64 {
|
||||||
|
if a < b { a } else { b }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max(a: f64, b: f64) -> f64 {
|
||||||
|
if a > b { a } else { b }
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect::new(
|
||||||
|
Point::new(
|
||||||
|
Abs::pt(min(rect.x1, rect.x2)),
|
||||||
|
Abs::pt(min(rect.y1, rect.y2)),
|
||||||
|
),
|
||||||
|
Point::new(
|
||||||
|
Abs::pt(max(rect.x1, rect.x2)),
|
||||||
|
Abs::pt(max(rect.y1, rect.y2)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user