remove subtitle and render headlessly

Amolith created

Change summary

main.go | 182 ++++++++++++++++++++++++++++++++--------------------------
1 file changed, 100 insertions(+), 82 deletions(-)

Detailed changes

main.go 🔗

@@ -1,16 +1,19 @@
 package main
 
 import (
+	"bytes"
 	"embed"
 	"fmt"
 	"image"
 	"image/color"
+	"image/png"
+	"io/ioutil"
 	"os"
 	"strings"
 
-	"gioui.org/app"
 	"gioui.org/font/opentype"
-	"gioui.org/io/system"
+	"gioui.org/gpu/headless"
+	"gioui.org/io/router"
 	"gioui.org/layout"
 	"gioui.org/op"
 	"gioui.org/op/clip"
@@ -28,9 +31,9 @@ var (
 	flagHelp          *bool   = flag.BoolP("help", "h", false, "Show the help message")
 	flagInput         *string = flag.StringP("input", "i", "", "Path to input Markdown")
 	flagOutput        *string = flag.StringP("output", "o", "", "Path to output PNG")
-	flagMetaSize      *int    = flag.IntP("metasize", "m", 30, "Size of font for meta information")
-	flagPostTitleSize *int    = flag.IntP("posttitlesize", "p", 50, "Size of font for post title")
-	flagSiteTitleSize *int    = flag.IntP("sitetitlesize", "s", 40, "Size of font for site title")
+	flagMetaSize      *int    = flag.IntP("metasize", "m", 40, "Size of font for meta information")
+	flagPostTitleSize *int    = flag.IntP("posttitlesize", "p", 60, "Size of font for post title")
+	flagSiteTitleSize *int    = flag.IntP("sitetitlesize", "s", 50, "Size of font for site title")
 )
 
 //go:embed fonts
@@ -46,114 +49,131 @@ func main() {
 
 	validateFlags()
 
-	postTitle, postSubtitle, postDate, postContent := getPostInfo(*flagInput)
+	postTitle, postDate, postContent := getPostInfo(*flagInput)
 	postReadTime := getReadTime(postContent)
 	siteTitle := getSiteTitle()
 	dateEdited := getGitDate(*flagInput)
 
-	// TODO: Render information to image
-	fmt.Printf(`Title:          %s
-Subtitle:       %s
-Read Time:      %d minutes
-Date Published: %s
-Date Edited:    %s
-Site Title:     %s
-`, postTitle, postSubtitle, postReadTime, postDate, dateEdited, siteTitle)
-
 	collection := fontCollection()
 
-	go func() {
-		w := app.NewWindow(app.Title("p2c"), app.Size(unit.Dp(1200), unit.Dp(630)))
-		var ops op.Ops
-		th := material.NewTheme(collection)
-		for e := range w.Events() {
-			switch e := e.(type) {
-			case system.FrameEvent:
-				gtx := layout.NewContext(&ops, e)
-
-				layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx,
+	sz := image.Point{X: 1200, Y: 630}
 
+	w, err := headless.NewWindow(sz.X, sz.Y)
+	if err != nil {
+		panic(err)
+	}
+	th := material.NewTheme(collection)
+	gtx := layout.Context{
+		Ops:   new(op.Ops),
+		Queue: new(router.Router),
+	}
+	w.Frame(gtx.Ops)
+	layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
+		gtx.Constraints.Max.X = int(float64(gtx.Constraints.Max.X) * .6)
+		gtx.Constraints.Min.X = gtx.Constraints.Max.X
+		gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
+
+		return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle, Spacing: layout.SpaceAround}.Layout(gtx,
+
+			layout.Rigid(layout.Spacer{Height: 50}.Layout),
+
+			layout.Rigid(func(gtx layout.Context) layout.Dimensions {
+				gtx.Constraints.Max.X = int(float64(gtx.Constraints.Max.X) * .9)
+				gtx.Constraints.Min.X = gtx.Constraints.Max.X
+				title := material.Label(th, unit.Sp(float32(*flagPostTitleSize)), postTitle)
+				title.Font = text.Font{Typeface: "Primary font", Variant: "", Style: text.Regular, Weight: text.Bold}
+				title.Alignment = text.Middle
+				return title.Layout(gtx)
+			}),
+
+			layout.Rigid(func(gtx layout.Context) layout.Dimensions {
+				gtx.Constraints.Max.X = int(float64(gtx.Constraints.Max.X) * .7)
+				gtx.Constraints.Min.X = gtx.Constraints.Max.X
+				return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
 					layout.Rigid(func(gtx layout.Context) layout.Dimensions {
-						return layout.Spacer{Height: unit.Dp(100)}.Layout(gtx)
-					}),
-
-					layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
-						title := material.Label(th, unit.Sp(float32(*flagPostTitleSize)), postTitle)
-						title.Font = text.Font{Typeface: "Primary font", Variant: "", Style: text.Regular, Weight: text.Bold}
-						title.Alignment = text.Middle
-						return title.Layout(gtx)
-					}),
-
-					layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
-						layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
-							layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
+						return layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceBetween}.Layout(gtx,
+							layout.Rigid(func(gtx layout.Context) layout.Dimensions {
 								rTime := material.Label(th, unit.Sp(float32(*flagMetaSize)), "Reading Time: ")
 								rTime.Font = text.Font{Typeface: "Primary font", Variant: "", Style: text.Regular, Weight: text.Bold}
-								rTime.Alignment = text.Middle
+								rTime.Alignment = text.Start
 								return rTime.Layout(gtx)
 							}),
-							layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
+							layout.Rigid(func(gtx layout.Context) layout.Dimensions {
 								rTime := material.Label(th, unit.Sp(float32(*flagMetaSize)), fmt.Sprint(postReadTime, " minutes"))
 								rTime.Font = text.Font{Typeface: "Primary font", Variant: "", Style: text.Regular, Weight: text.Normal}
-								rTime.Alignment = text.Middle
+								rTime.Alignment = text.End
 								return rTime.Layout(gtx)
 							}),
 						)
+					}),
 
-						layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
-							layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
+					layout.Rigid(func(gtx layout.Context) layout.Dimensions {
+						return layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceBetween}.Layout(gtx,
+							layout.Rigid(func(gtx layout.Context) layout.Dimensions {
 								pDate := material.Label(th, unit.Sp(float32(*flagMetaSize)), "Published: ")
 								pDate.Font = text.Font{Typeface: "Primary font", Variant: "", Style: text.Regular, Weight: text.Bold}
-								pDate.Alignment = text.Middle
+								pDate.Alignment = text.Start
 								return pDate.Layout(gtx)
 							}),
-							layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
+							layout.Rigid(func(gtx layout.Context) layout.Dimensions {
 								pDate := material.Label(th, unit.Sp(float32(*flagMetaSize)), fmt.Sprint(postDate))
-								pDate.Font = text.Font{Typeface: "Primary font", Variant: "", Style: text.Regular, Weight: text.Bold}
-								pDate.Alignment = text.Middle
+								pDate.Font = text.Font{Typeface: "Primary font", Variant: "", Style: text.Regular, Weight: text.Normal}
+								pDate.Alignment = text.End
 								return pDate.Layout(gtx)
 							}),
 						)
+					}),
 
-						layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
-							layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
+					layout.Rigid(func(gtx layout.Context) layout.Dimensions {
+						return layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceBetween}.Layout(gtx,
+							layout.Rigid(func(gtx layout.Context) layout.Dimensions {
 								eDate := material.Label(th, unit.Sp(float32(*flagMetaSize)), "Edited: ")
 								eDate.Font = text.Font{Typeface: "Primary font", Variant: "", Style: text.Regular, Weight: text.Bold}
-								eDate.Alignment = text.Middle
+								eDate.Alignment = text.Start
 								return eDate.Layout(gtx)
 							}),
-							layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
+							layout.Rigid(func(gtx layout.Context) layout.Dimensions {
 								eDate := material.Label(th, unit.Sp(float32(*flagMetaSize)), fmt.Sprint(dateEdited))
-								eDate.Font = text.Font{Typeface: "Primary font", Variant: "", Style: text.Regular, Weight: text.Bold}
-								eDate.Alignment = text.Middle
+								eDate.Font = text.Font{Typeface: "Primary font", Variant: "", Style: text.Regular, Weight: text.Normal}
+								eDate.Alignment = text.End
 								return eDate.Layout(gtx)
 							}),
 						)
-						return layout.Dimensions{}
-					}),
-
-					layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
-						size := image.Pt(700, 4)
-						defer clip.Rect{Max: size}.Push(&ops).Pop()
-						paint.ColorOp{Color: color.NRGBA{A: 0xFF}}.Add(&ops)
-						paint.PaintOp{}.Add(&ops)
-						return layout.Dimensions{Size: size}
-					}),
-
-					layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
-						sTitle := material.Body1(th, siteTitle)
-						sTitle.Font = text.Font{Typeface: "Primary font", Variant: "", Style: text.Regular, Weight: text.Bold}
-						sTitle.Alignment = text.Middle
-						return sTitle.Layout(gtx)
 					}),
 				)
+			}),
+
+			layout.Rigid(func(gtx layout.Context) layout.Dimensions {
+				size := image.Pt(gtx.Constraints.Max.X, 4)
+				defer clip.Rect{Max: size}.Push(gtx.Ops).Pop()
+				paint.ColorOp{Color: color.NRGBA{A: 0xFF}}.Add(gtx.Ops)
+				paint.PaintOp{}.Add(gtx.Ops)
+				return layout.Dimensions{Size: size}
+			}),
+
+			layout.Rigid(func(gtx layout.Context) layout.Dimensions {
+				gtx.Constraints.Max.X = int(float64(gtx.Constraints.Max.X) * .9)
+				gtx.Constraints.Min.X = gtx.Constraints.Max.X
+				sTitle := material.Label(th, unit.Sp(float32(*flagSiteTitleSize)), siteTitle)
+				sTitle.Font = text.Font{Typeface: "Primary font", Variant: "", Style: text.Regular, Weight: text.Bold}
+				sTitle.Alignment = text.Middle
+				return sTitle.Layout(gtx)
+			}),
+			layout.Rigid(layout.Spacer{Height: 50}.Layout),
+		)
+	})
+	img := image.NewRGBA(image.Rectangle{Max: sz})
+	err = w.Screenshot(img)
+	if err != nil {
+		fmt.Println("Error: Could not transform GUI to image")
+	}
+	var buf bytes.Buffer
+	if err := png.Encode(&buf, img); err != nil {
+		fmt.Println("Error: Could not encode image to PNG")
+	}
+	ioutil.WriteFile(*flagOutput, buf.Bytes(), 0o640)
 
-				e.Frame(gtx.Ops)
-			}
-		}
-		os.Exit(0)
-	}()
-	app.Main()
+	os.Exit(0)
 }
 
 func fontCollection() []text.FontFace {
@@ -262,7 +282,6 @@ p2c is meant for use with Hugo.
 It looks at...
 - The Markdown file's frontmatter fields for
   - title
-  - subtitle
   - date
 - The site's config.{toml/yaml/yml} fields for
   - title (site title)
@@ -284,7 +303,7 @@ func validateFlags() {
 }
 
 // Get the post's title, subtitle, and date
-func getPostInfo(input string) (string, string, string, string) {
+func getPostInfo(input string) (string, string, string) {
 	if _, err := os.Stat(input); os.IsNotExist(err) {
 		fmt.Println("Error: Input file does not exist")
 		fmt.Println(err)
@@ -299,9 +318,8 @@ func getPostInfo(input string) (string, string, string, string) {
 	}
 
 	var fm struct {
-		Title    string `yaml:"title"`
-		Subtitle string `yaml:"subtitle"`
-		Date     string `yaml:"date"`
+		Title string `yaml:"title"`
+		Date  string `yaml:"date"`
 	}
 
 	content, err := frontmatter.Parse(strings.NewReader(string(fileContents)), &fm)
@@ -313,7 +331,7 @@ func getPostInfo(input string) (string, string, string, string) {
 
 	fm.Date = strings.Split(fm.Date, "T")[0]
 
-	return fm.Title, fm.Subtitle, fm.Date, string(content)
+	return fm.Title, fm.Date, string(content)
 }
 
 // Get the read time of the post
@@ -333,7 +351,7 @@ func getSiteTitle() string {
 		validConf = conf
 	}
 	if validConf == "" {
-		fmt.Println("Error: No valid config file found")
+		fmt.Println("Error: No valid config file found, please run this in the root of a Hugo site")
 		os.Exit(1)
 	}