@@ -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)
}