1package hashiwokakero23import (4 "math"5 "strconv"6 "unicode/utf8"78 "codeberg.org/go-pdf/fpdf"9 "github.com/FelineStateMachine/puzzletea/pdfexport"10)1112type printAdapter struct{}1314var PDFPrintAdapter = printAdapter{}1516func (printAdapter) CanonicalGameType() string { return "Hashiwokakero" }17func (printAdapter) Aliases() []string {18 return []string{"hashi", "hashiwokakero", "hashi wokakero"}19}2021func (printAdapter) BuildPDFPayload(save []byte) (any, error) {22 return pdfexport.ParseHashiPrintData(save)23}2425func (printAdapter) RenderPDFBody(pdf *fpdf.Fpdf, payload any) error {26 switch data := payload.(type) {27 case *pdfexport.HashiData:28 renderHashiPage(pdf, data)29 }30 return nil31}3233func renderHashiPage(pdf *fpdf.Fpdf, data *pdfexport.HashiData) {34 if data == nil || data.Width <= 0 || data.Height <= 0 {35 return36 }3738 pageW, pageH := pdf.GetPageSize()39 pageNo := pdf.PageNo()4041 spanX := max(data.Width-1, 1)42 spanY := max(data.Height-1, 1)43 body := pdfexport.PuzzleBodyRect(pageW, pageH, pageNo)44 rules := []string{"Connect islands horizontally/vertically with up to two bridges and no crossings."}45 ruleLines := pdfexport.InstructionLineCount(pdf, body.W, rules)46 area := pdfexport.PuzzleBoardRect(pageW, pageH, pageNo, ruleLines)47 step := pdfexport.FitHashiCellSize(spanX, spanY, area)48 if step <= 0 {49 return50 }5152 boardW := float64(spanX) * step53 boardH := float64(spanY) * step54 originX, originY := pdfexport.CenteredOrigin(area, spanX, spanY, step)55 islandRadius := hashiIslandRadius(step)5657 drawHashiGuideDots(pdf, originX, originY, data.Width, data.Height, step)58 drawHashiBoardBorder(pdf, originX, originY, boardW, boardH, islandRadius)59 drawHashiIslands(pdf, originX, originY, step, islandRadius, data.Islands)6061 ruleY := pdfexport.InstructionY(originY+boardH+pdfexport.InstructionLineHMM, pageH, ruleLines)62 pdfexport.RenderInstructions(pdf, body.X, ruleY, body.W, rules)63}6465func drawHashiGuideDots(pdf *fpdf.Fpdf, originX, originY float64, width, height int, step float64) {66 pdf.SetFillColor(180, 180, 180)67 r := math.Max(0.20, math.Min(0.55, step*0.035))68 for y := range height {69 for x := range width {70 cx := originX + float64(x)*step71 cy := originY + float64(y)*step72 pdf.Circle(cx, cy, r, "F")73 }74 }75}7677func drawHashiBoardBorder(pdf *fpdf.Fpdf, originX, originY, boardW, boardH, islandRadius float64) {78 if boardW <= 0 || boardH <= 0 {79 return80 }81 borderPad := islandRadius + 1.282 pdf.SetDrawColor(35, 35, 35)83 pdf.SetLineWidth(pdfexport.OuterBorderLineMM)84 pdf.Rect(originX-borderPad, originY-borderPad, boardW+2*borderPad, boardH+2*borderPad, "D")85}8687func drawHashiIslands(88 pdf *fpdf.Fpdf,89 originX,90 originY,91 step,92 radius float64,93 islands []pdfexport.HashiIsland,94) {95 pdf.SetDrawColor(20, 20, 20)96 pdf.SetFillColor(255, 255, 255)97 pdf.SetLineWidth(pdfexport.MajorGridLineMM)9899 for _, island := range islands {100 cx := originX + float64(island.X)*step101 cy := originY + float64(island.Y)*step102 pdf.Circle(cx, cy, radius, "DF")103 drawHashiIslandNumber(pdf, cx, cy, radius, island.Required)104 }105}106107func hashiIslandRadius(step float64) float64 {108 return math.Max(1.4, math.Min(3.2, step*0.23))109}110111func drawHashiIslandNumber(pdf *fpdf.Fpdf, cx, cy, radius float64, required int) {112 text := strconv.Itoa(required)113 fontSize := pdfexport.StandardCellFontSize(radius*2.0, 0.95)114 runeCount := utf8.RuneCountInString(text)115 switch {116 case runeCount >= 3:117 fontSize *= 0.70118 case runeCount == 2:119 fontSize *= 0.82120 }121 fontSize = pdfexport.ClampStandardCellFontSize(fontSize)122123 pdf.SetTextColor(pdfexport.PrimaryTextGray, pdfexport.PrimaryTextGray, pdfexport.PrimaryTextGray)124 pdf.SetFont(pdfexport.SansFontFamily, "B", fontSize)125 lineH := fontSize * 0.88126 pdf.SetXY(cx-radius, cy-lineH/2)127 pdf.CellFormat(radius*2, lineH, text, "", 0, "C", false, 0, "")128}