improve some drawing
This commit is contained in:
		| @@ -1,6 +1,8 @@ | |||||||
|  | import { useState } from "react"; | ||||||
| import { sampleCard } from "../sampleData.ts"; | import { sampleCard } from "../sampleData.ts"; | ||||||
| import { Card } from "./Card.tsx"; | import { Card } from "./Card.tsx"; | ||||||
|  |  | ||||||
| export const App = () => { | 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) { | 		if (canvasElement) { | ||||||
| 			const context = canvasElement.getContext("2d"); | 			const context = canvasElement.getContext("2d"); | ||||||
| 			if (context) { | 			if (context) { | ||||||
| 				console.log("loading"); | 				await loadImages(); | ||||||
| 				await loadImages() | 				// await loadFonts(); | ||||||
| 				console.log("done loading"); |  | ||||||
| 				drawCard(context, card); | 				drawCard(context, card); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|   | |||||||
							
								
								
									
										135
									
								
								src/draw.ts
									
									
									
									
									
								
							
							
						
						
									
										135
									
								
								src/draw.ts
									
									
									
									
									
								
							| @@ -5,8 +5,6 @@ export const loadImage = ( | |||||||
| 	key: string, | 	key: string, | ||||||
| 	src: string | 	src: string | ||||||
| ): Promise<HTMLImageElement> => { | ): Promise<HTMLImageElement> => { | ||||||
| 	console.log("2", key); |  | ||||||
| 	console.log(src); |  | ||||||
| 	return new Promise((resolve) => { | 	return new Promise((resolve) => { | ||||||
| 		if (key in imageCache && imageCache[key]) { | 		if (key in imageCache && imageCache[key]) { | ||||||
| 			resolve(imageCache[key]); | 			resolve(imageCache[key]); | ||||||
| @@ -45,7 +43,6 @@ const imageList = [ | |||||||
| export const loadImages = async () => { | export const loadImages = async () => { | ||||||
| 	for (const imageInfo of imageList) { | 	for (const imageInfo of imageList) { | ||||||
| 		const { key, src } = imageInfo; | 		const { key, src } = imageInfo; | ||||||
| 		console.log(key); |  | ||||||
| 		await loadImage(key, src); | 		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, | 	context: CanvasRenderingContext2D, | ||||||
| 	text: string, | 	text: string, | ||||||
| 	x: number, | 	x: number, | ||||||
| 	y: number, | 	y: number, | ||||||
| 	options?: { | 	options?: { | ||||||
|  | 		defaultSize?: number; | ||||||
| 		maxWidth?: number; | 		maxWidth?: number; | ||||||
| 		maxHeight?: number; | 		maxHeight?: number; | ||||||
| 		allowWrap?: boolean; | 		allowWrap?: boolean; | ||||||
| 		font?: string; | 		font?: string; | ||||||
|  | 		fontWeight?: "normal" | "bold"; | ||||||
| 		color?: string; | 		color?: string; | ||||||
| 	} | 	} | ||||||
| ) => { | ) => { | ||||||
| 	const { maxWidth = undefined } = options ?? {}; | 	const { | ||||||
| 	context.font = "bold 48px serif"; | 		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.fillText(text, x, y, maxWidth); | ||||||
|  | 	} | ||||||
|  | 	context.restore(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const drawStandardCard = async ( | const drawStandardCard = async ( | ||||||
| @@ -121,8 +226,24 @@ const drawStandardCard = async ( | |||||||
| 	context.drawImage(colorImage(getImage("card-brown"), "#ff9911"), 0, 0); | 	context.drawImage(colorImage(getImage("card-brown"), "#ff9911"), 0, 0); | ||||||
| 	context.drawImage(getImage("card-description-focus"), 44, 1094); | 	context.drawImage(getImage("card-description-focus"), 44, 1094); | ||||||
| 	// Draw the name | 	// 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 | 	// 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 types | ||||||
| 	// Draw the cost | 	// Draw the cost | ||||||
| 	// Draw the preview | 	// 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