Skip to main content
Technology & EngineeringFile Formats326 lines

WOFF/WOFF2 Web Open Font Format

The WOFF and WOFF2 web font formats — compressed font containers optimized for efficient web delivery, enabling custom typography on websites with smaller file sizes.

Quick Summary18 lines
You are a file format specialist with deep expertise in WOFF/WOFF2 web font formats, including Brotli font-specific preprocessing, @font-face CSS integration, unicode-range subsetting with pyftsubset, font-display loading strategies, and web performance optimization for typography.

## Key Points

- **Extension:** `.woff`
- **MIME type:** `font/woff`
- **Compression:** zlib (DEFLATE) per table
- **Specification:** W3C Recommendation (2012)
- **Compression ratio:** ~40% smaller than TTF/OTF
- **Extension:** `.woff2`
- **MIME type:** `font/woff2`
- **Compression:** Brotli with font-specific preprocessing
- **Specification:** W3C Recommendation (2018)
- **Compression ratio:** ~50-70% smaller than TTF/OTF, ~30% smaller than WOFF
- Signature: 0x774F4632 ("wOF2")
- Flavor: original font type (TrueType or CFF)
skilldb get file-formats-skills/WOFF/WOFF2 Web Open Font FormatFull skill: 326 lines
Paste into your CLAUDE.md or agent config

You are a file format specialist with deep expertise in WOFF/WOFF2 web font formats, including Brotli font-specific preprocessing, @font-face CSS integration, unicode-range subsetting with pyftsubset, font-display loading strategies, and web performance optimization for typography.

WOFF/WOFF2 Web Open Font Format (.woff, .woff2)

Overview

WOFF (Web Open Font Format) and WOFF2 are font packaging formats designed specifically for web delivery. They wrap TrueType or OpenType fonts with compression and metadata, reducing file sizes for faster page loads. WOFF was standardized by the W3C in 2012, and WOFF2 (using Brotli compression) followed in 2018 with dramatically better compression — typically 30% smaller than WOFF and 50-70% smaller than raw TTF/OTF.

WOFF2 is now the standard for web fonts, supported by all modern browsers and used by Google Fonts, Adobe Fonts, and virtually every web typography service.

Core Philosophy

WOFF and WOFF2 (Web Open Font Format) are font packaging formats designed specifically for web delivery. They take existing OpenType/TrueType font data and apply web-optimized compression (zlib for WOFF, Brotli for WOFF2) to reduce download size. A WOFF2 file is typically 30-50% smaller than the equivalent TTF/OTF file with identical font features and rendering quality.

WOFF2 is the only web font format you need for modern browsers. Support is universal across Chrome, Firefox, Safari, Edge, and all modern mobile browsers. WOFF (version 1) exists as a fallback for very old browsers but is rarely needed. The progression from raw TTF/OTF (desktop) to WOFF2 (web) is analogous to the progression from BMP to PNG — same content, better compression, same fidelity.

When deploying web fonts, subset aggressively. Most Latin-script web pages use fewer than 200 glyphs from fonts that may contain thousands. Tools like glyphhanger, fonttools (pyftsubset), and Google Fonts' text parameter can produce subsetted WOFF2 files 60-90% smaller than the full font. Combine subsetting with font-display: swap or font-display: optional to ensure text remains visible during font loading.

Technical Specifications

WOFF (1.0)

  • Extension: .woff
  • MIME type: font/woff
  • Compression: zlib (DEFLATE) per table
  • Specification: W3C Recommendation (2012)
  • Compression ratio: ~40% smaller than TTF/OTF

WOFF2

  • Extension: .woff2
  • MIME type: font/woff2
  • Compression: Brotli with font-specific preprocessing
  • Specification: W3C Recommendation (2018)
  • Compression ratio: ~50-70% smaller than TTF/OTF, ~30% smaller than WOFF

WOFF2 Structure

[WOFF2 Header (48 bytes)]
  - Signature: 0x774F4632 ("wOF2")
  - Flavor: original font type (TrueType or CFF)
  - Total size
  - Number of tables
  - Total compressed size
  - Major/minor version
  - Metadata offset/length (optional, compressed XML)
  - Private data offset/length (optional)

[Table Directory]
  - Flags (known table tag or custom)
  - Original table length
  - Transform version (preprocessing applied)

[Compressed Data]
  - All tables concatenated and Brotli-compressed
  - Font-specific transforms applied before compression:
    - glyf/loca transform (triplet encoding for points)
    - hmtx transform (reconstructed from glyf data)

Key Innovation: Font-Specific Preprocessing

WOFF2 applies transforms before Brotli compression:

  1. glyf transform — Encodes glyph points as deltas with variable-length triplet coding
  2. loca reconstruction — Rebuilds from glyf data, doesn't need to be stored
  3. hmtx optimization — Derives metrics from glyph data when possible

These transforms make font data significantly more compressible.

How to Work With It

Using Web Fonts in CSS

/* Modern approach — WOFF2 with WOFF fallback */
@font-face {
    font-family: 'MyFont';
    src: url('myfont.woff2') format('woff2'),
         url('myfont.woff') format('woff');
    font-weight: 400;
    font-style: normal;
    font-display: swap;  /* Show fallback immediately, swap when loaded */
}

/* Using the font */
body {
    font-family: 'MyFont', system-ui, sans-serif;
}

/* Variable font */
@font-face {
    font-family: 'MyVarFont';
    src: url('variable.woff2') format('woff2-variations');
    font-weight: 100 900;  /* full weight range */
}

Converting TTF/OTF to WOFF2

# Using fonttools (Python — recommended)
# pip install fonttools brotli
python3 -c "
from fontTools.ttLib import TTFont
font = TTFont('font.ttf')
font.flavor = 'woff2'
font.save('font.woff2')
"

# Using woff2 CLI tool (Google's reference implementation)
woff2_compress font.ttf            # creates font.woff2
woff2_decompress font.woff2       # back to TTF

# Batch convert
for f in *.ttf; do
    python3 -c "
from fontTools.ttLib import TTFont
font = TTFont('$f')
font.flavor = 'woff2'
font.save('${f%.ttf}.woff2')
"
done

Subsetting for Web (Critical Optimization)

# Subset to Latin characters only (huge size reduction for CJK/icon fonts)
pyftsubset font.ttf \
    --output-file=font-latin.woff2 \
    --flavor=woff2 \
    --unicodes="U+0000-007F,U+00A0-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD" \
    --layout-features="kern,liga,calt"

# Subset for specific text only
pyftsubset font.ttf \
    --output-file=hero.woff2 \
    --flavor=woff2 \
    --text="Welcome to My Website"

# Google Fonts uses unicode-range splitting:
# Serve Latin, Latin Extended, Cyrillic, etc. as separate files
# Browser only downloads what's needed via unicode-range descriptor

Optimizing Web Font Loading

<!-- Preload critical fonts -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>

<!-- Self-host instead of using CDN for privacy and performance -->
<style>
@font-face {
    font-family: 'Inter';
    src: url('/fonts/inter-var.woff2') format('woff2');
    font-weight: 100 900;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC;
}
</style>

Inspecting WOFF2 Files

from fontTools.ttLib import TTFont

font = TTFont('font.woff2')
print(f"Format: {font.flavor}")
print(f"Glyphs: {font['maxp'].numGlyphs}")
print(f"Tables: {sorted(font.keys())}")

# Check file sizes
import os
ttf_size = os.path.getsize('font.ttf')
woff2_size = os.path.getsize('font.woff2')
print(f"Compression: {(1 - woff2_size/ttf_size)*100:.1f}% smaller")

Common Use Cases

  • Web typography: Custom fonts on websites (the primary use case)
  • Progressive web apps: Embedded fonts for offline-capable apps
  • Email templates: Some email clients support web fonts
  • Digital publications: EPUB3 supports WOFF for ebook typography
  • Web applications: UI fonts for SaaS products and dashboards
  • Font distribution: Google Fonts, Adobe Fonts, Fontshare serve WOFF2

Pros & Cons

Pros

  • WOFF2 achieves 50-70% compression over raw TTF/OTF — critical for web performance
  • Universal browser support (WOFF2: 97%+ global, WOFF: 98%+)
  • W3C standard — open, well-documented, royalty-free
  • Preserves all OpenType features, hinting, and metadata
  • Font-specific preprocessing makes Brotli compression more effective
  • Subsetting tools enable serving only needed characters
  • unicode-range CSS descriptor enables automatic per-script loading

Cons

  • Not intended for desktop installation (technically possible but not standard)
  • WOFF2 compression/decompression is slower than raw font access
  • Cannot be edited directly — must decompress to TTF/OTF, edit, and recompress
  • WOFF metadata (XML block) is rarely used in practice
  • Subsetting requires careful planning to avoid missing characters
  • Font licensing may restrict web embedding (check license terms)

Compatibility

BrowserWOFFWOFF2Notes
Chrome6+36+Full support
Firefox3.6+39+Full support
Safari5.1+10+Full support (macOS and iOS)
Edge12+14+Full support
IE 11YesNoWOFF only, no WOFF2
Opera11.1+23+Full support

Server configuration:

# nginx MIME types (usually pre-configured)
types {
    font/woff  woff;
    font/woff2 woff2;
}

# Enable caching (fonts rarely change)
location ~* \.(woff2?)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    add_header Access-Control-Allow-Origin "*";
}

Programming languages: Python (fonttools with brotli), C/C++ (woff2 reference library by Google), JavaScript (wawoff2 WASM port), Rust (woff2 crate).

Practical Usage

Subset and convert a font for web use with pyftsubset

# Install dependencies
pip install fonttools brotli

# Create a Latin-only WOFF2 subset with common OpenType features
pyftsubset "SourceSans3-Regular.ttf" \
  --output-file="sourcesans-latin.woff2" \
  --flavor=woff2 \
  --unicodes="U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD" \
  --layout-features="kern,liga,calt,frac,sups,subs"

Automate font format comparison and audit in Python

import os
from fontTools.ttLib import TTFont

for ext in ['ttf', 'woff', 'woff2']:
    path = f'myfont.{ext}'
    if os.path.exists(path):
        size = os.path.getsize(path)
        font = TTFont(path)
        glyphs = font['maxp'].numGlyphs
        print(f"{ext.upper():6s}: {size:>8,} bytes, {glyphs} glyphs")
# Example output:
# TTF   :  245,392 bytes, 1421 glyphs
# WOFF  :  148,712 bytes, 1421 glyphs
# WOFF2 :   89,244 bytes, 1421 glyphs

Set up optimal self-hosted web font loading

<!-- Preload the most critical font weight -->
<link rel="preload" href="/fonts/inter-latin-400.woff2" as="font" type="font/woff2" crossorigin>

<style>
  /* Variable font with unicode-range for automatic subsetting by browser */
  @font-face {
    font-family: 'Inter';
    src: url('/fonts/inter-latin.woff2') format('woff2');
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC;
  }
  @font-face {
    font-family: 'Inter';
    src: url('/fonts/inter-latin-ext.woff2') format('woff2');
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB;
  }
</style>

Anti-Patterns

Serving raw TTF or OTF files to web browsers instead of WOFF2. Uncompressed fonts can be 2-3x larger than WOFF2, directly increasing page load time. Always convert to WOFF2 for web delivery; the savings are significant even for small fonts.

Loading the entire Unicode character set when only Latin is needed. A full CJK font can be 5-10 MB. Use pyftsubset to create subsets containing only the character ranges your site actually uses, and use unicode-range in CSS to let the browser download only what is needed per page.

Using font-display: block or omitting font-display entirely. This causes invisible text (FOIT) for up to 3 seconds while the font downloads. Use font-display: swap for body text so content is immediately readable with a fallback font, or font-display: optional for non-critical decorative fonts.

Including both WOFF and WOFF2 formats when only targeting modern browsers. WOFF2 has 97%+ global browser support. Unless you must support IE 11, you can safely serve only WOFF2 and eliminate the WOFF file, reducing your font asset count and build complexity.

Self-hosting Google Fonts by downloading only the full TTF and converting. Google Fonts already provides optimally subsetted WOFF2 files split by unicode range. Use google-webfonts-helper or download directly from the Google Fonts CSS to get pre-subsetted, properly hinted files.

Related Formats

  • TTF — Source TrueType font (uncompressed)
  • OTF — Source OpenType/CFF font (uncompressed)
  • Variable Fonts — Single WOFF2 file covering entire type family
  • EOT — Embedded OpenType (IE-only, obsolete)
  • SVG fonts — Deprecated web font format (replaced by WOFF/WOFF2)
  • Data URIs — Base64-encoded fonts inline in CSS (small fonts only)

Install this skill directly: skilldb add file-formats-skills

Get CLI access →