clone

fifteen puzzle games for the terminal, with daily puzzles, weekly ladders and xp, built on bubble tea

chore/app-model-decomp
puzzletea / games / nonogram / print_adapter.go
print_adapter.go 6.4K
1package nonogram23import (4 "strconv"5 "strings"67 "codeberg.org/go-pdf/fpdf"8 "github.com/FelineStateMachine/puzzletea/export/pdf"9)1011type printAdapter struct{}1213var PDFPrintAdapter = printAdapter{}1415func (printAdapter) CanonicalGameType() string { return "Nonogram" }16func (printAdapter) Aliases() []string { return []string{"nonogram"} }1718func (printAdapter) BuildPDFPayload(save []byte) (any, error) {19 return pdfexport.ParseNonogramPrintData(save)20}2122func (printAdapter) RenderPDFBody(pdf *fpdf.Fpdf, payload any) error {23 switch data := payload.(type) {24 case *pdfexport.NonogramData:25 renderNonogramPage(pdf, data)26 }27 return nil28}2930func renderNonogramPage(pdf *fpdf.Fpdf, data *pdfexport.NonogramData) {31 if data == nil || data.Width <= 0 || data.Height <= 0 {32 return33 }3435 pageW, pageH := pdf.GetPageSize()36 pageNo := pdf.PageNo()3738 rowHints := normalizeNonogramHintsForRender(data.RowHints, data.Height)39 colHints := normalizeNonogramHintsForRender(data.ColHints, data.Width)4041 rowHintCols := maxHintDepth(rowHints)42 colHintRows := maxHintDepth(colHints)43 if rowHintCols < 1 {44 rowHintCols = 145 }46 if colHintRows < 1 {47 colHintRows = 148 }4950 body := pdfexport.PuzzleBodyRect(pageW, pageH, pageNo)51 rules := []string{"Use row/column hints to fill blocks in order; groups are separated by at least one blank cell."}52 ruleLines := pdfexport.InstructionLineCount(pdf, body.W, rules)53 layout := layoutNonogram(54 pageW,55 pageH,56 pageNo,57 ruleLines,58 data.Width,59 data.Height,60 rowHintCols,61 colHintRows,62 )63 cellSize := layout.cellSize64 if cellSize <= 0 {65 return66 }6768 gridW := float64(data.Width) * cellSize69 gridH := float64(data.Height) * cellSize70 startX := layout.hintStartX71 startY := layout.hintStartY72 xSep := layout.gridX73 ySep := layout.gridY7475 for row := 0; row < colHintRows; row++ {76 for col := 0; col < data.Width; col++ {77 cellX := xSep + float64(col)*cellSize78 cellY := startY + float64(row)*cellSize79 if text := colHintText(colHints[col], colHintRows, row); text != "" {80 drawNonogramHintText(pdf, cellX, cellY, cellSize, cellSize, text)81 }82 }83 }8485 for row := 0; row < data.Height; row++ {86 for col := 0; col < rowHintCols; col++ {87 cellX := startX + float64(col)*cellSize88 cellY := ySep + float64(row)*cellSize89 if text := rowHintText(rowHints[row], rowHintCols, col); text != "" {90 drawNonogramHintText(pdf, cellX, cellY, cellSize, cellSize, text)91 }92 }93 }9495 drawNonogramPuzzleGrid(pdf, xSep, ySep, data.Width, data.Height, cellSize)96 drawNonogramMajorLines(pdf, xSep, ySep, cellSize, data.Width, data.Height, 5)9798 pdf.SetLineWidth(pdfexport.OuterBorderLineMM)99 pdf.Rect(xSep, ySep, gridW, gridH, "D")100101 ruleY := pdfexport.InstructionY(ySep+gridH, pageH, ruleLines)102 pdfexport.RenderInstructions(pdf, body.X, ruleY, body.W, rules)103}104105func drawNonogramPuzzleGrid(106 pdf *fpdf.Fpdf,107 startX,108 startY float64,109 width,110 height int,111 cellSize float64,112) {113 if width <= 0 || height <= 0 || cellSize <= 0 {114 return115 }116117 gridW := float64(width) * cellSize118 gridH := float64(height) * cellSize119120 pdf.SetDrawColor(45, 45, 45)121 pdf.SetLineWidth(pdfexport.ThinGridLineMM)122 for col := 0; col <= width; col++ {123 x := startX + float64(col)*cellSize124 pdf.Line(x, startY, x, startY+gridH)125 }126 for row := 0; row <= height; row++ {127 y := startY + float64(row)*cellSize128 pdf.Line(startX, y, startX+gridW, y)129 }130}131132func drawNonogramHintText(pdf *fpdf.Fpdf, x, y, w, h float64, text string) {133 if strings.TrimSpace(text) == "" {134 return135 }136137 pdf.SetTextColor(pdfexport.PrimaryTextGray, pdfexport.PrimaryTextGray, pdfexport.PrimaryTextGray)138 fontSize := pdfexport.StandardCellFontSize(h, 0.70)139 pdf.SetFont(pdfexport.SansFontFamily, "B", fontSize)140 lineH := fontSize * 0.86141 pdf.SetXY(x, y+(h-lineH)/2)142 pdf.CellFormat(w, lineH, text, "", 0, "C", false, 0, "")143}144145type nonogramLayout struct {146 cellSize float64147 hintStartX float64148 hintStartY float64149 gridX float64150 gridY float64151}152153func layoutNonogram(154 pageW,155 pageH float64,156 pageNo,157 ruleLines,158 gridCols,159 gridRows,160 rowHintCols,161 colHintRows int,162) nonogramLayout {163 totalCols := rowHintCols + gridCols164 totalRows := colHintRows + gridRows165 area := pdfexport.PuzzleBoardRect(pageW, pageH, pageNo, ruleLines)166 cellSize := pdfexport.FitNonogramCellSize(totalCols, totalRows, area)167 if cellSize <= 0 {168 return nonogramLayout{}169 }170171 if rowHintCols > 0 {172 centeredCapW := area.W / float64(gridCols+2*rowHintCols)173 if centeredCapW > 0 && centeredCapW < cellSize {174 cellSize = centeredCapW175 }176 }177 if colHintRows > 0 {178 centeredCapH := area.H / float64(gridRows+2*colHintRows)179 if centeredCapH > 0 && centeredCapH < cellSize {180 cellSize = centeredCapH181 }182 }183184 gridW := float64(gridCols) * cellSize185 gridH := float64(gridRows) * cellSize186 gridX := area.X + (area.W-gridW)/2187 gridY := area.Y + (area.H-gridH)/2188 hintStartX := gridX - float64(rowHintCols)*cellSize189 hintStartY := gridY - float64(colHintRows)*cellSize190191 return nonogramLayout{192 cellSize: cellSize,193 hintStartX: hintStartX,194 hintStartY: hintStartY,195 gridX: gridX,196 gridY: gridY,197 }198}199200func colHintText(hints []int, depth, row int) string {201 if len(hints) == 0 {202 return ""203 }204 start := depth - len(hints)205 if row < start {206 return ""207 }208 return strconv.Itoa(hints[row-start])209}210211func rowHintText(hints []int, depth, col int) string {212 if len(hints) == 0 {213 return ""214 }215 start := depth - len(hints)216 if col < start {217 return ""218 }219 return strconv.Itoa(hints[col-start])220}221222func maxHintDepth(hints [][]int) int {223 maxDepth := 0224 for _, h := range hints {225 if len(h) > maxDepth {226 maxDepth = len(h)227 }228 }229 return maxDepth230}231232func normalizeNonogramHintsForRender(hints [][]int, size int) [][]int {233 if size <= 0 {234 return nil235 }236237 normalized := make([][]int, size)238 for i := range size {239 if i >= len(hints) || len(hints[i]) == 0 {240 normalized[i] = []int{0}241 continue242 }243 normalized[i] = append([]int(nil), hints[i]...)244 }245 return normalized246}247248func drawNonogramMajorLines(249 pdf *fpdf.Fpdf,250 puzzleStartX,251 puzzleStartY,252 cellSize float64,253 width,254 height,255 step int,256) {257 if step <= 0 || width <= 0 || height <= 0 {258 return259 }260261 pdf.SetDrawColor(45, 45, 45)262 pdf.SetLineWidth(pdfexport.MajorGridLineMM)263264 for col := step; col < width; col += step {265 x := puzzleStartX + float64(col)*cellSize266 pdf.Line(x, puzzleStartY, x, puzzleStartY+float64(height)*cellSize)267 }268 for row := step; row < height; row += step {269 y := puzzleStartY + float64(row)*cellSize270 pdf.Line(puzzleStartX, y, puzzleStartX+float64(width)*cellSize, y)271 }272}