From 607efe934f78e4d042612a1b3a39fc688eee5d61 Mon Sep 17 00:00:00 2001 From: Cocoa Date: Mon, 18 Dec 2023 23:19:28 -0500 Subject: [PATCH 1/4] Fix perfectbound and booklet modes --- .jshintrc | 4 + index.html | 5 +- src/book.js | 287 ++++++++++++++++++++++---------------------- src/booklet.js | 57 --------- src/booklet.test.js | 130 -------------------- src/constants.js | 30 +++++ src/perfectbound.js | 45 ++++--- src/signatures.js | 52 +++++++- 8 files changed, 262 insertions(+), 348 deletions(-) create mode 100644 .jshintrc delete mode 100644 src/booklet.js delete mode 100644 src/booklet.test.js diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..6cc3da4 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,4 @@ +{ + "esversion": 9 + } + \ No newline at end of file diff --git a/index.html b/index.html index 6f5553b..5366aef 100644 --- a/index.html +++ b/index.html @@ -224,9 +224,8 @@

Flyleaf

Signature Format

- - + + diff --git a/src/book.js b/src/book.js index 46b48a9..9b53e0b 100644 --- a/src/book.js +++ b/src/book.js @@ -5,10 +5,9 @@ import { PDFDocument, degrees, grayscale, rgb } from 'pdf-lib'; import { saveAs } from 'file-saver'; import { Signatures } from './signatures.js'; -import { Booklet } from './booklet.js'; import { PerfectBound } from './perfectbound.js'; import { WackyImposition } from './wacky_imposition.js'; -import { PAGE_LAYOUTS, PAGE_SIZES, TARGET_BOOK_SIZE, LINE_LEN } from './constants.js'; +import { PAGE_LAYOUTS, PAGE_SIZES, LINE_LEN } from './constants.js'; import { updatePaperSelectOptionsUnits, updateAddOrRemoveCustomPaperOption, updatePageLayoutInfo} from './utils/renderUtils.js'; import JSZip from 'jszip'; export class Book { @@ -51,7 +50,7 @@ export class Book { this.fore_edge_padding_pt = 0; // (wacky only atm) -- to track buffer space on non-binding edge this.pack_pages = true; // (wacky only atm) - to track if the white space should be distributed - this.padding_pt = {'top': 0, 'bottom': 0, 'binding': 0, 'fore_edge': 0} + this.padding_pt = {'top': 0, 'bottom': 0, 'binding': 0, 'fore_edge': 0}; } update(form) { @@ -60,7 +59,7 @@ export class Book { this.paper_rotation_90 = form.has('paper_rotation_90'); this.papersize = PAGE_SIZES[form.get('paper_size')]; if (this.paper_rotation_90) { - this.papersize = [this.papersize[1], this.papersize[0]] + this.papersize = [this.papersize[1], this.papersize[0]]; } this.source_rotation = form.get("source_rotation"); @@ -91,7 +90,7 @@ export class Book { this.page_layout = form.get('pagelayout') == null ? 'folio' : PAGE_LAYOUTS[form.get('pagelayout')]; this.per_sheet = this.page_layout.per_sheet; this.pack_pages = form.get('wacky_spacing') == 'wacky_pack'; - this.fore_edge_padding_pt = this.extractIntFromForm(form, 'fore_edge_padding_pt') + this.fore_edge_padding_pt = this.extractIntFromForm(form, 'fore_edge_padding_pt'); this.padding_pt = { 'top' : this.extractIntFromForm(form, 'top_edge_padding_pt'), @@ -99,12 +98,12 @@ export class Book { 'binding': this.extractIntFromForm(form, 'binding_edge_padding_pt'), 'fore_edge': this.extractIntFromForm(form, 'main_fore_edge_padding_pt') }; - updateAddOrRemoveCustomPaperOption() - updatePaperSelectOptionsUnits() // make sure this goes AFTER the Custom update! + updateAddOrRemoveCustomPaperOption(); + updatePaperSelectOptionsUnits(); // make sure this goes AFTER the Custom update! } extractIntFromForm(form, fieldName) { - let num = parseInt(form.get(fieldName)) + let num = parseInt(form.get(fieldName)); return (isNaN(num)) ? 0 : num; } @@ -138,7 +137,7 @@ export class Book { this.cropbox = page.getCropBox(); } } - }) + }); } @@ -171,7 +170,7 @@ export class Book { this.orderedpages.push('b'); } } - console.log("Calculated pagecount [",this.pagecount,"] and ordered pages: ", this.orderedpages) + console.log("Calculated pagecount [",this.pagecount,"] and ordered pages: ", this.orderedpages); } /** @@ -181,17 +180,17 @@ export class Book { async createpages() { this.createpagelist(); let pages; - [this.managedDoc, pages] = await this.embedPagesInNewPdf(this.currentdoc) + [this.managedDoc, pages] = await this.embedPagesInNewPdf(this.currentdoc); for (var i = 0; i < pages.length; ++i) { - var page = pages[i] + var page = pages[i]; var newPage = this.managedDoc.addPage(); var rotate90cw = this.source_rotation == '90cw' || (this.source_rotation == 'out_binding' && i % 2 == 0) - || (this.source_rotation == 'in_binding' && i % 2 == 1) + || (this.source_rotation == 'in_binding' && i % 2 == 1); var rotate90ccw = this.source_rotation == '90ccw' || (this.source_rotation == 'out_binding' && i % 2 == 1) - || (this.source_rotation == 'in_binding' && i % 2 == 0) + || (this.source_rotation == 'in_binding' && i % 2 == 0); if (this.source_rotation == 'none') { newPage.setSize(page.width, page.height); newPage.drawPage(page); @@ -220,21 +219,35 @@ export class Book { console.log("The updatedDoc doc has : ", this.managedDoc.getPages(), " vs --- ", this.managedDoc.getPageCount()); - if (this.format == 'booklet') { - this.book = new Booklet(this.orderedpages, this.duplex); - } else if (this.format == 'perfect') { - this.book = new PerfectBound(this.orderedpages, this.duplex); - } else if (this.format == 'standardsig' || this.format == 'customsig') { - this.book = new Signatures(this.orderedpages, this.duplex, this.sigsize, this.per_sheet, this.duplexrotate); - - if (this.customsig) { - this.book.setsigconfig(this.signatureconfig); - } else { - this.book.createsigconfig(); - } - this.rearrangedpages = this.book.pagelist; - } else if (this.format == 'a9_3_3_4' || this.format == 'a10_6_10s' || this.format == 'A7_2_16s' || this.format == '1_3rd' || this.format == '8_zine'|| this.format == 'a_3_6s' || this.format == 'a_4_8s') { - this.book = new WackyImposition(this.orderedpages, this.duplex, this.format, this.pack_pages) + switch(this.format) { + case 'perfect': + this.book = new PerfectBound(this.orderedpages, this.duplex, this.per_sheet, this.duplexrotate); + this.rearrangedpages = [this.book.pagelist]; + break; + case 'booklet': + // Booklets are a special case where sig size is the total book size + this.sigsize = Math.ceil(this.orderedpages.length / this.per_sheet); + /* falls through */ + case 'standardsig': + case 'customsig': + this.book = new Signatures(this.orderedpages, this.duplex, this.sigsize, this.per_sheet, this.duplexrotate); + + if (this.customsig) { + this.book.setsigconfig(this.signatureconfig); + } else { + this.book.createsigconfig(); + } + this.rearrangedpages = this.book.pagelist; + break; + case 'a9_3_3_4': + case 'a10_6_10s': + case 'A7_2_16s': + case '1_3rd': + case '8_zine': + case 'a_3_6s': + case 'a_4_8s': + this.book = new WackyImposition(this.orderedpages, this.duplex, this.format, this.pack_pages); + break; } // dracula console.log("Created pages for : ",this.book); @@ -248,7 +261,7 @@ export class Book { cropbox: this.cropbox, managedDoc: this.managedDoc, positions: this.calculatelayout() - }) + }); } /** @@ -260,39 +273,25 @@ export class Book { async createoutputfiles(isPreview) { let previewFrame = document.getElementById('pdf'); previewFrame.style.display = 'none'; - let resultPDF = null + let resultPDF = null; // 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/, ''); - this.filename = origFileName - - if (this.format == 'booklet') { - await this.createsignatures({ - pages: this.rearrangedpages, - id: 'booklet', - isDuplex: this.duplex, - fileList: this.fileList - }); - } else if (this.format == 'perfect') { - await this.createsignatures({ - pages: this.rearrangedpages, - id: 'perfect', - isDuplex: this.duplex, - fileList: this.filelist - }); - } else if (this.format == 'standardsig' || this.format == 'customsig') { - const generateAggregate = this.print_file != "signatures" - const generateSignatures = this.print_file != "aggregated" - const side1PageNumbers = new Set(this.rearrangedpages.reduce((accumulator, currentValue) => { return accumulator.concat(currentValue[0]) },[])) + this.filename = origFileName; + + if (this.format == 'perfect' || this.format == 'booklet' || this.format == 'standardsig' || this.format == 'customsig') { + const generateAggregate = this.print_file != "signatures"; + const generateSignatures = this.print_file != "aggregated"; + const side1PageNumbers = new Set(this.rearrangedpages.reduce((accumulator, currentValue) => { return accumulator.concat(currentValue[0]); },[])); const [pdf0PageNumbers, pdf1PageNumbers] = (!generateAggregate || this.duplex) ? [null, null] : [ - Array.from(Array(this.managedDoc.getPageCount()).keys()).map( p => { return (side1PageNumbers.has(p) ? p : 'b')}), - Array.from(Array(this.managedDoc.getPageCount()).keys()).map( p => { return (!side1PageNumbers.has(p) ? p : 'b')}) + Array.from(Array(this.managedDoc.getPageCount()).keys()).map( p => { return (side1PageNumbers.has(p) ? p : 'b');}), + Array.from(Array(this.managedDoc.getPageCount()).keys()).map( p => { return (!side1PageNumbers.has(p) ? p : 'b');}) ]; - const [aggregatePdf0, embeddedPages0] = (generateAggregate) ? await this.embedPagesInNewPdf(this.managedDoc, pdf0PageNumbers) : [null, null] - const [aggregatePdf1, embeddedPages1] = (generateAggregate && !this.duplex) ? await this.embedPagesInNewPdf(this.managedDoc, pdf1PageNumbers) : [null, null] + const [aggregatePdf0, embeddedPages0] = (generateAggregate) ? await this.embedPagesInNewPdf(this.managedDoc, pdf0PageNumbers) : [null, null]; + const [aggregatePdf1, embeddedPages1] = (generateAggregate && !this.duplex) ? await this.embedPagesInNewPdf(this.managedDoc, pdf1PageNumbers) : [null, null]; const forLoop = async _ => { for (let i = 0; i < this.rearrangedpages.length; i++) { let page = this.rearrangedpages[i]; @@ -305,7 +304,7 @@ export class Book { fileList: this.filelist }); } - } + }; await forLoop(); if (aggregatePdf1 != null) { @@ -321,8 +320,8 @@ export class Book { }); } var rotationMetaInfo = ((this.paper_rotation_90) ? "_paperRotated" : "") - + ((this.source_rotation == 'none') ? "" : `_${this.source_rotation}`) - this.filename = `${origFileName}${rotationMetaInfo}` + + ((this.source_rotation == 'none') ? "" : `_${this.source_rotation}`); + this.filename = `${origFileName}${rotationMetaInfo}`; resultPDF = aggregatePdf0; } else if (this.format == 'a9_3_3_4') { resultPDF = await this.buildSheets(this.filename, this.book.a9_3_3_4_builder()); @@ -343,16 +342,16 @@ export class Book { if (resultPDF != null) { const pdfDataUri = await resultPDF.saveAsBase64({ dataUri: true }); - const viewerPrefs = resultPDF.catalog.getOrCreateViewerPreferences() - viewerPrefs.setHideToolbar(false) - viewerPrefs.setHideMenubar(false) - viewerPrefs.setHideWindowUI(false) - viewerPrefs.setFitWindow(true) - viewerPrefs.setCenterWindow(true) - viewerPrefs.setDisplayDocTitle(true) + const viewerPrefs = resultPDF.catalog.getOrCreateViewerPreferences(); + viewerPrefs.setHideToolbar(false); + viewerPrefs.setHideMenubar(false); + viewerPrefs.setHideWindowUI(false); + viewerPrefs.setFitWindow(true); + viewerPrefs.setCenterWindow(true); + viewerPrefs.setDisplayDocTitle(true); previewFrame.style.width = `450px`; - let height = this.papersize[1] / this.papersize[0] * 500 + let height = this.papersize[1] / this.papersize[0] * 500; previewFrame.style.height = `${height}px`; previewFrame.style.display = ''; previewFrame.src = pdfDataUri; @@ -368,7 +367,7 @@ export class Book { * @return the aggregate file w/ all the original pages embedded, but nothing placed */ async create_base_aggregate_files() { - return aggregatePdf + return aggregatePdf; } /** @@ -383,9 +382,9 @@ export class Book { const newPdf = await PDFDocument.create(); const needsReSorting = pageNumbers != null && pageNumbers.includes("b"); if (pageNumbers == null) { - pageNumbers = Array.from(Array(sourcePdf.getPageCount()).keys()) + pageNumbers = Array.from(Array(sourcePdf.getPageCount()).keys()); } else { - pageNumbers = pageNumbers.filter( p => {return typeof p === 'number'}) + pageNumbers = pageNumbers.filter( p => {return typeof p === 'number';}); } let embeddedPages = await newPdf.embedPdf(sourcePdf, pageNumbers); // what a gnarly little hack. Letting this sit for now -- @@ -395,10 +394,10 @@ export class Book { if (needsReSorting) { embeddedPages = embeddedPages.reduce((acc, curVal, curI) => { acc[pageNumbers[curI]] = curVal; - return acc - },[]) + return acc; + },[]); } - return [newPdf, embeddedPages] + return [newPdf, embeddedPages]; } /** @@ -415,8 +414,8 @@ export class Book { * @return reference to the new PDF created */ async writepages(config) { - const printSignatures = config.outname != null - const printAggregate = config.providedPages != null && config.destPdf != null + const printSignatures = config.outname != null; + const printAggregate = config.providedPages != null && config.destPdf != null; const pagelist = config.pageList; const back = config.back; let filteredList = []; @@ -431,8 +430,8 @@ export class Book { const [outPDF, embeddedPages] = (printSignatures) ? await this.embedPagesInNewPdf(this.managedDoc, filteredList) : [null, null]; let destPdfPages = (printAggregate) ? filteredList.map( (pI) => { - return config.providedPages[pI] - }) : null + return config.providedPages[pI]; + }) : null; if (printSignatures) blankIndices.forEach(i => embeddedPages.splice(i, 0, 'b')); @@ -488,15 +487,15 @@ export class Book { } draw_block_onto_page(config) { - const block_start = config.block_start - const block_end = config.block_end - const papersize = config.papersize - const outPDF = config.outPDF - const positions = config.positions - const cropmarks = config.cropmarks - const cutmarks = config.cutmarks - const alt = config.alt - let side2flag = config.side2flag + const block_start = config.block_start; + const block_end = config.block_end; + const papersize = config.papersize; + const outPDF = config.outPDF; + const positions = config.positions; + const cropmarks = config.cropmarks; + const cutmarks = config.cutmarks; + const alt = config.alt; + let side2flag = config.side2flag; let block = config.embeddedPages.slice(block_start, block_end); let currPage = outPDF.addPage([papersize[0], papersize[1]]); @@ -514,12 +513,12 @@ export class Book { this.draw_cropmarks(currPage, side2flag); } if (cutmarks) { - this.draw_cutmarks(currPage) + this.draw_cutmarks(currPage); } if (alt) { side2flag = !side2flag; } - return side2flag + return side2flag; } draw_cropmarks(currPage, side2flag) { @@ -541,6 +540,7 @@ export class Book { }); } } + /* falls through */ case 16: if (side2flag) { if (this.duplexrotate){ @@ -551,13 +551,14 @@ export class Book { dashArray: [3, 5] });} else { currPage.drawLine({ - start: {x: this.papersize[0] * 0.5, y: this.papersize[1] *.25 }, + start: {x: this.papersize[0] * 0.5, y: this.papersize[1] * 0.25 }, end: {x: this.papersize[0], y: this.papersize[1] * 0.25 }, opacity: 0.4, dashArray: [3, 5] }); } } + /* falls through */ case 8: if (side2flag) { if (this.duplexrotate){ @@ -575,6 +576,7 @@ export class Book { }); } } + /* falls through */ case 4: if (!side2flag) { currPage.drawLine({ @@ -588,7 +590,7 @@ export class Book { } draw_cutmarks(currPage) { - let lines = [] + let lines = []; switch(this.per_sheet){ case 32: lines = [...lines, @@ -596,16 +598,19 @@ export class Book { ...this.draw_hline(this.papersize[1] * 0.25, 0, this.papersize[0]), ...this.draw_cross(this.papersize[0] * 0.5, this.papersize[1] * 0.75), ...this.draw_cross(this.papersize[0] * 0.5, this.papersize[1] * 0.25) - ] + ]; + /* falls through */ case 16: lines = [...lines, ...this.draw_vline(this.papersize[0] * 0.5, 0, this.papersize[1]), ...this.draw_cross(this.papersize[0] * 0.5, this.papersize[1] * 0.5) ]; + /* falls through */ case 8: lines = [...lines, ...this.draw_hline(this.papersize[1] * 0.5, 0, this.papersize[0]) ]; + /* falls through */ case 4: } @@ -615,15 +620,15 @@ export class Book { } draw_vline(x, ystart, yend){ - return [{start: {x: x, y: ystart}, end: {x: x, y: ystart + LINE_LEN}}, {start: {x: x, y: yend - LINE_LEN}, end: {x: x, y: yend}}] + return [{start: {x: x, y: ystart}, end: {x: x, y: ystart + LINE_LEN}}, {start: {x: x, y: yend - LINE_LEN}, end: {x: x, y: yend}}]; } draw_hline(y, xstart, xend){ - return [{start: {x: xstart, y: y}, end: {x: xstart + LINE_LEN, y: y}}, {start: {x: xend - LINE_LEN, y: y}, end: {x: xend, y: y}}] + return [{start: {x: xstart, y: y}, end: {x: xstart + LINE_LEN, y: y}}, {start: {x: xend - LINE_LEN, y: y}, end: {x: xend, y: y}}]; } draw_cross(x, y) { - return [{start: {x: x - LINE_LEN, y: y}, end: {x: x + LINE_LEN, y: y}}, {start: {x: x, y: y - LINE_LEN}, end: {x: x, y: y + LINE_LEN}}] + return [{start: {x: x - LINE_LEN, y: y}, end: {x: x + LINE_LEN, y: y}}, {start: {x: x, y: y - LINE_LEN}, end: {x: x, y: y + LINE_LEN}}]; } /** @@ -648,11 +653,11 @@ export class Book { * } */ calculate_dimensions() { - let onlyPos = function(v) { return (v > 0) ? v : 0 } - let onlyNeg = function(v) { return (v < 0) ? v : 0 } + let onlyPos = function(v) { return (v > 0) ? v : 0; }; + let onlyNeg = function(v) { return (v < 0) ? v : 0; }; // PDF + margins (positive) - let pagex = this.cropbox.width + onlyPos(this.padding_pt['binding']) + onlyPos(this.padding_pt['fore_edge']); - let pagey = this.cropbox.height + onlyPos(this.padding_pt['top']) + onlyPos(this.padding_pt['bottom']); + let pagex = this.cropbox.width + onlyPos(this.padding_pt.binding) + onlyPos(this.padding_pt.fore_edge); + let pagey = this.cropbox.height + onlyPos(this.padding_pt.top) + onlyPos(this.padding_pt.bottom); let layout = this.page_layout; @@ -682,39 +687,39 @@ export class Book { } // else = centered retains 1 x 1 let 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 + '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 - let positioning = this.page_positioning + let positioning = this.page_positioning; let xForeEdgeShiftFunc = function() { // amount to inset by, relative to fore edge, on left side of book let xgap = finalx - pagex * sx; - return padding['fore_edge'] + ((positioning == 'centered' )? xgap/2 : xgap); - } + return padding.fore_edge + ((positioning == 'centered' )? xgap/2 : xgap); + }; let xBindingShiftFunc = function() { // amount to inset by, relative to binding, on right side of book let xgap = finalx - pagex * sx; - return padding['binding'] + ((positioning == 'centered' )? xgap/2 : 0); - } + return padding.binding + ((positioning == 'centered' )? xgap/2 : 0); + }; let yTopShiftFunc = function() { let ygap = finaly - pagey * sy; - return padding['top'] + ygap/2 ; - } + return padding.top + ygap/2 ; + }; let yBottomShiftFunc = function() { let ygap = finaly - pagey * sy; - return padding['bottom'] + ygap/2 ; - } + return padding.bottom + ygap/2 ; + }; let xPdfWidthFunc = function() { - return pagex * sx - padding['fore_edge'] - padding['binding']; - } + return pagex * sx - padding.fore_edge - padding.binding; + }; let yPdfHeightFunc = function() { - return pagey * sy - padding['top'] - padding['bottom']; - } + return pagey * sy - padding.top - padding.bottom; + }; return { layout: layout, rawPdfSize: [this.cropbox.width, this.cropbox.height], @@ -731,7 +736,7 @@ export class Book { yBottomShiftFunc: yBottomShiftFunc, positioning: positioning - } + }; } /** @@ -742,10 +747,10 @@ export class Book { */ calculatelayout(alt_folio){ // vampire - let l = this.calculate_dimensions() - let cellWidth = l.layoutCell[0] - let cellHeight = l.layoutCell[1] - let positions = [] + let l = this.calculate_dimensions(); + let cellWidth = l.layoutCell[0]; + let cellHeight = l.layoutCell[1]; + let positions = []; l.layout.rotations.forEach((row, i) => { row.forEach((col, j) => { @@ -775,10 +780,10 @@ export class Book { } 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}) - }) - }) - console.log("And in the end of it all, (calculatelayout) we get: ",positions) + positions.push({rotation: col, sx: l.pdfScale[0], sy: l.pdfScale[1], x: x, y: y}); + }); + }); + console.log("And in the end of it all, (calculatelayout) we get: ",positions); return positions; } @@ -794,8 +799,8 @@ export class Book { * - fileList : list of filenames for sig filename to be added to (modifies list) */ async createsignatures(config) { - const printAggregate = config.aggregatePdfs != null - const printSignatures = config.id != null + const printAggregate = config.aggregatePdfs != null; + const printSignatures = config.id != null; const pages = config.pageIndexes; // duplex printers print both sides of the sheet, if (config.isDuplex) { @@ -838,14 +843,14 @@ export class Book { config.fileList.push(outname2); } } - console.log("After creating signatures, our filelist looks like: ",this.filelist) + console.log("After creating signatures, our filelist looks like: ",this.filelist); } saveZip() { - console.log("Saving zip... ") + console.log("Saving zip... "); return this.zip.generateAsync({ type: "blob" }) .then(blob => { - console.log(" calling saveAs on ", this.filename) + console.log(" calling saveAs on ", this.filename); saveAs(blob, this.filename + ".zip"); }); } @@ -874,12 +879,12 @@ export class Book { for (let i=0; i < sheets.length; ++i ) { let isFront = i % 2 == 0; let isFirst = i < 2; - console.log("Trying to write ", sheets[i]) + console.log("Trying to write ", sheets[i]); let targetPDF = (this.duplex || isFront) ? outPDF : outPDF_back; await this.write_single_page(targetPDF, builder.isLandscape, isFront, isFirst, sheets[i], lineMaker); } { - console.log("Trying to save to PDF ", builder.fileNameMod, " w/ packing : ", this.pack_pages) + console.log("Trying to save to PDF ", builder.fileNameMod, " w/ packing : ", this.pack_pages); let fileName = id + "_" + builder.fileNameMod + ( this.duplex ? '' : '_fronts') +'.pdf'; await outPDF.save().then(pdfBytes => { console.log("Calling zip.file on ", fileName); @@ -888,7 +893,7 @@ export class Book { this.filelist.push(fileName); } if (!this.duplex) { - console.log("Trying to save to PDF (back pages)") + console.log("Trying to save to PDF (back pages)"); let fileName = id + "_" + builder.fileNameMod + '_backs.pdf'; await outPDF_back.save().then(pdfBytes => { console.log("Calling zip.file on ", fileName); @@ -897,7 +902,7 @@ export class Book { this.filelist.push(fileName); } console.log("buildSheets complete"); - return outPDF + return outPDF; } /** @@ -921,20 +926,20 @@ export class Book { */ async write_single_page(outPDF, isLandscape, isFront, isFirst, pagelist, lineMaker) { let filteredList = []; - console.log(pagelist) + console.log(pagelist); pagelist = pagelist.filter( r => { // need second sheet to remain small even if there's room to expand return !isFirst || r.filter(c => {return c.isBlank == false;}).length > 0; }); - console.log(pagelist) - console.log("Hitting that write_single_page : isPacked[",this.pack_pages,"] || (front ",isFront,"/ first ",isFirst,") [",pagelist.length,",",pagelist[0].length,"]") - pagelist.forEach(row => { row.forEach( page => { if (!page.isBlank) filteredList.push(page.num) }) }); + console.log(pagelist); + console.log("Hitting that write_single_page : isPacked[",this.pack_pages,"] || (front ",isFront,"/ first ",isFirst,") [",pagelist.length,",",pagelist[0].length,"]"); + pagelist.forEach(row => { row.forEach( page => { if (!page.isBlank) filteredList.push(page.num); }); }); if (filteredList.length == 0) { console.warn("All the pages are empty! : ",pagelist); return; } let embeddedPages = await outPDF.embedPdf(this.managedDoc, filteredList); // TODO : make sure the max dimen is correct here... - let papersize = isLandscape ? [this.papersize[1], this.papersize[0]] : [this.papersize[0], this.papersize[1]] + let papersize = isLandscape ? [this.papersize[1], this.papersize[0]] : [this.papersize[0], this.papersize[1]]; let curPage = outPDF.addPage(papersize); let sourcePage = embeddedPages.slice(0, 1)[0]; let pageHeight = papersize[1] / pagelist.length; @@ -953,10 +958,10 @@ export class Book { let y = sourcePage.height * pageScale * row; for (let i=0; i < pagelist[row].length; ++i) { let x = (sourcePage.width + this.fore_edge_padding_pt) * pageScale * i + (this.fore_edge_padding_pt * (i + 1)%2); - let pageInfo = pagelist[row][i] + let pageInfo = pagelist[row][i]; if (pageInfo.isBlank) continue; - let origPage = embeddedPages[filteredList.indexOf(pageInfo.num)] + let origPage = embeddedPages[filteredList.indexOf(pageInfo.num)]; let hOffset = (this.pack_pages) ? leftGap : (1 + i - i % 2) * leftGap; let vOffset = (this.pack_pages) ? topGap : topGap + (2 * topGap * row); let positioning = { @@ -965,7 +970,7 @@ export class Book { width: printPageWidth , height: printPageHeight, rotate: pageInfo.vFlip ? degrees(180) : degrees(0) - } + }; //console.log(" [",row,",",i,"] Given page info ", pageInfo, " now embedding at ", positioning); curPage.drawPage(origPage, positioning); } @@ -976,7 +981,7 @@ export class Book { renderPageSize: [printPageWidth + printedForeEdgeGutter, printPageHeight], paperSize: papersize, isPacked: this.pack_pages - }).forEach( line => { curPage.drawLine(line)}); + }).forEach( line => { curPage.drawLine(line);}); } } diff --git a/src/booklet.js b/src/booklet.js deleted file mode 100644 index d3cd295..0000000 --- a/src/booklet.js +++ /dev/null @@ -1,57 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// 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 { BOOKLET_LAYOUTS } from './constants'; - -export class Booklet { - constructor(pages, duplex, per_sheet, duplexrotate) { - this.duplex = duplex; - - this.sigconfig = [1]; - this.pagelist = duplex ? [[]] : [[], []]; - this.sheets = 1; - this.per_sheet = per_sheet; - this.rotate = duplexrotate; - - let center = pages.length / 2; // because of zero indexing, this is actually the first page after the center fold - const pageblock = per_sheet / 2; // number of pages before and after the center fold, per sheet - const front_config = BOOKLET_LAYOUTS[this.per_sheet].front; - const back_config = this.rotate - ? BOOKLET_LAYOUTS[this.per_sheet].rotate - : BOOKLET_LAYOUTS[this.per_sheet].back; - - // The way the code works: we start with the innermost sheet of the signature (the only one with consecutive page numbers) - // We then grab the the sections of pages that come on either side and reorder according to predefined page layout - - let front_start = center - pageblock; - let front_end = center; - let back_start = center; - let back_end = center + pageblock; - - while (front_start >= 0 && back_end <= pages.length) { - let front_block = pages.slice(front_start, front_end); - let back_block = pages.slice(back_start, back_end); - - let block = [...front_block, ...back_block]; - - front_config.forEach((pnum) => { - let page = block[pnum - 1]; //page layouts are 1-indexed, not 0-index - this.pagelist[0].push(page); - }); - - const backlist = this.duplex ? 0 : 1; - - back_config.forEach((pnum) => { - let page = block[pnum - 1]; - this.pagelist[backlist].push(page); - }); - - // Update all our counters - front_start -= pageblock; - front_end -= pageblock; - back_start += pageblock; - back_end += pageblock; - } - } -} diff --git a/src/booklet.test.js b/src/booklet.test.js deleted file mode 100644 index 20c435d..0000000 --- a/src/booklet.test.js +++ /dev/null @@ -1,130 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// 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 { expect, describe, it } from 'vitest'; - -import { Booklet } from './booklet'; - -describe('Booklet model', () => { - it('returns a new booklet based on the provided params (default book values)', () => { - const testPages = []; - const testDuplex = false; - const testPerSheet = 8; - const testDuplexRotate = true; - const expected = { - duplex: false, - sigconfig: [1], - pagelist: [[], []], - sheets: 1, - per_sheet: 8, - rotate: true, - }; - const actual = new Booklet( - testPages, - testDuplex, - testPerSheet, - testDuplexRotate - ); - - expect(actual).toEqual(expected); - }); - - it('returns a new booklet based on the provided params (duplex, no rotate, 4 per sheet)', () => { - const testPages = []; - const testDuplex = true; - const testPerSheet = 4; - const testDuplexRotate = false; - const expected = { - duplex: true, - sigconfig: [1], - pagelist: [[]], - sheets: 1, - per_sheet: 4, - rotate: false, - }; - const actual = new Booklet( - testPages, - testDuplex, - testPerSheet, - testDuplexRotate - ); - - expect(actual).toEqual(expected); - }); - - it('returns a new booklet based on the provided params (now with pages)', () => { - const testPages = [ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 'b', - 'b', - 'b', - ]; - // TODO confirm this 'expected' result is indeed what is wanted - I've just copied the result of making a new model with the testPages as input, assuming it's currently working as-intended - const expected = { - duplex: true, - sigconfig: [1], - pagelist: [ - [ - 12, - 11, - 10, - 13, - 14, - 9, - 8, - 15, - 16, - 7, - 6, - 17, - 18, - 5, - 4, - 19, - 20, - 3, - 2, - 'b', - 'b', - 1, - 0, - 'b', - ], - ], - sheets: 1, - per_sheet: 4, - rotate: false, - }; - const testDuplex = true; - const testPerSheet = 4; - const testDuplexRotate = false; - const actual = new Booklet( - testPages, - testDuplex, - testPerSheet, - testDuplexRotate - ); - expect(actual).toEqual(expected); - }); -}); diff --git a/src/constants.js b/src/constants.js index 965eb61..3fe8435 100644 --- a/src/constants.js +++ b/src/constants.js @@ -147,3 +147,33 @@ export const BOOKLET_LAYOUTS = { rotate: [28, 5, 4, 29, 21, 12, 13, 20, 24, 9, 16, 17, 25, 8, 1, 32], }, }; + +export const PERFECTBOUND_LAYOUTS = { + /* + For page layouts: pages are 1-indexed for sanity reasons, and the order for the back list must be reversed + + 'front' will be the side that ends up with consecutive pagenumbers on the innermost fold, by convention. + + page numbers should be listed from left to right, top to bottom, starting in the top left. + */ + 4: { + front: [3, 2], + back: [1, 4], + rotate: [4, 1], + }, + 8: { + front: [4, 1, 7, 6], + back: [8, 5, 3, 2], + rotate: [2, 3, 5, 8], + }, + 16: { + front: [5, 10, 8, 11, 3, 16, 2, 13], + back: [1, 14, 4, 15, 7, 12, 6, 9], + rotate: [9, 6, 12, 7, 15, 4, 14, 1], + }, + 32: { + front: [8, 5, 10, 11, 27, 26, 21, 24, 32, 29, 18, 19, 3, 2, 13, 16], + back: [4, 1, 14, 15, 31, 30, 17, 20, 28, 25, 22, 23, 7, 6, 9, 12], + rotate: [12, 9, 6, 7, 23, 22, 25, 28, 20, 17, 30, 31, 15, 14, 1, 4], + }, +}; diff --git a/src/perfectbound.js b/src/perfectbound.js index 5ec47c5..022bbf0 100644 --- a/src/perfectbound.js +++ b/src/perfectbound.js @@ -1,6 +1,7 @@ // This Source Code Form is subject to the terms of the Mozilla Public // 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 { PERFECTBOUND_LAYOUTS } from './constants'; export class PerfectBound { @@ -10,26 +11,42 @@ export class PerfectBound { // and all the sheets collated into a block for gluing. // this.pagelist holds the rearranged index numbers that the // book class uses to create a finished document - constructor(pages, duplex) { + constructor(pages, duplex, per_sheet, duplexrotate) { this.duplex=duplex; + this.per_sheet = per_sheet || 4; // pages per sheet - default is 4. + this.duplexrotate = duplexrotate || false; - this.sheets = Math.ceil(pages.length/4); + this.sheets = Math.ceil(pages.length/per_sheet); this.sigconfig= new Array(this.sheets).fill('4'); + const {front, rotate, back} = PERFECTBOUND_LAYOUTS[per_sheet]; + const front_config = front; + const back_config = duplexrotate ? rotate : back; + this.pagelist = duplex ? [[]] : [[], []]; - for (let i=0; i < pages.length; i=i+4 ) { - if (duplex) { - this.pagelist[0].push(pages[i+3]); - this.pagelist[0].push(pages[i]); - this.pagelist[0].push(pages[i+1]); - this.pagelist[0].push(pages[i+2]); - } else { - this.pagelist[0].push(pages[i+3]); - this.pagelist[0].push(pages[i]); - this.pagelist[1].push(pages[i+1]); - this.pagelist[1].push(pages[i+2]) ; - } + // Pad the page list with blanks if necessary + const totalpages = this.sheets * per_sheet; + if (totalpages > pages.length) { + const diff = totalpages - pages.length; + let blanks = new Array(diff).fill('b'); + this.inputpagelist.push(...blanks); + } + + for (let i=0; i < pages.length; i=i+per_sheet ) { + const block = pages.slice(i, i + per_sheet); + + front_config.forEach((pnum) => { + let page = block[pnum - 1]; //page layouts are 1-indexed, not 0-index + this.pagelist[0].push(page); + }); + + const backlist = this.duplex ? 0 : 1; + + back_config.forEach((pnum) => { + let page = block[pnum - 1]; + this.pagelist[backlist].push(page); + }); } } diff --git a/src/signatures.js b/src/signatures.js index e363a80..fd14d6b 100644 --- a/src/signatures.js +++ b/src/signatures.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 {Booklet} from './booklet.js'; +import { BOOKLET_LAYOUTS } from './constants'; export class Signatures { @@ -78,8 +78,8 @@ export class Signatures { // Use the booklet class for each signature this.signaturepagelists.forEach(pagerange => { - let newlist = new Booklet(pagerange, this.duplex, this.per_sheet, this.duplexrotate); - newsigs.push(newlist.pagelist); + let pagelist = this.booklet(pagerange, this.duplex, this.per_sheet, this.duplexrotate); + newsigs.push(pagelist); }); this.pagelist = newsigs; @@ -115,4 +115,50 @@ export class Signatures { return result; } + + booklet(pages, duplex, per_sheet, duplexrotate) { + let pagelist = duplex ? [[]] : [[], []]; + let sheets = 1; + const {front, rotate, back} = BOOKLET_LAYOUTS[per_sheet]; + + let center = pages.length / 2; // because of zero indexing, this is actually the first page after the center fold + const pageblock = per_sheet / 2; // number of pages before and after the center fold, per sheet + const front_config = front; + const back_config = duplexrotate ? rotate : back; + + // The way the code works: we start with the innermost sheet of the signature (the only one with consecutive page numbers) + // We then grab the the sections of pages that come on either side and reorder according to predefined page layout + + let front_start = center - pageblock; + let front_end = center; + let back_start = center; + let back_end = center + pageblock; + + while (front_start >= 0 && back_end <= pages.length) { + let front_block = pages.slice(front_start, front_end); + let back_block = pages.slice(back_start, back_end); + + let block = [...front_block, ...back_block]; + + front_config.forEach((pnum) => { + let page = block[pnum - 1]; //page layouts are 1-indexed, not 0-index + pagelist[0].push(page); + }); + + const backlist = this.duplex ? 0 : 1; + + back_config.forEach((pnum) => { + let page = block[pnum - 1]; + pagelist[backlist].push(page); + }); + + // Update all our counters + front_start -= pageblock; + front_end -= pageblock; + back_start += pageblock; + back_end += pageblock; + } + + return pagelist; + } } \ No newline at end of file From 2b97534535c81cf7a4d33448e414b24f69d3ac06 Mon Sep 17 00:00:00 2001 From: Cocoa Date: Tue, 19 Dec 2023 15:36:53 -0500 Subject: [PATCH 2/4] Add tests --- src/book.js | 3 +- src/constants.test.js | 83 ++++++++++++++++++++++++++++++++++++++++ src/perfectbound.js | 2 +- src/perfectbound.test.js | 45 ++++++++++++++++++---- 4 files changed, 124 insertions(+), 9 deletions(-) create mode 100644 src/constants.test.js diff --git a/src/book.js b/src/book.js index 9b53e0b..e93b40f 100644 --- a/src/book.js +++ b/src/book.js @@ -284,6 +284,7 @@ export class Book { if (this.format == 'perfect' || this.format == 'booklet' || this.format == 'standardsig' || this.format == 'customsig') { const generateAggregate = this.print_file != "signatures"; const generateSignatures = this.print_file != "aggregated"; + const filename = (this.format == 'standardsig' || this.format == 'customsig') ? 'signatures' : this.format; const side1PageNumbers = new Set(this.rearrangedpages.reduce((accumulator, currentValue) => { return accumulator.concat(currentValue[0]); },[])); const [pdf0PageNumbers, pdf1PageNumbers] = (!generateAggregate || this.duplex) ? [null, null] : [ @@ -299,7 +300,7 @@ export class Book { embeddedPages: (generateAggregate) ? [embeddedPages0, embeddedPages1] : null, aggregatePdfs: (generateAggregate) ? [aggregatePdf0, aggregatePdf1] : null, pageIndexes: page, - id: (generateSignatures) ? `signature${i}` : null, + id: (generateSignatures) ? `${filename}${i}` : null, isDuplex: this.duplex, fileList: this.filelist }); diff --git a/src/constants.test.js b/src/constants.test.js new file mode 100644 index 0000000..ef7f354 --- /dev/null +++ b/src/constants.test.js @@ -0,0 +1,83 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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 { expect, describe, it } from 'vitest'; + +import { PERFECTBOUND_LAYOUTS, BOOKLET_LAYOUTS, PAGE_LAYOUTS } from './constants'; + +function numSort(a, b) { + return a - b; + } + + +describe('Tests of the perfectbound layout constants', () => { + Object.keys(PERFECTBOUND_LAYOUTS).forEach( (key) => { + const {front, back, rotate} = PERFECTBOUND_LAYOUTS[key]; + it(`has the same pages on the back regardless of rotation for ${key}-per-sheet layouts`, () => { + const back_sorted = back.sort(numSort); + const back_rotated = rotate.sort(numSort); + expect(back_sorted).toEqual(back_rotated); + + }); + + it(`accounts for all pages in ${key}-per-sheet layouts`, () => { + let both = front.concat(back); + both.sort(numSort); + const expected = Array.from({length: key}, (x, i) => i + 1); + expect(both).toEqual(expected); + + }); +}); + +}); + + +describe('Tests of the booklet layout constants', () => { + Object.keys(BOOKLET_LAYOUTS).forEach( (key) => { + const {front, back, rotate} = BOOKLET_LAYOUTS[key]; + it(`has the same pages on the back regardless of rotation for ${key}-per-sheet layouts`, () => { + const back_sorted = back.sort(numSort); + const back_rotated = rotate.sort(numSort); + expect(back_sorted).toEqual(back_rotated); + + }); + + it(`accounts for all pages in ${key}-per-sheet layouts`, () => { + let both = front.concat(back); + both.sort(numSort); + const expected = Array.from({length: key}, (x, i) => i + 1); + expect(both).toEqual(expected); + + }); +}); + +}); + +describe('Tests of the page layout constants', () => { + Object.keys(PAGE_LAYOUTS).forEach( (key) => { + const {rotations, rows, cols, per_sheet} = PAGE_LAYOUTS[key]; + it(`has the correct number of rows and columns ${key} layouts`, () => { + const row_cols = rows * cols + // rows and cols are per-side, while per_sheet is both sides + expect(per_sheet).toEqual(row_cols * 2); + + }); + + it(`accounts for all rows in ${key} layout rotations`, () => { + expect(rows).toEqual(rotations.length); + + }); + + it(`accounts for all columns in ${key} layout rotations`, () => { + let col_counts = []; + rotations.forEach(row => { + col_counts.push(row.length); + }); + const col_array = new Array(rows).fill(cols); + expect(col_counts).toEqual(col_array); + + }); +}); + +}); \ No newline at end of file diff --git a/src/perfectbound.js b/src/perfectbound.js index 022bbf0..c8e8261 100644 --- a/src/perfectbound.js +++ b/src/perfectbound.js @@ -17,7 +17,7 @@ export class PerfectBound { this.duplexrotate = duplexrotate || false; this.sheets = Math.ceil(pages.length/per_sheet); - this.sigconfig= new Array(this.sheets).fill('4'); + this.sigconfig=['N/A']; const {front, rotate, back} = PERFECTBOUND_LAYOUTS[per_sheet]; const front_config = front; diff --git a/src/perfectbound.test.js b/src/perfectbound.test.js index 133cf20..dd3184a 100644 --- a/src/perfectbound.test.js +++ b/src/perfectbound.test.js @@ -6,32 +6,63 @@ import { expect, describe, it } from 'vitest'; import { PerfectBound } from './perfectbound'; +const testPages = []; +const testPagesEven = Array.from({length: 32}, (x, i) => i + 1); + describe('PerfectBound model', () => { it('returns config for a perfectbound booklet based on params (default)', () => { - const testPages = []; const testDuplex = true; const expected = { duplex: true, sheets: 0, - sigconfig: [], + per_sheet: 4, + duplexrotate: true, + sigconfig: ['N/A'], pagelist: [[]], }; - const actual = new PerfectBound(testPages, testDuplex); + const actual = new PerfectBound(testPages, testDuplex, 4, true); expect(actual).toEqual(expected); }); it('returns config for a perfectbound booklet based on params (no duplex)', () => { - const testPages = []; const testDuplex = false; const expected = { duplex: false, + duplexrotate: false, sheets: 0, - sigconfig: [], + per_sheet: 4, + sigconfig: ['N/A'], pagelist: [[], []], }; - const actual = new PerfectBound(testPages, testDuplex); + const actual = new PerfectBound(testPages, testDuplex, 4, false); + expect(actual).toEqual(expected); + }); + + it('correctly orders folio pages with no duplex', () => { + const testDuplex = false; + const expected = { + duplex: false, + duplexrotate: false, + sheets: 8, + per_sheet: 4, + sigconfig: ['N/A'], + pagelist: [[3,2,7,6,11,10,15,14,19,18,23,22,27,26,31,30], [1,4,5,8,9,12,13,16,17,20,21,24,25,28,29,32]], + }; + const actual = new PerfectBound(testPagesEven, testDuplex, 4, false); expect(actual).toEqual(expected); }); - // TODO add tests with actual pages + it('correctly orders quarto pages with no duplex', () => { + const testDuplex = false; + const expected = { + duplex: false, + duplexrotate: false, + sheets: 4, + per_sheet: 8, + sigconfig: ['N/A'], + pagelist: [[4,1,7,6,12,9,15,14,20,17,23,22,28,25,31,30], [8,5,3,2,16,13,11,10,24,21,19,18,32,29,27,26]], + }; + const actual = new PerfectBound(testPagesEven, testDuplex, 8, false); + expect(actual).toEqual(expected); + }); }); From ca5219a9115c9fbcf897b03ba5ab599f2a775276 Mon Sep 17 00:00:00 2001 From: Cocoa Date: Thu, 28 Dec 2023 14:49:17 -0500 Subject: [PATCH 3/4] Re-add old version link, and update version number --- index.html | 6 +-- package-lock.json | 71 ++++++---------------------------- package.json | 5 ++- {old => public/old}/index.html | 0 {old => public/old}/preload.js | 0 vite.config.js | 10 +++-- 6 files changed, 24 insertions(+), 68 deletions(-) rename {old => public/old}/index.html (100%) rename {old => public/old}/preload.js (100%) diff --git a/index.html b/index.html index 5366aef..7a3ec73 100644 --- a/index.html +++ b/index.html @@ -20,9 +20,9 @@

Bookbinder JS

A Javascript-based app for formatting PDFs for bookbinding. For more information, feature requests, or to contribute, view the project's Github repository. - If you have issues with this version, you can try the old version instead, which only supports folio layouts. + If you have issues with this version, you can try the old version instead, which only supports folio layouts.
current version: 1.1.0
+ padding-top: 0.75em;font-variant-caps: small-caps;">current version: %PACKAGE_VERSION%
@@ -111,7 +111,7 @@

Printer

ℹ️ - NOTE: Not currently working for folios, sorry! Use the old version for now. + NOTE: Not currently working for folios, sorry! Use the old version for now.
diff --git a/package-lock.json b/package-lock.json index 5a8a40a..762a23c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "dependencies": { "file-saver": "^2.0.5", "jszip": "^3.7.1", - "pdf-lib": "^1.16.0" + "pdf-lib": "^1.16.0", + "vite-plugin-package-version": "^1.1.0" }, "devDependencies": { "vite": "^5.0.10", @@ -25,7 +26,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "android" @@ -41,7 +41,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "android" @@ -57,7 +56,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "android" @@ -73,7 +71,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -89,7 +86,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -105,7 +101,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -121,7 +116,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -137,7 +131,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "linux" @@ -153,7 +146,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -169,7 +161,6 @@ "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "linux" @@ -185,7 +176,6 @@ "cpu": [ "loong64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -201,7 +191,6 @@ "cpu": [ "mips64el" ], - "dev": true, "optional": true, "os": [ "linux" @@ -217,7 +206,6 @@ "cpu": [ "ppc64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -233,7 +221,6 @@ "cpu": [ "riscv64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -249,7 +236,6 @@ "cpu": [ "s390x" ], - "dev": true, "optional": true, "os": [ "linux" @@ -265,7 +251,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -281,7 +266,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "netbsd" @@ -297,7 +281,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "openbsd" @@ -313,7 +296,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "sunos" @@ -329,7 +311,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -345,7 +326,6 @@ "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "win32" @@ -361,7 +341,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -386,7 +365,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, "optional": true, "peer": true, "dependencies": { @@ -402,7 +380,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, "optional": true, "peer": true, "engines": { @@ -413,7 +390,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, "optional": true, "peer": true, "engines": { @@ -424,7 +400,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", - "dev": true, "optional": true, "peer": true, "dependencies": { @@ -436,7 +411,6 @@ "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true, "optional": true, "peer": true }, @@ -444,7 +418,6 @@ "version": "0.3.18", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", - "dev": true, "optional": true, "peer": true, "dependencies": { @@ -475,7 +448,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "android" @@ -488,7 +460,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "android" @@ -501,7 +472,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -514,7 +484,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -527,7 +496,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "linux" @@ -540,7 +508,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -553,7 +520,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -566,7 +532,6 @@ "cpu": [ "riscv64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -579,7 +544,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -592,7 +556,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -605,7 +568,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -618,7 +580,6 @@ "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "win32" @@ -631,7 +592,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -658,7 +618,6 @@ "version": "20.10.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", - "dev": true, "optional": true, "peer": true, "dependencies": { @@ -818,7 +777,7 @@ "version": "8.11.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", - "dev": true, + "devOptional": true, "bin": { "acorn": "bin/acorn" }, @@ -906,7 +865,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, "optional": true, "peer": true }, @@ -967,7 +925,6 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, "optional": true, "peer": true }, @@ -1121,7 +1078,6 @@ "version": "0.19.9", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.9.tgz", "integrity": "sha512-U9CHtKSy+EpPsEBa+/A2gMs/h3ylBC0H0KSqIg7tpztHerLi6nrrcoUJAkNCEPumx8yJ+Byic4BVwHgRbN0TBg==", - "dev": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -1248,7 +1204,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -1547,7 +1502,6 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, "funding": [ { "type": "github", @@ -1654,8 +1608,7 @@ "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "node_modules/pkg-types": { "version": "1.0.3", @@ -1672,7 +1625,6 @@ "version": "8.4.32", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", - "dev": true, "funding": [ { "type": "opencollective", @@ -1749,7 +1701,6 @@ "version": "4.9.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.1.tgz", "integrity": "sha512-pgPO9DWzLoW/vIhlSoDByCzcpX92bKEorbgXuZrqxByte3JFk2xSW2JEeAcyLc9Ru9pqcNNW+Ob7ntsk2oT/Xw==", - "dev": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -1837,7 +1788,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "optional": true, "peer": true, "engines": { @@ -1848,7 +1798,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -1857,7 +1806,6 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, "optional": true, "peer": true, "dependencies": { @@ -1909,7 +1857,6 @@ "version": "5.17.6", "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.6.tgz", "integrity": "sha512-V8QHcs8YuyLkLHsJO5ucyff1ykrLVsR4dNnS//L5Y3NiSXpbK1J+WMVUs67eI0KTxs9JtHhgEQpXQVHlHI92DQ==", - "dev": true, "optional": true, "peer": true, "dependencies": { @@ -2017,7 +1964,6 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, "optional": true, "peer": true }, @@ -2041,7 +1987,6 @@ "version": "5.0.10", "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.10.tgz", "integrity": "sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==", - "dev": true, "dependencies": { "esbuild": "^0.19.3", "postcss": "^8.4.32", @@ -2114,6 +2059,14 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/vite-plugin-package-version": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vite-plugin-package-version/-/vite-plugin-package-version-1.1.0.tgz", + "integrity": "sha512-TPoFZXNanzcaKCIrC3e2L/TVRkkRLB6l4RPN/S7KbG7rWfyLcCEGsnXvxn6qR7fyZwXalnnSN/I9d6pSFjHpEA==", + "peerDependencies": { + "vite": ">=2.0.0-beta.69" + } + }, "node_modules/vitest": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.0.4.tgz", diff --git a/package.json b/package.json index 97495bd..d289109 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bookbinder", - "version": "1.0.0", + "version": "1.2.0", "description": "An app to rearrange PDF pages for printing for bookbinding", "type": "module", "scripts": { @@ -18,6 +18,7 @@ "dependencies": { "file-saver": "^2.0.5", "jszip": "^3.7.1", - "pdf-lib": "^1.16.0" + "pdf-lib": "^1.16.0", + "vite-plugin-package-version": "^1.1.0" } } diff --git a/old/index.html b/public/old/index.html similarity index 100% rename from old/index.html rename to public/old/index.html diff --git a/old/preload.js b/public/old/preload.js similarity index 100% rename from old/preload.js rename to public/old/preload.js diff --git a/vite.config.js b/vite.config.js index f4daf80..5fe821a 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,8 +1,10 @@ -import { defineConfig } from 'vite' +import { defineConfig } from 'vite'; +import version from 'vite-plugin-package-version'; export default defineConfig({ base: 'https://momijizukamori.github.io/bookbinder-js/', test: { - environment: 'jsdom' - } -}) \ No newline at end of file + environment: 'jsdom', + }, + plugins: [version()] +}); \ No newline at end of file From 1c4a5d7e2a2fb1d2a70ed8b8d4e33903e8c9de43 Mon Sep 17 00:00:00 2001 From: Cocoa Date: Thu, 28 Dec 2023 15:04:51 -0500 Subject: [PATCH 4/4] Make final page count print properly for perfectbound --- src/utils/renderUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/renderUtils.js b/src/utils/renderUtils.js index 08d5e73..c4e99ee 100644 --- a/src/utils/renderUtils.js +++ b/src/utils/renderUtils.js @@ -19,7 +19,7 @@ export function renderInfoBox(book) { return const outputPages = book.book.pagelist.reduce((acc, list) => { - list.forEach((sublist) => (acc += sublist.length)); + list.forEach((sublist) => (acc += (sublist.length ? sublist.length : 1))); return acc; }, 0);