start on making a text framework
This commit is contained in:
		
							
								
								
									
										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, | ||||
| 		{ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user