mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
87c73e50fb
A new book was a cover, a spread and a back cover, so it opened straight onto pages 2 and 3. A bound book does not: the inside of the cover is blank, and the first thing facing the reader is a single right-hand page, page 1. The same at the end, where a single left-hand page sits before the back cover. Both are laid down now, empty, in a new book and in a laid-out one, and they are fixed the way the covers are: an inner spread only ever goes between them. The page numbers follow. They were counted from a spread's position in the array, which assumed the spread after the cover was the first, and three places did that sum on their own. One function now counts along the book, one page for a single leaf and two for a spread, so the sheet on screen and the label in the print view and the folio on the page all say the same number. A book with a first page starts at 1; a book from before the first page existed has none and keeps the numbers it always had.
192 lines
7.5 KiB
TypeScript
192 lines
7.5 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
||
import type { BookDocument, BookSpread } from '@trek/shared'
|
||
import { bookPageSetupSchema } from '@trek/shared'
|
||
import { MARK_LENGTH, folioLabel, foliosOf, isNumbered, sheetBox, sheetsFor } from '../../../src/components/Studio/bookSheets'
|
||
|
||
/**
|
||
* Cutting a book into sheets (#1973).
|
||
*
|
||
* The arithmetic here is what a press measures, so the tests are about
|
||
* millimetres: where the trim edge lands on the sheet, how far the marks reach,
|
||
* and which half of a spread each leaf shows. Getting the offset wrong is not a
|
||
* visible bug on screen — it is a photograph that arrives cut in the wrong
|
||
* place, in a book someone paid to have printed.
|
||
*/
|
||
|
||
const page = (over: Record<string, unknown> = {}) =>
|
||
bookPageSetupSchema.parse({ preset: 'square-210', pageWidth: 210, pageHeight: 210, bleed: 3, ...over })
|
||
|
||
const spread = (role: BookSpread['role'], id: string): BookSpread => ({
|
||
id, role, background: null, elements: [], parked: [], entryId: null,
|
||
})
|
||
|
||
function book(roles: BookSpread['role'][], pageOver: Record<string, unknown> = {}): BookDocument {
|
||
return {
|
||
version: 1,
|
||
title: 'T',
|
||
page: page(pageOver),
|
||
spreads: roles.map((r, i) => spread(r, `s${i}`)),
|
||
} as BookDocument
|
||
}
|
||
|
||
describe('the sheet box', () => {
|
||
it('is the trim plus a bleed on each side when there are no marks', () => {
|
||
const box = sheetBox(210, 297, 3, false)
|
||
expect(box.width).toBe(216)
|
||
expect(box.height).toBe(303)
|
||
expect(box.margin).toBe(3)
|
||
})
|
||
|
||
/*
|
||
* Marks go outside the bleed, not inside it. Inside means printing them onto
|
||
* the strip that gets cut off — they would be trimmed away along with the
|
||
* thing they were marking.
|
||
*/
|
||
it('makes room outside the bleed for the marks', () => {
|
||
const box = sheetBox(210, 210, 3, true)
|
||
expect(box.margin).toBe(3 + MARK_LENGTH)
|
||
expect(box.width).toBe(210 + (3 + MARK_LENGTH) * 2)
|
||
// The bleed is still the bleed — the extra room is not more of it.
|
||
expect(box.bleed).toBe(3)
|
||
})
|
||
|
||
it('is the trim itself when the book has no bleed at all', () => {
|
||
const box = sheetBox(210, 210, 0, false)
|
||
expect(box.width).toBe(210)
|
||
expect(box.margin).toBe(0)
|
||
})
|
||
})
|
||
|
||
describe('single pages', () => {
|
||
it('cuts every inner spread into two leaves and leaves the covers whole', () => {
|
||
const sheets = sheetsFor(book(['cover', 'inner', 'inner', 'back']), 'pages')
|
||
expect(sheets).toHaveLength(1 + 2 + 2 + 1)
|
||
expect(sheets.map(s => s.width)).toEqual([210, 210, 210, 210, 210, 210])
|
||
})
|
||
|
||
/*
|
||
* The cut is a window onto the spread, never a re-layout: the right-hand leaf
|
||
* is the same spread shifted by one page width. That is what makes a
|
||
* photograph crossing the gutter line up again once both halves are printed.
|
||
*/
|
||
it('shows the right-hand leaf through a window one page further in', () => {
|
||
const sheets = sheetsFor(book(['cover', 'inner']), 'pages')
|
||
const [, left, right] = sheets
|
||
expect(left.offset).toBe(0)
|
||
expect(right.offset).toBe(210)
|
||
// Both halves are the same spread, so nothing was moved to make them fit.
|
||
expect(right.spread).toBe(left.spread)
|
||
expect(right.spreadWidth).toBe(420)
|
||
})
|
||
|
||
it('keeps a cover at one page wide, with nothing shifted', () => {
|
||
const [cover] = sheetsFor(book(['cover', 'inner']), 'pages')
|
||
expect(cover.width).toBe(210)
|
||
expect(cover.spreadWidth).toBe(210)
|
||
expect(cover.offset).toBe(0)
|
||
})
|
||
|
||
/* Labels are for the print view. Covers carry no folio, in any book. */
|
||
it('numbers the leaves the way the folios do, and leaves covers unlabelled', () => {
|
||
const sheets = sheetsFor(book(['cover', 'inner', 'inner']), 'pages')
|
||
expect(sheets.map(s => s.label)).toEqual(['', '2', '3', '4', '5'])
|
||
})
|
||
|
||
it('follows startAt when the book counts from somewhere else', () => {
|
||
const sheets = sheetsFor(book(['cover', 'inner'], { pageNumbers: { startAt: 1 } }), 'pages')
|
||
expect(sheets.map(s => s.label)).toEqual(['', '1', '2'])
|
||
})
|
||
})
|
||
|
||
describe('spreads', () => {
|
||
it('keeps each spread on one sheet', () => {
|
||
const sheets = sheetsFor(book(['cover', 'inner', 'inner', 'back']), 'spreads')
|
||
expect(sheets).toHaveLength(4)
|
||
expect(sheets.map(s => s.width)).toEqual([210, 420, 420, 210])
|
||
expect(sheets.every(s => s.offset === 0)).toBe(true)
|
||
})
|
||
|
||
it('labels an inner sheet with the pair of numbers it carries', () => {
|
||
const sheets = sheetsFor(book(['cover', 'inner', 'inner']), 'spreads')
|
||
expect(sheets.map(s => s.label)).toEqual(['', '2 – 3', '4 – 5'])
|
||
})
|
||
})
|
||
|
||
describe('an empty book', () => {
|
||
it('produces no sheets rather than one blank one', () => {
|
||
expect(sheetsFor(book([]), 'pages')).toEqual([])
|
||
})
|
||
})
|
||
|
||
/*
|
||
* A bound book opens onto one right-hand page and closes on one left-hand
|
||
* page (#2317). They are single leaves like the covers, but unlike the covers
|
||
* they are numbered — and the numbers count along the book, so a book with
|
||
* them and a book from before them both read right.
|
||
*/
|
||
describe('the first and last pages', () => {
|
||
it('stay whole in both modes, like the covers', () => {
|
||
const roles: BookSpread['role'][] = ['cover', 'first', 'inner', 'last', 'back']
|
||
for (const mode of ['pages', 'spreads'] as const) {
|
||
const sheets = sheetsFor(book(roles), mode)
|
||
const firstSheet = sheets.find(s => s.spread.role === 'first')!
|
||
const lastSheet = sheets.find(s => s.spread.role === 'last')!
|
||
expect(firstSheet.single, mode).toBe(true)
|
||
expect(firstSheet.width, mode).toBe(210)
|
||
expect(lastSheet.single, mode).toBe(true)
|
||
expect(lastSheet.width, mode).toBe(210)
|
||
}
|
||
})
|
||
|
||
it('are numbered, counting from startAt along the book', () => {
|
||
const roles: BookSpread['role'][] = ['cover', 'first', 'inner', 'inner', 'last', 'back']
|
||
const sheets = sheetsFor(book(roles, { pageNumbers: { startAt: 1 } }), 'pages')
|
||
expect(sheets.map(s => s.label)).toEqual(['', '1', '2', '3', '4', '5', '6', ''])
|
||
expect(sheetsFor(book(roles, { pageNumbers: { startAt: 1 } }), 'spreads').map(s => s.label))
|
||
.toEqual(['', '1', '2 – 3', '4 – 5', '6', ''])
|
||
})
|
||
|
||
it('leave a book without them on the numbers it always had', () => {
|
||
// startAt 2, no first page: the first spread still opens on 2 – 3.
|
||
const sheets = sheetsFor(book(['cover', 'inner', 'inner', 'back']), 'spreads')
|
||
expect(sheets.map(s => s.label)).toEqual(['', '2 – 3', '4 – 5', ''])
|
||
})
|
||
})
|
||
|
||
describe('foliosOf', () => {
|
||
const roles: BookSpread['role'][] = ['cover', 'first', 'inner', 'inner', 'last', 'back']
|
||
const spreads = book(roles).spreads
|
||
|
||
it('hands a cover nothing, a single page one number and a spread two', () => {
|
||
expect(foliosOf(spreads, 0, 1)).toEqual([])
|
||
expect(foliosOf(spreads, 1, 1)).toEqual([1])
|
||
expect(foliosOf(spreads, 2, 1)).toEqual([2, 3])
|
||
expect(foliosOf(spreads, 3, 1)).toEqual([4, 5])
|
||
expect(foliosOf(spreads, 4, 1)).toEqual([6])
|
||
expect(foliosOf(spreads, 5, 1)).toEqual([])
|
||
})
|
||
|
||
it('starts wherever the book says', () => {
|
||
expect(foliosOf(spreads, 1, 3)).toEqual([3])
|
||
expect(foliosOf(spreads, 2, 3)).toEqual([4, 5])
|
||
})
|
||
|
||
it('is empty past the end of the book', () => {
|
||
expect(foliosOf(spreads, 99, 1)).toEqual([])
|
||
})
|
||
|
||
it('numbers everything inside the covers', () => {
|
||
expect(isNumbered('cover')).toBe(false)
|
||
expect(isNumbered('back')).toBe(false)
|
||
expect(isNumbered('first')).toBe(true)
|
||
expect(isNumbered('inner')).toBe(true)
|
||
expect(isNumbered('last')).toBe(true)
|
||
})
|
||
|
||
it('labels a pair with a dash, one number plainly, and nothing as nothing', () => {
|
||
expect(folioLabel([2, 3])).toBe('2 – 3')
|
||
expect(folioLabel([7])).toBe('7')
|
||
expect(folioLabel([])).toBe('')
|
||
})
|
||
})
|