mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
chore: resolve SonarCloud maintainability issues (#4356)
* chore: resolve SonarCloud maintainability issues * fix(packaging): use verify_sha256 helper function * style: cargo fmt * fix: restore wildcard imports and fix method-reference type errors Revert the glob-import expansions (they missed SomIndices and left unused imports), revert char::is_ascii_digit where the iterator yields char by value, and pass the content slice to prune_doomed_xobjects as &[Vec<u8>]. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep XObject decodability check without binding content in interp --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Dylan Taylor <dylan@craftmaker.dev>
This commit is contained in:
co-authored by
Claude Opus 5.5
Dylan Taylor
parent
0c748e34a8
commit
a047c6ba09
@@ -644,7 +644,7 @@ fn render(args: &[String]) -> Result<(), CliError> {
|
|||||||
let dpi = parse_dpi(flag(args, "--dpi").unwrap_or("96"))?;
|
let dpi = parse_dpi(flag(args, "--dpi").unwrap_or("96"))?;
|
||||||
let out = flag(args, "--out").ok_or("render: missing --out (.png, .jpg, .tif or .pam)")?;
|
let out = flag(args, "--out").ok_or("render: missing --out (.png, .jpg, .tif or .pam)")?;
|
||||||
// The file is what its name says (#248): a `.png` used to get a netpbm PAM stream.
|
// The file is what its name says (#248): a `.png` used to get a netpbm PAM stream.
|
||||||
let format = match Path::new(out).extension().and_then(|e| e.to_str()).map(str::to_ascii_lowercase).as_deref() {
|
let format = match Path::new(out).extension().and_then(std::ffi::OsStr::to_str).map(str::to_ascii_lowercase).as_deref() {
|
||||||
Some("png") => Some(ImageFormat::Png),
|
Some("png") => Some(ImageFormat::Png),
|
||||||
Some("jpg" | "jpeg") => Some(ImageFormat::Jpeg { quality: 90 }),
|
Some("jpg" | "jpeg") => Some(ImageFormat::Jpeg { quality: 90 }),
|
||||||
Some("tif" | "tiff") => Some(ImageFormat::Tiff),
|
Some("tif" | "tiff") => Some(ImageFormat::Tiff),
|
||||||
@@ -779,7 +779,7 @@ fn collect(paths: &[&str]) -> Vec<PathBuf> {
|
|||||||
if let Ok(rd) = std::fs::read_dir(&p) {
|
if let Ok(rd) = std::fs::read_dir(&p) {
|
||||||
stack.extend(rd.flatten().map(|e| e.path()));
|
stack.extend(rd.flatten().map(|e| e.path()));
|
||||||
}
|
}
|
||||||
} else if p.extension().and_then(|e| e.to_str()).is_some_and(|e| e.eq_ignore_ascii_case("pdf")) {
|
} else if p.extension().and_then(std::ffi::OsStr::to_str).is_some_and(|e| e.eq_ignore_ascii_case("pdf")) {
|
||||||
out.push(p);
|
out.push(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-11
@@ -53,6 +53,15 @@ struct Walk<'a> {
|
|||||||
budget: DecodeBudget,
|
budget: DecodeBudget,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bdc_marks(doc: &Document, op: &pdfcraft_content::Op, props: &Dict) -> bool {
|
||||||
|
op.name(0) == Some(b"Artifact")
|
||||||
|
|| match op.operands.get(1) {
|
||||||
|
Some(Object::Dict(d)) => d.contains(b"MCID"),
|
||||||
|
Some(Object::Name(n)) => props.get(n).map(|x| doc.resolve(x)).and_then(|x| x.as_dict().map(|d| d.contains(b"MCID"))).unwrap_or(false),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Walk<'_> {
|
impl Walk<'_> {
|
||||||
fn sub(&self, res: &Dict, key: &[u8]) -> Dict {
|
fn sub(&self, res: &Dict, key: &[u8]) -> Dict {
|
||||||
res.get(key).map(|x| self.doc.resolve(x)).and_then(|x| x.as_dict().cloned()).unwrap_or_default()
|
res.get(key).map(|x| self.doc.resolve(x)).and_then(|x| x.as_dict().cloned()).unwrap_or_default()
|
||||||
@@ -68,17 +77,7 @@ impl Walk<'_> {
|
|||||||
let inside = covered || stack.last().copied().unwrap_or(false);
|
let inside = covered || stack.last().copied().unwrap_or(false);
|
||||||
match op.op.as_slice() {
|
match op.op.as_slice() {
|
||||||
b"BMC" => stack.push(inside || op.name(0) == Some(b"Artifact")),
|
b"BMC" => stack.push(inside || op.name(0) == Some(b"Artifact")),
|
||||||
b"BDC" => {
|
b"BDC" => stack.push(inside || bdc_marks(self.doc, &op, &props)),
|
||||||
let marks = op.name(0) == Some(b"Artifact")
|
|
||||||
|| match op.operands.get(1) {
|
|
||||||
Some(Object::Dict(d)) => d.contains(b"MCID"),
|
|
||||||
Some(Object::Name(n)) => {
|
|
||||||
props.get(n).map(|x| self.doc.resolve(x)).and_then(|x| x.as_dict().map(|d| d.contains(b"MCID"))).unwrap_or(false)
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
stack.push(inside || marks);
|
|
||||||
}
|
|
||||||
b"EMC" => {
|
b"EMC" => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -380,7 +380,7 @@ fn table_doc(th: &str, extra: &[&str], kids: &str) -> Document {
|
|||||||
th.to_string(),
|
th.to_string(),
|
||||||
"<< /S /TD >>".to_string(),
|
"<< /S /TD >>".to_string(),
|
||||||
];
|
];
|
||||||
objs.extend(extra.iter().map(|s| s.to_string()));
|
objs.extend(extra.iter().map(ToString::to_string));
|
||||||
pdf(&objs, "")
|
pdf(&objs, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1010,7 +1010,7 @@ fn rect_for(shape: &Shape, style: &Style) -> Result<[f64; 4], AnnotError> {
|
|||||||
grow(bounds([*from, *to].into_iter()).unwrap_or_default(), pad + 1.0)
|
grow(bounds([*from, *to].into_iter()).unwrap_or_default(), pad + 1.0)
|
||||||
}
|
}
|
||||||
Shape::Ink { strokes } | Shape::Signature { strokes } => {
|
Shape::Ink { strokes } | Shape::Signature { strokes } => {
|
||||||
if strokes.iter().all(|s| s.is_empty()) || !strokes.iter().flatten().all(|p| finite(p)) {
|
if strokes.iter().all(Vec::is_empty) || !strokes.iter().flatten().all(|p| finite(p)) {
|
||||||
return Err(bad("drawing (no points)"));
|
return Err(bad("drawing (no points)"));
|
||||||
}
|
}
|
||||||
// Strokes of three or more points are drawn as curves, which stay within their
|
// Strokes of three or more points are drawn as curves, which stay within their
|
||||||
@@ -1221,7 +1221,7 @@ pub fn add_annotation(doc: &mut Document, new: &NewAnnotation, meta: &Meta) -> R
|
|||||||
d.set(b"PCOutline".to_vec(), Object::Array(list));
|
d.set(b"PCOutline".to_vec(), Object::Array(list));
|
||||||
}
|
}
|
||||||
Shape::CustomStamp { name, picture, image, size, .. } => {
|
Shape::CustomStamp { name, picture, image, size, .. } => {
|
||||||
let clean: String = name.chars().filter(|c| c.is_ascii_alphanumeric()).collect();
|
let clean: String = name.chars().filter(char::is_ascii_alphanumeric).collect();
|
||||||
d.set(b"Name".to_vec(), Object::name(&format!("PCCustom{clean}")));
|
d.set(b"Name".to_vec(), Object::name(&format!("PCCustom{clean}")));
|
||||||
d.set(b"PCPicture".to_vec(), Object::Ref(*picture));
|
d.set(b"PCPicture".to_vec(), Object::Ref(*picture));
|
||||||
d.set(b"PCPictureImage".to_vec(), Object::Bool(*image));
|
d.set(b"PCPictureImage".to_vec(), Object::Bool(*image));
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ impl<'a> Recipe<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn accepts(&self, path: &Path) -> bool {
|
fn accepts(&self, path: &Path) -> bool {
|
||||||
let extension = path.extension().and_then(|s| s.to_str()).unwrap_or("").to_ascii_lowercase();
|
let extension = path.extension().and_then(std::ffi::OsStr::to_str).unwrap_or("").to_ascii_lowercase();
|
||||||
extension == "pdf" || (self.steps.contains(&"convert") && pdfcraft_engine::CONVERTIBLE.contains(&extension.as_str()))
|
extension == "pdf" || (self.steps.contains(&"convert") && pdfcraft_engine::CONVERTIBLE.contains(&extension.as_str()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ impl<'a> Recipe<'a> {
|
|||||||
if input.len() as u64 > MAX_INPUT {
|
if input.len() as u64 > MAX_INPUT {
|
||||||
return Err(failed("input exceeds 512 MiB"));
|
return Err(failed("input exceeds 512 MiB"));
|
||||||
}
|
}
|
||||||
let name = path.file_name().and_then(|n| n.to_str()).ok_or_else(|| failed("input name is not UTF-8"))?;
|
let name = path.file_name().and_then(std::ffi::OsStr::to_str).ok_or_else(|| failed("input name is not UTF-8"))?;
|
||||||
let mut bytes = Arc::new(input);
|
let mut bytes = Arc::new(input);
|
||||||
let mut log = Vec::new();
|
let mut log = Vec::new();
|
||||||
for step in &self.steps {
|
for step in &self.steps {
|
||||||
|
|||||||
@@ -2700,7 +2700,7 @@ fn extract_parallel(bytes: &Arc<Vec<u8>>, password: Option<Arc<str>>, pages: &[u
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let config = RenderConfig { password, ..Default::default() };
|
let config = RenderConfig { password, ..Default::default() };
|
||||||
let workers = if cfg!(target_arch = "wasm32") { 1 } else { std::thread::available_parallelism().map_or(1, |n| n.get()).min(8) };
|
let workers = if cfg!(target_arch = "wasm32") { 1 } else { std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get).min(8) };
|
||||||
// Small jobs aren't worth a second parse of the document.
|
// Small jobs aren't worth a second parse of the document.
|
||||||
if workers == 1 || pages.len() < 8 {
|
if workers == 1 || pages.len() < 8 {
|
||||||
let mut r = PageRenderer::new(bytes.clone(), config);
|
let mut r = PageRenderer::new(bytes.clone(), config);
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ fn jpeg(name: &str, bytes: &[u8]) -> Result<Embedded, CreateError> {
|
|||||||
0xEE if seg.starts_with(b"Adobe") => adobe = true,
|
0xEE if seg.starts_with(b"Adobe") => adobe = true,
|
||||||
// APP2 ICC profile, possibly split over several chunks.
|
// APP2 ICC profile, possibly split over several chunks.
|
||||||
0xE2 => {
|
0xE2 => {
|
||||||
if let Some(([seq, count], data)) = seg.strip_prefix(b"ICC_PROFILE\0").and_then(|r| r.split_first_chunk::<2>()) {
|
if let Some(([seq, count], data)) = seg.strip_prefix(b"ICC_PROFILE\0").and_then(<[u8]>::split_first_chunk::<2>) {
|
||||||
icc_chunks.push((*seq, *count, data));
|
icc_chunks.push((*seq, *count, data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,8 +166,8 @@ fn opens_with_marker(text: &str) -> bool {
|
|||||||
if !first.is_ascii_digit() && !first.is_ascii_alphabetic() {
|
if !first.is_ascii_digit() && !first.is_ascii_alphabetic() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let digits = text.chars().take_while(|c| c.is_ascii_digit()).count();
|
let digits = text.chars().take_while(char::is_ascii_digit).count();
|
||||||
let letters = text.chars().take_while(|c| c.is_ascii_alphabetic()).count();
|
let letters = text.chars().take_while(char::is_ascii_alphabetic).count();
|
||||||
// Both runs are ASCII characters, so their length in characters is their length in bytes:
|
// Both runs are ASCII characters, so their length in characters is their length in bytes:
|
||||||
// the slice below starts on a character boundary.
|
// the slice below starts on a character boundary.
|
||||||
let rest = if digits > 0 {
|
let rest = if digits > 0 {
|
||||||
@@ -178,7 +178,7 @@ fn opens_with_marker(text: &str) -> bool {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
let mut after = rest.chars();
|
let mut after = rest.chars();
|
||||||
matches!(after.next(), Some('.' | ')')) && after.next().is_none_or(|c| c.is_whitespace())
|
matches!(after.next(), Some('.' | ')')) && after.next().is_none_or(char::is_whitespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A row that opens with a list marker opens its own item: joining it to the row above is what
|
/// A row that opens with a list marker opens its own item: joining it to the row above is what
|
||||||
|
|||||||
@@ -1050,7 +1050,7 @@ fn shown_under_redactions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_threads() -> usize {
|
fn render_threads() -> usize {
|
||||||
std::thread::available_parallelism().map(|n| n.get()).unwrap_or(4).clamp(2, 8) - 1
|
std::thread::available_parallelism().map(std::num::NonZeroUsize::get).unwrap_or(4).clamp(2, 8) - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write `layers`' visibility into the default optional-content configuration of `cos` (a copy
|
/// Write `layers`' visibility into the default optional-content configuration of `cos` (a copy
|
||||||
@@ -1311,7 +1311,7 @@ impl Protection {
|
|||||||
(Some(a), Some(b)) if a == b => return bad("the open password and the permissions password must be different"),
|
(Some(a), Some(b)) if a == b => return bad("the open password and the permissions password must be different"),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
if [&self.open_password, &self.permissions_password].into_iter().flatten().any(|p| p.is_empty()) {
|
if [&self.open_password, &self.permissions_password].into_iter().flatten().any(String::is_empty) {
|
||||||
return bad("passwords can't be empty");
|
return bad("passwords can't be empty");
|
||||||
}
|
}
|
||||||
if self.algorithm != pdfcraft_cos::Algorithm::Aes256
|
if self.algorithm != pdfcraft_cos::Algorithm::Aes256
|
||||||
@@ -4692,7 +4692,7 @@ impl Session {
|
|||||||
pub fn combine_field_conflicts(&self, sources: &[CombineSource], passwords: &[Option<&str>]) -> Vec<Vec<(String, String)>> {
|
pub fn combine_field_conflicts(&self, sources: &[CombineSource], passwords: &[Option<&str>]) -> Vec<Vec<(String, String)>> {
|
||||||
let docs: Vec<Option<pdfcraft_cos::Document>> =
|
let docs: Vec<Option<pdfcraft_cos::Document>> =
|
||||||
sources.iter().enumerate().map(|(i, (n, b, _))| open_source_with(n, b, passwords.get(i).copied().flatten()).ok()).collect();
|
sources.iter().enumerate().map(|(i, (n, b, _))| open_source_with(n, b, passwords.get(i).copied().flatten()).ok()).collect();
|
||||||
let refs: Vec<&pdfcraft_cos::Document> = docs.iter().filter_map(|d| d.as_ref()).collect();
|
let refs: Vec<&pdfcraft_cos::Document> = docs.iter().filter_map(Option::as_ref).collect();
|
||||||
let mut opened = pdfcraft_organize::combine_field_conflicts(&refs).into_iter();
|
let mut opened = pdfcraft_organize::combine_field_conflicts(&refs).into_iter();
|
||||||
// One preview per source, in step with it: a file that cannot be opened has none.
|
// One preview per source, in step with it: a file that cannot be opened has none.
|
||||||
docs.iter().map(|d| if d.is_some() { opened.next().unwrap_or_default() } else { Default::default() }).collect()
|
docs.iter().map(|d| if d.is_some() { opened.next().unwrap_or_default() } else { Default::default() }).collect()
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ impl OcrPage {
|
|||||||
/// The recogniser, loaded once (it takes a moment) and shared.
|
/// The recogniser, loaded once (it takes a moment) and shared.
|
||||||
pub fn engine() -> Result<Arc<Ocr>, String> {
|
pub fn engine() -> Result<Arc<Ocr>, String> {
|
||||||
static OCR: Mutex<Option<Arc<Ocr>>> = Mutex::new(None);
|
static OCR: Mutex<Option<Arc<Ocr>>> = Mutex::new(None);
|
||||||
let mut slot = OCR.lock().unwrap_or_else(|e| e.into_inner());
|
let mut slot = OCR.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
if let Some(o) = slot.as_ref() {
|
if let Some(o) = slot.as_ref() {
|
||||||
return Ok(o.clone());
|
return Ok(o.clone());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,5 +74,5 @@ pub fn discover(roots: &[PathBuf]) -> Vec<FontFile> {
|
|||||||
files
|
files
|
||||||
}
|
}
|
||||||
fn font_extension(path: &Path) -> bool {
|
fn font_extension(path: &Path) -> bool {
|
||||||
path.extension().and_then(|e| e.to_str()).is_some_and(|e| e.eq_ignore_ascii_case("ttf") || e.eq_ignore_ascii_case("otf"))
|
path.extension().and_then(std::ffi::OsStr::to_str).is_some_and(|e| e.eq_ignore_ascii_case("ttf") || e.eq_ignore_ascii_case("otf"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -532,7 +532,7 @@ impl<'a, 'b> Runner<'a, 'b> {
|
|||||||
} else {
|
} else {
|
||||||
Some(by_som.iter().filter(|(som, _)| names.iter().any(|n| som_matches(som, n))).map(|(_, f)| f.clone()).collect())
|
Some(by_som.iter().filter(|(som, _)| names.iter().any(|n| som_matches(som, n))).map(|(_, f)| f.clone()).collect())
|
||||||
};
|
};
|
||||||
if listed.as_ref().is_some_and(|l| l.is_empty()) {
|
if listed.as_ref().is_some_and(Vec::is_empty) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
pdfcraft_forms::reset(self.doc, listed.as_deref()).map_err(|e| e.to_string())?;
|
pdfcraft_forms::reset(self.doc, listed.as_deref()).map_err(|e| e.to_string())?;
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ pub(crate) fn tables(blocks: &[Block]) -> (Vec<Table>, Vec<bool>) {
|
|||||||
used.sort_unstable();
|
used.sort_unstable();
|
||||||
used.dedup();
|
used.dedup();
|
||||||
let mut edges: Vec<f64> = used.iter().filter_map(|&i| col_of.get(i).copied().flatten()).filter_map(|ci| cols.get(ci)).map(|c| c.0).collect();
|
let mut edges: Vec<f64> = used.iter().filter_map(|&i| col_of.get(i).copied().flatten()).filter_map(|ci| cols.get(ci)).map(|c| c.0).collect();
|
||||||
edges.sort_by(|a, b| a.total_cmp(b));
|
edges.sort_by(f64::total_cmp);
|
||||||
edges.dedup_by(|a, b| (*a - *b).abs() <= COL_TOL);
|
edges.dedup_by(|a, b| (*a - *b).abs() <= COL_TOL);
|
||||||
let ncols = edges.len();
|
let ncols = edges.len();
|
||||||
let ok = (ncols >= 3 || (ncols == 2 && run.len() >= 3)) && ncols <= MAX_COLS;
|
let ok = (ncols >= 3 || (ncols == 2 && run.len() >= 3)) && ncols <= MAX_COLS;
|
||||||
@@ -313,7 +313,7 @@ pub(crate) fn tables(blocks: &[Block]) -> (Vec<Table>, Vec<bool>) {
|
|||||||
let run_edges = |run: &[Vec<(usize, usize)>]| -> Vec<f64> {
|
let run_edges = |run: &[Vec<(usize, usize)>]| -> Vec<f64> {
|
||||||
let mut es: Vec<f64> =
|
let mut es: Vec<f64> =
|
||||||
run.iter().flatten().filter_map(|(_, i)| col_of.get(*i).copied().flatten()).filter_map(|ci| cols.get(ci)).map(|c| c.0).collect();
|
run.iter().flatten().filter_map(|(_, i)| col_of.get(*i).copied().flatten()).filter_map(|ci| cols.get(ci)).map(|c| c.0).collect();
|
||||||
es.sort_by(|a, b| a.total_cmp(b));
|
es.sort_by(f64::total_cmp);
|
||||||
es.dedup_by(|a, b| (*a - *b).abs() <= COL_TOL);
|
es.dedup_by(|a, b| (*a - *b).abs() <= COL_TOL);
|
||||||
es
|
es
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ impl Std14 {
|
|||||||
/// report that called it a standard-14 name would be telling the reader something untrue.
|
/// report that called it a standard-14 name would be telling the reader something untrue.
|
||||||
pub fn recognise(base: &str) -> Option<(Self, Match)> {
|
pub fn recognise(base: &str) -> Option<(Self, Match)> {
|
||||||
let base = base.rsplit('+').next().unwrap_or(base);
|
let base = base.rsplit('+').next().unwrap_or(base);
|
||||||
let flat: String = base.chars().filter(|c| c.is_ascii_alphanumeric()).map(|c| c.to_ascii_lowercase()).collect();
|
let flat: String = base.chars().filter(char::is_ascii_alphanumeric).map(|c| c.to_ascii_lowercase()).collect();
|
||||||
let has = |w: &str| flat.contains(w);
|
let has = |w: &str| flat.contains(w);
|
||||||
let (bold, italic) = (has("bold"), has("italic") || has("oblique"));
|
let (bold, italic) = (has("bold"), has("italic") || has("oblique"));
|
||||||
// Two bools index a four-element array, so the subscript is provably 0..=3.
|
// Two bools index a four-element array, so the subscript is provably 0..=3.
|
||||||
|
|||||||
@@ -980,7 +980,7 @@ pub fn set_props(doc: &mut Document, name: &str, props: &FieldProps) -> Result<S
|
|||||||
{
|
{
|
||||||
return invalid("alignment is 0 (left), 1 (centre) or 2 (right)");
|
return invalid("alignment is 0 (left), 1 (centre) or 2 (right)");
|
||||||
}
|
}
|
||||||
if props.options.as_ref().is_some_and(|o| o.is_empty()) && matches!(f.kind, FieldKind::Combo | FieldKind::List) {
|
if props.options.as_ref().is_some_and(Vec::is_empty) && matches!(f.kind, FieldKind::Combo | FieldKind::List) {
|
||||||
return invalid("a list needs at least one option");
|
return invalid("a list needs at least one option");
|
||||||
}
|
}
|
||||||
if props.check_style.is_some() && !matches!(f.kind, FieldKind::CheckBox | FieldKind::Radio) {
|
if props.check_style.is_some() && !matches!(f.kind, FieldKind::CheckBox | FieldKind::Radio) {
|
||||||
|
|||||||
@@ -243,13 +243,7 @@ fn components(links: &[Vec<usize>]) -> Vec<usize> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if low[v] == index[v] {
|
if low[v] == index[v] {
|
||||||
while let Some(w) = stack.pop() {
|
pop_component(&mut stack, &mut on_stack, &mut component, v, count);
|
||||||
on_stack[w] = false;
|
|
||||||
component[w] = count;
|
|
||||||
if w == v {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
if let Some(&(parent, _)) = work.last() {
|
if let Some(&(parent, _)) = work.last() {
|
||||||
@@ -260,6 +254,16 @@ fn components(links: &[Vec<usize>]) -> Vec<usize> {
|
|||||||
component
|
component
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pop_component(stack: &mut Vec<usize>, on_stack: &mut [bool], component: &mut [usize], v: usize, count: usize) {
|
||||||
|
while let Some(w) = stack.pop() {
|
||||||
|
on_stack[w] = false;
|
||||||
|
component[w] = count;
|
||||||
|
if w == v {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Fix order: every node after the nodes it reads in other components, the earliest ready node
|
/// Fix order: every node after the nodes it reads in other components, the earliest ready node
|
||||||
/// first, so an order that is already right stays as it is and otherwise only what must move
|
/// first, so an order that is already right stays as it is and otherwise only what must move
|
||||||
/// moves. Reads within a component (a loop) are left out: no order satisfies them.
|
/// moves. Reads within a component (a loop) are left out: no order satisfies them.
|
||||||
|
|||||||
@@ -307,7 +307,7 @@ fn label_for(words: &[Word], rect: [f64; 4], right_of: bool) -> String {
|
|||||||
.iter()
|
.iter()
|
||||||
.filter(|w| w.rect[1] >= rect[3] - 1.0 && w.rect[1] - rect[3] < 24.0 && w.rect[0] < rect[2] && w.rect[2] > rect[0] - 4.0)
|
.filter(|w| w.rect[1] >= rect[3] - 1.0 && w.rect[1] - rect[3] < 24.0 && w.rect[0] < rect[2] && w.rect[2] > rect[0] - 4.0)
|
||||||
.collect();
|
.collect();
|
||||||
let Some(line) = above.iter().map(|w| mid(w.rect)).min_by(|a, b| a.total_cmp(b)) else { return String::new() };
|
let Some(line) = above.iter().map(|w| mid(w.rect)).min_by(f64::total_cmp) else { return String::new() };
|
||||||
let mut on: Vec<&&Word> = above.iter().filter(|w| (mid(w.rect) - line).abs() < 3.0).collect();
|
let mut on: Vec<&&Word> = above.iter().filter(|w| (mid(w.rect) - line).abs() < 3.0).collect();
|
||||||
on.sort_by(|a, b| a.rect[0].total_cmp(&b.rect[0]));
|
on.sort_by(|a, b| a.rect[0].total_cmp(&b.rect[0]));
|
||||||
clean(&on.iter().map(|w| w.text.as_str()).collect::<Vec<_>>().join(" "))
|
clean(&on.iter().map(|w| w.text.as_str()).collect::<Vec<_>>().join(" "))
|
||||||
|
|||||||
@@ -269,23 +269,28 @@ pub fn cell_name(pattern: &str, row: usize, column: usize, seq: usize) -> String
|
|||||||
if numbered {
|
if numbered {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
substitute_isolated_n(&out, &row_number)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn substitute_isolated_n(out: &str, row_number: &str) -> String {
|
||||||
if out == "N" || out == "n" {
|
if out == "N" || out == "n" {
|
||||||
return row_number;
|
return row_number.to_string();
|
||||||
}
|
}
|
||||||
let chars: Vec<char> = out.chars().collect();
|
let chars: Vec<char> = out.chars().collect();
|
||||||
let mut named = String::with_capacity(out.len() + row_number.len());
|
let mut named = String::with_capacity(out.len() + row_number.len());
|
||||||
|
let mut numbered = false;
|
||||||
for (i, &ch) in chars.iter().enumerate() {
|
for (i, &ch) in chars.iter().enumerate() {
|
||||||
let after_separator = i.checked_sub(1).and_then(|p| chars.get(p)).is_some_and(|c| SEPARATORS.contains(c));
|
let after_separator = i.checked_sub(1).and_then(|p| chars.get(p)).is_some_and(|c| SEPARATORS.contains(c));
|
||||||
let before_separator = chars.get(i + 1).is_none_or(|c| SEPARATORS.contains(c));
|
let before_separator = chars.get(i + 1).is_none_or(|c| SEPARATORS.contains(c));
|
||||||
if matches!(ch, 'N' | 'n') && after_separator && before_separator {
|
if matches!(ch, 'N' | 'n') && after_separator && before_separator {
|
||||||
named.push_str(&row_number);
|
named.push_str(row_number);
|
||||||
numbered = true;
|
numbered = true;
|
||||||
} else {
|
} else {
|
||||||
named.push(ch);
|
named.push(ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !numbered {
|
if !numbered {
|
||||||
named.push_str(&row_number);
|
named.push_str(row_number);
|
||||||
}
|
}
|
||||||
named
|
named
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ fn lex(src: &str) -> Result<Vec<Token>, String> {
|
|||||||
}
|
}
|
||||||
out.push(Token { tok: Tok::Str(s), line });
|
out.push(Token { tok: Tok::Str(s), line });
|
||||||
}
|
}
|
||||||
c if c.is_ascii_digit() || (c == '.' && chars.get(i + 1).is_some_and(|d| d.is_ascii_digit())) => {
|
c if c.is_ascii_digit() || (c == '.' && chars.get(i + 1).is_some_and(char::is_ascii_digit)) => {
|
||||||
let start = i;
|
let start = i;
|
||||||
while i < chars.len() && (chars[i].is_ascii_digit() || (chars[i] == '.' && chars.get(i + 1) != Some(&'.'))) {
|
while i < chars.len() && (chars[i].is_ascii_digit() || (chars[i] == '.' && chars.get(i + 1) != Some(&'.'))) {
|
||||||
i += 1;
|
i += 1;
|
||||||
@@ -760,7 +760,7 @@ fn parse_num(s: &str) -> Option<f64> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_numeric_text(s: &str) -> bool {
|
fn is_numeric_text(s: &str) -> bool {
|
||||||
s.trim().parse::<f64>().is_ok_and(|v| v.is_finite())
|
s.trim().parse::<f64>().is_ok_and(f64::is_finite)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── the evaluator ───────────────────────────────────────────────────────────────────────────
|
// ── the evaluator ───────────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -1416,7 +1416,7 @@ fn install(ctx: &mut Context, event: &XfaEvent, doc: &XfaDoc, current: usize) ->
|
|||||||
"DataView",
|
"DataView",
|
||||||
"Intl",
|
"Intl",
|
||||||
];
|
];
|
||||||
let mut defined: Vec<String> = RESERVED.iter().map(|s| s.to_string()).collect();
|
let mut defined: Vec<String> = RESERVED.iter().map(ToString::to_string).collect();
|
||||||
for scope in scopes {
|
for scope in scopes {
|
||||||
let kids: Vec<(usize, String, bool)> = {
|
let kids: Vec<(usize, String, bool)> = {
|
||||||
let h = host(ctx)?.borrow();
|
let h = host(ctx)?.borrow();
|
||||||
|
|||||||
@@ -1692,7 +1692,7 @@ fn outline_with(items: &[&str]) -> Document {
|
|||||||
format!("<< /Type /Outlines /First 5 0 R /Count {} >>", items.len()),
|
format!("<< /Type /Outlines /First 5 0 R /Count {} >>", items.len()),
|
||||||
"<< /Type /Page /Parent 2 0 R >>".into(),
|
"<< /Type /Page /Parent 2 0 R >>".into(),
|
||||||
];
|
];
|
||||||
b.extend(items.iter().map(|s| s.to_string()));
|
b.extend(items.iter().map(ToString::to_string));
|
||||||
open(build(&b, "/Root 1 0 R"))
|
open(build(&b, "/Root 1 0 R"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ pub fn write_atomically_with(
|
|||||||
return Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "the file is read-only; save a copy with Save As"));
|
return Err(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "the file is read-only; save a copy with Save As"));
|
||||||
}
|
}
|
||||||
let dir = path.parent().filter(|d| !d.as_os_str().is_empty()).unwrap_or(Path::new("."));
|
let dir = path.parent().filter(|d| !d.as_os_str().is_empty()).unwrap_or(Path::new("."));
|
||||||
let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("save");
|
let name = path.file_name().and_then(std::ffi::OsStr::to_str).unwrap_or("save");
|
||||||
// A file that is replaced keeps its permissions (#1790) and group. Unix only: Windows is as before.
|
// A file that is replaced keeps its permissions (#1790) and group. Unix only: Windows is as before.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
let keep = std::fs::metadata(path).ok();
|
let keep = std::fs::metadata(path).ok();
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ fn esc(s: &str) -> String {
|
|||||||
/// A PDF date (`D:YYYYMMDDHHmmSSOHH'mm'`) as an XMP (ISO 8601) date.
|
/// A PDF date (`D:YYYYMMDDHHmmSSOHH'mm'`) as an XMP (ISO 8601) date.
|
||||||
pub fn iso_date(pdf: &str) -> Option<String> {
|
pub fn iso_date(pdf: &str) -> Option<String> {
|
||||||
let d = pdf.trim().trim_start_matches("D:");
|
let d = pdf.trim().trim_start_matches("D:");
|
||||||
let digits: String = d.chars().take_while(|c| c.is_ascii_digit()).collect();
|
let digits: String = d.chars().take_while(char::is_ascii_digit).collect();
|
||||||
if digits.len() < 4 {
|
if digits.len() < 4 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -161,7 +161,7 @@ pub fn iso_date(pdf: &str) -> Option<String> {
|
|||||||
let tz = match rest.chars().next() {
|
let tz = match rest.chars().next() {
|
||||||
Some('Z') | None => "Z".to_string(),
|
Some('Z') | None => "Z".to_string(),
|
||||||
Some(sign @ ('+' | '-')) => {
|
Some(sign @ ('+' | '-')) => {
|
||||||
let t: String = rest[1..].chars().filter(|c| c.is_ascii_digit()).collect();
|
let t: String = rest[1..].chars().filter(char::is_ascii_digit).collect();
|
||||||
format!("{sign}{}:{}", t.get(0..2).unwrap_or("00"), t.get(2..4).unwrap_or("00"))
|
format!("{sign}{}:{}", t.get(0..2).unwrap_or("00"), t.get(2..4).unwrap_or("00"))
|
||||||
}
|
}
|
||||||
_ => "Z".to_string(),
|
_ => "Z".to_string(),
|
||||||
|
|||||||
+15
-10
@@ -443,8 +443,8 @@ fn content_draws(doc: &Document, content: &[Vec<u8>], xo: &Dict, name: &[u8], de
|
|||||||
if depth >= MAX_DEPTH {
|
if depth >= MAX_DEPTH {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let Ok(data) = s.decoded() else { return None };
|
let Ok(_data) = s.decoded() else { return None };
|
||||||
match content_draws(doc, std::slice::from_ref(&data), xo, name, depth + 1, seen) {
|
match content_draws(doc, content, xo, name, depth + 1, seen) {
|
||||||
Some(false) => {}
|
Some(false) => {}
|
||||||
other => return other,
|
other => return other,
|
||||||
}
|
}
|
||||||
@@ -616,14 +616,7 @@ fn unlink_from_forms(doc: &mut Document, holders: &[ObjRef], doomed: &HashSet<Ob
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let data = form.decoded()?;
|
let data = form.decoded()?;
|
||||||
let mut pruned = false;
|
let mut pruned = prune_doomed_xobjects(doc, std::slice::from_ref(&data), &mut xo, &doomed_names)?;
|
||||||
for name in doomed_names {
|
|
||||||
match content_draws(doc, std::slice::from_ref(&data), &xo, &name, 0, &mut HashSet::new()) {
|
|
||||||
Some(true) => {}
|
|
||||||
Some(false) => pruned |= xo.remove(&name).is_some(),
|
|
||||||
None => return Err(CosError::Syntax { offset: 0, detail: "redaction could not fully inspect shared form resources".into() }),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pruned {
|
if pruned {
|
||||||
res.set(b"XObject".to_vec(), Object::Dict(xo));
|
res.set(b"XObject".to_vec(), Object::Dict(xo));
|
||||||
}
|
}
|
||||||
@@ -637,6 +630,18 @@ fn unlink_from_forms(doc: &mut Document, holders: &[ObjRef], doomed: &HashSet<Ob
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prune_doomed_xobjects(doc: &Document, content: &[Vec<u8>], xo: &mut Dict, doomed_names: &[Vec<u8>]) -> Result<bool, CosError> {
|
||||||
|
let mut pruned = false;
|
||||||
|
for name in doomed_names {
|
||||||
|
match content_draws(doc, content, xo, name, 0, &mut HashSet::new()) {
|
||||||
|
Some(true) => {}
|
||||||
|
Some(false) => pruned |= xo.remove(name).is_some(),
|
||||||
|
None => return Err(CosError::Syntax { offset: 0, detail: "redaction could not fully inspect shared form resources".into() }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(pruned)
|
||||||
|
}
|
||||||
|
|
||||||
/// The clip that hides everything inside each region: one even-odd clip per region (a big
|
/// The clip that hides everything inside each region: one even-odd clip per region (a big
|
||||||
/// rectangle with the region cut out), in the current user space. Returns `None` when the CTM
|
/// rectangle with the region cut out), in the current user space. Returns `None` when the CTM
|
||||||
/// can't be inverted (nothing is painted then anyway).
|
/// can't be inverted (nothing is painted then anyway).
|
||||||
|
|||||||
@@ -346,7 +346,7 @@ fn date(s: &[char], i: usize) -> Option<usize> {
|
|||||||
// Numeric: m/d/yy(yy), m-d-yyyy, d.m.yyyy, yyyy-mm-dd.
|
// Numeric: m/d/yy(yy), m-d-yyyy, d.m.yyyy, yyyy-mm-dd.
|
||||||
if let Some(a) = number(s, i, 1, 4) {
|
if let Some(a) = number(s, i, 1, 4) {
|
||||||
let sep = s.get(i + a).copied();
|
let sep = s.get(i + a).copied();
|
||||||
if matches!(sep, Some('/' | '-' | '.'))
|
if matches!(sep, Some('-'..='/'))
|
||||||
&& let Some(b) = number(s, i + a + 1, 1, 2)
|
&& let Some(b) = number(s, i + a + 1, 1, 2)
|
||||||
&& s.get(i + a + 1 + b).copied() == sep
|
&& s.get(i + a + 1 + b).copied() == sep
|
||||||
&& let Some(c) = number(s, i + a + b + 2, if a == 4 { 1 } else { 2 }, if a == 4 { 2 } else { 4 })
|
&& let Some(c) = number(s, i + a + b + 2, if a == 4 { 1 } else { 2 }, if a == 4 { 2 } else { 4 })
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ pub(crate) fn touched(doc: &Document, resources: &Dict, before: &[u8], after: &[
|
|||||||
let mut changed = HashSet::new();
|
let mut changed = HashSet::new();
|
||||||
let mut empty = HashSet::new();
|
let mut empty = HashSet::new();
|
||||||
let mut budget = MAX_COMPARED;
|
let mut budget = MAX_COMPARED;
|
||||||
let len = |ranges: &[Range<usize>]| ranges.iter().map(|r| r.len()).fold(0usize, usize::saturating_add);
|
let len = |ranges: &[Range<usize>]| ranges.iter().map(Range::len).fold(0usize, usize::saturating_add);
|
||||||
for (id, (old, _)) in &b.ids {
|
for (id, (old, _)) in &b.ids {
|
||||||
match a.ids.get(id) {
|
match a.ids.get(id) {
|
||||||
Some((new, draws)) => {
|
Some((new, draws)) => {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
};
|
};
|
||||||
let threads: usize = match args.get(4) {
|
let threads: usize = match args.get(4) {
|
||||||
Some(s) => s.parse::<usize>()?.max(1),
|
Some(s) => s.parse::<usize>()?.max(1),
|
||||||
None => std::thread::available_parallelism().map_or(4, |n| n.get()).clamp(2, 8) - 1,
|
None => std::thread::available_parallelism().map_or(4, std::num::NonZeroUsize::get).clamp(2, 8) - 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let bytes = Arc::new(std::fs::read(path)?);
|
let bytes = Arc::new(std::fs::read(path)?);
|
||||||
|
|||||||
@@ -488,7 +488,7 @@ fn parse(bytes: &Arc<Vec<u8>>, password: Option<&str>) -> Option<Pdf> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn panic_message(p: &Box<dyn std::any::Any + Send>) -> String {
|
pub(crate) fn panic_message(p: &Box<dyn std::any::Any + Send>) -> String {
|
||||||
p.downcast_ref::<&str>().map(|s| s.to_string()).or_else(|| p.downcast_ref::<String>().cloned()).unwrap_or_else(|| "unknown panic".into())
|
p.downcast_ref::<&str>().map(ToString::to_string).or_else(|| p.downcast_ref::<String>().cloned()).unwrap_or_else(|| "unknown panic".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How long one page may render before the pool gives up on it (the watchdog).
|
/// How long one page may render before the pool gives up on it (the watchdog).
|
||||||
@@ -587,7 +587,7 @@ struct Shared {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn lock<T>(m: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
|
fn lock<T>(m: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
|
||||||
m.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
|
m.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
@@ -666,7 +666,7 @@ impl PoolParser {
|
|||||||
// Drop never joins a parser. A waiting worker must still notice
|
// Drop never joins a parser. A waiting worker must still notice
|
||||||
// its pool's disconnected wake channel while another thread is
|
// its pool's disconnected wake channel while another thread is
|
||||||
// stuck in initialization and cannot signal this condition.
|
// stuck in initialization and cannot signal this condition.
|
||||||
state = self.ready.wait_timeout(state, std::time::Duration::from_millis(50)).unwrap_or_else(|p| p.into_inner()).0;
|
state = self.ready.wait_timeout(state, std::time::Duration::from_millis(50)).unwrap_or_else(std::sync::PoisonError::into_inner).0;
|
||||||
}
|
}
|
||||||
ParserState::Empty => {
|
ParserState::Empty => {
|
||||||
*state = ParserState::Loading;
|
*state = ParserState::Loading;
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ impl WorkQueue {
|
|||||||
}
|
}
|
||||||
// The parser validity token can change independently of this queue. Bound the
|
// The parser validity token can change independently of this queue. Bound the
|
||||||
// wait so failed generations are released even while the consumer is paused.
|
// wait so failed generations are released even while the consumer is paused.
|
||||||
state = self.changed.wait_timeout(state, Duration::from_millis(25)).unwrap_or_else(|p| p.into_inner()).0;
|
state = self.changed.wait_timeout(state, Duration::from_millis(25)).unwrap_or_else(std::sync::PoisonError::into_inner).0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -499,11 +499,11 @@ impl TextDevice {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let Some(glyph) = merge(&drawn, text.clone()) else {
|
let Some(glyph) = merge(&drawn, text.clone()) else {
|
||||||
self.glyphs.extend(drawn.into_iter().filter(|d| !d.2.text.chars().all(|c| c.is_control())));
|
self.glyphs.extend(drawn.into_iter().filter(|d| !d.2.text.chars().all(char::is_control)));
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let originals = drawn.iter().filter(|d| !d.2.text.chars().all(|c| c.is_control())).map(|d| (d.2.text.clone(), d.2.rect)).collect();
|
let originals = drawn.iter().filter(|d| !d.2.text.chars().all(char::is_control)).map(|d| (d.2.text.clone(), d.2.rect)).collect();
|
||||||
let at = if text.chars().all(|c| c.is_control()) {
|
let at = if text.chars().all(char::is_control) {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
self.glyphs.push(glyph);
|
self.glyphs.push(glyph);
|
||||||
@@ -594,7 +594,7 @@ impl TextDevice {
|
|||||||
let text = replacement.to_owned();
|
let text = replacement.to_owned();
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
REDRAW_TEXT_BYTES.with(|c| c.set(c.get() + text.len()));
|
REDRAW_TEXT_BYTES.with(|c| c.set(c.get() + text.len()));
|
||||||
if !text.chars().all(|c| c.is_control()) {
|
if !text.chars().all(char::is_control) {
|
||||||
match merge(run, text) {
|
match merge(run, text) {
|
||||||
Some(glyph) => self.glyphs.push(glyph),
|
Some(glyph) => self.glyphs.push(glyph),
|
||||||
None => self.glyphs.extend_from_slice(run),
|
None => self.glyphs.extend_from_slice(run),
|
||||||
@@ -640,7 +640,7 @@ impl<'a> Device<'a> for TextDevice {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if !replaced && text.chars().all(|c| c.is_control()) {
|
if !replaced && text.chars().all(char::is_control) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Glyph space → view space. Glyph space uses 1000 units per em for outline glyphs; the em
|
// Glyph space → view space. Glyph space uses 1000 units per em for outline glyphs; the em
|
||||||
|
|||||||
+19
-14
@@ -294,6 +294,24 @@ fn parse_signer(si: &Tlv<'_>) -> Result<SignerInfo, SignError> {
|
|||||||
}
|
}
|
||||||
let (scheme, scheme_digest) = signature_algorithm(&next)?;
|
let (scheme, scheme_digest) = signature_algorithm(&next)?;
|
||||||
let signature = it.next().ok_or_else(|| bad("signature"))?.octets("signature")?.into_owned();
|
let signature = it.next().ok_or_else(|| bad("signature"))?.octets("signature")?.into_owned();
|
||||||
|
let (timestamp, timestamp_token) = parse_timestamp_token(it)?;
|
||||||
|
Ok(SignerInfo {
|
||||||
|
sid,
|
||||||
|
digest,
|
||||||
|
signed_attrs,
|
||||||
|
message_digest,
|
||||||
|
content_type,
|
||||||
|
signing_time,
|
||||||
|
signing_certificate,
|
||||||
|
scheme,
|
||||||
|
scheme_digest,
|
||||||
|
signature,
|
||||||
|
timestamp,
|
||||||
|
timestamp_token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_timestamp_token<'a>(it: impl Iterator<Item = Tlv<'a>>) -> Result<(bool, Option<Vec<u8>>), SignError> {
|
||||||
let mut timestamp = false;
|
let mut timestamp = false;
|
||||||
let mut timestamp_token = None;
|
let mut timestamp_token = None;
|
||||||
for t in it {
|
for t in it {
|
||||||
@@ -309,20 +327,7 @@ fn parse_signer(si: &Tlv<'_>) -> Result<SignerInfo, SignError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(SignerInfo {
|
Ok((timestamp, timestamp_token))
|
||||||
sid,
|
|
||||||
digest,
|
|
||||||
signed_attrs,
|
|
||||||
message_digest,
|
|
||||||
content_type,
|
|
||||||
signing_time,
|
|
||||||
signing_certificate,
|
|
||||||
scheme,
|
|
||||||
scheme_digest,
|
|
||||||
signature,
|
|
||||||
timestamp,
|
|
||||||
timestamp_token,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attach an RFC 3161 `signatureTimeStampToken` unsigned attribute to a detached CMS object.
|
/// Attach an RFC 3161 `signatureTimeStampToken` unsigned attribute to a detached CMS object.
|
||||||
|
|||||||
+15
-16
@@ -657,22 +657,21 @@ impl PdfCraftApp {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
match self.session.extract(id, &pages) {
|
match self.session.extract(id, &pages) {
|
||||||
Ok(bytes) => {
|
Ok(bytes) => {
|
||||||
let message = if pages.len() == 1 {
|
let message = if pages.len() == 1 {
|
||||||
tl!("Extracted 1 page").to_string()
|
tl!("Extracted 1 page").to_string()
|
||||||
} else {
|
} else {
|
||||||
crate::i18n::fmt(tl!("Extracted {n} pages"), &[("n", &pages.len().to_string())])
|
crate::i18n::fmt(tl!("Extracted {n} pages"), &[("n", &pages.len().to_string())])
|
||||||
};
|
};
|
||||||
// Save As offers the tab's name, so a name typed in the dialog is kept.
|
// Save As offers the tab's name, so a name typed in the dialog is kept.
|
||||||
let name = if renamed { format!("{base}.pdf") } else { format!("{stem} (extract).pdf") };
|
let name = if renamed { format!("{base}.pdf") } else { format!("{stem} (extract).pdf") };
|
||||||
self.open_created(&name, bytes, &message)
|
self.open_created(&name, bytes, &message)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.notify_fmt("Couldn't extract pages: {e}", &[("e", &e.to_string())]);
|
self.notify_fmt("Couldn't extract pages: {e}", &[("e", &e.to_string())]);
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if opts.delete {
|
if opts.delete {
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ impl ProtectDraft {
|
|||||||
/// A rough password strength, as Acrobat's meter shows (Weak / Medium / Strong).
|
/// A rough password strength, as Acrobat's meter shows (Weak / Medium / Strong).
|
||||||
pub fn strength(pw: &str) -> (&'static str, Color32) {
|
pub fn strength(pw: &str) -> (&'static str, Color32) {
|
||||||
let classes = [
|
let classes = [
|
||||||
pw.chars().any(|c| c.is_lowercase()),
|
pw.chars().any(char::is_lowercase),
|
||||||
pw.chars().any(|c| c.is_uppercase()),
|
pw.chars().any(char::is_uppercase),
|
||||||
pw.chars().any(|c| c.is_ascii_digit()),
|
pw.chars().any(|c| c.is_ascii_digit()),
|
||||||
pw.chars().any(|c| !c.is_alphanumeric()),
|
pw.chars().any(|c| !c.is_alphanumeric()),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -206,10 +206,10 @@ impl RecoveryStore {
|
|||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
for entry in dir.flatten() {
|
for entry in dir.flatten() {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
if path.extension().and_then(|e| e.to_str()) != Some("pdf") || !entry.file_type().is_ok_and(|t| t.is_file()) {
|
if path.extension().and_then(std::ffi::OsStr::to_str) != Some("pdf") || !entry.file_type().is_ok_and(|t| t.is_file()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let Some(key) = path.file_stem().and_then(|s| s.to_str()) else { continue };
|
let Some(key) = path.file_stem().and_then(std::ffi::OsStr::to_str) else { continue };
|
||||||
let meta = std::fs::read(path.with_extension("json"))
|
let meta = std::fs::read(path.with_extension("json"))
|
||||||
.and_then(|b| serde_json::from_slice::<RecoveryMeta>(&b).map_err(std::io::Error::other))
|
.and_then(|b| serde_json::from_slice::<RecoveryMeta>(&b).map_err(std::io::Error::other))
|
||||||
.and_then(|m| if m.key == key { Ok(m) } else { Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "snapshot key mismatch")) });
|
.and_then(|m| if m.key == key { Ok(m) } else { Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "snapshot key mismatch")) });
|
||||||
@@ -266,7 +266,7 @@ impl RecoveryStore {
|
|||||||
for entry in dir.flatten() {
|
for entry in dir.flatten() {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
// Only sidecars whose PDF is gone: they hold no document content.
|
// Only sidecars whose PDF is gone: they hold no document content.
|
||||||
if path.extension().and_then(|e| e.to_str()) == Some("json")
|
if path.extension().and_then(std::ffi::OsStr::to_str) == Some("json")
|
||||||
&& entry.file_type().is_ok_and(|t| t.is_file())
|
&& entry.file_type().is_ok_and(|t| t.is_file())
|
||||||
&& !path.with_extension("pdf").exists()
|
&& !path.with_extension("pdf").exists()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ fn pdf(annots: &[&str]) -> Vec<u8> {
|
|||||||
"<< /Length 0 >>\nstream\n\nendstream".into(),
|
"<< /Length 0 >>\nstream\n\nendstream".into(),
|
||||||
"<< /Type /XObject /Subtype /Form /BBox [0 0 200 100] /Length 0 >>\nstream\n\nendstream".into(),
|
"<< /Type /XObject /Subtype /Form /BBox [0 0 200 100] /Length 0 >>\nstream\n\nendstream".into(),
|
||||||
];
|
];
|
||||||
objs.extend(annots.iter().map(|o| o.to_string()));
|
objs.extend(annots.iter().map(ToString::to_string));
|
||||||
let mut out = b"%PDF-1.7\n".to_vec();
|
let mut out = b"%PDF-1.7\n".to_vec();
|
||||||
let mut offsets = Vec::new();
|
let mut offsets = Vec::new();
|
||||||
for (i, o) in objs.iter().enumerate() {
|
for (i, o) in objs.iter().enumerate() {
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ fn store(tag: &str) -> RecoveryStore {
|
|||||||
|
|
||||||
/// Snapshot files (the PDF and its sidecar), not the running instances' lock files.
|
/// Snapshot files (the PDF and its sidecar), not the running instances' lock files.
|
||||||
fn files_in(s: &RecoveryStore) -> usize {
|
fn files_in(s: &RecoveryStore) -> usize {
|
||||||
std::fs::read_dir(s.dir()).map(|d| d.flatten().filter(|e| e.path().extension().and_then(|x| x.to_str()) != Some("lock")).count()).unwrap_or(0)
|
std::fs::read_dir(s.dir())
|
||||||
|
.map(|d| d.flatten().filter(|e| e.path().extension().and_then(std::ffi::OsStr::to_str) != Some("lock")).count())
|
||||||
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A session that edits a document, autosaves, and then "crashes" (is dropped).
|
/// A session that edits a document, autosaves, and then "crashes" (is dropped).
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ fn pdf(acroform: &str, extra: &str, objects: &[&str]) -> Vec<u8> {
|
|||||||
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 300 200] /Contents 4 0 R >>".into(),
|
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 300 200] /Contents 4 0 R >>".into(),
|
||||||
"<< /Length 0 >>\nstream\n\nendstream".into(),
|
"<< /Length 0 >>\nstream\n\nendstream".into(),
|
||||||
];
|
];
|
||||||
objs.extend(objects.iter().map(|o| o.to_string()));
|
objs.extend(objects.iter().map(ToString::to_string));
|
||||||
let mut out = b"%PDF-1.7\n".to_vec();
|
let mut out = b"%PDF-1.7\n".to_vec();
|
||||||
let mut offsets = Vec::new();
|
let mut offsets = Vec::new();
|
||||||
for (i, o) in objs.iter().enumerate() {
|
for (i, o) in objs.iter().enumerate() {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ fn load_average() -> Option<f64> {
|
|||||||
/// Frame budgets only mean something on a machine that isn't overcommitted: with the load
|
/// Frame budgets only mean something on a machine that isn't overcommitted: with the load
|
||||||
/// average above twice the core count (other builds running), skip and say so.
|
/// average above twice the core count (other builds running), skip and say so.
|
||||||
fn overloaded() -> bool {
|
fn overloaded() -> bool {
|
||||||
let cores = std::thread::available_parallelism().map_or(4, |n| n.get()) as f64;
|
let cores = std::thread::available_parallelism().map_or(4, std::num::NonZeroUsize::get) as f64;
|
||||||
match load_average() {
|
match load_average() {
|
||||||
Some(l) if l > 2.0 * cores => {
|
Some(l) if l > 2.0 * cores => {
|
||||||
eprintln!("PERF skipped: load average {l:.0} on {cores} cores; frame budgets need a quiet machine");
|
eprintln!("PERF skipped: load average {l:.0} on {cores} cores; frame budgets need a quiet machine");
|
||||||
|
|||||||
@@ -441,11 +441,8 @@ pub fn pattern_to_iso(text: &str, pattern: &str) -> Option<String> {
|
|||||||
"yyyy" | "yy" | "mm" | "m" | "dd" | "d" => {
|
"yyyy" | "yy" | "mm" | "m" | "dd" | "d" => {
|
||||||
// Digits up to the next literal (or a fixed width).
|
// Digits up to the next literal (or a fixed width).
|
||||||
let fixed = matches!(t.as_str(), "yyyy" | "yy" | "mm" | "dd");
|
let fixed = matches!(t.as_str(), "yyyy" | "yy" | "mm" | "dd");
|
||||||
let width = if fixed {
|
let width =
|
||||||
t.len()
|
if fixed { t.len() } else { rest.chars().take_while(char::is_ascii_digit).count().min(if t == "d" || t == "m" { 2 } else { 4 }) };
|
||||||
} else {
|
|
||||||
rest.chars().take_while(|c| c.is_ascii_digit()).count().min(if t == "d" || t == "m" { 2 } else { 4 })
|
|
||||||
};
|
|
||||||
let digits: String = rest.chars().take(width).collect();
|
let digits: String = rest.chars().take(width).collect();
|
||||||
if digits.is_empty() || !digits.chars().all(|c| c.is_ascii_digit()) {
|
if digits.is_empty() || !digits.chars().all(|c| c.is_ascii_digit()) {
|
||||||
return None;
|
return None;
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ fn words(runs: &[Run], base: &Face, embed: &dyn Fn(&str, usize) -> String, embed
|
|||||||
.filter_map(|c| match c {
|
.filter_map(|c| match c {
|
||||||
'\r' | '\n' | '\t' | '\u{2028}' | '\u{2029}' | '\u{2009}' | '\u{202F}' | '\u{3000}' => Some(' '),
|
'\r' | '\n' | '\t' | '\u{2028}' | '\u{2029}' | '\u{2009}' | '\u{202F}' | '\u{3000}' => Some(' '),
|
||||||
'\u{00AD}' | '\u{200B}' | '\u{200C}' | '\u{200D}' | '\u{FEFF}' => None,
|
'\u{00AD}' | '\u{200B}' | '\u{200C}' | '\u{200D}' | '\u{FEFF}' => None,
|
||||||
'\u{2011}' | '\u{2010}' | '\u{2012}' | '\u{2013}' => Some('-'),
|
'\u{2010}'..='\u{2013}' => Some('-'),
|
||||||
'\u{2018}' | '\u{2019}' => Some('\''),
|
'\u{2018}' | '\u{2019}' => Some('\''),
|
||||||
_ => Some(c),
|
_ => Some(c),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -92,6 +92,12 @@ has() {
|
|||||||
case " $FORMATS " in *" $format "*) return 0 ;; *) return 1 ;; esac
|
case " $FORMATS " in *" $format "*) return 0 ;; *) return 1 ;; esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
verify_sha256() {
|
||||||
|
local expected="$1"
|
||||||
|
local target="$2"
|
||||||
|
printf '%s %s\n' "$expected" "$target" | sha256sum -c -
|
||||||
|
}
|
||||||
|
|
||||||
# ---- .tar.gz ------------------------------------------------------------------------------------
|
# ---- .tar.gz ------------------------------------------------------------------------------------
|
||||||
if has tar; then
|
if has tar; then
|
||||||
mkdir -p "$WORK/tar"
|
mkdir -p "$WORK/tar"
|
||||||
@@ -164,21 +170,21 @@ if has appimage; then
|
|||||||
download="$TOOL.download"
|
download="$TOOL.download"
|
||||||
curl --proto '=https' -fsSL -o "$download" \
|
curl --proto '=https' -fsSL -o "$download" \
|
||||||
"https://github.com/AppImage/appimagetool/releases/download/$APPIMAGETOOL_VERSION/appimagetool-$ARCH.AppImage"
|
"https://github.com/AppImage/appimagetool/releases/download/$APPIMAGETOOL_VERSION/appimagetool-$ARCH.AppImage"
|
||||||
printf '%s %s\n' "$APPIMAGETOOL_SHA256" "$download" | sha256sum -c -
|
verify_sha256 "$APPIMAGETOOL_SHA256" "$download"
|
||||||
chmod 755 "$download"
|
chmod 755 "$download"
|
||||||
mv "$download" "$TOOL"
|
mv "$download" "$TOOL"
|
||||||
fi
|
fi
|
||||||
printf '%s %s\n' "$APPIMAGETOOL_SHA256" "$TOOL" | sha256sum -c -
|
verify_sha256 "$APPIMAGETOOL_SHA256" "$TOOL"
|
||||||
fi
|
fi
|
||||||
RUNTIME="$CARGO_TARGET_DIR/appimage-runtime-$APPIMAGE_RUNTIME_VERSION-$ARCH"
|
RUNTIME="$CARGO_TARGET_DIR/appimage-runtime-$APPIMAGE_RUNTIME_VERSION-$ARCH"
|
||||||
if [[ ! -f "$RUNTIME" ]]; then
|
if [[ ! -f "$RUNTIME" ]]; then
|
||||||
download="$RUNTIME.download"
|
download="$RUNTIME.download"
|
||||||
curl --proto '=https' -fsSL -o "$download" \
|
curl --proto '=https' -fsSL -o "$download" \
|
||||||
"https://github.com/AppImage/type2-runtime/releases/download/$APPIMAGE_RUNTIME_VERSION/runtime-$ARCH"
|
"https://github.com/AppImage/type2-runtime/releases/download/$APPIMAGE_RUNTIME_VERSION/runtime-$ARCH"
|
||||||
printf '%s %s\n' "$APPIMAGE_RUNTIME_SHA256" "$download" | sha256sum -c -
|
verify_sha256 "$APPIMAGE_RUNTIME_SHA256" "$download"
|
||||||
mv "$download" "$RUNTIME"
|
mv "$download" "$RUNTIME"
|
||||||
fi
|
fi
|
||||||
printf '%s %s\n' "$APPIMAGE_RUNTIME_SHA256" "$RUNTIME" | sha256sum -c -
|
verify_sha256 "$APPIMAGE_RUNTIME_SHA256" "$RUNTIME"
|
||||||
# Absolute, because appimagetool runs in $DIST below (CARGO_TARGET_DIR or APPIMAGETOOL may be
|
# Absolute, because appimagetool runs in $DIST below (CARGO_TARGET_DIR or APPIMAGETOOL may be
|
||||||
# relative, e.g. target/agent-<name>).
|
# relative, e.g. target/agent-<name>).
|
||||||
OUT="$(cd "$DIST" && pwd)/$BASENAME.AppImage"
|
OUT="$(cd "$DIST" && pwd)/$BASENAME.AppImage"
|
||||||
|
|||||||
+1
-1
@@ -229,7 +229,7 @@ fn cargo_lock_versions(root: &Path) -> Result<BTreeSet<(String, String)>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_asset_path(path: &str) -> bool {
|
pub fn is_asset_path(path: &str) -> bool {
|
||||||
Path::new(path).extension().and_then(|e| e.to_str()).is_some_and(|e| ASSET_EXTENSIONS.contains(&e.to_ascii_lowercase().as_str()))
|
Path::new(path).extension().and_then(std::ffi::OsStr::to_str).is_some_and(|e| ASSET_EXTENSIONS.contains(&e.to_ascii_lowercase().as_str()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Every licence id in an SPDX expression (`MIT OR Apache-2.0`, `MIT AND Bitstream-Vera`).
|
/// Every licence id in an SPDX expression (`MIT OR Apache-2.0`, `MIT AND Bitstream-Vera`).
|
||||||
|
|||||||
+1
-1
@@ -228,7 +228,7 @@ pub fn check(crates: &[Crate]) -> Vec<Violation> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out.sort_by_key(|v| v.to_string());
|
out.sort_by_key(ToString::to_string);
|
||||||
out.dedup();
|
out.dedup();
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user