generated from VLADIMIR/template
up
This commit is contained in:
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
@@ -0,0 +1,98 @@
|
|||||||
|
package game_print_service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/go-pdf/fpdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed assets/paper.jpg
|
||||||
|
var diplomaPaper []byte
|
||||||
|
|
||||||
|
var errInvalidDiplomaPlace = errors.New("некорректное место диплома")
|
||||||
|
|
||||||
|
func diplomaPDF(place int) ([]byte, error) {
|
||||||
|
ranks := [...]string{"I", "II", "III"}
|
||||||
|
if place < 1 || place > len(ranks) {
|
||||||
|
return nil, errInvalidDiplomaPlace
|
||||||
|
}
|
||||||
|
pdf := fpdf.New("P", "mm", "A4", "")
|
||||||
|
pdf.SetMargins(20, 20, 20)
|
||||||
|
pdf.SetAutoPageBreak(false, 0)
|
||||||
|
pdf.AddUTF8FontFromBytes("typewriter", "", typewriterFont)
|
||||||
|
pdf.SetFont("typewriter", "", 12)
|
||||||
|
pdf.AddPage()
|
||||||
|
options := fpdf.ImageOptions{ImageType: "JPG"}
|
||||||
|
paper := pdf.RegisterImageOptionsReader("diploma-paper", options, bytes.NewReader(diplomaPaper))
|
||||||
|
if err := pdf.Error(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
height := 210 * paper.Height() / paper.Width()
|
||||||
|
pdf.ClipRect(0, 0, 210, 297, false)
|
||||||
|
pdf.ImageOptions("diploma-paper", 0, (297-height)/2, 210, height, false, options, 0, "")
|
||||||
|
pdf.ClipEnd()
|
||||||
|
pdf.SetDrawColor(99, 75, 51)
|
||||||
|
pdf.SetTextColor(61, 45, 34)
|
||||||
|
pdf.SetFillColor(99, 75, 51)
|
||||||
|
pdf.SetLineWidth(0.45)
|
||||||
|
pdf.Rect(12, 12, 186, 273, "D")
|
||||||
|
pdf.SetLineWidth(0.16)
|
||||||
|
pdf.Rect(15, 15, 180, 267, "D")
|
||||||
|
for _, corner := range []fpdf.PointType{{21, 21}, {189, 21}, {21, 276}, {189, 276}} {
|
||||||
|
x, y := corner.X, corner.Y
|
||||||
|
pdf.Polygon([]fpdf.PointType{{x, y - 2}, {x + 2, y}, {x, y + 2}, {x - 2, y}}, "F")
|
||||||
|
pdf.Line(x-4, y, x-2.5, y)
|
||||||
|
pdf.Line(x+2.5, y, x+4, y)
|
||||||
|
pdf.Line(x, y-4, x, y-2.5)
|
||||||
|
pdf.Line(x, y+2.5, x, y+4)
|
||||||
|
}
|
||||||
|
pdf.SetFillColor(255, 253, 247)
|
||||||
|
pdf.Circle(105, 38, 17, "FD")
|
||||||
|
logo := pdf.RegisterImageOptionsReader("diploma-logo", options, bytes.NewReader(printLogo))
|
||||||
|
if err := pdf.Error(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
logoHeight := 28 * logo.Height() / logo.Width()
|
||||||
|
pdf.ClipCircle(105, 38, max(28, logoHeight)/2+0.1, false)
|
||||||
|
pdf.ImageOptions("diploma-logo", 91, 38-logoHeight/2, 28, 0, false, options, 0, "")
|
||||||
|
pdf.ClipEnd()
|
||||||
|
diplomaText(pdf, 61, 15, "Вечерний детектив")
|
||||||
|
pdf.Line(75, 73, 135, 73)
|
||||||
|
diplomaText(pdf, 82, 42, "ДИПЛОМ")
|
||||||
|
diplomaText(pdf, 116, 13, "Награждается команда")
|
||||||
|
pdf.SetLineWidth(0.3)
|
||||||
|
pdf.Line(32, 141, 178, 141)
|
||||||
|
pdf.TransformBegin()
|
||||||
|
pdf.TransformRotate(-5, 105, 181)
|
||||||
|
pdf.SetDrawColor(133, 53, 43)
|
||||||
|
pdf.SetTextColor(133, 53, 43)
|
||||||
|
pdf.SetLineWidth(0.55)
|
||||||
|
pdf.Rect(62, 156, 86, 51, "D")
|
||||||
|
pdf.SetLineWidth(0.18)
|
||||||
|
pdf.Rect(64, 158, 82, 47, "D")
|
||||||
|
diplomaText(pdf, 160, 11, "ЗА")
|
||||||
|
diplomaText(pdf, 168, 44, ranks[place-1])
|
||||||
|
diplomaText(pdf, 193, 15, "МЕСТО")
|
||||||
|
pdf.TransformEnd()
|
||||||
|
pdf.SetDrawColor(99, 75, 51)
|
||||||
|
pdf.SetTextColor(61, 45, 34)
|
||||||
|
diplomaText(pdf, 222, 11, "За проницательность, командную работу")
|
||||||
|
diplomaText(pdf, 230, 11, "и мастерство расследования")
|
||||||
|
pdf.SetLineWidth(0.25)
|
||||||
|
pdf.Line(32, 260, 77, 260)
|
||||||
|
pdf.Line(112, 260, 178, 260)
|
||||||
|
pdf.SetFont("typewriter", "", 9)
|
||||||
|
pdf.SetXY(32, 264)
|
||||||
|
pdf.CellFormat(45, 5, "Дата", "", 0, "C", false, 0, "")
|
||||||
|
pdf.SetXY(112, 264)
|
||||||
|
pdf.CellFormat(66, 5, "Организатор / подпись", "", 0, "C", false, 0, "")
|
||||||
|
return pdfBytes(pdf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func diplomaText(pdf *fpdf.Fpdf, y, size float64, text string) {
|
||||||
|
pdf.SetFont("typewriter", "", size)
|
||||||
|
pdf.SetXY(20, y)
|
||||||
|
pdf.CellFormat(170, size*0.3528*1.2, text, "", 0, "C", false, 0, "")
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package game_print_service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDiplomasUsePortraitA4AndBrandAssets(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
place int
|
||||||
|
rank string
|
||||||
|
}{{1, "I"}, {2, "II"}, {3, "III"}} {
|
||||||
|
t.Run(tc.rank, func(t *testing.T) {
|
||||||
|
data, err := diplomaPDF(tc.place)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if bytes.Count(data, []byte("/Type /Page\n")) != 1 || !bytes.Contains(data, []byte("/MediaBox [0 0 595.28 841.89]")) {
|
||||||
|
t.Fatal("diploma is not one portrait A4 page")
|
||||||
|
}
|
||||||
|
if !bytes.Contains(data, diplomaPaper) || !bytes.Contains(data, printLogo) || bytes.Count(data, []byte("/Subtype /Image")) != 2 {
|
||||||
|
t.Fatal("original paper and logo not embedded")
|
||||||
|
}
|
||||||
|
if !bytes.Contains(data, []byte("/BaseFont /utf8typewriter")) {
|
||||||
|
t.Fatal("typewriter font missing")
|
||||||
|
}
|
||||||
|
t.Run("contents", func(t *testing.T) {
|
||||||
|
if _, err := exec.LookPath("pdftotext"); err != nil {
|
||||||
|
t.Skip("pdftotext is needed only for optional rendered-content checks")
|
||||||
|
}
|
||||||
|
text := extractedPDFText(t, data)
|
||||||
|
for _, want := range []string{"ДИПЛОМ", "Вечерний детектив", "Награждается команда", "МЕСТО", "Дата", "Организатор / подпись", "мастерство расследования"} {
|
||||||
|
if !strings.Contains(text, want) {
|
||||||
|
t.Errorf("missing text %s", want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !regexp.MustCompile(`(?m)^\s*` + tc.rank + `\s*$`).MatchString(text) {
|
||||||
|
t.Fatalf("wrong award place: %s", text)
|
||||||
|
}
|
||||||
|
if regexp.MustCompile(`(?m)^\s*[123]\s*$`).MatchString(text) {
|
||||||
|
t.Fatal("page number printed")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiplomaRejectsUnknownPlace(t *testing.T) {
|
||||||
|
for _, place := range []int{-1, 0, 4} {
|
||||||
|
t.Run(strconv.Itoa(place), func(t *testing.T) {
|
||||||
|
data, err := diplomaPDF(place)
|
||||||
|
if !errors.Is(err, errInvalidDiplomaPlace) || len(data) != 0 {
|
||||||
|
t.Fatalf("place %d: data = %d bytes, error = %v", place, len(data), err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -147,6 +147,15 @@ func (s *GamePrintService) Download(ctx context.Context, gameID int) ([]byte, st
|
|||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for place := 1; place <= 3; place++ {
|
||||||
|
data, err := diplomaPDF(place)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("generate diploma %d: %w", place, err)
|
||||||
|
}
|
||||||
|
if err := add(fmt.Sprintf("%sДипломы/%d место.pdf", hostFolder, place), data); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
if len(teams) > 0 {
|
if len(teams) > 0 {
|
||||||
data, _, err := s.qr.GenerateTeamQR(ctx, game, teams)
|
data, _, err := s.qr.GenerateTeamQR(ctx, game, teams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ func TestDownloadPrintMaterials(t *testing.T) {
|
|||||||
playerFolder + "Правила.pdf", playerFolder + "Введение.pdf", playerFolder + "Вопросы.pdf",
|
playerFolder + "Правила.pdf", playerFolder + "Введение.pdf", playerFolder + "Вопросы.pdf",
|
||||||
playerFolder + "Список пидарасов.pdf", playerFolder + "Черновик расследования.pdf",
|
playerFolder + "Список пидарасов.pdf", playerFolder + "Черновик расследования.pdf",
|
||||||
hostFolder + "Ответы.pdf", hostFolder + "Лист выдачи улик.pdf",
|
hostFolder + "Ответы.pdf", hostFolder + "Лист выдачи улик.pdf",
|
||||||
|
hostFolder + "Дипломы/1 место.pdf", hostFolder + "Дипломы/2 место.pdf", hostFolder + "Дипломы/3 место.pdf",
|
||||||
} {
|
} {
|
||||||
if !bytes.HasPrefix(entries[name], []byte("%PDF-")) {
|
if !bytes.HasPrefix(entries[name], []byte("%PDF-")) {
|
||||||
t.Errorf("missing PDF %s", name)
|
t.Errorf("missing PDF %s", name)
|
||||||
@@ -168,7 +169,7 @@ func TestDownloadPrintMaterials(t *testing.T) {
|
|||||||
if !bytes.Equal(entries[playerFolder+"Улики/001 - Письмо _ улика.pdf"], f.files["clue.pdf"]) {
|
if !bytes.Equal(entries[playerFolder+"Улики/001 - Письмо _ улика.pdf"], f.files["clue.pdf"]) {
|
||||||
t.Fatal("original clue was changed or missing")
|
t.Fatal("original clue was changed or missing")
|
||||||
}
|
}
|
||||||
wantEntries := 9
|
wantEntries := 12
|
||||||
if tc.wantQR {
|
if tc.wantQR {
|
||||||
wantEntries++
|
wantEntries++
|
||||||
}
|
}
|
||||||
@@ -314,6 +315,12 @@ func TestPrintArchiveUsesScenarioTitle(t *testing.T) {
|
|||||||
if strings.Contains(name, "QR команд") {
|
if strings.Contains(name, "QR команд") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(name, hostFolder+"Дипломы/") {
|
||||||
|
if strings.Contains(text, f.scenarioName) {
|
||||||
|
t.Fatal("scenario title printed in universal diploma")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
if name == playerFolder+"Правила.pdf" || name == playerFolder+"Черновик расследования.pdf" || name == playerFolder+"Список пидарасов.pdf" {
|
if name == playerFolder+"Правила.pdf" || name == playerFolder+"Черновик расследования.pdf" || name == playerFolder+"Список пидарасов.pdf" {
|
||||||
if strings.Contains(text, f.scenarioName) {
|
if strings.Contains(text, f.scenarioName) {
|
||||||
t.Fatalf("scenario title printed in %s", name)
|
t.Fatalf("scenario title printed in %s", name)
|
||||||
|
|||||||
Reference in New Issue
Block a user