Skip to content

Re-introduce Duotone Icon Support for FA v6/v7#302

Open
DontBullyMeIllCode wants to merge 3 commits intofluttercommunity:masterfrom
DontBullyMeIllCode:feat/duotone-support
Open

Re-introduce Duotone Icon Support for FA v6/v7#302
DontBullyMeIllCode wants to merge 3 commits intofluttercommunity:masterfrom
DontBullyMeIllCode:feat/duotone-support

Conversation

@DontBullyMeIllCode
Copy link
Copy Markdown

@DontBullyMeIllCode DontBullyMeIllCode commented Mar 25, 2026

Re-introduce duotone icon support for FA v6/v7

I have been a user of font_awesome_flutter since V5. It is the only icon system I use and wanted to contribute as well as enable a feature I have been missing.

Duotone icon support was dropped when Font Awesome v6 switched from separate Unicode codepoints per layer to a ligature-based system that Flutter's Icon/IconData pipeline couldn't handle. This PR brings it back by bypassing that pipeline entirely and using stacked RichText widgets with OTF name-based ligatures, which Flutter 3.32.5+ resolves correctly via HarfBuzz.

Problem

Font Awesome v6/v7 duotone icons encode their two layers (primary and secondary) using OpenType ligature substitution in the font's GSUB table. Flutter's IconData only accepts a single integer codepoint and the icon tree-shaker strips any glyph only reachable via ligatures — making duotone icons impossible through the standard Icon widget.

Solution

  • New widget: FaDuotoneIcon — renders duotone icons by stacking two RichText widgets in a Stack, one per layer, with independent color and opacity controls
  • Name-based ligatures — the OTF desktop font resolves ligatures by icon name: icon-name# produces the primary glyph, icon-name## produces the secondary glyph
  • Tree-shaker bypass — the duotone OTF is registered as a regular FontFamily (not an icon font), so Flutter's icon tree-shaker ignores it entirely
  • Rewritten FaDuotoneIconData — stores the icon's codepoint (for identification), ligature name (for rendering), and font family. The old v5-era class with two IconData fields is replaced
  • Configurator --duotone flag — generates FontAwesomeDuotoneIcons constants (with ligatureName) directly into font_awesome_flutter.dart alongside FontAwesomeIcons

Files changed

File Change
lib/src/icon_data.dart Rewrote FaDuotoneIconData — single codePoint + ligatureName + fontFamily, with primaryGlyph/secondaryGlyph getters
lib/src/fa_duotone_icon.dart NewFaDuotoneIcon widget with full parity to FaIcon (color, size, shadows, font variations, blend mode, text scaling, semantics)
lib/font_awesome_flutter.dart Added export for fa_duotone_icon.dart
pubspec.yaml Added all 8 duotone font family entries: Duotone + Sharp Duotone in Solid/Regular/Light/Thin weights (commented out — Pro-only)
util/lib/main.dart Added --duotone flag; generateDuotoneIconDefinitionClass() appends FontAwesomeDuotoneIcons to main generated file; parses FA v7 svgs.duotone and svgs.sharp-duotone families with all weight variants; wildcard 'duotone' exclusion covers all variants; duotoneStyleToFontFamily delegates to styleToFontFamily for correct multi-word mapping
test/fa_duotone_icon_test.dart New — 31 tests covering FaDuotoneIconData (ligature strings, equality, all 8 font families) and FaDuotoneIcon (sizing, colors, opacity, swap, font family passthrough, shadows, font variations, blend mode, text scaling, semantics, IconTheme integration)
example/lib/main.dart Added DuotoneShowcasePage with three demo icons; added palette button in app bar to navigate to DuotoneShowcase
example/lib/duotone_showcase.dart New — standalone showcase page demonstrating all duotone families (Classic + Sharp), weight variants (Solid/Regular/Light/Thin), and color/opacity options. Includes info banner explaining Pro font requirement when fonts are absent
README.md Replaced "discontinued" notice with full duotone documentation: setup, usage examples, how it works, weight variants table, properties table. Added note about example app duotone showcase
.gitignore Added **/ephemeral/ to ignore Flutter build artifacts

How it works

┌─────────────────────────────────────────────┐
│ FaDuotoneIcon                               │
│                                             │
│  ┌─ SizedBox (iconSize × iconSize) ───────┐ │
│  │  Stack(alignment: center)              │ │
│  │                                        │ │
│  │   RichText("calendar-days##")          │ │  ← secondary layer (background)
│  │     fontFamily: FontAwesomeDuotone     │ │    color + secondaryOpacity
│  │     GSUB ligature → background glyph   │ │
│  │                                        │ │
│  │   RichText("calendar-days#")           │ │  ← primary layer (foreground)
│  │     fontFamily: FontAwesomeDuotone     │ │    color + primaryOpacity
│  │     GSUB ligature → foreground glyph   │ │
│  └────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘

Usage

// Simple — inherits theme color, 40% secondary opacity
FaDuotoneIcon(FontAwesomeDuotoneIcons.calendarDays, size: 32)

// Custom colors
FaDuotoneIcon(
  FontAwesomeDuotoneIcons.cat,
  size: 48,
  primaryColor: Colors.deepPurple,
  secondaryColor: Colors.amber,
  secondaryOpacity: 0.6,
)

// Swap opacity (FA's fa-swap-opacity equivalent)
FaDuotoneIcon(
  FontAwesomeDuotoneIcons.userDoctor,
  size: 36,
  swapOpacity: true,
  primaryColor: Colors.teal,
)

Weight variants

All 8 duotone font variants are supported (4 weights x 2 families):

Style Font Family Naming Prefix
Duotone Solid (default) FontAwesomeDuotone (none)
Duotone Regular FontAwesomeDuotoneRegular regular
Duotone Light FontAwesomeDuotoneLight light
Duotone Thin FontAwesomeDuotoneThin thin
Sharp Duotone Solid FontAwesomeSharpDuotone sharp
Sharp Duotone Regular FontAwesomeSharpDuotoneRegular sharpRegular
Sharp Duotone Light FontAwesomeSharpDuotoneLight sharpLight
Sharp Duotone Thin FontAwesomeSharpDuotoneThin sharpThin

The --exclude duotone flag uses wildcard matching to exclude all duotone variants at once
(same pattern as --exclude sharp).

Requirements

  • Flutter 3.32.5+ (ligature support in text rendering)
  • Font Awesome Pro duotone OTF font files (one per weight variant you want to use)
  • Run configurator with --duotone flag to generate icon constants

Test plan

  • FaDuotoneIconData unit tests (ligature strings, equality, font family, toString)
  • FaDuotoneIconData weight variant tests (all 8 font families produce correct ligatures)
  • FaDuotoneIcon widget tests (sizing, colors, opacity swap, layer stacking, semantics)
  • FaDuotoneIcon font family passthrough test (non-default weight renders with correct family)
  • FaDuotoneIcon theme integration tests (IconTheme color/size/opacity/shadows/font variations fallback)
  • FaDuotoneIcon advanced feature tests (blendMode, applyTextScaling, fontWeight)
  • All 31 new tests pass, all 9 existing tests unaffected
  • flutter analyze reports zero issues
  • Manual verification with FA Pro duotone OTF fonts (all weights)
  • Verify configurator --duotone generates correct constants from Pro icons.json
  • Verify duotone showcase page renders correctly with Pro fonts installed
  • Verify info banner displays on showcase page when duotone fonts are absent

Screenshots

Example App

Duotones Enabled

image

Duotones Disabled

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant