This commit is contained in:
Владимир Фёдоров 2025-06-17 01:40:55 +07:00
parent b17599eb39
commit be8e7fa482
5 changed files with 166 additions and 40 deletions

View File

@ -5,8 +5,11 @@
"Точка", "Точка",
"точки", "точки",
"AUTOINCREMENT", "AUTOINCREMENT",
"gopdf",
"gwmux", "gwmux",
"localtime", "localtime",
"palces" "palces",
"qrcode",
"signintech"
] ]
} }

7
go.mod
View File

@ -9,6 +9,11 @@ require (
google.golang.org/grpc v1.64.0 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 ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
@ -20,6 +25,8 @@ require (
require ( require (
github.com/mattn/go-sqlite3 v1.14.28 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/net v0.23.0 // indirect
golang.org/x/sys v0.26.0 // indirect golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.15.0 // indirect golang.org/x/text v0.15.0 // indirect

View File

@ -1,7 +1,49 @@
package models package models
import (
"fmt"
"net"
"net/url"
)
type Team struct { type Team struct {
ID int64 ID int64
Name string Name string
Password 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
}

View File

@ -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
}

View File

@ -9,8 +9,6 @@ import (
"evening_detective/internal/services/story_service" "evening_detective/internal/services/story_service"
"evening_detective/proto" "evening_detective/proto"
"fmt" "fmt"
"net"
"net/url"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
@ -153,7 +151,7 @@ func (s *Services) GetTeams(ctx context.Context, _ *proto.GetTeamsReq) (*proto.G
if err != nil { if err != nil {
return nil, err return nil, err
} }
newTeam.Url, err = getTeamUrl(team) newTeam.Url, err = team.GetTeamUrl()
if err != nil { if err != nil {
return nil, err 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)) 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
}