Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,6 @@
.trakt-share-poster {
border-radius: 12px;

box-shadow:
0px 3.08px 7px 0px rgba(19, 21, 23, 0.16),
0px 12.6px 12.6px 0px rgba(19, 21, 23, 0.14),
0px 28.28px 16.8px 0px rgba(19, 21, 23, 0.08),
0px 50.12px 20.16px 0px rgba(19, 21, 23, 0.02),
0px 78.4px 21.84px 0px rgba(19, 21, 23, 0);
box-shadow: 0px 12px 32px 0px rgba(19, 21, 23, 0.22);
}
</style>
13 changes: 9 additions & 4 deletions projects/client/src/routes/api/shareable-image/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { buildImageMetadata } from './_internal/buildImageMetadata.ts';
import { buildImagePath } from './_internal/buildImagePath.ts';
import { fetchMediaData } from './_internal/fetchMediaData.ts';
import { fetchWithUserAgent } from './_internal/fetchWithUserAgent.ts';
import { loadShareFonts } from './_internal/loadShareFonts.ts';
import { resolvePosterDataUri } from './_internal/resolvePosterDataUri.ts';

const cacheControl = 'public, max-age=604800';
Expand Down Expand Up @@ -79,10 +80,13 @@ export const GET: RequestHandler = async (

const { media, ratings, crew } = mediaData;

const posterDataUri = await resolvePosterDataUri({
posterUrl: media.poster.url.medium,
fetch: fetchFn,
});
const [posterDataUri, fonts] = await Promise.all([
resolvePosterDataUri({
posterUrl: media.poster.url.medium,
fetch: fetchFn,
}),
loadShareFonts({ bucket: platform?.env?.R2_WALTER }),
]);

const { width, height } = SHARE_TYPE_DIMENSIONS[shareType];

Expand All @@ -92,6 +96,7 @@ export const GET: RequestHandler = async (
{
width,
height,
fonts,
debug: IS_DEV && url.searchParams.get('debug') === 'true',
},
{ media, crew, ratings, posterUrl: posterDataUri, variant: shareType },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { describe, expect, it, vi } from 'vitest';

type LoadShareFonts = typeof import('./loadShareFonts.ts')['loadShareFonts'];
type Bucket = Parameters<LoadShareFonts>[0]['bucket'];

const REGULAR = 'assets/fonts/NotoSans-Regular.ttf';
const BOLD = 'assets/fonts/NotoSans-Bold.ttf';

async function freshLoader(): Promise<LoadShareFonts> {
vi.resetModules();
const { loadShareFonts } = await import('./loadShareFonts.ts');

return loadShareFonts;
}

function bucketWith(paths: ReadonlyArray<string>) {
const get = vi.fn((path: string) => {
if (!paths.includes(path)) {
return Promise.resolve(null);
}

return Promise.resolve({
arrayBuffer: () => Promise.resolve(new ArrayBuffer(8)),
});
});

return { get } as unknown as Bucket & { get: typeof get };
}

describe('util: loadShareFonts', () => {
describe('when the bucket has both weights', () => {
it('should return a regular and a bold face', async () => {
const loadShareFonts = await freshLoader();

const fonts = await loadShareFonts({
bucket: bucketWith([REGULAR, BOLD]),
});

expect(fonts?.map((font) => font.weight)).toEqual([400, 700]);
});

it('should read each face only once across calls', async () => {
const loadShareFonts = await freshLoader();
const bucket = bucketWith([REGULAR, BOLD]);

await loadShareFonts({ bucket });
await loadShareFonts({ bucket });

expect(bucket.get).toHaveBeenCalledTimes(2);
});
});

describe('when the fonts are unavailable', () => {
it('should fall back when there is no bucket', async () => {
const loadShareFonts = await freshLoader();

const fonts = await loadShareFonts({ bucket: undefined });

expect(fonts).toBeUndefined();
});

it('should fall back when a face is missing', async () => {
const loadShareFonts = await freshLoader();

const fonts = await loadShareFonts({ bucket: bucketWith([REGULAR]) });

expect(fonts).toBeUndefined();
});

it('should retry on the next call rather than caching the failure', async () => {
const loadShareFonts = await freshLoader();

await loadShareFonts({ bucket: bucketWith([]) });
const fonts = await loadShareFonts({
bucket: bucketWith([REGULAR, BOLD]),
});

expect(fonts?.map((font) => font.weight)).toEqual([400, 700]);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { error } from '$lib/utils/console/print.ts';
import type { R2Bucket } from '@cloudflare/workers-types';
import type { ImageResponseOptions } from '@ethercorps/sveltekit-og';

type ShareFonts = NonNullable<ImageResponseOptions['fonts']>;

type LoadShareFontsProps = {
bucket: Pick<R2Bucket, 'get'> | Nil;
};

const FONT_FAMILY = 'Inter';
Comment thread
Marius-TV marked this conversation as resolved.

const FONT_SOURCES = [
{ path: 'assets/fonts/NotoSans-Regular.ttf', weight: 400 },
{ path: 'assets/fonts/NotoSans-Bold.ttf', weight: 700 },
] as const;

let cachedFonts: ShareFonts | undefined;

export async function loadShareFonts(
{ bucket }: LoadShareFontsProps,
): Promise<ShareFonts | undefined> {
if (cachedFonts) {
return cachedFonts;
}

if (!bucket) {
return;
}

try {
cachedFonts = await Promise.all(
FONT_SOURCES.map(async ({ path, weight }) => {
const object = await bucket.get(path);

if (!object) {
throw new Error(`Missing font in R2: ${path}`);
}

return {
name: FONT_FAMILY,
data: await object.arrayBuffer(),
weight,
style: 'normal',
} as const;
}),
);

return cachedFonts;
} catch (e) {
error('Failed to load share fonts, falling back to the bundled set:', e);
return;
}
}
Loading