📝 Originally published (in Japanese) at forge.workstyle.tech.
TL;DR In an offline container, emoji were handled as CDN images and could not be fetched. Adding a TTF font made the glyph visible, but the broken image icon remained. Setting emoji not to convert to images and passing the config with --config-file removed the icon.
Conclusion First
In a container without network access, emojis were treated as images served from a CDN (a system for delivering images from an external source). Because those images couldn’t be fetched, the PDF showed broken image markers. Adding a TTF (font file format) displayed the glyphs, but the image retrieval errors remained. Adding a setting to prevent emojis from being converted to images and passing it with --config-file removed the markers.
Why Did the Broken Image Marker Remain After I Added a Font?
In the previous post, I wrote about PDF generation stopping when JavaScript was disabled. This time, I was trying to display emoji in a container with no network access.
First, I added a font. At 15:02:41, I deployed an emoji TTF into a new image layer. fc-cache updates font information, but I didn’t run it. The font was picked up by checking the font directory’s mtime (modification time). I used fc-match with charset=1f539 and confirmed that it selected the target glyph.
I thought that would fix the PDF too. But the PDF created at 17:33:36 was 301,735 bytes. The glyph for U+1F539 appeared, but the “broken image” marker remained just before it.
At first, I thought the font hadn’t loaded correctly. But the glyph was in the PDF. Looking only at the font couldn’t explain the marker. The cause was how the emoji were rendered. The initial rendering configuration used images hosted on a CDN for emoji. Because the container had no network access, it couldn’t fetch the image, so the PDF generator displayed a broken image marker before the glyph.
Fonts and images take separate paths. Adding a font for the glyph doesn’t solve the problem of being unable to fetch an external image.
Which setting stopped emoji from being rendered as images
Next, I changed how emojis were handled in the configuration. In the 17:47 commit, I enabled shortcodes (a notation for specifying emojis and other items by short names) and added a setting to prevent Unicode characters from being converted into images. I passed the configuration file to PDF generation with --config-file. I deployed it at 17:50.
The PDF generated at 22:25 was 312,020 bytes. This time, the broken image markers were gone, and the person who requested it reviewed the PDF and approved it.
In the previous article, disabling JavaScript stopped the process itself. This time, the process ran and produced a PDF. Even so, if the setting to treat emojis as external images remained, the images could not be fetched in an environment without network access. The fact that a PDF was generated alone was not enough to tell whether its contents were correct.
Why are face emoji still monochrome?
Not every emoji is fixed. Face emoji such as :smile: are still monochrome because DejaVu Sans is selected before Noto Color Emoji. Possible fixes include configuring fontconfig, which controls font selection, or setting font-family in CSS, which controls how a web page looks. I left this unresolved for now and moved on.
To try generating a real PDF locally, you need CHROME_NO_SANDBOX=1. A long TMPDIR value also causes SingletonSocket to crash. These issues are separate from emoji rendering, but they’re worth knowing before trying to reproduce the problem.
What I confirmed this time is that adding a font can make glyphs appear while a separate failure remains if the emoji is fetched as an image. I inspected the PDF contents and traced glyphs and image markers separately. Face emoji are still a task for another day.
Key takeaways
- Check the glyph and broken image icon separately in the output PDF.
- Use
fc-matchwithcharset=1f539to check whether the target glyph is selected. - Configure emoji not to convert to images and pass the file with
--config-file. - Open the PDF to confirm the icon is gone; face emoji may remain monochrome.
- For local reproduction, set
CHROME_NO_SANDBOX=1and check whether a longTMPDIRcausesSingletonSocketto fail.
Top comments (0)