commit dc27eca1611d21a98445e713b2be3c3204b4f083
parent c6a3ce4974104d42eb8f3e8d11391f19017c36ff
Author: triesap <tyson@radroots.org>
Date: Wed, 22 Apr 2026 21:54:13 +0000
app: enforce letter media for customer-label print plans
- keep pack sheet and pickup roster on the existing simple lp submission path
- submit customer-label plans with explicit media=Letter and the prepared stock asset
- declare Letter geometry inside the prepared Avery 5160 postscript output
- extend planner and runtime tests to lock the command-plan split
Diffstat:
2 files changed, 64 insertions(+), 16 deletions(-)
diff --git a/crates/launchers/desktop/src/pack_day_print.rs b/crates/launchers/desktop/src/pack_day_print.rs
@@ -11,6 +11,9 @@ use radroots_app_models::{
use thiserror::Error;
const CUSTOMER_LABEL_PREPARED_ASSET_ROOT: &str = "radroots_app_pack_day_print";
+const LETTER_MEDIA_OPTION: &str = "media=Letter";
+const LETTER_PAGE_WIDTH_POINTS: u16 = 612;
+const LETTER_PAGE_HEIGHT_POINTS: u16 = 792;
const AVERY_5160_LABELS_PER_ROW: usize = 3;
const AVERY_5160_LABEL_ROWS_PER_PAGE: usize = 10;
const AVERY_5160_LABELS_PER_PAGE: usize =
@@ -275,23 +278,41 @@ pub fn plan_pack_day_print(
});
}
- let target_path = match kind {
+ let (target_path, command_args) = match kind {
PackDayPrintKind::PrintPackSheet => {
- resolve_bundle_artifact_path(bundle, PackDayExportArtifactKind::PackSheet, kind)?
+ let target_path =
+ resolve_bundle_artifact_path(bundle, PackDayExportArtifactKind::PackSheet, kind)?;
+ let command_args = vec![target_path.to_string_lossy().into_owned()];
+ (target_path, command_args)
}
PackDayPrintKind::PrintPickupRoster => {
- resolve_bundle_artifact_path(bundle, PackDayExportArtifactKind::PickupRoster, kind)?
+ let target_path = resolve_bundle_artifact_path(
+ bundle,
+ PackDayExportArtifactKind::PickupRoster,
+ kind,
+ )?;
+ let command_args = vec![target_path.to_string_lossy().into_owned()];
+ (target_path, command_args)
}
PackDayPrintKind::PrintCustomerLabels => {
- prepare_customer_label_stock_asset(bundle, PackDayPrintLabelStock::Avery5160Letter30Up)?
+ let target_path = prepare_customer_label_stock_asset(
+ bundle,
+ PackDayPrintLabelStock::Avery5160Letter30Up,
+ )?;
+ let command_args = vec![
+ "-o".to_owned(),
+ LETTER_MEDIA_OPTION.to_owned(),
+ target_path.to_string_lossy().into_owned(),
+ ];
+ (target_path, command_args)
}
};
Ok(PackDayPrintCommandPlan {
kind,
- target_path: target_path.clone(),
+ target_path,
command_program: "lp",
- command_args: vec![target_path.to_string_lossy().into_owned()],
+ command_args,
})
}
@@ -443,7 +464,14 @@ fn render_avery_5160_customer_labels_postscript(blocks: Vec<Vec<String>>) -> Str
let _ = writeln!(&mut rendered, "%!PS-Adobe-3.0");
let _ = writeln!(&mut rendered, "%%Creator: radroots_app");
let _ = writeln!(&mut rendered, "%%Pages: {page_count}");
- let _ = writeln!(&mut rendered, "%%BoundingBox: 0 0 612 792");
+ let _ = writeln!(
+ &mut rendered,
+ "%%BoundingBox: 0 0 {LETTER_PAGE_WIDTH_POINTS} {LETTER_PAGE_HEIGHT_POINTS}"
+ );
+ let _ = writeln!(
+ &mut rendered,
+ "%%DocumentMedia: Letter {LETTER_PAGE_WIDTH_POINTS} {LETTER_PAGE_HEIGHT_POINTS} 0 () ()"
+ );
let _ = writeln!(&mut rendered, "%%EndComments");
for (page_index, page_blocks) in blocks.chunks(AVERY_5160_LABELS_PER_PAGE).enumerate() {
@@ -451,6 +479,10 @@ fn render_avery_5160_customer_labels_postscript(blocks: Vec<Vec<String>>) -> Str
let _ = writeln!(&mut rendered, "%%Page: {page_number} {page_number}");
let _ = writeln!(
&mut rendered,
+ "<< /PageSize [{LETTER_PAGE_WIDTH_POINTS} {LETTER_PAGE_HEIGHT_POINTS}] >> setpagedevice"
+ );
+ let _ = writeln!(
+ &mut rendered,
"/Courier findfont {} scalefont setfont",
AVERY_5160_TEXT_FONT_SIZE_POINTS
);
@@ -610,9 +642,9 @@ fn run_macos_print_command(
#[cfg(test)]
mod tests {
use super::{
- PackDayPrintCommandResult, PackDayPrintError, execute_pack_day_print_plan_with,
- plan_pack_day_print, prepared_customer_label_asset_directory,
- prepared_customer_label_asset_path,
+ LETTER_MEDIA_OPTION, PackDayPrintCommandResult, PackDayPrintError,
+ execute_pack_day_print_plan_with, plan_pack_day_print,
+ prepared_customer_label_asset_directory, prepared_customer_label_asset_path,
};
use radroots_app_models::{
PackDayExportArtifact, PackDayExportArtifactKind, PackDayExportBundle,
@@ -734,7 +766,11 @@ mod tests {
assert_eq!(plan.command_program, "lp");
assert_eq!(
plan.command_args,
- vec![prepared_path.to_string_lossy().into_owned()]
+ vec![
+ "-o".to_owned(),
+ LETTER_MEDIA_OPTION.to_owned(),
+ prepared_path.to_string_lossy().into_owned()
+ ]
);
assert!(plan.target_path.is_file());
assert!(!plan.target_path.starts_with(temp_dir.path()));
@@ -752,6 +788,8 @@ mod tests {
fs::read_to_string(&prepared_path).expect("prepared labels should render");
assert!(prepared_contents.contains("%!PS-Adobe-3.0"));
assert!(prepared_contents.contains("%%Pages: 1"));
+ assert!(prepared_contents.contains("%%DocumentMedia: Letter 612 792 0 () ()"));
+ assert!(prepared_contents.contains("<< /PageSize [612 792] >> setpagedevice"));
assert!(prepared_contents.contains("(Casey) show"));
assert!(prepared_contents.contains("(Taylor) show"));
assert!(
diff --git a/crates/launchers/desktop/src/runtime.rs b/crates/launchers/desktop/src/runtime.rs
@@ -7526,12 +7526,14 @@ mod tests {
assert_eq!(prepared.0.label_stock, kind.label_stock());
assert_eq!(prepared.1.kind, kind);
assert_eq!(prepared.1.command_program, "lp");
- assert_eq!(
- prepared.1.command_args,
- vec![prepared.1.target_path.to_string_lossy().into_owned()]
- );
match expected_exported_suffix {
- Some(suffix) => assert!(prepared.1.target_path.ends_with(suffix)),
+ Some(suffix) => {
+ assert!(prepared.1.target_path.ends_with(suffix));
+ assert_eq!(
+ prepared.1.command_args,
+ vec![prepared.1.target_path.to_string_lossy().into_owned()]
+ );
+ }
None => {
let export_bundle = summary
.pack_day_projection
@@ -7558,6 +7560,14 @@ mod tests {
.to_string_lossy()
.contains(export_bundle.export_instance_id.to_string().as_str())
);
+ assert_eq!(
+ prepared.1.command_args,
+ vec![
+ "-o".to_owned(),
+ "media=Letter".to_owned(),
+ prepared.1.target_path.to_string_lossy().into_owned()
+ ]
+ );
}
}