diff --git a/src/dominiontext.ts b/src/dominiontext.ts new file mode 100644 index 0000000..2836cc2 --- /dev/null +++ b/src/dominiontext.ts @@ -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 | Promise; + +type PieceMeasure = { + type: "content" | "space" | "break"; + width: number; + ascent: number; + descent: number; +}; + +type PieceDef = { + type: T; + measure( + context: CanvasRenderingContext2D, + piece: Piece & { type: T } + ): PromiseOr; + render( + context: CanvasRenderingContext2D, + piece: Piece & { type: T }, + x: number, + y: number, + measure: NoInfer + ): PromiseOr; +}; + +const pieceDef = ( + def: PieceDef +) => { + 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), + })); +}; diff --git a/src/draw.ts b/src/draw.ts index 4fb800c..8088c0e 100644 --- a/src/draw.ts +++ b/src/draw.ts @@ -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, {