improve some drawing
This commit is contained in:
		| @@ -1,6 +1,8 @@ | ||||
| import { useState } from "react"; | ||||
| import { sampleCard } from "../sampleData.ts"; | ||||
| import { Card } from "./Card.tsx"; | ||||
|  | ||||
| export const App = () => { | ||||
| 	return <div><Card card={sampleCard}/></div>; | ||||
| 	const [count, setCount] = useState(0); | ||||
| 	return <div><Card key={count} card={sampleCard}/><button onClick={() => {setCount(c => c+1)}}>Rerender (for fonts)</button></div>; | ||||
| }; | ||||
|   | ||||
| @@ -19,9 +19,8 @@ export const Card = (props: {card: DominionCard}) => { | ||||
| 		if (canvasElement) { | ||||
| 			const context = canvasElement.getContext("2d"); | ||||
| 			if (context) { | ||||
| 				console.log("loading"); | ||||
| 				await loadImages() | ||||
| 				console.log("done loading"); | ||||
| 				await loadImages(); | ||||
| 				// await loadFonts(); | ||||
| 				drawCard(context, card); | ||||
| 			} | ||||
| 		} | ||||
|   | ||||
							
								
								
									
										137
									
								
								src/draw.ts
									
									
									
									
									
								
							
							
						
						
									
										137
									
								
								src/draw.ts
									
									
									
									
									
								
							| @@ -5,8 +5,6 @@ export const loadImage = ( | ||||
| 	key: string, | ||||
| 	src: string | ||||
| ): Promise<HTMLImageElement> => { | ||||
| 	console.log("2", key); | ||||
| 	console.log(src); | ||||
| 	return new Promise((resolve) => { | ||||
| 		if (key in imageCache && imageCache[key]) { | ||||
| 			resolve(imageCache[key]); | ||||
| @@ -45,7 +43,6 @@ const imageList = [ | ||||
| export const loadImages = async () => { | ||||
| 	for (const imageInfo of imageList) { | ||||
| 		const { key, src } = imageInfo; | ||||
| 		console.log(key); | ||||
| 		await loadImage(key, src); | ||||
| 	} | ||||
| }; | ||||
| @@ -88,22 +85,130 @@ export const drawCard = ( | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| const drawText = ( | ||||
| 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(/\s+/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 = 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 { maxWidth = undefined } = options ?? {}; | ||||
| 	context.font = "bold 48px serif"; | ||||
| 	context.fillText(text, x, y, maxWidth); | ||||
| 	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 = 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 ( | ||||
| @@ -121,8 +226,24 @@ const drawStandardCard = async ( | ||||
| 	context.drawImage(colorImage(getImage("card-brown"), "#ff9911"), 0, 0); | ||||
| 	context.drawImage(getImage("card-description-focus"), 44, 1094); | ||||
| 	// Draw the name | ||||
| 	drawText(context, card.title, 400, 500); | ||||
| 	drawTextCentered(context, "Moonlit Scheme", w / 2, 220, { | ||||
| 		maxWidth: 1100, | ||||
| 		font: "DominionTitle", | ||||
| 		fontWeight: "bold", | ||||
| 	}); | ||||
| 	// Draw the description | ||||
| 	drawTextCentered( | ||||
| 		context, | ||||
| 		"You may play an Action card from your hand.", | ||||
| 		w / 2, | ||||
| 		1520, | ||||
| 		{ | ||||
| 			maxWidth: 1100, | ||||
| 			font: "DominionText", | ||||
| 			allowWrap: true, | ||||
| 			defaultSize: 60, | ||||
| 		} | ||||
| 	); | ||||
| 	// Draw the types | ||||
| 	// Draw the cost | ||||
| 	// Draw the preview | ||||
|   | ||||
							
								
								
									
										25
									
								
								src/richtext.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/richtext.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| // type RichnessNodeDefinition<N extends {type: string}> = { | ||||
| // 	type: N["type"] | ||||
| // 	measure(context: CanvasRenderingContext2D, node: N): Promise<TextMetrics>; | ||||
| // 	render( | ||||
| // 		context: CanvasRenderingContext2D, | ||||
| // 		node: N, | ||||
| // 		x: number, | ||||
| // 		y: number | ||||
| // 	): Promise<void>; | ||||
| // }; | ||||
|  | ||||
| // type Richness<N extends {type: string}> = {[K in N["type"]]: RichnessNodeDefinition<N & {type: K}>} | ||||
|  | ||||
| // const drawRichText = <N extends {type: string}>( | ||||
| // 	context: CanvasRenderingContext2D, | ||||
| // 	richness: Richness<N>, | ||||
| // 	richText: N[], | ||||
| // 	x: number, | ||||
| // 	y: number, | ||||
| // 	maxWidth: number, | ||||
| // ) => { | ||||
| // 	context.save(); | ||||
| // 	const | ||||
| // 	context.restore(); | ||||
| // }; | ||||
		Reference in New Issue
	
	Block a user