From b6fe43a955758861457d9607a94a582f2c962d2f Mon Sep 17 00:00:00 2001 From: Cocoa Date: Tue, 13 Feb 2024 17:19:30 -0500 Subject: [PATCH 1/4] Refactor drawing functions into a new file --- src/book.js | 200 +++++------------------------------------ src/utils/drawing.js | 207 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 231 insertions(+), 176 deletions(-) create mode 100644 src/utils/drawing.js diff --git a/src/book.js b/src/book.js index d91c328..2e3c499 100644 --- a/src/book.js +++ b/src/book.js @@ -2,14 +2,15 @@ // 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, rgb } from 'pdf-lib'; +import { PDFDocument, degrees } from 'pdf-lib'; import { saveAs } from 'file-saver'; import { Signatures } from './signatures.js'; import { WackyImposition } from './wacky_imposition.js'; -import { PAGE_LAYOUTS, PAGE_SIZES, LINE_LEN } from './constants.js'; +import { PAGE_LAYOUTS, PAGE_SIZES } from './constants.js'; import { updatePageLayoutInfo } from './utils/renderUtils.js'; import JSZip from 'jszip'; import { loadConfiguration } from './utils/formUtils.js'; +import { drawFoldlines, drawCropmarks, drawSpineMarks } from './utils/drawing.js'; // Some JSDoc typedefs we use multiple places /** @@ -23,7 +24,6 @@ import { loadConfiguration } from './utils/formUtils.js'; /** * @typedef Position * @type {object} - * {rotation (degrees), sx, sy, x, y} * @property {number} rotation - Rotation in degrees * @property {number} sx - x scale factor (where 1.0 is 100%) * @property {number} sy - y scale factor (where 1.0 is 100%) @@ -553,201 +553,49 @@ export class Book { const papersize = config.papersize; const outPDF = config.outPDF; const positions = config.positions; - const cropmarks = config.cropmarks; + const foldmarks = config.cropmarks; const pdfEdgeMarks = config.pdfEdgeMarks; const cutmarks = config.cutmarks; const alt = config.alt; let side2flag = config.side2flag; const block = config.embeddedPages.slice(block_start, block_end); - const currPage = outPDF.addPage([papersize[0], papersize[1]]); + const currPage = outPDF.addPage(papersize); + const cropLines = cutmarks ? drawCropmarks(papersize, this.per_sheet) : []; + const foldLines = foldmarks + ? drawFoldlines(side2flag, this.duplexrotate, papersize, this.per_sheet) + : []; + const drawLines = [...cropLines, ...foldLines]; block.forEach((page, i) => { if (page == 'b' || page === undefined) { // blank page, move on. } else { - const pos = positions[i]; - const rot = pos.rotation; + const { y, x, sx, sy, rotation } = positions[i]; currPage.drawPage(page, { - y: pos.y, - x: pos.x, - xScale: pos.sx, - yScale: pos.sy, - rotate: degrees(rot), + y, + x, + xScale: sx, + yScale: sy, + rotate: degrees(rotation), }); } - }); - block.forEach((page, i) => { - if (sigDetails[i].isSigStart || sigDetails[i].isSigEnd) { - if (pdfEdgeMarks) { - this.draw_spine_marks(currPage, sigDetails[i], positions[i]); - } + + if (pdfEdgeMarks && (sigDetails[i].isSigStart || sigDetails[i].isSigEnd)) { + drawLines.push(drawSpineMarks(sigDetails[i], positions[i])); } }); - if (cropmarks) { - this.draw_cropmarks(currPage, side2flag); - } - if (cutmarks) { - this.draw_cutmarks(currPage); - } + + drawLines.forEach((line) => { + currPage.drawLine(line); + }); + if (alt) { side2flag = !side2flag; } return side2flag; } - /** - * @param curPage - PDFPage - * @param {PageInfo} sigDetails - page info object - * @param {Position} position - position info object - */ - draw_spine_marks(curPage, sigDetails, position) { - const w = 5; - let startX, startY, endX, endY; - if (sigDetails.isSigStart) { - [startX, startY] = position.spineMarkTop; - [endX, endY] = position.spineMarkTop; - } else { - [startX, startY] = position.spineMarkBottom; - [endX, endY] = position.spineMarkBottom; - } - - if (position.rotation == 0) { - startX -= w / 2; - endX += w / 2; - } else if (sigDetails.isSigStart) { - startY -= w; - endY += w; - } else { - startY -= w / 2; - endY += w / 2; - } - - const drawLineArgs = { - start: { x: startX, y: startY }, - end: { x: endX, y: endY }, - thickness: position.rotation == 0 ? 0.5 : 0.25, - color: rgb(0, 0, 0), - opacity: 1, - }; - - console.log(' --> draw this: ', drawLineArgs); - curPage.drawLine(drawLineArgs); - } - - /** - * @param curPage - PDFPage - * @param {boolean} side2flag - whether we're on the back side or not - */ - draw_cropmarks(currPage, side2flag) { - const lineSettings = { - opacity: 0.4, - dashArray: [1, 5], - }; - let start, end; - - switch (this.per_sheet) { - case 32: - if (side2flag) { - lineSettings.dashArray = [1, 5]; - if (this.duplexrotate) { - start = { x: this.papersize[0] * 0.75, y: this.papersize[1] * 0.75 }; - end = { x: this.papersize[0] * 0.75, y: this.papersize[1] * 0.5 }; - } else { - start = { x: this.papersize[0] * 0.25, y: this.papersize[1] * 0.5 }; - end = { x: this.papersize[0] * 0.25, y: this.papersize[1] * 0.25 }; - } - currPage.drawLine({ start, end, ...lineSettings }); - } - /* falls through */ - case 16: - if (side2flag) { - lineSettings.dashArray = [3, 5]; - if (this.duplexrotate) { - start = { x: 0, y: this.papersize[1] * 0.75 }; - end = { x: this.papersize[0] * 0.5, y: this.papersize[1] * 0.75 }; - } else { - start = { x: this.papersize[0] * 0.5, y: this.papersize[1] * 0.25 }; - end = { x: this.papersize[0], y: this.papersize[1] * 0.25 }; - } - currPage.drawLine({ start, end, ...lineSettings }); - } - /* falls through */ - case 8: - if (side2flag) { - lineSettings.dashArray = [5, 5]; - if (this.duplexrotate) { - start = { x: this.papersize[0] * 0.5, y: 0 }; - end = { y: this.papersize[1] * 0.5, x: this.papersize[0] * 0.5 }; - } else { - start = { x: this.papersize[0] * 0.5, y: this.papersize[1] }; - end = { y: this.papersize[1] * 0.5, x: this.papersize[0] * 0.5 }; - } - currPage.drawLine({ start, end, ...lineSettings }); - } - /* falls through */ - case 4: - if (!side2flag) { - lineSettings.dashArray = [10, 5]; - start = { x: 0, y: this.papersize[1] * 0.5 }; - end = { x: this.papersize[0], y: this.papersize[1] * 0.5 }; - currPage.drawLine({ start, end, ...lineSettings }); - } - break; - } - } - - draw_cutmarks(currPage) { - let lines = []; - switch (this.per_sheet) { - case 32: - lines = [ - ...lines, - ...this.draw_hline(this.papersize[1] * 0.75, 0, this.papersize[0]), - ...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: - } - - lines.forEach((line) => { - currPage.drawLine({ ...line, opacity: 0.4 }); - }); - } - - 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 } }, - ]; - } - - 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 } }, - ]; - } - - 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 } }, - ]; - } - /** * 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 diff --git a/src/utils/drawing.js b/src/utils/drawing.js new file mode 100644 index 0000000..c2f2670 --- /dev/null +++ b/src/utils/drawing.js @@ -0,0 +1,207 @@ +import { LINE_LEN } from '../constants'; +import { rgb } from 'pdf-lib'; + +/** + * @typedef Point + * @type {object} + * @property {number} x - horizontal position + * @property {number} y - vertical position + */ + +/** + * @typedef Line + * @type {object} + * @property {Point} start - start position + * @property {Point} end - end position + * @property {number} [opacity] - line opacity + * @property {number[]} [dashArray] - sequence of dash and gap lengths to be repeated for a dashed line + */ + +/** + * @param {boolean} side2flag - whether we're on the back or not. + * @param {boolean} duplexrotate - if alternate sides are rotated or not + * @param {number[]} papersize - paper dimensions + * @param {number} per_sheet - pages per sheet of paper + * @returns {Line[]} + */ +export function drawFoldlines(side2flag, duplexrotate, papersize, per_sheet) { + const lineSettings = { + opacity: 0.4, + dashArray: [1, 5], + }; + let x, xStart, xEnd; + let y, yStart, yEnd; + const [width, height] = papersize; + const lines = []; + + switch (per_sheet) { + case 32: + if (side2flag) { + lineSettings.dashArray = [1, 5]; + + x = duplexrotate ? width * 0.75 : width * 0.25; + yStart = height * 0.5; + yEnd = duplexrotate ? height * 0.75 : height * 0.25; + + lines.push({ ...drawVLine(x, yStart, yEnd), ...lineSettings }); + } + /* falls through */ + case 16: + if (side2flag) { + lineSettings.dashArray = [3, 5]; + + y = duplexrotate ? height * 0.75 : height * 0.25; + xStart = width * 0.5; + xEnd = duplexrotate ? 0 : height; + + lines.push({ ...drawHLine(y, xStart, xEnd), ...lineSettings }); + } + /* falls through */ + case 8: + if (side2flag) { + lineSettings.dashArray = [5, 5]; + + x = width * 0.5; + yStart = height * 0.5; + yEnd = duplexrotate ? 0 : height; + + lines.push({ ...drawVLine(x, yStart, yEnd), ...lineSettings }); + } + /* falls through */ + case 4: + if (!side2flag) { + lineSettings.dashArray = [10, 5]; + lines.push({ ...drawHLine(height * 0.5, 0, width), ...lineSettings }); + } + break; + } + return lines; +} + +/** + * @param {number[]} papersize - paper dimensions + * @param {number} per_sheet - number of pages per sheet of paper + * @returns {Line[]} + */ +export function drawCropmarks(papersize, per_sheet) { + let lines = []; + const [width, height] = papersize; + switch (per_sheet) { + case 32: + lines = [ + ...lines, + ...drawHCrop(height * 0.75, 0, width), + ...drawHCrop(height * 0.25, 0, width), + ...drawCross(width * 0.5, height * 0.75), + ...drawCross(width * 0.5, height * 0.25), + ]; + /* falls through */ + case 16: + lines = [ + ...lines, + ...drawVCrop(width * 0.5, 0, height), + ...drawCross(width * 0.5, height * 0.5), + ]; + /* falls through */ + case 8: + lines = [...lines, ...drawHCrop(height * 0.5, 0, width)]; + /* falls through */ + case 4: + } + + return lines; +} + +/** + * @param {PageInfo} sigDetails - page info object + * @param {Position} position - position info object + * @returns {Line[]} + */ +export function drawSpineMarks(sigDetails, position) { + const w = 5; + let startX, startY, endX, endY; + if (sigDetails.isSigStart) { + [startX, startY] = position.spineMarkTop; + [endX, endY] = position.spineMarkTop; + } else { + [startX, startY] = position.spineMarkBottom; + [endX, endY] = position.spineMarkBottom; + } + + if (position.rotation == 0) { + startX -= w / 2; + endX += w / 2; + } else { + startY -= w / 2; + endY += w / 2; + } + + const drawLineArgs = { + start: { x: startX, y: startY }, + end: { x: endX, y: endY }, + thickness: position.rotation == 0 ? 0.5 : 0.25, + color: rgb(0, 0, 0), + opacity: 1, + }; + + console.log(' --> draw this: ', drawLineArgs); + return drawLineArgs; +} + +/** + * @param {number} x + * @param {number} ystart + * @param {number} yend + * @returns {Line} + */ +function drawVLine(x, ystart, yend) { + return { start: { x: x, y: ystart }, end: { x: x, y: yend } }; +} + +/** + * @param {number} y + * @param {number} xstart + * @param {number} xend + * @returns {Line} + */ +function drawHLine(y, xstart, xend) { + return { start: { x: xstart, y: y }, end: { x: xend, y: y } }; +} + +/** + * @param {number} x + * @param {number} ystart + * @param {number} yend + * @returns {Line[]} + */ +function drawVCrop(x, ystart, yend) { + return [ + { start: { x: x, y: ystart }, end: { x: x, y: ystart + LINE_LEN }, opacity: 0.4 }, + { start: { x: x, y: yend - LINE_LEN }, end: { x: x, y: yend }, opacity: 0.4 }, + ]; +} + +/** + * @param {number} y + * @param {number} xstart + * @param {number} xend + * @returns {Line[]} + */ +function drawHCrop(y, xstart, xend) { + return [ + { start: { x: xstart, y: y }, end: { x: xstart + LINE_LEN, y: y }, opacity: 0.4 }, + { start: { x: xend - LINE_LEN, y: y }, end: { x: xend, y: y }, opacity: 0.4 }, + ]; +} + +/** + * @param {number} y + * @param {number} x + * @returns {Line[]} + */ +function drawCross(x, y) { + return [ + { start: { x: x - LINE_LEN, y: y }, end: { x: x + LINE_LEN, y: y }, opacity: 0.4 }, + { start: { x: x, y: y - LINE_LEN }, end: { x: x, y: y + LINE_LEN }, opacity: 0.4 }, + ]; +} From 1c0bcf46bb0b43224946db9608da36be0d8f69e4 Mon Sep 17 00:00:00 2001 From: Cocoa Date: Tue, 13 Feb 2024 20:51:27 -0500 Subject: [PATCH 2/4] Add tests for drawFoldlines --- src/utils/drawing.test.js | 88 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 src/utils/drawing.test.js diff --git a/src/utils/drawing.test.js b/src/utils/drawing.test.js new file mode 100644 index 0000000..fffb4cf --- /dev/null +++ b/src/utils/drawing.test.js @@ -0,0 +1,88 @@ +// 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 { drawFoldlines } from './drawing.js'; + +describe('tests foldline drawing', () => { + const mockPaper = [50, 100]; + const folioFoldsRotate = [ + { start: { x: 0, y: 50 }, end: { x: 50, y: 50 }, dashArray: [10, 5], opacity: 0.4 }, + ]; + const quartoFoldsRotate = [ + { start: { x: 25, y: 50 }, end: { x: 25, y: 0 }, dashArray: [5, 5], opacity: 0.4 }, + ]; + const octavoFoldsRotate = [ + { start: { x: 25, y: 75 }, end: { x: 0, y: 75 }, dashArray: [3, 5], opacity: 0.4 }, + ...quartoFoldsRotate, + ]; + const sextidecimoFoldsRotate = [ + { start: { x: 37.5, y: 50 }, end: { x: 37.5, y: 75 }, dashArray: [1, 5], opacity: 0.4 }, + ...octavoFoldsRotate, + ]; + + const folioFolds = folioFoldsRotate; + const quartoFolds = [ + { start: { x: 25, y: 50 }, end: { x: 25, y: 100 }, dashArray: [5, 5], opacity: 0.4 }, + ]; + const octavoFolds = [ + { start: { x: 25, y: 25 }, end: { x: 100, y: 25 }, dashArray: [3, 5], opacity: 0.4 }, + ...quartoFolds, + ]; + const sextidecimoFolds = [ + { start: { x: 12.5, y: 50 }, end: { x: 12.5, y: 25 }, dashArray: [1, 5], opacity: 0.4 }, + ...octavoFolds, + ]; + + const results = { + 4: { + frontRotate: folioFoldsRotate, + backRotate: [], + front: folioFolds, + back: [], + }, + 8: { + frontRotate: folioFoldsRotate, + backRotate: quartoFoldsRotate, + front: folioFolds, + back: quartoFolds, + }, + 16: { + frontRotate: folioFoldsRotate, + backRotate: octavoFoldsRotate, + front: folioFolds, + back: octavoFolds, + }, + 32: { + frontRotate: folioFoldsRotate, + backRotate: sextidecimoFoldsRotate, + front: folioFolds, + back: sextidecimoFolds, + }, + }; + Object.keys(results).forEach((key) => { + const { frontRotate, backRotate, front, back } = results[key]; + const intKey = parseInt(key, 10); + it(`produces the correct foldlines for ${key}-per-sheet fronts`, () => { + let actual = drawFoldlines(false, false, mockPaper, intKey); + expect(actual).toEqual(front); + }); + + it(`produces the correct foldlines for ${key}-per-sheet backs`, () => { + let actual = drawFoldlines(true, false, mockPaper, intKey); + expect(actual).toEqual(back); + }); + + it(`produces the correct foldlines for ${key}-per-sheet fronts with duplex rotation`, () => { + let actual = drawFoldlines(false, true, mockPaper, intKey); + expect(actual).toEqual(frontRotate); + }); + + it(`produces the correct foldlines for ${key}-per-sheet backs with duplex rotation`, () => { + const actual = drawFoldlines(true, true, mockPaper, intKey); + expect(actual).toEqual(backRotate); + }); + }); +}); From f5322f01b2b4950d8fb99f9e51b7fb459735ec9b Mon Sep 17 00:00:00 2001 From: Cocoa Date: Wed, 14 Feb 2024 12:34:41 -0500 Subject: [PATCH 3/4] Linting --- src/utils/drawing.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/drawing.test.js b/src/utils/drawing.test.js index fffb4cf..1e64a4c 100644 --- a/src/utils/drawing.test.js +++ b/src/utils/drawing.test.js @@ -66,17 +66,17 @@ describe('tests foldline drawing', () => { const { frontRotate, backRotate, front, back } = results[key]; const intKey = parseInt(key, 10); it(`produces the correct foldlines for ${key}-per-sheet fronts`, () => { - let actual = drawFoldlines(false, false, mockPaper, intKey); + const actual = drawFoldlines(false, false, mockPaper, intKey); expect(actual).toEqual(front); }); it(`produces the correct foldlines for ${key}-per-sheet backs`, () => { - let actual = drawFoldlines(true, false, mockPaper, intKey); + const actual = drawFoldlines(true, false, mockPaper, intKey); expect(actual).toEqual(back); }); it(`produces the correct foldlines for ${key}-per-sheet fronts with duplex rotation`, () => { - let actual = drawFoldlines(false, true, mockPaper, intKey); + const actual = drawFoldlines(false, true, mockPaper, intKey); expect(actual).toEqual(frontRotate); }); From c5f851ab2914078027ae44003f529fad4b116cc6 Mon Sep 17 00:00:00 2001 From: Cocoa Date: Wed, 14 Feb 2024 13:35:11 -0500 Subject: [PATCH 4/4] Add unit tests for cropmark drawing --- src/utils/drawing.test.js | 46 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/utils/drawing.test.js b/src/utils/drawing.test.js index 1e64a4c..f6e97c7 100644 --- a/src/utils/drawing.test.js +++ b/src/utils/drawing.test.js @@ -4,10 +4,12 @@ import { expect, describe, it } from 'vitest'; -import { drawFoldlines } from './drawing.js'; +import { drawFoldlines, drawCropmarks } from './drawing.js'; +import { LINE_LEN } from '../constants.js'; + +const mockPaper = [50, 100]; describe('tests foldline drawing', () => { - const mockPaper = [50, 100]; const folioFoldsRotate = [ { start: { x: 0, y: 50 }, end: { x: 50, y: 50 }, dashArray: [10, 5], opacity: 0.4 }, ]; @@ -86,3 +88,43 @@ describe('tests foldline drawing', () => { }); }); }); + +describe('tests cropmark drawing', () => { + const quartoMarks = [ + { start: { x: 0, y: 50 }, end: { x: LINE_LEN, y: 50 }, opacity: 0.4 }, + { start: { x: 50 - LINE_LEN, y: 50 }, end: { x: 50, y: 50 }, opacity: 0.4 }, + ]; + const octavoMarks = [ + { start: { x: 25, y: 0 }, end: { x: 25, y: LINE_LEN }, opacity: 0.4 }, + { start: { x: 25, y: 100 - LINE_LEN }, end: { x: 25, y: 100 }, opacity: 0.4 }, + { start: { x: 25 - LINE_LEN, y: 50 }, end: { x: 25 + LINE_LEN, y: 50 }, opacity: 0.4 }, + { start: { x: 25, y: 50 - LINE_LEN }, end: { x: 25, y: 50 + LINE_LEN }, opacity: 0.4 }, + ...quartoMarks, + ]; + const sextidecimoMarks = [ + { start: { x: 0, y: 75 }, end: { x: LINE_LEN, y: 75 }, opacity: 0.4 }, + { start: { x: 50 - LINE_LEN, y: 75 }, end: { x: 50, y: 75 }, opacity: 0.4 }, + { start: { x: 0, y: 25 }, end: { x: LINE_LEN, y: 25 }, opacity: 0.4 }, + { start: { x: 50 - LINE_LEN, y: 25 }, end: { x: 50, y: 25 }, opacity: 0.4 }, + { start: { x: 25 - LINE_LEN, y: 75 }, end: { x: 25 + LINE_LEN, y: 75 }, opacity: 0.4 }, + { start: { x: 25, y: 75 - LINE_LEN }, end: { x: 25, y: 75 + LINE_LEN }, opacity: 0.4 }, + { start: { x: 25 - LINE_LEN, y: 25 }, end: { x: 25 + LINE_LEN, y: 25 }, opacity: 0.4 }, + { start: { x: 25, y: 25 - LINE_LEN }, end: { x: 25, y: 25 + LINE_LEN }, opacity: 0.4 }, + ...octavoMarks, + ]; + + const results = { + 4: [], + 8: quartoMarks, + 16: octavoMarks, + 32: sextidecimoMarks, + }; + Object.keys(results).forEach((key) => { + const expected = results[key]; + const intKey = parseInt(key, 10); + it(`produces the correct cropmarks for ${key}-per-sheet layouts`, () => { + const actual = drawCropmarks(mockPaper, intKey); + expect(actual).toEqual(expected); + }); + }); +});