Skip to content
Merged
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
Binary file added assets/fonts/NunitoSans-Bold.ttf
Binary file not shown.
Binary file added assets/fonts/NunitoSans-Medium.ttf
Binary file not shown.
Binary file added assets/fonts/NunitoSans-Regular.ttf
Binary file not shown.
Binary file added assets/fonts/NunitoSans-SemiBold.ttf
Binary file not shown.
93 changes: 93 additions & 0 deletions assets/fonts/OFL-NunitoSans.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
Copyright 2016 The Nunito Sans Project Authors (https://github.com/Fonthausen/NunitoSans)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
93 changes: 93 additions & 0 deletions assets/fonts/OFL-PlayfairDisplay.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
Copyright 2017 The Playfair Display Project Authors (https://github.com/clauseggers/Playfair-Display), with Reserved Font Name "Playfair Display"

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Binary file added assets/fonts/PlayfairDisplay-Bold.ttf
Binary file not shown.
Binary file added assets/fonts/PlayfairDisplay-SemiBold.ttf
Binary file not shown.
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ How the codebase works -- implementation guides, architecture, and technical ref
- **[widget-standards.md](code/widget-standards.md)** - Widget patterns and standards
- **[ui-components.md](code/ui-components.md)** - Shared UI components (OverflowMenu)
- **[network.md](code/network.md)** - Network security configuration and allowlist
- **[fonts.md](code/fonts.md)** - Bundled typefaces, why they aren't fetched at runtime, and how to add a weight
- **[api/](code/api/)** - API documentation
- [README.md](code/api/README.md) - API overview
- [data-api-reference.md](code/api/data-api-reference.md) - Complete API reference
Expand Down
94 changes: 94 additions & 0 deletions docs/code/fonts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Fonts

The app pairs **Playfair Display** (headings, app bar title) with **Nunito Sans**
(body, labels). Both are declared in `lib/app_theme.dart` via `buildAppTextTheme`
and resolved through the `google_fonts` package.

## The font files are bundled, not fetched

`assets/fonts/` ships the exact `.ttf` files `google_fonts` would otherwise
download from `fonts.gstatic.com` at runtime. `google_fonts` checks the asset
bundle before it checks the network, so bundling means the typefaces resolve
locally on every platform.

Two things this buys us:

- **First paint has no network dependency.** Previously the app rendered in a
fallback face until the font download finished — over festival wifi, or on a
phone with no signal, that could be the whole session.
- **Golden tests are deterministic.** `test/flutter_test_config.dart` sets
`GoogleFonts.config.allowRuntimeFetching = false` for the whole test suite,
so goldens render the real faces identically on any machine, online or not.

Before this, golden tests rendered every glyph as the blocky `FlutterTest`
placeholder box, which meant no golden could catch a typography or text-layout
regression (issue #520).

## Which variants are bundled

Only the weights `buildAppTextTheme` actually requests:

| File | Used by |
|---|---|
| `NunitoSans-Regular.ttf` (w400) | `bodyLarge`, `bodyMedium`, `bodySmall`, and the `nunitoSansTextTheme()` base |
| `NunitoSans-Medium.ttf` (w500) | `labelSmall`, base theme label styles |
| `NunitoSans-SemiBold.ttf` (w600) | `titleSmall`, `labelLarge`, `labelMedium` |
| `NunitoSans-Bold.ttf` (w700) | `titleMedium` |
| `PlayfairDisplay-SemiBold.ttf` (w600) | `displaySmall`, `headlineLarge/Medium/Small`, `titleLarge` |
| `PlayfairDisplay-Bold.ttf` (w700) | `displayLarge`, `displayMedium`, `appBarTheme.titleTextStyle` |

Roughly 674 KB in total.

## Licensing

Bundling means the app **redistributes** the font binaries rather than linking to
Google's CDN, and both families are licensed under the [SIL Open Font License
1.1](https://openfontlicense.org/), which requires the licence to travel with the
files. `assets/fonts/` therefore also holds `OFL-NunitoSans.txt` and
`OFL-PlayfairDisplay.txt`, and `registerFontLicenses()` in `lib/app_theme.dart`
adds them to Flutter's `LicenseRegistry` so they show up in the app's standard
"View licences" page. It is called from `main()` before `runApp`.

**Bundling a third family means registering its licence too** — add the entry to
`fontLicenseAssets` in `lib/app_theme.dart`. `test/app_theme_test.dart` loads
every declared licence asset for real, so a wrong path fails the suite instead of
silently registering nothing.

## Adding a weight

If you add a style to `buildAppTextTheme` that needs a weight not in the table
above, **the test suite will fail loudly** with:

```
GoogleFonts.config.allowRuntimeFetching is false but font X was not found in
the application assets.
```

That failure is the feature — it stops a new weight from silently depending on a
network fetch that only works on a connected machine. To fix it, add the variant:

1. Find the file hash for the family/weight in the `google_fonts` package
manifest (`lib/src/google_fonts_parts/part_<letter>.g.dart` in the pub cache);
each entry is `GoogleFontsFile('<sha256>', <length>)`.
2. Download it and verify both the hash and the length match:

```bash
curl -sSL -o assets/fonts/<Family>-<Variant>.ttf \
"https://fonts.gstatic.com/s/a/<sha256>.ttf"
sha256sum assets/fonts/<Family>-<Variant>.ttf # must equal <sha256>
stat -c%s assets/fonts/<Family>-<Variant>.ttf # must equal <length>
```

3. The filename must be `<FamilyWithoutSpaces>-<Variant>.ttf` — that is exactly
the name quoted in the failure message. `assets/fonts/` is already listed in
`pubspec.yaml`, so no manifest edit is needed.
4. Regenerate the affected goldens and review them by eye:
`./bin/mise run goldens:update <test_file>`.

Do **not** fix this by re-enabling runtime fetching in tests.

## Known gap

Material icon glyphs still render as hollow boxes in goldens — `MaterialIcons`
is not loaded by `flutter_test`. That is unrelated to this setup and unchanged by
it; goldens cover icon *position and size*, not the glyph itself.
34 changes: 34 additions & 0 deletions lib/app_theme.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,42 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';

const Color appSeedColor = Color(0xFF2B3170); // CBF 2026: poster navy blue

/// The SIL Open Font License texts shipped alongside the bundled typefaces in
/// `assets/fonts/`, keyed by the licence entry name shown to users.
@visibleForTesting
const Map<String, String> fontLicenseAssets = <String, String>{
'Nunito Sans': 'assets/fonts/OFL-NunitoSans.txt',
'Playfair Display': 'assets/fonts/OFL-PlayfairDisplay.txt',
};

/// Reads each licence in [fontLicenseAssets] out of the asset bundle.
///
/// Split out from [registerFontLicenses] so tests can consume the stream
/// directly — draining the global `LicenseRegistry.licenses` never completes
/// under `flutter_test`.
@visibleForTesting
Stream<LicenseEntry> loadFontLicenses() async* {
for (final MapEntry<String, String> entry in fontLicenseAssets.entries) {
final String license = await rootBundle.loadString(entry.value);
yield LicenseEntryWithLineBreaks(<String>[entry.key], license);
}
}

/// Registers the OFL licences for the fonts bundled in `assets/fonts/` so they
/// appear in the app's "View licences" page.
///
/// Bundling the font binaries (rather than letting `google_fonts` fetch them
/// from fonts.gstatic.com) means the app redistributes them, and the SIL Open
/// Font License requires the licence to travel with the files. Call this from
/// `main()` before `runApp`.
void registerFontLicenses() {
LicenseRegistry.addLicense(loadFontLicenses);
}

TextTheme buildAppTextTheme(ColorScheme colorScheme) {
final base = GoogleFonts.nunitoSansTextTheme();
return base.copyWith(
Expand Down
4 changes: 4 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ void main() async {

WidgetsFlutterBinding.ensureInitialized();

// The bundled typefaces in assets/fonts/ are redistributed under the SIL
// Open Font License, which requires the licence to ship with them.
registerFontLicenses();

try {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
Expand Down
8 changes: 8 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,11 @@ flutter:
uses-material-design: true
assets:
- assets/
# Bundled so `google_fonts` resolves the app's typefaces from assets rather
# than fetching them from fonts.gstatic.com at runtime. This makes golden
# tests deterministic (see test/flutter_test_config.dart) and removes a
# network round-trip from first paint. Only the variants buildAppTextTheme
# actually asks for are shipped. This entry covers the whole directory, so
# adding a weight means dropping the .ttf into assets/fonts/ — no edit here.
# The directory also holds the OFL licence texts. See docs/code/fonts.md.
- assets/fonts/
Loading
Loading