dominionator/src/dominiontext.ts

152 lines
3.2 KiB
TypeScript
Raw Normal View History

2025-01-06 12:26:18 -05:00
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),
}));
};