diff --git a/src/dominiontext.ts b/src/dominiontext.ts
index 2836cc2..a4d8f2b 100644
--- a/src/dominiontext.ts
+++ b/src/dominiontext.ts
@@ -1,4 +1,6 @@
-type Piece =
+import { getImage } from "./draw.ts";
+
+export type Piece =
 	| { type: "text"; text: string; isBold?: boolean; isItalic?: boolean }
 	| { type: "space" }
 	| { type: "break" }
@@ -13,18 +15,43 @@ type PieceMeasure = {
 	descent: number;
 };
 
+type Line = {
+	pieces: {
+		piece: Piece;
+		xOffset: number;
+	}[];
+	width: number;
+	ascent: number;
+	descent: number;
+};
+
+type PieceTools = {
+	measurePiece: (
+		context: CanvasRenderingContext2D,
+		piece: Piece
+	) => PromiseOr<PieceMeasure>;
+	renderPiece: (
+		context: CanvasRenderingContext2D,
+		piece: Piece,
+		x: number,
+		y: number
+	) => PromiseOr<void>;
+};
+
 type PieceDef<T extends Piece["type"], M extends PieceMeasure> = {
 	type: T;
 	measure(
 		context: CanvasRenderingContext2D,
-		piece: Piece & { type: T }
+		piece: Piece & { type: T },
+		tools: PieceTools
 	): PromiseOr<M>;
 	render(
 		context: CanvasRenderingContext2D,
 		piece: Piece & { type: T },
 		x: number,
 		y: number,
-		measure: NoInfer<M>
+		measure: NoInfer<M>,
+		tools: PieceTools
 	): PromiseOr<void>;
 };
 
@@ -41,8 +68,8 @@ const textPiece = pieceDef({
 		return {
 			type: "content",
 			width: metrics.width,
-			ascent: metrics.emHeightAscent,
-			descent: metrics.emHeightDescent,
+			ascent: metrics.fontBoundingBoxAscent,
+			descent: metrics.fontBoundingBoxDescent,
 		};
 	},
 	render(context, piece, x, y) {
@@ -57,8 +84,8 @@ const spacePiece = pieceDef({
 		return {
 			type: "space",
 			width: metrics.width,
-			ascent: metrics.emHeightAscent,
-			descent: metrics.emHeightDescent,
+			ascent: metrics.fontBoundingBoxAscent,
+			descent: metrics.fontBoundingBoxDescent,
 		};
 	},
 	render() {},
@@ -71,8 +98,8 @@ const breakPiece = pieceDef({
 		return {
 			type: "break",
 			width: 0,
-			ascent: metrics.emHeightAscent,
-			descent: metrics.emHeightDescent,
+			ascent: metrics.fontBoundingBoxAscent,
+			descent: metrics.fontBoundingBoxDescent,
 		};
 	},
 	render() {},
@@ -82,33 +109,42 @@ const coinPiece = pieceDef({
 	type: "coin",
 	measure(context, _piece) {
 		const metrics = context.measureText(" ");
+		const height =
+			metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent;
+		const coinImage = getImage("coin");
 		return {
 			type: "content",
-			width: metrics.emHeightAscent + metrics.emHeightDescent,
-			ascent: metrics.emHeightAscent,
-			descent: metrics.emHeightDescent,
+			width: coinImage.width * (height / coinImage.height),
+			ascent: metrics.fontBoundingBoxAscent,
+			descent: metrics.fontBoundingBoxDescent,
 		};
 	},
 	render(context, piece, x, y, measure) {
 		context.save();
-		context.fillStyle = "yellow";
-		context.fillRect(
+		// context.fillStyle = "yellow";
+		const height = measure.ascent + measure.descent;
+		// context.fillRect(x, y - measure.ascent, measure.width, height);
+		context.drawImage(
+			getImage("coin"),
 			x,
 			y - measure.ascent,
 			measure.width,
-			measure.ascent + measure.descent
+			height
 		);
 		context.fillStyle = "black";
-		context.fillText(piece.text, x, y);
+		context.textAlign = "center";
+		context.fillText(piece.text, x + measure.width / 2, y);
 		context.restore();
 	},
 });
 
 const pieceDefs = [textPiece, spacePiece, breakPiece, coinPiece];
 
+let tools: PieceTools = {} as any;
+
 const measurePiece = (context: CanvasRenderingContext2D, piece: Piece) => {
 	const def = pieceDefs.find((def) => def.type === piece.type)!;
-	return def.measure(context, piece as any);
+	return def.measure(context, piece as any, tools);
 };
 
 const renderPiece = (
@@ -118,18 +154,12 @@ const renderPiece = (
 	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);
+	const measure = def.measure(context, piece as any, tools);
+	return def.render(context, piece as any, x, y, measure as any, tools);
 };
 
-// export const drawDominionText = (
-// 	context: CanvasRenderingContext2D,
-// 	text: Piece[],
-// 	x: number,
-// 	y: number,
-// 	w: number,
-// 	h: number
-// ) => {};
+tools.measurePiece = measurePiece;
+tools.renderPiece = renderPiece;
 
 type DominionFont = {
 	font: "text" | "title";
@@ -138,14 +168,106 @@ type DominionFont = {
 	isItalic: boolean;
 };
 
-export const measureDominionText = (
+type PieceWithInfo = {
+	piece: Piece;
+	measure: PieceMeasure;
+};
+
+export const measureDominionText = async (
 	context: CanvasRenderingContext2D,
 	pieces: Piece[],
-	font: DominionFont,
 	maxWidth: number
 ) => {
-	const data = pieces.map((piece) => ({
-		piece,
-		measure: measurePiece(context, piece),
-	}));
+	const data: PieceWithInfo[] = await Promise.all(
+		pieces.map(async (piece) => ({
+			piece,
+			measure: await measurePiece(context, piece),
+		}))
+	);
+	const lines: Line[] = [{ pieces: [], width: 0, ascent: 0, descent: 0 }];
+	for (const pieceInfo of data) {
+		const line = lines[lines.length - 1]!;
+		if (pieceInfo.measure.type === "break") {
+			line.pieces.push({ piece: pieceInfo.piece, xOffset: line.width });
+			line.width += pieceInfo.measure.width;
+			line.ascent = Math.max(line.ascent, pieceInfo.measure.ascent);
+			line.descent = Math.max(line.descent, pieceInfo.measure.descent);
+			lines.push({ pieces: [], width: 0, ascent: 0, descent: 0 });
+		} else {
+			if (line.width + pieceInfo.measure.width > maxWidth) {
+				lines.push({
+					pieces: [{ piece: pieceInfo.piece, xOffset: 0 }],
+					width: pieceInfo.measure.width,
+					ascent: pieceInfo.measure.ascent,
+					descent: pieceInfo.measure.descent,
+				});
+			} else {
+				line.pieces.push({
+					piece: pieceInfo.piece,
+					xOffset: line.width,
+				});
+				line.width += pieceInfo.measure.width;
+				line.ascent = Math.max(line.ascent, pieceInfo.measure.ascent);
+				line.descent = Math.max(
+					line.descent,
+					pieceInfo.measure.descent
+				);
+			}
+		}
+	}
+	return {
+		lines,
+		width: Math.max(...lines.map((line) => line.width)),
+		height: lines
+			.map((line) => line.ascent + line.descent)
+			.reduce((a, b) => a + b),
+	};
+};
+
+export const renderDominionText = async (
+	context: CanvasRenderingContext2D,
+	pieces: Piece[],
+	x: number,
+	y: number,
+	maxWidth: number
+) => {
+	const { lines, height } = await measureDominionText(
+		context,
+		pieces,
+		maxWidth
+	);
+	let yOffset = 0;
+	for (const line of lines) {
+		yOffset += line.ascent;
+		for (const { piece, xOffset } of line.pieces) {
+			await renderPiece(
+				context,
+				piece,
+				x - line.width / 2 + xOffset,
+				y - height / 2 + yOffset
+			);
+		}
+		yOffset += line.descent;
+	}
+};
+
+export const parse = (text: string): Piece[] => {
+	const pieces: Piece[] = [];
+	for (let i = 0; i < text.length; i++) {
+		const char = text[i];
+		if (char === " ") {
+			pieces.push({ type: "space" });
+		} else if (char === "\n") {
+			pieces.push({ type: "break" });
+		} else if (char === "$") {
+			const end = text.slice(i).match(/\$\d*/)![0].length;
+			pieces.push({ type: "coin", text: text.slice(i + 1, i + end) });
+			i += end - 1;
+		} else {
+			const end = text.slice(i).match(/\S+/)![0].length;
+			pieces.push({ type: "text", text: text.slice(i, i + end) });
+			i += end - 1;
+		}
+	}
+	return pieces;
 };
diff --git a/src/draw.ts b/src/draw.ts
index 8088c0e..7fac90c 100644
--- a/src/draw.ts
+++ b/src/draw.ts
@@ -1,3 +1,4 @@
+import { parse, renderDominionText } from "./dominiontext.ts";
 import { TYPE_ACTION } from "./types.ts";
 import { DominionCard } from "./types.ts";
 
@@ -39,6 +40,10 @@ const imageList = [
 		key: "card-description-focus",
 		src: "/static/assets/DescriptionFocus.png",
 	},
+	{
+		key: "coin",
+		src: "/static/assets/Coin.png",
+	},
 ];
 
 export const loadImages = async () => {
@@ -86,132 +91,6 @@ export const drawCard = (
 	}
 };
 
-const wrapText = (
-	context: CanvasRenderingContext2D,
-	text: string,
-	w: number
-) => {
-	return text.split("\n").flatMap((paragraph) => {
-		const lines: string[] = [];
-		let words = 0;
-		let remainingText = paragraph.trim().replace(/ +/g, " ");
-		let oldLine = "";
-		let countdown = 100;
-		while (remainingText.length > 0) {
-			countdown--;
-			if (countdown <= 0) {
-				console.log("CUT SHORT");
-				return [];
-			}
-			words++;
-			const newLine = remainingText.split(" ").slice(0, words).join(" ");
-			const metrics = context.measureText(newLine);
-			if (metrics.width > w) {
-				words = 0;
-				lines.push(oldLine);
-				remainingText = remainingText.slice(oldLine.length).trim();
-			} else if (newLine.length >= remainingText.length) {
-				words = 0;
-				lines.push(newLine);
-				remainingText = "";
-			}
-			oldLine = newLine;
-		}
-		if (!lines.length) {
-			return [""];
-		}
-		return lines;
-	});
-};
-
-const measureText = (
-	context: CanvasRenderingContext2D,
-	text: string,
-	maxWidth: number | undefined,
-	allowWrap: boolean | undefined
-) => {
-	const measure = context.measureText(text);
-	const lineHeight = 1.2 * (measure.emHeightAscent + measure.emHeightDescent);
-	if (!allowWrap || !maxWidth) {
-		return {
-			width: measure.width,
-			height: lineHeight,
-		};
-	}
-	const lines = wrapText(context, text, maxWidth);
-	const width = Math.max(
-		...lines.map((line) => context.measureText(line).width)
-	);
-	const height = lines.length * lineHeight;
-	return { width, height };
-};
-
-const drawTextCentered = (
-	context: CanvasRenderingContext2D,
-	text: string,
-	x: number,
-	y: number,
-	options?: {
-		defaultSize?: number;
-		maxWidth?: number;
-		maxHeight?: number;
-		allowWrap?: boolean;
-		font?: string;
-		fontWeight?: "normal" | "bold";
-		color?: string;
-	}
-) => {
-	const {
-		defaultSize = 75,
-		maxWidth,
-		maxHeight,
-		font = "DominionText",
-		fontWeight = "normal",
-		color,
-		allowWrap = false,
-	} = options ?? {};
-	context.save();
-	if (color) {
-		context.fillStyle = color;
-	}
-	let size = defaultSize;
-	context.font = `${fontWeight} ${size}pt ${font}`;
-	if (maxWidth) {
-		while (
-			measureText(context, text, maxWidth, allowWrap).width > maxWidth
-		) {
-			size -= 2;
-			context.font = `${fontWeight} ${size}pt ${font}`;
-		}
-	}
-	if (maxHeight) {
-		while (
-			measureText(context, text, maxWidth, allowWrap).height > maxHeight
-		) {
-			size -= 1;
-			context.font = `${fontWeight} ${size}pt ${font}`;
-		}
-	}
-	const measure = context.measureText(text);
-	const lineHeight = 1.2 * (measure.emHeightAscent + measure.emHeightDescent);
-	context.textAlign = "center";
-	context.textBaseline = "middle";
-	if (allowWrap && maxWidth) {
-		const lines = wrapText(context, text, maxWidth);
-		lines.forEach((line, i) => {
-			context.fillText(
-				line,
-				x,
-				y - (lineHeight * lines.length) / 2 + lineHeight * i,
-				maxWidth
-			);
-		});
-	} else {
-		context.fillText(text, x, y, maxWidth);
-	}
-	context.restore();
-};
-
 const drawStandardCard = async (
 	context: CanvasRenderingContext2D,
 	card: DominionCard
@@ -227,23 +106,22 @@ const drawStandardCard = async (
 	context.drawImage(colorImage(getImage("card-brown"), "#ff9911"), 0, 0);
 	context.drawImage(getImage("card-description-focus"), 44, 1094);
 	// Draw the name
-	drawTextCentered(context, "Moonlit Scheme", w / 2, 220, {
-		maxWidth: 1100,
-		font: "DominionTitle",
-		fontWeight: "bold",
-	});
-	// Draw the description
-	drawTextCentered(
+	context.font = "75pt DominionTitle";
+	await renderDominionText(
 		context,
-		"You may play an Action card from your hand costing up to \u202f◯\u202f.",
+		parse("Moonlit Scheme"),
+		w / 2,
+		220,
+		1100
+	);
+	// Draw the description
+	context.font = "60pt DominionText";
+	await renderDominionText(
+		context,
+		parse("You may play an Action card from your hand costing up to $4."),
 		w / 2,
 		1520,
-		{
-			maxWidth: 1100,
-			font: "DominionText",
-			allowWrap: true,
-			defaultSize: 60,
-		}
+		1100
 	);
 	// Draw the types
 	// Draw the cost