DEV Community

Cover image for Make a beautiful OG image effortlessly for Qwik (emoji support)
YURII DE.
YURII DE.

Posted on • Edited on

Make a beautiful OG image effortlessly for Qwik (emoji support)

Original: https://github.com/fabian-hiller/valibot/blob/main/website/src/routes/og-image/index.ts
Fonts: https://github.com/fabian-hiller/valibot/tree/main/website/src/fonts

This script "OG image" created for the website Qit.tools.

import type { RequestHandler } from "@builder.io/qwik-city";
import { fetchFont, html, ImageResponse } from "og-img";

const U200D = String.fromCharCode(8205);
const UFE0Fg = /\uFE0F/g;

/**
 * Calculates the icon code based on the character.
 *
 * @param {string} char - The character to calculate the icon code for.
 * @return {string} The icon code calculated based on the character.
 */
function getIconCode(char: string): string {
  return toCodePoint(char.indexOf(U200D) < 0 ? char.replace(UFE0Fg, "") : char);
}

/**
 * Converts a string of Unicode surrogates into a string of hexadecimal code points.
 *
 * @param {string} unicodeSurrogates - The string of Unicode surrogates to convert.
 * @return {string} The string of hexadecimal code points.
 */
function toCodePoint(unicodeSurrogates: string): string {
  const r = [];
  let c = 0,
    p = 0,
    i = 0;

  while (i < unicodeSurrogates.length) {
    c = unicodeSurrogates.charCodeAt(i++);
    if (p) {
      r.push((65536 + ((p - 55296) << 10) + (c - 56320)).toString(16));
      p = 0;
    } else if (55296 <= c && c <= 56319) {
      p = c;
    } else {
      r.push(c.toString(16));
    }
  }
  return r.join("-");
}

/**
 * Generates a URL for a Twemoji SVG image based on the given code.
 *
 * @param {string} code - The code representing the Twemoji.
 * @return {string} The URL of the Twemoji SVG image.
 */
export const apis = {
  twemoji: (code: string) =>
    "https://cdnjs.cloudflare.com/ajax/libs/twemoji/15.1.0/svg/" +
    code.toLowerCase() +
    ".svg",
  openmoji: "https://cdn.jsdelivr.net/npm/@svgmoji/openmoji@2.0.0/svg/",
  blobmoji: "https://cdn.jsdelivr.net/npm/@svgmoji/blob@2.0.0/svg/",
  noto: "https://cdn.jsdelivr.net/gh/svgmoji/svgmoji/packages/svgmoji__noto/svg/",
  fluent: (code: string) =>
    "https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/" +
    code.toLowerCase() +
    "_color.svg",
  fluentFlat: (code: string) =>
    "https://cdn.jsdelivr.net/gh/shuding/fluentui-emoji-unicode/assets/" +
    code.toLowerCase() +
    "_flat.svg",
};

/**
 * Asynchronously loads emoji based on the provided code and type.
 *
 * @param {string} code - The code representing the emoji.
 * @param {keyof typeof apis} [type] - The type of emoji API to use.
 * @return {Promise<string>} The loaded emoji as text.
 */
async function loadEmoji(
  code: string,
  type?: keyof typeof apis,
): Promise<string> {
  if (!type || !apis[type]) {
    type = "twemoji";
  }

  const api = apis[type];
  if (typeof api === "function") {
    const r = await fetch(api(code));
    return await r.text();
  }
  const r_1 = await fetch(`${api}${code.toUpperCase()}.svg`);
  return await r_1.text();
}

/**
 * Asynchronously loads an additional asset based on the provided code and segment.
 *
 * @param {string} code - The code representing the asset.
 * @param {string} segment - The segment of the asset.
 * @return {Promise<string>} The loaded asset code or the asset itself as a data URL.
 */
async function loadAdditionalAsset(
  code: string,
  segment: string,
): Promise<string> {
  // console.log(code, segment, Buffer.from(segment).toString('base64'));
  // console.log(getIconCode(segment));
  // console.log(await loadEmoji(getIconCode(segment)));
  if (code === "emoji") {
    return (
      `data:image/svg+xml;base64,` + btoa(await loadEmoji(getIconCode(segment)))
    );
  }

  return code;
}

export const onGet: RequestHandler = async ({ cacheControl, send, url }) => {
  // Disable caching
  cacheControl("no-cache");

  // Get data from search params
  const title = url.searchParams.get("title");
  const description = url.searchParams.get("description");
  const path = url.searchParams.get("path");

  // Create icon and font directory URL
  const iconUrl = import.meta.env.PUBLIC_WEBSITE_URL + "/pwa-192x192.png";
  const fontDirUrl = import.meta.env.PUBLIC_WEBSITE_URL + "/fonts";

  // Create Lexend 400 font object
  const lexend400 = {
    name: "Lexend",
    data: await fetchFont(fontDirUrl + "/lexend-400.ttf"),
    style: "normal",
    weight: 400,
  } as const;

  // Create Lexend 500 font object
  const lexend500 = {
    name: "Lexend",
    data: await fetchFont(fontDirUrl + "/lexend-500.ttf"),
    style: "normal",
    weight: 500,
  } as const;

  // Create Lexend Exa 500 font object
  const lexendExa500 = {
    name: "Lexend Exa",
    data: await fetchFont(fontDirUrl + "/lexend-exa-500.ttf"),
    style: "normal",
    weight: 500,
  } as const;

  // If title is available, return image with text
  if (title) {
    send(
      new ImageResponse(
        html`
          <div
            tw="flex h-full w-full flex-col justify-between bg-indigo-900 p-16"
            style="font-family: 'Lexend';background-image: linear-gradient( 120deg, #0f172a 15%, #172554 );"
          >
            <div tw="flex items-center justify-between">
              <div tw="flex items-center">
                <img tw="w-16 h-16" src="${iconUrl}" />
                <div tw="flex items-end">
                  <div tw="text-5xl font-medium text-slate-300 ml-2">Qit.</div>
                  <div class="ml-1 flex pb-0 text-base text-slate-300">
                    tools
                  </div>
                </div>
              </div>
              <div
                tw="max-w-[70%] text-3xl text-slate-400"
                style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap"
              >
                qit.tools${path ? path : ""}
              </div>
            </div>
            <div tw="flex flex-col">
              <h1
                tw="max-w-[90%] text-5xl font-medium leading-normal text-slate-200"
                style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap"
              >
                ${title}
              </h1>
              <p
                tw="text-3xl text-slate-400 leading-normal"
                style="${description ? "" : "display: none"}}"
              >
                ${description
                  ? description.length > 170
                    ? description.slice(0, 170).trimEnd() + "..."
                    : description
                  : ""}
              </p>
            </div>
          </div>
        `,
        {
          width: 1200,
          height: 630,
          fonts: [lexend400, lexend500, lexendExa500],
          loadAdditionalAsset,
        },
      ),
    );

    // Otherwise, return image just with logo
  } else {
    send(
      new ImageResponse(
        html`
          <div
            tw="flex h-full w-full items-center justify-center bg-slate-900"
            style="font-family: 'Lexend Exa';background-image: linear-gradient( 120deg, #0f172a 15%, #172554 );"
          >
            <div tw="flex items-center">
              <img tw="w-36 h-36" src="${iconUrl}" />
              <div tw="flex items-end">
                <div tw="text-9xl font-medium text-slate-300 ml-10">Qit.</div>
                <div class="ml-2 flex pb-2 text-2xl text-slate-300">tools</div>
              </div>
            </div>
          </div>
        `,
        {
          width: 1200,
          height: 630,
          fonts: [lexendExa500],
          loadAdditionalAsset,
        },
      ),
    );
  }
};

Enter fullscreen mode Exit fullscreen mode

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more