showPdfFontMenu function
Future<void>
showPdfFontMenu({
- required BuildContext context,
- required PdfEditingController controller,
- PdfFontPicker? fontPicker,
- List<
PdfBundledFont> ? bundled, - List<
PdfPlatformFont> ? platformFonts, - List<
PdfEmbeddedFont> ? documentFonts, - PdfTextFont? currentFont,
- void onSelected(
- PdfTextFont font
Pops a font menu anchored at context's widget and applies the pick:
the standard families, the fonts already embedded in the open document
(documentFonts, defaulting to PdfEditingController.documentFonts),
the bundled fonts, the platform fonts (platformFonts, defaulting to
the host-populated pdfPlatformFonts registry), then "Load font…" (when
a fontPicker is given). Recently-picked fonts head the list in a
"Recently used" group. The search field is focused on open and the menu
is searchable. Bundled, platform, document and custom fonts embed into
the document so the text renders everywhere.
Implementation
Future<void> showPdfFontMenu({
required BuildContext context,
required PdfEditingController controller,
PdfFontPicker? fontPicker,
List<PdfBundledFont>? bundled,
List<PdfPlatformFont>? platformFonts,
List<PdfEmbeddedFont>? documentFonts,
PdfTextFont? currentFont,
void Function(PdfTextFont font)? onSelected,
}) async {
final bundledFonts = bundled ?? pdfBundledFonts;
final platform = platformFonts ?? pdfPlatformFonts;
final inDocument = documentFonts ?? controller.documentFonts;
// Register each document font's outlines with the engine (best-effort) so
// its menu row can preview in its own face. Only fonts that can actually
// draw their whole name get a preview: document fonts are usually subsets
// whose unused glyphs were stripped (the cmap survives but the outline is
// empty), so previewing their name would drop letters - those render in the
// legible UI face instead. A font that fails to register also falls back.
// A document subset that can't cover the basic alphabet can only type the
// characters the file already used, so flag it "limited".
final limited = [for (final f in inDocument) !f.canRender(_typeableProbe)];
final previewFamilies = <int, String>{};
await Future.wait([
for (var i = 0; i < inDocument.length; i++)
if (inDocument[i].canRender(inDocument[i].displayName))
_ensureDocumentFontPreview(inDocument[i]).then((family) {
if (family != null) previewFamilies[i] = family;
}),
]);
if (!context.mounted) return;
final entries = <_FontEntry>[
// Document fonts head the catalogue in their own "In this document"
// section, previewed in their own face and shown by their real family
// name (subset tag stripped).
for (var i = 0; i < inDocument.length; i++)
_FontEntry(
key: ValueKey('pdf-font-document-$i'),
label: inDocument[i].displayName,
subtitle: limited[i] ? pdfL10n(context).propLimitedCharacters : null,
searchText: '${inDocument[i].displayName} '
'${inDocument[i].familyName} ${inDocument[i].postScriptName} '
'document embedded font'
.toLowerCase(),
choice: _DocumentChoice(inDocument[i]),
fontFamily: previewFamilies[i],
recentKey: 'doc:${inDocument[i].postScriptName}',
section: pdfL10n(context).propSectionInThisDocument,
limited: limited[i],
),
_FontEntry(
key: const ValueKey('pdf-font-std-sans'),
label: 'Sans (Helvetica)',
subtitle: pdfL10n(context).propStandardPdfFont,
searchText: 'sans helvetica standard pdf font',
choice: const _StandardChoice(PdfStandardFontFamily.sans),
fontFamily: 'Helvetica',
recentKey: 'std:sans',
section: pdfL10n(context).propSectionAllFonts,
),
_FontEntry(
key: const ValueKey('pdf-font-std-serif'),
label: 'Serif (Times)',
subtitle: pdfL10n(context).propStandardPdfFont,
searchText: 'serif times times-roman standard pdf font',
choice: const _StandardChoice(PdfStandardFontFamily.serif),
fontFamily: 'Times New Roman',
recentKey: 'std:serif',
section: pdfL10n(context).propSectionAllFonts,
),
_FontEntry(
key: const ValueKey('pdf-font-std-mono'),
label: 'Mono (Courier)',
subtitle: pdfL10n(context).propStandardPdfFont,
searchText: 'mono monospace courier standard pdf font',
choice: const _StandardChoice(PdfStandardFontFamily.mono),
fontFamily: 'Courier',
recentKey: 'std:mono',
section: pdfL10n(context).propSectionAllFonts,
),
for (var i = 0; i < bundledFonts.length; i++)
_FontEntry(
key: ValueKey('pdf-font-bundled-$i'),
label: bundledFonts[i].label,
subtitle: pdfL10n(context).propBundledFont,
searchText: '${bundledFonts[i].label} bundled font'.toLowerCase(),
choice: _BundledChoice(bundledFonts[i]),
// Seed the preview face from the cache so a re-open is instant; a
// first open shows the default face and the dialog swaps it in as the
// lazy registration completes (see [_PdfFontPickerDialogState]).
fontFamily: _bundledPreviewFamilies.contains(bundledFonts[i].label)
? bundledFonts[i].label
: null,
bundledFont: bundledFonts[i],
recentKey: 'bundled:${bundledFonts[i].label}',
section: pdfL10n(context).propSectionAllFonts,
),
for (var i = 0; i < platform.length; i++)
_FontEntry(
key: ValueKey('pdf-font-platform-$i'),
label: platform[i].label,
subtitle: pdfL10n(context).propSystemFont,
searchText: '${platform[i].label} ${platform[i].family ?? ''} '
'system platform font'
.toLowerCase(),
choice: _PlatformChoice(platform[i]),
fontFamily: platform[i].family,
recentKey: 'platform:${platform[i].label}',
section: pdfL10n(context).propSectionAllFonts,
),
if (fontPicker != null)
_FontEntry(
key: const ValueKey('pdf-font-load'),
label: pdfL10n(context).propLoadFont,
subtitle: pdfL10n(context).propLoadFontSubtitle,
searchText: 'load custom font ttf otf file upload',
choice: const _LoadChoice(),
section: pdfL10n(context).propSectionAllFonts,
),
];
// Resolve the persisted recent keys back to live catalogue entries, newest
// first, giving each a distinct key so it can sit above its twin. Keys with
// no current match (e.g. a document font from a since-closed file) drop out.
final byRecentKey = <String, _FontEntry>{};
for (final entry in entries) {
if (entry.recentKey case final key?) byRecentKey.putIfAbsent(key, () => entry);
}
final recent = <_FontEntry>[];
for (final key in controller.preferences.recentFonts) {
final match = byRecentKey[key];
if (match == null) continue;
recent.add(_FontEntry(
key: ValueKey('pdf-font-recent-${recent.length}'),
label: match.label,
subtitle: match.subtitle,
searchText: match.searchText,
choice: match.choice,
fontFamily: match.fontFamily,
bundledFont: match.bundledFont,
recentKey: match.recentKey,
section: match.section,
limited: match.limited,
));
}
final choice = await showPdfDialog<_FontChoice>(
context: context,
builder: (_) => _PdfFontPickerDialog(entries: entries, recent: recent),
);
if (choice == null) return;
void apply(PdfTextFont font) {
if (onSelected != null) {
onSelected(font);
} else {
pdfApplyFont(controller, font);
}
}
// Records the pick as the newest recent, so the next open floats it up.
void noteRecent(String? key) {
if (key != null) controller.preferences.noteRecentFont(key);
}
switch (choice) {
case _StandardChoice(:final family):
final current = currentFont ??
controller.selectedTextStyle?.font ??
controller.selectedMeasurementCaptionStyle?.font ??
controller.fontFamily;
apply(PdfStandardFont.styled(family,
bold: current is PdfStandardFont && current.isBold,
italic: current is PdfStandardFont && current.isItalic));
noteRecent('std:${family.name}');
case _DocumentChoice(:final font):
apply(font);
noteRecent('doc:${font.postScriptName}');
case _BundledChoice(:final font):
try {
final bytes = await loadBundledFont(font);
apply(PdfEmbeddedFont.parse(bytes));
noteRecent('bundled:${font.label}');
} catch (_) {
// A missing/corrupt bundled asset just leaves the font unchanged.
}
case _PlatformChoice(:final font):
try {
final bytes = await font.loadBytes();
if (bytes != null) {
apply(PdfEmbeddedFont.parse(bytes));
noteRecent('platform:${font.label}');
}
} catch (_) {
// An unreadable/unsupported platform font (e.g. .ttc, WOFF, or one
// uninstalled since discovery) leaves the font unchanged.
}
case _LoadChoice():
if (fontPicker == null) return;
if (!context.mounted) return;
final bytes = await fontPicker(context);
if (bytes == null) return;
if (onSelected != null) {
try {
onSelected(PdfEmbeddedFont.parse(bytes));
} catch (_) {
// Invalid custom font bytes leave the current font unchanged.
}
} else {
controller.setCustomFont(bytes);
}
}
}