Fixes suggested by Reviewers 2

This commit is contained in:
Combiler01
2026-10-23 10:28:47 +05:30
parent 118f1ae6ca
commit 2a9f7eb862
7 changed files with 219 additions and 195 deletions
+44 -77
View File
@@ -55,7 +55,7 @@ pub struct PageImage {
pub smask: Option<MaskIdentity>,
/// The content stream its `Do` (its first token) is in, and the `Do`'s index among the
/// operators of the page's streams joined ([`Pieces`]).
stream: usize,
pub stream: usize,
op: usize,
/// CTM before `Do`, excluding a Form's own Matrix.
draw_matrix: [f64; 6],
@@ -148,24 +148,31 @@ struct ImageState {
placement: Placement,
stack: Vec<Placement>,
path: BezPath,
subpath_start: Option<Point>,
pending_clip: Option<bool>,
}
impl ImageState {
fn new(matrix: Matrix, clip: [f64; 4], clips: Vec<ClipPath>) -> Self {
Self { placement: Placement { matrix, clip, clips }, stack: Vec::new(), path: BezPath::new(), pending_clip: None }
Self { placement: Placement { matrix, clip, clips }, stack: Vec::new(), path: BezPath::new(), subpath_start: None, pending_clip: None }
}
fn current_point(&self) -> Option<Point> {
match self.path.elements().last() {
Some(kurbo::PathEl::MoveTo(p) | kurbo::PathEl::LineTo(p)) => Some(*p),
Some(kurbo::PathEl::QuadTo(_, p2) | kurbo::PathEl::CurveTo(_, _, p2)) => Some(*p2),
Some(kurbo::PathEl::ClosePath) => self.subpath_start,
None => None,
}
}
fn has_current_point(&self) -> bool {
matches!(
self.path.elements().last(),
Some(kurbo::PathEl::MoveTo(_) | kurbo::PathEl::LineTo(_) | kurbo::PathEl::QuadTo(_, _) | kurbo::PathEl::CurveTo(_, _, _))
)
self.current_point().is_some()
}
fn point(&self, x: f64, y: f64) -> Point {
fn point(&self, x: f64, y: f64) -> Option<Point> {
let (x, y) = self.placement.matrix.apply(x, y);
if x.is_finite() && y.is_finite() { Point::new(x, y) } else { Point::ZERO }
if x.is_finite() && y.is_finite() { Some(Point::new(x, y)) } else { None }
}
fn apply(&mut self, op: &Op) -> Result<(), EditError> {
@@ -193,8 +200,11 @@ impl ImageState {
}
}
b"m" => {
if let Some([x, y]) = op.nums::<2>() {
self.path.move_to(self.point(x, y));
if let Some([x, y]) = op.nums::<2>()
&& let Some(p) = self.point(x, y)
{
self.subpath_start = Some(p);
self.path.move_to(p);
}
}
b"l" => {
@@ -203,35 +213,33 @@ impl ImageState {
// renderer, MuPDF and pdftotext do (#3581).
if let Some([x, y]) = op.nums::<2>()
&& self.has_current_point()
&& let Some(p) = self.point(x, y)
{
self.path.line_to(self.point(x, y));
self.path.line_to(p);
}
}
b"c" => {
if let Some([x1, y1, x2, y2, x3, y3]) = op.nums::<6>()
&& self.has_current_point()
&& let (Some(p1), Some(p2), Some(p3)) = (self.point(x1, y1), self.point(x2, y2), self.point(x3, y3))
{
self.path.curve_to(self.point(x1, y1), self.point(x2, y2), self.point(x3, y3));
self.path.curve_to(p1, p2, p3);
}
}
b"v" => {
if let Some([x1, y1, x2, y2]) = op.nums::<4>() {
let p1 = self.point(x1, y1);
let p2 = self.point(x2, y2);
if let Some(current) = self.path.elements().last().and_then(|e| e.end_point()) {
self.path.curve_to(current, p1, p2);
} else {
self.path.move_to(p1);
self.path.curve_to(p1, p1, p2);
}
if let Some([x1, y1, x2, y2]) = op.nums::<4>()
&& self.has_current_point()
&& let (Some(p1), Some(p2)) = (self.point(x1, y1), self.point(x2, y2))
&& let Some(current) = self.current_point()
{
self.path.curve_to(current, p1, p2);
}
}
b"y" => {
if let Some([x1, y1, x2, y2]) = op.nums::<4>()
&& self.has_current_point()
&& let (Some(p1), Some(end)) = (self.point(x1, y1), self.point(x2, y2))
{
let p1 = self.point(x1, y1);
let end = self.point(x2, y2);
self.path.curve_to(p1, end, end);
}
}
@@ -242,11 +250,15 @@ impl ImageState {
}
}
b"re" => {
if let Some([x, y, w, h]) = op.nums::<4>() {
self.path.move_to(self.point(x, y));
self.path.line_to(self.point(x + w, y));
self.path.line_to(self.point(x + w, y + h));
self.path.line_to(self.point(x, y + h));
if let Some([x, y, w, h]) = op.nums::<4>()
&& let (Some(p0), Some(p1), Some(p2), Some(p3)) =
(self.point(x, y), self.point(x + w, y), self.point(x + w, y + h), self.point(x, y + h))
{
self.subpath_start = Some(p0);
self.path.move_to(p0);
self.path.line_to(p1);
self.path.line_to(p2);
self.path.line_to(p3);
self.path.close_path();
}
}
@@ -261,6 +273,7 @@ impl ImageState {
self.placement.clips.push(ClipPath { path: Arc::new(std::mem::take(&mut self.path)), even_odd });
}
self.path = BezPath::new();
self.subpath_start = None;
}
_ => {}
}
@@ -1659,26 +1672,9 @@ fn crop_image_impl(
if x1 - x0 < 1e-6 || y1 - y0 < 1e-6 {
return Err(EditError::Invalid("the crop box has no area".into()));
}
let inv = Matrix(image.matrix).invert().ok_or_else(|| EditError::Invalid("the image has no area".into()))?;
let corners = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)].map(|(x, y)| inv.apply(x, y));
let min_u = corners.iter().map(|(u, _)| *u).fold(f64::INFINITY, f64::min).clamp(0.0, 1.0);
let max_u = corners.iter().map(|(u, _)| *u).fold(f64::NEG_INFINITY, f64::max).clamp(0.0, 1.0);
let min_v = corners.iter().map(|(_, v)| *v).fold(f64::INFINITY, f64::min).clamp(0.0, 1.0);
let max_v = corners.iter().map(|(_, v)| *v).fold(f64::NEG_INFINITY, f64::max).clamp(0.0, 1.0);
if max_u - min_u < 1e-6 || max_v - min_v < 1e-6 {
return Err(EditError::Invalid("the crop box has no area".into()));
if intersection(image.rect, [x0, y0, x1, y1]).is_none() {
return Err(EditError::Invalid("the crop box does not intersect the image".into()));
}
let (u0, v0, u1, v1) = (min_u, min_v, max_u, max_v);
if u1 - u0 < 1e-6 || v1 - v0 < 1e-6 {
return Err(EditError::Invalid("the crop box has no area".into()));
}
let m = Matrix(image.matrix);
let p00 = m.apply(u0, v0);
let p10 = m.apply(u1, v0);
let p11 = m.apply(u1, v1);
let p01 = m.apply(u0, v1);
let pieces = joined(streams);
let ops = pieces.parse().ops;
@@ -1688,7 +1684,7 @@ fn crop_image_impl(
}
let (start, end, placement) = image_transform_scope(&ops, target)?.unwrap_or((target, target, Matrix(image.draw_matrix)));
let inverse = placement.invert().ok_or_else(|| EditError::Invalid("the image has no area".into()))?;
let corners = [p00, p10, p11, p01].map(|(x, y)| inverse.apply(x, y));
let corners = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)].map(|(x, y)| inverse.apply(x, y));
if !corners.iter().all(|(x, y)| x.is_finite() && y.is_finite() && x.abs() <= 1e12 && y.abs() <= 1e12) {
return Err(EditError::Invalid("the crop box is too large for the image's placement".into()));
}
@@ -1841,32 +1837,3 @@ pub(crate) fn translate_images(doc: &mut Document, page: usize, indexes: &[usize
doc.update_dict(p.obj, |d| d.set(b"Contents".to_vec(), contents))?;
Ok(())
}
/// Post-multiplies the image's existing CTM `[a, b, c, d, e, f]` by the crop transform
/// `[u_min, v_min, u_max, v_max]` to shrink the footprint without stretching.
pub fn crop_ctm(ctm: [f64; 6], crop: [f64; 4]) -> [f64; 6] {
let [a, b, c, d, e, f] = ctm;
let [u_min, v_min, u_max, v_max] = crop;
let new_a = a * (u_max - u_min);
let new_b = b * (u_max - u_min);
let new_c = c * (v_max - v_min);
let new_d = d * (v_max - v_min);
let new_e = e + a * u_min + c * v_min;
let new_f = f + b * u_min + d * v_min;
[new_a, new_b, new_c, new_d, new_e, new_f]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_crop_ctm_standard_non_stretched() {
// Standard upright CTM: width=200, height=100 at origin (50, 100)
let ctm = [200.0, 0.0, 0.0, 100.0, 50.0, 100.0];
let crop = [0.25, 0.25, 0.75, 0.75];
let cropped = crop_ctm(ctm, crop);
let expected = [100.0, 0.0, 0.0, 50.0, 100.0, 125.0];
assert_eq!(cropped, expected);
}
}
+19 -21
View File
@@ -2785,7 +2785,10 @@ fn crop_rotated_image_preserves_angle_and_in_place_geometry() {
let bytes = page_content_bytes(&doc, 0);
let str_content = String::from_utf8_lossy(&bytes);
assert!(str_content.contains("re") && str_content.contains("W\nn"), "{str_content}");
assert!(
(str_content.contains("re") || (str_content.contains("m\n") && str_content.contains("l\n"))) && str_content.contains("W\nn"),
"{str_content}"
);
assert!(str_content.contains("/Im0 Do"), "{str_content}");
let mut doc = doc;
@@ -7156,26 +7159,6 @@ fn reading_keeps_dotted_leader_entries_whole_and_in_order() {
use proptest::prelude::*;
proptest! {
#[test]
fn prop_crop_ctm_finite_for_valid_inputs(
a in -1000.0f64..1000.0,
b in -1000.0f64..1000.0,
c in -1000.0f64..1000.0,
d in -1000.0f64..1000.0,
e in -1000.0f64..1000.0,
f in -1000.0f64..1000.0,
x1 in -1000.0f64..1000.0,
y1 in -1000.0f64..1000.0,
w in 1.0f64..1000.0,
h in 1.0f64..1000.0,
) {
let ctm = [a, b, c, d, e, f];
let crop = [x1, y1, x1 + w, y1 + h];
let res = images::crop_ctm(ctm, crop);
for v in res {
prop_assert!(v.is_finite());
}
}
#[test]
fn prop_is_entire_page_wrapped_arbitrary_bytes(bytes in proptest::collection::vec(any::<u8>(), 0..2048)) {
@@ -7304,3 +7287,18 @@ fn noop_arrange_is_not_ok_without_change() {
let after = write_incremental(&doc, &SaveOptions::default()).unwrap();
assert!(r.is_err() || before != after, "Ok with no change");
}
/// Cropping a turned image keeps the requested upright box in user space, rather than expanding
/// to the bounding box of the turned image coordinates.
#[test]
fn crop_turned_image_preserves_requested_area() {
let mut doc = regression_page("q 70.71 70.71 -70.71 70.71 300 100 cm /Im0 Do Q");
let req = [270.0, 150.0, 330.0, 200.0];
images::crop_image(&mut doc, 0, 0, req).expect("crop succeeds");
let after = &images::page_images(&reopen(&doc), 0).unwrap()[0];
let v = after.visible_rect.expect("visible rect exists");
assert!((v[0] - req[0]).abs() < 1.0, "x0: expected {}, got {}", req[0], v[0]);
assert!((v[1] - req[1]).abs() < 1.0, "y0: expected {}, got {}", req[1], v[1]);
assert!((v[2] - req[2]).abs() < 1.0, "x1: expected {}, got {}", req[2], v[2]);
assert!((v[3] - req[3]).abs() < 1.0, "y1: expected {}, got {}", req[3], v[3]);
}
+99 -78
View File
@@ -2691,18 +2691,84 @@ fn run_edit(doc: &mut pdfcraft_cos::Document, edit: &Edit, cx: &mut EditCtx) ->
ImageEdit::RemoveCrop => pdfcraft_edit::ImageChange::RemoveCrop,
};
pdfcraft_edit::change_image(doc, *page, *index, &c)?;
if let Some(target_ref) = img.object {
let added_items: Vec<(usize, Added)> = pdfcraft_edit::list_added(doc).into_iter().filter(|a| a.page == *page).enumerate().collect();
if let Some((added_idx, added_item)) =
added_items.into_iter().find(|(_, a)| matches!(&a.content, AddedContent::Image(ai) if ai.image == target_ref))
&& let AddedContent::Image(mut ai) = added_item.content
{
match change {
ImageEdit::Move(to) => {
ai.rect = *to;
pdfcraft_edit::update_content(doc, *page, added_idx, &AddedContent::Image(ai))?;
let page_obj = pdfcraft_model::pages(doc).into_iter().nth(*page);
let target_stream_obj = page_obj.as_ref().and_then(|p| match p.dict.get(b"Contents") {
None => None,
Some(c) => match &*doc.resolve(c) {
pdfcraft_cos::Object::Array(a) => a.get(img.stream).and_then(|o| o.as_ref()),
_ if img.stream == 0 => c.as_ref(),
_ => None,
},
});
let added_items: Vec<(usize, Added)> = pdfcraft_edit::list_added(doc).into_iter().filter(|a| a.page == *page).enumerate().collect();
let matched_added = if let Some(st_ref) = target_stream_obj {
added_items.iter().find(|(_, a)| a.obj == st_ref)
} else if let Some(target_ref) = img.object {
added_items.iter().find(|(_, a)| matches!(&a.content, AddedContent::Image(ai) if ai.image == target_ref))
} else {
None
};
if let Some((added_idx, added_item)) = matched_added
&& let AddedContent::Image(mut ai) = added_item.content.clone()
{
let added_idx = *added_idx;
match change {
ImageEdit::Move(to) => {
ai.rect = *to;
pdfcraft_edit::update_content(doc, *page, added_idx, &AddedContent::Image(ai))?;
}
ImageEdit::Transform(t) => {
let apply = |x: f64, y: f64| (t[0] * x + t[2] * y + t[4], t[1] * x + t[3] * y + t[5]);
let pts = [
apply(ai.rect[0], ai.rect[1]),
apply(ai.rect[2], ai.rect[1]),
apply(ai.rect[0], ai.rect[3]),
apply(ai.rect[2], ai.rect[3]),
];
let mut b = [f64::MAX, f64::MAX, f64::MIN, f64::MIN];
for (x, y) in pts {
b = [b[0].min(x), b[1].min(y), b[2].max(x), b[3].max(y)];
}
ImageEdit::Transform(t) => {
ai.rect = b;
if t[1].abs() < 1e-6 && t[2].abs() < 1e-6 {
pdfcraft_edit::update_content(doc, *page, added_idx, &AddedContent::Image(ai))?;
} else {
let _ = doc.update_dict(added_item.obj, |d| {
if let Some(pdfcraft_cos::Object::Dict(params)) = d.get_mut(b"PCAdded") {
params.set(
b"Rect".to_vec(),
pdfcraft_cos::Object::Array(ai.rect.iter().map(|x| pdfcraft_cos::Object::Real(*x)).collect()),
);
}
});
}
}
ImageEdit::Rotate(q) => {
let k = ((4 - q.rem_euclid(4)) % 4) as u8;
ai.rotation = (ai.rotation + k) % 4;
let r = ai.rect;
let (cx, cy) = ((r[0] + r[2]) / 2.0, (r[1] + r[3]) / 2.0);
if k % 2 == 1 {
let (w, h) = (r[2] - r[0], r[3] - r[1]);
ai.rect = [cx - h / 2.0, cy - w / 2.0, cx + h / 2.0, cy + w / 2.0];
}
pdfcraft_edit::update_content(doc, *page, added_idx, &AddedContent::Image(ai))?;
}
ImageEdit::RotateBy(degrees) => {
let deg = degrees.rem_euclid(360.0);
if (deg % 90.0).abs() < 1e-4 || (deg % 90.0 - 90.0).abs() < 1e-4 {
let quarters = ((deg + 45.0) / 90.0).floor() as i64;
let k = ((4 - quarters.rem_euclid(4)) % 4) as u8;
ai.rotation = (ai.rotation + k) % 4;
let r = ai.rect;
let (cx, cy) = ((r[0] + r[2]) / 2.0, (r[1] + r[3]) / 2.0);
if k % 2 == 1 {
let (w, h) = (r[2] - r[0], r[3] - r[1]);
ai.rect = [cx - h / 2.0, cy - w / 2.0, cx + h / 2.0, cy + w / 2.0];
}
pdfcraft_edit::update_content(doc, *page, added_idx, &AddedContent::Image(ai))?;
} else if let Some(t) = pdfcraft_edit::turn_by_degrees(img.visible_rect.unwrap_or(img.rect), *degrees) {
let apply = |x: f64, y: f64| (t[0] * x + t[2] * y + t[4], t[1] * x + t[3] * y + t[5]);
let pts = [
apply(ai.rect[0], ai.rect[1]),
@@ -2715,76 +2781,31 @@ fn run_edit(doc: &mut pdfcraft_cos::Document, edit: &Edit, cx: &mut EditCtx) ->
b = [b[0].min(x), b[1].min(y), b[2].max(x), b[3].max(y)];
}
ai.rect = b;
if t[1].abs() < 1e-6 && t[2].abs() < 1e-6 {
pdfcraft_edit::update_content(doc, *page, added_idx, &AddedContent::Image(ai))?;
} else {
let _ = doc.update_dict(added_item.obj, |d| {
if let Some(pdfcraft_cos::Object::Dict(params)) = d.get_mut(b"PCAdded") {
params.set(
b"Rect".to_vec(),
pdfcraft_cos::Object::Array(ai.rect.iter().map(|x| pdfcraft_cos::Object::Real(*x)).collect()),
);
}
});
}
}
ImageEdit::Rotate(q) => {
let k = ((4 - q.rem_euclid(4)) % 4) as u8;
ai.rotation = (ai.rotation + k) % 4;
let r = ai.rect;
let (cx, cy) = ((r[0] + r[2]) / 2.0, (r[1] + r[3]) / 2.0);
if k % 2 == 1 {
let (w, h) = (r[2] - r[0], r[3] - r[1]);
ai.rect = [cx - h / 2.0, cy - w / 2.0, cx + h / 2.0, cy + w / 2.0];
}
pdfcraft_edit::update_content(doc, *page, added_idx, &AddedContent::Image(ai))?;
}
ImageEdit::RotateBy(degrees) => {
let deg = degrees.rem_euclid(360.0);
if (deg % 90.0).abs() < 1e-4 || (deg % 90.0 - 90.0).abs() < 1e-4 {
let quarters = ((deg + 45.0) / 90.0).floor() as i64;
let k = ((4 - quarters.rem_euclid(4)) % 4) as u8;
ai.rotation = (ai.rotation + k) % 4;
let r = ai.rect;
let (cx, cy) = ((r[0] + r[2]) / 2.0, (r[1] + r[3]) / 2.0);
if k % 2 == 1 {
let (w, h) = (r[2] - r[0], r[3] - r[1]);
ai.rect = [cx - h / 2.0, cy - w / 2.0, cx + h / 2.0, cy + w / 2.0];
let _ = doc.update_dict(added_item.obj, |d| {
if let Some(pdfcraft_cos::Object::Dict(params)) = d.get_mut(b"PCAdded") {
params.set(
b"Rect".to_vec(),
pdfcraft_cos::Object::Array(ai.rect.iter().map(|x| pdfcraft_cos::Object::Real(*x)).collect()),
);
}
pdfcraft_edit::update_content(doc, *page, added_idx, &AddedContent::Image(ai))?;
} else if let Some(t) = pdfcraft_edit::turn_by_degrees(img.visible_rect.unwrap_or(img.rect), *degrees) {
let apply = |x: f64, y: f64| (t[0] * x + t[2] * y + t[4], t[1] * x + t[3] * y + t[5]);
let pts = [
apply(ai.rect[0], ai.rect[1]),
apply(ai.rect[2], ai.rect[1]),
apply(ai.rect[0], ai.rect[3]),
apply(ai.rect[2], ai.rect[3]),
];
let mut b = [f64::MAX, f64::MAX, f64::MIN, f64::MIN];
for (x, y) in pts {
b = [b[0].min(x), b[1].min(y), b[2].max(x), b[3].max(y)];
}
ai.rect = b;
let _ = doc.update_dict(added_item.obj, |d| {
if let Some(pdfcraft_cos::Object::Dict(params)) = d.get_mut(b"PCAdded") {
params.set(
b"Rect".to_vec(),
pdfcraft_cos::Object::Array(ai.rect.iter().map(|x| pdfcraft_cos::Object::Real(*x)).collect()),
);
}
});
}
});
}
ImageEdit::Flip { horizontal: true } => {
ai.flip_h = !ai.flip_h;
pdfcraft_edit::update_content(doc, *page, added_idx, &AddedContent::Image(ai))?;
}
ImageEdit::Flip { horizontal: false } => {
ai.flip_v = !ai.flip_v;
pdfcraft_edit::update_content(doc, *page, added_idx, &AddedContent::Image(ai))?;
}
_ => {}
}
ImageEdit::Flip { horizontal: true } => {
ai.flip_h = !ai.flip_h;
pdfcraft_edit::update_content(doc, *page, added_idx, &AddedContent::Image(ai))?;
}
ImageEdit::Flip { horizontal: false } => {
ai.flip_v = !ai.flip_v;
pdfcraft_edit::update_content(doc, *page, added_idx, &AddedContent::Image(ai))?;
}
ImageEdit::Arrange(_) | ImageEdit::Delete => {
let _ = doc.update_dict(added_item.obj, |d| {
d.remove(b"PCAdded");
d.remove(b"PCMark");
});
}
_ => {}
}
}
}
+20
View File
@@ -7304,3 +7304,23 @@ fn added_image_move_then_rotate_by_keeps_position() {
let (a, b) = (c(moved), c(rotated));
assert!((a.0 - b.0).abs() < 1.0 && (a.1 - b.1).abs() < 1.0, "rotate_by moved the centre from {a:?} to {b:?}");
}
/// Arranging an added image moves it into the document stream and converts it to an ordinary
/// page image (dropping the PCAdded marker), so no empty shell remains in list_added.
#[test]
fn added_image_arrange_converts_to_ordinary_image() {
let mut png = Vec::new();
{
let mut enc = png::Encoder::new(&mut png, 2, 2);
enc.set_color(png::ColorType::Rgb);
enc.write_header().unwrap().write_image_data(&[255u8; 12]).unwrap();
}
let (mut s, id) = session_with(1);
s.apply(id, Edit::AddImage { page: 0, rect: Some([20.0, 20.0, 60.0, 60.0]), name: "p1.png".into(), bytes: Arc::new(png.clone()) }).unwrap();
s.apply(id, Edit::AddImage { page: 0, rect: Some([100.0, 100.0, 200.0, 200.0]), name: "p2.png".into(), bytes: Arc::new(png) }).unwrap();
assert_eq!(s.get(id).unwrap().page_images(0).len(), 2);
assert_eq!(s.get(id).unwrap().added().len(), 2);
s.apply(id, Edit::EditPageImage { page: 0, index: 1, change: ImageEdit::Arrange(ArrangeOrder::SendToBack) }).unwrap();
assert_eq!(s.get(id).unwrap().page_images(0).len(), 2);
assert_eq!(s.get(id).unwrap().added().len(), 1, "arranged image is converted and no longer an added-item shell");
}
+7 -5
View File
@@ -54,13 +54,11 @@ pub fn discover(roots: &[PathBuf]) -> Vec<FontFile> {
if meta.is_dir() && depth < 4 {
if let Ok(children) = std::fs::read_dir(&path) {
let remaining = 4096usize.saturating_sub(entries.saturating_add(pending.len()));
pending.extend(children.take(remaining).filter_map(Result::ok).map(|p| (p.path(), depth + 1)));
let mut sorted_children: Vec<_> = children.take(remaining).filter_map(Result::ok).map(|p| p.path()).collect();
sorted_children.sort();
pending.extend(sorted_children.into_iter().rev().map(|p| (p, depth + 1)));
}
} else if meta.is_file() && meta.len() <= (16 << 20) && font_extension(&path) {
if meta.len() > budget {
continue;
}
budget = budget.saturating_sub(meta.len());
let mut bytes = Vec::new();
let read = std::fs::File::open(&path).and_then(|f| f.take((16 << 20) + 1).read_to_end(&mut bytes));
if read.is_err() {
@@ -69,6 +67,10 @@ pub fn discover(roots: &[PathBuf]) -> Vec<FontFile> {
if pdfcraft_fonts::embedding::validate_document_input(&bytes).is_err() {
continue;
}
if meta.len() > budget {
continue;
}
budget = budget.saturating_sub(meta.len());
if let Ok((family, face)) = pdfcraft_fonts::embedding::names(&bytes) {
let key = font_content_key(&bytes);
if seen_face_content.insert((face.clone(), key)) {
+23 -11
View File
@@ -888,15 +888,19 @@ impl crate::PdfCraftApp {
Some(crate::create_ui::Clip::Text(_)) => None,
None => {
#[cfg(not(target_arch = "wasm32"))]
let sys_result = if self.system_clipboard { crate::os_clipboard::with(|cb| cb.get_image()).ok() } else { None };
let sys_result = if self.system_clipboard {
crate::os_clipboard::with(|cb| cb.get_image()).ok().map(|img| (img.width, img.height, img.bytes.into_owned()))
} else {
None
};
#[cfg(target_arch = "wasm32")]
let sys_result: Option<arboard::ImageData<'static>> = None;
let sys_result: Option<(usize, usize, Vec<u8>)> = None;
if let Some(img) = sys_result {
if let Some((img_width, img_height, img_bytes)) = sys_result {
let is_same_as_last = self
.last_copied_image
.as_ref()
.is_some_and(|(w, h, rgba)| *w as usize == img.width && *h as usize == img.height && rgba.as_slice() == img.bytes.as_ref());
.is_some_and(|(w, h, rgba)| *w as usize == img_width && *h as usize == img_height && rgba.as_slice() == img_bytes.as_slice());
if is_same_as_last && let Some((name, bytes)) = self.last_copied_file.clone() {
Some((name, bytes))
} else if is_same_as_last && let Some((w, h, rgba)) = self.last_copied_image.clone() {
@@ -904,9 +908,9 @@ impl crate::PdfCraftApp {
} else {
// System clipboard has a newer / external image!
self.last_copied_file = None;
self.last_copied_image = Some((img.width as u32, img.height as u32, img.bytes.to_vec()));
self.last_copied_image = Some((img_width as u32, img_height as u32, img_bytes.clone()));
self.last_copied_image_rect = None;
crate::create_ui::png_from_rgba(img.width, img.height, &img.bytes).ok().map(|b| ("clipboard_image.png".to_string(), b))
crate::create_ui::png_from_rgba(img_width, img_height, &img_bytes).ok().map(|b| ("clipboard_image.png".to_string(), b))
}
} else if let Some((name, bytes)) = self.last_copied_file.clone() {
Some((name, bytes))
@@ -947,8 +951,12 @@ impl crate::PdfCraftApp {
if let Some(r) = placed_rect {
self.last_copied_image_rect = Some(r);
}
let added_count = self.session.get(id).map_or(0, |d| d.added().iter().filter(|a| a.page == page).count());
let added_idx = added_count.saturating_sub(1);
if let Some(v) = self.views.get_mut(i) {
v.current = page;
v.content.selected = Some((page, added_idx));
v.content.select_added = Some((page, added_idx));
v.image_selection = Some(crate::edit_text_ui::ImageSelection {
page,
index: before,
@@ -982,19 +990,23 @@ impl crate::PdfCraftApp {
}
} else {
#[cfg(not(target_arch = "wasm32"))]
let sys_img = if self.system_clipboard { crate::os_clipboard::with(|cb| cb.get_image()).ok() } else { None };
let sys_img = if self.system_clipboard {
crate::os_clipboard::with(|cb| cb.get_image()).ok().map(|img| (img.width, img.height, img.bytes.into_owned()))
} else {
None
};
#[cfg(target_arch = "wasm32")]
let sys_img: Option<arboard::ImageData<'static>> = None;
let sys_img: Option<(usize, usize, Vec<u8>)> = None;
if let Some(img) = sys_img {
if let Some((img_width, img_height, img_bytes)) = sys_img {
let is_same = self
.last_copied_image
.as_ref()
.is_some_and(|(w, h, bytes)| *w as usize == img.width && *h as usize == img.height && bytes.as_slice() == img.bytes.as_ref());
.is_some_and(|(w, h, bytes)| *w as usize == img_width && *h as usize == img_height && bytes.as_slice() == img_bytes.as_slice());
if is_same && let Some(src_r) = self.last_copied_image_rect {
(src_r[2] - src_r[0], src_r[3] - src_r[1])
} else {
(img.width as f64, img.height as f64)
(img_width as f64, img_height as f64)
}
} else if let Some(src_r) = self.last_copied_image_rect {
(src_r[2] - src_r[0], src_r[3] - src_r[1])
+7 -3
View File
@@ -3175,13 +3175,17 @@ impl PdfCraftApp {
self.cut_page_image(sel.page, sel.index);
}
if paste {
let has_override_image = matches!(self.clipboard_override, Some(crate::create_ui::Clip::Image { .. }));
#[cfg(not(target_arch = "wasm32"))]
let system_has_text =
if self.system_clipboard { crate::os_clipboard::with(|cb| cb.get_text()).map(|t| !t.is_empty()).unwrap_or(false) } else { false };
let system_has_text = if !has_override_image && self.system_clipboard {
crate::os_clipboard::with(|cb| cb.get_text()).map(|t| !t.is_empty()).unwrap_or(false)
} else {
false
};
#[cfg(target_arch = "wasm32")]
let system_has_text = false;
if paste_event_text.is_some() || system_has_text {
if !has_override_image && (paste_event_text.is_some() || system_has_text) {
self.last_copied_image = None;
self.last_copied_file = None;
self.last_copied_image_rect = None;