start on making a text framework
This commit is contained in:
parent
1e6e336f73
commit
8e7bcc185c
151
src/dominiontext.ts
Normal file
151
src/dominiontext.ts
Normal 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),
|
||||
}));
|
||||
};
|
@ -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,
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user