Skip to content

Correct the CSS font docs: TrueType only, and no fonts/ subdirectory requirement - #5502

Closed
shai-almog wants to merge 3 commits into
masterfrom
docs/css-font-ttf-only
Closed

Correct the CSS font docs: TrueType only, and no fonts/ subdirectory requirement#5502
shai-almog wants to merge 3 commits into
masterfrom
docs/css-font-ttf-only

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Why

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 our own 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 (Font.java:342), and IPhoneBuilder registers only .ttf files in UIAppFonts (IPhoneBuilder.java:4989). The developer guide claimed "TTF/OTF fonts" and the initializr CSS skill reference said ".ttf (or .otf)".

The fonts/ subdirectory was never a requirement. A relative src URL is resolved against the directory holding the CSS file (CSSTheme.java:1173), 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.

What changed

Docs only — no behavior change. There was no code requirement to remove; root-level fonts already worked.

  • docs/developer-guide/css.asciidoc — TTF only, with an IMPORTANT explaining the runtime rejection. New subsections covering where the font file may live, family-name quoting, and how weights/styles actually resolve.
  • Initializr skill references (css.md, android-to-cn1.md, react-to-cn1.md) — same two corrections, plus a troubleshooting row for "custom font is ignored entirely, no error."

While in the section, documented the other two things 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 (CSSTheme.java:5306), so every weight collides under one family.
  • The @font-face font-weight / font-style descriptors are parsed but never consulted when a family is matched (CSSTheme.java:5522-5526), so each weight needs its own family name and a whole-theme swap goes through the Default selector.

Also corrected 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 (so mvn clean discards it).

Tests

The location contract is now documented, so it is pinned:

  • CSSFontFaceLocationTest (new) — 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 root-placement test was checked for vacuity by renaming the fixture; it fails with FileNotFoundException on the expected root path.

Verification

  • mvn -pl css-compiler,css-cli test — 15 tests, green
  • Vale 0 errors / 0 warnings / 0 suggestions; paragraph-capitalization 0 issues; asciidoctor --failure-level WARN clean; LanguageTool 0 matches, status=ok
  • Copyright headers pass; new Java sources are ASCII-only

🤖 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.
@github-actions

github-actions Bot commented Jul 31, 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.

@shai-almog

shai-almog commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 7.85% (7601/96841 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 7.71% (39571/513127), branch 2.80% (1360/48627), complexity 3.15% (1643/52177), method 4.87% (1343/27595), class 9.95% (366/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% (7601/96841 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 7.71% (39571/513127), branch 2.80% (1360/48627), complexity 3.15% (1643/52177), method 4.87% (1343/27595), class 9.95% (366/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 154ms / native 98ms = 1.5x speedup
SIMD float-mul (64K x300) java 118ms / native 83ms = 1.4x 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 70.000 ms
Base64 CN1 decode 68.000 ms
Base64 native encode 376.000 ms
Base64 encode ratio (CN1/native) 0.186x (81.4% faster)
Base64 native decode 287.000 ms
Base64 decode ratio (CN1/native) 0.237x (76.3% faster)
Image encode benchmark status skipped (SIMD unsupported)

@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@shai-almog

shai-almog commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

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

@shai-almog

shai-almog commented Jul 31, 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: 523 seconds

Build and Run Timing

Metric Duration
Simulator Boot 81000 ms
Simulator Boot (Run) 1000 ms
App Install 19000 ms
App Launch 16000 ms
Test Execution 541000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 54ms / native 3ms = 18.0x speedup
SIMD float-mul (64K x300) java 55ms / native 3ms = 18.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 252.000 ms
Base64 CN1 decode 128.000 ms
Base64 native encode 730.000 ms
Base64 encode ratio (CN1/native) 0.345x (65.5% faster)
Base64 native decode 398.000 ms
Base64 decode ratio (CN1/native) 0.322x (67.8% faster)
Base64 SIMD encode 119.000 ms
Base64 encode ratio (SIMD/CN1) 0.472x (52.8% faster)
Base64 SIMD decode 96.000 ms
Base64 decode ratio (SIMD/CN1) 0.750x (25.0% faster)
Base64 encode ratio (SIMD/native) 0.163x (83.7% faster)
Base64 decode ratio (SIMD/native) 0.241x (75.9% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 8.000 ms
Image createMask (SIMD on) 1.000 ms
Image createMask ratio (SIMD on/off) 0.125x (87.5% faster)
Image applyMask (SIMD off) 77.000 ms
Image applyMask (SIMD on) 63.000 ms
Image applyMask ratio (SIMD on/off) 0.818x (18.2% faster)
Image modifyAlpha (SIMD off) 89.000 ms
Image modifyAlpha (SIMD on) 74.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.831x (16.9% faster)
Image modifyAlpha removeColor (SIMD off) 96.000 ms
Image modifyAlpha removeColor (SIMD on) 76.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.792x (20.8% faster)

@shai-almog

shai-almog commented Jul 31, 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 Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

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

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.

@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: fb7301ce3c

ℹ️ 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".


A `font-family` name that contains spaces must be quoted, both in the `@font-face` rule and wherever you reference it. An unquoted name is parsed as a list of separate identifiers, so `font-family: GuideDemoFont Bold` registers the family as `GuideDemoFont` and collides with your regular weight.

`font-weight` and `font-style` select between the built-in `native:` fonts, but they have no effect once `font-family` resolves to a `@font-face` rule. Declare one `@font-face` per weight and style you need, each with its own family name, as in the example above, then reference the right family from each UIID.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Correct custom-font weight and style behavior

This categorical statement conflates unused font-weight/font-style descriptors inside @font-face with those properties on a UIID. For a UIID using a custom face, CSSTheme.getThemeFont() preserves the requested style, and Resources.createTrueTypeFont() passes it to derive(); Android and JavaSE consequently synthesize bold/italic variants, although iOS currently ignores those flags. Please describe this as platform-dependent and recommend separate files for consistent cross-platform rendering rather than saying the properties have no effect; the same inaccurate claim also appears in scripts/initializr/common/src/main/resources/skill/references/css.md.

Useful? React with 👍 / 👎.

@github-actions

github-actions Bot commented Jul 31, 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)

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.

@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: f71e01789f

ℹ️ 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".


`font-weight` and `font-style` select between the built-in `native:` fonts, but they have no effect once `font-family` resolves to a `@font-face` rule. Declare one `@font-face` per weight and style you need, each with its own family name, as in the example above, then reference the right family from each UIID.

To change the base font of an entire theme, set `font-family` on the special `Default` selector, then override the UIIDs that need a bold or italic face:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Override explicit regular fonts in theme-wide swaps

When includeNativeBool is enabled, as it is in this guide fixture, setting Default.font only changes the fallback style; the bundled native themes explicitly set fonts on regular UIIDs such as Label and Button, so those components remain on native:MainRegular/native:MainLight. This recipe therefore does not change the base font of the entire theme, and users must also override every font-bearing regular UIID (not merely bold or italic ones); the same misleading whole-theme-swap advice appears in scripts/initializr/common/src/main/resources/skill/references/css.md.

Useful? React with 👍 / 👎.

@shai-almog

shai-almog commented Jul 31, 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: 290 seconds

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 77ms / native 3ms = 25.6x speedup
SIMD float-mul (64K x300) java 66ms / native 3ms = 22.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 157.000 ms
Base64 CN1 decode 93.000 ms
Base64 native encode 567.000 ms
Base64 encode ratio (CN1/native) 0.277x (72.3% faster)
Base64 native decode 248.000 ms
Base64 decode ratio (CN1/native) 0.375x (62.5% faster)
Base64 SIMD encode 48.000 ms
Base64 encode ratio (SIMD/CN1) 0.306x (69.4% faster)
Base64 SIMD decode 50.000 ms
Base64 decode ratio (SIMD/CN1) 0.538x (46.2% faster)
Base64 encode ratio (SIMD/native) 0.085x (91.5% faster)
Base64 decode ratio (SIMD/native) 0.202x (79.8% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 7.000 ms
Image createMask (SIMD on) 1.000 ms
Image createMask ratio (SIMD on/off) 0.143x (85.7% faster)
Image applyMask (SIMD off) 59.000 ms
Image applyMask (SIMD on) 46.000 ms
Image applyMask ratio (SIMD on/off) 0.780x (22.0% faster)
Image modifyAlpha (SIMD off) 51.000 ms
Image modifyAlpha (SIMD on) 47.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.922x (7.8% faster)
Image modifyAlpha removeColor (SIMD off) 58.000 ms
Image modifyAlpha removeColor (SIMD on) 49.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.845x (15.5% faster)

@shai-almog

shai-almog commented Jul 31, 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: 360 seconds

Build and Run Timing

Metric Duration
Simulator Boot 78000 ms
Simulator Boot (Run) 1000 ms
App Install 12000 ms
App Launch 2000 ms
Test Execution 412000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 107ms / native 8ms = 13.3x speedup
SIMD float-mul (64K x300) java 116ms / native 6ms = 19.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 197.000 ms
Base64 CN1 decode 94.000 ms
Base64 native encode 849.000 ms
Base64 encode ratio (CN1/native) 0.232x (76.8% faster)
Base64 native decode 288.000 ms
Base64 decode ratio (CN1/native) 0.326x (67.4% faster)
Base64 SIMD encode 48.000 ms
Base64 encode ratio (SIMD/CN1) 0.244x (75.6% faster)
Base64 SIMD decode 45.000 ms
Base64 decode ratio (SIMD/CN1) 0.479x (52.1% faster)
Base64 encode ratio (SIMD/native) 0.057x (94.3% faster)
Base64 decode ratio (SIMD/native) 0.156x (84.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 35.000 ms
Image createMask (SIMD on) 34.000 ms
Image createMask ratio (SIMD on/off) 0.971x (2.9% faster)
Image applyMask (SIMD off) 307.000 ms
Image applyMask (SIMD on) 181.000 ms
Image applyMask ratio (SIMD on/off) 0.590x (41.0% faster)
Image modifyAlpha (SIMD off) 259.000 ms
Image modifyAlpha (SIMD on) 176.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.680x (32.0% faster)
Image modifyAlpha removeColor (SIMD off) 120.000 ms
Image modifyAlpha removeColor (SIMD on) 106.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.883x (11.7% faster)

@shai-almog

Copy link
Copy Markdown
Collaborator Author

Folded into #5508 - one PR for the whole change.

@shai-almog shai-almog closed this Aug 2, 2026
@shai-almog
shai-almog deleted the docs/css-font-ttf-only branch August 2, 2026 01:14
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.

1 participant