Skip to content

Unable to disable font ligatures in insert_htmlbox method #4981

Description

@kishaningithub

Description of the bug

I would like to disable font ligatures when creating a page using insert_htmlbox method.

I tried creating the following css to disable the same but it had no effect

 body {
   font-family:  'Calibri';
   font-size: 12pt;
   font-feature-settings: "liga" 0, "calt" 0 !important;
   font-variant-ligatures: none !important;
 }

Image

If you notice the image below in the word "Introduction" the letters "ti" as compressed into one

Image

Impact

  • If a user tries to copy the word from the PDF and pastes it into another app it becomes "Introducon" (Notice that the characters "ti" are dropped here.
  • This also affecting the search_for() function, when i pass "Introduction" as the parameter i.e. page.search_for("Introduction") it is unable to find the same

How to reproduce the bug

  • Create a pdf using insert_htmlbox method with Calibri font with content as "Introduction"

PyMuPDF version

1.27.2.3

Operating system

MacOS

Python version

3.14

Activity

  1. self-assigned this
    on May 3, 2026
  2. JorjMcKie commented on May 3, 2026

    @JorjMcKie
    Collaborator

    Parsing the HTML and CSS syntax in the text and the css parameters is done by our base library MuPDF. As documented, not the full respective syntaxes are covered by this parsing. So you will always find unsupported language expressions.

    PyMuPDF has no way of influencing this. All you can do is contacting the MuPDF team in this Discord channel.

    However: Even when your request would be implemented, it would not solve your problem, because "ti" is no official ligature. There exists no Unicode definition for it.
    The problem is caused by the font itself.

    Text searching in PyMuPDF has no problem with ligatures: they will be automatically decomposed, letting you successfully find e.g. "fi" - whether present as separate characters or as ligature "fi".
    I reproduced your observation with this snippet:

    import pymupdf
    from pathlib import Path
    
    calibri = Path("calibri.ttf").read_bytes()
    
    arch = pymupdf.Archive(calibri, "calibri")
    css = "@font-face {font-family: sans-serif; src: url(calibri);}"
    css += "* {font-family: sans-serif}"
    
    doc = pymupdf.open()
    page = doc.new_page()
    rect = (200, 200, 400, 400)
    text = "Introduction"
    page.insert_htmlbox(rect, text, archive=arch, css=css)
    print(page.get_text())  # extract with auto-replacing unknown Unicodes
    print(page.get_text(flags=0))  # extract suppressing replacement
    # doc.ez_save("test.pdf")

    The output:

    Image

    IAW: the font contains no backtranslation information for this pseudo-ligature.

    My recommendation: Use a different font!

  3. added
    not a bugnot a bug / user error / unable to reproduce
    wontfixno intention to resolve
    on May 3, 2026
  4. JorjMcKie commented on May 4, 2026

    @JorjMcKie
    Collaborator

    You can of course use page.insert_textbox() - this is a character-based output method and thus avoids the use of Harfbuzz. The result will be as you expect it.

  5. kishaningithub commented on May 4, 2026

    @kishaningithub
    Author

    @JorjMcKie My usecase is the following

    I have a page (table of contents page) created using the insert_htmlbox method and the rest of the contents of the pdf come from merging several other PDFs

    I would like to create links (preferably with <a> tag) to other pages.. I tried the following

    for toc_entry in chunk:
        title = toc_entry[1].strip()
        page_no = toc_entry[2]
        rl = page.search_for(title)
        print(f"toc_entry: {toc_entry}")
        if rl:
            link = {
                "kind": pymupdf.LINK_GOTO,
                "from": rl[0],
                "to": toc_entry[3]["to"],
                "page": page_no,
            }
            page.insert_link(link)

    but the above did not works as expected because search_for was not detecting the strings like "Introduction"

    Any pointers would be much appreciated.

  6. JorjMcKie commented on May 4, 2026

    @JorjMcKie
    Collaborator

    You could give up using .insert_htmlbox() altogether and use the Story object directly. The HTMLBOX method also makes use of it under the hood.

    If you use Story then you can let it create TOCs automatically. It also takes care of fitting content to page rectangles and much more. This would free you from above pesky problems.

    Using Story basically means that you prepare the complete content in one string, including require HTML / CSS styling, tables, and potential images to be embedded. Then use one of the Story methods to create the PDF output.

  7. JorjMcKie commented on May 4, 2026

    @JorjMcKie
    Collaborator

    BTW - You are aware of Calibri license restrictions that may apply to you? This is not a free font.

  8. kishaningithub commented on May 4, 2026

    @kishaningithub
    Author

    @JorjMcKie How to solve the linking problem using the Story object? Could not get that working. Any pointers to examples would be great..

  9. JorjMcKie commented on May 4, 2026

    @JorjMcKie
    Collaborator

    Use a method like this one to get a a PDF Document object in which the links contained in your source text have been correctly resolved.

  10. JorjMcKie commented on May 5, 2026

    @JorjMcKie
    Collaborator

    Let me know how you get on with this!

  11. kishaningithub commented on May 5, 2026

    @kishaningithub
    Author

    Definitely @JorjMcKie I am currently thinking, if there is a way i can define the internal address in the href attribute of the <a> tag. The href attribute should specify both the "page number" and the "y" location in the page so that when the user clicks the link in the first page it goes to the "exact" location.

  12. JorjMcKie commented on May 5, 2026

    @JorjMcKie
    Collaborator

    Let's move this to the Discussions tab first.

  13. locked and limited conversation to collaborators on May 5, 2026
  14. converted this issue into a discussion #4985 on May 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

not a bugnot a bug / user error / unable to reproducewontfixno intention to resolve

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions