Files
designcraft/crates/pdf/src/marks.rs
T
Brandon ThomasandClaude Opus 5.5 cc4a8610c2 M6.4: PDF export (krilla) + file.exportPdf
New crate designcraft-pdf (L3): pages or spreads with MediaBox/TrimBox/BleedBox,
parent items with per-page folios, fills (solid + gradient swatches), aligned/dashed
strokes, corner options, opacity/blend, simple drop shadow, clipped images (JPEG
passthrough), and real text: composed glyph runs with embedded font subsets and
Unicode mapping from the story. CMYK as DeviceCMYK, RGB as DeviceRGB, spot swatches
as Separation, [Registration] as /All. Crop/bleed marks and page info. PDF/A-2b via
krilla; PDF/X-4 writes PDF 1.6 (output intent TODO).

Engine command file.exportPdf {path?, pages?, spreads?, bleed?, marks?, standard?,
compressImages?, title?, author?} (base64 when no path), CLI --export out.pdf with
--pdf-options, File > Export PDF... in the UI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:20:59 -04:00

69 lines
2.9 KiB
Rust

//! Printer's marks drawn in the slug area: crop marks at the trim corners, bleed marks at the bleed
//! corners, and a page-information line. All marks use `[Registration]` (every plate).
use designcraft_geom::{BezPath, Point};
use krilla::num::NormalizedF32;
use krilla::paint::{LineCap, LineJoin, Stroke};
use krilla::surface::Surface;
use crate::export::{Exporter, MARK_LEN, Sheet, civil, mark_start, registration, to_path};
impl Exporter<'_> {
pub(crate) fn marks(&mut self, s: &mut Surface, sh: &Sheet, title: &str, created: Option<i64>) {
let m = self.opts.marks;
if !m.any() {
return;
}
let bleed = if self.opts.bleed { self.doc.settings.bleed.map(|v| v.max(0.0)) } else { [0.0; 4] };
let start = mark_start(self.opts, bleed);
let t = sh.trim;
let mut bp = BezPath::new();
let mut seg = |a: (f64, f64), b: (f64, f64)| {
bp.move_to(Point::new(a.0, a.1));
bp.line_to(Point::new(b.0, b.1));
};
if m.crop {
// Each trim corner: a horizontal mark beside it and a vertical one above/below it.
for (x, sx) in [(t.x0, -1.0), (t.x1, 1.0)] {
for (y, sy) in [(t.y0, -1.0), (t.y1, 1.0)] {
seg((x + sx * start, y), (x + sx * (start + MARK_LEN), y));
seg((x, y + sy * start), (x, y + sy * (start + MARK_LEN)));
}
}
}
let b = sh.bleed;
if m.bleed && (b.x0 < t.x0 || b.y0 < t.y0 || b.x1 > t.x1 || b.y1 > t.y1) {
// Shorter marks on the bleed lines, outside the crop-mark start.
let len = MARK_LEN / 2.0;
for (x, bx, sx) in [(t.x0, b.x0, -1.0), (t.x1, b.x1, 1.0)] {
for (y, by, sy) in [(t.y0, b.y0, -1.0), (t.y1, b.y1, 1.0)] {
seg((x + sx * start, by), (x + sx * (start + len), by));
seg((bx, y + sy * start), (bx, y + sy * (start + len)));
}
}
}
if let Some(p) = to_path(&bp) {
s.set_fill(None);
s.set_stroke(Some(Stroke {
paint: registration(1.0, self.rgb_only).into(),
width: m.weight.max(0.05) as f32,
miter_limit: 4.0,
line_cap: LineCap::Butt,
line_join: LineJoin::Miter,
opacity: NormalizedF32::ONE,
dash: None,
}));
s.draw_path(&p);
s.set_stroke(None);
}
if m.page_info {
let mut info = format!("{title} Page {}", sh.label);
if let Some(c) = created.map(civil) {
info.push_str(&format!(" {:04}-{:02}-{:02} {:02}:{:02} UTC", c.0, c.1, c.2, c.3, c.4));
}
let at = (t.x0 + start + 6.0, t.y1 + start + 10.0);
self.plain_text(s, &info, at, 6.0, registration(1.0, self.rgb_only));
}
}
}