dominionator/src/draw.ts

262 lines
5.9 KiB
TypeScript
Raw Normal View History

import { TYPE_ACTION } from "./types.ts";
2024-12-29 23:00:38 -05:00
import { DominionCard } from "./types.ts";
2023-12-27 11:37:37 -08:00
2024-12-31 11:53:45 -05:00
const imageCache: Record<string, HTMLImageElement> = {};
export const loadImage = (
key: string,
src: string
): Promise<HTMLImageElement> => {
return new Promise((resolve) => {
if (key in imageCache && imageCache[key]) {
resolve(imageCache[key]);
}
const img = new Image();
img.onload = () => {
imageCache[key] = img;
resolve(img);
};
2025-01-04 22:32:17 -05:00
img.onerror = (e) => {
console.log("err", e);
};
2024-12-31 11:53:45 -05:00
img.src = src;
});
};
const imageList = [
{
2025-01-04 22:32:17 -05:00
key: "card-color-1",
src: "/static/assets/CardColorOne.png",
},
{
key: "card-brown",
src: "/static/assets/CardBrown.png",
},
{
key: "card-gray",
src: "/static/assets/CardGray.png",
},
{
key: "card-description-focus",
src: "/static/assets/DescriptionFocus.png",
2024-12-31 11:53:45 -05:00
},
];
2025-01-04 22:32:17 -05:00
export const loadImages = async () => {
for (const imageInfo of imageList) {
const { key, src } = imageInfo;
await loadImage(key, src);
}
};
2024-12-31 11:53:45 -05:00
export const getImage = (key: string) => {
const image = imageCache[key];
if (!image) {
throw Error(`Tried to get an invalid image ${key}`);
}
return image;
};
2025-01-04 22:32:17 -05:00
export const colorImage = (
image: HTMLImageElement,
color?: string
2025-01-04 22:32:17 -05:00
): HTMLCanvasElement => {
const canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
const context = canvas.getContext("2d")!;
context.save();
context.drawImage(image, 0, 0);
context.globalCompositeOperation = "multiply";
context.fillStyle = color ?? "white";
2025-01-04 22:32:17 -05:00
context.fillRect(0, 0, canvas.width, canvas.height);
context.globalCompositeOperation = "destination-atop"; // restore transparency
context.drawImage(image, 0, 0);
context.restore();
return canvas;
};
2024-12-29 23:00:38 -05:00
export const drawCard = (
context: CanvasRenderingContext2D,
card: DominionCard
): Promise<void> => {
2023-12-27 11:37:37 -08:00
if (card.orientation === "card") {
2024-12-29 23:00:38 -05:00
return drawStandardCard(context, card);
2023-12-27 11:37:37 -08:00
} else {
2024-12-29 23:00:38 -05:00
return drawLandscapeCard(context, card);
2023-12-27 11:37:37 -08:00
}
2024-12-29 23:00:38 -05:00
};
2023-12-27 11:37:37 -08:00
2025-01-05 23:55:22 -05:00
const wrapText = (
context: CanvasRenderingContext2D,
text: string,
w: number
) => {
return text.split("\n").flatMap((paragraph) => {
const lines: string[] = [];
let words = 0;
let remainingText = paragraph.trim().replace(/\s+/g, " ");
let oldLine = "";
let countdown = 100;
while (remainingText.length > 0) {
countdown--;
if (countdown <= 0) {
console.log("CUT SHORT");
return [];
}
words++;
const newLine = remainingText.split(" ").slice(0, words).join(" ");
const metrics = context.measureText(newLine);
if (metrics.width > w) {
words = 0;
lines.push(oldLine);
remainingText = remainingText.slice(oldLine.length).trim();
} else if (newLine.length >= remainingText.length) {
words = 0;
lines.push(newLine);
remainingText = "";
}
oldLine = newLine;
}
if (!lines.length) {
return [""];
}
return lines;
});
};
const measureText = (
context: CanvasRenderingContext2D,
text: string,
maxWidth: number | undefined,
allowWrap: boolean | undefined
) => {
const measure = context.measureText(text);
const lineHeight = measure.emHeightAscent + measure.emHeightDescent;
if (!allowWrap || !maxWidth) {
return {
width: measure.width,
height: lineHeight,
};
}
const lines = wrapText(context, text, maxWidth);
const width = Math.max(
...lines.map((line) => context.measureText(line).width)
);
const height = lines.length * lineHeight;
return { width, height };
};
const drawTextCentered = (
2025-01-05 10:12:27 -05:00
context: CanvasRenderingContext2D,
text: string,
x: number,
y: number,
options?: {
2025-01-05 23:55:22 -05:00
defaultSize?: number;
2025-01-05 10:12:27 -05:00
maxWidth?: number;
maxHeight?: number;
allowWrap?: boolean;
font?: string;
2025-01-05 23:55:22 -05:00
fontWeight?: "normal" | "bold";
2025-01-05 10:12:27 -05:00
color?: string;
}
) => {
2025-01-05 23:55:22 -05:00
const {
defaultSize = 75,
maxWidth,
maxHeight,
font = "DominionText",
fontWeight = "normal",
color,
allowWrap = false,
} = options ?? {};
context.save();
if (color) {
context.fillStyle = color;
}
let size = defaultSize;
context.font = `${fontWeight} ${size}pt ${font}`;
if (maxWidth) {
while (
measureText(context, text, maxWidth, allowWrap).width > maxWidth
) {
size -= 2;
context.font = `${fontWeight} ${size}pt ${font}`;
}
}
if (maxHeight) {
while (
measureText(context, text, maxWidth, allowWrap).height > maxHeight
) {
size -= 1;
context.font = `${fontWeight} ${size}pt ${font}`;
}
}
const measure = context.measureText(text);
const lineHeight = measure.emHeightAscent + measure.emHeightDescent;
context.textAlign = "center";
context.textBaseline = "middle";
if (allowWrap && maxWidth) {
const lines = wrapText(context, text, maxWidth);
lines.forEach((line, i) => {
context.fillText(
line,
x,
y - (lineHeight * lines.length) / 2 + lineHeight * i,
maxWidth
);
});
} else {
context.fillText(text, x, y, maxWidth);
}
context.restore();
2025-01-05 10:12:27 -05:00
};
2024-12-29 23:00:38 -05:00
const drawStandardCard = async (
context: CanvasRenderingContext2D,
card: DominionCard
): Promise<void> => {
const w = context.canvas.width;
const h = context.canvas.height;
context.save();
2025-01-04 22:32:17 -05:00
// Draw the image
// Draw the card base
const color = TYPE_ACTION.color?.value; // "#ffbc55";
2025-01-05 10:12:27 -05:00
context.drawImage(colorImage(getImage("card-color-1"), color), 0, 0);
2025-01-04 22:32:17 -05:00
context.drawImage(getImage("card-gray"), 0, 0);
context.drawImage(colorImage(getImage("card-brown"), "#ff9911"), 0, 0);
context.drawImage(getImage("card-description-focus"), 44, 1094);
// Draw the name
2025-01-05 23:55:22 -05:00
drawTextCentered(context, "Moonlit Scheme", w / 2, 220, {
maxWidth: 1100,
font: "DominionTitle",
fontWeight: "bold",
});
2025-01-04 22:32:17 -05:00
// Draw the description
2025-01-05 23:55:22 -05:00
drawTextCentered(
context,
"You may play an Action card from your hand.",
w / 2,
1520,
{
maxWidth: 1100,
font: "DominionText",
allowWrap: true,
defaultSize: 60,
}
);
2025-01-04 22:32:17 -05:00
// Draw the types
// Draw the cost
// Draw the preview
// Draw the icon
// Draw the credit
2024-12-29 23:00:38 -05:00
context.restore();
};
2023-12-27 11:37:37 -08:00
2024-12-29 23:00:38 -05:00
const drawLandscapeCard = async (
context: CanvasRenderingContext2D,
card: DominionCard
): Promise<void> => {
2023-12-27 11:37:37 -08:00
// TODO: everything
2024-12-29 23:00:38 -05:00
};