start on making a text framework

This commit is contained in:
Dylan Pizzo 2025-01-06 12:26:18 -05:00
parent 1e6e336f73
commit 8e7bcc185c
2 changed files with 155 additions and 4 deletions

151
src/dominiontext.ts Normal file
View File

@ -0,0 +1,151 @@
type Piece =
| { type: "text"; text: string; isBold?: boolean; isItalic?: boolean }
| { type: "space" }
| { type: "break" }
| { type: "coin"; text: string };
type PromiseOr<T> = T | Promise<T>;
type PieceMeasure = {
type: "content" | "space" | "break";
width: number;
ascent: number;
descent: number;
};
type PieceDef<T extends Piece["type"], M extends PieceMeasure> = {
type: T;
measure(
context: CanvasRenderingContext2D,
piece: Piece & { type: T }
): PromiseOr<M>;
render(
context: CanvasRenderingContext2D,
piece: Piece & { type: T },
x: number,
y: number,
measure: NoInfer<M>
): PromiseOr<void>;
};
const pieceDef = <T extends Piece["type"], M extends PieceMeasure>(
def: PieceDef<T, M>
) => {
return def;
};
const textPiece = pieceDef({
type: "text",
measure(context, piece) {
const metrics = context.measureText(piece.text);
return {
type: "content",
width: metrics.width,
ascent: metrics.emHeightAscent,
descent: metrics.emHeightDescent,
};
},
render(context, piece, x, y) {
context.fillText(piece.text, x, y);
},
});
const spacePiece = pieceDef({
type: "space",
measure(context, _piece) {
const metrics = context.measureText(" ");
return {
type: "space",
width: metrics.width,
ascent: metrics.emHeightAscent,
descent: metrics.emHeightDescent,
};
},
render() {},
});
const breakPiece = pieceDef({
type: "break",
measure(context, _piece) {
const metrics = context.measureText(" ");
return {
type: "break",
width: 0,
ascent: metrics.emHeightAscent,
descent: metrics.emHeightDescent,
};
},
render() {},
});
const coinPiece = pieceDef({
type: "coin",
measure(context, _piece) {
const metrics = context.measureText(" ");
return {
type: "content",
width: metrics.emHeightAscent + metrics.emHeightDescent,
ascent: metrics.emHeightAscent,
descent: metrics.emHeightDescent,
};
},
render(context, piece, x, y, measure) {
context.save();
context.fillStyle = "yellow";
context.fillRect(
x,
y - measure.ascent,
measure.width,
measure.ascent + measure.descent
);
context.fillStyle = "black";
context.fillText(piece.text, x, y);
context.restore();
},
});
const pieceDefs = [textPiece, spacePiece, breakPiece, coinPiece];
const measurePiece = (context: CanvasRenderingContext2D, piece: Piece) => {
const def = pieceDefs.find((def) => def.type === piece.type)!;
return def.measure(context, piece as any);
};
const renderPiece = (
context: CanvasRenderingContext2D,
piece: Piece,
x: number,
y: number
) => {
const def = pieceDefs.find((def) => def.type === piece.type)!;
const measure = def.measure(context, piece as any);
return def.render(context, piece as any, x, y, measure as any);
};
// export const drawDominionText = (
// context: CanvasRenderingContext2D,
// text: Piece[],
// x: number,
// y: number,
// w: number,
// h: number
// ) => {};
type DominionFont = {
font: "text" | "title";
size: number;
isBold: boolean;
isItalic: boolean;
};
export const measureDominionText = (
context: CanvasRenderingContext2D,
pieces: Piece[],
font: DominionFont,
maxWidth: number
) => {
const data = pieces.map((piece) => ({
piece,
measure: measurePiece(context, piece),
}));
};

View File

@ -94,7 +94,7 @@ const wrapText = (
return text.split("\n").flatMap((paragraph) => {
const lines: string[] = [];
let words = 0;
let remainingText = paragraph.trim().replace(/\s+/g, " ");
let remainingText = paragraph.trim().replace(/ +/g, " ");
let oldLine = "";
let countdown = 100;
while (remainingText.length > 0) {
@ -131,7 +131,7 @@ const measureText = (
allowWrap: boolean | undefined
) => {
const measure = context.measureText(text);
const lineHeight = measure.emHeightAscent + measure.emHeightDescent;
const lineHeight = 1.2 * (measure.emHeightAscent + measure.emHeightDescent);
if (!allowWrap || !maxWidth) {
return {
width: measure.width,
@ -193,7 +193,7 @@ const drawTextCentered = (
}
}
const measure = context.measureText(text);
const lineHeight = measure.emHeightAscent + measure.emHeightDescent;
const lineHeight = 1.2 * (measure.emHeightAscent + measure.emHeightDescent);
context.textAlign = "center";
context.textBaseline = "middle";
if (allowWrap && maxWidth) {
@ -235,7 +235,7 @@ const drawStandardCard = async (
// Draw the description
drawTextCentered(
context,
"You may play an Action card from your hand.",
"You may play an Action card from your hand costing up to \u202f◯\u202f.",
w / 2,
1520,
{