mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-25 10:57:14 -06:00
Merge upstream/main (#4184 craft-libs bump) into the batch
This commit is contained in:
Generated
+1477
-53
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -54,7 +54,7 @@ harfrust = "0.13"
|
||||
wuff = "0.2.9"
|
||||
vello_cpu = "0.2"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp", "gif", "bmp", "tiff"] }
|
||||
craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "9f75d3dc19b4b514c4f3d4847ed1ec1b0e86b4ef", version = "=0.1.0", default-features = false, features = ["write"] }
|
||||
craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "17744430be327d0257496a6a31c8d38fe20a6756", version = "=0.1.0", default-features = false, features = ["write"] }
|
||||
quick-xml = "0.41"
|
||||
zip = { version = "3", default-features = false, features = ["deflate"] }
|
||||
# zlib streams (compressed metafile pictures in `.doc` drawings); already in the lock as a dependency of other crates.
|
||||
@@ -110,4 +110,4 @@ debug = 0
|
||||
|
||||
# Required by craft-pdf: the backend rejects writers that failed during unwinding.
|
||||
[patch.crates-io]
|
||||
krilla = { git = "https://github.com/storytold/craft-libs", rev = "9f75d3dc19b4b514c4f3d4847ed1ec1b0e86b4ef" }
|
||||
krilla = { git = "https://github.com/storytold/craft-libs", rev = "17744430be327d0257496a6a31c8d38fe20a6756" }
|
||||
|
||||
@@ -73,7 +73,7 @@ windows = { version = "0.62", features = ["UI_ViewManagement", "System_UserProfi
|
||||
clipboard-win = { version = "5.4", features = ["std"] }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
craft-print = { git = "https://github.com/storytold/craft-libs", rev = "d27646a5f7c8b4fc85d9dd3213b8c88b58e0088d" }
|
||||
craft-print = { git = "https://github.com/storytold/craft-libs", rev = "17744430be327d0257496a6a31c8d38fe20a6756" }
|
||||
rfd = { workspace = true }
|
||||
# Waits for an `rfd::AsyncFileDialog` on a background thread (the dialogs don't block the UI, #94);
|
||||
# rfd itself already depends on it.
|
||||
|
||||
@@ -13,7 +13,7 @@ pub fn discover() -> Discover {
|
||||
}
|
||||
|
||||
// Keep submission workers reachable until normal app shutdown; dropping a JoinHandle would
|
||||
// let process exit kill PowerShell/CUPS before it hands the job to the spooler.
|
||||
// let process exit end an in-process Windows job or the CUPS hand-off before the spooler has it.
|
||||
static JOBS: std::sync::Mutex<Vec<std::thread::JoinHandle<()>>> = std::sync::Mutex::new(Vec::new());
|
||||
|
||||
pub fn finish_pending_jobs() {
|
||||
|
||||
@@ -27,7 +27,6 @@ use std::num::NonZeroU16;
|
||||
use std::sync::Arc;
|
||||
|
||||
use craft_pdf::write::{self, Metadata, WriteError, Writer, backend as krilla};
|
||||
use krilla::action::{Action, LinkAction};
|
||||
use krilla::annotation::{Annotation, LinkAnnotation, Target};
|
||||
use krilla::color::rgb;
|
||||
use krilla::destination::XyzDestination;
|
||||
@@ -1601,11 +1600,11 @@ impl Exporter<'_> {
|
||||
None => continue,
|
||||
}
|
||||
} else {
|
||||
let url = target.trim();
|
||||
if url.is_empty() {
|
||||
continue;
|
||||
// Unsafe targets (javascript:, file:, shares, other schemes) get no link; the text stays.
|
||||
match write::uri_target(target) {
|
||||
Some(t) => t,
|
||||
None => continue,
|
||||
}
|
||||
Target::Action(Action::Link(LinkAction::new(url.to_string())))
|
||||
};
|
||||
v.push(Annotation::new_link(LinkAnnotation::new(rect, t), Some(target.clone())));
|
||||
}
|
||||
|
||||
@@ -2386,3 +2386,28 @@ fn borders_are_dashed_with_words_patterns() {
|
||||
assert!(raw.contains("[6 2]0 d") || raw.contains("[6 2] 0 d"), "the dashed border");
|
||||
assert!(raw.contains("[3 1 1 1]0 d") || raw.contains("[3 1 1 1] 0 d"), "the dash-dot border");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_safe_link_targets_become_annotations() {
|
||||
use hayro_syntax::object::{Array, Dict, String as PdfString};
|
||||
|
||||
let mut d = Document::new();
|
||||
d.body = ["https://example.com/", "mailto:ada@example.com", "javascript:alert(1)", "file:///etc/passwd"]
|
||||
.into_iter()
|
||||
.map(|url| para_block(Paragraph::with_text("Click here", CharProps { link: Some(url.into()), ..Default::default() })))
|
||||
.collect();
|
||||
let bytes = export(&d, &PdfOptions::default()).unwrap();
|
||||
assert_eq!(squash(&extract_text(&bytes)[0]).matches("Click here").count(), 4, "unlinked text stays");
|
||||
let pdf = hayro_syntax::Pdf::new(bytes).unwrap();
|
||||
let uris: Vec<String> = pdf.pages()[0]
|
||||
.raw()
|
||||
.get::<Array>(b"Annots")
|
||||
.into_iter()
|
||||
.flat_map(|a| a.iter::<Dict>().collect::<Vec<_>>())
|
||||
.map(|a| {
|
||||
let uri = a.get::<Dict>(b"A").unwrap().get::<PdfString>(b"URI").unwrap();
|
||||
String::from_utf8_lossy(uri.as_ref()).into_owned()
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(uris, ["https://example.com/", "mailto:ada@example.com"]);
|
||||
}
|
||||
|
||||
@@ -12,8 +12,9 @@ workspace = true
|
||||
craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "d27646a5f7c8b4fc85d9dd3213b8c88b58e0088d" }
|
||||
# Interface translations: lookup, fallback, plurals and system-locale matching over the craft-i18n
|
||||
# catalogs that `build.rs` embeds from `CRAFT_I18N_DIR` (storytold/craft-i18n, pinned in
|
||||
# `craft-i18n.rev`). Its own rev: lazy per-locale parsing (storytold/craft-libs#247).
|
||||
craft-i18n = { git = "https://github.com/storytold/craft-libs", rev = "f7cc8a7df15e824b02cf73859567a6242d3a7de4" }
|
||||
# `craft-i18n.rev`). Its own rev, ahead of craft-ui's: lazy per-locale parsing
|
||||
# (storytold/craft-libs#247), ICU4X plural rules and bidi isolates around inserted values.
|
||||
craft-i18n = { git = "https://github.com/storytold/craft-libs", rev = "17744430be327d0257496a6a31c8d38fe20a6756" }
|
||||
wordcraft-geom = { workspace = true, features = ["image"] }
|
||||
wordcraft-doc = { workspace = true }
|
||||
unicode-bidi = { workspace = true }
|
||||
|
||||
@@ -82,16 +82,17 @@ impl ChartDataForm {
|
||||
Ok(json!({"data": {"categories": self.categories, "series": series}}))
|
||||
}
|
||||
|
||||
// Default names become document text (saved and exported), so no isolate marks.
|
||||
fn add_row(&mut self) {
|
||||
if self.categories.len() < MAX_POINTS {
|
||||
self.categories.push(crate::i18n::fmt(tl!("Category {n}"), &[("n", &(self.categories.len() + 1).to_string())]));
|
||||
self.categories.push(crate::i18n::fmt_plain(tl!("Category {n}"), &[("n", &(self.categories.len() + 1).to_string())]));
|
||||
self.cells.push(vec![String::new(); self.names.len()]);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_series(&mut self) {
|
||||
if self.names.len() < MAX_SERIES {
|
||||
self.names.push(crate::i18n::fmt(tl!("Series {n}"), &[("n", &(self.names.len() + 1).to_string())]));
|
||||
self.names.push(crate::i18n::fmt_plain(tl!("Series {n}"), &[("n", &(self.names.len() + 1).to_string())]));
|
||||
for r in &mut self.cells {
|
||||
r.push(String::new());
|
||||
}
|
||||
@@ -356,4 +357,19 @@ mod tests {
|
||||
f.cells[1][0] = "abc".into();
|
||||
assert!(f.params().is_err());
|
||||
}
|
||||
|
||||
/// Default names are saved into the document, so an RTL interface must not add isolate marks.
|
||||
#[test]
|
||||
fn default_names_in_an_rtl_interface_have_no_isolates() {
|
||||
crate::i18n::set_current(crate::i18n::fixture_lang("ar", "\tCategory {n}\tالفئة {n}\n\tSeries {n}\tالسلسلة {n}\n"));
|
||||
let mut f = ChartDataForm::of(&ChartSpec::sample(ChartType::Column));
|
||||
f.add_row();
|
||||
f.add_series();
|
||||
crate::i18n::set_current(crate::i18n::Lang::EN);
|
||||
let (category, series) = (f.categories.last().unwrap(), f.names.last().unwrap());
|
||||
assert!(category.starts_with("الفئة") && series.starts_with("السلسلة"), "{category:?} {series:?}");
|
||||
for name in [category, series] {
|
||||
assert!(!name.chars().any(|c| ('\u{2066}'..='\u{2069}').contains(&c)), "{name:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,13 +32,14 @@
|
||||
//! - [`get`]: one context's translation only, never the plain row (`spacing`'s "Open").
|
||||
//! - [`location`]: a ribbon location such as `Home › Font`, segment by segment.
|
||||
//! - [`fmt`]: fill `{name}` placeholders after a lookup; translators may reorder them.
|
||||
//! - [`fmt_plain`]: [`fmt`] without bidi isolate marks, for text that goes into the document.
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::sync::{LazyLock, OnceLock};
|
||||
|
||||
use craft_i18n::I18n;
|
||||
|
||||
pub use craft_i18n::fmt;
|
||||
pub use craft_i18n::{fmt, fmt_plain};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/craft_i18n.rs"));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user