Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ packages
pubspec.lock
.flutter-plugins
local.properties
**/ephemeral/
119 changes: 115 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ Due to restrictions in dart, icons starting with numbers have those numbers writ

View the Flutter app in the `example` directory to see all the available `FontAwesomeIcons`.

The example app also includes a **Duotone Showcase** page (accessible via the palette icon in the
app bar) that demonstrates all duotone icon families, weight variants, and color/opacity options.
Duotone icons require Font Awesome Pro fonts — if the fonts are not installed, a banner on the
page explains the requirement.

## Customizing font awesome flutter

We supply a configurator tool to assist you with common customizations to this package.
Expand Down Expand Up @@ -147,11 +152,117 @@ getIconFromCss('far custom-class fa-abacus'); // returns the abacus icon in regu

## Duotone icons

Duotone support has been discontinued after font awesome changed the way they lay out the icon glyphs inside the font's
file. The new way using ligatures is not supported by flutter at the moment.
Duotone icon support is back! Flutter 3.32.5+ resolves OpenType ligatures when text is rendered
via `RichText` / `TextSpan`, which makes the FA v6/v7 two-layer ligature system work.

Duotone icons are **Pro-only** — you need the Font Awesome Pro duotone OTF font file.

### Setup

1. Follow the [pro icons setup](#enable-pro-icons) to place your Pro font files and `icons.json`
in `lib/fonts`.
2. Run the configurator with the `--duotone` flag:
```
$ ./configurator.sh --duotone
```
This generates `FontAwesomeDuotoneIcons` constants in `lib/font_awesome_flutter.dart`
and enables the `FontAwesomeDuotone` font family in `pubspec.yaml`.

### Usage

Use the `FaDuotoneIcon` widget instead of `FaIcon`:

```dart
import 'package:font_awesome_flutter/font_awesome_flutter.dart';

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

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

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

### How it works

The `FaDuotoneIcon` widget stacks two `RichText` layers using the OTF font's
name-based ligature system:
- **Primary layer**: renders `icon-name#` which triggers the font's GSUB ligature
substitution to produce the foreground glyph
- **Secondary layer**: renders `icon-name##` which triggers the GSUB substitution
to produce the background/detail glyph

The duotone OTF is registered as a regular font family (not an icon font), which keeps it
out of Flutter's icon tree-shaker entirely. The full font ships in the app.

### Weight variants

FA v7 duotone icons come in four weights, each requiring its own OTF font file:

| Weight | Classic Duotone Font | Sharp Duotone Font |
|---|---|---|
| **Solid** (900, default) | `Font-Awesome-7-Duotone-Solid-900.otf` | `Font-Awesome-7-Sharp-Duotone-Solid-900.otf` |
| **Regular** (400) | `Font-Awesome-7-Duotone-Regular-400.otf` | `Font-Awesome-7-Sharp-Duotone-Regular-400.otf` |
| **Light** (300) | `Font-Awesome-7-Duotone-Light-300.otf` | `Font-Awesome-7-Sharp-Duotone-Light-300.otf` |
| **Thin** (100) | `Font-Awesome-7-Duotone-Thin-100.otf` | `Font-Awesome-7-Sharp-Duotone-Thin-100.otf` |

Place the font files you need in `lib/fonts/` and run the configurator. It will detect available
weights and generate constants with appropriate prefixes:

```dart
// Solid (default — no prefix)
FaDuotoneIcon(FontAwesomeDuotoneIcons.calendarDays)

// Regular weight
FaDuotoneIcon(FontAwesomeDuotoneIcons.regularCalendarDays)

// Light weight
FaDuotoneIcon(FontAwesomeDuotoneIcons.lightCalendarDays)

// Thin weight
FaDuotoneIcon(FontAwesomeDuotoneIcons.thinCalendarDays)

// Sharp solid
FaDuotoneIcon(FontAwesomeDuotoneIcons.sharpCalendarDays)

// Sharp light
FaDuotoneIcon(FontAwesomeDuotoneIcons.sharpLightCalendarDays)
```

For more information on why duotone icon support was discontinued, see
[this comment](https://github.com/fluttercommunity/font_awesome_flutter/issues/192#issuecomment-1073003668).
### Properties

| Property | Type | Default | Description |
|---|---|---|---|
| `icon` | `FaDuotoneIconData` | required | The duotone icon to display |
| `size` | `double?` | `24.0` | Icon size in logical pixels |
| `primaryColor` | `Color?` | theme color | Primary (foreground) layer color |
| `secondaryColor` | `Color?` | `primaryColor` | Secondary (background) layer color |
| `primaryOpacity` | `double` | `1.0` | Primary layer opacity (0.0-1.0) |
| `secondaryOpacity` | `double` | `0.4` | Secondary layer opacity (0.0-1.0) |
| `swapOpacity` | `bool` | `false` | Swap opacity values between layers |
| `semanticLabel` | `String?` | `null` | Accessibility label |
| `textDirection` | `TextDirection?` | ambient | Text direction override |
| `fill` | `double?` | theme | Variable font FILL axis |
| `weight` | `double?` | theme | Variable font wght axis |
| `grade` | `double?` | theme | Variable font GRAD axis |
| `opticalSize` | `double?` | theme | Variable font opsz axis |
| `shadows` | `List<Shadow>?` | theme | Shadows painted beneath the icon |
| `applyTextScaling` | `bool?` | `false` | Scale icon size with text scaler |
| `blendMode` | `BlendMode?` | `null` | Blend mode for painting |
| `fontWeight` | `FontWeight?` | `null` | Font weight for glyph rendering |

## FAQ

Expand Down
2 changes: 0 additions & 2 deletions example/ios/Flutter/AppFrameworkInfo.plist
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,5 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
</dict>
</plist>
30 changes: 9 additions & 21 deletions example/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,13 @@
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objectVersion = 54;
objects = {

/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
Expand All @@ -26,8 +22,6 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
Expand All @@ -38,13 +32,11 @@
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
Expand All @@ -57,8 +49,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -68,9 +58,7 @@
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B80C3931E831B6300D905FE /* App.framework */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEBA1CF902C7004384FC /* Flutter.framework */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
Expand Down Expand Up @@ -147,7 +135,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1020;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
Expand Down Expand Up @@ -191,20 +179,23 @@
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
Expand Down Expand Up @@ -253,7 +244,6 @@
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
Expand Down Expand Up @@ -293,7 +283,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
Expand Down Expand Up @@ -330,7 +320,6 @@
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
Expand Down Expand Up @@ -376,7 +365,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
Expand All @@ -386,7 +375,6 @@
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
Expand Down Expand Up @@ -426,7 +414,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand All @@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
Expand All @@ -45,11 +46,13 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
Expand Down
11 changes: 7 additions & 4 deletions example/ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import UIKit
import Flutter
import UIKit

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
@main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
}
25 changes: 25 additions & 0 deletions example/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
Expand All @@ -22,6 +24,29 @@
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneDelegateClassName</key>
<string>FlutterSceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
Expand Down
Loading