Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
684849a
Gates for the guide defects Asciidoctor cannot see
shai-almog Sep 2, 2026
eebe28f
Bank the four holes the constraints chapter no longer has
shai-almog Sep 2, 2026
2555762
An unrelated subsection must not stand in for a swallowed chapter
shai-almog Sep 2, 2026
877a371
The ifeval branch of the conditional test could never match
shai-almog Sep 2, 2026
2a08ef7
Record why commented-out URLs are checked like any other
shai-almog Sep 2, 2026
f69f47c
Four links left the book to re-enter it, and nothing checked the anchor
shai-almog Sep 2, 2026
82ed7bf
Two silent blind spots in the guards become loud refusals
shai-almog Sep 2, 2026
520ced0
Check what swallows a heading, not whether its title turns up somewhere
shai-almog Sep 2, 2026
9ffb68c
The slash rule rejected routes the site declares, and Hugo could move…
shai-almog Sep 2, 2026
4f6f2a5
The PDF branch was never rendered, and an admonition counted as a lis…
shai-almog Sep 2, 2026
bbc2ee5
Three links from the old wiki went nowhere, and a directive is not a …
shai-almog Sep 2, 2026
9a508aa
A Cloudflare function serves /files and /demos, and the PDF surrogate…
shai-almog Sep 2, 2026
9d351ff
A five-dash block is still a literal block, and both scanners read in…
shai-almog Sep 2, 2026
425e749
Guide figures render with a bundled font instead of the host's (#5668)
shai-almog Sep 2, 2026
94a3b45
A comment block is not content, and img/ really does ship beside the …
shai-almog Sep 2, 2026
773297b
The packaging exemption trusted the repository, not the published output
shai-almog Sep 2, 2026
93b0518
The Introduction taught a dead IDE menu, an iTunes walkthrough and 20…
shai-almog Sep 2, 2026
b1f80df
Copyright headers for the two Windows wrappers, and duplicate include…
shai-almog Sep 2, 2026
cce4454
Refuse a conditional include at every depth, not only in the manifest
shai-almog Sep 2, 2026
aa6af63
A typo in the authority hid behind a correct hostname
shai-almog Sep 2, 2026
41c3133
A listing hides content from the reader, not directives from the prep…
shai-almog Sep 2, 2026
5e79881
The DNS root dot walked straight past the same-site checks
shai-almog Sep 2, 2026
b50b795
Read the fallback routes from the Function, and close the authority f…
shai-almog Sep 2, 2026
2a25ea8
An inline directive is content, and an attribute-built URL is unreadable
shai-almog Sep 2, 2026
2cc2941
Three Javadoc links pointed at a package that is spelt lowercase
shai-almog Sep 2, 2026
50cf047
The Javadoc index documented a package the build deliberately removes
shai-almog Sep 2, 2026
21e5d5c
Javadoc documents public types, and the index was recording every sou…
shai-almog Sep 2, 2026
4c34787
An uppercase scheme was invisible, and the two slash rules had drifte…
shai-almog Sep 2, 2026
6c1c945
A nested type was taken on trust, and an include hid inside an inline…
shai-almog Sep 2, 2026
db15d73
An invented middle level passed, and xref: was a third spelling nobod…
shai-almog Sep 2, 2026
c3ebc41
The publication-date fix was inert: the parser never stored the keys
shai-almog Sep 2, 2026
a1fa03d
Real nesting, real timestamps, and a comment stripper that ate braces
shai-almog Sep 2, 2026
a706801
The self-link rule existed on one branch and not the other
shai-almog Sep 2, 2026
3e7b4a5
An inline comment became part of the value, and href tolerated no spaces
shai-almog Sep 2, 2026
6655675
A nested annotation was rejected, and an attributed include was invis…
shai-almog Sep 2, 2026
ab628e1
Thirty-five Javadoc anchors used a form javadoc stopped emitting year…
shai-almog Sep 2, 2026
e7bb4ee
An unquoted href value is still an href
shai-almog Sep 2, 2026
d3ba948
The array brackets I added last round broke two links, and aliases ke…
shai-almog Sep 2, 2026
92a9a91
A macro target is not prose, and a chapter has one place in the book
shai-almog Sep 2, 2026
e54e3f2
class-use pages are not generated, so linking to one is not fine
shai-almog Sep 2, 2026
bd3fb14
A reused fragment renders twice, and the CLDC sources trigger nothing
shai-almog Sep 2, 2026
2982652
An anchor decorates the block below it; it is not the block
shai-almog Sep 2, 2026
7cbeee1
Two package pages javadoc never writes, and multiplicity that stopped…
shai-almog Sep 2, 2026
d45e1da
Two leniency hatches that accepted anything shaped vaguely right
shai-almog Sep 2, 2026
102bd45
A wildcard declares a slashed route just as a literal one does
shai-almog Sep 2, 2026
79cb5e9
Rejecting javadoc's asset directories rather than enumerating them
shai-almog Sep 2, 2026
abdb22b
A titled listing is a listing, and a figure was never the promised code
shai-almog Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 46 additions & 19 deletions .github/workflows/developer-guide-docs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,8 +50,13 @@ jobs:
docs:
- 'docs/developer-guide/**'
- 'docs/demos/common/src/main/snippets/**'
- 'scripts/developer-guide/migrate-inline-guide-snippets.py'
- 'scripts/developer-guide/validate-guide-snippets.py'
# The whole directory, not two named files. on.pull_request.paths
# already triggers on scripts/developer-guide/**, so a change to any
# other script here started the workflow while leaving this filter
# false -- which skipped the very steps that script governs. A
# change to compare-screenshots.py could have merged without the
# screenshot check ever running it.
- 'scripts/developer-guide/**'
- '.github/workflows/developer-guide-docs.yml'
# Triggering the workflow is not enough on its own: the HTML and PDF
# build and the steps beside it are gated on this filter, so the
Expand DownExpand Up@@ -122,6 +127,7 @@ jobs:
run: |
set -euo pipefail
GENERATED_DIR="$RUNNER_TEMP/pre-advanced-theming-screenshots"
echo "GUIDE_GENERATED_SCREENSHOTS=$GENERATED_DIR" >> "$GITHUB_ENV"
rm -rf "$GENERATED_DIR"
mkdir -p "$GENERATED_DIR"
xvfb-run -a mvn -B -ntp \
Expand All@@ -131,23 +137,25 @@ jobs:
-P guide-screenshot-generator \
-Dguide.screenshot.output="$GENERATED_DIR" \
verify
COUNT="$(find "$GENERATED_DIR" -maxdepth 1 -type f -name '*.png' | wc -l | tr -d ' ')"
if [ "$COUNT" != "24" ]; then
echo "::error::Expected 24 generated pre-Advanced Theming screenshots, found $COUNT"
exit 1
fi
for generated in "$GENERATED_DIR"/*.png; do
name="$(basename "$generated")"
committed="docs/developer-guide/img/$name"
if [ ! -f "$committed" ]; then
echo "::error::Generated screenshot has no committed counterpart: $name"
exit 1
fi
if ! cmp -s "$generated" "$committed"; then
echo "::error::Committed screenshot is stale: $name"
exit 1
fi
done
pip install --user --quiet Pillow
# Byte equality is still the rule; a figure only gets a bounded
# difference if it carries a .tolerance sidecar saying why.
python3 scripts/developer-guide/compare-screenshots.py \
--generated "$GENERATED_DIR" \
--committed docs/developer-guide/img \
--expected-count 24

# When the byte compare fails, the message names the file but not what it
# actually rendered, which leaves no way to tell a real regression from an
# environment difference. Publishing what this runner produced makes the
# two distinguishable without adding a debugging round trip to CI.
- name: Upload generated screenshots when they do not match
if: failure() && env.GUIDE_GENERATED_SCREENSHOTS != ''
uses: actions/upload-artifact@v7
with:
name: guide-generated-screenshots
path: ${{ env.GUIDE_GENERATED_SCREENSHOTS }}
if-no-files-found: warn

- name: Verify developer guide images are referenced
if: github.event_name != 'pull_request' || steps.changes.outputs.docs == 'true' || steps.changes.outputs.demos == 'true' || steps.changes.outputs.workflow == 'true'
Expand DownExpand Up@@ -271,6 +279,25 @@ jobs:
echo "Asciidoctor exited with status $STATUS — the final quality-gate step will fail the build." >&2
fi

# These four cover defects the gates above are structurally blind to: a
# chapter swallowed by the one before it, a cross-reference that resolves
# to nothing, prose that promises a code block which is not there, and a
# link the website does not serve. Asciidoctor reports none of them, and
# every one of them shipped. The structure and cross-reference checks
# render the book, so they must run after the Asciidoctor install.
- name: Check developer guide structure, cross-references, code blocks and links
run: |
set -euo pipefail
python3 scripts/developer-guide/check-guide-structure.py
python3 scripts/developer-guide/check-guide-xrefs.py
python3 scripts/developer-guide/check-missing-code-blocks.py
# Note this only catches a link the GUIDE breaks. When the website
# moves the route instead -- a page renamed, deleted or re-slugged --
# the same script runs from website-docs.yml, which triggers on
# docs/website/**. Putting those paths here would run this whole job
# (maven install, demo build, screenshots) for every blog post.
python3 scripts/developer-guide/check-guide-links.py

- name: Build Developer Guide HTML and PDF
if: github.event_name != 'pull_request' || steps.changes.outputs.docs == 'true' || steps.changes.outputs.demos == 'true' || steps.changes.outputs.workflow == 'true'
run: |
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/website-docs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,10 @@ on:
- 'vm/ByteCodeTranslator/**'
- 'vm/JavaAPI/**'
- 'CodenameOne/src/**'
# The other root build_javadocs.sh generates from, and the other one
# check-guide-links.py indexes. Without it, deleting a linked CLDC type
# merges unvalidated and the next scheduled build is where it surfaces.
- 'Ports/CLDC11/src/**'
# The developer guide this site renders includes a build hint table that is
# generated rather than committed, so a change to the catalog or to the
# renderer changes the published page without touching docs/. The
Expand All@@ -46,6 +50,10 @@ on:
- 'vm/ByteCodeTranslator/**'
- 'vm/JavaAPI/**'
- 'CodenameOne/src/**'
# The other root build_javadocs.sh generates from, and the other one
# check-guide-links.py indexes. Without it, deleting a linked CLDC type
# merges unvalidated and the next scheduled build is where it surfaces.
- 'Ports/CLDC11/src/**'
# As above: the build hint table is generated, not committed.
- 'maven/build-hint-catalog/**'
- 'maven/build-hint-tools/**'
Expand DownExpand Up@@ -277,6 +285,17 @@ jobs:
--root-dir public
public/**/*.html

# The developer guide links into this site, so a page renamed, deleted or
# re-slugged here breaks the guide. The check lives in THIS workflow rather
# than in developer-guide-docs.yml because it is the route data that
# changed: widening that workflow's paths to docs/website/** would run the
# full guide build -- maven install, demo build, screenshot generation --
# on every blog post the daily publisher pushes, to run one Python script.
# This job already triggers on docs/website/**, and the check needs nothing
# but a checkout.
- name: Check developer guide links against the routes this site serves
run: python3 scripts/developer-guide/check-guide-links.py
Comment thread
shai-almog marked this conversation as resolved.

- name: Reject absolute codenameone.com links
run: |
set -euo pipefail
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,41 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codenameone.developerguide.screenshots;

import com.codename1.components.SpanLabel;
import com.codenameone.developerguide.snippets.generated.BasicsJava034Snippet;
import com.codename1.ui.Button;
import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.CN;
import com.codename1.ui.FontImage;
import com.codename1.ui.Font;
import com.codename1.ui.Form;
import com.codename1.ui.Graphics;
import com.codename1.ui.Image;
import com.codename1.ui.Label;
import com.codename1.ui.TextArea;
import com.codename1.ui.TextField;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.BoxLayout;
Expand DownExpand Up@@ -40,10 +64,33 @@ public final class PreAdvancedThemingScreenshots {
private static final int BLUE = 0x0b57d0;
private static final int GREEN = 0x06a806;
private static final int WHITE = 0xffffff;
private static final Font TITLE_FONT = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_LARGE)
.derive(35, Font.STYLE_PLAIN);
private static final Font BLOCK_FONT = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_LARGE)
.derive(24, Font.STYLE_PLAIN);
private static final Font TITLE_FONT = screenshotFont(35);
private static final Font BLOCK_FONT = screenshotFont(24);
/// Sized to match the height the theme's own default font had (13px), so
/// pinning the face on components the block styling does not touch keeps
/// their layout exactly where it was.
private static final Font FIELD_FONT = screenshotFont(11);

/// Loads a figure font from the port's bundled Roboto rather than from the host.
///
/// `Font.createSystemFont` resolves through `JavaSEPort.fontFaceSystem`, which is
/// "Arial" on macOS and Linux alike. Arial exists on a developer's Mac and not on a
/// stock CI runner, so AWT silently substitutes a different face and every glyph in
/// every figure changes -- which is why none of these screenshots could be
/// regenerated outside CI and byte-compared against what was committed. The
/// `native:` scheme reads `/com/codename1/impl/javase/Roboto-*.ttf` off the
/// classpath, so the result does not depend on what the machine happens to have
/// installed. It is also what this project's font rule requires everywhere.
private static Font screenshotFont(int pixelSize) {
Font font = Font.createTrueTypeFont("native:MainRegular", "native:MainRegular");
if (font == null) {
// Falling back to a host font would quietly restore the very
// non-determinism this exists to remove, so refuse instead.
throw new IllegalStateException(
"the native font scheme is unavailable, so figures would render with a host font");
}
return font.derive(pixelSize, Font.STYLE_PLAIN);
}

private PreAdvancedThemingScreenshots() {
}
Expand All@@ -53,6 +100,32 @@ public interface ScreenshotSink {
}

public static void generate(ScreenshotSink sink) throws IOException {
// JavaSEPort.loadTrueTypeFont has an earlier branch for native: fonts:
// when isIOS is set -- which loadSkinFile does for any skin whose
// systemFontFamily contains "helvetica" -- it resolves to the first
// INSTALLED SF or Helvetica family and never reaches the bundled Roboto.
// This generator never loads a skin, so that branch is not taken, and the
// measurement agrees: figures rendered on a Mac match the Linux runner
// byte for byte, which could not happen if one side were using Helvetica
// Neue. Guard it anyway, because a future change that loads a skin here
// would put the host's fonts back into the output without any other
// symptom.
String platform = Display.getInstance().getPlatformName();
if ("ios".equals(platform)) {
throw new IllegalStateException(
"an iOS skin is active, so native: fonts would resolve to installed "
+ "system faces instead of the bundled ones and these figures would "
+ "stop being reproducible off this machine");
}

// MigLayout picks its default gaps from PlatformDefaults, which reads
// System.getProperty("os.name") and returns MAC_OSX, GNOME or WINDOWS_XP.
// The gaps differ per platform, so mig-layout.png came out with macOS
// spacing on a Mac and GNOME spacing on the Linux runner -- a 12.8% pixel
// difference that has nothing to do with fonts. Pin it so the figure shows
// the same thing wherever it is generated.
com.codename1.ui.layouts.mig.PlatformDefaults.setPlatform(
com.codename1.ui.layouts.mig.PlatformDefaults.GNOME);
write(sink, "flow-layout.png", flow("Flow Layout", Component.LEFT, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
write(sink, "flow-layout-center.png", flow("Flow Layout", Component.CENTER, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
write(sink, "flow-layout-right.png", flow("Flow Layout", Component.RIGHT, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
Expand DownExpand Up@@ -328,6 +401,22 @@ private static void applyScreenshotStyle(Form form) {
private static void applyBlockStyleToContent(Container container) {
for (int i = 0; i < container.getComponentCount(); i++) {
Component component = container.getComponentAt(i);
// Pin the font on EVERY component rather than on the types that also
// get block colours. Anything left on the theme's default font
// resolves through the host, and that is what made
// guibuilder-2-insets-3.png -- the only figure containing a
// TextField -- differ between a Mac and CI while the other 23
// matched byte for byte. Enumerating the types that carry text
// would leave the next one to be added broken in the same way.
component.getAllStyles().setFont(FIELD_FONT);
if (component instanceof TextArea) {
// The hint is painted by a Label that is not in the component
// tree, so the walk above never reaches it.
Label hint = ((TextArea) component).getHintLabel();
if (hint != null) {
hint.getAllStyles().setFont(FIELD_FONT);
}
}
if (component instanceof Label || component instanceof Button) {
styleBlock(component);
}
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
// Generated from docs/developer-guide source blocks. Edit the guide snippets here, not inline.

// tag::working-with-windows-bash-001[]
mvn -pl common package -Dcodename1.platform=windows -Dcodename1.buildTarget=windows-device
mvn package -Dcodename1.platform=win -Dcodename1.buildTarget=windows-device
// end::working-with-windows-bash-001[]
2 changes: 1 addition & 1 deletion docs/developer-guide/Advanced-Theming.asciidoc
Original file line numberDiff line numberDiff line change
Expand Up@@ -728,7 +728,7 @@ Will set the foreground color of the https://www.codenameone.com/javadoc/com/cod

When a Codename One https://www.codenameone.com/javadoc/com/codename1/ui/Component.html[Component] is instantiated it requests a https://www.codenameone.com/javadoc/com/codename1/ui/plaf/Style.html[Style] object from the https://www.codenameone.com/javadoc/com/codename1/ui/plaf/UIManager.html[UIManager] class. The `Style` object is based on the settings within the theme and can be modified through code or by using the theme.

You can replace the theme dynamically in runtime and refresh the styles assigned to the various components using the https://www.codenameone.com/javadoc/com/codename1/ui/Component.html#refreshTheme--[refreshTheme()] method.
You can replace the theme dynamically in runtime and refresh the styles assigned to the various components using the https://www.codenameone.com/javadoc/com/codename1/ui/Component.html#refreshTheme()[refreshTheme()] method.

NOTE: It's a common mistake to invoke `refreshTheme()` without actually changing the theme. You see developers doing it when all they need is a `repaint()` or `revalidate()`. Since `refreshTheme()` is **** expensive recommend that you don't use it unless you need to...

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
684849a
Gates for the guide defects Asciidoctor cannot see
shai-almog Sep 2, 2026
eebe28f
Bank the four holes the constraints chapter no longer has
shai-almog Sep 2, 2026
2555762
An unrelated subsection must not stand in for a swallowed chapter
shai-almog Sep 2, 2026
877a371
The ifeval branch of the conditional test could never match
shai-almog Sep 2, 2026
2a08ef7
Record why commented-out URLs are checked like any other
shai-almog Sep 2, 2026
f69f47c
Four links left the book to re-enter it, and nothing checked the anchor
shai-almog Sep 2, 2026
82ed7bf
Two silent blind spots in the guards become loud refusals
shai-almog Sep 2, 2026
520ced0
Check what swallows a heading, not whether its title turns up somewhere
shai-almog Sep 2, 2026
9ffb68c
The slash rule rejected routes the site declares, and Hugo could move…
shai-almog Sep 2, 2026
4f6f2a5
The PDF branch was never rendered, and an admonition counted as a lis…
shai-almog Sep 2, 2026
bbc2ee5
Three links from the old wiki went nowhere, and a directive is not a …
shai-almog Sep 2, 2026
9a508aa
A Cloudflare function serves /files and /demos, and the PDF surrogate…
shai-almog Sep 2, 2026
9d351ff
A five-dash block is still a literal block, and both scanners read in…
shai-almog Sep 2, 2026
425e749
Guide figures render with a bundled font instead of the host's (#5668)
shai-almog Sep 2, 2026
94a3b45
A comment block is not content, and img/ really does ship beside the …
shai-almog Sep 2, 2026
773297b
The packaging exemption trusted the repository, not the published output
shai-almog Sep 2, 2026
93b0518
The Introduction taught a dead IDE menu, an iTunes walkthrough and 20…
shai-almog Sep 2, 2026
b1f80df
Copyright headers for the two Windows wrappers, and duplicate include…
shai-almog Sep 2, 2026
cce4454
Refuse a conditional include at every depth, not only in the manifest
shai-almog Sep 2, 2026
aa6af63
A typo in the authority hid behind a correct hostname
shai-almog Sep 2, 2026
41c3133
A listing hides content from the reader, not directives from the prep…
shai-almog Sep 2, 2026
5e79881
The DNS root dot walked straight past the same-site checks
shai-almog Sep 2, 2026
b50b795
Read the fallback routes from the Function, and close the authority f…
shai-almog Sep 2, 2026
2a25ea8
An inline directive is content, and an attribute-built URL is unreadable
shai-almog Sep 2, 2026
2cc2941
Three Javadoc links pointed at a package that is spelt lowercase
shai-almog Sep 2, 2026
50cf047
The Javadoc index documented a package the build deliberately removes
shai-almog Sep 2, 2026
21e5d5c
Javadoc documents public types, and the index was recording every sou…
shai-almog Sep 2, 2026
4c34787
An uppercase scheme was invisible, and the two slash rules had drifte…
shai-almog Sep 2, 2026
6c1c945
A nested type was taken on trust, and an include hid inside an inline…
shai-almog Sep 2, 2026
db15d73
An invented middle level passed, and xref: was a third spelling nobod…
shai-almog Sep 2, 2026
c3ebc41
The publication-date fix was inert: the parser never stored the keys
shai-almog Sep 2, 2026
a1fa03d
Real nesting, real timestamps, and a comment stripper that ate braces
shai-almog Sep 2, 2026
a706801
The self-link rule existed on one branch and not the other
shai-almog Sep 2, 2026
3e7b4a5
An inline comment became part of the value, and href tolerated no spaces
shai-almog Sep 2, 2026
6655675
A nested annotation was rejected, and an attributed include was invis…
shai-almog Sep 2, 2026
ab628e1
Thirty-five Javadoc anchors used a form javadoc stopped emitting year…
shai-almog Sep 2, 2026
e7bb4ee
An unquoted href value is still an href
shai-almog Sep 2, 2026
d3ba948
The array brackets I added last round broke two links, and aliases ke…
shai-almog Sep 2, 2026
92a9a91
A macro target is not prose, and a chapter has one place in the book
shai-almog Sep 2, 2026
e54e3f2
class-use pages are not generated, so linking to one is not fine
shai-almog Sep 2, 2026
bd3fb14
A reused fragment renders twice, and the CLDC sources trigger nothing
shai-almog Sep 2, 2026
2982652
An anchor decorates the block below it; it is not the block
shai-almog Sep 2, 2026
7cbeee1
Two package pages javadoc never writes, and multiplicity that stopped…
shai-almog Sep 2, 2026
d45e1da
Two leniency hatches that accepted anything shaped vaguely right
shai-almog Sep 2, 2026
102bd45
A wildcard declares a slashed route just as a literal one does
shai-almog Sep 2, 2026
79cb5e9
Rejecting javadoc's asset directories rather than enumerating them
shai-almog Sep 2, 2026
abdb22b
A titled listing is a listing, and a figure was never the promised code
shai-almog Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 46 additions & 19 deletions .github/workflows/developer-guide-docs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,8 +50,13 @@ jobs:
docs:
- 'docs/developer-guide/**'
- 'docs/demos/common/src/main/snippets/**'
- 'scripts/developer-guide/migrate-inline-guide-snippets.py'
- 'scripts/developer-guide/validate-guide-snippets.py'
# The whole directory, not two named files. on.pull_request.paths
# already triggers on scripts/developer-guide/**, so a change to any
# other script here started the workflow while leaving this filter
# false -- which skipped the very steps that script governs. A
# change to compare-screenshots.py could have merged without the
# screenshot check ever running it.
- 'scripts/developer-guide/**'
- '.github/workflows/developer-guide-docs.yml'
# Triggering the workflow is not enough on its own: the HTML and PDF
# build and the steps beside it are gated on this filter, so the
Expand DownExpand Up@@ -122,6 +127,7 @@ jobs:
run: |
set -euo pipefail
GENERATED_DIR="$RUNNER_TEMP/pre-advanced-theming-screenshots"
echo "GUIDE_GENERATED_SCREENSHOTS=$GENERATED_DIR" >> "$GITHUB_ENV"
rm -rf "$GENERATED_DIR"
mkdir -p "$GENERATED_DIR"
xvfb-run -a mvn -B -ntp \
Expand All@@ -131,23 +137,25 @@ jobs:
-P guide-screenshot-generator \
-Dguide.screenshot.output="$GENERATED_DIR" \
verify
COUNT="$(find "$GENERATED_DIR" -maxdepth 1 -type f -name '*.png' | wc -l | tr -d ' ')"
if [ "$COUNT" != "24" ]; then
echo "::error::Expected 24 generated pre-Advanced Theming screenshots, found $COUNT"
exit 1
fi
for generated in "$GENERATED_DIR"/*.png; do
name="$(basename "$generated")"
committed="docs/developer-guide/img/$name"
if [ ! -f "$committed" ]; then
echo "::error::Generated screenshot has no committed counterpart: $name"
exit 1
fi
if ! cmp -s "$generated" "$committed"; then
echo "::error::Committed screenshot is stale: $name"
exit 1
fi
done
pip install --user --quiet Pillow
# Byte equality is still the rule; a figure only gets a bounded
# difference if it carries a .tolerance sidecar saying why.
python3 scripts/developer-guide/compare-screenshots.py \
--generated "$GENERATED_DIR" \
--committed docs/developer-guide/img \
--expected-count 24

# When the byte compare fails, the message names the file but not what it
# actually rendered, which leaves no way to tell a real regression from an
# environment difference. Publishing what this runner produced makes the
# two distinguishable without adding a debugging round trip to CI.
- name: Upload generated screenshots when they do not match
if: failure() && env.GUIDE_GENERATED_SCREENSHOTS != ''
uses: actions/upload-artifact@v7
with:
name: guide-generated-screenshots
path: ${{ env.GUIDE_GENERATED_SCREENSHOTS }}
if-no-files-found: warn

- name: Verify developer guide images are referenced
if: github.event_name != 'pull_request' || steps.changes.outputs.docs == 'true' || steps.changes.outputs.demos == 'true' || steps.changes.outputs.workflow == 'true'
Expand DownExpand Up@@ -271,6 +279,25 @@ jobs:
echo "Asciidoctor exited with status $STATUS — the final quality-gate step will fail the build." >&2
fi

# These four cover defects the gates above are structurally blind to: a
# chapter swallowed by the one before it, a cross-reference that resolves
# to nothing, prose that promises a code block which is not there, and a
# link the website does not serve. Asciidoctor reports none of them, and
# every one of them shipped. The structure and cross-reference checks
# render the book, so they must run after the Asciidoctor install.
- name: Check developer guide structure, cross-references, code blocks and links
run: |
set -euo pipefail
python3 scripts/developer-guide/check-guide-structure.py
python3 scripts/developer-guide/check-guide-xrefs.py
python3 scripts/developer-guide/check-missing-code-blocks.py
# Note this only catches a link the GUIDE breaks. When the website
# moves the route instead -- a page renamed, deleted or re-slugged --
# the same script runs from website-docs.yml, which triggers on
# docs/website/**. Putting those paths here would run this whole job
# (maven install, demo build, screenshots) for every blog post.
python3 scripts/developer-guide/check-guide-links.py

- name: Build Developer Guide HTML and PDF
if: github.event_name != 'pull_request' || steps.changes.outputs.docs == 'true' || steps.changes.outputs.demos == 'true' || steps.changes.outputs.workflow == 'true'
run: |
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/website-docs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,10 @@ on:
- 'vm/ByteCodeTranslator/**'
- 'vm/JavaAPI/**'
- 'CodenameOne/src/**'
# The other root build_javadocs.sh generates from, and the other one
# check-guide-links.py indexes. Without it, deleting a linked CLDC type
# merges unvalidated and the next scheduled build is where it surfaces.
- 'Ports/CLDC11/src/**'
# The developer guide this site renders includes a build hint table that is
# generated rather than committed, so a change to the catalog or to the
# renderer changes the published page without touching docs/. The
Expand All@@ -46,6 +50,10 @@ on:
- 'vm/ByteCodeTranslator/**'
- 'vm/JavaAPI/**'
- 'CodenameOne/src/**'
# The other root build_javadocs.sh generates from, and the other one
# check-guide-links.py indexes. Without it, deleting a linked CLDC type
# merges unvalidated and the next scheduled build is where it surfaces.
- 'Ports/CLDC11/src/**'
# As above: the build hint table is generated, not committed.
- 'maven/build-hint-catalog/**'
- 'maven/build-hint-tools/**'
Expand DownExpand Up@@ -277,6 +285,17 @@ jobs:
--root-dir public
public/**/*.html

# The developer guide links into this site, so a page renamed, deleted or
# re-slugged here breaks the guide. The check lives in THIS workflow rather
# than in developer-guide-docs.yml because it is the route data that
# changed: widening that workflow's paths to docs/website/** would run the
# full guide build -- maven install, demo build, screenshot generation --
# on every blog post the daily publisher pushes, to run one Python script.
# This job already triggers on docs/website/**, and the check needs nothing
# but a checkout.
- name: Check developer guide links against the routes this site serves
run: python3 scripts/developer-guide/check-guide-links.py
Comment thread
shai-almog marked this conversation as resolved.

- name: Reject absolute codenameone.com links
run: |
set -euo pipefail
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,41 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codenameone.developerguide.screenshots;

import com.codename1.components.SpanLabel;
import com.codenameone.developerguide.snippets.generated.BasicsJava034Snippet;
import com.codename1.ui.Button;
import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.CN;
import com.codename1.ui.FontImage;
import com.codename1.ui.Font;
import com.codename1.ui.Form;
import com.codename1.ui.Graphics;
import com.codename1.ui.Image;
import com.codename1.ui.Label;
import com.codename1.ui.TextArea;
import com.codename1.ui.TextField;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.BoxLayout;
Expand DownExpand Up@@ -40,10 +64,33 @@ public final class PreAdvancedThemingScreenshots {
private static final int BLUE = 0x0b57d0;
private static final int GREEN = 0x06a806;
private static final int WHITE = 0xffffff;
private static final Font TITLE_FONT = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_LARGE)
.derive(35, Font.STYLE_PLAIN);
private static final Font BLOCK_FONT = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_LARGE)
.derive(24, Font.STYLE_PLAIN);
private static final Font TITLE_FONT = screenshotFont(35);
private static final Font BLOCK_FONT = screenshotFont(24);
/// Sized to match the height the theme's own default font had (13px), so
/// pinning the face on components the block styling does not touch keeps
/// their layout exactly where it was.
private static final Font FIELD_FONT = screenshotFont(11);

/// Loads a figure font from the port's bundled Roboto rather than from the host.
///
/// `Font.createSystemFont` resolves through `JavaSEPort.fontFaceSystem`, which is
/// "Arial" on macOS and Linux alike. Arial exists on a developer's Mac and not on a
/// stock CI runner, so AWT silently substitutes a different face and every glyph in
/// every figure changes -- which is why none of these screenshots could be
/// regenerated outside CI and byte-compared against what was committed. The
/// `native:` scheme reads `/com/codename1/impl/javase/Roboto-*.ttf` off the
/// classpath, so the result does not depend on what the machine happens to have
/// installed. It is also what this project's font rule requires everywhere.
private static Font screenshotFont(int pixelSize) {
Font font = Font.createTrueTypeFont("native:MainRegular", "native:MainRegular");
if (font == null) {
// Falling back to a host font would quietly restore the very
// non-determinism this exists to remove, so refuse instead.
throw new IllegalStateException(
"the native font scheme is unavailable, so figures would render with a host font");
}
return font.derive(pixelSize, Font.STYLE_PLAIN);
}

private PreAdvancedThemingScreenshots() {
}
Expand All@@ -53,6 +100,32 @@ public interface ScreenshotSink {
}

public static void generate(ScreenshotSink sink) throws IOException {
// JavaSEPort.loadTrueTypeFont has an earlier branch for native: fonts:
// when isIOS is set -- which loadSkinFile does for any skin whose
// systemFontFamily contains "helvetica" -- it resolves to the first
// INSTALLED SF or Helvetica family and never reaches the bundled Roboto.
// This generator never loads a skin, so that branch is not taken, and the
// measurement agrees: figures rendered on a Mac match the Linux runner
// byte for byte, which could not happen if one side were using Helvetica
// Neue. Guard it anyway, because a future change that loads a skin here
// would put the host's fonts back into the output without any other
// symptom.
String platform = Display.getInstance().getPlatformName();
if ("ios".equals(platform)) {
throw new IllegalStateException(
"an iOS skin is active, so native: fonts would resolve to installed "
+ "system faces instead of the bundled ones and these figures would "
+ "stop being reproducible off this machine");
}

// MigLayout picks its default gaps from PlatformDefaults, which reads
// System.getProperty("os.name") and returns MAC_OSX, GNOME or WINDOWS_XP.
// The gaps differ per platform, so mig-layout.png came out with macOS
// spacing on a Mac and GNOME spacing on the Linux runner -- a 12.8% pixel
// difference that has nothing to do with fonts. Pin it so the figure shows
// the same thing wherever it is generated.
com.codename1.ui.layouts.mig.PlatformDefaults.setPlatform(
com.codename1.ui.layouts.mig.PlatformDefaults.GNOME);
write(sink, "flow-layout.png", flow("Flow Layout", Component.LEFT, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
write(sink, "flow-layout-center.png", flow("Flow Layout", Component.CENTER, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
write(sink, "flow-layout-right.png", flow("Flow Layout", Component.RIGHT, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
Expand DownExpand Up@@ -328,6 +401,22 @@ private static void applyScreenshotStyle(Form form) {
private static void applyBlockStyleToContent(Container container) {
for (int i = 0; i < container.getComponentCount(); i++) {
Component component = container.getComponentAt(i);
// Pin the font on EVERY component rather than on the types that also
// get block colours. Anything left on the theme's default font
// resolves through the host, and that is what made
// guibuilder-2-insets-3.png -- the only figure containing a
// TextField -- differ between a Mac and CI while the other 23
// matched byte for byte. Enumerating the types that carry text
// would leave the next one to be added broken in the same way.
component.getAllStyles().setFont(FIELD_FONT);
if (component instanceof TextArea) {
// The hint is painted by a Label that is not in the component
// tree, so the walk above never reaches it.
Label hint = ((TextArea) component).getHintLabel();
if (hint != null) {
hint.getAllStyles().setFont(FIELD_FONT);
}
}
if (component instanceof Label || component instanceof Button) {
styleBlock(component);
}
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
// Generated from docs/developer-guide source blocks. Edit the guide snippets here, not inline.

// tag::working-with-windows-bash-001[]
mvn -pl common package -Dcodename1.platform=windows -Dcodename1.buildTarget=windows-device
mvn package -Dcodename1.platform=win -Dcodename1.buildTarget=windows-device
// end::working-with-windows-bash-001[]
2 changes: 1 addition & 1 deletion docs/developer-guide/Advanced-Theming.asciidoc
Original file line numberDiff line numberDiff line change
Expand Up@@ -728,7 +728,7 @@ Will set the foreground color of the https://www.codenameone.com/javadoc/com/cod

When a Codename One https://www.codenameone.com/javadoc/com/codename1/ui/Component.html[Component] is instantiated it requests a https://www.codenameone.com/javadoc/com/codename1/ui/plaf/Style.html[Style] object from the https://www.codenameone.com/javadoc/com/codename1/ui/plaf/UIManager.html[UIManager] class. The `Style` object is based on the settings within the theme and can be modified through code or by using the theme.

You can replace the theme dynamically in runtime and refresh the styles assigned to the various components using the https://www.codenameone.com/javadoc/com/codename1/ui/Component.html#refreshTheme--[refreshTheme()] method.
You can replace the theme dynamically in runtime and refresh the styles assigned to the various components using the https://www.codenameone.com/javadoc/com/codename1/ui/Component.html#refreshTheme()[refreshTheme()] method.

NOTE: It's a common mistake to invoke `refreshTheme()` without actually changing the theme. You see developers doing it when all they need is a `repaint()` or `revalidate()`. Since `refreshTheme()` is **** expensive recommend that you don't use it unless you need to...

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
684849a
Gates for the guide defects Asciidoctor cannot see
shai-almog Sep 2, 2026
eebe28f
Bank the four holes the constraints chapter no longer has
shai-almog Sep 2, 2026
2555762
An unrelated subsection must not stand in for a swallowed chapter
shai-almog Sep 2, 2026
877a371
The ifeval branch of the conditional test could never match
shai-almog Sep 2, 2026
2a08ef7
Record why commented-out URLs are checked like any other
shai-almog Sep 2, 2026
f69f47c
Four links left the book to re-enter it, and nothing checked the anchor
shai-almog Sep 2, 2026
82ed7bf
Two silent blind spots in the guards become loud refusals
shai-almog Sep 2, 2026
520ced0
Check what swallows a heading, not whether its title turns up somewhere
shai-almog Sep 2, 2026
9ffb68c
The slash rule rejected routes the site declares, and Hugo could move…
shai-almog Sep 2, 2026
4f6f2a5
The PDF branch was never rendered, and an admonition counted as a lis…
shai-almog Sep 2, 2026
bbc2ee5
Three links from the old wiki went nowhere, and a directive is not a …
shai-almog Sep 2, 2026
9a508aa
A Cloudflare function serves /files and /demos, and the PDF surrogate…
shai-almog Sep 2, 2026
9d351ff
A five-dash block is still a literal block, and both scanners read in…
shai-almog Sep 2, 2026
425e749
Guide figures render with a bundled font instead of the host's (#5668)
shai-almog Sep 2, 2026
94a3b45
A comment block is not content, and img/ really does ship beside the …
shai-almog Sep 2, 2026
773297b
The packaging exemption trusted the repository, not the published output
shai-almog Sep 2, 2026
93b0518
The Introduction taught a dead IDE menu, an iTunes walkthrough and 20…
shai-almog Sep 2, 2026
b1f80df
Copyright headers for the two Windows wrappers, and duplicate include…
shai-almog Sep 2, 2026
cce4454
Refuse a conditional include at every depth, not only in the manifest
shai-almog Sep 2, 2026
aa6af63
A typo in the authority hid behind a correct hostname
shai-almog Sep 2, 2026
41c3133
A listing hides content from the reader, not directives from the prep…
shai-almog Sep 2, 2026
5e79881
The DNS root dot walked straight past the same-site checks
shai-almog Sep 2, 2026
b50b795
Read the fallback routes from the Function, and close the authority f…
shai-almog Sep 2, 2026
2a25ea8
An inline directive is content, and an attribute-built URL is unreadable
shai-almog Sep 2, 2026
2cc2941
Three Javadoc links pointed at a package that is spelt lowercase
shai-almog Sep 2, 2026
50cf047
The Javadoc index documented a package the build deliberately removes
shai-almog Sep 2, 2026
21e5d5c
Javadoc documents public types, and the index was recording every sou…
shai-almog Sep 2, 2026
4c34787
An uppercase scheme was invisible, and the two slash rules had drifte…
shai-almog Sep 2, 2026
6c1c945
A nested type was taken on trust, and an include hid inside an inline…
shai-almog Sep 2, 2026
db15d73
An invented middle level passed, and xref: was a third spelling nobod…
shai-almog Sep 2, 2026
c3ebc41
The publication-date fix was inert: the parser never stored the keys
shai-almog Sep 2, 2026
a1fa03d
Real nesting, real timestamps, and a comment stripper that ate braces
shai-almog Sep 2, 2026
a706801
The self-link rule existed on one branch and not the other
shai-almog Sep 2, 2026
3e7b4a5
An inline comment became part of the value, and href tolerated no spaces
shai-almog Sep 2, 2026
6655675
A nested annotation was rejected, and an attributed include was invis…
shai-almog Sep 2, 2026
ab628e1
Thirty-five Javadoc anchors used a form javadoc stopped emitting year…
shai-almog Sep 2, 2026
e7bb4ee
An unquoted href value is still an href
shai-almog Sep 2, 2026
d3ba948
The array brackets I added last round broke two links, and aliases ke…
shai-almog Sep 2, 2026
92a9a91
A macro target is not prose, and a chapter has one place in the book
shai-almog Sep 2, 2026
e54e3f2
class-use pages are not generated, so linking to one is not fine
shai-almog Sep 2, 2026
bd3fb14
A reused fragment renders twice, and the CLDC sources trigger nothing
shai-almog Sep 2, 2026
2982652
An anchor decorates the block below it; it is not the block
shai-almog Sep 2, 2026
7cbeee1
Two package pages javadoc never writes, and multiplicity that stopped…
shai-almog Sep 2, 2026
d45e1da
Two leniency hatches that accepted anything shaped vaguely right
shai-almog Sep 2, 2026
102bd45
A wildcard declares a slashed route just as a literal one does
shai-almog Sep 2, 2026
79cb5e9
Rejecting javadoc's asset directories rather than enumerating them
shai-almog Sep 2, 2026
abdb22b
A titled listing is a listing, and a figure was never the promised code
shai-almog Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 46 additions & 19 deletions .github/workflows/developer-guide-docs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,8 +50,13 @@ jobs:
docs:
- 'docs/developer-guide/**'
- 'docs/demos/common/src/main/snippets/**'
- 'scripts/developer-guide/migrate-inline-guide-snippets.py'
- 'scripts/developer-guide/validate-guide-snippets.py'
# The whole directory, not two named files. on.pull_request.paths
# already triggers on scripts/developer-guide/**, so a change to any
# other script here started the workflow while leaving this filter
# false -- which skipped the very steps that script governs. A
# change to compare-screenshots.py could have merged without the
# screenshot check ever running it.
- 'scripts/developer-guide/**'
- '.github/workflows/developer-guide-docs.yml'
# Triggering the workflow is not enough on its own: the HTML and PDF
# build and the steps beside it are gated on this filter, so the
Expand DownExpand Up@@ -122,6 +127,7 @@ jobs:
run: |
set -euo pipefail
GENERATED_DIR="$RUNNER_TEMP/pre-advanced-theming-screenshots"
echo "GUIDE_GENERATED_SCREENSHOTS=$GENERATED_DIR" >> "$GITHUB_ENV"
rm -rf "$GENERATED_DIR"
mkdir -p "$GENERATED_DIR"
xvfb-run -a mvn -B -ntp \
Expand All@@ -131,23 +137,25 @@ jobs:
-P guide-screenshot-generator \
-Dguide.screenshot.output="$GENERATED_DIR" \
verify
COUNT="$(find "$GENERATED_DIR" -maxdepth 1 -type f -name '*.png' | wc -l | tr -d ' ')"
if [ "$COUNT" != "24" ]; then
echo "::error::Expected 24 generated pre-Advanced Theming screenshots, found $COUNT"
exit 1
fi
for generated in "$GENERATED_DIR"/*.png; do
name="$(basename "$generated")"
committed="docs/developer-guide/img/$name"
if [ ! -f "$committed" ]; then
echo "::error::Generated screenshot has no committed counterpart: $name"
exit 1
fi
if ! cmp -s "$generated" "$committed"; then
echo "::error::Committed screenshot is stale: $name"
exit 1
fi
done
pip install --user --quiet Pillow
# Byte equality is still the rule; a figure only gets a bounded
# difference if it carries a .tolerance sidecar saying why.
python3 scripts/developer-guide/compare-screenshots.py \
--generated "$GENERATED_DIR" \
--committed docs/developer-guide/img \
--expected-count 24

# When the byte compare fails, the message names the file but not what it
# actually rendered, which leaves no way to tell a real regression from an
# environment difference. Publishing what this runner produced makes the
# two distinguishable without adding a debugging round trip to CI.
- name: Upload generated screenshots when they do not match
if: failure() && env.GUIDE_GENERATED_SCREENSHOTS != ''
uses: actions/upload-artifact@v7
with:
name: guide-generated-screenshots
path: ${{ env.GUIDE_GENERATED_SCREENSHOTS }}
if-no-files-found: warn

- name: Verify developer guide images are referenced
if: github.event_name != 'pull_request' || steps.changes.outputs.docs == 'true' || steps.changes.outputs.demos == 'true' || steps.changes.outputs.workflow == 'true'
Expand DownExpand Up@@ -271,6 +279,25 @@ jobs:
echo "Asciidoctor exited with status $STATUS — the final quality-gate step will fail the build." >&2
fi

# These four cover defects the gates above are structurally blind to: a
# chapter swallowed by the one before it, a cross-reference that resolves
# to nothing, prose that promises a code block which is not there, and a
# link the website does not serve. Asciidoctor reports none of them, and
# every one of them shipped. The structure and cross-reference checks
# render the book, so they must run after the Asciidoctor install.
- name: Check developer guide structure, cross-references, code blocks and links
run: |
set -euo pipefail
python3 scripts/developer-guide/check-guide-structure.py
python3 scripts/developer-guide/check-guide-xrefs.py
python3 scripts/developer-guide/check-missing-code-blocks.py
# Note this only catches a link the GUIDE breaks. When the website
# moves the route instead -- a page renamed, deleted or re-slugged --
# the same script runs from website-docs.yml, which triggers on
# docs/website/**. Putting those paths here would run this whole job
# (maven install, demo build, screenshots) for every blog post.
python3 scripts/developer-guide/check-guide-links.py

- name: Build Developer Guide HTML and PDF
if: github.event_name != 'pull_request' || steps.changes.outputs.docs == 'true' || steps.changes.outputs.demos == 'true' || steps.changes.outputs.workflow == 'true'
run: |
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/website-docs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,10 @@ on:
- 'vm/ByteCodeTranslator/**'
- 'vm/JavaAPI/**'
- 'CodenameOne/src/**'
# The other root build_javadocs.sh generates from, and the other one
# check-guide-links.py indexes. Without it, deleting a linked CLDC type
# merges unvalidated and the next scheduled build is where it surfaces.
- 'Ports/CLDC11/src/**'
# The developer guide this site renders includes a build hint table that is
# generated rather than committed, so a change to the catalog or to the
# renderer changes the published page without touching docs/. The
Expand All@@ -46,6 +50,10 @@ on:
- 'vm/ByteCodeTranslator/**'
- 'vm/JavaAPI/**'
- 'CodenameOne/src/**'
# The other root build_javadocs.sh generates from, and the other one
# check-guide-links.py indexes. Without it, deleting a linked CLDC type
# merges unvalidated and the next scheduled build is where it surfaces.
- 'Ports/CLDC11/src/**'
# As above: the build hint table is generated, not committed.
- 'maven/build-hint-catalog/**'
- 'maven/build-hint-tools/**'
Expand DownExpand Up@@ -277,6 +285,17 @@ jobs:
--root-dir public
public/**/*.html

# The developer guide links into this site, so a page renamed, deleted or
# re-slugged here breaks the guide. The check lives in THIS workflow rather
# than in developer-guide-docs.yml because it is the route data that
# changed: widening that workflow's paths to docs/website/** would run the
# full guide build -- maven install, demo build, screenshot generation --
# on every blog post the daily publisher pushes, to run one Python script.
# This job already triggers on docs/website/**, and the check needs nothing
# but a checkout.
- name: Check developer guide links against the routes this site serves
run: python3 scripts/developer-guide/check-guide-links.py
Comment thread
shai-almog marked this conversation as resolved.

- name: Reject absolute codenameone.com links
run: |
set -euo pipefail
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,41 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codenameone.developerguide.screenshots;

import com.codename1.components.SpanLabel;
import com.codenameone.developerguide.snippets.generated.BasicsJava034Snippet;
import com.codename1.ui.Button;
import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.CN;
import com.codename1.ui.FontImage;
import com.codename1.ui.Font;
import com.codename1.ui.Form;
import com.codename1.ui.Graphics;
import com.codename1.ui.Image;
import com.codename1.ui.Label;
import com.codename1.ui.TextArea;
import com.codename1.ui.TextField;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.BoxLayout;
Expand DownExpand Up@@ -40,10 +64,33 @@ public final class PreAdvancedThemingScreenshots {
private static final int BLUE = 0x0b57d0;
private static final int GREEN = 0x06a806;
private static final int WHITE = 0xffffff;
private static final Font TITLE_FONT = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_LARGE)
.derive(35, Font.STYLE_PLAIN);
private static final Font BLOCK_FONT = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_LARGE)
.derive(24, Font.STYLE_PLAIN);
private static final Font TITLE_FONT = screenshotFont(35);
private static final Font BLOCK_FONT = screenshotFont(24);
/// Sized to match the height the theme's own default font had (13px), so
/// pinning the face on components the block styling does not touch keeps
/// their layout exactly where it was.
private static final Font FIELD_FONT = screenshotFont(11);

/// Loads a figure font from the port's bundled Roboto rather than from the host.
///
/// `Font.createSystemFont` resolves through `JavaSEPort.fontFaceSystem`, which is
/// "Arial" on macOS and Linux alike. Arial exists on a developer's Mac and not on a
/// stock CI runner, so AWT silently substitutes a different face and every glyph in
/// every figure changes -- which is why none of these screenshots could be
/// regenerated outside CI and byte-compared against what was committed. The
/// `native:` scheme reads `/com/codename1/impl/javase/Roboto-*.ttf` off the
/// classpath, so the result does not depend on what the machine happens to have
/// installed. It is also what this project's font rule requires everywhere.
private static Font screenshotFont(int pixelSize) {
Font font = Font.createTrueTypeFont("native:MainRegular", "native:MainRegular");
if (font == null) {
// Falling back to a host font would quietly restore the very
// non-determinism this exists to remove, so refuse instead.
throw new IllegalStateException(
"the native font scheme is unavailable, so figures would render with a host font");
}
return font.derive(pixelSize, Font.STYLE_PLAIN);
}

private PreAdvancedThemingScreenshots() {
}
Expand All@@ -53,6 +100,32 @@ public interface ScreenshotSink {
}

public static void generate(ScreenshotSink sink) throws IOException {
// JavaSEPort.loadTrueTypeFont has an earlier branch for native: fonts:
// when isIOS is set -- which loadSkinFile does for any skin whose
// systemFontFamily contains "helvetica" -- it resolves to the first
// INSTALLED SF or Helvetica family and never reaches the bundled Roboto.
// This generator never loads a skin, so that branch is not taken, and the
// measurement agrees: figures rendered on a Mac match the Linux runner
// byte for byte, which could not happen if one side were using Helvetica
// Neue. Guard it anyway, because a future change that loads a skin here
// would put the host's fonts back into the output without any other
// symptom.
String platform = Display.getInstance().getPlatformName();
if ("ios".equals(platform)) {
throw new IllegalStateException(
"an iOS skin is active, so native: fonts would resolve to installed "
+ "system faces instead of the bundled ones and these figures would "
+ "stop being reproducible off this machine");
}

// MigLayout picks its default gaps from PlatformDefaults, which reads
// System.getProperty("os.name") and returns MAC_OSX, GNOME or WINDOWS_XP.
// The gaps differ per platform, so mig-layout.png came out with macOS
// spacing on a Mac and GNOME spacing on the Linux runner -- a 12.8% pixel
// difference that has nothing to do with fonts. Pin it so the figure shows
// the same thing wherever it is generated.
com.codename1.ui.layouts.mig.PlatformDefaults.setPlatform(
com.codename1.ui.layouts.mig.PlatformDefaults.GNOME);
write(sink, "flow-layout.png", flow("Flow Layout", Component.LEFT, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
write(sink, "flow-layout-center.png", flow("Flow Layout", Component.CENTER, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
write(sink, "flow-layout-right.png", flow("Flow Layout", Component.RIGHT, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
Expand DownExpand Up@@ -328,6 +401,22 @@ private static void applyScreenshotStyle(Form form) {
private static void applyBlockStyleToContent(Container container) {
for (int i = 0; i < container.getComponentCount(); i++) {
Component component = container.getComponentAt(i);
// Pin the font on EVERY component rather than on the types that also
// get block colours. Anything left on the theme's default font
// resolves through the host, and that is what made
// guibuilder-2-insets-3.png -- the only figure containing a
// TextField -- differ between a Mac and CI while the other 23
// matched byte for byte. Enumerating the types that carry text
// would leave the next one to be added broken in the same way.
component.getAllStyles().setFont(FIELD_FONT);
if (component instanceof TextArea) {
// The hint is painted by a Label that is not in the component
// tree, so the walk above never reaches it.
Label hint = ((TextArea) component).getHintLabel();
if (hint != null) {
hint.getAllStyles().setFont(FIELD_FONT);
}
}
if (component instanceof Label || component instanceof Button) {
styleBlock(component);
}
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
// Generated from docs/developer-guide source blocks. Edit the guide snippets here, not inline.

// tag::working-with-windows-bash-001[]
mvn -pl common package -Dcodename1.platform=windows -Dcodename1.buildTarget=windows-device
mvn package -Dcodename1.platform=win -Dcodename1.buildTarget=windows-device
// end::working-with-windows-bash-001[]
2 changes: 1 addition & 1 deletion docs/developer-guide/Advanced-Theming.asciidoc
Original file line numberDiff line numberDiff line change
Expand Up@@ -728,7 +728,7 @@ Will set the foreground color of the https://www.codenameone.com/javadoc/com/cod

When a Codename One https://www.codenameone.com/javadoc/com/codename1/ui/Component.html[Component] is instantiated it requests a https://www.codenameone.com/javadoc/com/codename1/ui/plaf/Style.html[Style] object from the https://www.codenameone.com/javadoc/com/codename1/ui/plaf/UIManager.html[UIManager] class. The `Style` object is based on the settings within the theme and can be modified through code or by using the theme.

You can replace the theme dynamically in runtime and refresh the styles assigned to the various components using the https://www.codenameone.com/javadoc/com/codename1/ui/Component.html#refreshTheme--[refreshTheme()] method.
You can replace the theme dynamically in runtime and refresh the styles assigned to the various components using the https://www.codenameone.com/javadoc/com/codename1/ui/Component.html#refreshTheme()[refreshTheme()] method.

NOTE: It's a common mistake to invoke `refreshTheme()` without actually changing the theme. You see developers doing it when all they need is a `repaint()` or `revalidate()`. Since `refreshTheme()` is **** expensive recommend that you don't use it unless you need to...

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
684849a
Gates for the guide defects Asciidoctor cannot see
shai-almog Sep 2, 2026
eebe28f
Bank the four holes the constraints chapter no longer has
shai-almog Sep 2, 2026
2555762
An unrelated subsection must not stand in for a swallowed chapter
shai-almog Sep 2, 2026
877a371
The ifeval branch of the conditional test could never match
shai-almog Sep 2, 2026
2a08ef7
Record why commented-out URLs are checked like any other
shai-almog Sep 2, 2026
f69f47c
Four links left the book to re-enter it, and nothing checked the anchor
shai-almog Sep 2, 2026
82ed7bf
Two silent blind spots in the guards become loud refusals
shai-almog Sep 2, 2026
520ced0
Check what swallows a heading, not whether its title turns up somewhere
shai-almog Sep 2, 2026
9ffb68c
The slash rule rejected routes the site declares, and Hugo could move…
shai-almog Sep 2, 2026
4f6f2a5
The PDF branch was never rendered, and an admonition counted as a lis…
shai-almog Sep 2, 2026
bbc2ee5
Three links from the old wiki went nowhere, and a directive is not a …
shai-almog Sep 2, 2026
9a508aa
A Cloudflare function serves /files and /demos, and the PDF surrogate…
shai-almog Sep 2, 2026
9d351ff
A five-dash block is still a literal block, and both scanners read in…
shai-almog Sep 2, 2026
425e749
Guide figures render with a bundled font instead of the host's (#5668)
shai-almog Sep 2, 2026
94a3b45
A comment block is not content, and img/ really does ship beside the …
shai-almog Sep 2, 2026
773297b
The packaging exemption trusted the repository, not the published output
shai-almog Sep 2, 2026
93b0518
The Introduction taught a dead IDE menu, an iTunes walkthrough and 20…
shai-almog Sep 2, 2026
b1f80df
Copyright headers for the two Windows wrappers, and duplicate include…
shai-almog Sep 2, 2026
cce4454
Refuse a conditional include at every depth, not only in the manifest
shai-almog Sep 2, 2026
aa6af63
A typo in the authority hid behind a correct hostname
shai-almog Sep 2, 2026
41c3133
A listing hides content from the reader, not directives from the prep…
shai-almog Sep 2, 2026
5e79881
The DNS root dot walked straight past the same-site checks
shai-almog Sep 2, 2026
b50b795
Read the fallback routes from the Function, and close the authority f…
shai-almog Sep 2, 2026
2a25ea8
An inline directive is content, and an attribute-built URL is unreadable
shai-almog Sep 2, 2026
2cc2941
Three Javadoc links pointed at a package that is spelt lowercase
shai-almog Sep 2, 2026
50cf047
The Javadoc index documented a package the build deliberately removes
shai-almog Sep 2, 2026
21e5d5c
Javadoc documents public types, and the index was recording every sou…
shai-almog Sep 2, 2026
4c34787
An uppercase scheme was invisible, and the two slash rules had drifte…
shai-almog Sep 2, 2026
6c1c945
A nested type was taken on trust, and an include hid inside an inline…
shai-almog Sep 2, 2026
db15d73
An invented middle level passed, and xref: was a third spelling nobod…
shai-almog Sep 2, 2026
c3ebc41
The publication-date fix was inert: the parser never stored the keys
shai-almog Sep 2, 2026
a1fa03d
Real nesting, real timestamps, and a comment stripper that ate braces
shai-almog Sep 2, 2026
a706801
The self-link rule existed on one branch and not the other
shai-almog Sep 2, 2026
3e7b4a5
An inline comment became part of the value, and href tolerated no spaces
shai-almog Sep 2, 2026
6655675
A nested annotation was rejected, and an attributed include was invis…
shai-almog Sep 2, 2026
ab628e1
Thirty-five Javadoc anchors used a form javadoc stopped emitting year…
shai-almog Sep 2, 2026
e7bb4ee
An unquoted href value is still an href
shai-almog Sep 2, 2026
d3ba948
The array brackets I added last round broke two links, and aliases ke…
shai-almog Sep 2, 2026
92a9a91
A macro target is not prose, and a chapter has one place in the book
shai-almog Sep 2, 2026
e54e3f2
class-use pages are not generated, so linking to one is not fine
shai-almog Sep 2, 2026
bd3fb14
A reused fragment renders twice, and the CLDC sources trigger nothing
shai-almog Sep 2, 2026
2982652
An anchor decorates the block below it; it is not the block
shai-almog Sep 2, 2026
7cbeee1
Two package pages javadoc never writes, and multiplicity that stopped…
shai-almog Sep 2, 2026
d45e1da
Two leniency hatches that accepted anything shaped vaguely right
shai-almog Sep 2, 2026
102bd45
A wildcard declares a slashed route just as a literal one does
shai-almog Sep 2, 2026
79cb5e9
Rejecting javadoc's asset directories rather than enumerating them
shai-almog Sep 2, 2026
abdb22b
A titled listing is a listing, and a figure was never the promised code
shai-almog Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 46 additions & 19 deletions .github/workflows/developer-guide-docs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,8 +50,13 @@ jobs:
docs:
- 'docs/developer-guide/**'
- 'docs/demos/common/src/main/snippets/**'
- 'scripts/developer-guide/migrate-inline-guide-snippets.py'
- 'scripts/developer-guide/validate-guide-snippets.py'
# The whole directory, not two named files. on.pull_request.paths
# already triggers on scripts/developer-guide/**, so a change to any
# other script here started the workflow while leaving this filter
# false -- which skipped the very steps that script governs. A
# change to compare-screenshots.py could have merged without the
# screenshot check ever running it.
- 'scripts/developer-guide/**'
- '.github/workflows/developer-guide-docs.yml'
# Triggering the workflow is not enough on its own: the HTML and PDF
# build and the steps beside it are gated on this filter, so the
Expand DownExpand Up@@ -122,6 +127,7 @@ jobs:
run: |
set -euo pipefail
GENERATED_DIR="$RUNNER_TEMP/pre-advanced-theming-screenshots"
echo "GUIDE_GENERATED_SCREENSHOTS=$GENERATED_DIR" >> "$GITHUB_ENV"
rm -rf "$GENERATED_DIR"
mkdir -p "$GENERATED_DIR"
xvfb-run -a mvn -B -ntp \
Expand All@@ -131,23 +137,25 @@ jobs:
-P guide-screenshot-generator \
-Dguide.screenshot.output="$GENERATED_DIR" \
verify
COUNT="$(find "$GENERATED_DIR" -maxdepth 1 -type f -name '*.png' | wc -l | tr -d ' ')"
if [ "$COUNT" != "24" ]; then
echo "::error::Expected 24 generated pre-Advanced Theming screenshots, found $COUNT"
exit 1
fi
for generated in "$GENERATED_DIR"/*.png; do
name="$(basename "$generated")"
committed="docs/developer-guide/img/$name"
if [ ! -f "$committed" ]; then
echo "::error::Generated screenshot has no committed counterpart: $name"
exit 1
fi
if ! cmp -s "$generated" "$committed"; then
echo "::error::Committed screenshot is stale: $name"
exit 1
fi
done
pip install --user --quiet Pillow
# Byte equality is still the rule; a figure only gets a bounded
# difference if it carries a .tolerance sidecar saying why.
python3 scripts/developer-guide/compare-screenshots.py \
--generated "$GENERATED_DIR" \
--committed docs/developer-guide/img \
--expected-count 24

# When the byte compare fails, the message names the file but not what it
# actually rendered, which leaves no way to tell a real regression from an
# environment difference. Publishing what this runner produced makes the
# two distinguishable without adding a debugging round trip to CI.
- name: Upload generated screenshots when they do not match
if: failure() && env.GUIDE_GENERATED_SCREENSHOTS != ''
uses: actions/upload-artifact@v7
with:
name: guide-generated-screenshots
path: ${{ env.GUIDE_GENERATED_SCREENSHOTS }}
if-no-files-found: warn

- name: Verify developer guide images are referenced
if: github.event_name != 'pull_request' || steps.changes.outputs.docs == 'true' || steps.changes.outputs.demos == 'true' || steps.changes.outputs.workflow == 'true'
Expand DownExpand Up@@ -271,6 +279,25 @@ jobs:
echo "Asciidoctor exited with status $STATUS — the final quality-gate step will fail the build." >&2
fi

# These four cover defects the gates above are structurally blind to: a
# chapter swallowed by the one before it, a cross-reference that resolves
# to nothing, prose that promises a code block which is not there, and a
# link the website does not serve. Asciidoctor reports none of them, and
# every one of them shipped. The structure and cross-reference checks
# render the book, so they must run after the Asciidoctor install.
- name: Check developer guide structure, cross-references, code blocks and links
run: |
set -euo pipefail
python3 scripts/developer-guide/check-guide-structure.py
python3 scripts/developer-guide/check-guide-xrefs.py
python3 scripts/developer-guide/check-missing-code-blocks.py
# Note this only catches a link the GUIDE breaks. When the website
# moves the route instead -- a page renamed, deleted or re-slugged --
# the same script runs from website-docs.yml, which triggers on
# docs/website/**. Putting those paths here would run this whole job
# (maven install, demo build, screenshots) for every blog post.
python3 scripts/developer-guide/check-guide-links.py

- name: Build Developer Guide HTML and PDF
if: github.event_name != 'pull_request' || steps.changes.outputs.docs == 'true' || steps.changes.outputs.demos == 'true' || steps.changes.outputs.workflow == 'true'
run: |
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/website-docs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,10 @@ on:
- 'vm/ByteCodeTranslator/**'
- 'vm/JavaAPI/**'
- 'CodenameOne/src/**'
# The other root build_javadocs.sh generates from, and the other one
# check-guide-links.py indexes. Without it, deleting a linked CLDC type
# merges unvalidated and the next scheduled build is where it surfaces.
- 'Ports/CLDC11/src/**'
# The developer guide this site renders includes a build hint table that is
# generated rather than committed, so a change to the catalog or to the
# renderer changes the published page without touching docs/. The
Expand All@@ -46,6 +50,10 @@ on:
- 'vm/ByteCodeTranslator/**'
- 'vm/JavaAPI/**'
- 'CodenameOne/src/**'
# The other root build_javadocs.sh generates from, and the other one
# check-guide-links.py indexes. Without it, deleting a linked CLDC type
# merges unvalidated and the next scheduled build is where it surfaces.
- 'Ports/CLDC11/src/**'
# As above: the build hint table is generated, not committed.
- 'maven/build-hint-catalog/**'
- 'maven/build-hint-tools/**'
Expand DownExpand Up@@ -277,6 +285,17 @@ jobs:
--root-dir public
public/**/*.html

# The developer guide links into this site, so a page renamed, deleted or
# re-slugged here breaks the guide. The check lives in THIS workflow rather
# than in developer-guide-docs.yml because it is the route data that
# changed: widening that workflow's paths to docs/website/** would run the
# full guide build -- maven install, demo build, screenshot generation --
# on every blog post the daily publisher pushes, to run one Python script.
# This job already triggers on docs/website/**, and the check needs nothing
# but a checkout.
- name: Check developer guide links against the routes this site serves
run: python3 scripts/developer-guide/check-guide-links.py
Comment thread
shai-almog marked this conversation as resolved.

- name: Reject absolute codenameone.com links
run: |
set -euo pipefail
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,41 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codenameone.developerguide.screenshots;

import com.codename1.components.SpanLabel;
import com.codenameone.developerguide.snippets.generated.BasicsJava034Snippet;
import com.codename1.ui.Button;
import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.CN;
import com.codename1.ui.FontImage;
import com.codename1.ui.Font;
import com.codename1.ui.Form;
import com.codename1.ui.Graphics;
import com.codename1.ui.Image;
import com.codename1.ui.Label;
import com.codename1.ui.TextArea;
import com.codename1.ui.TextField;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.BoxLayout;
Expand DownExpand Up@@ -40,10 +64,33 @@ public final class PreAdvancedThemingScreenshots {
private static final int BLUE = 0x0b57d0;
private static final int GREEN = 0x06a806;
private static final int WHITE = 0xffffff;
private static final Font TITLE_FONT = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_LARGE)
.derive(35, Font.STYLE_PLAIN);
private static final Font BLOCK_FONT = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_LARGE)
.derive(24, Font.STYLE_PLAIN);
private static final Font TITLE_FONT = screenshotFont(35);
private static final Font BLOCK_FONT = screenshotFont(24);
/// Sized to match the height the theme's own default font had (13px), so
/// pinning the face on components the block styling does not touch keeps
/// their layout exactly where it was.
private static final Font FIELD_FONT = screenshotFont(11);

/// Loads a figure font from the port's bundled Roboto rather than from the host.
///
/// `Font.createSystemFont` resolves through `JavaSEPort.fontFaceSystem`, which is
/// "Arial" on macOS and Linux alike. Arial exists on a developer's Mac and not on a
/// stock CI runner, so AWT silently substitutes a different face and every glyph in
/// every figure changes -- which is why none of these screenshots could be
/// regenerated outside CI and byte-compared against what was committed. The
/// `native:` scheme reads `/com/codename1/impl/javase/Roboto-*.ttf` off the
/// classpath, so the result does not depend on what the machine happens to have
/// installed. It is also what this project's font rule requires everywhere.
private static Font screenshotFont(int pixelSize) {
Font font = Font.createTrueTypeFont("native:MainRegular", "native:MainRegular");
if (font == null) {
// Falling back to a host font would quietly restore the very
// non-determinism this exists to remove, so refuse instead.
throw new IllegalStateException(
"the native font scheme is unavailable, so figures would render with a host font");
}
return font.derive(pixelSize, Font.STYLE_PLAIN);
}

private PreAdvancedThemingScreenshots() {
}
Expand All@@ -53,6 +100,32 @@ public interface ScreenshotSink {
}

public static void generate(ScreenshotSink sink) throws IOException {
// JavaSEPort.loadTrueTypeFont has an earlier branch for native: fonts:
// when isIOS is set -- which loadSkinFile does for any skin whose
// systemFontFamily contains "helvetica" -- it resolves to the first
// INSTALLED SF or Helvetica family and never reaches the bundled Roboto.
// This generator never loads a skin, so that branch is not taken, and the
// measurement agrees: figures rendered on a Mac match the Linux runner
// byte for byte, which could not happen if one side were using Helvetica
// Neue. Guard it anyway, because a future change that loads a skin here
// would put the host's fonts back into the output without any other
// symptom.
String platform = Display.getInstance().getPlatformName();
if ("ios".equals(platform)) {
throw new IllegalStateException(
"an iOS skin is active, so native: fonts would resolve to installed "
+ "system faces instead of the bundled ones and these figures would "
+ "stop being reproducible off this machine");
}

// MigLayout picks its default gaps from PlatformDefaults, which reads
// System.getProperty("os.name") and returns MAC_OSX, GNOME or WINDOWS_XP.
// The gaps differ per platform, so mig-layout.png came out with macOS
// spacing on a Mac and GNOME spacing on the Linux runner -- a 12.8% pixel
// difference that has nothing to do with fonts. Pin it so the figure shows
// the same thing wherever it is generated.
com.codename1.ui.layouts.mig.PlatformDefaults.setPlatform(
com.codename1.ui.layouts.mig.PlatformDefaults.GNOME);
write(sink, "flow-layout.png", flow("Flow Layout", Component.LEFT, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
write(sink, "flow-layout-center.png", flow("Flow Layout", Component.CENTER, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
write(sink, "flow-layout-right.png", flow("Flow Layout", Component.RIGHT, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
Expand DownExpand Up@@ -328,6 +401,22 @@ private static void applyScreenshotStyle(Form form) {
private static void applyBlockStyleToContent(Container container) {
for (int i = 0; i < container.getComponentCount(); i++) {
Component component = container.getComponentAt(i);
// Pin the font on EVERY component rather than on the types that also
// get block colours. Anything left on the theme's default font
// resolves through the host, and that is what made
// guibuilder-2-insets-3.png -- the only figure containing a
// TextField -- differ between a Mac and CI while the other 23
// matched byte for byte. Enumerating the types that carry text
// would leave the next one to be added broken in the same way.
component.getAllStyles().setFont(FIELD_FONT);
if (component instanceof TextArea) {
// The hint is painted by a Label that is not in the component
// tree, so the walk above never reaches it.
Label hint = ((TextArea) component).getHintLabel();
if (hint != null) {
hint.getAllStyles().setFont(FIELD_FONT);
}
}
if (component instanceof Label || component instanceof Button) {
styleBlock(component);
}
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
// Generated from docs/developer-guide source blocks. Edit the guide snippets here, not inline.

// tag::working-with-windows-bash-001[]
mvn -pl common package -Dcodename1.platform=windows -Dcodename1.buildTarget=windows-device
mvn package -Dcodename1.platform=win -Dcodename1.buildTarget=windows-device
// end::working-with-windows-bash-001[]
2 changes: 1 addition & 1 deletion docs/developer-guide/Advanced-Theming.asciidoc
Original file line numberDiff line numberDiff line change
Expand Up@@ -728,7 +728,7 @@ Will set the foreground color of the https://www.codenameone.com/javadoc/com/cod

When a Codename One https://www.codenameone.com/javadoc/com/codename1/ui/Component.html[Component] is instantiated it requests a https://www.codenameone.com/javadoc/com/codename1/ui/plaf/Style.html[Style] object from the https://www.codenameone.com/javadoc/com/codename1/ui/plaf/UIManager.html[UIManager] class. The `Style` object is based on the settings within the theme and can be modified through code or by using the theme.

You can replace the theme dynamically in runtime and refresh the styles assigned to the various components using the https://www.codenameone.com/javadoc/com/codename1/ui/Component.html#refreshTheme--[refreshTheme()] method.
You can replace the theme dynamically in runtime and refresh the styles assigned to the various components using the https://www.codenameone.com/javadoc/com/codename1/ui/Component.html#refreshTheme()[refreshTheme()] method.

NOTE: It's a common mistake to invoke `refreshTheme()` without actually changing the theme. You see developers doing it when all they need is a `repaint()` or `revalidate()`. Since `refreshTheme()` is **** expensive recommend that you don't use it unless you need to...

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
684849a
Gates for the guide defects Asciidoctor cannot see
shai-almog Sep 2, 2026
eebe28f
Bank the four holes the constraints chapter no longer has
shai-almog Sep 2, 2026
2555762
An unrelated subsection must not stand in for a swallowed chapter
shai-almog Sep 2, 2026
877a371
The ifeval branch of the conditional test could never match
shai-almog Sep 2, 2026
2a08ef7
Record why commented-out URLs are checked like any other
shai-almog Sep 2, 2026
f69f47c
Four links left the book to re-enter it, and nothing checked the anchor
shai-almog Sep 2, 2026
82ed7bf
Two silent blind spots in the guards become loud refusals
shai-almog Sep 2, 2026
520ced0
Check what swallows a heading, not whether its title turns up somewhere
shai-almog Sep 2, 2026
9ffb68c
The slash rule rejected routes the site declares, and Hugo could move…
shai-almog Sep 2, 2026
4f6f2a5
The PDF branch was never rendered, and an admonition counted as a lis…
shai-almog Sep 2, 2026
bbc2ee5
Three links from the old wiki went nowhere, and a directive is not a …
shai-almog Sep 2, 2026
9a508aa
A Cloudflare function serves /files and /demos, and the PDF surrogate…
shai-almog Sep 2, 2026
9d351ff
A five-dash block is still a literal block, and both scanners read in…
shai-almog Sep 2, 2026
425e749
Guide figures render with a bundled font instead of the host's (#5668)
shai-almog Sep 2, 2026
94a3b45
A comment block is not content, and img/ really does ship beside the …
shai-almog Sep 2, 2026
773297b
The packaging exemption trusted the repository, not the published output
shai-almog Sep 2, 2026
93b0518
The Introduction taught a dead IDE menu, an iTunes walkthrough and 20…
shai-almog Sep 2, 2026
b1f80df
Copyright headers for the two Windows wrappers, and duplicate include…
shai-almog Sep 2, 2026
cce4454
Refuse a conditional include at every depth, not only in the manifest
shai-almog Sep 2, 2026
aa6af63
A typo in the authority hid behind a correct hostname
shai-almog Sep 2, 2026
41c3133
A listing hides content from the reader, not directives from the prep…
shai-almog Sep 2, 2026
5e79881
The DNS root dot walked straight past the same-site checks
shai-almog Sep 2, 2026
b50b795
Read the fallback routes from the Function, and close the authority f…
shai-almog Sep 2, 2026
2a25ea8
An inline directive is content, and an attribute-built URL is unreadable
shai-almog Sep 2, 2026
2cc2941
Three Javadoc links pointed at a package that is spelt lowercase
shai-almog Sep 2, 2026
50cf047
The Javadoc index documented a package the build deliberately removes
shai-almog Sep 2, 2026
21e5d5c
Javadoc documents public types, and the index was recording every sou…
shai-almog Sep 2, 2026
4c34787
An uppercase scheme was invisible, and the two slash rules had drifte…
shai-almog Sep 2, 2026
6c1c945
A nested type was taken on trust, and an include hid inside an inline…
shai-almog Sep 2, 2026
db15d73
An invented middle level passed, and xref: was a third spelling nobod…
shai-almog Sep 2, 2026
c3ebc41
The publication-date fix was inert: the parser never stored the keys
shai-almog Sep 2, 2026
a1fa03d
Real nesting, real timestamps, and a comment stripper that ate braces
shai-almog Sep 2, 2026
a706801
The self-link rule existed on one branch and not the other
shai-almog Sep 2, 2026
3e7b4a5
An inline comment became part of the value, and href tolerated no spaces
shai-almog Sep 2, 2026
6655675
A nested annotation was rejected, and an attributed include was invis…
shai-almog Sep 2, 2026
ab628e1
Thirty-five Javadoc anchors used a form javadoc stopped emitting year…
shai-almog Sep 2, 2026
e7bb4ee
An unquoted href value is still an href
shai-almog Sep 2, 2026
d3ba948
The array brackets I added last round broke two links, and aliases ke…
shai-almog Sep 2, 2026
92a9a91
A macro target is not prose, and a chapter has one place in the book
shai-almog Sep 2, 2026
e54e3f2
class-use pages are not generated, so linking to one is not fine
shai-almog Sep 2, 2026
bd3fb14
A reused fragment renders twice, and the CLDC sources trigger nothing
shai-almog Sep 2, 2026
2982652
An anchor decorates the block below it; it is not the block
shai-almog Sep 2, 2026
7cbeee1
Two package pages javadoc never writes, and multiplicity that stopped…
shai-almog Sep 2, 2026
d45e1da
Two leniency hatches that accepted anything shaped vaguely right
shai-almog Sep 2, 2026
102bd45
A wildcard declares a slashed route just as a literal one does
shai-almog Sep 2, 2026
79cb5e9
Rejecting javadoc's asset directories rather than enumerating them
shai-almog Sep 2, 2026
abdb22b
A titled listing is a listing, and a figure was never the promised code
shai-almog Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 46 additions & 19 deletions .github/workflows/developer-guide-docs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,8 +50,13 @@ jobs:
docs:
- 'docs/developer-guide/**'
- 'docs/demos/common/src/main/snippets/**'
- 'scripts/developer-guide/migrate-inline-guide-snippets.py'
- 'scripts/developer-guide/validate-guide-snippets.py'
# The whole directory, not two named files. on.pull_request.paths
# already triggers on scripts/developer-guide/**, so a change to any
# other script here started the workflow while leaving this filter
# false -- which skipped the very steps that script governs. A
# change to compare-screenshots.py could have merged without the
# screenshot check ever running it.
- 'scripts/developer-guide/**'
- '.github/workflows/developer-guide-docs.yml'
# Triggering the workflow is not enough on its own: the HTML and PDF
# build and the steps beside it are gated on this filter, so the
Expand DownExpand Up@@ -122,6 +127,7 @@ jobs:
run: |
set -euo pipefail
GENERATED_DIR="$RUNNER_TEMP/pre-advanced-theming-screenshots"
echo "GUIDE_GENERATED_SCREENSHOTS=$GENERATED_DIR" >> "$GITHUB_ENV"
rm -rf "$GENERATED_DIR"
mkdir -p "$GENERATED_DIR"
xvfb-run -a mvn -B -ntp \
Expand All@@ -131,23 +137,25 @@ jobs:
-P guide-screenshot-generator \
-Dguide.screenshot.output="$GENERATED_DIR" \
verify
COUNT="$(find "$GENERATED_DIR" -maxdepth 1 -type f -name '*.png' | wc -l | tr -d ' ')"
if [ "$COUNT" != "24" ]; then
echo "::error::Expected 24 generated pre-Advanced Theming screenshots, found $COUNT"
exit 1
fi
for generated in "$GENERATED_DIR"/*.png; do
name="$(basename "$generated")"
committed="docs/developer-guide/img/$name"
if [ ! -f "$committed" ]; then
echo "::error::Generated screenshot has no committed counterpart: $name"
exit 1
fi
if ! cmp -s "$generated" "$committed"; then
echo "::error::Committed screenshot is stale: $name"
exit 1
fi
done
pip install --user --quiet Pillow
# Byte equality is still the rule; a figure only gets a bounded
# difference if it carries a .tolerance sidecar saying why.
python3 scripts/developer-guide/compare-screenshots.py \
--generated "$GENERATED_DIR" \
--committed docs/developer-guide/img \
--expected-count 24

# When the byte compare fails, the message names the file but not what it
# actually rendered, which leaves no way to tell a real regression from an
# environment difference. Publishing what this runner produced makes the
# two distinguishable without adding a debugging round trip to CI.
- name: Upload generated screenshots when they do not match
if: failure() && env.GUIDE_GENERATED_SCREENSHOTS != ''
uses: actions/upload-artifact@v7
with:
name: guide-generated-screenshots
path: ${{ env.GUIDE_GENERATED_SCREENSHOTS }}
if-no-files-found: warn

- name: Verify developer guide images are referenced
if: github.event_name != 'pull_request' || steps.changes.outputs.docs == 'true' || steps.changes.outputs.demos == 'true' || steps.changes.outputs.workflow == 'true'
Expand DownExpand Up@@ -271,6 +279,25 @@ jobs:
echo "Asciidoctor exited with status $STATUS — the final quality-gate step will fail the build." >&2
fi

# These four cover defects the gates above are structurally blind to: a
# chapter swallowed by the one before it, a cross-reference that resolves
# to nothing, prose that promises a code block which is not there, and a
# link the website does not serve. Asciidoctor reports none of them, and
# every one of them shipped. The structure and cross-reference checks
# render the book, so they must run after the Asciidoctor install.
- name: Check developer guide structure, cross-references, code blocks and links
run: |
set -euo pipefail
python3 scripts/developer-guide/check-guide-structure.py
python3 scripts/developer-guide/check-guide-xrefs.py
python3 scripts/developer-guide/check-missing-code-blocks.py
# Note this only catches a link the GUIDE breaks. When the website
# moves the route instead -- a page renamed, deleted or re-slugged --
# the same script runs from website-docs.yml, which triggers on
# docs/website/**. Putting those paths here would run this whole job
# (maven install, demo build, screenshots) for every blog post.
python3 scripts/developer-guide/check-guide-links.py

- name: Build Developer Guide HTML and PDF
if: github.event_name != 'pull_request' || steps.changes.outputs.docs == 'true' || steps.changes.outputs.demos == 'true' || steps.changes.outputs.workflow == 'true'
run: |
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/website-docs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,10 @@ on:
- 'vm/ByteCodeTranslator/**'
- 'vm/JavaAPI/**'
- 'CodenameOne/src/**'
# The other root build_javadocs.sh generates from, and the other one
# check-guide-links.py indexes. Without it, deleting a linked CLDC type
# merges unvalidated and the next scheduled build is where it surfaces.
- 'Ports/CLDC11/src/**'
# The developer guide this site renders includes a build hint table that is
# generated rather than committed, so a change to the catalog or to the
# renderer changes the published page without touching docs/. The
Expand All@@ -46,6 +50,10 @@ on:
- 'vm/ByteCodeTranslator/**'
- 'vm/JavaAPI/**'
- 'CodenameOne/src/**'
# The other root build_javadocs.sh generates from, and the other one
# check-guide-links.py indexes. Without it, deleting a linked CLDC type
# merges unvalidated and the next scheduled build is where it surfaces.
- 'Ports/CLDC11/src/**'
# As above: the build hint table is generated, not committed.
- 'maven/build-hint-catalog/**'
- 'maven/build-hint-tools/**'
Expand DownExpand Up@@ -277,6 +285,17 @@ jobs:
--root-dir public
public/**/*.html

# The developer guide links into this site, so a page renamed, deleted or
# re-slugged here breaks the guide. The check lives in THIS workflow rather
# than in developer-guide-docs.yml because it is the route data that
# changed: widening that workflow's paths to docs/website/** would run the
# full guide build -- maven install, demo build, screenshot generation --
# on every blog post the daily publisher pushes, to run one Python script.
# This job already triggers on docs/website/**, and the check needs nothing
# but a checkout.
- name: Check developer guide links against the routes this site serves
run: python3 scripts/developer-guide/check-guide-links.py
Comment thread
shai-almog marked this conversation as resolved.

- name: Reject absolute codenameone.com links
run: |
set -euo pipefail
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,41 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codenameone.developerguide.screenshots;

import com.codename1.components.SpanLabel;
import com.codenameone.developerguide.snippets.generated.BasicsJava034Snippet;
import com.codename1.ui.Button;
import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.CN;
import com.codename1.ui.FontImage;
import com.codename1.ui.Font;
import com.codename1.ui.Form;
import com.codename1.ui.Graphics;
import com.codename1.ui.Image;
import com.codename1.ui.Label;
import com.codename1.ui.TextArea;
import com.codename1.ui.TextField;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.BoxLayout;
Expand DownExpand Up@@ -40,10 +64,33 @@ public final class PreAdvancedThemingScreenshots {
private static final int BLUE = 0x0b57d0;
private static final int GREEN = 0x06a806;
private static final int WHITE = 0xffffff;
private static final Font TITLE_FONT = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_LARGE)
.derive(35, Font.STYLE_PLAIN);
private static final Font BLOCK_FONT = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_LARGE)
.derive(24, Font.STYLE_PLAIN);
private static final Font TITLE_FONT = screenshotFont(35);
private static final Font BLOCK_FONT = screenshotFont(24);
/// Sized to match the height the theme's own default font had (13px), so
/// pinning the face on components the block styling does not touch keeps
/// their layout exactly where it was.
private static final Font FIELD_FONT = screenshotFont(11);

/// Loads a figure font from the port's bundled Roboto rather than from the host.
///
/// `Font.createSystemFont` resolves through `JavaSEPort.fontFaceSystem`, which is
/// "Arial" on macOS and Linux alike. Arial exists on a developer's Mac and not on a
/// stock CI runner, so AWT silently substitutes a different face and every glyph in
/// every figure changes -- which is why none of these screenshots could be
/// regenerated outside CI and byte-compared against what was committed. The
/// `native:` scheme reads `/com/codename1/impl/javase/Roboto-*.ttf` off the
/// classpath, so the result does not depend on what the machine happens to have
/// installed. It is also what this project's font rule requires everywhere.
private static Font screenshotFont(int pixelSize) {
Font font = Font.createTrueTypeFont("native:MainRegular", "native:MainRegular");
if (font == null) {
// Falling back to a host font would quietly restore the very
// non-determinism this exists to remove, so refuse instead.
throw new IllegalStateException(
"the native font scheme is unavailable, so figures would render with a host font");
}
return font.derive(pixelSize, Font.STYLE_PLAIN);
}

private PreAdvancedThemingScreenshots() {
}
Expand All@@ -53,6 +100,32 @@ public interface ScreenshotSink {
}

public static void generate(ScreenshotSink sink) throws IOException {
// JavaSEPort.loadTrueTypeFont has an earlier branch for native: fonts:
// when isIOS is set -- which loadSkinFile does for any skin whose
// systemFontFamily contains "helvetica" -- it resolves to the first
// INSTALLED SF or Helvetica family and never reaches the bundled Roboto.
// This generator never loads a skin, so that branch is not taken, and the
// measurement agrees: figures rendered on a Mac match the Linux runner
// byte for byte, which could not happen if one side were using Helvetica
// Neue. Guard it anyway, because a future change that loads a skin here
// would put the host's fonts back into the output without any other
// symptom.
String platform = Display.getInstance().getPlatformName();
if ("ios".equals(platform)) {
throw new IllegalStateException(
"an iOS skin is active, so native: fonts would resolve to installed "
+ "system faces instead of the bundled ones and these figures would "
+ "stop being reproducible off this machine");
}

// MigLayout picks its default gaps from PlatformDefaults, which reads
// System.getProperty("os.name") and returns MAC_OSX, GNOME or WINDOWS_XP.
// The gaps differ per platform, so mig-layout.png came out with macOS
// spacing on a Mac and GNOME spacing on the Linux runner -- a 12.8% pixel
// difference that has nothing to do with fonts. Pin it so the figure shows
// the same thing wherever it is generated.
com.codename1.ui.layouts.mig.PlatformDefaults.setPlatform(
com.codename1.ui.layouts.mig.PlatformDefaults.GNOME);
write(sink, "flow-layout.png", flow("Flow Layout", Component.LEFT, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
write(sink, "flow-layout-center.png", flow("Flow Layout", Component.CENTER, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
write(sink, "flow-layout-right.png", flow("Flow Layout", Component.RIGHT, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
Expand DownExpand Up@@ -328,6 +401,22 @@ private static void applyScreenshotStyle(Form form) {
private static void applyBlockStyleToContent(Container container) {
for (int i = 0; i < container.getComponentCount(); i++) {
Component component = container.getComponentAt(i);
// Pin the font on EVERY component rather than on the types that also
// get block colours. Anything left on the theme's default font
// resolves through the host, and that is what made
// guibuilder-2-insets-3.png -- the only figure containing a
// TextField -- differ between a Mac and CI while the other 23
// matched byte for byte. Enumerating the types that carry text
// would leave the next one to be added broken in the same way.
component.getAllStyles().setFont(FIELD_FONT);
if (component instanceof TextArea) {
// The hint is painted by a Label that is not in the component
// tree, so the walk above never reaches it.
Label hint = ((TextArea) component).getHintLabel();
if (hint != null) {
hint.getAllStyles().setFont(FIELD_FONT);
}
}
if (component instanceof Label || component instanceof Button) {
styleBlock(component);
}
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
// Generated from docs/developer-guide source blocks. Edit the guide snippets here, not inline.

// tag::working-with-windows-bash-001[]
mvn -pl common package -Dcodename1.platform=windows -Dcodename1.buildTarget=windows-device
mvn package -Dcodename1.platform=win -Dcodename1.buildTarget=windows-device
// end::working-with-windows-bash-001[]
2 changes: 1 addition & 1 deletion docs/developer-guide/Advanced-Theming.asciidoc
Original file line numberDiff line numberDiff line change
Expand Up@@ -728,7 +728,7 @@ Will set the foreground color of the https://www.codenameone.com/javadoc/com/cod

When a Codename One https://www.codenameone.com/javadoc/com/codename1/ui/Component.html[Component] is instantiated it requests a https://www.codenameone.com/javadoc/com/codename1/ui/plaf/Style.html[Style] object from the https://www.codenameone.com/javadoc/com/codename1/ui/plaf/UIManager.html[UIManager] class. The `Style` object is based on the settings within the theme and can be modified through code or by using the theme.

You can replace the theme dynamically in runtime and refresh the styles assigned to the various components using the https://www.codenameone.com/javadoc/com/codename1/ui/Component.html#refreshTheme--[refreshTheme()] method.
You can replace the theme dynamically in runtime and refresh the styles assigned to the various components using the https://www.codenameone.com/javadoc/com/codename1/ui/Component.html#refreshTheme()[refreshTheme()] method.

NOTE: It's a common mistake to invoke `refreshTheme()` without actually changing the theme. You see developers doing it when all they need is a `repaint()` or `revalidate()`. Since `refreshTheme()` is **** expensive recommend that you don't use it unless you need to...

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
684849a
Gates for the guide defects Asciidoctor cannot see
shai-almog Sep 2, 2026
eebe28f
Bank the four holes the constraints chapter no longer has
shai-almog Sep 2, 2026
2555762
An unrelated subsection must not stand in for a swallowed chapter
shai-almog Sep 2, 2026
877a371
The ifeval branch of the conditional test could never match
shai-almog Sep 2, 2026
2a08ef7
Record why commented-out URLs are checked like any other
shai-almog Sep 2, 2026
f69f47c
Four links left the book to re-enter it, and nothing checked the anchor
shai-almog Sep 2, 2026
82ed7bf
Two silent blind spots in the guards become loud refusals
shai-almog Sep 2, 2026
520ced0
Check what swallows a heading, not whether its title turns up somewhere
shai-almog Sep 2, 2026
9ffb68c
The slash rule rejected routes the site declares, and Hugo could move…
shai-almog Sep 2, 2026
4f6f2a5
The PDF branch was never rendered, and an admonition counted as a lis…
shai-almog Sep 2, 2026
bbc2ee5
Three links from the old wiki went nowhere, and a directive is not a …
shai-almog Sep 2, 2026
9a508aa
A Cloudflare function serves /files and /demos, and the PDF surrogate…
shai-almog Sep 2, 2026
9d351ff
A five-dash block is still a literal block, and both scanners read in…
shai-almog Sep 2, 2026
425e749
Guide figures render with a bundled font instead of the host's (#5668)
shai-almog Sep 2, 2026
94a3b45
A comment block is not content, and img/ really does ship beside the …
shai-almog Sep 2, 2026
773297b
The packaging exemption trusted the repository, not the published output
shai-almog Sep 2, 2026
93b0518
The Introduction taught a dead IDE menu, an iTunes walkthrough and 20…
shai-almog Sep 2, 2026
b1f80df
Copyright headers for the two Windows wrappers, and duplicate include…
shai-almog Sep 2, 2026
cce4454
Refuse a conditional include at every depth, not only in the manifest
shai-almog Sep 2, 2026
aa6af63
A typo in the authority hid behind a correct hostname
shai-almog Sep 2, 2026
41c3133
A listing hides content from the reader, not directives from the prep…
shai-almog Sep 2, 2026
5e79881
The DNS root dot walked straight past the same-site checks
shai-almog Sep 2, 2026
b50b795
Read the fallback routes from the Function, and close the authority f…
shai-almog Sep 2, 2026
2a25ea8
An inline directive is content, and an attribute-built URL is unreadable
shai-almog Sep 2, 2026
2cc2941
Three Javadoc links pointed at a package that is spelt lowercase
shai-almog Sep 2, 2026
50cf047
The Javadoc index documented a package the build deliberately removes
shai-almog Sep 2, 2026
21e5d5c
Javadoc documents public types, and the index was recording every sou…
shai-almog Sep 2, 2026
4c34787
An uppercase scheme was invisible, and the two slash rules had drifte…
shai-almog Sep 2, 2026
6c1c945
A nested type was taken on trust, and an include hid inside an inline…
shai-almog Sep 2, 2026
db15d73
An invented middle level passed, and xref: was a third spelling nobod…
shai-almog Sep 2, 2026
c3ebc41
The publication-date fix was inert: the parser never stored the keys
shai-almog Sep 2, 2026
a1fa03d
Real nesting, real timestamps, and a comment stripper that ate braces
shai-almog Sep 2, 2026
a706801
The self-link rule existed on one branch and not the other
shai-almog Sep 2, 2026
3e7b4a5
An inline comment became part of the value, and href tolerated no spaces
shai-almog Sep 2, 2026
6655675
A nested annotation was rejected, and an attributed include was invis…
shai-almog Sep 2, 2026
ab628e1
Thirty-five Javadoc anchors used a form javadoc stopped emitting year…
shai-almog Sep 2, 2026
e7bb4ee
An unquoted href value is still an href
shai-almog Sep 2, 2026
d3ba948
The array brackets I added last round broke two links, and aliases ke…
shai-almog Sep 2, 2026
92a9a91
A macro target is not prose, and a chapter has one place in the book
shai-almog Sep 2, 2026
e54e3f2
class-use pages are not generated, so linking to one is not fine
shai-almog Sep 2, 2026
bd3fb14
A reused fragment renders twice, and the CLDC sources trigger nothing
shai-almog Sep 2, 2026
2982652
An anchor decorates the block below it; it is not the block
shai-almog Sep 2, 2026
7cbeee1
Two package pages javadoc never writes, and multiplicity that stopped…
shai-almog Sep 2, 2026
d45e1da
Two leniency hatches that accepted anything shaped vaguely right
shai-almog Sep 2, 2026
102bd45
A wildcard declares a slashed route just as a literal one does
shai-almog Sep 2, 2026
79cb5e9
Rejecting javadoc's asset directories rather than enumerating them
shai-almog Sep 2, 2026
abdb22b
A titled listing is a listing, and a figure was never the promised code
shai-almog Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 46 additions & 19 deletions .github/workflows/developer-guide-docs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,8 +50,13 @@ jobs:
docs:
- 'docs/developer-guide/**'
- 'docs/demos/common/src/main/snippets/**'
- 'scripts/developer-guide/migrate-inline-guide-snippets.py'
- 'scripts/developer-guide/validate-guide-snippets.py'
# The whole directory, not two named files. on.pull_request.paths
# already triggers on scripts/developer-guide/**, so a change to any
# other script here started the workflow while leaving this filter
# false -- which skipped the very steps that script governs. A
# change to compare-screenshots.py could have merged without the
# screenshot check ever running it.
- 'scripts/developer-guide/**'
- '.github/workflows/developer-guide-docs.yml'
# Triggering the workflow is not enough on its own: the HTML and PDF
# build and the steps beside it are gated on this filter, so the
Expand DownExpand Up@@ -122,6 +127,7 @@ jobs:
run: |
set -euo pipefail
GENERATED_DIR="$RUNNER_TEMP/pre-advanced-theming-screenshots"
echo "GUIDE_GENERATED_SCREENSHOTS=$GENERATED_DIR" >> "$GITHUB_ENV"
rm -rf "$GENERATED_DIR"
mkdir -p "$GENERATED_DIR"
xvfb-run -a mvn -B -ntp \
Expand All@@ -131,23 +137,25 @@ jobs:
-P guide-screenshot-generator \
-Dguide.screenshot.output="$GENERATED_DIR" \
verify
COUNT="$(find "$GENERATED_DIR" -maxdepth 1 -type f -name '*.png' | wc -l | tr -d ' ')"
if [ "$COUNT" != "24" ]; then
echo "::error::Expected 24 generated pre-Advanced Theming screenshots, found $COUNT"
exit 1
fi
for generated in "$GENERATED_DIR"/*.png; do
name="$(basename "$generated")"
committed="docs/developer-guide/img/$name"
if [ ! -f "$committed" ]; then
echo "::error::Generated screenshot has no committed counterpart: $name"
exit 1
fi
if ! cmp -s "$generated" "$committed"; then
echo "::error::Committed screenshot is stale: $name"
exit 1
fi
done
pip install --user --quiet Pillow
# Byte equality is still the rule; a figure only gets a bounded
# difference if it carries a .tolerance sidecar saying why.
python3 scripts/developer-guide/compare-screenshots.py \
--generated "$GENERATED_DIR" \
--committed docs/developer-guide/img \
--expected-count 24

# When the byte compare fails, the message names the file but not what it
# actually rendered, which leaves no way to tell a real regression from an
# environment difference. Publishing what this runner produced makes the
# two distinguishable without adding a debugging round trip to CI.
- name: Upload generated screenshots when they do not match
if: failure() && env.GUIDE_GENERATED_SCREENSHOTS != ''
uses: actions/upload-artifact@v7
with:
name: guide-generated-screenshots
path: ${{ env.GUIDE_GENERATED_SCREENSHOTS }}
if-no-files-found: warn

- name: Verify developer guide images are referenced
if: github.event_name != 'pull_request' || steps.changes.outputs.docs == 'true' || steps.changes.outputs.demos == 'true' || steps.changes.outputs.workflow == 'true'
Expand DownExpand Up@@ -271,6 +279,25 @@ jobs:
echo "Asciidoctor exited with status $STATUS — the final quality-gate step will fail the build." >&2
fi

# These four cover defects the gates above are structurally blind to: a
# chapter swallowed by the one before it, a cross-reference that resolves
# to nothing, prose that promises a code block which is not there, and a
# link the website does not serve. Asciidoctor reports none of them, and
# every one of them shipped. The structure and cross-reference checks
# render the book, so they must run after the Asciidoctor install.
- name: Check developer guide structure, cross-references, code blocks and links
run: |
set -euo pipefail
python3 scripts/developer-guide/check-guide-structure.py
python3 scripts/developer-guide/check-guide-xrefs.py
python3 scripts/developer-guide/check-missing-code-blocks.py
# Note this only catches a link the GUIDE breaks. When the website
# moves the route instead -- a page renamed, deleted or re-slugged --
# the same script runs from website-docs.yml, which triggers on
# docs/website/**. Putting those paths here would run this whole job
# (maven install, demo build, screenshots) for every blog post.
python3 scripts/developer-guide/check-guide-links.py

- name: Build Developer Guide HTML and PDF
if: github.event_name != 'pull_request' || steps.changes.outputs.docs == 'true' || steps.changes.outputs.demos == 'true' || steps.changes.outputs.workflow == 'true'
run: |
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/website-docs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,10 @@ on:
- 'vm/ByteCodeTranslator/**'
- 'vm/JavaAPI/**'
- 'CodenameOne/src/**'
# The other root build_javadocs.sh generates from, and the other one
# check-guide-links.py indexes. Without it, deleting a linked CLDC type
# merges unvalidated and the next scheduled build is where it surfaces.
- 'Ports/CLDC11/src/**'
# The developer guide this site renders includes a build hint table that is
# generated rather than committed, so a change to the catalog or to the
# renderer changes the published page without touching docs/. The
Expand All@@ -46,6 +50,10 @@ on:
- 'vm/ByteCodeTranslator/**'
- 'vm/JavaAPI/**'
- 'CodenameOne/src/**'
# The other root build_javadocs.sh generates from, and the other one
# check-guide-links.py indexes. Without it, deleting a linked CLDC type
# merges unvalidated and the next scheduled build is where it surfaces.
- 'Ports/CLDC11/src/**'
# As above: the build hint table is generated, not committed.
- 'maven/build-hint-catalog/**'
- 'maven/build-hint-tools/**'
Expand DownExpand Up@@ -277,6 +285,17 @@ jobs:
--root-dir public
public/**/*.html

# The developer guide links into this site, so a page renamed, deleted or
# re-slugged here breaks the guide. The check lives in THIS workflow rather
# than in developer-guide-docs.yml because it is the route data that
# changed: widening that workflow's paths to docs/website/** would run the
# full guide build -- maven install, demo build, screenshot generation --
# on every blog post the daily publisher pushes, to run one Python script.
# This job already triggers on docs/website/**, and the check needs nothing
# but a checkout.
- name: Check developer guide links against the routes this site serves
run: python3 scripts/developer-guide/check-guide-links.py
Comment thread
shai-almog marked this conversation as resolved.

- name: Reject absolute codenameone.com links
run: |
set -euo pipefail
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,41 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codenameone.developerguide.screenshots;

import com.codename1.components.SpanLabel;
import com.codenameone.developerguide.snippets.generated.BasicsJava034Snippet;
import com.codename1.ui.Button;
import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.CN;
import com.codename1.ui.FontImage;
import com.codename1.ui.Font;
import com.codename1.ui.Form;
import com.codename1.ui.Graphics;
import com.codename1.ui.Image;
import com.codename1.ui.Label;
import com.codename1.ui.TextArea;
import com.codename1.ui.TextField;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.BoxLayout;
Expand DownExpand Up@@ -40,10 +64,33 @@ public final class PreAdvancedThemingScreenshots {
private static final int BLUE = 0x0b57d0;
private static final int GREEN = 0x06a806;
private static final int WHITE = 0xffffff;
private static final Font TITLE_FONT = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_LARGE)
.derive(35, Font.STYLE_PLAIN);
private static final Font BLOCK_FONT = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_LARGE)
.derive(24, Font.STYLE_PLAIN);
private static final Font TITLE_FONT = screenshotFont(35);
private static final Font BLOCK_FONT = screenshotFont(24);
/// Sized to match the height the theme's own default font had (13px), so
/// pinning the face on components the block styling does not touch keeps
/// their layout exactly where it was.
private static final Font FIELD_FONT = screenshotFont(11);

/// Loads a figure font from the port's bundled Roboto rather than from the host.
///
/// `Font.createSystemFont` resolves through `JavaSEPort.fontFaceSystem`, which is
/// "Arial" on macOS and Linux alike. Arial exists on a developer's Mac and not on a
/// stock CI runner, so AWT silently substitutes a different face and every glyph in
/// every figure changes -- which is why none of these screenshots could be
/// regenerated outside CI and byte-compared against what was committed. The
/// `native:` scheme reads `/com/codename1/impl/javase/Roboto-*.ttf` off the
/// classpath, so the result does not depend on what the machine happens to have
/// installed. It is also what this project's font rule requires everywhere.
private static Font screenshotFont(int pixelSize) {
Font font = Font.createTrueTypeFont("native:MainRegular", "native:MainRegular");
if (font == null) {
// Falling back to a host font would quietly restore the very
// non-determinism this exists to remove, so refuse instead.
throw new IllegalStateException(
"the native font scheme is unavailable, so figures would render with a host font");
}
return font.derive(pixelSize, Font.STYLE_PLAIN);
}

private PreAdvancedThemingScreenshots() {
}
Expand All@@ -53,6 +100,32 @@ public interface ScreenshotSink {
}

public static void generate(ScreenshotSink sink) throws IOException {
// JavaSEPort.loadTrueTypeFont has an earlier branch for native: fonts:
// when isIOS is set -- which loadSkinFile does for any skin whose
// systemFontFamily contains "helvetica" -- it resolves to the first
// INSTALLED SF or Helvetica family and never reaches the bundled Roboto.
// This generator never loads a skin, so that branch is not taken, and the
// measurement agrees: figures rendered on a Mac match the Linux runner
// byte for byte, which could not happen if one side were using Helvetica
// Neue. Guard it anyway, because a future change that loads a skin here
// would put the host's fonts back into the output without any other
// symptom.
String platform = Display.getInstance().getPlatformName();
if ("ios".equals(platform)) {
throw new IllegalStateException(
"an iOS skin is active, so native: fonts would resolve to installed "
+ "system faces instead of the bundled ones and these figures would "
+ "stop being reproducible off this machine");
}

// MigLayout picks its default gaps from PlatformDefaults, which reads
// System.getProperty("os.name") and returns MAC_OSX, GNOME or WINDOWS_XP.
// The gaps differ per platform, so mig-layout.png came out with macOS
// spacing on a Mac and GNOME spacing on the Linux runner -- a 12.8% pixel
// difference that has nothing to do with fonts. Pin it so the figure shows
// the same thing wherever it is generated.
com.codename1.ui.layouts.mig.PlatformDefaults.setPlatform(
com.codename1.ui.layouts.mig.PlatformDefaults.GNOME);
write(sink, "flow-layout.png", flow("Flow Layout", Component.LEFT, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
write(sink, "flow-layout-center.png", flow("Flow Layout", Component.CENTER, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
write(sink, "flow-layout-right.png", flow("Flow Layout", Component.RIGHT, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
Expand DownExpand Up@@ -328,6 +401,22 @@ private static void applyScreenshotStyle(Form form) {
private static void applyBlockStyleToContent(Container container) {
for (int i = 0; i < container.getComponentCount(); i++) {
Component component = container.getComponentAt(i);
// Pin the font on EVERY component rather than on the types that also
// get block colours. Anything left on the theme's default font
// resolves through the host, and that is what made
// guibuilder-2-insets-3.png -- the only figure containing a
// TextField -- differ between a Mac and CI while the other 23
// matched byte for byte. Enumerating the types that carry text
// would leave the next one to be added broken in the same way.
component.getAllStyles().setFont(FIELD_FONT);
if (component instanceof TextArea) {
// The hint is painted by a Label that is not in the component
// tree, so the walk above never reaches it.
Label hint = ((TextArea) component).getHintLabel();
if (hint != null) {
hint.getAllStyles().setFont(FIELD_FONT);
}
}
if (component instanceof Label || component instanceof Button) {
styleBlock(component);
}
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
// Generated from docs/developer-guide source blocks. Edit the guide snippets here, not inline.

// tag::working-with-windows-bash-001[]
mvn -pl common package -Dcodename1.platform=windows -Dcodename1.buildTarget=windows-device
mvn package -Dcodename1.platform=win -Dcodename1.buildTarget=windows-device
// end::working-with-windows-bash-001[]
2 changes: 1 addition & 1 deletion docs/developer-guide/Advanced-Theming.asciidoc
Original file line numberDiff line numberDiff line change
Expand Up@@ -728,7 +728,7 @@ Will set the foreground color of the https://www.codenameone.com/javadoc/com/cod

When a Codename One https://www.codenameone.com/javadoc/com/codename1/ui/Component.html[Component] is instantiated it requests a https://www.codenameone.com/javadoc/com/codename1/ui/plaf/Style.html[Style] object from the https://www.codenameone.com/javadoc/com/codename1/ui/plaf/UIManager.html[UIManager] class. The `Style` object is based on the settings within the theme and can be modified through code or by using the theme.

You can replace the theme dynamically in runtime and refresh the styles assigned to the various components using the https://www.codenameone.com/javadoc/com/codename1/ui/Component.html#refreshTheme--[refreshTheme()] method.
You can replace the theme dynamically in runtime and refresh the styles assigned to the various components using the https://www.codenameone.com/javadoc/com/codename1/ui/Component.html#refreshTheme()[refreshTheme()] method.

NOTE: It's a common mistake to invoke `refreshTheme()` without actually changing the theme. You see developers doing it when all they need is a `repaint()` or `revalidate()`. Since `refreshTheme()` is **** expensive recommend that you don't use it unless you need to...

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
684849a
Gates for the guide defects Asciidoctor cannot see
shai-almog Sep 2, 2026
eebe28f
Bank the four holes the constraints chapter no longer has
shai-almog Sep 2, 2026
2555762
An unrelated subsection must not stand in for a swallowed chapter
shai-almog Sep 2, 2026
877a371
The ifeval branch of the conditional test could never match
shai-almog Sep 2, 2026
2a08ef7
Record why commented-out URLs are checked like any other
shai-almog Sep 2, 2026
f69f47c
Four links left the book to re-enter it, and nothing checked the anchor
shai-almog Sep 2, 2026
82ed7bf
Two silent blind spots in the guards become loud refusals
shai-almog Sep 2, 2026
520ced0
Check what swallows a heading, not whether its title turns up somewhere
shai-almog Sep 2, 2026
9ffb68c
The slash rule rejected routes the site declares, and Hugo could move…
shai-almog Sep 2, 2026
4f6f2a5
The PDF branch was never rendered, and an admonition counted as a lis…
shai-almog Sep 2, 2026
bbc2ee5
Three links from the old wiki went nowhere, and a directive is not a …
shai-almog Sep 2, 2026
9a508aa
A Cloudflare function serves /files and /demos, and the PDF surrogate…
shai-almog Sep 2, 2026
9d351ff
A five-dash block is still a literal block, and both scanners read in…
shai-almog Sep 2, 2026
425e749
Guide figures render with a bundled font instead of the host's (#5668)
shai-almog Sep 2, 2026
94a3b45
A comment block is not content, and img/ really does ship beside the …
shai-almog Sep 2, 2026
773297b
The packaging exemption trusted the repository, not the published output
shai-almog Sep 2, 2026
93b0518
The Introduction taught a dead IDE menu, an iTunes walkthrough and 20…
shai-almog Sep 2, 2026
b1f80df
Copyright headers for the two Windows wrappers, and duplicate include…
shai-almog Sep 2, 2026
cce4454
Refuse a conditional include at every depth, not only in the manifest
shai-almog Sep 2, 2026
aa6af63
A typo in the authority hid behind a correct hostname
shai-almog Sep 2, 2026
41c3133
A listing hides content from the reader, not directives from the prep…
shai-almog Sep 2, 2026
5e79881
The DNS root dot walked straight past the same-site checks
shai-almog Sep 2, 2026
b50b795
Read the fallback routes from the Function, and close the authority f…
shai-almog Sep 2, 2026
2a25ea8
An inline directive is content, and an attribute-built URL is unreadable
shai-almog Sep 2, 2026
2cc2941
Three Javadoc links pointed at a package that is spelt lowercase
shai-almog Sep 2, 2026
50cf047
The Javadoc index documented a package the build deliberately removes
shai-almog Sep 2, 2026
21e5d5c
Javadoc documents public types, and the index was recording every sou…
shai-almog Sep 2, 2026
4c34787
An uppercase scheme was invisible, and the two slash rules had drifte…
shai-almog Sep 2, 2026
6c1c945
A nested type was taken on trust, and an include hid inside an inline…
shai-almog Sep 2, 2026
db15d73
An invented middle level passed, and xref: was a third spelling nobod…
shai-almog Sep 2, 2026
c3ebc41
The publication-date fix was inert: the parser never stored the keys
shai-almog Sep 2, 2026
a1fa03d
Real nesting, real timestamps, and a comment stripper that ate braces
shai-almog Sep 2, 2026
a706801
The self-link rule existed on one branch and not the other
shai-almog Sep 2, 2026
3e7b4a5
An inline comment became part of the value, and href tolerated no spaces
shai-almog Sep 2, 2026
6655675
A nested annotation was rejected, and an attributed include was invis…
shai-almog Sep 2, 2026
ab628e1
Thirty-five Javadoc anchors used a form javadoc stopped emitting year…
shai-almog Sep 2, 2026
e7bb4ee
An unquoted href value is still an href
shai-almog Sep 2, 2026
d3ba948
The array brackets I added last round broke two links, and aliases ke…
shai-almog Sep 2, 2026
92a9a91
A macro target is not prose, and a chapter has one place in the book
shai-almog Sep 2, 2026
e54e3f2
class-use pages are not generated, so linking to one is not fine
shai-almog Sep 2, 2026
bd3fb14
A reused fragment renders twice, and the CLDC sources trigger nothing
shai-almog Sep 2, 2026
2982652
An anchor decorates the block below it; it is not the block
shai-almog Sep 2, 2026
7cbeee1
Two package pages javadoc never writes, and multiplicity that stopped…
shai-almog Sep 2, 2026
d45e1da
Two leniency hatches that accepted anything shaped vaguely right
shai-almog Sep 2, 2026
102bd45
A wildcard declares a slashed route just as a literal one does
shai-almog Sep 2, 2026
79cb5e9
Rejecting javadoc's asset directories rather than enumerating them
shai-almog Sep 2, 2026
abdb22b
A titled listing is a listing, and a figure was never the promised code
shai-almog Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 46 additions & 19 deletions .github/workflows/developer-guide-docs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,8 +50,13 @@ jobs:
docs:
- 'docs/developer-guide/**'
- 'docs/demos/common/src/main/snippets/**'
- 'scripts/developer-guide/migrate-inline-guide-snippets.py'
- 'scripts/developer-guide/validate-guide-snippets.py'
# The whole directory, not two named files. on.pull_request.paths
# already triggers on scripts/developer-guide/**, so a change to any
# other script here started the workflow while leaving this filter
# false -- which skipped the very steps that script governs. A
# change to compare-screenshots.py could have merged without the
# screenshot check ever running it.
- 'scripts/developer-guide/**'
- '.github/workflows/developer-guide-docs.yml'
# Triggering the workflow is not enough on its own: the HTML and PDF
# build and the steps beside it are gated on this filter, so the
Expand DownExpand Up@@ -122,6 +127,7 @@ jobs:
run: |
set -euo pipefail
GENERATED_DIR="$RUNNER_TEMP/pre-advanced-theming-screenshots"
echo "GUIDE_GENERATED_SCREENSHOTS=$GENERATED_DIR" >> "$GITHUB_ENV"
rm -rf "$GENERATED_DIR"
mkdir -p "$GENERATED_DIR"
xvfb-run -a mvn -B -ntp \
Expand All@@ -131,23 +137,25 @@ jobs:
-P guide-screenshot-generator \
-Dguide.screenshot.output="$GENERATED_DIR" \
verify
COUNT="$(find "$GENERATED_DIR" -maxdepth 1 -type f -name '*.png' | wc -l | tr -d ' ')"
if [ "$COUNT" != "24" ]; then
echo "::error::Expected 24 generated pre-Advanced Theming screenshots, found $COUNT"
exit 1
fi
for generated in "$GENERATED_DIR"/*.png; do
name="$(basename "$generated")"
committed="docs/developer-guide/img/$name"
if [ ! -f "$committed" ]; then
echo "::error::Generated screenshot has no committed counterpart: $name"
exit 1
fi
if ! cmp -s "$generated" "$committed"; then
echo "::error::Committed screenshot is stale: $name"
exit 1
fi
done
pip install --user --quiet Pillow
# Byte equality is still the rule; a figure only gets a bounded
# difference if it carries a .tolerance sidecar saying why.
python3 scripts/developer-guide/compare-screenshots.py \
--generated "$GENERATED_DIR" \
--committed docs/developer-guide/img \
--expected-count 24

# When the byte compare fails, the message names the file but not what it
# actually rendered, which leaves no way to tell a real regression from an
# environment difference. Publishing what this runner produced makes the
# two distinguishable without adding a debugging round trip to CI.
- name: Upload generated screenshots when they do not match
if: failure() && env.GUIDE_GENERATED_SCREENSHOTS != ''
uses: actions/upload-artifact@v7
with:
name: guide-generated-screenshots
path: ${{ env.GUIDE_GENERATED_SCREENSHOTS }}
if-no-files-found: warn

- name: Verify developer guide images are referenced
if: github.event_name != 'pull_request' || steps.changes.outputs.docs == 'true' || steps.changes.outputs.demos == 'true' || steps.changes.outputs.workflow == 'true'
Expand DownExpand Up@@ -271,6 +279,25 @@ jobs:
echo "Asciidoctor exited with status $STATUS — the final quality-gate step will fail the build." >&2
fi

# These four cover defects the gates above are structurally blind to: a
# chapter swallowed by the one before it, a cross-reference that resolves
# to nothing, prose that promises a code block which is not there, and a
# link the website does not serve. Asciidoctor reports none of them, and
# every one of them shipped. The structure and cross-reference checks
# render the book, so they must run after the Asciidoctor install.
- name: Check developer guide structure, cross-references, code blocks and links
run: |
set -euo pipefail
python3 scripts/developer-guide/check-guide-structure.py
python3 scripts/developer-guide/check-guide-xrefs.py
python3 scripts/developer-guide/check-missing-code-blocks.py
# Note this only catches a link the GUIDE breaks. When the website
# moves the route instead -- a page renamed, deleted or re-slugged --
# the same script runs from website-docs.yml, which triggers on
# docs/website/**. Putting those paths here would run this whole job
# (maven install, demo build, screenshots) for every blog post.
python3 scripts/developer-guide/check-guide-links.py

- name: Build Developer Guide HTML and PDF
if: github.event_name != 'pull_request' || steps.changes.outputs.docs == 'true' || steps.changes.outputs.demos == 'true' || steps.changes.outputs.workflow == 'true'
run: |
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/website-docs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,10 @@ on:
- 'vm/ByteCodeTranslator/**'
- 'vm/JavaAPI/**'
- 'CodenameOne/src/**'
# The other root build_javadocs.sh generates from, and the other one
# check-guide-links.py indexes. Without it, deleting a linked CLDC type
# merges unvalidated and the next scheduled build is where it surfaces.
- 'Ports/CLDC11/src/**'
# The developer guide this site renders includes a build hint table that is
# generated rather than committed, so a change to the catalog or to the
# renderer changes the published page without touching docs/. The
Expand All@@ -46,6 +50,10 @@ on:
- 'vm/ByteCodeTranslator/**'
- 'vm/JavaAPI/**'
- 'CodenameOne/src/**'
# The other root build_javadocs.sh generates from, and the other one
# check-guide-links.py indexes. Without it, deleting a linked CLDC type
# merges unvalidated and the next scheduled build is where it surfaces.
- 'Ports/CLDC11/src/**'
# As above: the build hint table is generated, not committed.
- 'maven/build-hint-catalog/**'
- 'maven/build-hint-tools/**'
Expand DownExpand Up@@ -277,6 +285,17 @@ jobs:
--root-dir public
public/**/*.html

# The developer guide links into this site, so a page renamed, deleted or
# re-slugged here breaks the guide. The check lives in THIS workflow rather
# than in developer-guide-docs.yml because it is the route data that
# changed: widening that workflow's paths to docs/website/** would run the
# full guide build -- maven install, demo build, screenshot generation --
# on every blog post the daily publisher pushes, to run one Python script.
# This job already triggers on docs/website/**, and the check needs nothing
# but a checkout.
- name: Check developer guide links against the routes this site serves
run: python3 scripts/developer-guide/check-guide-links.py
Comment thread
shai-almog marked this conversation as resolved.

- name: Reject absolute codenameone.com links
run: |
set -euo pipefail
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,41 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codenameone.developerguide.screenshots;

import com.codename1.components.SpanLabel;
import com.codenameone.developerguide.snippets.generated.BasicsJava034Snippet;
import com.codename1.ui.Button;
import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.CN;
import com.codename1.ui.FontImage;
import com.codename1.ui.Font;
import com.codename1.ui.Form;
import com.codename1.ui.Graphics;
import com.codename1.ui.Image;
import com.codename1.ui.Label;
import com.codename1.ui.TextArea;
import com.codename1.ui.TextField;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.BoxLayout;
Expand DownExpand Up@@ -40,10 +64,33 @@ public final class PreAdvancedThemingScreenshots {
private static final int BLUE = 0x0b57d0;
private static final int GREEN = 0x06a806;
private static final int WHITE = 0xffffff;
private static final Font TITLE_FONT = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_LARGE)
.derive(35, Font.STYLE_PLAIN);
private static final Font BLOCK_FONT = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_LARGE)
.derive(24, Font.STYLE_PLAIN);
private static final Font TITLE_FONT = screenshotFont(35);
private static final Font BLOCK_FONT = screenshotFont(24);
/// Sized to match the height the theme's own default font had (13px), so
/// pinning the face on components the block styling does not touch keeps
/// their layout exactly where it was.
private static final Font FIELD_FONT = screenshotFont(11);

/// Loads a figure font from the port's bundled Roboto rather than from the host.
///
/// `Font.createSystemFont` resolves through `JavaSEPort.fontFaceSystem`, which is
/// "Arial" on macOS and Linux alike. Arial exists on a developer's Mac and not on a
/// stock CI runner, so AWT silently substitutes a different face and every glyph in
/// every figure changes -- which is why none of these screenshots could be
/// regenerated outside CI and byte-compared against what was committed. The
/// `native:` scheme reads `/com/codename1/impl/javase/Roboto-*.ttf` off the
/// classpath, so the result does not depend on what the machine happens to have
/// installed. It is also what this project's font rule requires everywhere.
private static Font screenshotFont(int pixelSize) {
Font font = Font.createTrueTypeFont("native:MainRegular", "native:MainRegular");
if (font == null) {
// Falling back to a host font would quietly restore the very
// non-determinism this exists to remove, so refuse instead.
throw new IllegalStateException(
"the native font scheme is unavailable, so figures would render with a host font");
}
return font.derive(pixelSize, Font.STYLE_PLAIN);
}

private PreAdvancedThemingScreenshots() {
}
Expand All@@ -53,6 +100,32 @@ public interface ScreenshotSink {
}

public static void generate(ScreenshotSink sink) throws IOException {
// JavaSEPort.loadTrueTypeFont has an earlier branch for native: fonts:
// when isIOS is set -- which loadSkinFile does for any skin whose
// systemFontFamily contains "helvetica" -- it resolves to the first
// INSTALLED SF or Helvetica family and never reaches the bundled Roboto.
// This generator never loads a skin, so that branch is not taken, and the
// measurement agrees: figures rendered on a Mac match the Linux runner
// byte for byte, which could not happen if one side were using Helvetica
// Neue. Guard it anyway, because a future change that loads a skin here
// would put the host's fonts back into the output without any other
// symptom.
String platform = Display.getInstance().getPlatformName();
if ("ios".equals(platform)) {
throw new IllegalStateException(
"an iOS skin is active, so native: fonts would resolve to installed "
+ "system faces instead of the bundled ones and these figures would "
+ "stop being reproducible off this machine");
}

// MigLayout picks its default gaps from PlatformDefaults, which reads
// System.getProperty("os.name") and returns MAC_OSX, GNOME or WINDOWS_XP.
// The gaps differ per platform, so mig-layout.png came out with macOS
// spacing on a Mac and GNOME spacing on the Linux runner -- a 12.8% pixel
// difference that has nothing to do with fonts. Pin it so the figure shows
// the same thing wherever it is generated.
com.codename1.ui.layouts.mig.PlatformDefaults.setPlatform(
com.codename1.ui.layouts.mig.PlatformDefaults.GNOME);
write(sink, "flow-layout.png", flow("Flow Layout", Component.LEFT, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
write(sink, "flow-layout-center.png", flow("Flow Layout", Component.CENTER, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
write(sink, "flow-layout-right.png", flow("Flow Layout", Component.RIGHT, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
Expand DownExpand Up@@ -328,6 +401,22 @@ private static void applyScreenshotStyle(Form form) {
private static void applyBlockStyleToContent(Container container) {
for (int i = 0; i < container.getComponentCount(); i++) {
Component component = container.getComponentAt(i);
// Pin the font on EVERY component rather than on the types that also
// get block colours. Anything left on the theme's default font
// resolves through the host, and that is what made
// guibuilder-2-insets-3.png -- the only figure containing a
// TextField -- differ between a Mac and CI while the other 23
// matched byte for byte. Enumerating the types that carry text
// would leave the next one to be added broken in the same way.
component.getAllStyles().setFont(FIELD_FONT);
if (component instanceof TextArea) {
// The hint is painted by a Label that is not in the component
// tree, so the walk above never reaches it.
Label hint = ((TextArea) component).getHintLabel();
if (hint != null) {
hint.getAllStyles().setFont(FIELD_FONT);
}
}
if (component instanceof Label || component instanceof Button) {
styleBlock(component);
}
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
// Generated from docs/developer-guide source blocks. Edit the guide snippets here, not inline.

// tag::working-with-windows-bash-001[]
mvn -pl common package -Dcodename1.platform=windows -Dcodename1.buildTarget=windows-device
mvn package -Dcodename1.platform=win -Dcodename1.buildTarget=windows-device
// end::working-with-windows-bash-001[]
2 changes: 1 addition & 1 deletion docs/developer-guide/Advanced-Theming.asciidoc
Original file line numberDiff line numberDiff line change
Expand Up@@ -728,7 +728,7 @@ Will set the foreground color of the https://www.codenameone.com/javadoc/com/cod

When a Codename One https://www.codenameone.com/javadoc/com/codename1/ui/Component.html[Component] is instantiated it requests a https://www.codenameone.com/javadoc/com/codename1/ui/plaf/Style.html[Style] object from the https://www.codenameone.com/javadoc/com/codename1/ui/plaf/UIManager.html[UIManager] class. The `Style` object is based on the settings within the theme and can be modified through code or by using the theme.

You can replace the theme dynamically in runtime and refresh the styles assigned to the various components using the https://www.codenameone.com/javadoc/com/codename1/ui/Component.html#refreshTheme--[refreshTheme()] method.
You can replace the theme dynamically in runtime and refresh the styles assigned to the various components using the https://www.codenameone.com/javadoc/com/codename1/ui/Component.html#refreshTheme()[refreshTheme()] method.

NOTE: It's a common mistake to invoke `refreshTheme()` without actually changing the theme. You see developers doing it when all they need is a `repaint()` or `revalidate()`. Since `refreshTheme()` is **** expensive recommend that you don't use it unless you need to...

Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
684849a
Gates for the guide defects Asciidoctor cannot see
shai-almog Sep 2, 2026
eebe28f
Bank the four holes the constraints chapter no longer has
shai-almog Sep 2, 2026
2555762
An unrelated subsection must not stand in for a swallowed chapter
shai-almog Sep 2, 2026
877a371
The ifeval branch of the conditional test could never match
shai-almog Sep 2, 2026
2a08ef7
Record why commented-out URLs are checked like any other
shai-almog Sep 2, 2026
f69f47c
Four links left the book to re-enter it, and nothing checked the anchor
shai-almog Sep 2, 2026
82ed7bf
Two silent blind spots in the guards become loud refusals
shai-almog Sep 2, 2026
520ced0
Check what swallows a heading, not whether its title turns up somewhere
shai-almog Sep 2, 2026
9ffb68c
The slash rule rejected routes the site declares, and Hugo could move…
shai-almog Sep 2, 2026
4f6f2a5
The PDF branch was never rendered, and an admonition counted as a lis…
shai-almog Sep 2, 2026
bbc2ee5
Three links from the old wiki went nowhere, and a directive is not a …
shai-almog Sep 2, 2026
9a508aa
A Cloudflare function serves /files and /demos, and the PDF surrogate…
shai-almog Sep 2, 2026
9d351ff
A five-dash block is still a literal block, and both scanners read in…
shai-almog Sep 2, 2026
425e749
Guide figures render with a bundled font instead of the host's (#5668)
shai-almog Sep 2, 2026
94a3b45
A comment block is not content, and img/ really does ship beside the …
shai-almog Sep 2, 2026
773297b
The packaging exemption trusted the repository, not the published output
shai-almog Sep 2, 2026
93b0518
The Introduction taught a dead IDE menu, an iTunes walkthrough and 20…
shai-almog Sep 2, 2026
b1f80df
Copyright headers for the two Windows wrappers, and duplicate include…
shai-almog Sep 2, 2026
cce4454
Refuse a conditional include at every depth, not only in the manifest
shai-almog Sep 2, 2026
aa6af63
A typo in the authority hid behind a correct hostname
shai-almog Sep 2, 2026
41c3133
A listing hides content from the reader, not directives from the prep…
shai-almog Sep 2, 2026
5e79881
The DNS root dot walked straight past the same-site checks
shai-almog Sep 2, 2026
b50b795
Read the fallback routes from the Function, and close the authority f…
shai-almog Sep 2, 2026
2a25ea8
An inline directive is content, and an attribute-built URL is unreadable
shai-almog Sep 2, 2026
2cc2941
Three Javadoc links pointed at a package that is spelt lowercase
shai-almog Sep 2, 2026
50cf047
The Javadoc index documented a package the build deliberately removes
shai-almog Sep 2, 2026
21e5d5c
Javadoc documents public types, and the index was recording every sou…
shai-almog Sep 2, 2026
4c34787
An uppercase scheme was invisible, and the two slash rules had drifte…
shai-almog Sep 2, 2026
6c1c945
A nested type was taken on trust, and an include hid inside an inline…
shai-almog Sep 2, 2026
db15d73
An invented middle level passed, and xref: was a third spelling nobod…
shai-almog Sep 2, 2026
c3ebc41
The publication-date fix was inert: the parser never stored the keys
shai-almog Sep 2, 2026
a1fa03d
Real nesting, real timestamps, and a comment stripper that ate braces
shai-almog Sep 2, 2026
a706801
The self-link rule existed on one branch and not the other
shai-almog Sep 2, 2026
3e7b4a5
An inline comment became part of the value, and href tolerated no spaces
shai-almog Sep 2, 2026
6655675
A nested annotation was rejected, and an attributed include was invis…
shai-almog Sep 2, 2026
ab628e1
Thirty-five Javadoc anchors used a form javadoc stopped emitting year…
shai-almog Sep 2, 2026
e7bb4ee
An unquoted href value is still an href
shai-almog Sep 2, 2026
d3ba948
The array brackets I added last round broke two links, and aliases ke…
shai-almog Sep 2, 2026
92a9a91
A macro target is not prose, and a chapter has one place in the book
shai-almog Sep 2, 2026
e54e3f2
class-use pages are not generated, so linking to one is not fine
shai-almog Sep 2, 2026
bd3fb14
A reused fragment renders twice, and the CLDC sources trigger nothing
shai-almog Sep 2, 2026
2982652
An anchor decorates the block below it; it is not the block
shai-almog Sep 2, 2026
7cbeee1
Two package pages javadoc never writes, and multiplicity that stopped…
shai-almog Sep 2, 2026
d45e1da
Two leniency hatches that accepted anything shaped vaguely right
shai-almog Sep 2, 2026
102bd45
A wildcard declares a slashed route just as a literal one does
shai-almog Sep 2, 2026
79cb5e9
Rejecting javadoc's asset directories rather than enumerating them
shai-almog Sep 2, 2026
abdb22b
A titled listing is a listing, and a figure was never the promised code
shai-almog Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 46 additions & 19 deletions .github/workflows/developer-guide-docs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,8 +50,13 @@ jobs:
docs:
- 'docs/developer-guide/**'
- 'docs/demos/common/src/main/snippets/**'
- 'scripts/developer-guide/migrate-inline-guide-snippets.py'
- 'scripts/developer-guide/validate-guide-snippets.py'
# The whole directory, not two named files. on.pull_request.paths
# already triggers on scripts/developer-guide/**, so a change to any
# other script here started the workflow while leaving this filter
# false -- which skipped the very steps that script governs. A
# change to compare-screenshots.py could have merged without the
# screenshot check ever running it.
- 'scripts/developer-guide/**'
- '.github/workflows/developer-guide-docs.yml'
# Triggering the workflow is not enough on its own: the HTML and PDF
# build and the steps beside it are gated on this filter, so the
Expand DownExpand Up@@ -122,6 +127,7 @@ jobs:
run: |
set -euo pipefail
GENERATED_DIR="$RUNNER_TEMP/pre-advanced-theming-screenshots"
echo "GUIDE_GENERATED_SCREENSHOTS=$GENERATED_DIR" >> "$GITHUB_ENV"
rm -rf "$GENERATED_DIR"
mkdir -p "$GENERATED_DIR"
xvfb-run -a mvn -B -ntp \
Expand All@@ -131,23 +137,25 @@ jobs:
-P guide-screenshot-generator \
-Dguide.screenshot.output="$GENERATED_DIR" \
verify
COUNT="$(find "$GENERATED_DIR" -maxdepth 1 -type f -name '*.png' | wc -l | tr -d ' ')"
if [ "$COUNT" != "24" ]; then
echo "::error::Expected 24 generated pre-Advanced Theming screenshots, found $COUNT"
exit 1
fi
for generated in "$GENERATED_DIR"/*.png; do
name="$(basename "$generated")"
committed="docs/developer-guide/img/$name"
if [ ! -f "$committed" ]; then
echo "::error::Generated screenshot has no committed counterpart: $name"
exit 1
fi
if ! cmp -s "$generated" "$committed"; then
echo "::error::Committed screenshot is stale: $name"
exit 1
fi
done
pip install --user --quiet Pillow
# Byte equality is still the rule; a figure only gets a bounded
# difference if it carries a .tolerance sidecar saying why.
python3 scripts/developer-guide/compare-screenshots.py \
--generated "$GENERATED_DIR" \
--committed docs/developer-guide/img \
--expected-count 24

# When the byte compare fails, the message names the file but not what it
# actually rendered, which leaves no way to tell a real regression from an
# environment difference. Publishing what this runner produced makes the
# two distinguishable without adding a debugging round trip to CI.
- name: Upload generated screenshots when they do not match
if: failure() && env.GUIDE_GENERATED_SCREENSHOTS != ''
uses: actions/upload-artifact@v7
with:
name: guide-generated-screenshots
path: ${{ env.GUIDE_GENERATED_SCREENSHOTS }}
if-no-files-found: warn

- name: Verify developer guide images are referenced
if: github.event_name != 'pull_request' || steps.changes.outputs.docs == 'true' || steps.changes.outputs.demos == 'true' || steps.changes.outputs.workflow == 'true'
Expand DownExpand Up@@ -271,6 +279,25 @@ jobs:
echo "Asciidoctor exited with status $STATUS — the final quality-gate step will fail the build." >&2
fi

# These four cover defects the gates above are structurally blind to: a
# chapter swallowed by the one before it, a cross-reference that resolves
# to nothing, prose that promises a code block which is not there, and a
# link the website does not serve. Asciidoctor reports none of them, and
# every one of them shipped. The structure and cross-reference checks
# render the book, so they must run after the Asciidoctor install.
- name: Check developer guide structure, cross-references, code blocks and links
run: |
set -euo pipefail
python3 scripts/developer-guide/check-guide-structure.py
python3 scripts/developer-guide/check-guide-xrefs.py
python3 scripts/developer-guide/check-missing-code-blocks.py
# Note this only catches a link the GUIDE breaks. When the website
# moves the route instead -- a page renamed, deleted or re-slugged --
# the same script runs from website-docs.yml, which triggers on
# docs/website/**. Putting those paths here would run this whole job
# (maven install, demo build, screenshots) for every blog post.
python3 scripts/developer-guide/check-guide-links.py

- name: Build Developer Guide HTML and PDF
if: github.event_name != 'pull_request' || steps.changes.outputs.docs == 'true' || steps.changes.outputs.demos == 'true' || steps.changes.outputs.workflow == 'true'
run: |
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/website-docs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,10 @@ on:
- 'vm/ByteCodeTranslator/**'
- 'vm/JavaAPI/**'
- 'CodenameOne/src/**'
# The other root build_javadocs.sh generates from, and the other one
# check-guide-links.py indexes. Without it, deleting a linked CLDC type
# merges unvalidated and the next scheduled build is where it surfaces.
- 'Ports/CLDC11/src/**'
# The developer guide this site renders includes a build hint table that is
# generated rather than committed, so a change to the catalog or to the
# renderer changes the published page without touching docs/. The
Expand All@@ -46,6 +50,10 @@ on:
- 'vm/ByteCodeTranslator/**'
- 'vm/JavaAPI/**'
- 'CodenameOne/src/**'
# The other root build_javadocs.sh generates from, and the other one
# check-guide-links.py indexes. Without it, deleting a linked CLDC type
# merges unvalidated and the next scheduled build is where it surfaces.
- 'Ports/CLDC11/src/**'
# As above: the build hint table is generated, not committed.
- 'maven/build-hint-catalog/**'
- 'maven/build-hint-tools/**'
Expand DownExpand Up@@ -277,6 +285,17 @@ jobs:
--root-dir public
public/**/*.html

# The developer guide links into this site, so a page renamed, deleted or
# re-slugged here breaks the guide. The check lives in THIS workflow rather
# than in developer-guide-docs.yml because it is the route data that
# changed: widening that workflow's paths to docs/website/** would run the
# full guide build -- maven install, demo build, screenshot generation --
# on every blog post the daily publisher pushes, to run one Python script.
# This job already triggers on docs/website/**, and the check needs nothing
# but a checkout.
- name: Check developer guide links against the routes this site serves
run: python3 scripts/developer-guide/check-guide-links.py
Comment thread
shai-almog marked this conversation as resolved.

- name: Reject absolute codenameone.com links
run: |
set -euo pipefail
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,41 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codenameone.developerguide.screenshots;

import com.codename1.components.SpanLabel;
import com.codenameone.developerguide.snippets.generated.BasicsJava034Snippet;
import com.codename1.ui.Button;
import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.CN;
import com.codename1.ui.FontImage;
import com.codename1.ui.Font;
import com.codename1.ui.Form;
import com.codename1.ui.Graphics;
import com.codename1.ui.Image;
import com.codename1.ui.Label;
import com.codename1.ui.TextArea;
import com.codename1.ui.TextField;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.BoxLayout;
Expand DownExpand Up@@ -40,10 +64,33 @@ public final class PreAdvancedThemingScreenshots {
private static final int BLUE = 0x0b57d0;
private static final int GREEN = 0x06a806;
private static final int WHITE = 0xffffff;
private static final Font TITLE_FONT = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_LARGE)
.derive(35, Font.STYLE_PLAIN);
private static final Font BLOCK_FONT = Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_LARGE)
.derive(24, Font.STYLE_PLAIN);
private static final Font TITLE_FONT = screenshotFont(35);
private static final Font BLOCK_FONT = screenshotFont(24);
/// Sized to match the height the theme's own default font had (13px), so
/// pinning the face on components the block styling does not touch keeps
/// their layout exactly where it was.
private static final Font FIELD_FONT = screenshotFont(11);

/// Loads a figure font from the port's bundled Roboto rather than from the host.
///
/// `Font.createSystemFont` resolves through `JavaSEPort.fontFaceSystem`, which is
/// "Arial" on macOS and Linux alike. Arial exists on a developer's Mac and not on a
/// stock CI runner, so AWT silently substitutes a different face and every glyph in
/// every figure changes -- which is why none of these screenshots could be
/// regenerated outside CI and byte-compared against what was committed. The
/// `native:` scheme reads `/com/codename1/impl/javase/Roboto-*.ttf` off the
/// classpath, so the result does not depend on what the machine happens to have
/// installed. It is also what this project's font rule requires everywhere.
private static Font screenshotFont(int pixelSize) {
Font font = Font.createTrueTypeFont("native:MainRegular", "native:MainRegular");
if (font == null) {
// Falling back to a host font would quietly restore the very
// non-determinism this exists to remove, so refuse instead.
throw new IllegalStateException(
"the native font scheme is unavailable, so figures would render with a host font");
}
return font.derive(pixelSize, Font.STYLE_PLAIN);
}

private PreAdvancedThemingScreenshots() {
}
Expand All@@ -53,6 +100,32 @@ public interface ScreenshotSink {
}

public static void generate(ScreenshotSink sink) throws IOException {
// JavaSEPort.loadTrueTypeFont has an earlier branch for native: fonts:
// when isIOS is set -- which loadSkinFile does for any skin whose
// systemFontFamily contains "helvetica" -- it resolves to the first
// INSTALLED SF or Helvetica family and never reaches the bundled Roboto.
// This generator never loads a skin, so that branch is not taken, and the
// measurement agrees: figures rendered on a Mac match the Linux runner
// byte for byte, which could not happen if one side were using Helvetica
// Neue. Guard it anyway, because a future change that loads a skin here
// would put the host's fonts back into the output without any other
// symptom.
String platform = Display.getInstance().getPlatformName();
if ("ios".equals(platform)) {
throw new IllegalStateException(
"an iOS skin is active, so native: fonts would resolve to installed "
+ "system faces instead of the bundled ones and these figures would "
+ "stop being reproducible off this machine");
}

// MigLayout picks its default gaps from PlatformDefaults, which reads
// System.getProperty("os.name") and returns MAC_OSX, GNOME or WINDOWS_XP.
// The gaps differ per platform, so mig-layout.png came out with macOS
// spacing on a Mac and GNOME spacing on the Linux runner -- a 12.8% pixel
// difference that has nothing to do with fonts. Pin it so the figure shows
// the same thing wherever it is generated.
com.codename1.ui.layouts.mig.PlatformDefaults.setPlatform(
com.codename1.ui.layouts.mig.PlatformDefaults.GNOME);
write(sink, "flow-layout.png", flow("Flow Layout", Component.LEFT, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
write(sink, "flow-layout-center.png", flow("Flow Layout", Component.CENTER, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
write(sink, "flow-layout-right.png", flow("Flow Layout", Component.RIGHT, Component.TOP), PORTRAIT_WIDTH, PORTRAIT_HEIGHT);
Expand DownExpand Up@@ -328,6 +401,22 @@ private static void applyScreenshotStyle(Form form) {
private static void applyBlockStyleToContent(Container container) {
for (int i = 0; i < container.getComponentCount(); i++) {
Component component = container.getComponentAt(i);
// Pin the font on EVERY component rather than on the types that also
// get block colours. Anything left on the theme's default font
// resolves through the host, and that is what made
// guibuilder-2-insets-3.png -- the only figure containing a
// TextField -- differ between a Mac and CI while the other 23
// matched byte for byte. Enumerating the types that carry text
// would leave the next one to be added broken in the same way.
component.getAllStyles().setFont(FIELD_FONT);
if (component instanceof TextArea) {
// The hint is painted by a Label that is not in the component
// tree, so the walk above never reaches it.
Label hint = ((TextArea) component).getHintLabel();
if (hint != null) {
hint.getAllStyles().setFont(FIELD_FONT);
}
}
if (component instanceof Label || component instanceof Button) {
styleBlock(component);
}
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
// Generated from docs/developer-guide source blocks. Edit the guide snippets here, not inline.

// tag::working-with-windows-bash-001[]
mvn -pl common package -Dcodename1.platform=windows -Dcodename1.buildTarget=windows-device
mvn package -Dcodename1.platform=win -Dcodename1.buildTarget=windows-device
// end::working-with-windows-bash-001[]
2 changes: 1 addition & 1 deletion docs/developer-guide/Advanced-Theming.asciidoc
Original file line numberDiff line numberDiff line change
Expand Up@@ -728,7 +728,7 @@ Will set the foreground color of the https://www.codenameone.com/javadoc/com/cod

When a Codename One https://www.codenameone.com/javadoc/com/codename1/ui/Component.html[Component] is instantiated it requests a https://www.codenameone.com/javadoc/com/codename1/ui/plaf/Style.html[Style] object from the https://www.codenameone.com/javadoc/com/codename1/ui/plaf/UIManager.html[UIManager] class. The `Style` object is based on the settings within the theme and can be modified through code or by using the theme.

You can replace the theme dynamically in runtime and refresh the styles assigned to the various components using the https://www.codenameone.com/javadoc/com/codename1/ui/Component.html#refreshTheme--[refreshTheme()] method.
You can replace the theme dynamically in runtime and refresh the styles assigned to the various components using the https://www.codenameone.com/javadoc/com/codename1/ui/Component.html#refreshTheme()[refreshTheme()] method.

NOTE: It's a common mistake to invoke `refreshTheme()` without actually changing the theme. You see developers doing it when all they need is a `repaint()` or `revalidate()`. Since `refreshTheme()` is **** expensive recommend that you don't use it unless you need to...

Expand Down
Loading
Loading