Initial Commit

This commit is contained in:
2021-12-17 21:21:58 +01:00
commit ec3dbbaf3a
20 changed files with 1495 additions and 0 deletions

21
pkg/covergen/color.go Normal file
View File

@@ -0,0 +1,21 @@
package covergen
import "github.com/go-pdf/fpdf"
type Color struct {
R uint8
G uint8
B uint8
}
func (c Color) Text(pdf *fpdf.Fpdf) {
pdf.SetTextColor(int(c.R), int(c.G), int(c.B))
}
func (c Color) Draw(pdf *fpdf.Fpdf) {
pdf.SetDrawColor(int(c.R), int(c.G), int(c.B))
}
func (c Color) Fill(pdf *fpdf.Fpdf) {
pdf.SetFillColor(int(c.R), int(c.G), int(c.B))
}

View File

@@ -0,0 +1 @@
{"Tp":"TrueType","Name":"Mark-Light","Desc":{"Ascent":790,"Descent":-210,"CapHeight":700,"Flags":32,"FontBBox":{"Xmin":-463,"Ymin":-239,"Xmax":1072,"Ymax":954},"ItalicAngle":0,"StemV":70,"MissingWidth":599},"Up":-309,"Ut":52,"Cw":[599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,599,265,260,507,728,700,956,690,351,400,400,433,600,244,418,260,492,600,600,600,600,600,600,600,600,600,600,260,244,600,600,600,424,990,703,700,764,801,671,644,819,783,271,576,684,602,930,785,843,666,843,671,672,608,755,693,1104,687,632,664,369,492,369,599,468,599,622,623,512,622,561,438,618,599,239,240,521,239,965,599,596,623,622,407,487,452,593,515,863,503,514,533,420,351,420,599,599,700,599,244,599,411,705,599,599,599,599,672,310,1130,599,664,599,599,244,244,411,411,437,601,813,599,674,487,310,991,599,533,632,265,260,599,700,599,700,599,655,599,949,599,499,599,418,440,599,384,599,599,599,599,599,599,375,599,599,599,499,599,599,599,424,703,703,703,703,703,703,1133,764,671,671,671,671,271,271,271,271,888,785,843,843,843,843,843,600,843,755,755,755,755,632,666,608,622,622,622,622,622,622,910,512,561,561,561,561,239,239,239,239,595,599,596,596,596,596,596,599,596,593,593,593,593,514,623,514],"Enc":"cp1252","Diff":"","File":"Mark-Light.z","Size1":0,"Size2":0,"OriginalSize":49024,"N":0,"DiffN":0}

Binary file not shown.

View File

@@ -0,0 +1 @@
{"Tp":"TrueType","Name":"Mark-Medium","Desc":{"Ascent":790,"Descent":-210,"CapHeight":700,"Flags":32,"FontBBox":{"Xmin":-491,"Ymin":-237,"Xmax":1083,"Ymax":994},"ItalicAngle":0,"StemV":70,"MissingWidth":605},"Up":-368,"Ut":68,"Cw":[605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,605,270,265,556,740,700,992,727,371,425,425,475,600,253,423,265,504,600,600,600,600,600,600,600,600,600,600,265,253,600,600,600,445,986,724,702,766,794,682,652,820,794,303,575,712,615,944,796,847,662,847,686,676,604,765,705,1090,678,664,646,388,504,388,605,470,605,624,624,514,624,560,422,616,605,269,272,543,269,945,605,611,624,624,430,506,454,598,531,837,526,549,521,440,371,440,605,605,700,605,253,605,448,815,605,605,605,605,676,319,1127,605,646,605,605,253,253,448,448,423,589,807,605,790,506,319,968,605,521,664,270,265,605,700,605,700,605,654,605,956,605,531,605,423,509,605,421,605,605,605,605,605,605,328,605,605,605,531,605,605,605,445,724,724,724,724,724,724,1159,766,682,682,682,682,303,303,303,303,851,796,847,847,847,847,847,600,847,765,765,765,765,664,662,621,624,624,624,624,624,624,882,514,560,560,560,560,269,269,269,269,608,605,611,611,611,611,611,605,611,598,598,598,598,549,624,549],"Enc":"cp1252","Diff":"","File":"Mark-Medium.z","Size1":0,"Size2":0,"OriginalSize":47520,"N":0,"DiffN":0}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,251 @@
!00 U+0000 .notdef
!01 U+0001 .notdef
!02 U+0002 .notdef
!03 U+0003 .notdef
!04 U+0004 .notdef
!05 U+0005 .notdef
!06 U+0006 .notdef
!07 U+0007 .notdef
!08 U+0008 .notdef
!09 U+0009 .notdef
!0A U+000A .notdef
!0B U+000B .notdef
!0C U+000C .notdef
!0D U+000D .notdef
!0E U+000E .notdef
!0F U+000F .notdef
!10 U+0010 .notdef
!11 U+0011 .notdef
!12 U+0012 .notdef
!13 U+0013 .notdef
!14 U+0014 .notdef
!15 U+0015 .notdef
!16 U+0016 .notdef
!17 U+0017 .notdef
!18 U+0018 .notdef
!19 U+0019 .notdef
!1A U+001A .notdef
!1B U+001B .notdef
!1C U+001C .notdef
!1D U+001D .notdef
!1E U+001E .notdef
!1F U+001F .notdef
!20 U+0020 space
!21 U+0021 exclam
!22 U+0022 quotedbl
!23 U+0023 numbersign
!24 U+0024 dollar
!25 U+0025 percent
!26 U+0026 ampersand
!27 U+0027 quotesingle
!28 U+0028 parenleft
!29 U+0029 parenright
!2A U+002A asterisk
!2B U+002B plus
!2C U+002C comma
!2D U+002D hyphen
!2E U+002E period
!2F U+002F slash
!30 U+0030 zero
!31 U+0031 one
!32 U+0032 two
!33 U+0033 three
!34 U+0034 four
!35 U+0035 five
!36 U+0036 six
!37 U+0037 seven
!38 U+0038 eight
!39 U+0039 nine
!3A U+003A colon
!3B U+003B semicolon
!3C U+003C less
!3D U+003D equal
!3E U+003E greater
!3F U+003F question
!40 U+0040 at
!41 U+0041 A
!42 U+0042 B
!43 U+0043 C
!44 U+0044 D
!45 U+0045 E
!46 U+0046 F
!47 U+0047 G
!48 U+0048 H
!49 U+0049 I
!4A U+004A J
!4B U+004B K
!4C U+004C L
!4D U+004D M
!4E U+004E N
!4F U+004F O
!50 U+0050 P
!51 U+0051 Q
!52 U+0052 R
!53 U+0053 S
!54 U+0054 T
!55 U+0055 U
!56 U+0056 V
!57 U+0057 W
!58 U+0058 X
!59 U+0059 Y
!5A U+005A Z
!5B U+005B bracketleft
!5C U+005C backslash
!5D U+005D bracketright
!5E U+005E asciicircum
!5F U+005F underscore
!60 U+0060 grave
!61 U+0061 a
!62 U+0062 b
!63 U+0063 c
!64 U+0064 d
!65 U+0065 e
!66 U+0066 f
!67 U+0067 g
!68 U+0068 h
!69 U+0069 i
!6A U+006A j
!6B U+006B k
!6C U+006C l
!6D U+006D m
!6E U+006E n
!6F U+006F o
!70 U+0070 p
!71 U+0071 q
!72 U+0072 r
!73 U+0073 s
!74 U+0074 t
!75 U+0075 u
!76 U+0076 v
!77 U+0077 w
!78 U+0078 x
!79 U+0079 y
!7A U+007A z
!7B U+007B braceleft
!7C U+007C bar
!7D U+007D braceright
!7E U+007E asciitilde
!7F U+007F .notdef
!80 U+20AC Euro
!82 U+201A quotesinglbase
!83 U+0192 florin
!84 U+201E quotedblbase
!85 U+2026 ellipsis
!86 U+2020 dagger
!87 U+2021 daggerdbl
!88 U+02C6 circumflex
!89 U+2030 perthousand
!8A U+0160 Scaron
!8B U+2039 guilsinglleft
!8C U+0152 OE
!8E U+017D Zcaron
!91 U+2018 quoteleft
!92 U+2019 quoteright
!93 U+201C quotedblleft
!94 U+201D quotedblright
!95 U+2022 bullet
!96 U+2013 endash
!97 U+2014 emdash
!98 U+02DC tilde
!99 U+2122 trademark
!9A U+0161 scaron
!9B U+203A guilsinglright
!9C U+0153 oe
!9E U+017E zcaron
!9F U+0178 Ydieresis
!A0 U+00A0 space
!A1 U+00A1 exclamdown
!A2 U+00A2 cent
!A3 U+00A3 sterling
!A4 U+00A4 currency
!A5 U+00A5 yen
!A6 U+00A6 brokenbar
!A7 U+00A7 section
!A8 U+00A8 dieresis
!A9 U+00A9 copyright
!AA U+00AA ordfeminine
!AB U+00AB guillemotleft
!AC U+00AC logicalnot
!AD U+00AD hyphen
!AE U+00AE registered
!AF U+00AF macron
!B0 U+00B0 degree
!B1 U+00B1 plusminus
!B2 U+00B2 twosuperior
!B3 U+00B3 threesuperior
!B4 U+00B4 acute
!B5 U+00B5 mu
!B6 U+00B6 paragraph
!B7 U+00B7 periodcentered
!B8 U+00B8 cedilla
!B9 U+00B9 onesuperior
!BA U+00BA ordmasculine
!BB U+00BB guillemotright
!BC U+00BC onequarter
!BD U+00BD onehalf
!BE U+00BE threequarters
!BF U+00BF questiondown
!C0 U+00C0 Agrave
!C1 U+00C1 Aacute
!C2 U+00C2 Acircumflex
!C3 U+00C3 Atilde
!C4 U+00C4 Adieresis
!C5 U+00C5 Aring
!C6 U+00C6 AE
!C7 U+00C7 Ccedilla
!C8 U+00C8 Egrave
!C9 U+00C9 Eacute
!CA U+00CA Ecircumflex
!CB U+00CB Edieresis
!CC U+00CC Igrave
!CD U+00CD Iacute
!CE U+00CE Icircumflex
!CF U+00CF Idieresis
!D0 U+00D0 Eth
!D1 U+00D1 Ntilde
!D2 U+00D2 Ograve
!D3 U+00D3 Oacute
!D4 U+00D4 Ocircumflex
!D5 U+00D5 Otilde
!D6 U+00D6 Odieresis
!D7 U+00D7 multiply
!D8 U+00D8 Oslash
!D9 U+00D9 Ugrave
!DA U+00DA Uacute
!DB U+00DB Ucircumflex
!DC U+00DC Udieresis
!DD U+00DD Yacute
!DE U+00DE Thorn
!DF U+00DF germandbls
!E0 U+00E0 agrave
!E1 U+00E1 aacute
!E2 U+00E2 acircumflex
!E3 U+00E3 atilde
!E4 U+00E4 adieresis
!E5 U+00E5 aring
!E6 U+00E6 ae
!E7 U+00E7 ccedilla
!E8 U+00E8 egrave
!E9 U+00E9 eacute
!EA U+00EA ecircumflex
!EB U+00EB edieresis
!EC U+00EC igrave
!ED U+00ED iacute
!EE U+00EE icircumflex
!EF U+00EF idieresis
!F0 U+00F0 eth
!F1 U+00F1 ntilde
!F2 U+00F2 ograve
!F3 U+00F3 oacute
!F4 U+00F4 ocircumflex
!F5 U+00F5 otilde
!F6 U+00F6 odieresis
!F7 U+00F7 divide
!F8 U+00F8 oslash
!F9 U+00F9 ugrave
!FA U+00FA uacute
!FB U+00FB ucircumflex
!FC U+00FC udieresis
!FD U+00FD yacute
!FE U+00FE thorn
!FF U+00FF ydieresis

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

247
pkg/covergen/render.go Normal file
View File

@@ -0,0 +1,247 @@
package covergen
import (
"embed"
"fmt"
"strings"
"github.com/go-pdf/fpdf"
"github.com/rs/zerolog/log"
)
const margin = 20
const miwebbLogoName = "miwebbLogo"
const miwebbLogoPath = "miwebb.white.png"
const logoScale = 0.6
const gridXTicks = 10
const gridYTicks = 6
const gridMargin = 5
const lineHeight = 1.2
const maxFontSize = 100
var bgColor = Color{25, 25, 25}
var textColor = Color{255, 255, 255}
var gridColor = Color{80, 80, 80}
var gridNoduleColor = Color{255, 255, 255}
//go:embed miwebb.white.png font_embed
var fs embed.FS
const fontsEmbedPrefix = "font_embed/"
// Copied from fPDF's source, can't get it dynamically
// constant for mm is just to ensure you don't change it without noting the scale factor that depends on it
const scaleUnit = "mm"
const scaleFactor = 72.0 / 25.4
type CoverSettings struct {
Number string
NumberPrefix string
CustomerName string
HLColor Color
}
func GenerateInvoice(settings CoverSettings) (*fpdf.Fpdf, error) {
pdf := fpdf.New("P", scaleUnit, "A4", "")
if err := addEmbeddedFont(pdf, "Mark Medium", "", "Mark-Medium"); err != nil {
log.Fatal().Err(err).Msg("Error adding font")
}
if err := addEmbeddedFont(pdf, "Mark Light", "", "Mark-Light"); err != nil {
log.Fatal().Err(err).Msg("Error adding font")
}
miwebbLogo, err := addEmbeddedLogo(pdf, miwebbLogoName, miwebbLogoPath)
if err != nil {
log.Fatal().Err(err).Msg("Error adding logo")
}
pdf.SetMargins(margin, margin, margin)
pageWidth, pageHeight := pdf.GetPageSize()
pdf.SetAutoPageBreak(false, 0)
pdf.SetHeaderFunc(func() {
bgColor.Fill(pdf)
pdf.Rect(0, 0, pageWidth, pageHeight, "F")
pdf.Image(miwebbLogoName, margin-5, margin, miwebbLogo.Width()*logoScale, miwebbLogo.Height()*logoScale, false, "", 0, "")
})
pdf.AddPage()
drawCustomerName(pdf, settings)
drawInvoiceNumber(pdf, settings)
drawMiwebbLink(pdf)
pdf.AddPage()
drawCustomerName(pdf, settings)
if pdf.Err() {
return nil, pdf.Error()
}
return pdf, nil
}
func drawInvoiceNumber(pdf *fpdf.Fpdf, settings CoverSettings) {
_, pageHeight := pdf.GetPageSize()
pdf.MoveTo(margin, pageHeight-margin)
pdf.SetFont("Mark Light", "", 9)
textColor.Text(pdf)
pdf.TransformBegin()
pdf.TransformRotate(90, margin, pageHeight-margin)
invoiceText := strings.ToUpper(fmt.Sprintf("%s %s", settings.NumberPrefix, settings.Number))
pdf.CellFormat(0, 1, invoiceText, "", 0, "LT", false, 0, "")
pdf.TransformEnd()
_, h := pdf.GetFontSize()
// X is 'margin + half text height'
x := float64(margin) + h/2
// Y is 'where the text ended' + some gap
y := pageHeight - margin - pdf.GetStringWidth(invoiceText) - 5
textColor.Draw(pdf)
pdf.SetLineWidth(0.01)
pdf.Line(x, y, x, y-10)
}
func drawMiwebbLink(pdf *fpdf.Fpdf) {
pageWidth, pageHeight := pdf.GetPageSize()
pdf.MoveTo(pageWidth-margin, pageHeight-margin)
pdf.SetFont("Mark Light", "", 9)
textColor.Text(pdf)
pdf.CellFormat(0, 0, "WWW.MIWEBB.COM", "", 0, "RB", false, 0, "https://www.miwebb.com")
}
func drawCustomerName(pdf *fpdf.Fpdf, settings CoverSettings) {
ml, _, mr, _ := pdf.GetMargins()
pw, ph := pdf.GetPageSize()
width := pw - ml - mr
height := (width/gridXTicks - 1) * gridYTicks
text := strings.ToUpper(settings.CustomerName)
// Draw the grid with a weird gray color
gridColor.Draw(pdf)
pdf.SetLineWidth(0.01)
gridX, gridY := ml, (ph/2)-(height/2)
drawGrid(pdf, gridX, gridY, width, height, gridXTicks, gridYTicks)
// Draw the highlight on the right
drawNoduleLine(
pdf,
gridX+width, gridY,
gridX+width, gridY+height,
1.2, gridNoduleColor,
0.25, settings.HLColor)
pdf.SetFont("Mark Medium", "", 10)
// Estimate font size, and calculate line height immediately after
pdf.SetFontSize(estimateFontSize(pdf, text, width, height, gridMargin))
_, sz := pdf.GetFontSize()
sz *= lineHeight
_, textHeight := stringSize(pdf, text, sz, 0)
// Centered vertically:
// x = <gridX> + <margin>
// y = <gridY> + <gridH>/2 - textHeight/2
pdf.MoveTo(ml+gridMargin, ph/2-(textHeight/2))
textColor.Text(pdf)
pdf.MultiCell(width-2*gridMargin, sz, text, "", "ML", false)
}
func drawGrid(pdf *fpdf.Fpdf, x, y, w, h float64, xs, ys int) {
// Horizontal lines
for i := 0; i < ys; i++ {
yOff := y + (h/float64(ys-1))*float64(i)
pdf.Line(x, yOff, x+w, yOff)
}
// Vertical line(pw/2)-(width/2)s
for i := 0; i < xs; i++ {
xOff := x + (w/float64(xs-1))*float64(i)
pdf.Line(xOff, y, xOff, y+h)
}
}
func drawNoduleLine(pdf *fpdf.Fpdf, x1, y1, x2, y2, squareSize float64, squareColor Color, lineWidth float64, lineColor Color) {
lineColor.Draw(pdf)
pdf.SetLineWidth(lineWidth)
pdf.Line(x1, y1, x2, y2)
squareColor.Fill(pdf)
pdf.Rect(x1-(squareSize/2), y1-(squareSize/2), squareSize, squareSize, "F")
pdf.Rect(x2-(squareSize/2), y2-(squareSize/2), squareSize, squareSize, "F")
}
func estimateFontSize(pdf *fpdf.Fpdf, text string, containerWidth, containerHeight, margin float64) float64 {
// Margin is on both sides haha, but I add an extra just for 'safety'
maxWidth := containerWidth - 3*margin
maxHeight := containerHeight - 3*margin
// Probably smallest we can start with
for fontSize := 10.; fontSize <= maxFontSize; fontSize += 1 {
width, height := stringSize(pdf, text, (fontSize/scaleFactor)*lineHeight, fontSize)
if width >= maxWidth || height >= maxHeight {
return fontSize - 1
}
}
return maxFontSize
}
func stringSize(pdf *fpdf.Fpdf, text string, lineHeight, fontSizePt float64) (width, height float64) {
if fontSizePt == 0 {
fontSizePt, _ = pdf.GetFontSize()
}
lines := strings.Split(text, "\n")
height = float64(len(lines)) * lineHeight
for _, line := range lines {
lineWidth := float64(pdf.GetStringSymbolWidth(line)) * fontSizePt / scaleFactor / 1000
if lineWidth > width {
width = lineWidth
}
}
return
}
func addEmbeddedFont(pdf *fpdf.Fpdf, family, style, path string) error {
jsonData, err := fs.ReadFile(fontsEmbedPrefix + path + ".json")
if err != nil {
return err
}
zData, err := fs.ReadFile(fontsEmbedPrefix + path + ".z")
if err != nil {
return err
}
pdf.AddFontFromBytes(family, style, jsonData, zData)
if pdf.Err() {
return pdf.Error()
}
return nil
}
func addEmbeddedLogo(pdf *fpdf.Fpdf, name, path string) (*fpdf.ImageInfoType, error) {
reader, err := fs.Open(path)
if err != nil {
return nil, err
}
handle := pdf.RegisterImageOptionsReader(name, fpdf.ImageOptions{
ImageType: "PNG",
ReadDpi: true,
}, reader)
if pdf.Err() {
return nil, pdf.Error()
}
return handle, nil
}