如何用 TypeScript 計算 KDP 書脊寬度及完整封面尺寸
Computing KDP spine width and full-wrap cover size in TypeScript
TypeScript 函數可按 Amazon KDP 的印刷規則,計算平裝書及精裝書的書脊寬度和完整封面尺寸。頁數須按偶數計算,書脊寬度則根據紙張每頁厚度計算;精裝書的書脊另加 4.8 mm。平裝書每邊需加 0.125 in 出血,精裝書則採用 15 mm 包邊,並按書板尺寸調整封面。
A paperback cover for Amazon KDP is one flat PDF: the back cover, the spine and the front cover side by side, with a little extra around the edges. Its width depends on the page count and on the paper, so you can't finish the cover before the interior is final, and if the spine in the file doesn't match the book, KDP sends the cover back.
I maintain the module that holds KDP's print rules for a book tool. This post walks through the cover part of it, rewritten as small TypeScript functions with no dependencies. The numbers are KDP's. The code, the edge cases and the places where KDP's own documents disagree are what this post is about.
The shape of a paperback cover
Laid flat, a paperback cover reads left to right like this:
|<-0.125->|<---- back (trim width) ---->|<-spine->|<---- front (trim width) ---->|<-0.125->|
The 0.125 in strips are the bleed: the art runs past the edge and the printer trims it off. The same 0.125 in is added at the top and at the bottom. So for a paperback:
width = 0.125 + trimWidth + spine + trimWidth + 0.125
height = 0.125 + trimHeight + 0.125
KDP publishes its rules in inches and in millimetres. I work in inches everywhere and convert to points or pixels only at the very end.
Step 1: the page count is always even
KDP prints on both sides of every sheet, so a book always has an even number of pages. A 79-page PDF becomes an 80-page book: KDP adds a blank page. The spine is the stack of sheets, so it has to be computed from the even count:
const EPS = 1e-9;
/** KDP prints both sides of every sheet: an odd count gains one blank page. */
export function printedPages(pages: number): number {
if (!Number.isFinite(pages) || pages <= 0) return 0;
const whole = Math.ceil(pages - EPS);
return whole + (whole % 2);
}
One detail: KDP's public cover calculator uses the number exactly as you type it. Type 79 pages on cream paper and it gives a 0.198 in spine, while the printed book has 80 pages and a 0.200 in spine. The difference is small, but the even count is the one that matches the book. The simplest fix is to make the interior even before you size the cover.
Step 2: spine width = pages x paper thickness
Each paper has its own thickness per page. These are the factors KDP's cover calculator uses:
| Paper | Inches per page | Millimetres per page (converted) |
|---|---|---|
| Black & white, white paper | 0.002252 | 0.0572 |
| Black & white, cream paper | 0.0025 | 0.0635 |
| Black & white, groundwood paper | 0.00235 | 0.0597 |
| Premium color, white paper | 0.002347 | 0.0596 |
| Standard color, white paper | 0.002252 | 0.0572 |
KDP's documents don't fully agree here. The paperback submission guidelines give 0.002347 in for all "color paper", while the cover calculator and the cover help page use 0.002252 in for standard color. I follow the calculator, because it's the tool KDP gives authors for building the cover itself.
A hardcover adds the boards: 4.8 mm on top of the paper stack.
export type Binding = "paperback" | "hardcover";
export type Paper = "white" | "cream" | "groundwood" | "premiumColor" | "standardColor";
const MM_PER_IN = 25.4;
const mm = (v: number): number => v / MM_PER_IN;
/** Thickness of one printed page, in inches. */
export const SPINE_PER_PAGE_IN = {
white: 0.002252,
cream: 0.0025,
groundwood: 0.00235,
premiumColor: 0.002347,
standardColor: 0.002252,
} as const satisfies Record<Paper, number>;
/** Hardcover boards add 4.8 mm to the paper stack. */
const HARDCOVER_SPINE_EXTRA_IN = mm(4.8);
export function spineWidthIn(binding: Binding, paper: Paper, pages: number): number {
const stack = printedPages(pages) * SPINE_PER_PAGE_IN[paper];
return binding === "hardcover" ? stack + HARDCOVER_SPINE_EXTRA_IN : stack;
}
as const satisfies Record<Paper, number> does two jobs: the object keeps its literal values, and if someone adds a paper to the Paper union without a factor, the file stops compiling.
Some results:
- 200 pages on white paper: 200 x 0.002252 = 0.4504 in (11.4 mm).
- 200 pages on cream paper: 0.5 in exactly.
- The same 200 white pages as a hardcover: 0.4504 + 0.189 = 0.639 in.
Step 3: the full-wrap size
With the spine known, the cover is a sum. The paperback adds 0.125 in of bleed on every edge. The hardcover (KDP's "case laminate") is built differently: the printed sheet wraps 15 mm around a board that is 5 mm wider and 6 mm taller than the trim.
export interface Trim {
widthIn: number;
heightIn: number;
}
export interface Span {
x: number;
w: number;
}
export interface CoverSize {
widthIn: number;
heightIn: number;
spineIn: number;
/** Bleed (paperback) or wrap (hardcover) on every edge. */
edgeIn: number;
/** Horizontal spans, measured from the left edge of the PDF. */
back: Span;
spine: Span;
front: Span;
}
const PAPERBACK_BLEED_IN = 0.125;
const HARDCOVER = { wrapIn: mm(15), boardExtraWidthIn: mm(5), boardExtraHeightIn: mm(6) } as const;
export function coverSize(binding: Binding, paper: Paper, trim: Trim, pages: number): CoverSize {
const spineIn = spineWidthIn(binding, paper, pages);
const paperback = binding === "paperback";
const edgeIn = paperback ? PAPERBACK_BLEED_IN : HARDCOVER.wrapIn;
const panelW = paperback ? trim.widthIn : trim.widthIn + HARDCOVER.boardExtraWidthIn;
const panelH = paperback ? trim.heightIn : trim.heightIn + HARDCOVER.boardExtraHeightIn;
const back = { x: edgeIn, w: panelW };
const spine = { x: back.x + panelW, w: spineIn };
const front = { x: spine.x + spineIn, w: panelW };
return {
widthIn: 2 * edgeIn + 2 * panelW + spineIn,
heightIn: 2 * edgeIn + panelH,
spineIn,
edgeIn,
back,
spine,
front,
};
}
For a 6 x 9 in paperback with 200 pages on white paper:
- width = 0.125 + 6 + 0.4504 + 6 + 0.125 = 12.7004 in, height = 9.25 in;
- the front cover starts at x = 0.125 + 6 + 0.4504 = 6.5754 in;
- at 300 DPI that is a 3810 x 2775 px canvas, and in PDF points 914.43 x 666 pt.
The same book as a hardcover is 14.214 x 10.417 in: 2 x 0.5906 in of wrap, two boards of 6.197 in, a 0.639 in spine, and 9 in + 6 mm + 2 x 15 mm in height.
Two more KDP documents that disagree: the hardcover help page writes the wrap as "0.51 in (15 mm)", but 15 mm is 0.591 in, and the cover calculator uses 0.591 in. Convert from millimetres in code (mm(15)) rather than copying a rounded inch value.
The spans are what you need to draw the cover: put the back art in back, the spine text in spine, the front art in front, and extend the art over edgeIn on the outer edges. On a hardcover, also leave the 10 mm hinge next to the spine on each board empty: no text and no barcode there.
The unit helpers are one line each. Round pixels once, at the end:
const PT_PER_IN = 72;
export const toPt = (inches: number): number => inches * PT_PER_IN;
export const toPx = (inches: number, dpi = 300): number => Math.round(inches * dpi);
Don't confuse the cover with the interior pages. An interior page with bleed is (trim width + 0.125) x (trim height + 0.25) in: only one 0.125 in on the width, because there is no bleed on the gutter side. A 6 x 9 in interior with bleed is 6.125 x 9.25 in.
Step 4: when does text fit on the spine?
KDP's rule has two parts: spine text only on books with more than 79 pages (80 once the count is even), and at least 0.0625 in between the text and each fold.
That isn't always enough in practice. At 80 pages on white paper the spine is 0.180 in. Take 0.0625 in off each side and 0.055 in is left, about 4 pt, while KDP's minimum text size on a cover is 7 pt. So I add a rule of my own on top of KDP's (this part is my choice, not a KDP rule): the space left between the two clearances must hold 7 pt type.
Solving for the page count:
pages >= (2 x 0.0625 + 7/72) / thickness per page, rounded up to an even number
- white or standard color: 0.2222 / 0.002252 = 98.7, so 100 pages;
- cream: 0.2222 / 0.0025 = 88.9, so 90 pages;
- premium color (0.002347) and groundwood (0.00235): 94.7 and 94.6, so 96 pages.
/** KDP: spine text only on books with more than 79 pages, at least 0.0625 in from each fold. */
const SPINE_TEXT_MIN_PAGES = 80;
const SPINE_FOLD_CLEARANCE_IN = 0.0625;
/** KDP's smallest cover text size. Requiring it to fit the spine is my rule, not KDP's. */
const MIN_TEXT_PT = 7;
export function spineTextFits(paper: Paper, pages: number): boolean {
const p = printedPages(pages);
if (p < SPINE_TEXT_MIN_PAGES) return false;
const usable = spineWidthIn("paperback", paper, p) - 2 * SPINE_FOLD_CLEARANCE_IN;
return usable >= MIN_TEXT_PT / PT_PER_IN - EPS;
}
export function minPagesForSpineText(paper: Paper): number {
const neededIn = 2 * SPINE_FOLD_CLEARANCE_IN + MIN_TEXT_PT / PT_PER_IN;
return Math.max(SPINE_TEXT_MIN_PAGES, printedPages(neededIn / SPINE_PER_PAGE_IN[paper] - EPS));
}
These two functions cover paperbacks only. A hardcover spine always has the 4.8 mm of board in it, so it is wide enough from the shortest hardcover KDP prints.
Floating point
A few habits that kept these numbers stable:
- Keep full precision and round only for display. KDP's calculator shows three decimals (12.700 in). If you round the spine before adding it to the panels, the error ends up in the total.
-
Compare with a small epsilon at thresholds.
0.1 + 0.2 !== 0.3in JavaScript, and KDP's rules are full of "more than" and "at least". The- EPSinprintedPageskeeps a value like80.00000000000001from becoming 82. - Convert from the unit KDP chose. If KDP defines something in millimetres (the 15 mm wrap, the 4.8 mm boards), write it in millimetres and convert.
Testing
Pin a few cases that you have checked by hand in KDP's cover calculator. With node:test there is nothing to install, and a recent Node runs the .ts files directly (I ran this on Node 22.22):
import { test } from "node:test";
import assert from "node:assert/strict";
import { coverSize, minPagesForSpineText, printedPages, spineTextFits, spineWidthIn, toPx } from "./kdp-cover.ts";
const r3 = (n: number): number => Math.round(n * 1000) / 1000;
const SIX_BY_NINE = { widthIn: 6, heightIn: 9 };
test("odd page counts print as the next even number", () => {
assert.equal(printedPages(79), 80);
assert.equal(printedPages(80), 80);
});
test("spine width by paper", () => {
assert.equal(r3(spineWidthIn("paperback", "white", 200)), 0.45);
assert.equal(r3(spineWidthIn("paperback", "cream", 200)), 0.5);
assert.equal(r3(spineWidthIn("hardcover", "white", 200)), 0.639);
});
test("paperback full wrap, 6 x 9 in, 200 pages, white paper", () => {
const c = coverSize("paperback", "white", SIX_BY_NINE, 200);
assert.equal(r3(c.widthIn), 12.7);
assert.equal(c.heightIn, 9.25);
assert.deepEqual([toPx(c.widthIn), toPx(c.heightIn)], [3810, 2775]);
});
test("hardcover, 6 x 9 in, 200 pages, white paper", () => {
const c = coverSize("hardcover", "white", SIX_BY_NINE, 200);
assert.equal(r3(c.widthIn), 14.214);
assert.equal(r3(c.heightIn), 10.417);
});
test("spine text needs a wide enough spine", () => {
assert.equal(minPagesForSpineText("white"), 100);
assert.equal(minPagesForSpineText("cream"), 90);
assert.equal(spineTextFits("white", 98), false);
assert.equal(spineTextFits("white", 100), true);
});
What this code doesn't check
The functions above give sizes. They don't tell you whether KDP prints the combination at all. A few limits worth a lookup table in your own code:
- paperback page counts on trims from 5 x 8 to 8 x 10 in: 24 to 828 pages on white paper, 24 to 776 on cream, 24 to 812 on groundwood, 72 to 600 in standard color, 24 to 828 in premium color. the 8.25 in and 8.5 in wide trims and A4 have lower maximums for most papers;
- standard color isn't offered on A4 (8.27 x 11.69 in);
- hardcovers: 75 to 550 pages, on white, cream or premium color paper only. Groundwood and standard color are paperback only;
- the barcode: KDP prints a 2 x 1.2 in barcode box in the lower right of the back cover, so keep that area free.
Check your numbers
KDP's own cover calculator gives the size for one book at a time. I also keep a free KDP cover size calculator that uses these same formulas and draws a template with the guides. Disclosure: I build kdpbook.io, an independent tool for KDP authors, not affiliated with Amazon.
Sources (KDP Help):
- Create a paperback cover
- Create a hardcover cover
- Print options: ink, paper, trim sizes
- Paperback submission guidelines
This article was written with the help of AI: I used Claude to draft the text and the code samples from my own KDP rules module, then checked the code and the numbers myself.
來源:DEV Community · dev.to