Skip to content

PJUllrich/ogi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OGI (Oh Gee)

Generates and serves OpenGraph Images using Typst.

Inspired by OG-Image but uses Typst instead of Chrome+Puppeteer, so you can add it directly to your Phoenix app.

Generates (beautiful?) share images like this one for my blog peterullrich.com

Installation

def deps do
  [
    {:ogi, "~> 0.2.2"}
  ]
end

Setup

You need these three things:

  1. A Typst template.
  2. A Phoenix Controller and Route.
  3. An og:image metatag in your <head> tag.

1. The Typst Template

LLMs are pretty good at generating those and you can test them quickly on typst.app/play

Make sure that your markup follows the best-practices of OpenGraph Images which are:

  • Dimensions of ideally 1200x630
  • No more than 5MB
  • A bit of filled margin at the edges to prevent cropping of text

2. The Phoenix Controller and Route

Below is an example controller for serving OG Images for a blog post.

defmodule BlogWeb.ImageController do
  use BlogWeb, :controller

  alias Blog.Posts

  def show(conn, %{"id" => blog_id}) do
    post = Posts.get_post_by_id!(blog_id)
    assigns = [title: post.title]
    opts = [typst_opts: [root_dir: typst_root(), extra_fonts: [fonts_dir()]]]
    Ogi.render_image(conn, "#{blog_id}.png", typst_markup(), assigns, opts)
  end

  # these paths need to be called at runtime for releases
  defp typst_root, do: Application.app_dir(:blog, "priv/typst")
  defp fonts_dir, do: Path.join(typst_root(), "fonts")

  defp typst_markup do
    # Your Typst markup goes here.
    #
    # You can dynamically inline variables with:
    # Blog Title: <%= title %>
    #
    # Note: There is *no* @ before the variable, other than in HEEx templates!
    #
    # Example:
    """
    #set page(
      width: 1200pt,
      height: 630pt,
      margin: 64pt,
      fill: rgb("#0a1929")
    )
    #set text(size: 64pt, fill: white)

    #place(center + horizon)[Hello World!]
    #place(center + bottom)[Post: <%= title %>]
    """
  end
end

Then add this route to your router:

scope "/", BlogWeb do
  get "/og-image/:id", ImageController, :show
end

3. The Metatag

For adding dynamic Metatags, I recommend the Metatags library:

# In your Controller or LiveView serving the blog post, add this:
def handle_params(%{"id" => post_id}, _url, socket) do
  post = Posts.get_post_by_id!(post_id)

  socket =
    socket
    |> Metatags.put("og:title", post.title)
    |> Metatags.put("og:description", post.description)
    |> Metatags.put("og:image", url(~p"/og-image/#{post_id}"))

  {:ok, socket}
end

And that's it! You can test this by navigating to the route manually or by using a browser extension that previews OpenGraph information for a website.

Caveats

How to add Fonts and Images

Typst has access to system fonts, as well as fonts in directories specified by the extra_fonts option. If a font is unavailable, Typst will fallback to a serif font, unless you set fallback: false on a #text. In this case Typst will simply not render the text at all.

It is recommended to bundle fonts with your application. The example above places fonts in the priv/typst/fonts directory, and images and other file resources in priv/typst.

Don't use variable fonts

When adding fonts, make sure to add non-variable fonts (e.g. FiraSans-Bold.tff and FiraSans-SemiBold.tff etc.) instead of variable fonts (e.g. FiraSans.tff) because Typst does not support variable fonts (yet)! If you add a variable font, Typst will always render the same font weight.

Configuration

Currently, OGI supports the following configurations:

config :ogi,
  # Whether to cache rendered images or not (default: true)
  cache: true,
  # Where to store the cache. Defaults to a temporary folder.
  cache_dir: "./some/custom/dir",
  # An optional fallback image which is returned if the rendering
  # of the OG Image using Typst fails.
  fallback_image_path: "./priv/static/some-image.png"

Examples

You can find Livebooks with examples of various use-cases in the examples folder.

ToDo's

  • Clean up Cache when a certain size is reached
  • Allow async rendering. Useful for cache warmup.
  • Emoji Support
  • Support for templates
  • Allow per-request disabling cache operations
  • Add fallback OG Image option if render fails
  • Make cache dir path configurable.
  • Unit tests 😬

About

A Typst-based Elixir library for generating OpenGraph Images

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages