@prodbirdy/mockup-generator
Version:
Serverless-optimized TypeScript SDK for generating high-quality product mockups from PSD templates
168 lines (147 loc) • 5.11 kB
text/typescript
import type { Psd } from "ag-psd";
import type { Guide, GuideConstraints } from "../constants";
import { printInfo, printWarning } from "./formatting";
/**
* Extract guides from PSD image resources
*/
export function extractGuides(psd: Psd): Guide[] {
const guides: Guide[] = [];
if (psd.imageResources?.gridAndGuidesInformation?.guides) {
return psd.imageResources.gridAndGuidesInformation.guides;
}
return guides;
}
/**
* Find constraint area defined by guides within smart object bounds
* Assumes guides define a rectangular constraint area
*/
export function findGuideConstraints(
guides: Guide[],
smartObjectBounds: {
left: number;
top: number;
right: number;
bottom: number;
},
verbose: boolean = false
): GuideConstraints | null {
if (guides.length === 0) {
if (verbose) printWarning("No guides found in PSD");
return null;
}
// Convert smart object bounds to absolute coordinates if needed
const absoluteBounds = {
left: smartObjectBounds.left,
top: smartObjectBounds.top,
right: smartObjectBounds.right,
bottom: smartObjectBounds.bottom,
};
// Find guides that intersect with smart object area
const relevantGuides = guides.filter((guide) => {
if (guide.direction === "vertical") {
return (
guide.location >= absoluteBounds.left &&
guide.location <= absoluteBounds.right
);
} else {
return (
guide.location >= absoluteBounds.top &&
guide.location <= absoluteBounds.bottom
);
}
});
if (relevantGuides.length === 0) {
if (verbose) printWarning("No guides found within smart object bounds");
return null;
}
// Extract constraint boundaries
const verticalGuides = relevantGuides
.filter((g) => g.direction === "vertical")
.map((g) => g.location)
.sort((a, b) => a - b);
const horizontalGuides = relevantGuides
.filter((g) => g.direction === "horizontal")
.map((g) => g.location)
.sort((a, b) => a - b);
const constraints: GuideConstraints = {};
// Find leftmost and rightmost vertical guides
if (verticalGuides.length >= 2) {
constraints.left =
Math.max(verticalGuides[0], absoluteBounds.left) - absoluteBounds.left;
constraints.right =
Math.min(
verticalGuides[verticalGuides.length - 1],
absoluteBounds.right
) - absoluteBounds.left;
} else if (verticalGuides.length === 1) {
// Single vertical guide - use it as center reference
const guidePos = verticalGuides[0] - absoluteBounds.left;
const width = absoluteBounds.right - absoluteBounds.left;
constraints.left = Math.max(0, guidePos - width * 0.25);
constraints.right = Math.min(width, guidePos + width * 0.25);
}
// Find topmost and bottommost horizontal guides
if (horizontalGuides.length >= 2) {
constraints.top =
Math.max(horizontalGuides[0], absoluteBounds.top) - absoluteBounds.top;
constraints.bottom =
Math.min(
horizontalGuides[horizontalGuides.length - 1],
absoluteBounds.bottom
) - absoluteBounds.top;
} else if (horizontalGuides.length === 1) {
// Single horizontal guide - use it as center reference
const guidePos = horizontalGuides[0] - absoluteBounds.top;
const height = absoluteBounds.bottom - absoluteBounds.top;
constraints.top = Math.max(0, guidePos - height * 0.25);
constraints.bottom = Math.min(height, guidePos + height * 0.25);
}
if (verbose && Object.keys(constraints).length > 0) {
printInfo(`Found guide constraints: ${JSON.stringify(constraints)}`);
}
return Object.keys(constraints).length > 0 ? constraints : null;
}
/**
* Calculate constrained dimensions and position for image placement
*/
export function calculateConstrainedPlacement(
imageWidth: number,
imageHeight: number,
constraints: GuideConstraints,
smartObjectWidth: number,
smartObjectHeight: number
): {
width: number;
height: number;
offsetX: number;
offsetY: number;
constraintWidth: number;
constraintHeight: number;
} {
// Default to full smart object if no constraints
const constraintLeft = constraints.left ?? 0;
const constraintTop = constraints.top ?? 0;
const constraintRight = constraints.right ?? smartObjectWidth;
const constraintBottom = constraints.bottom ?? smartObjectHeight;
const constraintWidth = constraintRight - constraintLeft;
const constraintHeight = constraintBottom - constraintTop;
// Calculate scale to fit within constraints
const scaleX = constraintWidth / imageWidth;
const scaleY = constraintHeight / imageHeight;
const scale = Math.min(scaleX, scaleY);
const scaledWidth = Math.round(imageWidth * scale);
const scaledHeight = Math.round(imageHeight * scale);
// Center within constraint area
const offsetX =
constraintLeft + Math.round((constraintWidth - scaledWidth) / 2);
const offsetY =
constraintTop + Math.round((constraintHeight - scaledHeight) / 2);
return {
width: scaledWidth,
height: scaledHeight,
offsetX,
offsetY,
constraintWidth,
constraintHeight,
};
}