diff --git a/.vscode/settings.json b/.vscode/settings.json index 83cdb44..ae84134 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,8 +5,11 @@ "Точка", "точки", "AUTOINCREMENT", + "gopdf", "gwmux", "localtime", - "palces" + "palces", + "qrcode", + "signintech" ] } \ No newline at end of file diff --git a/go.mod b/go.mod index 6e4b8a0..44e6cf3 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,11 @@ require ( google.golang.org/grpc v1.64.0 ) +require ( + github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311 // indirect + github.com/pkg/errors v0.8.1 // indirect +) + require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/kr/text v0.2.0 // indirect @@ -20,6 +25,8 @@ require ( require ( github.com/mattn/go-sqlite3 v1.14.28 + github.com/signintech/gopdf v0.32.0 + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e golang.org/x/net v0.23.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.15.0 // indirect diff --git a/internal/models/team.go b/internal/models/team.go index 1d2041d..633c3ab 100644 --- a/internal/models/team.go +++ b/internal/models/team.go @@ -1,7 +1,49 @@ package models +import ( + "fmt" + "net" + "net/url" +) + type Team struct { ID int64 Name string Password string } + +func (t *Team) GetTeamUrl() (string, error) { + ips, err := getLocalIPs() + if err != nil { + return "", err + } + ip := ips[0] + u := fmt.Sprintf("http://%s:8100?name=%s&password=%s", ip, url.PathEscape(t.Name), t.Password) + return u, nil +} + +func getLocalIPs() ([]string, error) { + var ips []string + addrs, err := net.InterfaceAddrs() + if err != nil { + return nil, err + } + + for _, addr := range addrs { + ipNet, ok := addr.(*net.IPNet) + if !ok { + continue + } + + ip := ipNet.IP + if ip.IsLoopback() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() { + continue + } + + if ipv4 := ip.To4(); ipv4 != nil { + ips = append(ips, ipv4.String()) + } + } + + return ips, nil +} diff --git a/internal/services/pdf_service/service.go b/internal/services/pdf_service/service.go new file mode 100644 index 0000000..393da49 --- /dev/null +++ b/internal/services/pdf_service/service.go @@ -0,0 +1,112 @@ +package pdf_service + +import ( + "bytes" + "evening_detective/internal/models" + "strings" + + "github.com/signintech/gopdf" + "github.com/skip2/go-qrcode" +) + +func CreateTeamsPdf(teams []*models.Team) ([]byte, error) { + pdf := &gopdf.GoPdf{} + pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4}) // W: 595, H: 842 + if err := pdf.AddTTFFont("main", "JetBrainsMono-Medium.ttf"); err != nil { + return nil, err + } + if err := pdf.SetFont("main", "", 10); err != nil { + return nil, err + } + + padding := 17. + xDelta := 187. + yDelta := 202. + for i, team := range teams { + if i%12 == 0 { + pdf.AddPage() + pdf.SetPage(1 + i/12) + } + y := (padding + 15) + yDelta*float64(i%12/3) + x := padding + xDelta*float64(i%3) + + url, err := team.GetTeamUrl() + if err != nil { + return nil, err + } + if err := printQR(pdf, url, x+21, y); err != nil { + return nil, err + } + if err := printTextCenter(pdf, team.Name, xDelta-6, x+3, y+150); err != nil { + return nil, err + } + } + buffer := &bytes.Buffer{} + _, err := pdf.WriteTo(buffer) + return buffer.Bytes(), err +} + +func printQR(pdf *gopdf.GoPdf, url string, x, y float64) error { + png, err := qrcode.Encode(url, qrcode.Medium, 256) + if err != nil { + return err + } + imgHolder, err := gopdf.ImageHolderByBytes(png) + if err != nil { + return err + } + err = pdf.ImageByHolder(imgHolder, x, y, &gopdf.Rect{W: 145, H: 145}) + if err != nil { + return err + } + return nil +} + +func printTextCenter(pdf *gopdf.GoPdf, text string, pageWidth, x, y float64) error { + lines := splitTextByWords(pdf, text, pageWidth) + for i, line := range lines { + lineWidth, err := pdf.MeasureTextWidth(line) + if err != nil { + return err + } + + xLine := x + (pageWidth-lineWidth)/2 + yLine := y + 15*float64(i) + pdf.SetXY(xLine, yLine) + + pdf.Cell(nil, line) + } + return nil +} + +func splitTextByWords(pdf *gopdf.GoPdf, text string, maxWidth float64) []string { + words := strings.Fields(text) + var lines []string + var currentLine string + + for _, word := range words { + testLine := currentLine + if testLine != "" { + testLine += " " + word + } else { + testLine = word + } + + width, _ := pdf.MeasureTextWidth(testLine) + + if width <= maxWidth { + currentLine = testLine + } else { + if currentLine != "" { + lines = append(lines, currentLine) + } + currentLine = word + } + } + + if currentLine != "" { + lines = append(lines, currentLine) + } + + return lines +} diff --git a/internal/services/services.go b/internal/services/services.go index 370696e..4c48c44 100644 --- a/internal/services/services.go +++ b/internal/services/services.go @@ -9,8 +9,6 @@ import ( "evening_detective/internal/services/story_service" "evening_detective/proto" "fmt" - "net" - "net/url" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" @@ -153,7 +151,7 @@ func (s *Services) GetTeams(ctx context.Context, _ *proto.GetTeamsReq) (*proto.G if err != nil { return nil, err } - newTeam.Url, err = getTeamUrl(team) + newTeam.Url, err = team.GetTeamUrl() if err != nil { return nil, err } @@ -220,39 +218,3 @@ func addLog(team *models.Team, action string, v any) { fmt.Printf("Team %s: %s %s\n", team.Name, action, string(vJson)) } } - -func getTeamUrl(team *models.Team) (string, error) { - ips, err := getLocalIPs() - if err != nil { - return "", err - } - ip := ips[0] - u := fmt.Sprintf("http://%s:8100?name=%s&password=%s", ip, url.PathEscape(team.Name), team.Password) - return u, nil -} - -func getLocalIPs() ([]string, error) { - var ips []string - addrs, err := net.InterfaceAddrs() - if err != nil { - return nil, err - } - - for _, addr := range addrs { - ipNet, ok := addr.(*net.IPNet) - if !ok { - continue - } - - ip := ipNet.IP - if ip.IsLoopback() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() { - continue - } - - if ipv4 := ip.To4(); ipv4 != nil { - ips = append(ips, ipv4.String()) - } - } - - return ips, nil -}