import { createMemo } from 'solid-js'; import type { DeckStore } from './deckStore'; import type { PageData, CropMarkData } from './usePDFExport'; export interface A4Size { width: number; height: number; } export interface UsePageLayoutReturn { getA4Size: () => A4Size; pages: ReturnType>; cropMarks: ReturnType>; } const A4_WIDTH_PORTRAIT = 210; const A4_HEIGHT_PORTRAIT = 297; const A4_WIDTH_LANDSCAPE = 297; const A4_HEIGHT_LANDSCAPE = 210; const PRINT_MARGIN = 5; /** * 页面布局计算 hook */ export function usePageLayout(store: DeckStore): UsePageLayoutReturn { const orientation = () => store.state.printOrientation; const oddPageOffsetX = () => store.state.printOddPageOffsetX; const oddPageOffsetY = () => store.state.printOddPageOffsetY; const getA4Size = () => { if (orientation() === 'landscape') { return { width: A4_WIDTH_LANDSCAPE, height: A4_HEIGHT_LANDSCAPE }; } return { width: A4_WIDTH_PORTRAIT, height: A4_HEIGHT_PORTRAIT }; }; const pages = createMemo(() => { const cards = store.state.cards; const cardWidth = store.state.dimensions?.cardWidth || 56; const cardHeight = store.state.dimensions?.cardHeight || 88; const { width: a4Width, height: a4Height } = getA4Size(); const usableWidth = a4Width - PRINT_MARGIN * 2; const cardsPerRow = Math.floor(usableWidth / cardWidth); const usableHeight = a4Height - PRINT_MARGIN * 2; const rowsPerPage = Math.floor(usableHeight / cardHeight); const cardsPerPage = cardsPerRow * rowsPerPage; const maxGridWidth = cardsPerRow * cardWidth; const maxGridHeight = rowsPerPage * cardHeight; const baseOffsetX = (a4Width - maxGridWidth) / 2; const baseOffsetY = (a4Height - maxGridHeight) / 2; const result: PageData[] = []; let currentPage: PageData = { pageIndex: 0, cards: [], bounds: { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity }, frameBounds: { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity } }; for (let i = 0; i < cards.length; i++) { const pageIndex = Math.floor(i / cardsPerPage); const indexInPage = i % cardsPerPage; const row = Math.floor(indexInPage / cardsPerRow); const col = indexInPage % cardsPerRow; if (pageIndex !== currentPage.pageIndex) { result.push(currentPage); currentPage = { pageIndex, cards: [], bounds: { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity }, frameBounds: { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity } }; } const isOddPage = pageIndex % 2 === 0; const pageOffsetX = isOddPage ? oddPageOffsetX() : 0; const pageOffsetY = isOddPage ? oddPageOffsetY() : 0; const cardX = baseOffsetX + col * cardWidth + pageOffsetX; const cardY = baseOffsetY + row * cardHeight + pageOffsetY; currentPage.cards.push({ data: cards[i], x: cardX, y: cardY }); currentPage.bounds.minX = Math.min(currentPage.bounds.minX, cardX); currentPage.bounds.minY = Math.min(currentPage.bounds.minY, cardY); currentPage.bounds.maxX = Math.max(currentPage.bounds.maxX, cardX + cardWidth); currentPage.bounds.maxY = Math.max(currentPage.bounds.maxY, cardY + cardHeight); } if (currentPage.cards.length > 0) { result.push(currentPage); } return result.map(page => ({ ...page, frameBounds: { minX: baseOffsetX + (page.pageIndex % 2 === 0 ? oddPageOffsetX() : 0), minY: baseOffsetY + (page.pageIndex % 2 === 0 ? oddPageOffsetY() : 0), maxX: baseOffsetX + maxGridWidth + (page.pageIndex % 2 === 0 ? oddPageOffsetX() : 0), maxY: baseOffsetY + maxGridHeight + (page.pageIndex % 2 === 0 ? oddPageOffsetY() : 0) } })); }); const cropMarks = createMemo(() => { const pagesData = pages(); return pagesData.map(page => { const { frameBounds, cards } = page; const cardWidth = store.state.dimensions?.cardWidth || 56; const cardHeight = store.state.dimensions?.cardHeight || 88; const xPositions = new Set(); const yPositions = new Set(); cards.forEach(card => { xPositions.add(card.x); xPositions.add(card.x + cardWidth); yPositions.add(card.y); yPositions.add(card.y + cardHeight); }); const sortedX = Array.from(xPositions).sort((a, b) => a - b); const sortedY = Array.from(yPositions).sort((a, b) => a - b); const OVERLAP = 3; const horizontalLines = sortedY.map(y => ({ y, xStart: frameBounds.minX - OVERLAP, xEnd: frameBounds.maxX + OVERLAP })); const verticalLines = sortedX.map(x => ({ x, yStart: frameBounds.minY - OVERLAP, yEnd: frameBounds.maxY + OVERLAP })); const frameBoundsWithMargin = { x: frameBounds.minX - 1, y: frameBounds.minY - 1, width: frameBounds.maxX - frameBounds.minX + 2, height: frameBounds.maxY - frameBounds.minY + 2 }; return { horizontalLines, verticalLines, frameBounds, frameBoundsWithMargin }; }); }); return { getA4Size, pages, cropMarks }; }