Skip to main content Link Search Menu Expand Document Toggle dark mode Copy Code (external link)

Font Configuration

Scryber’s font system supports custom TrueType/OpenType fonts loaded from the file system, with a multi-tiered registry for font lookup. The system supports:

  • Custom user fonts (TTF/OTF files)
  • System-installed fonts
  • Embedded Type 1 fonts (PDF standard fonts)
  • Generic font family mappings

Font Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Template Request                                      β”‚
β”‚  <style>                                              β”‚
β”‚    body { font-family: Roboto; }                     β”‚
β”‚  </style>                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Font Registry Lookup (4 tiers)                       β”‚
β”‚  1. Custom Registry (user-registered fonts)          β”‚
β”‚  2. System Registry (OS fonts)                       β”‚
β”‚  3. Static Registry (embedded Type 1)                β”‚
β”‚  4. Generic Registry (family mappings)               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Font Selection                                        β”‚
β”‚  Match: Family + Weight + Style β†’ Font File         β”‚
β”‚  Extract: TTF metadata, glyph metrics               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ PDF Embedding                                         β”‚
β”‚  Subset glyphs, embed font data in PDF              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Font Registries

1. Static Registry (Embedded Type 1)

Built-in PDF standard fonts (always available, no file required):

  • Times-Roman, Times-Bold, Times-Italic, Times-BoldItalic
  • Helvetica, Helvetica-Bold, Helvetica-Oblique, Helvetica-BoldOblique
  • Courier, Courier-Bold, Courier-Oblique, Courier-BoldOblique
  • Symbol, ZapfDingbats

2. System Registry (Platform Fonts)

Discovered from OS font directories:

  • Windows: C:\Windows\Fonts\
  • macOS: /Library/Fonts/, /System/Library/Fonts/
  • Linux: /usr/share/fonts/, /usr/local/share/fonts/

Loaded lazily on first font request.

3. Custom Registry (User-Registered)

Fonts explicitly registered via configuration or API.

4. Generic Registry (Family Mappingsancements)

Generic family names mapped to concrete fonts:

  • sans-serif β†’ Helvetica
  • serif β†’ Times-Roman
  • monospace β†’ Courier
  • cursive β†’ (system-dependent)
  • fantasy β†’ (system-dependent)

Configuration

Basic Font Registration

scrybersettings.json:

{
  "Scryber": {
    "Fonts": {
      "Register": [
        {
          "Family": "Roboto",
          "File": "/path/to/fonts/Roboto-Regular.ttf",
          "Bold": "/path/to/fonts/Roboto-Bold.ttf",
          "Italic": "/path/to/fonts/Roboto-Italic.ttf",
          "BoldItalic": "/path/to/fonts/Roboto-BoldItalic.ttf"
        },
        {
          "Family": "Open Sans",
          "File": "/path/to/fonts/OpenSans-Regular.ttf",
          "Bold": "/path/to/fonts/OpenSans-Bold.ttf"
        }
      ]
    }
  }
}

Font Registration Properties

Property Required Description
Family Yes Font family name (used in CSS font-family)
File Yes Path to regular weight font file (.ttf or .otf)
Bold No Path to bold weight font file
Italic No Path to italic style font file
BoldItalic No Path to bold+italic font file

Path Resolution

Paths can be:

  • Absolute: /usr/share/fonts/myfonts/Roboto-Regular.ttf
  • Relative to app: fonts/Roboto-Regular.ttf
  • Relative to configuration file: ./fonts/Roboto-Regular.ttf

Template Usage

Once registered, use fonts in templates:

<html xmlns='http://www.w3.org/1999/xhtml'>
    <head>
        <style>
            .heading {
                font-family: Roboto;
                font-size: 24pt;
                font-weight: bold;
            }
            .body {
                font-family: 'Open Sans';
                font-size: 11pt;
            }
        </style>
    </head>
    <body>
        <main>
            <span class='heading'>Hello World</span>
            <span class='body'>This uses Open Sans font.</span>
        </main>
    </body>
</html>

Implementation Details

Font Loading

Font registration in FontFactory.UnsafeLoadCustomFonts():

// Scryber.Drawing/FontFactory.cs
private static void UnsafeLoadCustomFonts(FontOptions options)
{
    if (options.Register != null && options.Register.Count > 0)
    {
        foreach (var reg in options.Register)
        {
            try
            {
                // Load regular weight
                if (!string.IsNullOrEmpty(reg.File))
                {
                    var info = LoadFontFile(reg.File);
                    _custom[info.FamilyName + "|" + info.Weight + "|" + info.Style] = info;
                }
                
                // Load bold
                if (!string.IsNullOrEmpty(reg.Bold))
                {
                    var info = LoadFontFile(reg.Bold);
                    _custom[info.FamilyName + "|Bold|Regular"] = info;
                }
                
                // Load italic
                if (!string.IsNullOrEmpty(reg.Italic))
                {
                    var info = LoadFontFile(reg.Italic);
                    _custom[info.FamilyName + "|Regular|Italic"] = info;
                }
                
                // Load bold+italic
                if (!string.IsNullOrEmpty(reg.BoldItalic))
                {
                    var info = LoadFontFile(reg.BoldItalic);
                    _custom[info.FamilyName + "|Bold|Italic"] = info;
                }
            }
            catch (Exception ex)
            {
                // Log error but continue loading other fonts
                TraceLog.Add(TraceLevel.Error, "FontFactory", 
                    $"Failed to load font '{reg.Family}': {ex.Message}");
            }
        }
    }
}

Font Lookup Hierarchy

Font resolution in FontFactory.GetFont():

public static PDFFont GetFont(string family, FontWeight weight, FontStyle style)
{
    string key = BuildFontKey(family, weight, style);
    
    // 1. Check custom registry
    if (_custom.TryGetValue(key, out FontInfo customFont))
    {
        return CreateFontFromInfo(customFont);
    }
    
    // 2. Check system registry
    if (_system == null)
        LoadSystemFonts();
        
    if (_system.TryGetValue(key, out FontInfo systemFont))
    {
        return CreateFontFromInfo(systemFont);
    }
    
    // 3. Check static registry (Type 1 fonts)
    if (_static.TryGetValue(key, out FontInfo staticFont))
    {
        return CreateFontFromInfo(staticFont);
    }
    
    // 4. Check generic registry
    string genericFamily = ResolveGenericFamily(family);
    if (genericFamily != family)
    {
        return GetFont(genericFamily, weight, style);  // Recursive lookup
    }
    
    // 5. Fallback to Helvetica
    return GetFont("Helvetica", FontWeight.Regular, FontStyle.Regular);
}

Font Key Generation

private static string BuildFontKey(string family, FontWeight weight, FontStyle style)
{
    string weightStr = weight == FontWeight.Bold ? "Bold" : "Regular";
    string styleStr = style == FontStyle.Italic ? "Italic" : "Regular";
    
    return $"{family}|{weightStr}|{styleStr}";
}

Advanced Configuration

Multiple Font Weights

Register a complete font family with multiple weights:

{
  "Scryber": {
    "Fonts": {
      "Register": [
        {
          "Family": "Roboto",
          "File": "fonts/Roboto-Regular.ttf",
          "Bold": "fonts/Roboto-Bold.ttf",
          "Italic": "fonts/Roboto-Italic.ttf",
          "BoldItalic": "fonts/Roboto-BoldItalic.ttf"
        },
        {
          "Family": "Roboto",
          "Weight": "Light",
          "File": "fonts/Roboto-Light.ttf",
          "Italic": "fonts/Roboto-LightItalic.ttf"
        },
        {
          "Family": "Roboto",
          "Weight": "Medium",
          "File": "fonts/Roboto-Medium.ttf",
          "Italic": "fonts/Roboto-MediumItalic.ttf"
        }
      ]
    }
  }
}

Programmatic Registration

using Microsoft.Extensions.DependencyInjection;
using Scryber;
using Scryber.Drawing;

var services = new ServiceCollection();

services.AddScryber(config =>
{
    config.FontOptions.Register.Add(new FontRegistration
    {
        Family = "Roboto",
        File = "/path/to/Roboto-Regular.ttf",
        Bold = "/path/to/Roboto-Bold.ttf",
        Italic = "/path/to/Roboto-Italic.ttf",
        BoldItalic = "/path/to/Roboto-BoldItalic.ttf"
    });
});

var provider = services.BuildServiceProvider();

Font Fallback Chain

Configure fallback fonts for missing glyphs:

<html>
    <head>
        <style>
            body {
                font-family: Roboto, 'Open Sans', Helvetica, sans-serif;
            }
        </style>
    </head>
</html>

Complete Example

1. Font Files Structure

MyApp/
β”œβ”€β”€ wwwroot/
β”‚   └── fonts/
β”‚       β”œβ”€β”€ Roboto-Regular.ttf
β”‚       β”œβ”€β”€ Roboto-Bold.ttf
β”‚       β”œβ”€β”€ Roboto-Italic.ttf
β”‚       β”œβ”€β”€ Roboto-BoldItalic.ttf
β”‚       β”œβ”€β”€ OpenSans-Regular.ttf
β”‚       └── OpenSans-Bold.ttf
β”œβ”€β”€ scrybersettings.json
└── Program.cs

2. Configuration

scrybersettings.json:

{
  "Scryber": {
    "Fonts": {
      "Register": [
        {
          "Family": "Roboto",
          "File": "wwwroot/fonts/Roboto-Regular.ttf",
          "Bold": "wwwroot/fonts/Roboto-Bold.ttf",
          "Italic": "wwwroot/fonts/Roboto-Italic.ttf",
          "BoldItalic": "wwwroot/fonts/Roboto-BoldItalic.ttf"
        },
        {
          "Family": "Open Sans",
          "File": "wwwroot/fonts/OpenSans-Regular.ttf",
          "Bold": "wwwroot/fonts/OpenSans-Bold.ttf"
        }
      ]
    }
  }
}

3. Template

Report.pdfx:

<?xml version='1.0' encoding='utf-8' ?>
<html xmlns='http://www.w3.org/1999/xhtml'>
    <head>
        <style>
            /* Heading style */
            .report-title {
                font-family: Roboto;
                font-size: 32pt;
                font-weight: bold;
                color: #1a1a1a;
            }
            
            /* Section heading */
            .section-heading {
                font-family: Roboto;
                font-size: 18pt;
                font-weight: bold;
                color: #333333;
                margin-top: 20pt;
                margin-bottom: 10pt;
            }
            
            /* Body text */
            .body-text {
                font-family: 'Open Sans';
                font-size: 11pt;
                color: #555555;
            }
            
            /* Emphasis */
            .emphasis {
                font-family: Roboto;
                font-size: 11pt;
                font-style: italic;
                color: #666666;
            }
        </style>
    </head>
    <body>
        <main>
            
            <h1 class='report-title'>Annual Report 2025</h1>
            
            <h2 class='section-heading'>Executive Summary</h2>
            
            <p class='body-text'>This report provides an overview of company performance...</p>
            
            <p class='emphasis'>Revenue increased by 15% year-over-year.</p>
            
            <h2 class='section-heading'>Financial Highlights</h2>
            
            <p class='body-text'>Key financial metrics for the fiscal year...</p>
            
        </main>
    </body>
</html>

4. Application

using Microsoft.Extensions.DependencyInjection;
using Scryber;
using Scryber.Components;

// Setup dependency injection
var services = new ServiceCollection();
services.AddScryber();  // Loads scrybersettings.json
var provider = services.BuildServiceProvider();

// Generate document
using (var reader = new StreamReader("Report.pdfx"))
{
    var doc = Document.ParseDocument(reader, ParseSourceType.DynamicContent);
    doc.ProcessDocument("Report.pdf");
}

Font Subsetting

Scryber automatically subsets fonts to include only used glyphs, reducing PDF file size:

// Only glyphs for "Hello World" are embedded, not entire font
<span>Hello World</span>

Benefits:

  • Smaller PDF files
  • Faster loading
  • Reduced memory usage
  • License-friendly (only distributing used glyphs)

Font Metrics

Font metrics are extracted from TTF files:

  • Ascent/Descent: For line height calculations
  • Em Square: For font scaling
  • Glyph Widths: For text measurement
  • Kerning Pairs: For text spacing (if available)

Best Practices

Font Organization

  • Store fonts in dedicated directory: wwwroot/fonts/
  • Use relative paths in configuration
  • Include all font weights/styles for consistency
  • Document font licenses

Font Selection

  • Prefer fonts with complete character sets
  • Test fonts with expected character ranges
  • Provide fallback fonts
  • Use web-safe fonts as last resort

Performance

  • Fonts are loaded lazily - registration is fast
  • Font files are cached in memory
  • Subsetting happens per-document
  • System fonts are indexed on first use

License Compliance

  • Verify font licenses allow PDF embedding
  • Consider font subsetting in licensing
  • Document font sources and licenses
  • Use fonts with permissive licenses

Troubleshooting

β€œFont not found”

  • Check font family name matches registration exactly (case-sensitive)
  • Verify file path is correct
  • Ensure font file is readable
  • Check configuration JSON syntax

β€œCould not load font file”

  • Verify file is valid TTF/OTF format
  • Check file permissions
  • Ensure font file is not corrupted
  • Try opening font in font viewer application

Font renders incorrectly

  • Verify correct font weight/style is registered
  • Check font supports required character set
  • Test with simple characters first
  • Verify font metrics are being read correctly

Font not embedded in PDF

  • Check font license allows embedding
  • Verify subsetting is working
  • Inspect PDF with Adobe Acrobat
  • Check for errors in Scryber logs

Generic Font Families

CSS generic font families are mapped as follows:

Generic Family Default Mapping Customizable
sans-serif Helvetica Via configuration
serif Times-Roman Via configuration
monospace Courier Via configuration
cursive System-dependent Via configuration
fantasy System-dependent Via configuration

Customizing Generic Mappings

{
  "Scryber": {
    "Fonts": {
      "GenericMappings": {
        "sans-serif": "Roboto",
        "serif": "Merriweather",
        "monospace": "Fira Code"
      }
    }
  }
}