From 14f2d8b920692dc06ea383fdb5afaa336458d699 Mon Sep 17 00:00:00 2001 From: Cocoa Date: Sat, 17 Feb 2024 17:49:41 -0500 Subject: [PATCH 1/3] Fix issue with non-duplexed aggregate side 1 --- src/book.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/book.js b/src/book.js index 2e3c499..74c90e4 100644 --- a/src/book.js +++ b/src/book.js @@ -312,7 +312,8 @@ export class Book { const generateSignatures = this.print_file != 'aggregated'; const side1PageNumbers = new Set( this.rearrangedpages.reduce((accumulator, currentValue) => { - return accumulator.concat(currentValue[0]); + const pageNums = currentValue[0].map((pageInfo) => pageInfo.info); + return accumulator.concat(pageNums); }, []) ); const [pdf0PageNumbers, pdf1PageNumbers] = From 06a0b5298a166eab2be71579ae6fbf019c20d3a9 Mon Sep 17 00:00:00 2001 From: acestronautical Date: Mon, 19 Feb 2024 12:43:21 -0800 Subject: [PATCH 2/3] Improve file naming and pathing Original pdf name will now correctly regex replace. The previous regex was lacking the g flag causing it to not remove the pdf extension if a space or comma was present. File name conversion now replaces space, commas, and hypens to underscores. Signature files will be placed into a signatures/ folder. --- package.json | 2 +- src/book.js | 27 +++++++++++++++++---------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 408557a..aaf7140 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bookbinder", - "version": "1.3.4", + "version": "1.3.5", "description": "An app to rearrange PDF pages for printing for bookbinding", "type": "module", "scripts": { diff --git a/src/book.js b/src/book.js index 74c90e4..4af5cc4 100644 --- a/src/book.js +++ b/src/book.js @@ -299,7 +299,11 @@ export class Book { // create a directory named after the input pdf and fill it with // the signatures this.zip = new JSZip(); - var origFileName = this.inputpdf.replace(/\s|,|\.pdf/, ''); + var origFileName = this.inputpdf; + origFileName = origFileName + .replace(/[-\s,_]+/g, '_') + .replace(/_*\.pdf/g, '') + .toLowerCase(); this.filename = origFileName; if ( @@ -341,7 +345,7 @@ export class Book { embeddedPages: generateAggregate ? [embeddedPages0, embeddedPages1] : null, aggregatePdfs: generateAggregate ? [aggregatePdf0, aggregatePdf1] : null, pageIndexDetails: signature, - id: generateSignatures ? `signature${i}` : null, + id: generateSignatures ? `${this.filename}_signature${i}` : null, isDuplex: this.duplex, fileList: this.filelist, }); @@ -351,17 +355,20 @@ export class Book { if (aggregatePdf1 != null) { await aggregatePdf1.save().then((pdfBytes) => { - if (!isPreview) this.zip.file('aggregate_side2.pdf', pdfBytes); + if (!isPreview) this.zip.file(`${this.filename}_typeset_side2.pdf`, pdfBytes); }); } if (aggregatePdf0 != null) { await aggregatePdf0.save().then((pdfBytes) => { if (!isPreview) - this.zip.file(this.duplex ? 'aggregate_book.pdf' : 'aggregate_side1.pdf', pdfBytes); + this.zip.file( + this.duplex ? `${this.filename}_typeset.pdf` : `${this.filename}_typeset_side1.pdf`, + pdfBytes + ); }); } var rotationMetaInfo = - (this.paper_rotation_90 ? '_paperRotated' : '') + + (this.paper_rotation_90 ? 'paper_rotated' : '') + (this.source_rotation == 'none' ? '' : `_${this.source_rotation}`); this.filename = `${origFileName}${rotationMetaInfo}`; resultPDF = aggregatePdf0; @@ -525,7 +532,7 @@ export class Book { if (printSignatures) { await outPDF.save().then((pdfBytes) => { - this.zip.file(config.outname, pdfBytes); + this.zip.file(`signatures/${config.outname}`, pdfBytes); }); } } @@ -795,7 +802,7 @@ export class Book { const pages = config.pageIndexDetails; // duplex printers print both sides of the sheet, if (config.isDuplex) { - const outduplex = printSignatures ? `${config.id}duplex.pdf` : null; + const outduplex = printSignatures ? `${config.id}_duplex.pdf` : null; await this.writepages({ outname: outduplex, pageList: pages[0], @@ -810,8 +817,8 @@ export class Book { } else { // for non-duplex printers we have two files, print the first, flip // the sheets over, then print the second - const outname1 = printSignatures ? `${config.id}side1.pdf` : null; - const outname2 = printSignatures ? `${config.id}side2.pdf` : null; + const outname1 = printSignatures ? `${config.id}_side1.pdf` : null; + const outname2 = printSignatures ? `${config.id}_side2.pdf` : null; await this.writepages({ outname: outname1, @@ -851,7 +858,7 @@ export class Book { this.bundleSettings(); return this.zip.generateAsync({ type: 'blob' }).then((blob) => { console.log(' calling saveAs on ', this.filename); - saveAs(blob, `${this.filename}.zip`); + saveAs(blob, `${this.filename}_bookbinder.zip`); }); } From 51cbedb3b89ee8d27b53b291846025a1d2ef6ae9 Mon Sep 17 00:00:00 2001 From: momijizukamori Date: Wed, 21 Feb 2024 12:56:51 -0500 Subject: [PATCH 3/3] Refactor layout code (#103) * Refactor layout code --------- Co-authored-by: Cocoa --- src/book.js | 216 ++++--------------------------------------- src/utils/drawing.js | 6 +- src/utils/layout.js | 178 +++++++++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 199 deletions(-) create mode 100644 src/utils/layout.js diff --git a/src/book.js b/src/book.js index 4af5cc4..785e622 100644 --- a/src/book.js +++ b/src/book.js @@ -2,7 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -import { PDFDocument, degrees } from 'pdf-lib'; +import { PDFDocument, PDFEmbeddedPage, degrees } from 'pdf-lib'; import { saveAs } from 'file-saver'; import { Signatures } from './signatures.js'; import { WackyImposition } from './wacky_imposition.js'; @@ -11,6 +11,7 @@ import { updatePageLayoutInfo } from './utils/renderUtils.js'; import JSZip from 'jszip'; import { loadConfiguration } from './utils/formUtils.js'; import { drawFoldlines, drawCropmarks, drawSpineMarks } from './utils/drawing.js'; +import { calculateDimensions, calculateLayout } from './utils/layout.js'; // Some JSDoc typedefs we use multiple places /** @@ -29,6 +30,9 @@ import { drawFoldlines, drawCropmarks, drawSpineMarks } from './utils/drawing.js * @property {number} sy - y scale factor (where 1.0 is 100%) * @property {number} x - x position * @property {number} y - y position + * @property {number[]} [spineMarkTop]: spineMarkTop, + * @property {number[]} [spineMarkBottom]: spineMarkBottom, + * @property {boolean} [isLeftPage]: isLeftPage, */ export class Book { @@ -110,7 +114,6 @@ export class Book { this.input = await file.arrayBuffer(); //fs.readFileSync(filepath); this.currentdoc = await PDFDocument.load(this.input); //TODO: handle pw-protected PDFs - /** @type {number} */ const pages = this.currentdoc.getPages(); this.cropbox = null; @@ -272,16 +275,17 @@ export class Book { } console.log('Created pages for : ', this.book); - const dim = this.calculate_dimensions(); + const dimensions = calculateDimensions(this); + const positions = calculateLayout(this); updatePageLayoutInfo({ - dimensions: dim, + dimensions, book: this.book, perSheet: this.per_sheet, papersize: this.papersize, cropbox: this.cropbox, managedDoc: this.managedDoc, - positions: this.calculatelayout(), + positions, }); } @@ -413,10 +417,10 @@ export class Book { /** * Generates a new PDF & embeds the prescribed pages of the source PDF into it * @param sourcePdf - * @param {number[]} pageNumbers - an array of page numbers. Ex: [1,5,6,7,8,'b',10] or null to embed all pages from source + * @param {(string|number)[]} [pageNumbers] - an array of page numbers. Ex: [1,5,6,7,8,'b',10] or null to embed all pages from source * NOTE: re-construction behavior kicks in if there's 'b's in the list * - * @return [newPdf with pages embedded, embedded page array] + * @return {Promise<[PDFDocument, PDFEmbeddedPage[]]>} PDF with pages embedded, embedded page array */ async embedPagesInNewPdf(sourcePdf, pageNumbers) { const newPdf = await PDFDocument.create(); @@ -486,9 +490,9 @@ export class Book { const offset = this.per_sheet / 2; let block_end = offset; - const alt_folio = this.per_sheet == 4 && back; + // const alt_folio = this.per_sheet == 4 && back; - const positions = this.calculatelayout(alt_folio); + const positions = calculateLayout(this); let side2flag = back; @@ -542,7 +546,7 @@ export class Book { * @param {string|null} config.outname : name of pdf added to ongoing zip file. Ex: 'signature1duplex.pdf' (or null if no signature file needed) * @param {PageInfo[]} config.sigDetails : objects that contain 3 values: { isSigStart: boolean, isSigEnd: boolean, info: either the page number or 'b'} * @param {boolean} config.side2flag : is 'back' of page (boolean) - * @param {number[]} config.papersize : paper size (as [number, number]) + * @param {[number, number]} config.papersize : paper size (as [number, number]) * @param {number} config.block_start: Starting page index * @param {number} config.block_end: Ending page index * @param {boolean} config.alt : alternate pages (boolean) @@ -550,8 +554,8 @@ export class Book { * @param {boolean} config.cropmarks: whether to print cropmarks * @param {boolean} config.pdfEdgeMarks: whether to print PDF edge marks * @param {Position[]} config.positions: list of page positions - * @param config.outPDF : PDF to write to, in addition to PDF created w/ `outname` (or null) - * @param config.embeddedPages : pages already embedded in the `destPdf` to assemble in addition (or null) + * @param {PDFDocument} [config.outPDF]: PDF to write to, in addition to PDF created w/ `outname` (or null) + * @param {(PDFEmbeddedPage|string)[]} [config.embeddedPages] : pages already embedded in the `destPdf` to assemble in addition (or null) */ draw_block_onto_page(config) { @@ -578,7 +582,7 @@ export class Book { block.forEach((page, i) => { if (page == 'b' || page === undefined) { // blank page, move on. - } else { + } else if (page instanceof PDFEmbeddedPage) { const { y, x, sx, sy, rotation } = positions[i]; currPage.drawPage(page, { y, @@ -587,6 +591,8 @@ export class Book { yScale: sy, rotate: degrees(rotation), }); + } else { + console.error('Unexpected type for page: ', page); } if (pdfEdgeMarks && (sigDetails[i].isSigStart || sigDetails[i].isSigEnd)) { @@ -604,187 +610,6 @@ export class Book { return side2flag; } - /** - * Looks at [this.cropbox] and [this.padding_pt] and [this.papersize] and [this.page_layout] and [this.page_scaling] - * in order to calculate the information needed to render a PDF page within a layout cell. It provides several functions - * in the return object that calculate the positioning and scaling needed when provided the rotation information. - * - * When calculating 'x' and 'y' values, those are relative to a laid out PDF page, not necessarily paper sheet x & y - * - * @return the object: { - * layoutCell: 2 dimensional array of the largest possible space the PDF page could take within the layout (and not overflow) - * rawPdfSize: 2 dimensional array of dimensions for the PDF (pre scaled) - * pdfSize: 2 dimensional array of dimensions for the PDF page + margins (pre scaled) - * pdfScale: 2 dimensional array of scaling factors for the raw PDF so it fits in layoutCell (w/ margins) - * padding: object containing the already scaled padding. Keys are: fore_edge, binding, top, bottom - * xForeEdgeShiftFunc: requires the page rotation, in degrees. In pts, already scaled. - * xBindingShiftFunc: requires the page rotation, in degrees. In pts, already scaled. - * xPdfWidthFunc: requires the page rotation, in degrees. In pts, already scaled. - * yPdfHeightFunc: requires the page rotation, in degrees. In pts, already scaled. - * yTopShiftFunc: requires the page rotation, in degrees. In pts, already scaled. - * yBottomShiftFunc: requires the page rotation, in degrees. In pts, already scaled. - * } - */ - calculate_dimensions() { - const onlyPos = function (v) { - return v > 0 ? v : 0; - }; - // const onlyNeg = function (v) { - // return v < 0 ? v : 0; - // }; - // PDF + margins (positive) - const pagex = - this.cropbox.width + onlyPos(this.padding_pt.binding) + onlyPos(this.padding_pt.fore_edge); - const pagey = - this.cropbox.height + onlyPos(this.padding_pt.top) + onlyPos(this.padding_pt.bottom); - - const layout = this.page_layout; - - // Calculate the size of each page box on the sheet - let finalx = this.papersize[0] / layout.cols; - let finaly = this.papersize[1] / layout.rows; - - // if pages are rotated a quarter-turn in this layout, we need to swap the width and height measurements - if (layout.landscape) { - const temp = finalx; - finalx = finaly; - finaly = temp; - } - - let sx = 1; - let sy = 1; - - // The page_scaling options are: 'lockratio', 'stretch', 'centered' - if (this.page_scaling == 'lockratio') { - const scale = Math.min(finalx / pagex, finaly / pagey); - sx = scale; - sy = scale; - } else if (this.page_scaling == 'stretch') { - sx = finalx / pagex; - sy = finaly / pagey; - } // else = centered retains 1 x 1 - - const padding = { - fore_edge: this.padding_pt.fore_edge * sx, - binding: this.padding_pt.binding * sx, - bottom: this.padding_pt.bottom * sy, - top: this.padding_pt.top * sy, - }; - - // page_positioning has 2 options: centered, binding_alinged - const positioning = this.page_positioning; - - const xForeEdgeShiftFunc = function () { - // amount to inset by, relative to fore edge, on left side of book - const xgap = finalx - pagex * sx; - return padding.fore_edge + (positioning == 'centered' ? xgap / 2 : xgap); - }; - const xBindingShiftFunc = function () { - // amount to inset by, relative to binding, on right side of book - const xgap = finalx - pagex * sx; - return padding.binding + (positioning == 'centered' ? xgap / 2 : 0); - }; - const yTopShiftFunc = function () { - const ygap = finaly - pagey * sy; - return padding.top + ygap / 2; - }; - const yBottomShiftFunc = function () { - const ygap = finaly - pagey * sy; - return padding.bottom + ygap / 2; - }; - const xPdfWidthFunc = function () { - return pagex * sx - padding.fore_edge - padding.binding; - }; - const yPdfHeightFunc = function () { - return pagey * sy - padding.top - padding.bottom; - }; - return { - layout: layout, - rawPdfSize: [this.cropbox.width, this.cropbox.height], - pdfScale: [sx, sy], - pdfSize: [pagex, pagey], - layoutCell: [finalx, finaly], - padding: padding, - - xForeEdgeShiftFunc: xForeEdgeShiftFunc, - xBindingShiftFunc: xBindingShiftFunc, - xPdfWidthFunc: xPdfWidthFunc, - yPdfHeightFunc: yPdfHeightFunc, - yTopShiftFunc: yTopShiftFunc, - yBottomShiftFunc: yBottomShiftFunc, - - positioning: positioning, - }; - } - - /** - * When considering page size, don't forget to take into account - * this.padding_pt's ['top','bottom','binding','fore_edge'] values - * - * @return {Position[]} - */ - calculatelayout() { - // vampire - const l = this.calculate_dimensions(); - const cellWidth = l.layoutCell[0]; - const cellHeight = l.layoutCell[1]; - const positions = []; - - l.layout.rotations.forEach((row, i) => { - row.forEach((col, j) => { - const xForeEdgeShift = l.xForeEdgeShiftFunc(); - const xBindingShift = l.xBindingShiftFunc(); - const yTopShift = l.yTopShiftFunc(); - const yBottomShift = l.yBottomShiftFunc(); - - let isLeftPage = j % 2 == 0; //page on 'left' side of open book - let x = j * cellWidth + (isLeftPage ? xForeEdgeShift : xBindingShift); - let y = i * cellHeight + yBottomShift; - let spineMarkTop = [j * cellWidth, (i + 1) * cellHeight - yTopShift]; - let spineMarkBottom = [(j + 1) * cellWidth, i * cellHeight + yBottomShift]; - - if (col == -180) { - // upside-down page - isLeftPage = j % 2 == 1; //page on 'left' (right side on screen) - y = (i + 1) * cellHeight - yBottomShift; - x = (j + 1) * cellWidth - (isLeftPage ? xForeEdgeShift : xBindingShift); - spineMarkTop = [(j + 1) * cellWidth, (i + 1) * cellHeight]; - spineMarkBottom = [(j + 1) * cellWidth, i * cellHeight]; - } else if (col == 90) { - // 'top' of page is on left, right side of screen - isLeftPage = i % 2 == 0; // page is on 'left' (top side of screen) - x = (1 + j) * cellHeight - yBottomShift; - y = i * cellWidth + (isLeftPage ? xBindingShift : xForeEdgeShift); - spineMarkTop = [(1 + j) * cellHeight, i * cellWidth]; - spineMarkBottom = [j * cellHeight, i * cellWidth]; - } else if (col == -90) { - // 'top' of page is on the right, left sight of screen - isLeftPage = i % 2 == 1; // page is on 'left' (bottom side of screen) - x = j * cellHeight + yBottomShift; - y = (1 + i) * cellWidth - (isLeftPage ? xForeEdgeShift : xBindingShift); - spineMarkTop = [(j + 1) * cellHeight - yTopShift, (isLeftPage ? i : i + 1) * cellWidth]; - spineMarkBottom = [j * cellHeight + yBottomShift, (isLeftPage ? i : i + 1) * cellWidth]; - } - - console.log( - `>> (${i},${j})[${col}] : [${x},${y}] :: [xForeEdgeShift: ${xForeEdgeShift}][xBindingShift: ${xBindingShift}]` - ); - positions.push({ - rotation: col, - sx: l.pdfScale[0], - sy: l.pdfScale[1], - x: x, - y: y, - spineMarkTop: spineMarkTop, - spineMarkBottom: spineMarkBottom, - isLeftPage: isLeftPage, - }); - }); - }); - console.log('And in the end of it all, (calculatelayout) we get: ', positions); - return positions; - } - /** * PDF builder base function for Classic (non-Wacky) layouts. Called by [createoutputfiles] * @@ -792,7 +617,7 @@ export class Book { * @param {PageInfo[][]|PageInfo[]} config.pageIndexDetails : a nested list of objects. * @param config.aggregatePdfs : list of destination PDF(s_ for aggregated content ( [0] for duplex & front, [1] for backs -- value is null if no aggregate printing enabled) * @param config.embeddedPages : list of lists of embedded pages from source document ( [0] for duplex & front, [1] for backs -- value is null if no aggregate printing enabled) - * @param {id} config.id : string dentifier for signature file name (null if no signature files to be generated) + * @param {string} config.id : string dentifier for signature file name (null if no signature files to be generated) * @param {boolean} config.isDuplex : boolean * @param {string[]} config.fileList : list of filenames for sig filename to be added to (modifies list) */ @@ -872,7 +697,6 @@ export class Book { /** * @callback LineMaker - * @param {number} x - ... */ /** diff --git a/src/utils/drawing.js b/src/utils/drawing.js index c2f2670..59af030 100644 --- a/src/utils/drawing.js +++ b/src/utils/drawing.js @@ -113,9 +113,9 @@ export function drawCropmarks(papersize, per_sheet) { } /** - * @param {PageInfo} sigDetails - page info object - * @param {Position} position - position info object - * @returns {Line[]} + * @param {import("../book.js").PageInfo} sigDetails - page info object + * @param {import("../book.js").Position} position - position info object + * @returns {Line} */ export function drawSpineMarks(sigDetails, position) { const w = 5; diff --git a/src/utils/layout.js b/src/utils/layout.js new file mode 100644 index 0000000..a040371 --- /dev/null +++ b/src/utils/layout.js @@ -0,0 +1,178 @@ +/** + * When considering page size, don't forget to take into account + * this.padding_pt's ['top','bottom','binding','fore_edge'] values + * + * @return {import("../book.js").Position[]} + */ +export function calculateLayout(book) { + const l = calculateDimensions(book); + const { + layoutCell, + xForeEdgeShiftFunc, + xBindingShiftFunc, + yTopShiftFunc, + yBottomShiftFunc, + pdfScale, + } = l; + const [cellWidth, cellHeight] = layoutCell; + const positions = []; + + l.layout.rotations.forEach((row, i) => { + row.forEach((col, j) => { + const xForeEdgeShift = xForeEdgeShiftFunc(); + const xBindingShift = xBindingShiftFunc(); + const yTopShift = yTopShiftFunc(); + const yBottomShift = yBottomShiftFunc(); + + let isLeftPage = j % 2 == 0; //page on 'left' side of open book + let x = j * cellWidth + (isLeftPage ? xForeEdgeShift : xBindingShift); + let y = i * cellHeight + yBottomShift; + let spineMarkTop = [j * cellWidth, (i + 1) * cellHeight - yTopShift]; + let spineMarkBottom = [(j + 1) * cellWidth, i * cellHeight + yBottomShift]; + + if (col == -180) { + // upside-down page + isLeftPage = j % 2 == 1; //page on 'left' (right side on screen) + y = (i + 1) * cellHeight - yBottomShift; + x = (j + 1) * cellWidth - (isLeftPage ? xForeEdgeShift : xBindingShift); + spineMarkTop = [(j + 1) * cellWidth, (i + 1) * cellHeight]; + spineMarkBottom = [(j + 1) * cellWidth, i * cellHeight]; + } else if (col == 90) { + // 'top' of page is on left, right side of screen + isLeftPage = i % 2 == 0; // page is on 'left' (top side of screen) + x = (1 + j) * cellHeight - yBottomShift; + y = i * cellWidth + (isLeftPage ? xBindingShift : xForeEdgeShift); + spineMarkTop = [(1 + j) * cellHeight, i * cellWidth]; + spineMarkBottom = [j * cellHeight, i * cellWidth]; + } else if (col == -90) { + // 'top' of page is on the right, left sight of screen + isLeftPage = i % 2 == 1; // page is on 'left' (bottom side of screen) + x = j * cellHeight + yBottomShift; + y = (1 + i) * cellWidth - (isLeftPage ? xForeEdgeShift : xBindingShift); + spineMarkTop = [(j + 1) * cellHeight - yTopShift, (isLeftPage ? i : i + 1) * cellWidth]; + spineMarkBottom = [j * cellHeight + yBottomShift, (isLeftPage ? i : i + 1) * cellWidth]; + } + + console.log( + `>> (${i},${j})[${col}] : [${x},${y}] :: [xForeEdgeShift: ${xForeEdgeShift}][xBindingShift: ${xBindingShift}]` + ); + positions.push({ + rotation: col, + sx: pdfScale[0], + sy: pdfScale[1], + x: x, + y: y, + spineMarkTop: spineMarkTop, + spineMarkBottom: spineMarkBottom, + isLeftPage: isLeftPage, + }); + }); + }); + console.log('And in the end of it all, (calculatelayout) we get: ', positions); + return positions; +} + +/** + * Looks at [this.cropbox] and [this.padding_pt] and [this.papersize] and [this.page_layout] and [this.page_scaling] + * in order to calculate the information needed to render a PDF page within a layout cell. It provides several functions + * in the return object that calculate the positioning and scaling needed when provided the rotation information. + * + * When calculating 'x' and 'y' values, those are relative to a laid out PDF page, not necessarily paper sheet x & y + * + * @return the object: { + * layoutCell: 2 dimensional array of the largest possible space the PDF page could take within the layout (and not overflow) + * rawPdfSize: 2 dimensional array of dimensions for the PDF (pre scaled) + * pdfSize: 2 dimensional array of dimensions for the PDF page + margins (pre scaled) + * pdfScale: 2 dimensional array of scaling factors for the raw PDF so it fits in layoutCell (w/ margins) + * padding: object containing the already scaled padding. Keys are: fore_edge, binding, top, bottom + * xForeEdgeShiftFunc: requires the page rotation, in degrees. In pts, already scaled. + * xBindingShiftFunc: requires the page rotation, in degrees. In pts, already scaled. + * xPdfWidthFunc: requires the page rotation, in degrees. In pts, already scaled. + * yPdfHeightFunc: requires the page rotation, in degrees. In pts, already scaled. + * yTopShiftFunc: requires the page rotation, in degrees. In pts, already scaled. + * yBottomShiftFunc: requires the page rotation, in degrees. In pts, already scaled. + * } + */ +export function calculateDimensions(book) { + const { cropbox, padding_pt, papersize, page_layout, page_positioning, page_scaling } = book; + + const { width, height } = cropbox; + const pageX = width + Math.max(padding_pt.binding, 0) + Math.max(padding_pt.fore_edge, 0); + const pageY = height + Math.max(padding_pt.top, 0) + Math.max(padding_pt.bottom, 0); + + // Calculate the size of each page box on the sheet + let finalX = papersize[0] / page_layout.cols; + let finalY = papersize[1] / page_layout.rows; + + // if pages are rotated a quarter-turn in this layout, we need to swap the width and height measurements + if (page_layout.landscape) { + const temp = finalX; + finalX = finalY; + finalY = temp; + } + + let sx = 1; + let sy = 1; + + // The page_scaling options are: 'lockratio', 'stretch', 'centered' + if (page_scaling == 'lockratio') { + const scale = Math.min(finalX / pageX, finalY / pageY); + sx = scale; + sy = scale; + } else if (page_scaling == 'stretch') { + sx = finalX / pageX; + sy = finalY / pageY; + } // else = centered retains 1 x 1 + + const padding = { + fore_edge: padding_pt.fore_edge * sx, + binding: padding_pt.binding * sx, + bottom: padding_pt.bottom * sy, + top: padding_pt.top * sy, + }; + + // page_positioning has 2 options: centered, binding_alinged + const positioning = page_positioning; + + const xForeEdgeShiftFunc = function () { + // amount to inset by, relative to fore edge, on left side of book + const xgap = finalX - pageX * sx; + return padding.fore_edge + (positioning == 'centered' ? xgap / 2 : xgap); + }; + const xBindingShiftFunc = function () { + // amount to inset by, relative to binding, on right side of book + const xgap = finalX - pageX * sx; + return padding.binding + (positioning == 'centered' ? xgap / 2 : 0); + }; + const yTopShiftFunc = function () { + const ygap = finalY - pageY * sy; + return padding.top + ygap / 2; + }; + const yBottomShiftFunc = function () { + const ygap = finalY - pageY * sy; + return padding.bottom + ygap / 2; + }; + const xPdfWidthFunc = function () { + return pageX * sx - padding.fore_edge - padding.binding; + }; + const yPdfHeightFunc = function () { + return pageY * sy - padding.top - padding.bottom; + }; + return { + layout: page_layout, + rawPdfSize: [width, height], + pdfScale: [sx, sy], + pdfSize: [pageX, pageY], + layoutCell: [finalX, finalY], + padding: padding, + + xForeEdgeShiftFunc: xForeEdgeShiftFunc, + xBindingShiftFunc: xBindingShiftFunc, + xPdfWidthFunc: xPdfWidthFunc, + yPdfHeightFunc: yPdfHeightFunc, + yTopShiftFunc: yTopShiftFunc, + yBottomShiftFunc: yBottomShiftFunc, + + positioning: positioning, + }; +}