crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

commit 14cdd15ac0e989b1dfdfa148718a619d00212884
parent df420d47fa51fe4e592941f620a002d31d834091
Author: Michael Camilleri <[email protected]>
Date:   Wed, 29 Jul 2026 08:21:25 +0900

Render overlay letters clearly in every square

Recognised overlay letters could disappear against their squares in one
appearance mode and render larger than ordinary entries on compact
grids.

This commit gives decoration text an explicit contrasting colour for
block and open squares, then measures and scales it to the available
cell bounds. Resolved text is cached by value and square type so
repeated overlay letters remain inexpensive to draw.

Co-Authored-By: Codex GPT 5.6 Sol <[email protected]>

Diffstat:
MCrossmate/Views/Puzzle/GridView.swift | 42+++++++++++++++++++++++++++++++++++++-----
1 file changed, 37 insertions(+), 5 deletions(-)

diff --git a/Crossmate/Views/Puzzle/GridView.swift b/Crossmate/Views/Puzzle/GridView.swift @@ -280,6 +280,8 @@ private struct PuzzleCellsLayer: View { // full grid has ~26 distinct glyphs, so almost every cell is a hit // and `resolve`/`measure` runs a couple of dozen times, not 441. var glyphCache: [String: (text: GraphicsContext.ResolvedText, size: CGSize)] = [:] + var decorationTextCache: + [String: (text: GraphicsContext.ResolvedText, size: CGSize)] = [:] // Resolved tiles are cached for this draw the same way glyphs are: // a grid art puzzle repeats a handful of motifs across many cells. var imageCache: [String: GraphicsContext.ResolvedImage] = [:] @@ -291,6 +293,7 @@ private struct PuzzleCellsLayer: View { drawDecorations( cell, in: geometry.cellRect(row: cell.row, col: cell.col), + textCache: &decorationTextCache, cache: &imageCache, context: context ) @@ -340,6 +343,7 @@ private struct PuzzleCellsLayer: View { private func drawDecorations( _ cell: CellDraw, in rect: CGRect, + textCache: inout [String: (text: GraphicsContext.ResolvedText, size: CGSize)], cache: inout [String: GraphicsContext.ResolvedImage], context: GraphicsContext ) { @@ -368,11 +372,39 @@ private struct PuzzleCellsLayer: View { case .color: continue case .text(let value): - let resolved = context.resolve( - Text(value) - .font(.system(size: baseFontSize * 0.8, weight: .semibold, design: .rounded)) - ) - context.draw(resolved, at: CGPoint(x: rect.midX, y: rect.midY), anchor: .center) + let key = "\(value)|\(cell.isBlock)" + let resolved: GraphicsContext.ResolvedText + let natural: CGSize + if let hit = textCache[key] { + resolved = hit.text + natural = hit.size + } else { + let text = context.resolve( + Text(value) + .font( + .system( + size: baseFontSize * 0.8, + weight: .semibold, + design: .rounded + ) + ) + .foregroundStyle(cell.isBlock ? Color.white : Color.black) + ) + let measured = text.measure( + in: CGSize(width: CGFloat.infinity, height: CGFloat.infinity) + ) + textCache[key] = (text, measured) + resolved = text + natural = measured + } + guard natural.width > 0, natural.height > 0 else { continue } + let availableWidth = rect.width - 4 + let scale = min(1, availableWidth / natural.width, rect.height / natural.height) + context.drawLayer { layer in + layer.translateBy(x: rect.midX, y: rect.midY) + layer.scaleBy(x: scale, y: scale) + layer.draw(resolved, at: .zero, anchor: .center) + } case .data(_, _, let payload): guard let image = decorationImages[payload] else { continue } let resolved: GraphicsContext.ResolvedImage