Merge upstream/main into main preserving custom features

- Resolve all 13 merge conflicts between upstream/main and main
- Preserve full image editing suite: OBB, crop, proportional resize, arrange, and context menu actions
- Preserve text editing formatting tools: bullet/numbered lists, subscript/superscript, and line spacing
- Cleanly integrate upstream's canvas modularization, font updates, and styling changes
- Refactor arrange_image and run_edit_page_image helpers to satisfy clippy cognitive complexity limits
- All engine, edit, and UI integration test suites passing cleanly
This commit is contained in:
Combiler01
2026-10-31 15:50:31 +05:30
565 changed files with 59061 additions and 14603 deletions
+32 -12
View File
@@ -1,4 +1,12 @@
use super::*;
#[cfg(test)]
use super::check_refresh_panic;
use super::{
Arc, DocId, Document, Edit, EditCtx, EditError, Label, MAX_UNDO, ObjectKind, OnceLock, RenderPool, SaveOptions, Scope, Session,
XFA_REMOVED_PREFIX, XFA_SCRIPTS_OFF, check_permission, comment_list, display_bytes, display_config, guard, info_fields, inspect, js, keys_after,
note_warnings, render_threads, run_edit, scan_colours, scope_of, shown_under_redactions, signatures_of, sync_xfa_state, undrawn_notices,
uses_scripts, write_incremental, xfa, xfa_sync_datasets,
};
use crate::{EditNotice, MarkKind};
impl Session {
/// Apply an edit. On success the previous state is undoable and the view data is refreshed.
@@ -159,13 +167,7 @@ impl Session {
if signed && ((rewrites(&next) && !rewrites(&editor.cos)) || (next.is_modified() && next.revisions().is_empty())) {
return Err(EditError::SignedRewrite(edit.label().to_string()));
}
// What this edit drew with `?` (a value or comment text its font can't draw, stored as
// typed): reported, never silent (AGENTS.md §4).
let notices = if matches!(scope_of(&edit), Scope::Form | Scope::Comments | Scope::Full) {
guard(|| undrawn_notices(&editor.cos, &next)).unwrap_or_default()
} else {
Vec::new()
};
let notices = edit_notices(&edit, &editor.cos, &next);
let previous = std::mem::replace(&mut editor.cos, next);
let keys = keys_after(&edit).unwrap_or_else(|| editor.keys.clone());
let previous_keys = std::mem::replace(&mut editor.keys, keys);
@@ -373,7 +375,7 @@ impl Session {
let signatures = if doc.signatures.is_empty() { Vec::new() } else { signatures_of(&editor.cos, &bytes, &doc.trust, &doc.sig_cache) };
doc.display = display_bytes(Some(editor), &bytes);
doc.signatures = Arc::new(signatures);
doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), doc.config.clone());
doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), display_config(&doc.config));
doc.bytes = bytes;
Ok(())
}
@@ -431,7 +433,7 @@ impl Session {
let signatures = if doc.signatures.is_empty() { Vec::new() } else { signatures_of(&editor.cos, &bytes, &doc.trust, &doc.sig_cache) };
doc.display = display_bytes(Some(editor), &bytes);
doc.signatures = Arc::new(signatures);
doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), doc.config.clone());
doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), display_config(&doc.config));
doc.bytes = bytes;
doc.fonts_stale = true;
Ok(())
@@ -458,7 +460,7 @@ impl Session {
/// After a refresh failed: draw the display bytes it left (the original, once released),
/// so the page still shows a document.
pub(super) fn show_released(doc: &mut Document) {
doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), doc.config.clone());
doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), display_config(&doc.config));
}
/// Rebuild working bytes, inspection and renderer from the current edit state.
@@ -515,8 +517,26 @@ impl Session {
let signatures = signatures_of(&editor.cos, &bytes, &doc.trust, &doc.sig_cache);
doc.display = display_bytes(Some(editor), &bytes);
doc.signatures = Arc::new(signatures);
doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), doc.config.clone());
doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), display_config(&doc.config));
doc.bytes = bytes;
Ok(())
}
}
/// What `edit`, taking `prev` to `next`, wants said: never silent (AGENTS.md §4).
fn edit_notices(edit: &Edit, prev: &pdfcraft_cos::Document, next: &pdfcraft_cos::Document) -> Vec<EditNotice> {
// What it drew with `?` (a value or comment text its font can't draw, stored as typed).
let mut notices: Vec<EditNotice> = if matches!(scope_of(edit), Scope::Form | Scope::Comments | Scope::Full) {
guard(|| undrawn_notices(prev, next)).unwrap_or_default().into_iter().map(EditNotice::Undrawn).collect()
} else {
Vec::new()
};
// Pages whose own white backdrop Remove background couldn't turn back on.
if matches!(edit, Edit::RemoveMarks { kind: MarkKind::Background }) {
let pages = guard(|| pdfcraft_edit::backdrops_left_off(next)).unwrap_or_default();
if !pages.is_empty() {
notices.push(EditNotice::BackdropsLeftOff { pages });
}
}
notices
}
+11 -5
View File
@@ -1,4 +1,8 @@
use super::*;
use super::{
Arc, CombineSource, Combined, DataFormat, DocId, EditError, Hidden, ImageResolution, LOW_RESOLUTION_PRINT_DPI, OptimizeReport, SaveOptions,
Session, SourceKind, SplitPart, decode_text, guard, open_source, open_source_with, optimize, print, print_raster, set_default_layer_state,
source_kind, writable, write_full,
};
impl Session {
/// The page count of another PDF (Replace Pages, Insert Pages dialogs).
@@ -64,10 +68,12 @@ impl Session {
if pdf { Ok(bytes.clone()) } else { self.convert_to_pdf(name, bytes).map(|(_, pdf)| pdf) }
}
/// Reduce File Size: Acrobat's defaults (images above 225 ppi to 150 ppi, JPEG medium
/// quality; thumbnails dropped), identical resources merged, unused objects dropped, objects
/// packed into compressed object streams. Returns the bytes and how many objects were
/// merged. The open document is not changed (Acrobat saves the reduced copy as a new file).
/// Reduce File Size: Acrobat's defaults (colour and grey images above 225 ppi to 150 ppi,
/// JPEG medium quality; black-and-white averaged down to 300 ppi above 450 ppi, and stored as
/// CCITT Group 4 whenever smaller; thumbnails dropped), identical resources merged, unused
/// objects dropped, objects packed into compressed object streams. Returns the bytes and how
/// many objects were merged. The open document is not changed (Acrobat saves the reduced copy
/// as a new file).
pub fn reduced_bytes(&self, id: DocId) -> Result<(Arc<Vec<u8>>, usize), EditError> {
let (bytes, report) = self.optimized_bytes(id, &optimize::Settings::default(), &[])?;
Ok((bytes, report.merged))
+6 -3
View File
@@ -1,4 +1,7 @@
use super::*;
use super::{
Arc, DocId, EditError, Keys, OpenError, RecoverySnapshot, RenderPool, SaveOptions, Session, SignOptions, TrustStore, display_bytes,
display_config, guard, render_threads, signatures_of, writable, write_full, write_incremental, xfa_sync_datasets,
};
impl Session {
/// The bytes to write for Save: an incremental update of the file as opened/last saved,
@@ -167,7 +170,7 @@ impl Session {
{
doc.config.reading_colors = old.config.reading_colors;
doc.config.hide_comments = old.config.hide_comments;
doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), doc.config.clone());
doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), display_config(&doc.config));
}
// Caches keyed by the edit generation (edit lines, images, measurements) must see a new
// one; there is nothing new to autosave.
@@ -246,7 +249,7 @@ impl Session {
let signatures = if doc.signatures.is_empty() { Vec::new() } else { signatures_of(&editor.cos, &doc.bytes, &doc.trust, &doc.sig_cache) };
doc.display = display_bytes(Some(editor), &doc.bytes);
doc.signatures = Arc::new(signatures);
doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), doc.config.clone());
doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), display_config(&doc.config));
}
}
+6 -3
View File
@@ -1,4 +1,7 @@
use super::*;
use super::{
Arc, BookmarkPage, BookmarkRow, DocId, Document, EditError, LayerOp, OpenError, ReadingColors, RenderConfig, RenderPool, Session, SummarySort,
apply_layer_state, comment_summary, display_config, local_utc_offset, outline_rows, parse_ymd, render_threads, use_layer_choices,
};
impl Session {
pub fn new() -> Self {
@@ -243,7 +246,7 @@ impl Session {
return false;
}
doc.config.hide_comments = hide;
doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), doc.config.clone());
doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), display_config(&doc.config));
doc.generation += 1;
true
}
@@ -256,7 +259,7 @@ impl Session {
return false;
}
doc.config.reading_colors = colors;
doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), doc.config.clone());
doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), display_config(&doc.config));
doc.generation += 1;
true
}