CSS fonts: OpenType support, compile-time validation and corrected docs - #5508
Conversation
…requirement A customer bundled Nexa as .otf files, put them in common/src/css, and got no font change and no error. Two of the three reasons were things the docs told them. OTF is not supported. It compiles without complaint because the CSS compiler loads fonts through java.awt.Font.createFont(TRUETYPE_FONT, ...), which also parses OpenType/CFF, but Font.createTrueTypeFont rejects any file name that doesn't end in .ttf, and IPhoneBuilder registers only .ttf files in UIAppFonts. The developer guide claimed "TTF/OTF fonts" and the initializr CSS skill reference said ".ttf (or .otf)". Both now say TrueType only and explain the failure mode. The fonts/ subdirectory was never a requirement either. A relative src URL is resolved against the directory holding the CSS file, and merge mode syncs that whole directory, so a font sitting directly beside theme.css works exactly as well as one under fonts/. The guide didn't document the resolution rule at all and three skill references presented common/src/main/css/fonts/ as the location. While in the section, document the parts that make a font silently do nothing: a font-family name with an unquoted space parses as separate identifiers and only the first is read back, so every weight collides under one family; and the @font-face font-weight/font-style descriptors are parsed but never consulted when a family is matched, so each weight needs its own family name and a whole-theme swap goes through the Default selector. Also corrects two stale claims: fonts land next to the compiled theme.res rather than in the project src directory, and the remote-font download cache lives in the build directory. Tests pin both halves of the location contract, since it is now documented: CSSFontFaceLocationTest compiles a theme with the font in the CSS root, in a subdirectory, and with two quoted multi-word families, asserting which file each family resolves to and that it is deployed flat next to theme.res. CN1CSSCLILogicTest covers the merge-mode url() rewrite that Maven actually takes, including that remote and absolute URLs pass through untouched.
The guide has a gate I missed: validate-guide-snippets.py requires every [source] block to be include-backed from docs/demos, so the two inline CSS examples I added failed the docs build. Moved both into guide-snippets-theme.css as tags css-css-044 and css-css-045. That means the demo build now compiles them, which is the point of the fixture rule -- so the examples had to use real fonts rather than a made-up MyFont. Added GuideRootFont.ttf (the 5.8KB icon font the CSSFontFaceTest sample already carries) at the CSS root, alongside the existing res/GuideDemoFont-Bold.ttf, so the snippet demonstrates the root and subdirectory forms with files that exist. Pointed Default at "GuideRootFont" rather than the regular demo font on purpose. A @font-face is only copied to the build output when some style actually references it, so without a reference the root-level font would compile silently and prove nothing. With it, mvn -f docs/demos/pom.xml process-classes deploys GuideRootFont.ttf next to guide-snippets-theme.res, which is exactly the behaviour the new section documents.
Touching the file pulled it into the copyright gate's scope, and it never had a header. It is first-party CN1 content, so it gets the header rather than an entry in copyright-header-exclusions.txt, which is reserved for third-party sources.
An .otf sailed through compilation and only broke on the device. The compiler loads fonts with java.awt.Font.createFont(TRUETYPE_FONT, ...), which also parses OpenType/CFF, so the font resolved, rendered in the simulator and got written into theme.res -- while Font.createTrueTypeFont rejects any file name not ending in .ttf and IPhoneBuilder never registered it in UIAppFonts. The reported symptom is a font that just doesn't change, with no error anywhere. CSSTheme.updateResources now validates every declared @font-face first and throws with the offending rules listed, which CN1CSSCLI turns into a non-zero exit and CompileCSSMojo into a failed build. Rejected: - anything not ending in .ttf, with the advice split by case: convert an .otf, rename an upper-case .TTF (the runtime's endsWith is case-sensitive) - a local font that doesn't exist, which used to surface as a bare FileNotFoundException naming no rule - a local font outside the directory holding the CSS file; merge mode syncs only that directory, so a ../ reference resolves for the author and breaks in a real build - two rules resolving to different files that share a file name, since fonts are deployed next to theme.res by file name alone and one would overwrite the other. Two families pointing at the same file stay legal -- that copy is idempotent, and rejecting it would break a legitimate alias. Every declared rule is checked, not only the referenced ones, so a typo fails the build that introduced it instead of the later build that first uses the family. Remote fonts are judged by URL alone, so validation never downloads. Containment is measured against baseURL rather than cssFile: cssFile is a "test.css" placeholder unless a caller assigns it, so using it rejected every font in the existing tests. Updates the guide and the initializr CSS reference, which described the old compile-clean-fail-at-runtime behaviour.
Two more ways a font passed the compile and then failed on a device, which is
the case the name check alone doesn't catch.
A file the font parser rejects left EditorTTFFont.actualFont null, because
refresh() swallows the Throwable, and then died as a bare NPE inside
EditableResources.save at getNativeFont()).getPSName() naming no rule. This
matters more now that the compile insists on a .ttf name: renaming an .otf is
the obvious workaround, and anything that isn't really loadable has to be
caught here rather than on the device.
A font with no PostScript name renders in the simulator and on Android, which
both look fonts up by file name, while iOS resolves purely by the PostScript
name written into the resource and falls back to the system font. That is
invisible until someone runs the app on an iPhone.
validateFontFaces now parses each local font and checks for a usable
PostScript name, reporting which rule and file is at fault.
Also corrects the extension message, which overclaimed. The constraint is the
file NAME -- Font.createTrueTypeFont tests endsWith(".ttf") and IPhoneBuilder
filters UIAppFonts the same way -- not the outline format. iOS registers
through UIAppFonts and resolves with [UIFont fontWithName:], Android uses
Typeface.createFromAsset, and both read CFF/OpenType content, so no check is
made against the sfnt flavour: an OpenType font renamed to .ttf is not proven
broken and isn't rejected on a guess.
An .otf was never rejected by any rendering stack -- it was rejected by our own file name checks. Core Text, Android's Typeface, DirectWrite, FontConfig, java.awt and the browser's FontFace all read the font container whether the outlines are TrueType or PostScript, so the .ttf-only gates cost users a conversion step for no reason, and gave no diagnostic when they skipped it. Every gate now accepts .ttf and .otf, case insensitively -- capitalisation never said anything about whether a font loads either: - Font.createTrueTypeFont, via the new Font.isSupportedFontFile - StyleParser.parseFontFile, which appended ".ttf" to anything lacking it and so turned "Foo.otf" into "Foo.otf.ttf" - IPhoneBuilder and TvNativeBuilder, which register UIAppFonts - JavaScriptBuilder, which relocates bundled fonts into assets/ - JavaSEPort, for fonts registered out of the app resources - WindowsImplementation and LinuxImplementation, on the in-memory loader path, plus cn1WinIsTtf in cn1_windows_text.c (renamed cn1WinIsFontFile) - the CSS compiler's @font-face validation HTML5Implementation derived the FontFace format hint from the extension rather than always claiming "truetype". Browsers treat the two hints as the same container, but a hint that matches the file leaves no room for a UA that decides to skip a source whose hint disagrees with its bytes. Formats no port can load, .woff among them, still fail the build rather than reaching a device. Verified a real OpenType font (NotoSansJavanese-Regular.otf, sfnt OTTO) through the CLI the CSS goal forks: exit 0, deployed next to theme.res. Full core-unittests suite green at 4677 tests. NOTE: IPhoneBuilder, TvNativeBuilder and JavaScriptBuilder are mirrored in the BuildDaemon repo and cloud builds use that copy, so the three builder changes need twin PRs there before a cloud iOS build will register a bundled .otf.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dfb5b11385
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Compared 12 screenshots: 12 matched. |
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
✅ ByteCodeTranslator Quality ReportTest & Coverage
Benchmark Results
Static Analysis
Generated automatically by the PR CI workflow. |
Cloudflare Preview
|
|
Compared 151 screenshots: 151 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
|
Compared 146 screenshots: 146 matched. Benchmark ResultsDetailed Performance Metrics
|
|
Compared 146 screenshots: 146 matched. Benchmark ResultsDetailed Performance Metrics
|
|
Compared 147 screenshots: 147 matched. |
|
Compared 147 screenshots: 147 matched. |
|
Compared 181 screenshots: 181 matched. |
|
Compared 146 screenshots: 146 matched. Benchmark ResultsDetailed Performance Metrics
|
The bundled-font scan in IOSNative.m only looked for "ttf" resources, and that scan is the whole registration story on watchOS: WatchNativeBuilder's plist carries no UIAppFonts array, unlike the iOS and tvOS ones. So an .otf would have rendered everywhere and fallen back to the system font on the watch -- exactly the split this change set exists to remove. The scan now covers both container extensions in either case. The font file itself already reaches the watch bundle: the watch target mirrors the iOS app's resources build phase, skipping only .xcassets/.storyboard/.xib. Registering a font that UIAppFonts already registered errors, which is expected on iOS/tvOS and discarded as before. Reported by Codex review on #5508.
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ea0f00576d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
This PR improves Codename One’s CSS font pipeline by enabling OpenType (.otf) everywhere .ttf was previously assumed, adding compile-time @font-face validation to fail fast with actionable errors, and updating the initializr references + developer guide to reflect the corrected behavior and constraints.
Changes:
- Accept
.otf(case-insensitive) across runtime ports/builders and fix.otfbeing mishandled as"*.otf.ttf"in CSS parsing. - Add compile-time
@font-facevalidation in the CSS compiler, with new regression tests covering accepted locations, extension checks, and failure modes. - Update initializr reference docs and the developer guide with corrected font placement rules, supported formats, and common pitfalls.
Reviewed changes
Copilot reviewed 21 out of 23 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/initializr/common/src/main/resources/skill/references/react-to-cn1.md | Updates guidance on where bundled font files may live under common/src/main/css/. |
| scripts/initializr/common/src/main/resources/skill/references/css.md | Expands font documentation: .ttf/.otf support, validation behavior, placement rules, and quoting/weights caveats. |
| scripts/initializr/common/src/main/resources/skill/references/android-to-cn1.md | Updates Android→CN1 mapping to reflect .otf support and flexible placement under css/. |
| Ports/WindowsPort/src/com/codename1/impl/windows/WindowsImplementation.java | Extends bundled-font detection to include .otf. |
| Ports/WindowsPort/nativeSources/cn1_windows_text.c | Extends Windows native font-file extension detection from .ttf to .ttf/.otf. |
| Ports/LinuxPort/src/com/codename1/impl/linux/LinuxImplementation.java | Extends bundled-font detection to include .otf. |
| Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java | Registers bundled fonts from both .ttf and .otf in skins/resources. |
| Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Implementation.java | Uses an extension-derived FontFace format hint (truetype vs opentype). |
| Ports/iOSPort/nativeSources/IOSNative.m | Extends iOS bundled-font registration scan to include .otf (and uppercase variants). |
| maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java | Adds @font-face validation (extension, existence, containment, parseability, PostScript name, duplicate filename collisions). |
| maven/css-compiler/src/test/java/com/codename1/designer/css/CSSFontFaceValidationTest.java | New tests for validation success/failure cases and error messaging. |
| maven/css-compiler/src/test/java/com/codename1/designer/css/CSSFontFaceLocationTest.java | New tests pinning down allowed font placement and quoting behavior for multi-word families. |
| maven/css-cli/src/test/java/com/codename1/designer/css/CN1CSSCLILogicTest.java | Extends CLI tests to ensure merge-mode URL prefixing works for root-level font URLs too. |
| maven/core-unittests/src/test/java/com/codename1/ui/FontTest.java | Updates/extends core tests for .otf acceptance and invalid extension rejection. |
| maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/TvNativeBuilder.java | Includes .otf in UIAppFonts generation for tvOS builds. |
| maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/JavaScriptBuilder.java | Ensures .otf gets relocated/packaged during JS build output merging. |
| maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/IPhoneBuilder.java | Includes .otf in UIAppFonts generation for iOS builds. |
| docs/developer-guide/css.asciidoc | Updates guide text and adds detailed sections about bundled font formats, placement, validation, and naming. |
| docs/demos/common/src/main/css/guide-snippets-theme.css | Adds guide snippet fixtures for font placement and Default-based theme font swapping examples. |
| CodenameOne/src/com/codename1/ui/plaf/StyleParser.java | Prevents .otf filenames from being mutated into *.otf.ttf during parsing (but see review comments). |
| CodenameOne/src/com/codename1/ui/Font.java | Extends runtime filename validation to accept .ttf and .otf via a shared helper. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Compared 148 screenshots: 148 matched. Benchmark Results
Detailed Performance Metrics
|
|
Compared 143 screenshots: 143 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
|
Compared 217 screenshots: 217 matched. |
|
Compared 144 screenshots: 144 matched. |
|
Compared 149 screenshots: 149 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
CodenameOne and Ports/CLDC11 are held to markdown doc comments by .github/scripts/validate-java25-markdown-docs.sh, which runs as the first step of build-test. The classic /** block I added failed all three JDK legs before a single test ran.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 23 changed files in this pull request and generated no new comments.
Suppressed comments (6)
CodenameOne/src/com/codename1/ui/plaf/StyleParser.java:670
- hasFontFileSuffix() uses indexOf() to detect a .ttf/.otf suffix. Because indexOf() returns the first occurrence, a filename like "foo.ttf.backup.ttf" (or any name containing ".ttf" earlier) will be misdetected as not having a suffix and will get an extra ".ttf" appended, producing a broken filename.
/// True when the argument already names a font file rather than a bare
/// family, for either of the container extensions the runtime loads.
private static boolean hasFontFileSuffix(String arg) {
String lower = arg.toLowerCase();
return lower.indexOf(".ttf") == lower.length() - 4
|| lower.indexOf(".otf") == lower.length() - 4;
}
maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java:2272
- isSupportedFontFileName() assumes fileName is non-null and calls toLowerCase() unconditionally. validateFontFaces() passes the result of fontFileName(url), which can be empty/null for some URL shapes; this can turn a validation error into a NullPointerException instead of a clean build failure message.
/// author convert an OpenType file or rename it.
private static boolean isSupportedFontFileName(String fileName) {
String lower = fileName.toLowerCase();
return lower.endsWith(".ttf") || lower.endsWith(".otf");
maven/css-compiler/src/test/java/com/codename1/designer/css/CSSFontFaceValidationTest.java:188
- Test method name has a grammar typo ("MayShared"), which makes intent harder to read and search for.
void testTwoFamiliesMaySharedOneFontFile() throws Exception {
docs/developer-guide/css.asciidoc:563
- Grammar: "as well at the" should be "as well as the".
This library supports the https://developer.mozilla.org/en/docs/Web/CSS/font[font], https://developer.mozilla.org/en/docs/Web/CSS/font-size[font-size], https://developer.mozilla.org/en/docs/Web/CSS/font-family[font-family], https://developer.mozilla.org/en/docs/Web/CSS/font-style[font-style], https://developer.mozilla.org/en/docs/Web/CSS/font-weight[font-weight], and https://developer.mozilla.org/en/docs/Web/CSS/text-decoration[text-decoration] properties, as well at the https://developer.mozilla.org/en/docs/Web/CSS/@font-face[@font-face] CSS "at" rule for including TrueType and OpenType fonts.
maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java:2221
- fontFileName() uses URLDecoder.decode(url.getPath(), "UTF-8") to decode the path. URLDecoder is for application/x-www-form-urlencoded and will convert '+' into a space, which can corrupt legitimate filenames/URLs containing '+'. Also, url.getPath() can be null for opaque URLs (e.g., data:), leading to a NullPointerException here.
This issue also appears on line 2269 of the same file.
private static String fontFileName(URL url) {
String path = url.getPath();
try {
path = java.net.URLDecoder.decode(path, "UTF-8");
} catch (UnsupportedEncodingException ex) {
scripts/initializr/common/src/main/resources/skill/references/react-to-cn1.md:84
- This guidance now describes the CSS directory correctly, but it still implies only .ttf is supported. Since this PR adds .otf support too, the docs should mention both extensions here to avoid readers missing that capability.
spacing in `mm`. **Bundle the real font** if you want to match the typeface: drop
the `.ttf` files anywhere under `common/src/main/css/` (beside `theme.css` or in a
subdirectory of it) and reference them with
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: adba3809c6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…support Eight review findings from Codex and Copilot, all real. StyleParser.hasFontFileSuffix used indexOf(...) == length() - 4, which is true for any name shorter than the suffix because both sides are -1. A three character family like "Foo" looked as though it already carried an extension and shipped with none at all. Uses endsWith now, with a regression test over bare names, both containers, an upper-case extension and the native: scheme. The watchOS scan enumerated four spellings, which only ever covers the spellings someone thought to write down while the rest of the stack accepts any case -- ".TtF" would be bundled and never registered. It now walks every bundle resource and compares the lower-cased extension. AddThemeEntry filtered the Resource Editor's font combo with a case-sensitive ".ttf" test, so resource-based themes could not select an OpenType font the API now loads. The @font-face collision key is lower-cased: fonts are flattened into one directory, and "Body.TTF" and "body.ttf" are the same file on a Windows target or in an Apple bundle even when the authoring host keeps them apart, so one would overwrite the other. Covered by a new test. Also: the IllegalArgumentException names ".ttf or .otf" with the dots, the alias test is testTwoFamiliesMayShareOneFontFile, and the guide's long-standing "as well at the" typo on the line this change set already touched is now "as well as the".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0d1239cad4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 25 changed files in this pull request and generated no new comments.
Suppressed comments (3)
Ports/LinuxPort/src/com/codename1/impl/linux/LinuxImplementation.java:1777
- In LinuxImplementation.loadTrueTypeFont(), the comment still references the Windows DirectWrite loader ("DirectWrite in-memory loader"), which is inaccurate for the Linux port and can mislead future maintenance (the Linux native path is via LinuxNative/FontConfig/FreeType).
// Bundled TTFs (material-design-font.ttf and any app font) ship as
// classpath resources embedded in the exe -- load them straight from the
// executable via the DirectWrite in-memory loader so there is no file
// next to the exe. Falls back to the file-based loader (a font staged
// beside the exe) when the resource isn't embedded.
CodenameOne/src/com/codename1/ui/Font.java:343
- Font.createTrueTypeFont(String, String) now accepts the extension case-insensitively (via isSupportedFontFile()), but the parameter docs still read like the extension must match the exact lowercase spelling. Updating the docs to mention case-insensitive matching would keep them aligned with behavior.
/// - `fileName`: the file name of the font as it appears in the src directory of the project, it MUST end
/// with the .ttf or .otf extension!
maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java:2228
- CSSTheme.fontFileName() uses URLDecoder.decode() on the full URL path. URLDecoder is intended for form encoding and will also translate '+' into a space, which can miscompute the deployed file name (and collision key) for valid URLs/filenames containing '+'. It is safer to extract the last path segment first and only percent-decode that segment while preserving '+'.
private static String fontFileName(URL url) {
String path = url.getPath();
try {
path = java.net.URLDecoder.decode(path, "UTF-8");
} catch (UnsupportedEncodingException ex) {
build-ios passed on ea0f005 and adba380 and failed on 0d1239c, the commit that switched cn1RegisterBundledFontsOnce to pathsForResourcesOfType:nil. The BrowserComponent screenshot test stopped emitting output; the other 142 screenshots still matched. The whole-bundle enumeration is the only iOS-side change in that commit. Keeps the case-insensitive match that the review asked for, but gets it from a single shallow listing of the bundle resource root instead of walking every resource in the bundle -- pods, map assets and TensorFlow models included -- on the way to the first glyph. Nothing is missed: fonts are deployed flat next to theme.res, which is exactly why createTrueTypeFont forbids a path separator in the name.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 25 changed files in this pull request and generated no new comments.
Suppressed comments (1)
maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java:2228
- fontFileName() uses URLDecoder.decode(url.getPath(), "UTF-8"). URLDecoder treats '+' as a space (application/x-www-form-urlencoded semantics), which can incorrectly rewrite legitimate '+' characters in file names/paths (e.g., url("My+Font.ttf") becomes "My Font.ttf"), leading to false “missing file”/“duplicate deploy name” validation failures. Prefer URI-based decoding (percent-escapes only) and guard against null paths.
private static String fontFileName(URL url) {
String path = url.getPath();
try {
path = java.net.URLDecoder.decode(path, "UTF-8");
} catch (UnsupportedEncodingException ex) {
// UTF-8 is always present; fall through with the raw path.
}
int slash = path.lastIndexOf('/');
return slash < 0 ? path : path.substring(slash + 1);
}
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 252b61b19d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…pace Two more review findings, both real. The deployment collision key used the no-argument toLowerCase(), so the answer depended on the build machine: under Turkish rules "I.ttf" folds to a dotless "i.ttf" and stops matching "i.ttf", and two files that really do collide when flattened onto a case-insensitive target would both be accepted. Keys now fold with Locale.ROOT, covered by a test that runs under tr-TR and fails without it. fontFileName() decoded the URL path with URLDecoder, which is form decoding: "+" means a space there. A font named "A+B.ttf" was validated as "A B.ttf" -- a name that never exists on disk, and one that could collide with an unrelated "A B.ttf". Percent escapes are now decoded without the form rule, and getFontFile() uses the same helper so a downloaded font lands under the name the validator checked. While here, every extension test goes through regionMatches(true, ...) instead of toLowerCase(). The letters in ".ttf"/".otf" happen to be locale-safe, but a reader shouldn't have to verify that: the checks are now independent of locale by construction, in core, the CSS compiler, the JavaSE/Windows/Linux ports, the iOS and tvOS builders and the resource editor.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 25 changed files in this pull request and generated no new comments.
Suppressed comments (1)
Ports/LinuxPort/src/com/codename1/impl/linux/LinuxImplementation.java:1778
- The comment above the bundled-font load path mentions "DirectWrite", which is Windows-specific and misleading in the Linux port. It looks like a copy/paste from the Windows implementation, but here the loader is via LinuxNative (FontConfig/FreeType), so the comment should be platform-neutral or Linux-specific.
// Bundled TTFs (material-design-font.ttf and any app font) ship as
// classpath resources embedded in the exe -- load them straight from the
// executable via the DirectWrite in-memory loader so there is no file
// next to the exe. Falls back to the file-based loader (a font staged
// beside the exe) when the resource isn't embedded.
if (fileName != null && isBundledFontFile(fileName)) {
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 02436e13d0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Two more review findings. An @font-face with a valid src but no font-family passed validation under a placeholder label. findFontFace() matches on the family name, so such a rule can never be referenced: the custom font is dropped and the app falls back to a native font, which is the silent failure this validation exists to surface. It is an error now, with a test. The UIAppFonts writers appended the font's file name straight into a <string>. A name carrying an XML metacharacter -- "A&B.ttf" is legal on every filesystem we target and Font.createTrueTypeFont accepts it -- produced a malformed Info.plist and failed the Xcode build. Both the iOS and tvOS writers escape now.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 25 changed files in this pull request and generated no new comments.
Suppressed comments (5)
scripts/initializr/common/src/main/resources/skill/references/css.md:360
- This line says “To load a TTF programmatically”, but the surrounding section now documents both .ttf and .otf bundled fonts. Consider wording that matches the broader support.
Bundled font files are **packaged with the app binary** (placed under the build output so the runtime can `Font.createTrueTypeFont(name, file)` them at startup) — they are **not** embedded inside `theme.res`. That means each font you add increases the deployed app size; choose lean subsets where possible.
To load a TTF programmatically:
```java
scripts/initializr/common/src/main/resources/skill/references/css.md:562
- The troubleshooting row still says “Custom TTF…”, but the feature now applies to both .ttf and .otf. Using a more general label avoids implying the issue is TTF-specific.
| Custom TTF doesn't render on device but works in simulator | The `@font-face` `src:` filename and the JS-side `Font.createTrueTypeFont(name, file)` filename must match exactly, and the file must end up packaged with the app. Re-check spelling and confirm the file is under `common/src/main/css/` (beside `theme.css` or in a subdirectory of it). |
scripts/initializr/common/src/main/resources/skill/references/css.md:338
- The section title and the placement sentence still refer to “TTF” only, but the text now documents both .ttf and .otf support. This is misleading/inconsistent for readers scanning headings and the first instruction line.
This issue also appears in the following locations of the same file:
- line 356
- line 562
### Custom TTF fonts
**`.ttf` and `.otf` both work**, upper-case or lower-case — every port loads both formats through its own font API (Core Text, Typeface, DirectWrite, FontConfig, java.awt, FontFace), so there's no need to convert an OpenType font. Web-only formats like `.woff` are not supported and **fail the build**. The compiler also parses each font at build time, so a corrupt file — or one with no PostScript name, which iOS needs to resolve it — fails the build rather than rendering fine in the simulator and falling back to the system font on an iPhone.
Drop the `.ttf` anywhere under `common/src/main/css/` — relative `src:` URLs resolve against the directory holding `theme.css`, so both `url("Inter-Regular.ttf")` beside the CSS and `url("fonts/Inter-Regular.ttf")` in a subdirectory work. It must be somewhere under that directory though: only that directory is copied into the build, so a `../` reference, a missing file, or two rules naming different files that share a file name all fail the build. Then reference its **font name (not file name)** in `font-family`:
scripts/initializr/common/src/main/resources/skill/references/react-to-cn1.md:85
- This guidance still says to “drop the .ttf files…”, but the PR now supports bundling .otf too. Updating the wording avoids implying .otf is unsupported in this workflow.
spacing in `mm`. **Bundle the real font** if you want to match the typeface: drop
the `.ttf` files anywhere under `common/src/main/css/` (beside `theme.css` or in a
subdirectory of it) and reference them with
`@font-face { font-family: "Inter"; src: url("fonts/Inter-Regular.ttf"); }` (one
Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Implementation.java:9036
- fontFormatOf() uses toLowerCase() just to do a case-insensitive suffix check, which allocates a new String on every call. This runs on the font load path, so it’s easy to avoid the allocation by using regionMatches(true, ...) like other new suffix checks in this PR.
private static String fontFormatOf(String fileName) {
return fileName != null && fileName.toLowerCase().endsWith(".otf") ? "opentype" : "truetype";
}
A customer bundled Nexa as
.otf, got no font change and no error. Fixing that properly turned into three related things, all here.1. OpenType now works
No rendering stack ever rejected OpenType — our own filename checks did. Every port loads fonts through an API that reads the container regardless of outline type:
java.awt.Font.createFont(TRUETYPE_FONT)LastResort.otf(sfnt=OTTO)Typeface.createFromAssetUIAppFonts+[UIFont fontWithName:](Core Text)FontFaceEvery gate now accepts
.ttfand.otf, case-insensitively:Font.createTrueTypeFont(via the newFont.isSupportedFontFile),StyleParser.parseFontFile(which turnedFoo.otfintoFoo.otf.ttf),IPhoneBuilder,TvNativeBuilder,JavaScriptBuilder,JavaSEPort,WindowsImplementation,LinuxImplementation,cn1WinIsTtf→cn1WinIsFontFileincn1_windows_text.c, and the CSS compiler.HTML5Implementationderives theFontFaceformat hint from the extension instead of hardcoding"truetype".2. The compile fails instead of the device
CSSTheme.updateResourcesvalidates every declared@font-facefirst, so these stop reaching a device:.woff)../resolves for the author and breaks in a real buildFileNotFoundExceptionnaming no rule)EditorTTFFont.actualFontnull and die as an NPE inEditableResources.saveEvery declared rule is checked, not just referenced ones, so a typo fails the build that introduced it. Remote fonts are judged by URL, so validation never downloads.
3. Docs corrected
The guide claimed "TTF/OTF" support (wrong then, right now for a different reason) and three initializr references presented
common/src/main/css/fonts/as mandatory. A relativesrcURL resolves against the CSS file's own directory, so besidetheme.cssworks as well as a subdirectory. Also documented: family names with spaces must be quoted (unquoted parses as separate identifiers and every weight collides under one family), and@font-face'sfont-weight/font-styledescriptors are never consulted, so each weight needs its own family and a whole-theme swap goes throughDefault.Verification
NotoSansJavanese-Regular.otf,sfnt=OTTO) through the CLI the CSS goal forks: exit 0, deployed next totheme.rescore-unittests: 4677 tests green; css-compiler + css-cli: 26 green@font-facein the repo already used.ttf, so nothing in-tree starts failingTwo caveats
.otf.cn1_windows_text.cis compile-unverified locally (needs clang-cl); CI's Windows job gives it its first compile.🤖 Generated with Claude Code