mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-10 13:01:29 +00:00
ATTRIBUTION.toml: sha256 lines taken from the base side; recomputed after all merges.
This commit is contained in:
commit
29ad20342c
31 files changed
+258
-7
No files matched your search
@@ -1208,6 +1208,7 @@ impl Automation {
|
||||
if let Some(s) = a.opt_int("start_number")? {
|
||||
hf.start_number = s.clamp(1, u32::MAX as i64) as u32;
|
||||
}
|
||||
hf.shrink = a.opt_bool("shrink")?.unwrap_or(false);
|
||||
Edit::AddHeaderFooter { pages, settings: hf, replace }
|
||||
}
|
||||
"doc_watermark" => {
|
||||
|
||||
@@ -875,7 +875,7 @@ pub fn tools() -> Vec<ToolDef> {
|
||||
t(
|
||||
"doc_header_footer",
|
||||
"Add header and footer",
|
||||
"Add a header and/or footer to pages. Text boxes: header_left/center/right, footer_left/center/right. Tokens: <<1>> page number, <<n>> page count, <<1 of n>>, <<Page 1 of n>>, <<1/n>>, dates <<m/d/yyyy>> <<yyyy-mm-dd>> <<mmmm d, yyyy>> (and more), Bates <<Bates Number#6#1#PREFIX#SUFFIX>>. replace: true swaps out existing ones (Update). Undoable.",
|
||||
"Add a header and/or footer to pages. Text boxes: header_left/center/right, footer_left/center/right. Tokens: <<1>> page number, <<n>> page count, <<1 of n>>, <<Page 1 of n>>, <<1/n>>, dates <<m/d/yyyy>> <<yyyy-mm-dd>> <<mmmm d, yyyy>> (and more), Bates <<Bates Number#6#1#PREFIX#SUFFIX>>. shrink: true scales the page content down evenly into the room the header and footer leave free, so they don't cover it. replace: true swaps out existing ones (Update). Undoable.",
|
||||
)
|
||||
.cmd("edit.header_footer")
|
||||
.with(schema(
|
||||
@@ -888,6 +888,7 @@ pub fn tools() -> Vec<ToolDef> {
|
||||
"color": { "type": "string", "description": "#RRGGBB or a colour name." },
|
||||
"margins": { "type": "array", "items": { "type": "number", "minimum": 0 }, "minItems": 4, "maxItems": 4, "description": "Top, bottom, left, right in points (default 36, 36, 72, 72)." },
|
||||
"start_number": { "type": "integer", "minimum": 1 },
|
||||
"shrink": { "type": "boolean", "description": "Shrink the page content so the header and footer don't cover it (default false)." },
|
||||
"replace": { "type": "boolean" },
|
||||
}),
|
||||
&["doc"],
|
||||
|
||||
@@ -2241,6 +2241,43 @@ fn headers_watermarks_and_backgrounds_through_tools() {
|
||||
ok(&mut a, "doc_save", json!({ "doc": doc, "path": "marked.pdf" }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_shrinking_header_and_footer_moves_the_page_content_out_of_their_way_through_tools() {
|
||||
let dir = workdir("hf-shrink");
|
||||
let mut a = auto(&dir);
|
||||
let doc = ok(&mut a, "doc_open", json!({ "path": "a.pdf" }))["doc"].as_u64().unwrap();
|
||||
// The box of "Page 1" (left, top, width, height in points), read back from a saved copy.
|
||||
let mut saved = 0;
|
||||
let mut text_box = |a: &mut Automation| -> [f64; 4] {
|
||||
saved += 1;
|
||||
let path = format!("shrunk{saved}.pdf");
|
||||
ok(a, "doc_save", json!({ "doc": doc, "path": path }));
|
||||
let copy = ok(a, "doc_open", json!({ "path": path }))["doc"].as_u64().unwrap();
|
||||
let found = ok(a, "text_find", json!({ "doc": copy, "query": "Page 1" }));
|
||||
let r = found["matches"][0]["rects"][0].as_array().unwrap_or_else(|| panic!("Page 1 is on the page: {found}"));
|
||||
let v: Vec<f64> = r.iter().map(|x| x.as_f64().unwrap()).collect();
|
||||
[v[0], v[1], v[2] - v[0], v[3] - v[1]]
|
||||
};
|
||||
let before = text_box(&mut a);
|
||||
ok(
|
||||
&mut a,
|
||||
"doc_header_footer",
|
||||
json!({ "doc": doc, "pages": [1], "header_center": "ACME", "footer_right": "<<Bates Number#6#1##>>", "shrink": true }),
|
||||
);
|
||||
let after = text_box(&mut a);
|
||||
// 200 × 300 page, 8 pt text and 36 pt margins: the header takes 48 pt, the footer 46.4 pt.
|
||||
let k = (300.0 - 48.0 - 46.4) / 300.0;
|
||||
assert!((after[2] / before[2] - k).abs() < 0.02 && (after[3] / before[3] - k).abs() < 0.02, "scaled by {k}: {before:?} → {after:?}");
|
||||
assert!((after[0] - (20.0 * k + (200.0 - 200.0 * k) / 2.0)).abs() < 1.0, "centred across: {before:?} → {after:?}");
|
||||
assert!(page_text(&mut a, doc)[0].contains("000001"), "the Bates number is drawn");
|
||||
// Margins that leave no room are refused; Remove brings the content back to full size.
|
||||
let full = json!({ "doc": doc, "header_center": "X", "margins": [295, 36, 72, 72], "shrink": true, "replace": true });
|
||||
assert!(matches!(a.call("doc_header_footer", &full), Err(ToolError::Failed(_))), "no room");
|
||||
ok(&mut a, "doc_remove_marks", json!({ "doc": doc, "kind": "header_footer" }));
|
||||
let restored = text_box(&mut a);
|
||||
assert!(restored.iter().zip(before).all(|(r, b)| (r - b).abs() < 0.01), "{before:?} → {restored:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn watermarks_move_from_the_page_centre_through_tools() {
|
||||
let dir = workdir("wm-offset");
|
||||
|
||||
+71
-1
@@ -65,12 +65,24 @@ pub struct HeaderFooter {
|
||||
pub margins: [f64; 4],
|
||||
/// The number `<<1>>` shows on the first page of the range.
|
||||
pub start_number: u32,
|
||||
/// Shrink the page content so the header and footer don't cover it (Acrobat's Appearance
|
||||
/// Options): the original content is scaled down evenly into the band the header and footer
|
||||
/// leave free, centred across the page. Remove and Update take the scaling away again.
|
||||
pub shrink: bool,
|
||||
}
|
||||
|
||||
impl Default for HeaderFooter {
|
||||
/// Acrobat's defaults: 8 pt black, margins 0.5 in top and bottom, 1 in left and right.
|
||||
fn default() -> Self {
|
||||
Self { text: Default::default(), font_size: 8.0, color: [0.0; 3], underline: false, margins: [36.0, 36.0, 72.0, 72.0], start_number: 1 }
|
||||
Self {
|
||||
text: Default::default(),
|
||||
font_size: 8.0,
|
||||
color: [0.0; 3],
|
||||
underline: false,
|
||||
margins: [36.0, 36.0, 72.0, 72.0],
|
||||
start_number: 1,
|
||||
shrink: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +95,42 @@ impl HeaderFooter {
|
||||
pub fn first_undrawable(&self) -> Option<char> {
|
||||
self.text.iter().flat_map(|t| t.lines()).find_map(pdfcraft_fonts::first_non_win_ansi)
|
||||
}
|
||||
|
||||
/// The room the header and the footer take at the top and the bottom of a page, in points:
|
||||
/// the margin, the lines of the tallest box, and a clearance of half the font size. A side
|
||||
/// without text takes none. This is the room [`HeaderFooter::shrink`] keeps free.
|
||||
pub fn bands(&self) -> (f64, f64) {
|
||||
let size = self.font_size;
|
||||
let line_h = size * 1.2;
|
||||
let lines = |boxes: &[String]| boxes.iter().filter(|t| !t.trim().is_empty()).map(|t| t.lines().count().max(1)).max();
|
||||
let (header, footer) = self.text.split_at(3);
|
||||
// Header lines hang below the top margin (a full line each); the footer's last line sits
|
||||
// on the bottom margin, so only its capitals (0.8 of the size) rise above it.
|
||||
let top = lines(header).map_or(0.0, |n| self.margins[0] + size + (n - 1) as f64 * line_h + size / 2.0);
|
||||
let bottom = lines(footer).map_or(0.0, |n| self.margins[1] + size * 0.8 + (n - 1) as f64 * line_h + size / 2.0);
|
||||
(top, bottom)
|
||||
}
|
||||
}
|
||||
|
||||
/// The `cm` that shrinks a page's original content (user space) into the band `hf` leaves free,
|
||||
/// for a page shown `size` (display space) with view matrix `view` (display to user space): an
|
||||
/// even scale, centred across, the content's bottom on the footer's band.
|
||||
fn shrink_matrix(hf: &HeaderFooter, size: (f64, f64), view: [f64; 6]) -> Result<[f64; 6], EditError> {
|
||||
let (w, h) = size;
|
||||
let (top, bottom) = hf.bands();
|
||||
let free = h - top - bottom;
|
||||
if !(free.is_finite() && free >= 1.0 && w.is_finite() && w > 0.0) {
|
||||
return Err(EditError::Invalid("the header and footer margins leave no room for the page content to shrink into".into()));
|
||||
}
|
||||
let k = free / h;
|
||||
let v = kurbo::Affine::new(view);
|
||||
let det = v.determinant();
|
||||
if !(det.is_finite() && det.abs() > 1e-12) {
|
||||
return Err(EditError::Invalid("the page can't be shrunk: its view matrix is degenerate".into()));
|
||||
}
|
||||
let shrink = kurbo::Affine::new([k, 0.0, 0.0, k, (w - w * k) / 2.0, bottom]);
|
||||
// User space to display space, shrink there, and back.
|
||||
Ok((v * shrink * v.inverse()).as_coeffs())
|
||||
}
|
||||
|
||||
/// A picture for a background or watermark (Acrobat: Source ▸ File): an XObject already in
|
||||
@@ -552,6 +600,18 @@ fn place_tagged(doc: &mut Document, page: &pdfcraft_model::Page, tag: &str, cont
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Put content tagged `tag` first inside the q/Q wrapper, so it applies to the page's original
|
||||
/// content and nothing else (a header's shrink). A page without a wrapper has no original content
|
||||
/// on it and is left alone.
|
||||
fn place_inside_wrapper(doc: &mut Document, page: &pdfcraft_model::Page, tag: &str, content: Vec<u8>) -> Result<(), EditError> {
|
||||
let mut list = contents(doc, page)?;
|
||||
let Some(open) = list.iter().position(|o| stream_tag(doc, o).as_deref() == Some("Wrap")) else { return Ok(()) };
|
||||
let stream = Object::Ref(doc.add(Object::Stream(Stream::flate(tagged(tag), &content))));
|
||||
list.insert(open + 1, stream);
|
||||
doc.update_dict(page.obj, |d| d.set(b"Contents".to_vec(), Object::Array(list)))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Draw `content` on top of a page (0-based) as permanent page content, with standard Helvetica
|
||||
/// available as `/PCHelv`. The stream is tagged `tag` (not a mark kind, so Remove never takes
|
||||
/// it away); the original content is wrapped in q/Q first. Used for applied redaction boxes.
|
||||
@@ -632,6 +692,11 @@ pub fn add_header_footer(doc: &mut Document, pages: &[usize], hf: &HeaderFooter,
|
||||
u64::MAX
|
||||
)));
|
||||
}
|
||||
// Shrinking needs room on every page: refuse before anything is drawn or replaced.
|
||||
let shrinks = pages
|
||||
.iter()
|
||||
.map(|&i| hf.shrink.then(|| shrink_matrix(hf, all[i].display_size(doc), all[i].view_matrix(doc))).transpose())
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
if replace {
|
||||
remove_marks(doc, pages, MarkKind::HeaderFooter)?;
|
||||
}
|
||||
@@ -679,6 +744,11 @@ pub fn add_header_footer(doc: &mut Document, pages: &[usize], hf: &HeaderFooter,
|
||||
add_resources(doc, page, None, None)?;
|
||||
let page = &page_list(doc)[i];
|
||||
place(doc, page, MarkKind::HeaderFooter, content, false)?;
|
||||
if let Some(Some(m)) = shrinks.get(k) {
|
||||
let cm = format!("{} cm\n", m.map(matrix_number).join(" "));
|
||||
let page = &page_list(doc)[i];
|
||||
place_inside_wrapper(doc, page, MarkKind::HeaderFooter.tag(), cm.into_bytes())?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -156,6 +156,100 @@ fn replace_and_remove_restore_the_original_content() {
|
||||
assert_eq!(streams(&reopen(&doc), 0), original, "back to exactly the original content");
|
||||
}
|
||||
|
||||
/// The six numbers of the first `… cm` in `content`.
|
||||
fn first_cm(content: &str) -> [f64; 6] {
|
||||
let line = content.lines().find(|l| l.ends_with(" cm")).unwrap_or_else(|| panic!("no cm in {content:?}"));
|
||||
let v: Vec<f64> = line.split_whitespace().take(6).map(|x| x.parse().unwrap()).collect();
|
||||
<[f64; 6]>::try_from(v).unwrap()
|
||||
}
|
||||
|
||||
fn map_point(m: [f64; 6], x: f64, y: f64) -> (f64, f64) {
|
||||
(m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5])
|
||||
}
|
||||
|
||||
fn near(a: (f64, f64), b: (f64, f64)) -> bool {
|
||||
(a.0 - b.0).abs() < 0.01 && (a.1 - b.1).abs() < 0.01
|
||||
}
|
||||
|
||||
fn shrinking(header: &str, footer: &str) -> HeaderFooter {
|
||||
let mut hf = HeaderFooter { shrink: true, ..HeaderFooter::default() };
|
||||
hf.text[1] = header.into();
|
||||
hf.text[5] = footer.into();
|
||||
hf
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shrink_scales_the_original_content_into_the_band_between_header_and_footer() {
|
||||
let mut doc = fixture();
|
||||
// 8 pt text, margins 36 top and bottom: the header takes 36 + 8 (+ 4 clearance) at the top,
|
||||
// the footer 36 + 6.4 (its capitals above the baseline, + 4) at the bottom.
|
||||
add_header_footer(&mut doc, &[0], &shrinking("ACME", "<<Bates Number#6#1##>>"), false, &cx()).unwrap();
|
||||
let doc = reopen(&doc);
|
||||
let s = streams(&doc, 0);
|
||||
// q-wrapper, shrink, original, Q-wrapper, header/footer.
|
||||
assert_eq!(s.len(), 5, "{s:?}");
|
||||
assert_eq!((s[0].as_str(), s[3].as_str()), ("q %PdfCraft\n", "\nQ %PdfCraft\n"));
|
||||
assert!(s[2].contains("(Hi) Tj"), "the original content follows the shrink: {s:?}");
|
||||
let m = first_cm(&s[1]);
|
||||
let k = (800.0 - 48.0 - 46.4) / 800.0;
|
||||
assert!((m[0] - k).abs() < 1e-3 && (m[3] - k).abs() < 1e-3 && m[1] == 0.0 && m[2] == 0.0, "uniform scale {k}: {m:?}");
|
||||
// The page's corners land on the band, centred across.
|
||||
assert!(near(map_point(m, 0.0, 0.0), ((600.0 - 600.0 * k) / 2.0, 46.4)), "{m:?}");
|
||||
assert!(near(map_point(m, 600.0, 800.0), ((600.0 + 600.0 * k) / 2.0, 800.0 - 48.0)), "{m:?}");
|
||||
assert!(s[4].contains("(000001) Tj"), "{}", s[4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shrink_follows_the_display_space_of_a_rotated_page() {
|
||||
let mut doc = fixture();
|
||||
// Page 2 is rotated 90°: shown 800 wide and 600 high. A footer alone keeps the top.
|
||||
add_header_footer(&mut doc, &[1], &shrinking("", "<<1>>"), false, &cx()).unwrap();
|
||||
let s = streams(&doc, 1);
|
||||
let m = first_cm(&s[1]);
|
||||
let page = &pdfcraft_model::pages(&doc)[1];
|
||||
let v = page.view_matrix(&doc);
|
||||
let k = (600.0 - 46.4) / 600.0;
|
||||
// Display (x, y) is user v·(x, y): the shrunk content's display corners.
|
||||
let to_user = |x: f64, y: f64| map_point(v, x, y);
|
||||
assert!(near(map_point(m, to_user(0.0, 0.0).0, to_user(0.0, 0.0).1), to_user((800.0 - 800.0 * k) / 2.0, 46.4)), "{m:?}");
|
||||
assert!(near(map_point(m, to_user(800.0, 600.0).0, to_user(800.0, 600.0).1), to_user((800.0 + 800.0 * k) / 2.0, 600.0)), "{m:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shrink_is_undone_by_remove_and_not_doubled_by_update() {
|
||||
let mut doc = fixture();
|
||||
let original = streams(&doc, 0);
|
||||
add_background(&mut doc, &[0], &Background::default(), false).unwrap();
|
||||
add_header_footer(&mut doc, &[0], &shrinking("First", ""), false, &cx()).unwrap();
|
||||
add_header_footer(&mut doc, &[0], &shrinking("Second", ""), true, &cx()).unwrap();
|
||||
let s = streams(&doc, 0);
|
||||
assert_eq!(s.iter().filter(|x| x.contains(" cm") && !x.contains("/PCMark")).count(), 2, "one shrink besides the original's own cm: {s:?}");
|
||||
assert!(s[0].contains("/PCMark /Background"), "the background stays behind, full size: {s:?}");
|
||||
// Update without shrinking: back to full size.
|
||||
let mut plain = shrinking("Third", "");
|
||||
plain.shrink = false;
|
||||
add_header_footer(&mut doc, &[0], &plain, true, &cx()).unwrap();
|
||||
assert_eq!(streams(&doc, 0).len(), 5, "background, wrapper, original, wrapper, header: {:?}", streams(&doc, 0));
|
||||
add_header_footer(&mut doc, &[0], &shrinking("Fourth", ""), true, &cx()).unwrap();
|
||||
assert_eq!(remove_marks(&mut doc, &[0], MarkKind::HeaderFooter).unwrap(), 2, "the header and its shrink");
|
||||
assert_eq!(remove_marks(&mut doc, &[0], MarkKind::Background).unwrap(), 1);
|
||||
assert_eq!(streams(&reopen(&doc), 0), original, "back to exactly the original content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shrink_with_no_room_left_is_refused_without_drawing() {
|
||||
let mut doc = fixture();
|
||||
let mut hf = shrinking("Top", "Bottom");
|
||||
hf.margins = [400.0, 400.0, 72.0, 72.0];
|
||||
let err = add_header_footer(&mut doc, &[0, 1], &hf, false, &cx()).unwrap_err();
|
||||
assert!(err.to_string().contains("no room"), "{err}");
|
||||
assert!(!doc.is_modified());
|
||||
// A page without content has nothing to shrink: it just gets the header.
|
||||
let mut doc = fixture();
|
||||
add_header_footer(&mut doc, &[2], &shrinking("Top", ""), false, &cx()).unwrap();
|
||||
assert_eq!(streams(&doc, 2).len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn watermarks_rotate_fade_and_can_go_behind() {
|
||||
let mut doc = fixture();
|
||||
|
||||
@@ -841,6 +841,7 @@
|
||||
Insert Page Number إدراج رقم الصفحة
|
||||
Insert Date إدراج التاريخ
|
||||
Start page number رقم صفحة البداية
|
||||
Shrink the page content to make room for the header and footer تصغير محتوى الصفحة لإفساح المجال للرأس والتذييل
|
||||
Source المصدر
|
||||
Fit to page ملاءمة الصفحة
|
||||
Rotation التدوير
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -514,6 +514,7 @@
|
||||
Insert Page Number Вмъкни номер на страница
|
||||
Insert Date Вмъкни дата
|
||||
Start page number Начален номер на страница
|
||||
Shrink the page content to make room for the header and footer Смаляване на съдържанието на страницата, за да има място за горния и долния колонтитул
|
||||
Source Източник
|
||||
Fit to page Побери в страницата
|
||||
Rotation Завъртане
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 8 and column 16.
|
@@ -916,6 +916,7 @@ key Space Mezerník
|
||||
Insert Page Number Vložit číslo stránky
|
||||
Insert Date Vložit datum
|
||||
Start page number Počáteční číslo stránky
|
||||
Shrink the page content to make room for the header and footer Zmenšit obsah stránky, aby se vešlo záhlaví a zápatí
|
||||
Source Zdroj
|
||||
Fit to page Přizpůsobit stránce
|
||||
Rotation Otočení
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -819,6 +819,7 @@
|
||||
Insert Page Number Seitenzahl einfügen
|
||||
Insert Date Datum einfügen
|
||||
Start page number Erste Seitenzahl
|
||||
Shrink the page content to make room for the header and footer Seiteninhalt verkleinern, damit Kopf- und Fußzeile Platz haben
|
||||
Source Quelle
|
||||
Fit to page An Seite anpassen
|
||||
Rotation Drehung
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 51.
|
@@ -424,6 +424,7 @@
|
||||
Insert Page Number Εισαγωγή αριθμού σελίδας
|
||||
Insert Date Εισαγωγή ημερομηνίας
|
||||
Start page number Αρχικός αριθμός σελίδας
|
||||
Shrink the page content to make room for the header and footer Σμίκρυνση του περιεχομένου της σελίδας ώστε να χωρούν η κεφαλίδα και το υποσέλιδο
|
||||
Source Πηγή
|
||||
Appear behind page Εμφάνιση πίσω από τη σελίδα
|
||||
Appear on top of page Εμφάνιση πάνω από τη σελίδα
|
||||
|
||||
|
@@ -835,6 +835,7 @@
|
||||
Insert Page Number Insertar número de página
|
||||
Insert Date Insertar fecha
|
||||
Start page number Número de página inicial
|
||||
Shrink the page content to make room for the header and footer Reducir el contenido de la página para dejar espacio al encabezado y al pie de página
|
||||
Source Origen
|
||||
Fit to page Ajustar a la página
|
||||
Rotation Rotación
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 60.
|
@@ -815,6 +815,7 @@
|
||||
Insert Page Number Insérer le numéro de page
|
||||
Insert Date Insérer la date
|
||||
Start page number Numéro de page de départ
|
||||
Shrink the page content to make room for the header and footer Réduire le contenu de la page pour laisser de la place à l'en-tête et au pied de page
|
||||
Source Source
|
||||
Fit to page Ajuster à la page
|
||||
Rotation Rotation
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 39.
|
@@ -812,6 +812,7 @@
|
||||
Insert Page Number Oldalszám beszúrása
|
||||
Insert Date Dátum beszúrása
|
||||
Start page number Kezdő oldalszám
|
||||
Shrink the page content to make room for the header and footer Az oldal tartalmának kicsinyítése, hogy elférjen az élőfej és az élőláb
|
||||
Source Forrás
|
||||
Fit to page Oldalhoz igazítás
|
||||
Rotation Elforgatás
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 44.
|
@@ -838,6 +838,7 @@
|
||||
Insert Page Number Inserisci numero di pagina
|
||||
Insert Date Inserisci data
|
||||
Start page number Numero di pagina iniziale
|
||||
Shrink the page content to make room for the header and footer Riduci il contenuto della pagina per fare spazio a intestazione e piè di pagina
|
||||
Source Origine
|
||||
Fit to page Adatta alla pagina
|
||||
Rotation Rotazione
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -842,6 +842,7 @@
|
||||
Insert Page Number ページ番号を挿入
|
||||
Insert Date 日付を挿入
|
||||
Start page number 開始ページ番号
|
||||
Shrink the page content to make room for the header and footer ヘッダーとフッターの領域を空けるためにページの内容を縮小
|
||||
Source ソース
|
||||
Fit to page ページに合わせる
|
||||
Rotation 回転
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -798,6 +798,7 @@
|
||||
Insert Page Number Бет нөмірін енгізу
|
||||
Insert Date Күнді енгізу
|
||||
Start page number Бастапқы бет нөмірі
|
||||
Shrink the page content to make room for the header and footer Жоғарғы және төменгі колонтитулға орын босату үшін бет мазмұнын кішірейту
|
||||
Source Дереккөз
|
||||
Fit to page Бетке сыйдыру
|
||||
Rotation Бұрылу
|
||||
|
||||
|
@@ -1145,6 +1145,7 @@
|
||||
Start Pradėti
|
||||
Start Checking Pradėti tikrinimą
|
||||
Start page number Pradinio puslapio numeris
|
||||
Shrink the page content to make room for the header and footer Sumažinti puslapio turinį, kad tilptų antraštė ir poraštė
|
||||
Status Būsena
|
||||
Steps Veiksmai
|
||||
Strength: {s} Stiprumas: {s}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 404 and column 9.
|
@@ -834,6 +834,7 @@
|
||||
Insert Page Number Wstaw numer strony
|
||||
Insert Date Wstaw datę
|
||||
Start page number Początkowy numer strony
|
||||
Shrink the page content to make room for the header and footer Zmniejsz zawartość strony, aby zrobić miejsce na nagłówek i stopkę
|
||||
Source Źródło
|
||||
Fit to page Dopasuj do strony
|
||||
Rotation Obrót
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -469,6 +469,7 @@
|
||||
Insert Page Number Inserir número de página
|
||||
Insert Date Inserir data
|
||||
Start page number Número da página inicial
|
||||
Shrink the page content to make room for the header and footer Reduzir o conteúdo da página para dar espaço ao cabeçalho e ao rodapé
|
||||
Source Origem
|
||||
Fit to page Ajustar à página
|
||||
Rotation Rotação
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -835,6 +835,7 @@
|
||||
Insert Page Number Вставить номер страницы
|
||||
Insert Date Вставить дату
|
||||
Start page number Начальный номер страницы
|
||||
Shrink the page content to make room for the header and footer Уменьшить содержимое страницы, чтобы освободить место для колонтитулов
|
||||
Source Источник
|
||||
Fit to page По странице
|
||||
Rotation Поворот
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -1764,6 +1764,7 @@
|
||||
Start Začetek
|
||||
Start Checking Začni preverjanje
|
||||
Start page number Številka začetne strani
|
||||
Shrink the page content to make room for the header and footer Pomanjšaj vsebino strani, da bo prostor za glavo in nogo
|
||||
Status Stanje
|
||||
Steps Koraki
|
||||
Sticky note Opomba
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -816,6 +816,7 @@ comment menu Edit సవరించు
|
||||
Insert Page Number పేజీ సంఖ్యను చొప్పించు
|
||||
Insert Date తేదీని చొప్పించు
|
||||
Start page number ప్రారంభ పేజీ సంఖ్య
|
||||
Shrink the page content to make room for the header and footer హెడర్ మరియు ఫుటర్కు చోటు కల్పించేందుకు పేజీ కంటెంట్ను కుదించు
|
||||
Source మూలం
|
||||
Fit to page పేజీకి సరిపోయేలా
|
||||
Rotation భ్రమణం
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 13 and column 16.
|
@@ -797,6 +797,7 @@
|
||||
Insert Page Number Sayfa Numarası Ekle
|
||||
Insert Date Tarih Ekle
|
||||
Start page number Başlangıç sayfa numarası
|
||||
Shrink the page content to make room for the header and footer Üst bilgi ve alt bilgiye yer açmak için sayfa içeriğini küçült
|
||||
Source Kaynak
|
||||
Fit to page Sayfaya sığdır
|
||||
Rotation Döndürme
|
||||
|
||||
|
@@ -839,6 +839,7 @@
|
||||
Insert Page Number Вставити номер сторінки
|
||||
Insert Date Вставити дату
|
||||
Start page number Початковий номер сторінки
|
||||
Shrink the page content to make room for the header and footer Зменшити вміст сторінки, щоб звільнити місце для колонтитулів
|
||||
Source Джерело
|
||||
Fit to page За розміром сторінки
|
||||
Rotation Поворот
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1136,6 +1136,7 @@ theme Appearance Giao diện
|
||||
Start Bắt đầu
|
||||
Start Checking Bắt đầu kiểm tra
|
||||
Start page number Số trang bắt đầu
|
||||
Shrink the page content to make room for the header and footer Thu nhỏ nội dung trang để chừa chỗ cho đầu trang và chân trang
|
||||
Status Trạng thái
|
||||
Steps Các bước
|
||||
Strength: {s} Độ mạnh: {s}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 491 and column 12.
|
@@ -521,6 +521,7 @@
|
||||
Insert Page Number 插入页码
|
||||
Insert Date 插入日期
|
||||
Start page number 起始页码
|
||||
Shrink the page content to make room for the header and footer 缩小页面内容,为页眉和页脚留出空间
|
||||
Source 来源
|
||||
Fit to page 适合页面
|
||||
Rotation 旋转
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -474,6 +474,7 @@
|
||||
Insert Page Number 插入頁碼
|
||||
Insert Date 插入日期
|
||||
Start page number 起始頁碼
|
||||
Shrink the page content to make room for the header and footer 縮小頁面內容,為頁首和頁尾留出空間
|
||||
Source 來源
|
||||
Fit to page 適合頁面
|
||||
Rotation 旋轉
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -223,6 +223,7 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens, kind: M
|
||||
ui.label(tl!("Start page number"));
|
||||
ui.add(egui::DragValue::new(&mut d.hf.start_number).range(1..=999_999));
|
||||
});
|
||||
ui.add(widgets::checkbox(&mut d.hf.shrink, tl!("Shrink the page content to make room for the header and footer")));
|
||||
}
|
||||
MarkKind::Watermark => {
|
||||
ui.horizontal(|ui| {
|
||||
@@ -306,11 +307,20 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens, kind: M
|
||||
ui.add_space(8.0);
|
||||
// Preview of the current page.
|
||||
let hf = d.hf.clone();
|
||||
let size_h = page.1.max(1.0);
|
||||
let (wm, bg, fit) = (d.wm.clone(), d.bg.clone(), d.fit);
|
||||
let file_name = d.use_file.then(|| d.file.as_ref().map(|f| f.0.clone())).flatten();
|
||||
ui.horizontal(|ui| {
|
||||
preview(ui, t, page, |p, r, s| match kind {
|
||||
MarkKind::HeaderFooter => {
|
||||
if hf.shrink {
|
||||
// The band the page content shrinks into, centred across.
|
||||
let (top, bottom) = hf.bands();
|
||||
let k = ((size_h - top - bottom) / size_h).clamp(0.0, 1.0) as f32;
|
||||
let band = Rect::from_min_max(pos2(r.left(), r.top() + top as f32 * s), pos2(r.right(), r.bottom() - bottom as f32 * s));
|
||||
let content = Rect::from_center_size(band.center(), vec2(r.width() * k, band.height().max(0.0)));
|
||||
p.rect_filled(content, CornerRadius::ZERO, Color32::from_gray(232));
|
||||
}
|
||||
for (k, text) in hf.text.iter().enumerate() {
|
||||
if text.trim().is_empty() {
|
||||
continue;
|
||||
|
||||
@@ -58,6 +58,21 @@ fn header_and_footer_dialog_inserts_tokens_and_applies_to_a_subset() {
|
||||
assert!(marks(&h).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_and_footer_dialog_can_shrink_the_page_content() {
|
||||
let mut h = harness();
|
||||
assert!(h.state_mut().execute("edit.bates"));
|
||||
h.run_steps(3);
|
||||
assert!(!h.state().marks_draft.hf.shrink, "off by default, as in Acrobat");
|
||||
h.get_by_label("Shrink the page content to make room for the header and footer").click();
|
||||
h.run_steps(2);
|
||||
assert!(h.state().marks_draft.hf.shrink);
|
||||
h.get_by_label("OK").click();
|
||||
h.run_steps(4);
|
||||
assert_eq!(h.state().dialog, None);
|
||||
assert_eq!(marks(&h), [MarkKind::HeaderFooter]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn watermark_and_background_dialogs() {
|
||||
let mut h = harness();
|
||||
|
||||
Reference in new issue
Block a user