diff --git a/README.md b/README.md index 5ffab95..425d666 100644 --- a/README.md +++ b/README.md @@ -85,32 +85,33 @@ const imagePrompt = getImagePrompt({ ## Print Layout -The output is a single PNG image with all 8 pages arranged in reading order: +The output is a single PNG image arranged for traditional mini-zine folding: ``` -┌─────────────┬─────────────┐ -│ Page 1 │ Page 2 │ Row 1 -├─────────────┼─────────────┤ -│ Page 3 │ Page 4 │ Row 2 -├─────────────┼─────────────┤ -│ Page 5 │ Page 6 │ Row 3 -├─────────────┼─────────────┤ -│ Page 7 │ Page 8 │ Row 4 -└─────────────┴─────────────┘ +┌──────────┬──────────┬──────────┬──────────┐ +│ 1↺ │ 8↺ │ 7↺ │ 6↺ │ Top row (upside down) +│ (cover) │ (cta) │ │ │ +├──────────┼──────────┼──────────┼──────────┤ +│ 2 │ 3 │ 4 │ 5 │ Bottom row (right side up) +│ │ │ │ │ +└──────────┴──────────┴──────────┴──────────┘ -Panel size: 4.25" x 2.75" (~10.8cm x 7cm) -Total: 8.5" x 11" at 300 DPI (2550 x 3300 pixels) +Paper: 11" x 8.5" landscape (US Letter rotated) +Panel size: 7cm x 10.8cm (~2.76" x 4.25") +Total: 3300 x 2550 pixels at 300 DPI ``` ## Folding Instructions After printing, fold your zine: -1. **Accordion fold**: Fold the paper in half horizontally (hamburger style) -2. **Fold again**: Fold in half vertically (hotdog style) -3. **One more fold**: Fold in half again -4. **Cut center**: Unfold completely, cut a slit along the center fold -5. **Push and fold**: Push through the center to create the booklet +1. **Fold in half** along the long edge (hotdog fold) - brings top row to bottom +2. **Fold in half again** along the short edge +3. **Fold once more** to create a small booklet shape +4. **Unfold completely** and lay flat +5. **Cut the center slit** - cut along the middle crease between pages 3-6 and 4-5 +6. **Refold and push** - fold hotdog style, then push the ends together so the cut opens into a booklet +7. **Flatten** - pages should now be in order 1→2→3→4→5→6→7→8 ## Examples diff --git a/src/index.mjs b/src/index.mjs index 1ef610e..3f05892 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -30,23 +30,24 @@ export const DEFAULTS = { /** * Page dimensions in pixels at 300 DPI + * Paper is landscape orientation for traditional mini-zine folding */ export const DIMENSIONS = { letter: { - width: 2550, // 8.5" x 300 - height: 3300, // 11" x 300 - panelWidth: 1275, // width / 2 cols - panelHeight: 825, // height / 4 rows - panelCols: 2, - panelRows: 4 + width: 3300, // 11" x 300 (landscape) + height: 2550, // 8.5" x 300 + panelWidth: 825, // ~7cm (width / 4 cols) + panelHeight: 1275, // ~10.8cm (height / 2 rows) + panelCols: 4, + panelRows: 2 }, a4: { - width: 2480, // 210mm at 300 DPI - height: 3508, // 297mm at 300 DPI - panelWidth: 1240, - panelHeight: 877, - panelCols: 2, - panelRows: 4 + width: 3508, // 297mm at 300 DPI (landscape) + height: 2480, // 210mm at 300 DPI + panelWidth: 877, + panelHeight: 1240, + panelCols: 4, + panelRows: 2 } }; diff --git a/src/layout.mjs b/src/layout.mjs index 5f0dcb6..5760c1c 100644 --- a/src/layout.mjs +++ b/src/layout.mjs @@ -1,10 +1,16 @@ /** * MycroZine Layout Generator * - * Creates a print-ready layout with all 8 pages on a single 8.5" x 11" sheet. - * Layout: 2 columns x 4 rows (reading order: left-to-right, top-to-bottom) + * Creates a print-ready layout with all 8 pages on a single sheet. + * Traditional 8-page mini-zine format for fold-and-cut assembly. * - * Panel size: 4.25" x 2.75" (~10.8cm x 7cm) + * Paper: Landscape 11" x 8.5" (US Letter rotated) + * Layout: 4 columns x 2 rows + * Panel size: 7cm x 10.8cm (~2.76" x 4.25") + * + * Page arrangement for proper folding: + * Top row (upside down): 1, 8, 7, 6 + * Bottom row (right side up): 2, 3, 4, 5 */ import sharp from 'sharp'; @@ -14,21 +20,30 @@ import fs from 'fs/promises'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -// 8.5" x 11" at 300 DPI = 2550 x 3300 pixels -const PAGE_WIDTH = 2550; -const PAGE_HEIGHT = 3300; +// 11" x 8.5" at 300 DPI = 3300 x 2550 pixels (landscape) +const PAGE_WIDTH = 3300; +const PAGE_HEIGHT = 2550; -// Layout configuration: 2 columns x 4 rows -const COLS = 2; -const ROWS = 4; -const PANEL_WIDTH = Math.floor(PAGE_WIDTH / COLS); // 1275 pixels -const PANEL_HEIGHT = Math.floor(PAGE_HEIGHT / ROWS); // 825 pixels +// Layout configuration: 4 columns x 2 rows +const COLS = 4; +const ROWS = 2; + +// Panel size: 7cm x 10.8cm at 300 DPI +// 7cm = 2.756" = 827 pixels, 10.8cm = 4.252" = 1275 pixels +const PANEL_WIDTH = Math.floor(PAGE_WIDTH / COLS); // 825 pixels (~7cm) +const PANEL_HEIGHT = Math.floor(PAGE_HEIGHT / ROWS); // 1275 pixels (~10.8cm) + +// Page order for traditional mini-zine folding +// Top row (rotated 180°): pages 1, 8, 7, 6 (left to right) +// Bottom row (normal): pages 2, 3, 4, 5 (left to right) +const TOP_ROW_PAGES = [0, 7, 6, 5]; // 0-indexed: pages 1, 8, 7, 6 +const BOTTOM_ROW_PAGES = [1, 2, 3, 4]; // 0-indexed: pages 2, 3, 4, 5 /** * Create a print-ready zine layout from 8 page images * * @param {Object} options - Layout options - * @param {string[]} options.pages - Array of 8 page image paths (in order) + * @param {string[]} options.pages - Array of 8 page image paths (in order: 1-8) * @param {string} [options.outputPath] - Output file path (default: output/mycrozine_print.png) * @param {string} [options.background] - Background color (default: '#ffffff') * @returns {Promise} - Path to generated print layout @@ -47,7 +62,7 @@ export async function createPrintLayout(options) { // Ensure output directory exists await fs.mkdir(path.dirname(outputPath), { recursive: true }); - // Load and resize all pages to panel size + // Load and resize all pages to panel size (7cm x 10.8cm) const resizedPages = await Promise.all( pages.map(async (pagePath) => { return sharp(pagePath) @@ -59,17 +74,35 @@ export async function createPrintLayout(options) { }) ); - // Build composite array for all 8 pages in reading order + // Rotate top row pages 180 degrees (they need to be upside down) + const rotatedTopPages = await Promise.all( + TOP_ROW_PAGES.map(async (pageIndex) => { + return sharp(resizedPages[pageIndex]) + .rotate(180) + .toBuffer(); + }) + ); + + // Build composite array with proper page arrangement const compositeImages = []; - for (let row = 0; row < ROWS; row++) { - for (let col = 0; col < COLS; col++) { - const pageIndex = row * COLS + col; // 0-7 - compositeImages.push({ - input: resizedPages[pageIndex], - left: col * PANEL_WIDTH, - top: row * PANEL_HEIGHT - }); - } + + // Top row: pages 1, 8, 7, 6 (rotated 180°) + for (let col = 0; col < COLS; col++) { + compositeImages.push({ + input: rotatedTopPages[col], + left: col * PANEL_WIDTH, + top: 0 + }); + } + + // Bottom row: pages 2, 3, 4, 5 (normal orientation) + for (let col = 0; col < COLS; col++) { + const pageIndex = BOTTOM_ROW_PAGES[col]; + compositeImages.push({ + input: resizedPages[pageIndex], + left: col * PANEL_WIDTH, + top: PANEL_HEIGHT + }); } // Create the final composite image @@ -86,8 +119,9 @@ export async function createPrintLayout(options) { .toFile(outputPath); console.log(`Created print layout: ${outputPath}`); - console.log(` Dimensions: ${PAGE_WIDTH}x${PAGE_HEIGHT} pixels (8.5"x11" @ 300 DPI)`); - console.log(` Panel size: ${PANEL_WIDTH}x${PANEL_HEIGHT} pixels (${COLS}x${ROWS} grid)`); + console.log(` Dimensions: ${PAGE_WIDTH}x${PAGE_HEIGHT} pixels (11"x8.5" landscape @ 300 DPI)`); + console.log(` Panel size: ${PANEL_WIDTH}x${PANEL_HEIGHT} pixels (7cm x 10.8cm)`); + console.log(` Layout: Top row [1↺, 8↺, 7↺, 6↺] | Bottom row [2, 3, 4, 5]`); return outputPath; }