diff --git a/main.go b/main.go index 23e26f07bf109a2e813f2b501716b3e574ba5e0c..de9269865d18cc6d582430a4e0caff18e63ad517 100644 --- a/main.go +++ b/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) }