more work

This commit is contained in:
Dylan Pizzo 2025-01-06 22:13:53 -05:00
parent 4e79fd38a1
commit 0099624165
5 changed files with 96 additions and 15 deletions

View File

@ -1,4 +1,5 @@
import { getImage } from "./draw.ts"; import { getImage } from "./draw.ts";
import { parseFont, stringifyFont } from "./fonthelper.ts";
export type Piece = export type Piece =
| { type: "text"; text: string; isBold?: boolean; isItalic?: boolean } | { type: "text"; text: string; isBold?: boolean; isItalic?: boolean }
@ -18,6 +19,7 @@ type PieceMeasure = {
type Line = { type Line = {
pieces: { pieces: {
piece: Piece; piece: Piece;
measure: PieceMeasure;
xOffset: number; xOffset: number;
}[]; }[];
width: number; width: number;
@ -108,10 +110,12 @@ const breakPiece = pieceDef({
const coinPiece = pieceDef({ const coinPiece = pieceDef({
type: "coin", type: "coin",
measure(context, _piece) { measure(context, _piece) {
context.save();
const metrics = context.measureText(" "); const metrics = context.measureText(" ");
const height = const height =
metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent; metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent;
const coinImage = getImage("coin"); const coinImage = getImage("coin");
context.restore();
return { return {
type: "content", type: "content",
width: coinImage.width * (height / coinImage.height), width: coinImage.width * (height / coinImage.height),
@ -131,6 +135,12 @@ const coinPiece = pieceDef({
measure.width, measure.width,
height height
); );
const fontInfo = parseFont(context.font);
fontInfo.family = ["DominionSpecial"];
fontInfo.weight = "bold";
fontInfo.size = parseInt(fontInfo.size.toString()) * 1.2;
const font = stringifyFont(fontInfo);
context.font = font;
context.fillStyle = "black"; context.fillStyle = "black";
context.textAlign = "center"; context.textAlign = "center";
context.fillText(piece.text, x + measure.width / 2, y); context.fillText(piece.text, x + measure.width / 2, y);
@ -140,7 +150,7 @@ const coinPiece = pieceDef({
const pieceDefs = [textPiece, spacePiece, breakPiece, coinPiece]; const pieceDefs = [textPiece, spacePiece, breakPiece, coinPiece];
let tools: PieceTools = {} as any; const tools: PieceTools = {} as any;
const measurePiece = (context: CanvasRenderingContext2D, piece: Piece) => { const measurePiece = (context: CanvasRenderingContext2D, piece: Piece) => {
const def = pieceDefs.find((def) => def.type === piece.type)!; const def = pieceDefs.find((def) => def.type === piece.type)!;
@ -188,7 +198,7 @@ export const measureDominionText = async (
for (const pieceInfo of data) { for (const pieceInfo of data) {
const line = lines[lines.length - 1]!; const line = lines[lines.length - 1]!;
if (pieceInfo.measure.type === "break") { if (pieceInfo.measure.type === "break") {
line.pieces.push({ piece: pieceInfo.piece, xOffset: line.width }); line.pieces.push({ ...pieceInfo, xOffset: line.width });
line.width += pieceInfo.measure.width; line.width += pieceInfo.measure.width;
line.ascent = Math.max(line.ascent, pieceInfo.measure.ascent); line.ascent = Math.max(line.ascent, pieceInfo.measure.ascent);
line.descent = Math.max(line.descent, pieceInfo.measure.descent); line.descent = Math.max(line.descent, pieceInfo.measure.descent);
@ -196,14 +206,14 @@ export const measureDominionText = async (
} else { } else {
if (line.width + pieceInfo.measure.width > maxWidth) { if (line.width + pieceInfo.measure.width > maxWidth) {
lines.push({ lines.push({
pieces: [{ piece: pieceInfo.piece, xOffset: 0 }], pieces: [{ ...pieceInfo, xOffset: 0 }],
width: pieceInfo.measure.width, width: pieceInfo.measure.width,
ascent: pieceInfo.measure.ascent, ascent: pieceInfo.measure.ascent,
descent: pieceInfo.measure.descent, descent: pieceInfo.measure.descent,
}); });
} else { } else {
line.pieces.push({ line.pieces.push({
piece: pieceInfo.piece, ...pieceInfo,
xOffset: line.width, xOffset: line.width,
}); });
line.width += pieceInfo.measure.width; line.width += pieceInfo.measure.width;
@ -216,7 +226,15 @@ export const measureDominionText = async (
} }
} }
return { return {
lines, lines: lines.map((line) => {
while (
line.pieces[line.pieces.length - 1] &&
line.pieces[line.pieces.length - 1]!.measure.type === "space"
) {
line.pieces = line.pieces.slice(0, -1);
}
return line;
}),
width: Math.max(...lines.map((line) => line.width)), width: Math.max(...lines.map((line) => line.width)),
height: lines height: lines
.map((line) => line.ascent + line.descent) .map((line) => line.ascent + line.descent)
@ -224,6 +242,8 @@ export const measureDominionText = async (
}; };
}; };
const debug = false;
export const renderDominionText = async ( export const renderDominionText = async (
context: CanvasRenderingContext2D, context: CanvasRenderingContext2D,
pieces: Piece[], pieces: Piece[],
@ -246,6 +266,30 @@ export const renderDominionText = async (
x - line.width / 2 + xOffset, x - line.width / 2 + xOffset,
y - height / 2 + yOffset y - height / 2 + yOffset
); );
if (debug) {
context.save();
context.strokeStyle = "blue";
context.lineWidth = 5;
const pieceMeasure = await measurePiece(context, piece);
context.strokeRect(
x - line.width / 2 + xOffset,
y - height / 2 - line.ascent + yOffset,
pieceMeasure.width,
pieceMeasure.ascent + pieceMeasure.descent
);
context.strokeStyle = "red";
context.beginPath();
context.moveTo(
x - line.width / 2 + xOffset - 5,
y - height / 2 + yOffset
);
context.lineTo(
x - line.width / 2 + xOffset + 5,
y - height / 2 + yOffset
);
context.stroke();
context.restore();
}
} }
yOffset += line.descent; yOffset += line.descent;
} }

View File

@ -107,24 +107,20 @@ const drawStandardCard = async (
context.drawImage(getImage("card-description-focus"), 44, 1094); context.drawImage(getImage("card-description-focus"), 44, 1094);
// Draw the name // Draw the name
context.font = "75pt DominionTitle"; context.font = "75pt DominionTitle";
await renderDominionText( await renderDominionText(context, parse(card.title), w / 2, 220, 1100);
context,
parse("Moonlit Scheme"),
w / 2,
220,
1100
);
// Draw the description // Draw the description
context.font = "60pt DominionText"; context.font = "60pt DominionText";
await renderDominionText( await renderDominionText(
context, context,
parse("You may play an Action card from your hand costing up to $4."), parse(card.description),
w / 2, w / 2,
1520, 1520,
1100 1100
); );
// Draw the types // Draw the types
// Draw the cost // Draw the cost
context.font = "90pt DominionText";
await renderDominionText(context, parse(card.price), 210, 1940, 200);
// Draw the preview // Draw the preview
// Draw the icon // Draw the icon
// Draw the credit // Draw the credit

41
src/fonthelper.ts Normal file
View File

@ -0,0 +1,41 @@
import font from "npm:css-font";
export type FontInfo = {
style: "normal" | "italic" | "oblique";
variant: "normal" | "small-caps";
weight:
| "normal"
| "bold"
| "lighter"
| "bolder"
| "100"
| "200"
| "300"
| "400"
| "500"
| "600"
| "700"
| "800"
| "900";
stretch:
| "normal"
| "condensed"
| "semi-condensed"
| "extra-condensed"
| "ultra-condensed"
| "expanded"
| "semi-expanded"
| "extra-expanded"
| "ultra-expanded";
lineHeight: "normal" | number | string;
size: number | string;
family: string[];
};
export const parseFont = (fontString: string): FontInfo => {
return { ...font.parse(fontString) };
};
export const stringifyFont = (fontInfo: FontInfo): string => {
return font.stringify(fontInfo);
};

View File

@ -9,6 +9,6 @@ export const sampleCard: DominionCard = {
artist: "", artist: "",
author: "", author: "",
version: "", version: "",
price: "", price: "$",
preview: "", preview: "",
}; };

View File

@ -17,7 +17,7 @@
} }
@font-face { @font-face {
font-family: 'DominionSpecials'; font-family: 'DominionSpecial';
font-display: auto; font-display: auto;
src: local("Minion Std Black"), local("MinionStd-Black"), local("Minion Std"), local('Minion Pro'), src: local("Minion Std Black"), local("MinionStd-Black"), local("Minion Std"), local('Minion Pro'),
url('https://fonts.cdnfonts.com/s/13260/MinionPro-Regular.woff') format('woff'), url('https://fonts.cdnfonts.com/s/13260/MinionPro-Regular.woff') format('woff'),