I recently ran into an issue that really threw me for loop and took some deep digging to resolve. Since I haven't seen much out there on this, I thought I'd share this in the hopes that others will find this useful (and maybe that Microsoft will evaluate how this works for future iterations of the PDF export renderer for SSRS).
SSRS by default attempts to embed a subset of fonts used when generating a PDF document (unless it is one of those ubiquitous fonts that everyone just has. This is to make sure the PDF produced from SSRS is as close a rendition as possible to the original SSRS display of the report being exported. And that's largely a good thing. SSRS even tries to be space-conscious by only including the necessary characters from the font when embedding it. This is, however, where we run into a really strange issue.
Font Ligature Replacement
The basis of the issue is that many fonts have a substitution table embedded in them which allows certain characters or groups of characters to be replaced by special characters within the font that are designed to provide a more optimal display. One example of this is when the lowercase letters "f" and "i" are placed together, they are frequently included in that substitution list to be rendered as the Latin Ligature "ﬁ" or hexadecimal character FB01. When SSRS renders a report that includes these lowercase letters in a scenario where they should be replaced by the ligature, the subset of font that is embedded includes the ligature mark. However unless the letters are used separately within the report, the individual characters that would normally be included in the substitution list are not included in the font. Apparently the font rendering within the PDF gets a bit confused when the glyphs that are being replaced in the substitution table are not present themselves within the font.
Following are four images. These are screenshots from PDF documents exported by SSRS where the default font embedding settings are enabled (partial font embedding is the default setting):
Figure one: we include text where "f" and "i" are only displayed next to each other using a non-standard font where the ligature is in the substitution table (the example here uses Lato). SSRS Viewer on the left, PDF view on the right:
Figure Two: we include text where "f" and "i" should be replaced by the ligature, but also include a separate use of "f" outside of the ligature (so the f's are rendering because that is included in the font) SSRS Viewer on the left, PDF view on the right:
Figure Three: we include text where "f" and "I" should be replaced by the ligature, but also include a separate use of both "f" and "I" outside of the ligature. SSRS Viewer on the left, PDF view on the right:
What does this show?
Well, obviously it shows that as long as the letters are used separately, they get embedded in the font and all is well. It also shows that there must be some sort of issue with the ligature substitution table being properly embedded in the font itself as the ligature is not rendering in the PDF display when all letters get used and embedded.
So how in the world can we fix this thing right now??? Three distinct possibilities come to mind, depending on the specific needs behind the report and it's user base.
FIX 1: Stop Embedding Fonts and Install on User Machines
Obviously this only works when you have control over the target user base machines, but we can use a prior blog here
to exclude embedding the fonts within the report for specific exports (you can also edit the report server configuration to exclude font embedded on all PDF exports at the server level). This means that the PDF will attempt to render with a copy of the font on the viewer's machine. If you can install the font on all machines where the PDFs will be viewed or printed, this takes care of business. You have the added bonus of a much smaller PDF file size as well. However, when we have external users viewing or printing the PDFs, this will not work.
FIX 2: Add a non breaking space (such as hex character 200A - hair space) between any characters that are setup within the substitution tables of the desired font to prevent the use of the ligatures
Another method we can get around this might be replace all problematic characters sequences with the same characters but adding a very small space between them. This will prevent the ligature substitution from firing (depending on the font substitution list as well... any space character that gets replaced out with nothing will still allow the ligature to fire). The other downside to this is if viewers attempt to copy and paste text from the PDF elsewhere, they will have the added space characters in the result.
FIX 3: Remove the ligatures from the font substitution table
So the option which will cause the least breaking changes and effects downstream would be to modify the font files being used and removing the ligatures from the substitution tables. There is a free tool out there called FontForge
that will let us do just that (If you are using a multi-monitor setup on Windows 10 like me, you may need this post
as well to get this tool working).
First, we open the font file in FontForge. In the Element menu, select "Font Info" and select "Lookups." On the "GSUB" tab will be the list of font substitution tables within the opened font.
Where the ligature substitution appears will depend on the font developer. In this example for Lato, we look at "'liga' Standard Ligatures in Latin lookup 30 subtable" and "'liga' Standard Ligatures in Latin lookup 31 subtable."
To eliminate the issue we are seeing, you can either remove these subtables or select each table and choose "Edit Data" and delete the individual problem substitutions. Upon removing the problem substitutions, you need to select "Generate Fonts" from the file menu, and Generate the new font file. Install this new font on the SSRS Server and any SSRS development boxes. An IIS or machine restart may be necessary when replacing fonts for the new ones to take effect. Now when we render the above report from figure one to PDF, there is no issue!