generated from VLADIMIR/template
up
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 2.2 MiB |
@@ -3,96 +3,24 @@ package game_print_service
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/go-pdf/fpdf"
|
"github.com/go-pdf/fpdf"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed assets/paper.jpg
|
//go:embed assets/diploma.png
|
||||||
var diplomaPaper []byte
|
var diplomaImage []byte
|
||||||
|
|
||||||
var errInvalidDiplomaPlace = errors.New("некорректное место диплома")
|
func diplomaPDF() ([]byte, error) {
|
||||||
|
|
||||||
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 := fpdf.New("P", "mm", "A4", "")
|
||||||
pdf.SetMargins(20, 20, 20)
|
|
||||||
pdf.SetAutoPageBreak(false, 0)
|
pdf.SetAutoPageBreak(false, 0)
|
||||||
pdf.AddUTF8FontFromBytes("typewriter", "", typewriterFont)
|
|
||||||
pdf.SetFont("typewriter", "", 12)
|
|
||||||
pdf.AddPage()
|
pdf.AddPage()
|
||||||
options := fpdf.ImageOptions{ImageType: "JPG"}
|
options := fpdf.ImageOptions{ImageType: "PNG"}
|
||||||
paper := pdf.RegisterImageOptionsReader("diploma-paper", options, bytes.NewReader(diplomaPaper))
|
image := pdf.RegisterImageOptionsReader("diploma", options, bytes.NewReader(diplomaImage))
|
||||||
if err := pdf.Error(); err != nil {
|
if err := pdf.Error(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
height := 210 * paper.Height() / paper.Width()
|
width := min(210.0, 297*image.Width()/image.Height())
|
||||||
pdf.ClipRect(0, 0, 210, 297, false)
|
height := width * image.Height() / image.Width()
|
||||||
pdf.ImageOptions("diploma-paper", 0, (297-height)/2, 210, height, false, options, 0, "")
|
pdf.ImageOptions("diploma", (210-width)/2, (297-height)/2, width, 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)
|
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, "")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,60 +2,32 @@ package game_print_service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"image/png"
|
||||||
"os/exec"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDiplomasUsePortraitA4AndBrandAssets(t *testing.T) {
|
func TestDiplomaUsesPortraitA4AndBlankArtwork(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, name := range []string{"blank diploma"} {
|
||||||
place int
|
t.Run(name, func(t *testing.T) {
|
||||||
rank string
|
data, err := diplomaPDF()
|
||||||
}{{1, "I"}, {2, "II"}, {3, "III"}} {
|
|
||||||
t.Run(tc.rank, func(t *testing.T) {
|
|
||||||
data, err := diplomaPDF(tc.place)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if bytes.Count(data, []byte("/Type /Page\n")) != 1 || !bytes.Contains(data, []byte("/MediaBox [0 0 595.28 841.89]")) {
|
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")
|
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 {
|
if bytes.Count(data, []byte("/Subtype /Image")) != 1 || !bytes.Contains(data, []byte("/Width 1054")) || !bytes.Contains(data, []byte("/Height 1492")) {
|
||||||
t.Fatal("original paper and logo not embedded")
|
t.Fatal("diploma artwork missing")
|
||||||
}
|
}
|
||||||
if !bytes.Contains(data, []byte("/BaseFont /utf8typewriter")) {
|
artwork, err := png.Decode(bytes.NewReader(diplomaImage))
|
||||||
t.Fatal("typewriter font missing")
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Run("contents", func(t *testing.T) {
|
if artwork.Bounds().Dx() != 1054 || artwork.Bounds().Dy() != 1492 {
|
||||||
if _, err := exec.LookPath("pdftotext"); err != nil {
|
t.Fatal("unexpected artwork dimensions")
|
||||||
t.Skip("pdftotext is needed only for optional rendered-content checks")
|
}
|
||||||
}
|
if bytes.Contains(data, []byte("/Type /Font")) {
|
||||||
text := extractedPDFText(t, data)
|
t.Fatal("diploma contains added text font")
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ func (s *GamePrintService) Download(ctx context.Context, gameID int) ([]byte, st
|
|||||||
{playerFolder + "Вопросы.pdf", func() ([]byte, error) { return questionsPDF(scenario.Name, story.Questions) }},
|
{playerFolder + "Вопросы.pdf", func() ([]byte, error) { return questionsPDF(scenario.Name, story.Questions) }},
|
||||||
{playerFolder + "Список пидарасов.pdf", func() ([]byte, error) { return draftPDF("Список пидарасов") }},
|
{playerFolder + "Список пидарасов.pdf", func() ([]byte, error) { return draftPDF("Список пидарасов") }},
|
||||||
{playerFolder + "Черновик расследования.pdf", func() ([]byte, error) { return draftPDF("Черновик расследования") }},
|
{playerFolder + "Черновик расследования.pdf", func() ([]byte, error) { return draftPDF("Черновик расследования") }},
|
||||||
|
{hostFolder + "Дипломы/Диплом.pdf", diplomaPDF},
|
||||||
{hostFolder + "Ответы.pdf", func() ([]byte, error) { return answersPDF(scenario.Name, story.Questions) }},
|
{hostFolder + "Ответы.pdf", func() ([]byte, error) { return answersPDF(scenario.Name, story.Questions) }},
|
||||||
{hostFolder + "Лист выдачи улик.pdf", func() ([]byte, error) { return issuancePDF(scenario.Name, teams, clues) }},
|
{hostFolder + "Лист выдачи улик.pdf", func() ([]byte, error) { return issuancePDF(scenario.Name, teams, clues) }},
|
||||||
}
|
}
|
||||||
@@ -147,15 +148,6 @@ 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,12 +151,21 @@ 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",
|
hostFolder + "Дипломы/Диплом.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
diplomas := 0
|
||||||
|
for name := range entries {
|
||||||
|
if strings.HasPrefix(name, hostFolder+"Дипломы/") {
|
||||||
|
diplomas++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if diplomas != 1 {
|
||||||
|
t.Fatalf("diplomas = %d, want one blank diploma", diplomas)
|
||||||
|
}
|
||||||
_, hasQR := entries[hostFolder+"QR команд.pdf"]
|
_, hasQR := entries[hostFolder+"QR команд.pdf"]
|
||||||
_, hasExternal := entries[hostFolder+"Внешние материалы.pdf"]
|
_, hasExternal := entries[hostFolder+"Внешние материалы.pdf"]
|
||||||
if hasQR != tc.wantQR || hasExternal != tc.wantExternal {
|
if hasQR != tc.wantQR || hasExternal != tc.wantExternal {
|
||||||
@@ -169,7 +178,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 := 12
|
wantEntries := 10
|
||||||
if tc.wantQR {
|
if tc.wantQR {
|
||||||
wantEntries++
|
wantEntries++
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user