From 8c80ee565e0d066dcc369fef480fd57eae2876a8 Mon Sep 17 00:00:00 2001 From: ThePhatak Date: Sat, 12 Sep 2026 22:55:18 +0530 Subject: [PATCH] add shuffle-only mode and booklet-signature option support --- cupsfilters/ipp-options-private.h | 13 +++- cupsfilters/ipp-options.c | 29 +++++--- cupsfilters/pdftopdf.c | 114 +++++++++++++++++++++++++++++- cupsfilters/test-filter-cases.txt | 5 ++ 4 files changed, 147 insertions(+), 14 deletions(-) diff --git a/cupsfilters/ipp-options-private.h b/cupsfilters/ipp-options-private.h index 10fbb6313..759363890 100644 --- a/cupsfilters/ipp-options-private.h +++ b/cupsfilters/ipp-options-private.h @@ -114,6 +114,13 @@ typedef enum cf_filter_orient_s // "orientation-requested" values CF_FILTER_ORIENT_NONE // No rotation } cf_filter_orient_t; +typedef enum pdftopdf_booklet_mode_e +{ + CF_PDFTOPDF_BOOKLET_OFF, + CF_PDFTOPDF_BOOKLET_ON, + CF_PDFTOPDF_BOOKLET_JUST_SHUFFLE +} pdftopdf_booklet_mode_t; + typedef enum cf_filter_error_report_e // Combination of "job-error-sheet-type" and "job-error-sheet-when" values { CF_FILTER_ERROR_REPORT_NONE, // "job-error-sheet-type" = 'none' @@ -190,8 +197,10 @@ typedef struct cf_filter_options_s // All filter options in one structure size_t num_force_front_side; // Number of "force-front-side" values int force_front_side[100]; // "force-front-side" values cf_filter_orient_t image_orientation; // "image-orientation" value - char imposition_template[128]; - // "imposition-template" value, if any + pdftopdf_booklet_mode_t booklet; + // "booklet" or "imposition-template" value + int booklet_signature; + // "booklet-signature" value, in sheets; -1 means all pages ippopt_error_sheet_t job_error_sheet; // "job-error-sheet" value char job_name[256]; // "job-name" value char job_originating_user_name[256]; diff --git a/cupsfilters/ipp-options.c b/cupsfilters/ipp-options.c index 506056e4a..1d603ef64 100644 --- a/cupsfilters/ipp-options.c +++ b/cupsfilters/ipp-options.c @@ -366,6 +366,8 @@ cfFilterOptionsCreate(size_t num_options, // I - Number of command-line option return (NULL); ippo->copies = 1; + ippo->booklet = CF_PDFTOPDF_BOOKLET_OFF; + ippo->booklet_signature = -1; ippo->image_orientation = CF_FILTER_ORIENT_NONE; ippo->multiple_document_handling = CF_FILTER_HANDLING_COLLATED_COPIES; ippo->print_scaling = CF_FILTER_SCALING_AUTO; @@ -493,16 +495,25 @@ cfFilterOptionsCreate(size_t num_options, // I - Number of command-line option if ((value = get_option("imposition-template", num_options, options)) != NULL || (value = get_option("booklet", num_options, options)) != NULL) { - if(!strcasecmp(value, "yes") || - !strcasecmp(value, "true") || - !strcasecmp(value, "booklet")) - { - cupsCopyString(ippo->imposition_template, "booklet", sizeof(ippo->imposition_template)); - } - else - { + if (!strcasecmp(value, "shuffle-only")) + ippo->booklet = CF_PDFTOPDF_BOOKLET_JUST_SHUFFLE; + else if (!strcasecmp(value, "yes") || + !strcasecmp(value, "true") || + !strcasecmp(value, "booklet")) + ippo->booklet = CF_PDFTOPDF_BOOKLET_ON; + else if (strcasecmp(value, "no") && + strcasecmp(value, "false") && + strcasecmp(value, "none")) fprintf(stderr, "cfFilterPDFToPDF: Unsupported booklet value %s, using booklet off", value); - } + } + + if ((value = get_option("booklet-signature", num_options, options)) != NULL) + { + intvalue = atoi(value); + if (intvalue > 0) + ippo->booklet_signature = intvalue; + else + fprintf(stderr, "cfFilterPDFToPDF: Unsupported booklet-signature value %s, using all pages", value); } if ((value = get_option("job-error-sheet", num_options, options)) != NULL) diff --git a/cupsfilters/pdftopdf.c b/cupsfilters/pdftopdf.c index f5397f479..07a4de424 100644 --- a/cupsfilters/pdftopdf.c +++ b/cupsfilters/pdftopdf.c @@ -630,7 +630,7 @@ prepare_number_up(xform_prepare_t *p) // I - Preparation data height; // Height of layout rectangle - if (!strcmp(p->options->imposition_template, "booklet")) + if (p->options->booklet == CF_PDFTOPDF_BOOKLET_ON) { // "imposition-template" = 'booklet' forces 2-up output... p->num_layout = 2; @@ -765,7 +765,115 @@ prepare_pages( bool use_page; // Use this page? - if (!strcmp(p->options->imposition_template, "booklet")) + if (p->options->booklet == CF_PDFTOPDF_BOOKLET_JUST_SHUFFLE) + { + pdfio_obj_t *selected_pages[XFORM_MAX_PAGES] = {NULL}; + size_t selected_page_count = 0; + size_t output_page_count; + size_t signature_size; + + // First collect the selected pages in their normal document order. + for (page = 1, i = num_documents, d = documents; i > 0; i--, d++) + { + while (page <= d->last_page) + { + if ((p->options->multiple_document_handling < CF_FILTER_HANDLING_SINGLE_DOCUMENT && + cfFilterOptionsIsPageInRange(p->options, page - d->first_page + 1)) || + (p->options->multiple_document_handling >= CF_FILTER_HANDLING_SINGLE_DOCUMENT && + cfFilterOptionsIsPageInRange(p->options, page))) + { + selected_pages[selected_page_count++] = + pdfioFileGetPage(d->pdf, (size_t)(page - d->first_page)); + } + page++; + } + + if (p->options->multiple_document_handling < CF_FILTER_HANDLING_SINGLE_DOCUMENT) + page = 1; + } + + // A sheet has two sides, so each signature contains four pages per sheet. + signature_size = p->options->booklet_signature > 0 ? + (size_t)p->options->booklet_signature * 4 : selected_page_count; + if (signature_size < 4) + signature_size = 4; + + // Add blank pages to complete the last signature. + output_page_count = (selected_page_count + signature_size - 1) / + signature_size * signature_size; + p->num_outpages = output_page_count; + + // Each signature is ordered from the outside inward: + // last, first, second, penultimate, ... + for (current = 0; current < output_page_count; current++) + { + size_t signature_start = (current / signature_size) * signature_size; + size_t signature_slot = current % signature_size; + size_t source_page; + size_t pair = signature_slot / 2; + + if ((signature_slot & 1) == 0) + source_page = signature_start + signature_size - 1 - pair; + else + source_page = signature_start + pair; + + p->outpages[current].pdf = p->pdf; + if (source_page < selected_page_count) + p->outpages[current].input[0] = selected_pages[source_page]; + } + } + else if (p->options->booklet == CF_PDFTOPDF_BOOKLET_ON && + p->options->booklet_signature > 0) + { + pdfio_obj_t *selected_pages[XFORM_MAX_PAGES] = { NULL }; + size_t selected_page_count = 0; + size_t signature_size = (size_t)p->options->booklet_signature * 4; + size_t shuffled_page_count; + + // Collect the pages selected for printing. + for (page = 1, i = num_documents, d = documents; i > 0; i--, d++) + { + while (page <= d->last_page) + { + if ((p->options->multiple_document_handling < CF_FILTER_HANDLING_SINGLE_DOCUMENT && + cfFilterOptionsIsPageInRange(p->options, page - d->first_page + 1)) || + (p->options->multiple_document_handling >= CF_FILTER_HANDLING_SINGLE_DOCUMENT && + cfFilterOptionsIsPageInRange(p->options, page))) + selected_pages[selected_page_count++] = + pdfioFileGetPage(d->pdf, (size_t)(page - d->first_page)); + + page++; + } + + if (p->options->multiple_document_handling < CF_FILTER_HANDLING_SINGLE_DOCUMENT) + page = 1; + } + + // Pad each signature to a complete set of sheets, then create 2-up pages. + shuffled_page_count = (selected_page_count + signature_size - 1) / + signature_size * signature_size; + p->num_outpages = shuffled_page_count / 2; + + for (current = 0; current < shuffled_page_count; current ++) + { + size_t signature_start = (current / signature_size) * signature_size; + size_t signature_slot = current % signature_size; + size_t pair = signature_slot / 2; + size_t source_page; + size_t output_page = current / 2; + size_t output_slot = current % 2; + + if ((signature_slot & 1) == 0) + source_page = signature_start + signature_size - 1 - pair; + else + source_page = signature_start + pair; + + p->outpages[output_page].pdf = p->pdf; + if (source_page < selected_page_count) + p->outpages[output_page].input[output_slot] = selected_pages[source_page]; + } + } + else if (p->options->booklet == CF_PDFTOPDF_BOOKLET_ON) { // Booklet printing arranges input pages so that the folded output can be // stapled along the midline... @@ -782,7 +890,7 @@ prepare_pages( else use_page = cfFilterOptionsIsPageInRange(p->options, page); - if (use_page) + if (use_page) { if (current < p->num_outpages) outpage = p->outpages + current; diff --git a/cupsfilters/test-filter-cases.txt b/cupsfilters/test-filter-cases.txt index d75f8c573..6a74faa78 100644 --- a/cupsfilters/test-filter-cases.txt +++ b/cupsfilters/test-filter-cases.txt @@ -14,6 +14,11 @@ cupsfilters/test_files/test_file_4pg.pdf application/pdf cupsfilters/test_files/ cupsfilters/test_files/test_file_4pg.pdf application/pdf cupsfilters/test_files/output_files/output_landscape_true.pdf application/pdf Generic PDF Color 2 1 1 text/plain,application/pdf 13 new-user custom-print 10 media-size=A4 printer-resolution=300dpi landscape=true cupsfilters/test_files/test_file_4pg.pdf application/pdf cupsfilters/test_files/output_files/output_nup_2.pdf application/pdf Generic PDF Color 2 1 1 text/plain,application/pdf 13 new-user custom-print 10 media-size=A4 printer-resolution=300dpi number-up=2 cupsfilters/test_files/test_file_4pg.pdf application/pdf cupsfilters/test_files/output_files/output_booklet.pdf application/pdf Generic PDF Color 2 1 1 text/plain,application/pdf 13 new-user custom-print 10 media-size=A4 printer-resolution=300dpi booklet=true sides=two-sided-short-edge +cupsfilters/test_files/test_file_4pg.pdf application/pdf cupsfilters/test_files/output_files/output_booklet_true.pdf application/pdf Generic PDF Color 2 1 1 text/plain,application/pdf 13 new-user custom-print 10 media-size=A4 printer-resolution=300dpi booklet=true sides=two-sided-short-edge +cupsfilters/test_files/test_file_4pg.pdf application/pdf cupsfilters/test_files/output_files/output_booklet_signature_1_2up.pdf application/pdf Generic PDF Color 2 1 1 text/plain,application/pdf 13 new-user custom-print 10 media-size=A4 printer-resolution=300dpi booklet=true booklet-signature=1 sides=two-sided-short-edge +cupsfilters/test_files/test_file_4pg.pdf application/pdf cupsfilters/test_files/output_files/output_booklet_shuffle_only.pdf application/pdf Generic PDF Color 2 1 1 text/plain,application/pdf 13 new-user custom-print 10 media-size=A4 printer-resolution=300dpi booklet=shuffle-only sides=two-sided-short-edge +cupsfilters/test_files/test_file_4pg.pdf application/pdf cupsfilters/test_files/output_files/output_booklet_signature_1.pdf application/pdf Generic PDF Color 2 1 1 text/plain,application/pdf 13 new-user custom-print 10 media-size=A4 printer-resolution=300dpi booklet=shuffle-only booklet-signature=1 sides=two-sided-short-edge +cupsfilters/test_files/test_file_4pg.pdf application/pdf cupsfilters/test_files/output_files/output_booklet_false.pdf application/pdf Generic PDF Color 2 1 1 text/plain,application/pdf 13 new-user custom-print 10 media-size=A4 printer-resolution=300dpi booklet=false sides=two-sided-short-edge cupsfilters/test_files/test_file_4pg.pdf application/pdf cupsfilters/test_files/output_files/output_mirror.pdf application/pdf Generic PDF Color 2 1 1 text/plain,application/pdf 13 new-user custom-print 10 media-size=A4 printer-resolution=300dpi mirror=true cupsfilters/test_files/test_file_4pg.pdf application/pdf cupsfilters/test_files/output_files/test_file_op.pwg image/pwg-raster Generic PDF Color 2 1 1 image/pwg-raster,application/pdf 13 new-user custom-print 5 sides=two-sided-short-edge media-size=A4 printer-resolution=300dpi cupsfilters/test_files/test_file_4pg.pdf application/pdf cupsfilters/test_files/output_files/test_file_op.pwg image/pwg-raster Generic PDF Color 2 1 1 image/pwg-raster,application/pdf 13 new-user custom-print 5 sides=two-sided-short-edge media-size=A4 printer-resolution=300dpi