Convert WriteFreely quotes to Instagram / PixelFed posts.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

129 lines
3.1 KiB

  1. /*
  2. * Copyright © 2021 A Bunch Tell LLC.
  3. *
  4. * This file is part of text-pic.
  5. *
  6. * text-pic is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License, included
  8. * in the LICENSE file in this source code package.
  9. */
  10. package textpic
  11. import (
  12. "fmt"
  13. "image/color"
  14. "path/filepath"
  15. "github.com/fogleman/gg"
  16. )
  17. func loadFont(dc *gg.Context, fontFace string, bold bool, points float64) error {
  18. fontBoldPath := filepath.Join("fonts", fontFace, fontFace+"-Bold.ttf")
  19. fontRegularPath := filepath.Join("fonts", fontFace, fontFace+"-Regular.ttf")
  20. path := fontRegularPath
  21. if bold {
  22. path = fontBoldPath
  23. }
  24. err := dc.LoadFontFace(path, points)
  25. if err != nil {
  26. return fmt.Errorf("load font: %s", err)
  27. }
  28. return nil
  29. }
  30. func GenerateImage(opt *ContentOptions, w, h int, outputFilename string) error {
  31. wf := float64(w)
  32. hf := float64(h)
  33. dc := gg.NewContext(w, h)
  34. dc.DrawRectangle(0, 0, wf, hf)
  35. dc.SetRGB(1, 1, 1)
  36. dc.Fill()
  37. // Define margins for footer text
  38. footerFontSize := 32.0
  39. footerMargin := 20.0
  40. x := footerMargin
  41. y := footerMargin
  42. footerMarginY := 20.0
  43. // Content parameters
  44. contentFontSize := 48.0
  45. lineSpacing := 1.8
  46. contentBottomMargin := 100.0
  47. contentRightMargin := 50.0
  48. contentTopMargin := 50.0
  49. contentWidth := wf - contentRightMargin - contentRightMargin
  50. // Create bold instance name
  51. err := loadFont(dc, "Lora", true, footerFontSize)
  52. if err != nil {
  53. return err
  54. }
  55. instance := opt.Instance
  56. baseTextWidth, textHeight := dc.MeasureString(instance)
  57. // Create user path
  58. err = loadFont(dc, "Lora", false, footerFontSize)
  59. if err != nil {
  60. return err
  61. }
  62. dc.SetColor(color.Black)
  63. var userPath = ""
  64. if opt.Username != "" {
  65. userPath = "/" + opt.Username
  66. }
  67. userTextWidth, _ := dc.MeasureString(userPath)
  68. // x = canvas halfway point - total text width halfway point
  69. x = wf/2 - (baseTextWidth+userTextWidth)/2
  70. y = hf - textHeight - footerMarginY
  71. err = loadFont(dc, "Lora", true, footerFontSize)
  72. if err != nil {
  73. return err
  74. }
  75. dc.DrawString(instance, x, y)
  76. // x = original x coordinate + base text width
  77. x += baseTextWidth
  78. y = hf - textHeight - footerMarginY
  79. err = loadFont(dc, "Lora", false, footerFontSize)
  80. if err != nil {
  81. return err
  82. }
  83. dc.DrawString(userPath, x, y)
  84. // Draw the content
  85. err = loadFont(dc, fonts[opt.UserFont], false, contentFontSize)
  86. if err != nil {
  87. return err
  88. }
  89. s := opt.Content
  90. lines := dc.WordWrap(s, contentWidth)
  91. // Center-align text by default for short content
  92. alignment := gg.AlignCenter
  93. linesStr := ""
  94. for i, str := range lines {
  95. linesStr += str
  96. if i != len(lines)-1 {
  97. linesStr += "\n"
  98. if alignment == gg.AlignCenter {
  99. // Since content uses multiple lines, left-align it.
  100. alignment = gg.AlignLeft
  101. }
  102. }
  103. }
  104. _, contentTextHeight := dc.MeasureMultilineString(linesStr, lineSpacing)
  105. x = contentRightMargin
  106. y = contentTopMargin - contentBottomMargin + hf/2 - contentTextHeight/2
  107. dc.DrawStringWrapped(s, x, y, 0, 0, contentWidth, lineSpacing, alignment)
  108. err = dc.SavePNG(outputFilename)
  109. if err != nil {
  110. return fmt.Errorf("save png: %s", err)
  111. }
  112. return nil
  113. }