improve some drawing

This commit is contained in:
Dylan Pizzo 2025-01-05 23:55:22 -05:00
parent 3bb0308949
commit f497057dae
4 changed files with 159 additions and 12 deletions

View File

@ -1,6 +1,8 @@
import { useState } from "react";
import { sampleCard } from "../sampleData.ts"; import { sampleCard } from "../sampleData.ts";
import { Card } from "./Card.tsx"; import { Card } from "./Card.tsx";
export const App = () => { export const App = () => {
return <div><Card card={sampleCard}/></div>; const [count, setCount] = useState(0);
return <div><Card key={count} card={sampleCard}/><button onClick={() => {setCount(c => c+1)}}>Rerender (for fonts)</button></div>;
}; };

View File

@ -19,9 +19,8 @@ export const Card = (props: {card: DominionCard}) => {
if (canvasElement) { if (canvasElement) {
const context = canvasElement.getContext("2d"); const context = canvasElement.getContext("2d");
if (context) { if (context) {
console.log("loading"); await loadImages();
await loadImages() // await loadFonts();
console.log("done loading");
drawCard(context, card); drawCard(context, card);
} }
} }

View File

@ -5,8 +5,6 @@ export const loadImage = (
key: string, key: string,
src: string src: string
): Promise<HTMLImageElement> => { ): Promise<HTMLImageElement> => {
console.log("2", key);
console.log(src);
return new Promise((resolve) => { return new Promise((resolve) => {
if (key in imageCache && imageCache[key]) { if (key in imageCache && imageCache[key]) {
resolve(imageCache[key]); resolve(imageCache[key]);
@ -45,7 +43,6 @@ const imageList = [
export const loadImages = async () => { export const loadImages = async () => {
for (const imageInfo of imageList) { for (const imageInfo of imageList) {
const { key, src } = imageInfo; const { key, src } = imageInfo;
console.log(key);
await loadImage(key, src); await loadImage(key, src);
} }
}; };
@ -88,22 +85,130 @@ export const drawCard = (
} }
}; };
const drawText = ( 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 = (
context: CanvasRenderingContext2D, context: CanvasRenderingContext2D,
text: string, text: string,
x: number, x: number,
y: number, y: number,
options?: { options?: {
defaultSize?: number;
maxWidth?: number; maxWidth?: number;
maxHeight?: number; maxHeight?: number;
allowWrap?: boolean; allowWrap?: boolean;
font?: string; font?: string;
fontWeight?: "normal" | "bold";
color?: string; color?: string;
} }
) => { ) => {
const { maxWidth = undefined } = options ?? {}; const {
context.font = "bold 48px serif"; defaultSize = 75,
context.fillText(text, x, y, maxWidth); 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();
}; };
const drawStandardCard = async ( const drawStandardCard = async (
@ -121,8 +226,24 @@ const drawStandardCard = async (
context.drawImage(colorImage(getImage("card-brown"), "#ff9911"), 0, 0); context.drawImage(colorImage(getImage("card-brown"), "#ff9911"), 0, 0);
context.drawImage(getImage("card-description-focus"), 44, 1094); context.drawImage(getImage("card-description-focus"), 44, 1094);
// Draw the name // Draw the name
drawText(context, card.title, 400, 500); drawTextCentered(context, "Moonlit Scheme", w / 2, 220, {
maxWidth: 1100,
font: "DominionTitle",
fontWeight: "bold",
});
// Draw the description // Draw the description
drawTextCentered(
context,
"You may play an Action card from your hand.",
w / 2,
1520,
{
maxWidth: 1100,
font: "DominionText",
allowWrap: true,
defaultSize: 60,
}
);
// Draw the types // Draw the types
// Draw the cost // Draw the cost
// Draw the preview // Draw the preview

25
src/richtext.ts Normal file
View File

@ -0,0 +1,25 @@
// type RichnessNodeDefinition<N extends {type: string}> = {
// type: N["type"]
// measure(context: CanvasRenderingContext2D, node: N): Promise<TextMetrics>;
// render(
// context: CanvasRenderingContext2D,
// node: N,
// x: number,
// y: number
// ): Promise<void>;
// };
// type Richness<N extends {type: string}> = {[K in N["type"]]: RichnessNodeDefinition<N & {type: K}>}
// const drawRichText = <N extends {type: string}>(
// context: CanvasRenderingContext2D,
// richness: Richness<N>,
// richText: N[],
// x: number,
// y: number,
// maxWidth: number,
// ) => {
// context.save();
// const
// context.restore();
// };