Skip to content

CSS fonts: OpenType support, compile-time validation and corrected docs - #5508

Merged
shai-almog merged 12 commits into
masterfrom
feature/otf-font-support
Aug 3, 2026
Merged

CSS fonts: OpenType support, compile-time validation and corrected docs#5508
shai-almog merged 12 commits into
masterfrom
feature/otf-font-support

Conversation

@shai-almog

@shai-almog shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

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:

Platform Loader Reads OpenType
Simulator / desktop java.awt.Font.createFont(TRUETYPE_FONT) Yes — verified by running it on LastResort.otf (sfnt=OTTO)
Android Typeface.createFromAsset Yes
iOS / tvOS / watchOS UIAppFonts + [UIFont fontWithName:] (Core Text) Yes
Windows DirectWrite Yes
Linux FreeType + FontConfig Yes
JavaScript FontFace Yes

Every gate now accepts .ttf and .otf, case-insensitively: Font.createTrueTypeFont (via the new Font.isSupportedFontFile), StyleParser.parseFontFile (which turned Foo.otf into Foo.otf.ttf), IPhoneBuilder, TvNativeBuilder, JavaScriptBuilder, JavaSEPort, WindowsImplementation, LinuxImplementation, cn1WinIsTtfcn1WinIsFontFile in cn1_windows_text.c, and the CSS compiler. HTML5Implementation derives the FontFace format hint from the extension instead of hardcoding "truetype".

2. The compile fails instead of the device

CSSTheme.updateResources validates every declared @font-face first, so these stop reaching a device:

  • a container no port loads (.woff)
  • a font outside the CSS directory — merge mode syncs only that directory, so ../ resolves for the author and breaks in a real build
  • a missing file (was a bare FileNotFoundException naming no rule)
  • a file the font parser rejects — used to leave EditorTTFFont.actualFont null and die as an NPE in EditableResources.save
  • a font with no PostScript name — renders in the simulator and on Android, which resolve by file name, but iOS resolves by PostScript name and falls back to the system font
  • two rules resolving to different files sharing a name (deploy is by file name alone, so one overwrites the other). Two families pointing at the same file stays legal.

Every 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 relative src URL resolves against the CSS file's own directory, so beside theme.css works 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's font-weight/font-style descriptors are never consulted, so each weight needs its own family and a whole-theme swap goes through Default.

Verification

  • Real OpenType font (NotoSansJavanese-Regular.otf, sfnt=OTTO) through the CLI the CSS goal forks: exit 0, deployed next to theme.res
  • core-unittests: 4677 tests green; css-compiler + css-cli: 26 green
  • Builds: core, javase, windows, linux, parparvm (JS port), maven plugin; SpotBugs clean on the plugin
  • Guide gates (snippet validator, asciidoctor, Vale, paragraph-cap, LanguageTool) and copyright headers clean
  • Every @font-face in the repo already used .ttf, so nothing in-tree starts failing

Two caveats

  1. The three builder changes need twin BuildDaemon PRs — cloud builds use that copy, so until they land a cloud iOS build won't register a bundled .otf.
  2. cn1_windows_text.c is compile-unverified locally (needs clang-cl); CI's Windows job gives it its first compile.

🤖 Generated with Claude Code

…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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread CodenameOne/src/com/codename1/ui/Font.java Outdated
@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 12 screenshots: 12 matched.
✅ JavaSE simulator integration screenshots matched stored baselines.

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 414 total, 0 failed, 14 skipped

Benchmark Results

  • Execution Time: 23991 ms

  • Hotspots (Top 20 sampled methods):

    • 18.14% java.util.ArrayList.indexOf (363 samples)
    • 5.70% com.codename1.tools.translator.BytecodeMethod.addToConstantPool (114 samples)
    • 5.65% com.codename1.tools.translator.ByteCodeClass.hasDeclaredMethod (113 samples)
    • 5.10% com.codename1.tools.translator.BytecodeMethod.equals (102 samples)
    • 3.85% com.codename1.tools.translator.Parser.cn1EnsureSubclassIndex (77 samples)
    • 3.15% java.lang.StringBuilder.append (63 samples)
    • 2.80% com.codename1.tools.translator.bytecodes.Invoke.findMethodUp (56 samples)
    • 2.25% com.codename1.tools.translator.Parser.classIndex (45 samples)
    • 1.90% com.codename1.tools.translator.BytecodeMethod.optimize (38 samples)
    • 1.80% com.codename1.tools.translator.BytecodeMethod.appendCMethodPrefix (36 samples)
    • 1.75% com.codename1.tools.translator.Parser.generateClassAndMethodIndexHeader (35 samples)
    • 1.70% java.lang.System.identityHashCode (34 samples)
    • 1.55% org.objectweb.asm.tree.analysis.Analyzer.analyze (31 samples)
    • 1.50% java.lang.String.equals (30 samples)
    • 1.45% org.objectweb.asm.ClassReader.readCode (29 samples)
    • 1.35% java.lang.Object.hashCode (27 samples)
    • 1.20% java.lang.StringCoding.encode (24 samples)
    • 1.15% org.objectweb.asm.tree.analysis.Analyzer.findSubroutine (23 samples)
    • 1.00% com.codename1.tools.translator.bytecodes.Invoke.resolveDirectTarget (20 samples)
    • 0.95% com.codename1.tools.translator.BytecodeMethod.appendMethodC (19 samples)
  • ⚠️ Coverage report not generated.

Static Analysis

  • ✅ SpotBugs: no findings (report was not generated by the build).
  • ⚠️ PMD report not generated.
  • ⚠️ Checkstyle report not generated.

Generated automatically by the PR CI workflow.

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 7.85% (7605/96841 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 7.72% (39610/513127), branch 2.80% (1363/48627), complexity 3.15% (1646/52177), method 4.87% (1345/27595), class 9.98% (367/3679)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 7.85% (7605/96841 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 7.72% (39610/513127), branch 2.80% (1363/48627), complexity 3.15% (1646/52177), method 4.87% (1345/27595), class 9.98% (367/3679)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend scalar fallback (no native SIMD)
SIMD int-add (64K x300) java 97ms / native 320ms = 0.3x speedup
SIMD float-mul (64K x300) java 176ms / native 179ms = 0.9x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 108.000 ms
Base64 CN1 decode 88.000 ms
Base64 native encode 473.000 ms
Base64 encode ratio (CN1/native) 0.228x (77.2% faster)
Base64 native decode 294.000 ms
Base64 decode ratio (CN1/native) 0.299x (70.1% faster)
Image encode benchmark status skipped (SIMD unsupported)

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 146 screenshots: 146 matched.
Native Windows port (x64 / Intel-AMD): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, SSE2 SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 64ms / native 4ms = 16.0x speedup
SIMD float-mul (64K x300) java 64ms / native 4ms = 16.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 192.000 ms
Base64 CN1 decode 131.000 ms
Base64 SIMD encode 99.000 ms
Base64 encode ratio (SIMD/CN1) 0.516x (48.4% faster)
Base64 SIMD decode 98.000 ms
Base64 decode ratio (SIMD/CN1) 0.748x (25.2% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 25.000 ms
Image createMask (SIMD on) 19.000 ms
Image createMask ratio (SIMD on/off) 0.760x (24.0% faster)
Image applyMask (SIMD off) 58.000 ms
Image applyMask (SIMD on) 53.000 ms
Image applyMask ratio (SIMD on/off) 0.914x (8.6% faster)
Image modifyAlpha (SIMD off) 182.000 ms
Image modifyAlpha (SIMD on) 36.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.198x (80.2% faster)
Image modifyAlpha removeColor (SIMD off) 41.000 ms
Image modifyAlpha removeColor (SIMD on) 33.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.805x (19.5% faster)

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 146 screenshots: 146 matched.
Native Windows port, REAL shipping pipeline: the hellocodenameone screenshot suite rendered by a binary CROSS-COMPILED on Linux (clang-cl + xwin, WebView2 linked) and RUN on a Windows x64 runner. Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 64ms / native 4ms = 16.0x speedup
SIMD float-mul (64K x300) java 97ms / native 7ms = 13.8x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 194.000 ms
Base64 CN1 decode 135.000 ms
Base64 SIMD encode 99.000 ms
Base64 encode ratio (SIMD/CN1) 0.510x (49.0% faster)
Base64 SIMD decode 100.000 ms
Base64 decode ratio (SIMD/CN1) 0.741x (25.9% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 37.000 ms
Image createMask (SIMD on) 33.000 ms
Image createMask ratio (SIMD on/off) 0.892x (10.8% faster)
Image applyMask (SIMD off) 70.000 ms
Image applyMask (SIMD on) 62.000 ms
Image applyMask ratio (SIMD on/off) 0.886x (11.4% faster)
Image modifyAlpha (SIMD off) 74.000 ms
Image modifyAlpha (SIMD on) 66.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.892x (10.8% faster)
Image modifyAlpha removeColor (SIMD off) 67.000 ms
Image modifyAlpha removeColor (SIMD on) 36.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.537x (46.3% faster)

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 147 screenshots: 147 matched.
Native Linux port (x64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub x64 runner. Baseline: scripts/linux/screenshots.

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 147 screenshots: 147 matched.
Native Linux port (arm64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub arm64 runner. Baseline: scripts/linux/screenshots-arm.

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 181 screenshots: 181 matched.
✅ JavaScript-port screenshot tests passed.

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 146 screenshots: 146 matched.
Native Windows port (arm64 / Apple Silicon - Arm): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, NEON SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 59ms / native 3ms = 19.6x speedup
SIMD float-mul (64K x300) java 56ms / native 3ms = 18.6x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 246.000 ms
Base64 CN1 decode 128.000 ms
Base64 SIMD encode 64.000 ms
Base64 encode ratio (SIMD/CN1) 0.260x (74.0% faster)
Base64 SIMD decode 63.000 ms
Base64 decode ratio (SIMD/CN1) 0.492x (50.8% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 12.000 ms
Image createMask (SIMD on) 8.000 ms
Image createMask ratio (SIMD on/off) 0.667x (33.3% faster)
Image applyMask (SIMD off) 26.000 ms
Image applyMask (SIMD on) 156.000 ms
Image applyMask ratio (SIMD on/off) 6.000x (500.0% slower)
Image modifyAlpha (SIMD off) 17.000 ms
Image modifyAlpha (SIMD on) 12.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.706x (29.4% faster)
Image modifyAlpha removeColor (SIMD off) 22.000 ms
Image modifyAlpha removeColor (SIMD on) 14.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.636x (36.4% faster)

@shai-almog
shai-almog changed the base branch from feature/css-font-face-validation to master August 2, 2026 01:14
@shai-almog shai-almog changed the title Support OpenType fonts on every port CSS fonts: OpenType support, compile-time validation and corrected docs Aug 2, 2026
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.
Copilot AI review requested due to automatic review settings August 2, 2026 01:21
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread Ports/iOSPort/nativeSources/IOSNative.m Outdated
Comment thread CodenameOne/src/com/codename1/ui/Font.java Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 .otf being mishandled as "*.otf.ttf" in CSS parsing.
  • Add compile-time @font-face validation 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.

Comment thread CodenameOne/src/com/codename1/ui/plaf/StyleParser.java
Comment thread Ports/iOSPort/nativeSources/IOSNative.m Outdated
Comment thread docs/developer-guide/css.asciidoc Outdated
Comment thread CodenameOne/src/com/codename1/ui/Font.java
@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 210 seconds

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 55ms / native 3ms = 18.3x speedup
SIMD float-mul (64K x300) java 55ms / native 2ms = 27.5x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 157.000 ms
Base64 CN1 decode 92.000 ms
Base64 native encode 478.000 ms
Base64 encode ratio (CN1/native) 0.328x (67.2% faster)
Base64 native decode 211.000 ms
Base64 decode ratio (CN1/native) 0.436x (56.4% faster)
Base64 SIMD encode 48.000 ms
Base64 encode ratio (SIMD/CN1) 0.306x (69.4% faster)
Base64 SIMD decode 44.000 ms
Base64 decode ratio (SIMD/CN1) 0.478x (52.2% faster)
Base64 encode ratio (SIMD/native) 0.100x (90.0% faster)
Base64 decode ratio (SIMD/native) 0.209x (79.1% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 6.000 ms
Image createMask (SIMD on) 2.000 ms
Image createMask ratio (SIMD on/off) 0.333x (66.7% faster)
Image applyMask (SIMD off) 42.000 ms
Image applyMask (SIMD on) 43.000 ms
Image applyMask ratio (SIMD on/off) 1.024x (2.4% slower)
Image modifyAlpha (SIMD off) 47.000 ms
Image modifyAlpha (SIMD on) 84.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.787x (78.7% slower)
Image modifyAlpha removeColor (SIMD off) 84.000 ms
Image modifyAlpha removeColor (SIMD on) 64.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.762x (23.8% faster)

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 143 screenshots: 143 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 297 seconds

Build and Run Timing

Metric Duration
Simulator Boot 61000 ms
Simulator Boot (Run) 1000 ms
App Install 12000 ms
App Launch 12000 ms
Test Execution 433000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 55ms / native 3ms = 18.3x speedup
SIMD float-mul (64K x300) java 54ms / native 3ms = 18.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 316.000 ms
Base64 CN1 decode 109.000 ms
Base64 native encode 356.000 ms
Base64 encode ratio (CN1/native) 0.888x (11.2% faster)
Base64 native decode 284.000 ms
Base64 decode ratio (CN1/native) 0.384x (61.6% faster)
Base64 SIMD encode 58.000 ms
Base64 encode ratio (SIMD/CN1) 0.184x (81.6% faster)
Base64 SIMD decode 47.000 ms
Base64 decode ratio (SIMD/CN1) 0.431x (56.9% faster)
Base64 encode ratio (SIMD/native) 0.163x (83.7% faster)
Base64 decode ratio (SIMD/native) 0.165x (83.5% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 7.000 ms
Image createMask (SIMD on) 3.000 ms
Image createMask ratio (SIMD on/off) 0.429x (57.1% faster)
Image applyMask (SIMD off) 50.000 ms
Image applyMask (SIMD on) 39.000 ms
Image applyMask ratio (SIMD on/off) 0.780x (22.0% faster)
Image modifyAlpha (SIMD off) 56.000 ms
Image modifyAlpha (SIMD on) 38.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.679x (32.1% faster)
Image modifyAlpha removeColor (SIMD off) 153.000 ms
Image modifyAlpha removeColor (SIMD on) 120.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.784x (21.6% faster)

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 217 screenshots: 217 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 144 screenshots: 144 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

@shai-almog

shai-almog commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 410 seconds

Build and Run Timing

Metric Duration
Simulator Boot 70000 ms
Simulator Boot (Run) 1000 ms
App Install 17000 ms
App Launch 11000 ms
Test Execution 478000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 65ms / native 4ms = 16.2x speedup
SIMD float-mul (64K x300) java 70ms / native 3ms = 23.3x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 230.000 ms
Base64 CN1 decode 99.000 ms
Base64 native encode 737.000 ms
Base64 encode ratio (CN1/native) 0.312x (68.8% faster)
Base64 native decode 398.000 ms
Base64 decode ratio (CN1/native) 0.249x (75.1% faster)
Base64 SIMD encode 56.000 ms
Base64 encode ratio (SIMD/CN1) 0.243x (75.7% faster)
Base64 SIMD decode 65.000 ms
Base64 decode ratio (SIMD/CN1) 0.657x (34.3% faster)
Base64 encode ratio (SIMD/native) 0.076x (92.4% faster)
Base64 decode ratio (SIMD/native) 0.163x (83.7% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 14.000 ms
Image createMask (SIMD on) 1.000 ms
Image createMask ratio (SIMD on/off) 0.071x (92.9% faster)
Image applyMask (SIMD off) 54.000 ms
Image applyMask (SIMD on) 39.000 ms
Image applyMask ratio (SIMD on/off) 0.722x (27.8% faster)
Image modifyAlpha (SIMD off) 48.000 ms
Image modifyAlpha (SIMD on) 60.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.250x (25.0% slower)
Image modifyAlpha removeColor (SIMD off) 178.000 ms
Image modifyAlpha removeColor (SIMD on) 120.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.674x (32.6% faster)

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.
Copilot AI review requested due to automatic review settings August 2, 2026 07:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java Outdated
…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".
Copilot AI review requested due to automatic review settings August 2, 2026 10:01

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copilot AI review requested due to automatic review settings August 2, 2026 14:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
    }

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java Outdated
…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.
Copilot AI review requested due to automatic review settings August 2, 2026 15:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)) {

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java Outdated
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.
Copilot AI review requested due to automatic review settings August 2, 2026 19:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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";
    }

@shai-almog
shai-almog merged commit 8e58b35 into master Aug 3, 2026
65 checks passed
@shai-almog
shai-almog deleted the feature/otf-font-support branch August 3, 2026 00:59
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.

2 participants