generated from VLADIMIR/template
up
This commit is contained in:
@@ -84,15 +84,6 @@ func Generate(cards []Card) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
x := margin + float64(i%columns)*width
|
x := margin + float64(i%columns)*width
|
||||||
y := margin + float64(i%perPage/columns)*height
|
y := margin + float64(i%perPage/columns)*height
|
||||||
bitmap := qr.Bitmap()
|
|
||||||
pixel := qrSize / float64(len(bitmap))
|
|
||||||
for row, cells := range bitmap {
|
|
||||||
for col, black := range cells {
|
|
||||||
if black {
|
|
||||||
pdf.Rect(x+(width-qrSize)/2+float64(col)*pixel, y+3+float64(row)*pixel, pixel, pixel, "F")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
name := strings.Join(strings.Fields(card.Name), " ")
|
name := strings.Join(strings.Fields(card.Name), " ")
|
||||||
for _, r := range name {
|
for _, r := range name {
|
||||||
if r > 0xffff {
|
if r > 0xffff {
|
||||||
@@ -105,6 +96,15 @@ func Generate(cards []Card) ([]byte, error) {
|
|||||||
instructions = append(instructions, pdf.SplitText(paragraph, width-6)...)
|
instructions = append(instructions, pdf.SplitText(paragraph, width-6)...)
|
||||||
}
|
}
|
||||||
instructionHeight := float64(len(instructions)) * 3.3
|
instructionHeight := float64(len(instructions)) * 3.3
|
||||||
|
bitmap := qr.Bitmap()
|
||||||
|
pixel := qrSize / float64(len(bitmap))
|
||||||
|
for row, cells := range bitmap {
|
||||||
|
for col, black := range cells {
|
||||||
|
if black {
|
||||||
|
pdf.Rect(x+(width-qrSize)/2+float64(col)*pixel, y+3+instructionHeight+float64(row)*pixel, pixel, pixel, "F")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
var lines []string
|
var lines []string
|
||||||
var lineHeight float64
|
var lineHeight float64
|
||||||
for size := 12.0; size >= 6; size-- {
|
for size := 12.0; size >= 6; size-- {
|
||||||
@@ -119,12 +119,12 @@ func Generate(cards []Card) ([]byte, error) {
|
|||||||
return nil, fmt.Errorf("название команды %d не помещается на карточке (%d строк, %f мм)", i+1, len(lines), lineHeight)
|
return nil, fmt.Errorf("название команды %d не помещается на карточке (%d строк, %f мм)", i+1, len(lines), lineHeight)
|
||||||
}
|
}
|
||||||
for line, text := range lines {
|
for line, text := range lines {
|
||||||
pdf.SetXY(x+3, y+qrSize+5+float64(line)*lineHeight)
|
pdf.SetXY(x+3, y+instructionHeight+qrSize+5+float64(line)*lineHeight)
|
||||||
pdf.CellFormat(width-6, lineHeight, text, "", 0, "C", false, 0, "")
|
pdf.CellFormat(width-6, lineHeight, text, "", 0, "C", false, 0, "")
|
||||||
}
|
}
|
||||||
pdf.SetFont("teams", "", 8)
|
pdf.SetFont("teams", "", 8)
|
||||||
for line, text := range instructions {
|
for line, text := range instructions {
|
||||||
pdf.SetXY(x+3, y+qrSize+5+float64(len(lines))*lineHeight+float64(line)*3.3)
|
pdf.SetXY(x+3, y+3+float64(line)*3.3)
|
||||||
pdf.CellFormat(width-6, 3.3, text, "", 0, "L", false, 0, "")
|
pdf.CellFormat(width-6, 3.3, text, "", 0, "L", false, 0, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ func WiFiInstructions(name, password string) []string {
|
|||||||
access = "Пароль: «" + password + "»"
|
access = "Пароль: «" + password + "»"
|
||||||
}
|
}
|
||||||
return []string{
|
return []string{
|
||||||
"1. Подключитесь к Wi-Fi «" + name + "».",
|
"Подключитесь к Wi-Fi «" + name + "».",
|
||||||
access,
|
access,
|
||||||
"2. Отсканируйте QR-код команды.",
|
"Оставайтесь в сети без интернета.",
|
||||||
"Если телефон сообщает, что сеть без интернета, оставайтесь подключены.",
|
"Затем отсканируйте QR-код.",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,10 +43,10 @@ func TestWiFiInstructions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
got := WiFiInstructions(" Venue ", " secret ")
|
got := WiFiInstructions(" Venue ", " secret ")
|
||||||
want := []string{
|
want := []string{
|
||||||
"1. Подключитесь к Wi-Fi « Venue ».",
|
"Подключитесь к Wi-Fi « Venue ».",
|
||||||
"Пароль: « secret »",
|
"Пароль: « secret »",
|
||||||
"2. Отсканируйте QR-код команды.",
|
"Оставайтесь в сети без интернета.",
|
||||||
"Если телефон сообщает, что сеть без интернета, оставайтесь подключены.",
|
"Затем отсканируйте QR-код.",
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(got, want) {
|
if !reflect.DeepEqual(got, want) {
|
||||||
t.Fatal("instructions changed credentials")
|
t.Fatal("instructions changed credentials")
|
||||||
|
|||||||
@@ -152,8 +152,8 @@ func renderReceipt(card team_qr_pdf.Card) (*image.Gray, error) {
|
|||||||
return nil, ErrInvalidReceipt
|
return nil, ErrInvalidReceipt
|
||||||
}
|
}
|
||||||
qrSize := len(bitmap) * scale
|
qrSize := len(bitmap) * scale
|
||||||
qrY := 16 + len(lines)*nameLineHeight + 16
|
qrY := 16 + len(instructions)*26 + 16
|
||||||
height := qrY + qrSize + 24 + len(instructions)*26
|
height := qrY + qrSize + 24 + len(lines)*nameLineHeight
|
||||||
for card.WiFiName != "" && height > receiptMaxHeight && nameSize > 16 {
|
for card.WiFiName != "" && height > receiptMaxHeight && nameSize > 16 {
|
||||||
_ = face.Close()
|
_ = face.Close()
|
||||||
nameSize -= 2
|
nameSize -= 2
|
||||||
@@ -163,8 +163,7 @@ func renderReceipt(card team_qr_pdf.Card) (*image.Gray, error) {
|
|||||||
return nil, fmt.Errorf("resize receipt font: %w", err)
|
return nil, fmt.Errorf("resize receipt font: %w", err)
|
||||||
}
|
}
|
||||||
lines = wrapReceiptName(name, face)
|
lines = wrapReceiptName(name, face)
|
||||||
qrY = 16 + len(lines)*nameLineHeight + 16
|
height = qrY + qrSize + 24 + len(lines)*nameLineHeight
|
||||||
height = qrY + qrSize + 24 + len(instructions)*26
|
|
||||||
}
|
}
|
||||||
if height > receiptMaxHeight {
|
if height > receiptMaxHeight {
|
||||||
return nil, ErrInvalidReceipt
|
return nil, ErrInvalidReceipt
|
||||||
@@ -174,7 +173,7 @@ func renderReceipt(card team_qr_pdf.Card) (*image.Gray, error) {
|
|||||||
drawer := font.Drawer{Dst: img, Src: image.Black, Face: face}
|
drawer := font.Drawer{Dst: img, Src: image.Black, Face: face}
|
||||||
for i, line := range lines {
|
for i, line := range lines {
|
||||||
width := drawer.MeasureString(line).Ceil()
|
width := drawer.MeasureString(line).Ceil()
|
||||||
drawer.Dot = fixed.P((receiptWidth-width)/2, 16+face.Metrics().Ascent.Ceil()+i*nameLineHeight)
|
drawer.Dot = fixed.P((receiptWidth-width)/2, qrY+qrSize+face.Metrics().Ascent.Ceil()+i*nameLineHeight)
|
||||||
drawer.DrawString(line)
|
drawer.DrawString(line)
|
||||||
}
|
}
|
||||||
x := (receiptWidth - qrSize) / 2
|
x := (receiptWidth - qrSize) / 2
|
||||||
@@ -187,7 +186,7 @@ func renderReceipt(card team_qr_pdf.Card) (*image.Gray, error) {
|
|||||||
}
|
}
|
||||||
drawer.Face = instructionFace
|
drawer.Face = instructionFace
|
||||||
for i, line := range instructions {
|
for i, line := range instructions {
|
||||||
drawer.Dot = fixed.P(16, qrY+qrSize+instructionFace.Metrics().Ascent.Ceil()+i*26)
|
drawer.Dot = fixed.P(16, 16+instructionFace.Metrics().Ascent.Ceil()+i*26)
|
||||||
drawer.DrawString(line)
|
drawer.DrawString(line)
|
||||||
}
|
}
|
||||||
for i, pixel := range img.Pix {
|
for i, pixel := range img.Pix {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"evening_detective_server/internal/repos"
|
"evening_detective_server/internal/repos"
|
||||||
"evening_detective_server/internal/repos/games_repo"
|
"evening_detective_server/internal/repos/games_repo"
|
||||||
"evening_detective_server/internal/repos/teams_repo"
|
"evening_detective_server/internal/repos/teams_repo"
|
||||||
|
"github.com/skip2/go-qrcode"
|
||||||
"golang.org/x/image/font"
|
"golang.org/x/image/font"
|
||||||
"golang.org/x/image/font/opentype"
|
"golang.org/x/image/font/opentype"
|
||||||
)
|
)
|
||||||
@@ -198,3 +199,26 @@ func TestWrapReceiptTextPreservesCredentials(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReceiptQRCodeBeforeTeamName(t *testing.T) {
|
||||||
|
card := team_qr_pdf.Card{Name: "Вечерние сыщики", URL: "https://example.org/team"}
|
||||||
|
img, err := renderReceipt(card)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
qr, err := qrcode.New(card.URL, qrcode.Medium)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
bitmap := qr.Bitmap()
|
||||||
|
scale := (receiptWidth - 32) / len(bitmap)
|
||||||
|
x := (receiptWidth - len(bitmap)*scale) / 2
|
||||||
|
// With no instructions the QR starts at the top, before the team name.
|
||||||
|
for row, cells := range bitmap {
|
||||||
|
for col, black := range cells {
|
||||||
|
if got := img.GrayAt(x+col*scale, 32+row*scale).Y == 0; got != black {
|
||||||
|
t.Fatal("QR must precede the team name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user