Using `fontfile` with text input · Issue #4577 · lovell/sharp · GitHub
Skip to content

Using fontfile with text input #4577

Open

Description

@delucis

Question about an existing feature

What are you trying to achieve?

I’m creating an image using the { text } input and want to use a custom font (not a system-installed font) via the the fontfile option, but can’t get it working. (Apologies in advance if I’m holding it wrong.)

When you searched for similar issues, what did you find that might be related?

#3778, #4523, and #4208. #4208 in particular seems to overlap a lot, but the discussion didn’t help me.

Please provide a minimal, standalone code sample, without other dependencies, that demonstrates this question

await sharp({
	text: {
		text: 'example',
		width: 800,
		height: 800,
		font: 'Relevant Font',
		fontfile: "/Users/user/repo/path/to/font.ttf",
	},
})
	.png()
	.toBuffer();

As an example, I downloaded this TTF font: https://github.com/propublica/weepeople/blob/master/weepeople.ttf
Set fontfile to point to it.
And tried various font values (WeePeople, WeePeople Regular, Wee People Regular, weepeople, etc.)
(The issue does not appear to be specific to this font file though, I’ve tried with various fonts with the same result.)

In all cases the fontfile appears to have no effect.

I tried setting FC_DEBUG=1 on the process running sharp. This caused FC_DEBUG=1 to be logged the first time sharp was called. But there was no other additional logging.

In case it’s relevant, some system info: Node.js 24.18.0 on macOS 15.7.7 (darwin-x64 versions of sharp/libvips via npm)

Please provide sample image(s) that help explain this question

Here’s what the code above produces, regardless of font/fontfile options:

Image

Activity

  1. lovell commented on Jul 29, 2026

    @lovell
    Owner

    Hello, I tried this locally (Linux x64, Node.js 24.18.0) with the sample font and get what I think is the expected output:

    await sharp({
    	text: {
    		text: 'example',
    		width: 800,
    		height: 800,
    		font: 'WeePeople',
    		fontfile: "./weepeople.ttf",
    	},
    })
    	.png()
    	.toFile('out.png');
    Image

    Please can you try adding the NODE_DEBUG=sharp environment variable to see if there are any other warnings during font loading.

  2. delucis commented on Jul 29, 2026

    @delucis
    Author

    NODE_DEBUG=sharp is moderately helpful in that it at least logs a warning if the fontfile path is inaccurate (SHARP 44421: unable to load fontfile), but even with the path correct, I still can’t get it to use the font for some reason 🤔

    I was running inside an Astro file, so tried a plain old .js file I ran with node, but same result.

    Here’s the full script I ran (basically the same as yours with a different fontfile path:

    import sharp from 'sharp';
    
    await sharp({
    	text: {
    		text: 'example',
    		width: 800,
    		height: 800,
    		font: 'WeePeople',
    		fontfile: './src/assets/weepeople.ttf',
    	},
    })
    	.png()
    	.toFile('test.png');

    And the output file it produced:

    Image
  3. kleisauke commented on Jul 30, 2026

    @kleisauke
    Contributor

    Pango defaults to the CoreText rendering and mapping backend on macOS. You could try to use the Fontconfig backend with the PANGOCAIRO_BACKEND="fontconfig" environment variable. See: #2399.

  4. lovell commented on Jul 30, 2026

    @lovell
    Owner

    Thanks for checking, are you able to reproduce on any platform other than Intel-based macOS? Perhaps something is going wrong creating the prebuilt binaries? I took a look at the build logs and there is a compiler warning on macOS:

    ../pango/pangocoretext.c:263:17: warning: incompatible pointer types assigning to 'PangoFontMap *' (aka 'struct _PangoFontMap *') from 'PangoCoreTextFontMap *' (aka 'struct _PangoCoreTextFontMap *') [-Wincompatible-pointer-types]
    

    It's also worth explicitly setting the PANGOCAIRO_BACKEND=fontconfig environment variable in case something else is overriding it - see https://sharp.pixelplumbing.com/install/#fonts

    UPDATE: I hadn't see Kleis' comment when posting this (thanks to GitHub's annoyingly-laggy UI), so yes, PANGOCAIRO_BACKEND is probably your friend here.

  5. delucis commented on Jul 30, 2026

    @delucis
    Author

    Thanks for the help both!

    I can confirm that running the script with PANGOCAIRO_BACKEND="fontconfig" produces the expected output on the Intel macOS.

    are you able to reproduce on any platform other than Intel-based macOS?

    I just tried on an M2 Mac and found new issues! 😄

    > node test-script.js
    Fontconfig error: Cannot load default config file: File not found
    # script hung here and had to be exited manually
    
    > PANGOCAIRO_BACKEND="fontconfig" node test-script.js
    Fontconfig error: Cannot load default config file: File not found
    Fontconfig error: Cannot load default config file: File not found
    # script hung here and had to be exited manually

    (Edit: just clicked that the script hanging was on me for not catching the async error when await-ing the call to sharp)

    This comment helped me though: #4208 (comment)

    1. I created an empty font.conf next to the script:

      <?xml version="1.0"?>
      <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
      <fontconfig>
      </fontconfig>
    2. I updated the script based on Using custom fonts from a ttf file #4208 (comment) to set the FONTCONFIG_FILE variable and running fc-cache before sharp() runs. (The path needs to be an absolute path from my tests, hence resolving it rather than just using the relative ./fonts.conf path.)

      import { execSync } from 'node:child_process';
      import { fileURLToPath } from 'node:url';
      import sharp from 'sharp';
      
      const configFilePath = fileURLToPath(new URL('./fonts.conf', import.meta.url));
      // Set environment variable for fontconfig
      process.env.FONTCONFIG_FILE = configFilePath;
      // Refresh the font cache with your custom fonts directory
      execSync('fc-cache -fv');
      
      await sharp({
      	text: {
      		text: 'example',
      		width: 800,
      		height: 800,
      		font: 'WeePeople',
      		fontfile: './src/assets/weepeople.ttf',
      	},
      })
      	.png()
      	.toFile('test.png');
    3. I did still have to run with PANGOCAIRO_BACKEND="fontconfig" to get the expected output.

    I guess it’s hard to know if that stuff is needed when building in a repo that needs to run in various environments (e.g. different dev machines + CI)? I’m not sure how I’d check if fonts.conf is missing and only do this when required.


    Edit: I’m not 100% sure if the fc-cache step is needed. I tried running it in the terminal to reset it and then commented it out in the script. The script still produced the correct output. Not sure if that’s a) because the previous run of the script changed some state that persisted or b) because that step is not actually needed.

  6. lovell commented on Jul 30, 2026

    @lovell
    Owner

    Does running brew install fontconfig on the ARM-based macOS also "fix" this, or at least provide enough basic plumbing for fontconfig? IIRC running fc-cache does things like scan for system fonts so probably isn't required when an explicit path to a font file is provided.

    We can probably improve sharp a bit here by emiting a warning on macOS when using fontfile but PANGOCAIRO_BACKEND is unset.

  7. delucis commented on Jul 31, 2026

    @delucis
    Author

    Does running brew install fontconfig on the ARM-based macOS also "fix" this

    Didn’t change anything for me, no. The basic script still errors with “Fontconfig error: Cannot load default config file: File not found” even after running brew install fontconfig and restarting the terminal session for good measure.

  8. lovell commented on Aug 2, 2026

    @lovell
    Owner

    I've opened a PR for a possible improvement to libvips around fontfile error/warning handling - see libvips/libvips#5162

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions