py-pdf / fpdf2

Simple PDF generation for Python
https://py-pdf.github.io/fpdf2/
GNU Lesser General Public License v3.0
1.12k stars 254 forks source link

KeyError: '.notdef' when adding a font with add_font() #1161

Closed Gdesau closed 6 months ago

Gdesau commented 6 months ago

Hello everyone, I'm trying to add a unicode Times New Roman font but I get a KeyError. Here is my code:

The function:

import os
from fpdf import FPDF

def trans_txt_to_pdf(trans_txt, output_dir):
    """
    Takes a .txt file of a transcription and
    creates a pdf of the transcription with
    the file name as title.

    Parameters
    ----------
    trans_txt: path to the .txt file containing
    the transcription (str)
    output_dir: path to the directory where
    to save the pdf created (str)

    Returns
    -------
    trans_pdf: path to the pdf created (str)

    """
    # Instantiate the pdf object
    pdf = FPDF()
    # Add a page
    pdf.add_page()
    # Add a unicode font to avoid error with
    # characters
    pdf.add_font(
        'tnr-uni',
        style='',
        fname='/System/Library/Fonts/Supplemental/Times New Roman.ttf'
    )
    pdf.add_font(
        'tnr-uni',
        style='b',
        fname='/System/Library/Fonts/Supplemental/Times New Roman Bold.ttf'
    )
    # Set the font parameters for title
    pdf.set_font('times-new-roman', 'bu', 18)
    # Retrieve the .txt file name
    trans_name = os.path.splitext(
        trans_txt.split('/')[-1]
    )[0]
    # Add the title cell
    pdf.cell(w=0, text=trans_name, align='C')
    # Linebreak
    pdf.ln(12)
    # Set the font parameters for the
    # transcription
    pdf.set_font(
        family='times-new-roman',
        style='',
        size=12
    )
    # Insert the transcription
    ## Open the .txt file in read mode
    with open(trans_txt, 'r') as txt_file:
        ## Read the lines of .txt file
        transcritpion = txt_file.read()
    # Close the .txt file to avoid issues
    txt_file.close()
    ## Insert them into the pdf
    pdf.multi_cell(0, 8, transcritpion)
    ## Linebreak
    pdf.ln()
    # Create the output path
    ## Add the .pdf extension to
    ## transcription title
    file_name = trans_name+'.pdf'
    ## Concatenate output dir and file name
    out_path = os.path.join(
        output_dir, file_name
    )
    # Save the pdf to a .pdf file, use .encode()
    # to avoid UnicodeEncodeError
    pdf.output(out_path)
    # Return the path to the created pdf file
    return out_path

The code where I use the function:

pdf_dir = '/Users/my_user/Dev/projects/project_dir/files/pdf'
trans_txt_to_pdf('/Users/my_user/Dev/projects/project_dir/files/txt/test_court_transcription.txt', pdf_dir)

And here is the error I get:

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Cell In[27], line 1
----> 1 trans_txt_to_pdf('/Users/my_user/Dev/projects/project_dir/files/txt/test_court_transcription.txt', pdf_dir)

Cell In[26], line 30, in trans_txt_to_pdf(trans_txt, output_dir)
     23 # Add a unicode font to avoid error with
     24 # characters
     25 pdf.add_font(
     26     'tnr-uni',
     27     style='',
     28     fname='/System/Library/Fonts/Supplemental/Times New Roman.ttf'
     29 )
---> 30 pdf.add_font(
     31     'tnr-uni',
     32     style='b',
     33     fname='/System/Library/Fonts/Supplemental/Times New Roman Bold.ttf'
     34 )
     35 # Set the font parameters for title
     36 pdf.set_font('times-new-roman', 'bu', 18)

File ~/anaconda3/envs/my_env/lib/python3.11/site-packages/fpdf/fpdf.py:1854, in FPDF.add_font(self, family, style, fname, uni)
   1848     warnings.warn(
   1849         f"Core font or font already added '{fontkey}': doing nothing",
   1850         stacklevel=get_stack_level(),
   1851     )
   1852     return
-> 1854 self.fonts[fontkey] = TTFFont(self, font_file_path, fontkey, style)

File ~/anaconda3/envs/my_env/lib/python3.11/site-packages/fpdf/fonts.py:169, in TTFFont.__init__(self, fpdf, font_file_path, fontkey, style)
    164 self.ttfont = ttLib.TTFont(
    165     self.ttffile, recalcTimestamp=False, fontNumber=0, lazy=True
    166 )
    168 self.scale = 1000 / self.ttfont["head"].unitsPerEm
--> 169 default_width = round(self.scale * self.ttfont["hmtx"].metrics[".notdef"][0])
    171 try:
    172     cap_height = self.ttfont["OS/2"].sCapHeight

KeyError: '.notdef'

My Environment

Does anyone have any idea on how to fix this? It's weird because it was working with the Arial font. Thanks in advance!

gmischler commented 6 months ago

As far as I understand, it is mandatory for a TTF font to include a ".notdef" glyph (with metrics). This glyph is used in a lot of software when the actually requested glyph is not present in the font (usually displaying as a box, possibly with a question mark or X inside).

Your error message looks like this glyph (specifically: its size information) is missing from your font. If that is indeed the case, then there's little we can do about it, other than maybe produce a more informative error message.

Gdesau commented 6 months ago

Hi @gmischler, thanks a lot for your quick answer! Maybe it's due to the fact that it's the system version of "Times New Roman" or you think that it doesn't contain this glyph at all?

gmischler commented 6 months ago

Apple has some of their own specialities with regards to font file formats. I'm not sure if we have ever systematically tested our code with Apple fonts. It's possible that we might need to do something a little bit differently with those.

Of course, it is also still possible that you're just dealing with a broken font...

Lucas-C commented 6 months ago

Do you have any other request regarding this @Gdesau? Or can this issue be closed?

Gdesau commented 6 months ago

Hi @Lucas-C, the issue can be closed, thanks @gmischler for your precious help.