const GRID_SIZE = 3; const TILE_COUNT = GRID_SIZE * GRID_SIZE; function normalizeQueryValue(value) { return String(value || '').trim(); } function sanitizeFileNamePart(value) { const normalized = normalizeQueryValue(value) .replace(/[\\/:*?"<>|]/g, '') .replace(/\s+/g, '-') .slice(0, 32); return normalized || 'taonier'; } function buildShareGridTileFileName(params, tileIndex) { const safeTitle = sanitizeFileNamePart(params.title || params.publicWorkCode); const safeCode = sanitizeFileNamePart(params.publicWorkCode || 'share'); const order = String(tileIndex + 1).padStart(2, '0'); return `${safeTitle}-${safeCode}-${order}.png`; } function normalizeShareGridQuery(query) { return { imageUrl: normalizeQueryValue(query && query.imageUrl), title: normalizeQueryValue(query && query.title) || '我的作品', publicWorkCode: normalizeQueryValue(query && query.publicWorkCode), }; } function buildShareGridTilePlan(imageWidth, imageHeight) { const tileWidth = Math.floor(imageWidth / GRID_SIZE); const tileHeight = Math.floor(imageHeight / GRID_SIZE); const plan = []; for (let row = 0; row < GRID_SIZE; row += 1) { for (let col = 0; col < GRID_SIZE; col += 1) { const index = row * GRID_SIZE + col; const sourceX = col * tileWidth; const sourceY = row * tileHeight; plan.push({ index, row, col, sourceX, sourceY, sourceWidth: col === GRID_SIZE - 1 ? imageWidth - sourceX : tileWidth, sourceHeight: row === GRID_SIZE - 1 ? imageHeight - sourceY : tileHeight, }); } } return plan; } module.exports = { GRID_SIZE, TILE_COUNT, buildShareGridTileFileName, buildShareGridTilePlan, normalizeShareGridQuery, };