mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-28 20:18:06 -06:00
chore: resolve SonarCloud maintainability issues
This commit is contained in:
@@ -644,7 +644,7 @@ fn render(args: &[String]) -> Result<(), CliError> {
|
||||
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)")?;
|
||||
// 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("jpg" | "jpeg") => Some(ImageFormat::Jpeg { quality: 90 }),
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
+12
-11
@@ -53,6 +53,17 @@ struct Walk<'a> {
|
||||
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<'_> {
|
||||
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()
|
||||
@@ -68,17 +79,7 @@ impl Walk<'_> {
|
||||
let inside = covered || stack.last().copied().unwrap_or(false);
|
||||
match op.op.as_slice() {
|
||||
b"BMC" => stack.push(inside || op.name(0) == Some(b"Artifact")),
|
||||
b"BDC" => {
|
||||
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"BDC" => stack.push(inside || bdc_marks(self.doc, &op, &props)),
|
||||
b"EMC" => {
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
@@ -380,7 +380,7 @@ fn table_doc(th: &str, extra: &[&str], kids: &str) -> Document {
|
||||
th.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, "")
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
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)"));
|
||||
}
|
||||
// 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));
|
||||
}
|
||||
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"PCPicture".to_vec(), Object::Ref(*picture));
|
||||
d.set(b"PCPictureImage".to_vec(), Object::Bool(*image));
|
||||
|
||||
@@ -58,7 +58,7 @@ impl<'a> Recipe<'a> {
|
||||
}
|
||||
|
||||
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()))
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ impl<'a> Recipe<'a> {
|
||||
if input.len() as u64 > MAX_INPUT {
|
||||
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 log = Vec::new();
|
||||
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 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.
|
||||
if workers == 1 || pages.len() < 8 {
|
||||
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,
|
||||
// APP2 ICC profile, possibly split over several chunks.
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use super::*;
|
||||
use pdfcraft_cos::{Dict, Document, ObjRef, Object, PdfString};
|
||||
|
||||
use crate::EditError;
|
||||
use super::{draw_laid_shaped, AddedText, GlyphError, GlyphOutline, LaidLine, Piece, ShapedCluster, ShapedScript, GLYPHS_PER_FONT};
|
||||
|
||||
/// Regression fingerprint for the small-item fixture: drawing bytes, font map and document
|
||||
/// objects, with generated streams decoded so Flate backend selection cannot change the hash.
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
use super::*;
|
||||
use pdfcraft_cos::{Dict, Document, Object, Stream};
|
||||
|
||||
use super::{
|
||||
added, add_header_footer, add_watermark, contents, is_wrap_close, page_list, place_tagged, remove_marks,
|
||||
stamp, stream_bytes, stream_tag, wrapper_role, Context, EditError, HeaderFooter, MarkKind, Watermark,
|
||||
ISOLATION_MAX_DEPTH, OLD_WRAP_CLOSE, WRAP_CLOSE, WRAP_OPEN,
|
||||
};
|
||||
|
||||
fn fixture(parts: &[&[u8]]) -> Document {
|
||||
let mut doc = Document::new_empty();
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
//! Geometry-based regions and mixed-style rewriting of existing text.
|
||||
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use pdfcraft_cos::{Dict, Document, Object, PdfString};
|
||||
|
||||
use super::{
|
||||
coincident_ops, content_streams, needs_type3, no_japanese_font, page_dict, reveal_ops, rewritten_contents,
|
||||
source_encodes, source_family, source_program, source_show, source_space, source_width, splice, type3_font,
|
||||
union, BlockStyle, EditError, LineEdit, Matrix, Metrics, Op, Pieces, RangeStyle, TextBlock, TextLine,
|
||||
MAX_TYPE3_GLYPHS,
|
||||
};
|
||||
|
||||
fn same_plane(a: &TextLine, b: &TextLine) -> bool {
|
||||
let at = Matrix(a.origin.tm).then(&Matrix(a.origin.ctm));
|
||||
@@ -166,8 +175,8 @@ fn opens_with_marker(text: &str) -> bool {
|
||||
if !first.is_ascii_digit() && !first.is_ascii_alphabetic() {
|
||||
return false;
|
||||
}
|
||||
let digits = text.chars().take_while(|c| c.is_ascii_digit()).count();
|
||||
let letters = text.chars().take_while(|c| c.is_ascii_alphabetic()).count();
|
||||
let digits = text.chars().take_while(char::is_ascii_digit).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:
|
||||
// the slice below starts on a character boundary.
|
||||
let rest = if digits > 0 {
|
||||
@@ -178,7 +187,7 @@ fn opens_with_marker(text: &str) -> bool {
|
||||
return false;
|
||||
};
|
||||
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
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
//! another one's output; the commit writes into the document it was opened on, and refuses if
|
||||
//! that document has changed since.
|
||||
|
||||
use super::*;
|
||||
use super::{
|
||||
group_blocks, layout_draft, rewrite_block_from, text_lines, BlockStyle, Document, EditError, LineEdit,
|
||||
TextBlock, TextEditLayout, TextLine, MAX_TYPE3_GLYPHS,
|
||||
};
|
||||
|
||||
/// One paragraph being edited: the document as it was opened, and the page's lines then.
|
||||
pub struct TextSession {
|
||||
|
||||
@@ -1050,7 +1050,7 @@ fn shown_under_redactions(
|
||||
}
|
||||
|
||||
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
|
||||
@@ -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"),
|
||||
_ => {}
|
||||
}
|
||||
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");
|
||||
}
|
||||
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)>> {
|
||||
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();
|
||||
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();
|
||||
// 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()
|
||||
|
||||
@@ -75,7 +75,7 @@ impl OcrPage {
|
||||
/// The recogniser, loaded once (it takes a moment) and shared.
|
||||
pub fn engine() -> Result<Arc<Ocr>, String> {
|
||||
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() {
|
||||
return Ok(o.clone());
|
||||
}
|
||||
|
||||
@@ -74,5 +74,5 @@ pub fn discover(roots: &[PathBuf]) -> Vec<FontFile> {
|
||||
files
|
||||
}
|
||||
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 {
|
||||
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(());
|
||||
}
|
||||
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.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();
|
||||
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);
|
||||
let ncols = edges.len();
|
||||
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 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();
|
||||
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
|
||||
};
|
||||
|
||||
@@ -59,7 +59,7 @@ impl Std14 {
|
||||
/// report that called it a standard-14 name would be telling the reader something untrue.
|
||||
pub fn recognise(base: &str) -> Option<(Self, Match)> {
|
||||
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 (bold, italic) = (has("bold"), has("italic") || has("oblique"));
|
||||
// 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)");
|
||||
}
|
||||
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");
|
||||
}
|
||||
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;
|
||||
}
|
||||
if low[v] == index[v] {
|
||||
while let Some(w) = stack.pop() {
|
||||
on_stack[w] = false;
|
||||
component[w] = count;
|
||||
if w == v {
|
||||
break;
|
||||
}
|
||||
}
|
||||
pop_component(&mut stack, &mut on_stack, &mut component, v, count);
|
||||
count += 1;
|
||||
}
|
||||
if let Some(&(parent, _)) = work.last() {
|
||||
@@ -260,6 +254,16 @@ fn components(links: &[Vec<usize>]) -> Vec<usize> {
|
||||
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
|
||||
/// 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.
|
||||
|
||||
@@ -307,7 +307,7 @@ fn label_for(words: &[Word], rect: [f64; 4], right_of: bool) -> String {
|
||||
.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)
|
||||
.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();
|
||||
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(" "))
|
||||
|
||||
@@ -269,23 +269,28 @@ pub fn cell_name(pattern: &str, row: usize, column: usize, seq: usize) -> String
|
||||
if numbered {
|
||||
return out;
|
||||
}
|
||||
substitute_isolated_n(&out, &row_number)
|
||||
}
|
||||
|
||||
fn substitute_isolated_n(out: &str, row_number: &str) -> String {
|
||||
if out == "N" || out == "n" {
|
||||
return row_number;
|
||||
return row_number.to_string();
|
||||
}
|
||||
let chars: Vec<char> = out.chars().collect();
|
||||
let mut named = String::with_capacity(out.len() + row_number.len());
|
||||
let mut numbered = false;
|
||||
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 before_separator = chars.get(i + 1).is_none_or(|c| SEPARATORS.contains(c));
|
||||
if matches!(ch, 'N' | 'n') && after_separator && before_separator {
|
||||
named.push_str(&row_number);
|
||||
named.push_str(row_number);
|
||||
numbered = true;
|
||||
} else {
|
||||
named.push(ch);
|
||||
}
|
||||
}
|
||||
if !numbered {
|
||||
named.push_str(&row_number);
|
||||
named.push_str(row_number);
|
||||
}
|
||||
named
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ fn lex(src: &str) -> Result<Vec<Token>, String> {
|
||||
}
|
||||
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;
|
||||
while i < chars.len() && (chars[i].is_ascii_digit() || (chars[i] == '.' && chars.get(i + 1) != Some(&'.'))) {
|
||||
i += 1;
|
||||
@@ -760,7 +760,7 @@ fn parse_num(s: &str) -> Option<f64> {
|
||||
}
|
||||
|
||||
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 ───────────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -1416,7 +1416,7 @@ fn install(ctx: &mut Context, event: &XfaEvent, doc: &XfaDoc, current: usize) ->
|
||||
"DataView",
|
||||
"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 {
|
||||
let kids: Vec<(usize, String, bool)> = {
|
||||
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()),
|
||||
"<< /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"))
|
||||
}
|
||||
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
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.
|
||||
#[cfg(unix)]
|
||||
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.
|
||||
pub fn iso_date(pdf: &str) -> Option<String> {
|
||||
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 {
|
||||
return None;
|
||||
}
|
||||
@@ -161,7 +161,7 @@ pub fn iso_date(pdf: &str) -> Option<String> {
|
||||
let tz = match rest.chars().next() {
|
||||
Some('Z') | None => "Z".to_string(),
|
||||
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"))
|
||||
}
|
||||
_ => "Z".to_string(),
|
||||
|
||||
@@ -616,14 +616,7 @@ fn unlink_from_forms(doc: &mut Document, holders: &[ObjRef], doomed: &HashSet<Ob
|
||||
continue;
|
||||
}
|
||||
let data = form.decoded()?;
|
||||
let mut pruned = false;
|
||||
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() }),
|
||||
}
|
||||
}
|
||||
let mut pruned = prune_doomed_xobjects(doc, &data, &mut xo, &doomed_names)?;
|
||||
if pruned {
|
||||
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(())
|
||||
}
|
||||
|
||||
fn prune_doomed_xobjects(doc: &Document, data: &[u8], xo: &mut Dict, doomed_names: &[Vec<u8>]) -> Result<bool, CosError> {
|
||||
let mut pruned = false;
|
||||
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() }),
|
||||
}
|
||||
}
|
||||
Ok(pruned)
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// 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.
|
||||
if let Some(a) = number(s, i, 1, 4) {
|
||||
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)
|
||||
&& 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 })
|
||||
|
||||
@@ -108,7 +108,7 @@ pub(crate) fn touched(doc: &Document, resources: &Dict, before: &[u8], after: &[
|
||||
let mut changed = HashSet::new();
|
||||
let mut empty = HashSet::new();
|
||||
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 {
|
||||
match a.ids.get(id) {
|
||||
Some((new, draws)) => {
|
||||
|
||||
@@ -51,7 +51,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
};
|
||||
let threads: usize = match args.get(4) {
|
||||
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)?);
|
||||
|
||||
@@ -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 {
|
||||
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).
|
||||
@@ -587,7 +587,7 @@ struct Shared {
|
||||
}
|
||||
|
||||
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"))]
|
||||
@@ -666,7 +666,7 @@ impl PoolParser {
|
||||
// Drop never joins a parser. A waiting worker must still notice
|
||||
// its pool's disconnected wake channel while another thread is
|
||||
// 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 => {
|
||||
*state = ParserState::Loading;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{PageRenderer, RenderConfig, RenderRequest, RenderedPage, Tile};
|
||||
|
||||
fn masked_shading(shading: &str, extra: &str, ink: &str) -> Vec<u8> {
|
||||
let content = format!("20.25 20.25 279.5 279.5 re W n /M gs {ink} 0 0 320 320 re f");
|
||||
|
||||
@@ -220,7 +220,7 @@ impl WorkQueue {
|
||||
}
|
||||
// The parser validity token can change independently of this queue. Bound the
|
||||
// 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;
|
||||
}
|
||||
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;
|
||||
};
|
||||
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 at = if text.chars().all(|c| c.is_control()) {
|
||||
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(char::is_control) {
|
||||
None
|
||||
} else {
|
||||
self.glyphs.push(glyph);
|
||||
@@ -594,7 +594,7 @@ impl TextDevice {
|
||||
let text = replacement.to_owned();
|
||||
#[cfg(test)]
|
||||
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) {
|
||||
Some(glyph) => self.glyphs.push(glyph),
|
||||
None => self.glyphs.extend_from_slice(run),
|
||||
@@ -640,7 +640,7 @@ impl<'a> Device<'a> for TextDevice {
|
||||
return;
|
||||
}
|
||||
};
|
||||
if !replaced && text.chars().all(|c| c.is_control()) {
|
||||
if !replaced && text.chars().all(char::is_control) {
|
||||
return;
|
||||
}
|
||||
// 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 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_token = None;
|
||||
for t in it {
|
||||
@@ -309,20 +327,7 @@ fn parse_signer(si: &Tlv<'_>) -> Result<SignerInfo, SignError> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(SignerInfo {
|
||||
sid,
|
||||
digest,
|
||||
signed_attrs,
|
||||
message_digest,
|
||||
content_type,
|
||||
signing_time,
|
||||
signing_certificate,
|
||||
scheme,
|
||||
scheme_digest,
|
||||
signature,
|
||||
timestamp,
|
||||
timestamp_token,
|
||||
})
|
||||
Ok((timestamp, timestamp_token))
|
||||
}
|
||||
|
||||
/// Attach an RFC 3161 `signatureTimeStampToken` unsigned attribute to a detached CMS object.
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
use super::*;
|
||||
use pdfcraft_cos::{Algorithm, NewEncryption, XrefEntry, write_full, write_incremental};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use pdfcraft_cos::{Algorithm, Dict, Document, NewEncryption, ObjRef, Object, SaveOptions, Stream, XrefEntry, write_full, write_incremental};
|
||||
|
||||
use super::{
|
||||
is_document_timestamp, list_cached, only_grew, DigestCache, DssEvidence, TrustStore, DISCOVERY_CACHE_LIMIT,
|
||||
MAX_DSS_BYTES, MAX_DSS_ENTRIES,
|
||||
};
|
||||
|
||||
const STAMP: &str = "<< /Type /Sig /SubFilter /ETSI.RFC3161 /ByteRange [0 1 2 1] >>";
|
||||
|
||||
fn fixture(extra: &[&str]) -> Document {
|
||||
|
||||
+15
-16
@@ -657,22 +657,21 @@ impl PdfCraftApp {
|
||||
}
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
match self.session.extract(id, &pages) {
|
||||
Ok(bytes) => {
|
||||
let message = if pages.len() == 1 {
|
||||
tl!("Extracted 1 page").to_string()
|
||||
} else {
|
||||
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.
|
||||
let name = if renamed { format!("{base}.pdf") } else { format!("{stem} (extract).pdf") };
|
||||
self.open_created(&name, bytes, &message)
|
||||
}
|
||||
Err(e) => {
|
||||
self.notify_fmt("Couldn't extract pages: {e}", &[("e", &e.to_string())]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
match self.session.extract(id, &pages) {
|
||||
Ok(bytes) => {
|
||||
let message = if pages.len() == 1 {
|
||||
tl!("Extracted 1 page").to_string()
|
||||
} else {
|
||||
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.
|
||||
let name = if renamed { format!("{base}.pdf") } else { format!("{stem} (extract).pdf") };
|
||||
self.open_created(&name, bytes, &message)
|
||||
}
|
||||
Err(e) => {
|
||||
self.notify_fmt("Couldn't extract pages: {e}", &[("e", &e.to_string())]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if opts.delete {
|
||||
|
||||
@@ -56,9 +56,9 @@ impl ProtectDraft {
|
||||
/// A rough password strength, as Acrobat's meter shows (Weak / Medium / Strong).
|
||||
pub fn strength(pw: &str) -> (&'static str, Color32) {
|
||||
let classes = [
|
||||
pw.chars().any(|c| c.is_lowercase()),
|
||||
pw.chars().any(|c| c.is_uppercase()),
|
||||
pw.chars().any(|c| c.is_ascii_digit()),
|
||||
pw.chars().any(char::is_lowercase),
|
||||
pw.chars().any(char::is_uppercase),
|
||||
pw.chars().any(char::is_ascii_digit),
|
||||
pw.chars().any(|c| !c.is_alphanumeric()),
|
||||
]
|
||||
.iter()
|
||||
|
||||
@@ -206,10 +206,10 @@ impl RecoveryStore {
|
||||
let mut out = Vec::new();
|
||||
for entry in dir.flatten() {
|
||||
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;
|
||||
}
|
||||
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"))
|
||||
.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")) });
|
||||
@@ -266,7 +266,7 @@ impl RecoveryStore {
|
||||
for entry in dir.flatten() {
|
||||
let path = entry.path();
|
||||
// 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())
|
||||
&& !path.with_extension("pdf").exists()
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ fn pdf(annots: &[&str]) -> Vec<u8> {
|
||||
"<< /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 offsets = Vec::new();
|
||||
for (i, o) in objs.iter().enumerate() {
|
||||
|
||||
@@ -43,7 +43,7 @@ fn store(tag: &str) -> RecoveryStore {
|
||||
|
||||
/// Snapshot files (the PDF and its sidecar), not the running instances' lock files.
|
||||
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).
|
||||
|
||||
@@ -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(),
|
||||
"<< /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 offsets = Vec::new();
|
||||
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
|
||||
/// average above twice the core count (other builds running), skip and say so.
|
||||
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() {
|
||||
Some(l) if l > 2.0 * cores => {
|
||||
eprintln!("PERF skipped: load average {l:.0} on {cores} cores; frame budgets need a quiet machine");
|
||||
|
||||
@@ -444,10 +444,10 @@ pub fn pattern_to_iso(text: &str, pattern: &str) -> Option<String> {
|
||||
let width = if fixed {
|
||||
t.len()
|
||||
} else {
|
||||
rest.chars().take_while(|c| c.is_ascii_digit()).count().min(if t == "d" || t == "m" { 2 } else { 4 })
|
||||
rest.chars().take_while(char::is_ascii_digit).count().min(if t == "d" || t == "m" { 2 } else { 4 })
|
||||
};
|
||||
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(char::is_ascii_digit) {
|
||||
return None;
|
||||
}
|
||||
let v: u32 = digits.parse().ok()?;
|
||||
|
||||
@@ -14,7 +14,10 @@ use base64::Engine;
|
||||
|
||||
use crate::XfaError;
|
||||
use crate::data::{DataNode, data_date_letters, data_path, data_to_iso, date_letters, is_on, iso_to_pattern};
|
||||
use crate::model::*;
|
||||
use crate::model::{
|
||||
Access, Area, Border, ChoiceList, Common, Draw, ExclGroup, Field, Font, HAlign, Layout, Margin, Node, Occur,
|
||||
PageArea, PageRole, Para, Placement, Presence, Rect, Script, Stroke, Subform, Template, Ui, VAlign, Value,
|
||||
};
|
||||
use crate::text::{self, Block, Face, Span};
|
||||
|
||||
/// Most pages a form may lay out to.
|
||||
|
||||
@@ -5,7 +5,11 @@ use base64::Engine;
|
||||
use roxmltree::Node as XmlNode;
|
||||
|
||||
use crate::XfaError;
|
||||
use crate::model::*;
|
||||
use crate::model::{
|
||||
Access, Anchor, Area, Border, Caption, ChoiceList, Color, Common, Draw, Edge, ExclGroup, Field, FontSpec,
|
||||
HAlign, Layout, Margin, Node, Occur, PageArea, PageRole, PageSet, ParaSpec, Paragraph, Placement, Presence,
|
||||
Rect, RichText, Run, Script, Stroke, Subform, Template, Ui, VAlign, Value,
|
||||
};
|
||||
|
||||
/// Deepest element nesting followed (templates are shallow; hostile ones are not).
|
||||
const MAX_DEPTH: usize = 64;
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::collections::{BTreeMap, HashMap};
|
||||
use pdfcraft_cos::{Dict, Document, Object, PdfString};
|
||||
|
||||
use crate::data::{DataNode, data_path, parse_datasets};
|
||||
use crate::model::*;
|
||||
use crate::model::{Access, Area, Draw, ExclGroup, Field, Node, Presence, Script, Subform, Template};
|
||||
use crate::{Report, XfaError};
|
||||
|
||||
/// Private key on the AcroForm: presence scripts set, by SOM path.
|
||||
|
||||
@@ -213,7 +213,7 @@ fn words(runs: &[Run], base: &Face, embed: &dyn Fn(&str, usize) -> String, embed
|
||||
.filter_map(|c| match c {
|
||||
'\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{2011}' | '\u{2010}' | '\u{2012}' | '\u{2013}' => Some('-'),
|
||||
'\u{2010}'..='\u{2013}' => Some('-'),
|
||||
'\u{2018}' | '\u{2019}' => Some('\''),
|
||||
_ => Some(c),
|
||||
})
|
||||
|
||||
@@ -154,6 +154,7 @@ if has appimage; then
|
||||
ln -s "$APP_ID.png" "$APPDIR/.DirIcon"
|
||||
|
||||
TOOL="${APPIMAGETOOL:-$(command -v appimagetool || true)}"
|
||||
SHA_FMT='%s %s\n'
|
||||
if [[ -z "$TOOL" ]]; then
|
||||
if [[ -z "$APPIMAGETOOL_SHA256" ]]; then
|
||||
echo "no pinned appimagetool for $ARCH; set APPIMAGETOOL or put appimagetool on PATH" >&2
|
||||
@@ -164,21 +165,21 @@ if has appimage; then
|
||||
download="$TOOL.download"
|
||||
curl --proto '=https' -fsSL -o "$download" \
|
||||
"https://github.com/AppImage/appimagetool/releases/download/$APPIMAGETOOL_VERSION/appimagetool-$ARCH.AppImage"
|
||||
printf '%s %s\n' "$APPIMAGETOOL_SHA256" "$download" | sha256sum -c -
|
||||
printf "$SHA_FMT" "$APPIMAGETOOL_SHA256" "$download" | sha256sum -c -
|
||||
chmod 755 "$download"
|
||||
mv "$download" "$TOOL"
|
||||
fi
|
||||
printf '%s %s\n' "$APPIMAGETOOL_SHA256" "$TOOL" | sha256sum -c -
|
||||
printf "$SHA_FMT" "$APPIMAGETOOL_SHA256" "$TOOL" | sha256sum -c -
|
||||
fi
|
||||
RUNTIME="$CARGO_TARGET_DIR/appimage-runtime-$APPIMAGE_RUNTIME_VERSION-$ARCH"
|
||||
if [[ ! -f "$RUNTIME" ]]; then
|
||||
download="$RUNTIME.download"
|
||||
curl --proto '=https' -fsSL -o "$download" \
|
||||
"https://github.com/AppImage/type2-runtime/releases/download/$APPIMAGE_RUNTIME_VERSION/runtime-$ARCH"
|
||||
printf '%s %s\n' "$APPIMAGE_RUNTIME_SHA256" "$download" | sha256sum -c -
|
||||
printf "$SHA_FMT" "$APPIMAGE_RUNTIME_SHA256" "$download" | sha256sum -c -
|
||||
mv "$download" "$RUNTIME"
|
||||
fi
|
||||
printf '%s %s\n' "$APPIMAGE_RUNTIME_SHA256" "$RUNTIME" | sha256sum -c -
|
||||
printf "$SHA_FMT" "$APPIMAGE_RUNTIME_SHA256" "$RUNTIME" | sha256sum -c -
|
||||
# Absolute, because appimagetool runs in $DIST below (CARGO_TARGET_DIR or APPIMAGETOOL may be
|
||||
# relative, e.g. target/agent-<name>).
|
||||
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 {
|
||||
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`).
|
||||
|
||||
+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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user