Skip to main content

Working with Fonts

PDFDancer supports both standard PDF fonts and custom TrueType fonts. You can use system fonts or upload your own font files.


Standard PDF Fonts

PDF includes 14 standard fonts that are always available in all PDF readers without embedding:

Times Family (Serif)

  • Times-Roman - Standard serif font
  • Times-Bold - Bold variant
  • Times-Italic - Italic variant
  • Times-BoldItalic - Bold italic variant

Helvetica Family (Sans-serif)

  • Helvetica - Standard sans-serif font
  • Helvetica-Bold - Bold variant
  • Helvetica-Oblique - Oblique (italic) variant
  • Helvetica-BoldOblique - Bold oblique variant

Courier Family (Monospace)

  • Courier - Standard monospace font
  • Courier-Bold - Bold variant
  • Courier-Oblique - Oblique (italic) variant
  • Courier-BoldOblique - Bold oblique variant

Symbol Fonts

  • Symbol - Mathematical and special characters
  • ZapfDingbats - Decorative symbols

Using Standard Fonts

from pdfdancer import PDFDancer, StandardFonts, Color

with PDFDancer.open("document.pdf") as pdf:
# Use standard fonts with enum (recommended)
pdf.new_paragraph() \
.text("Helvetica Bold text") \
.font(StandardFonts.HELVETICA_BOLD.value, 16) \
.color(Color(255, 0, 0)) \
.at(page_number=1, x=100, y=500) \
.add()

pdf.new_paragraph() \
.text("Times Roman text") \
.font(StandardFonts.TIMES_ROMAN.value, 14) \
.at(page_number=1, x=100, y=480) \
.add()

pdf.new_paragraph() \
.text("Courier monospace code") \
.font(StandardFonts.COURIER_BOLD.value, 12) \
.at(page_number=1, x=100, y=460) \
.add()

# You can also use font names directly as strings
pdf.new_paragraph() \
.text("Direct font name") \
.font("Helvetica", 12) \
.at(page_number=1, x=100, y=440) \
.add()

pdf.save("output.pdf")

Custom Fonts

PDFDancer supports custom TrueType (.ttf) fonts. You can use them in several ways depending on your needs:

Using a font file directly

from pathlib import Path
from pdfdancer import PDFDancer, Color

with PDFDancer.open("document.pdf") as pdf:
# Use custom font directly without registration
ttf_path = Path("fonts/DancingScript-Regular.ttf")

pdf.new_paragraph() \
.text("Beautiful custom font\nwith multiple lines") \
.font_file(ttf_path, 24) \
.line_spacing(1.8) \
.color(Color(0, 0, 255)) \
.at(page_number=1, x=300, y=500) \
.add()

pdf.save("output.pdf")

Using service-hosted fonts

Browse the full list of pre-registered fonts on the Available Fonts page.

from pdfdancer import PDFDancer

with PDFDancer.open("document.pdf") as pdf:
# Search for fonts available on the service
fonts = pdf.find_fonts("Roboto", 14)

if fonts:
# Use the first match (e.g., "Roboto-Regular")
font = fonts[0]
print(f"Using: {font.name} at {font.size}pt")

pdf.new_paragraph() \
.text("Text with service font") \
.font(font.name, font.size) \
.at(page_number=1, x=300, y=500) \
.add()

pdf.save("output.pdf")

Registering fonts for reuse

from pathlib import Path
from pdfdancer import PDFDancer

with PDFDancer.open("document.pdf") as pdf:
# Register custom TTF font
custom_font_path = Path("fonts/CustomFont.ttf")
font_name = pdf.register_font(str(custom_font_path))

# Now use the registered font by name
pdf.new_paragraph() \
.text("Text with registered font") \
.font(font_name, 14) \
.at(page_number=1, x=100, y=500) \
.add()

pdf.save("output.pdf")

Font licensing and privacy

  • Uploaded fonts are private. Custom fonts you upload are only available to your account and are never shared with other users.
  • You are responsible for licensing. Make sure you have the appropriate rights to use any font you upload.
  • Service-hosted fonts are free. All pre-registered fonts available on the service are licensed under the SIL Open Font License (OFL) and free to use. See the Available Fonts page for the full list.

Font Sizes and Styling

You can combine standard fonts with different sizes, colors, and line spacing for rich typography:

from pdfdancer import PDFDancer, StandardFonts, Color

with PDFDancer.open("document.pdf") as pdf:
# Large heading with bold Helvetica
pdf.new_paragraph() \
.text("Large heading") \
.font(StandardFonts.HELVETICA_BOLD.value, 24) \
.color(Color(0, 0, 0)) \
.at(page_number=1, x=100, y=700) \
.add()

# Normal body text with Times Roman
pdf.new_paragraph() \
.text("Normal body text in Times Roman") \
.font(StandardFonts.TIMES_ROMAN.value, 12) \
.at(page_number=1, x=100, y=660) \
.add()

# Monospace code example with Courier
pdf.new_paragraph() \
.text("def hello():\n print('Hello')") \
.font(StandardFonts.COURIER_BOLD.value, 11) \
.line_spacing(1.5) \
.color(Color(40, 40, 40)) \
.at(page_number=1, x=100, y=620) \
.add()

# Small footnote
pdf.new_paragraph() \
.text("Small footnote") \
.font(StandardFonts.HELVETICA.value, 8) \
.color(Color(128, 128, 128)) \
.at(page_number=1, x=100, y=580) \
.add()

pdf.save("output.pdf")

Embedded Fonts

If you're editing text in an existing PDF, the document may contain embedded fonts. This section covers what embedded fonts are, their limitations, and how to work with them.

What Are Embedded Fonts?

Embedded fonts are font files that are included directly in a PDF document. When you open a PDF with embedded fonts, you can view the document exactly as intended without needing the original font files installed on your system. This is different from the 14 standard PDF fonts, which are built into every PDF reader.

PDF Font Types

PDF supports several font format types, each with different characteristics:

Type 1 Fonts (PostScript)

  • Traditional PostScript fonts used in professional publishing
  • Compact and efficient
  • Limited to 256 glyphs per font
  • Can be embedded as complete font or subset

Type 3 Fonts (User-Defined)

  • Custom bitmap or vector fonts defined in PDF
  • Most flexible but least efficient
  • Can contain any graphical content
  • Often used for decorative or custom glyphs
  • Not searchable or extractable - characters are stored as graphics, not text
  • No hinting information for screen display

TrueType Fonts

  • Common font format from Microsoft and Apple
  • Excellent screen rendering
  • Can contain large character sets (Unicode support)
  • Usually embedded as subsets to reduce file size

OpenType/CFF Fonts

  • Modern font format combining TrueType and PostScript features
  • Superior typography features
  • Cross-platform compatibility
  • Large character set support

Why Embedded Fonts Have Limitations

When you edit text in a PDF that uses embedded fonts, you may encounter several limitations:

1. Subset Restrictions

  • Most PDFs embed only the characters actually used (font subsetting)
  • If you try to add new characters not in the original text, they won't be available
  • Example: Original PDF has "Hello" - trying to change it to "Help!" might fail because "p" and "!" weren't in the subset

2. Type 3 Font Issues

  • Type 3 fonts are rendered as graphics, not text
  • Modifying Type 3 font text often requires converting to a different font type
  • Search and copy operations may not work properly

3. Licensing and Encoding

  • Some embedded fonts include usage restrictions
  • Character encoding may be custom or non-standard
  • Font metrics might not support all glyph variations

4. No Font File Access

  • The embedded data is just enough to render existing characters
  • You can't add new glyphs or extend the character set
  • Font hinting and kerning data may be limited

Working with Embedded Fonts in PDFDancer

When you modify text that uses embedded fonts, PDFDancer will:

  1. Check character availability - Verify if the new text can be encoded with the embedded font
  2. Return warnings - The CommandResult will include a warning if there are encoding issues
  3. Provide font recommendations - The TextStatus includes font recommendations with similarity scores
  4. Indicate font type - Check FontType to see if the font is EMBEDDED, STANDARD, or SYSTEM
from pdfdancer import PDFDancer

with PDFDancer.open("document.pdf") as pdf:
paragraphs = pdf.page(1).select_paragraphs()

for para in paragraphs:
if para.status:
# Check font type
if para.status.get_font_type().value == "EMBEDDED":
print(f"⚠️ Embedded font: {para.font_name}")

# Check if text is encodable
if not para.status.is_encodable():
print("Cannot encode new characters with this font")

# Get font recommendation
rec = para.status.get_font_recommendation()
print(f"Recommended: {rec.get_font_name()} (similarity: {rec.get_similarity_score():.2f})")

# Attempt to modify embedded font text
para = paragraphs[0]
result = para.edit().replace("New text").apply()

if result.warning:
print(f"Warning: {result.warning}")
# Consider using a standard font instead
result = para.edit().replace("New text").font("Helvetica", 12).apply()

Common Problems

"Font does not support encoding: No glyph for U+XXXX"

java.io.IOException: Font does not support encoding: No glyph for U+F020 () in font SourceSans3-Regular

This error means the text you are trying to render contains a character that does not exist in the selected font. The Unicode code point in the message (e.g. U+F020) tells you exactly which character is missing.

Common causes:

  • Private Use Area characters (U+E000–U+F8FF). These code points are used by icon fonts (Font Awesome, Material Icons, etc.) and have no standard meaning. Regular text fonts like SourceSans3 or Roboto do not contain glyphs for them. If your source text includes icon characters, you need to either strip them or switch to the icon font that defines them.
  • Subset embedded fonts. If you are reusing an embedded font from an existing PDF, it may only contain the glyphs for the original text. Adding new characters that were not in the original document will fail. See Embedded Fonts for details.
  • Missing script or symbol coverage. The font may simply not support the script or symbols in your text (e.g. CJK characters in a Latin-only font).

How to fix it:

  1. Identify the problematic character from the U+XXXX code in the error message.
  2. Either remove or replace that character in your text, or switch to a font that contains it.
  3. If you are working with embedded fonts, consider using a standard font or uploading a custom font with full glyph coverage instead.

Next Steps