Skip to content

feat(text): Shape complex single line UI text - #3231

Open
OmarAglan wants to merge 1 commit into
TheSuperHackers:mainfrom
OmarAglan:feature/arabic-ui-text-shaping
Open

feat(text): Shape complex single line UI text#3231
OmarAglan wants to merge 1 commit into
TheSuperHackers:mainfrom
OmarAglan:feature/arabic-ui-text-shaping

Conversation

@OmarAglan

Copy link
Copy Markdown

This change adds contextual shaping and bidirectional ordering for complex single-line UI text in Render2DSentenceClass.

The sentence renderer normally processes text one WCHAR at a time. This prevents Arabic letters from using their contextual forms and does not preserve the correct visual order of mixed Arabic and Latin runs. Complex strings are now measured and rendered as one run with Windows Uniscribe before the resulting pixels are copied into the existing A4R4G4B4 sentence textures.

Plain Latin strings continue to use the existing per-character rendering path. Wrapped, multiline, hot-key parsed, and monospaced text remain unchanged and can be handled separately in later work.

The implementation uses the Windows usp10 library because the existing font path is based on GDI HFONT objects. This keeps the change within the current renderer and avoids introducing a broader DirectWrite backend change.

The main-menu test confirms correct contextual shaping, bidirectional ordering, digit ordering, centering, and clipping.

Before

sshot_20260828_014020_617

After

sshot_20260828_015936_767

The temporary main-menu test strings and diagnostic code are not included in this pull request.

The change was validated with:

  • Generals Release build
  • Zero Hour Release build
  • git diff --check
  • Runtime testing in Zero Hour

The implementation was developed with AI assistance, then reviewed and simplified against the nearby renderer code. The final diff was manually reviewed, and the rendering behavior was manually tested in game.

@OmarAglan
OmarAglan marked this pull request as ready for review August 28, 2026 10:21
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Shape complex single-line UI text with Uniscribe

✨ Enhancement ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Shapes and bidirectionally orders eligible complex single-line UI text with Windows Uniscribe.
• Preserves legacy rendering for Latin, wrapped, multiline, hot-key, and monospaced text.
• Copies shaped runs into existing sentence textures with chunking and clipping support.
Diagram

graph TD
  A["UI text"] --> B["Sentence renderer"] --> C{"Complex eligible?"}
  C -->|Yes| D["Uniscribe shaping"] --> E["GDI bitmap"] --> F["A4R4G4B4 textures"]
  C -->|No| G["Legacy glyph path"] --> F
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Migrate text rendering to DirectWrite
  • ➕ Uses a newer shaping and text-layout stack.
  • ➕ Provides a stronger foundation for multiline and advanced typography support.
  • ➖ Requires replacing or bridging the existing GDI HFONT pipeline.
  • ➖ Greatly expands scope, integration risk, and validation requirements.

Recommendation: Keep the Uniscribe approach for this PR because it integrates directly with the renderer's existing GDI fonts and A4R4G4B4 textures while preserving established behavior for unsupported modes. A DirectWrite migration is strategically stronger only as a separate, broader renderer project.

Files changed (3) +268 / -3

Enhancement (2) +267 / -3
render2dsentence.cppAdd complex single-line shaping and texture rasterization +260/-2

Add complex single-line shaping and texture rasterization

• Detects eligible complex text, measures and renders it as one Uniscribe run, then copies shaped pixels from a temporary GDI bitmap into existing sentence surfaces. Splits wide output across texture chunks, uses measured run height, supports alternate Unicode fonts, and falls back to the legacy path when the run is unsupported or shaping fails.

Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp

render2dsentence.hExpose complex-text font and sentence helpers +7/-1

Expose complex-text font and sentence helpers

• Declares font-level complexity detection, run measurement, and rasterization APIs. Adds sentence-level eligibility and build helpers and allows recorded chunks to use the shaped run height.

Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.h

Other (1) +1 / -0
CMakeLists.txtLink the WW3D2 renderer against Uniscribe +1/-0

Link the WW3D2 renderer against Uniscribe

• Adds the Windows usp10 library required by the new complex-script analysis, shaping, and output calls.

Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 28, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Mixed text uses fallback font ✓ Resolved 🐞 Bug ≡ Correctness
Description
The complex path selects AlternateUnicodeFont for the entire string, so Latin characters in mixed
Arabic/Latin UI text no longer use the requested primary font. Existing font behavior delegates only
non-ASCII characters to the configured Unicode fallback, so this changes Latin styling and metrics
whenever the two fonts differ.
Code

Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[R1418-1419]

+	FontCharsClass *render_font = AlternateUnicodeFont && this != AlternateUnicodeFont ?
+		AlternateUnicodeFont : this;
Evidence
The normal font lookup keeps characters below 256 in the primary font and delegates only non-ASCII
characters to the alternate font. The new code instead analyzes and outputs the whole string using
the alternate font's DC/HFONT, while game font loading identifies that font specifically as the
Unicode fallback.

Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[1312-1319]
Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[1418-1427]
Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[1492-1539]
Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp[80-99]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Complex mixed-script strings are measured and rendered entirely with `AlternateUnicodeFont`, replacing the requested primary font for Latin runs.
## Issue Context
The existing character path uses the primary font for ASCII and delegates only non-ASCII characters to `AlternateUnicodeFont`. Preserve that division while shaping the complete bidi string, using run-level font selection/fallback consistently for both measurement and rendering.
## Fix Focus Areas
- Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[1418-1427]
- Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[1492-1539]
- Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[1312-1319]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Run rerasterized per chunk ✓ Resolved 🐞 Bug ➹ Performance
Description
Every texture-width chunk calls Blit_Complex_Text, which remeasures, reshapes, allocates a
full-run bitmap, and rasterizes the entire string before copying one slice. Because chunks are
capped by the texture width, rendering cost and allocated pixel work grow quadratically with long
single-line strings and repeat even for ordinary runs wider than one texture.
Code

Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[R679-680]

+		if (!Font->Blit_Complex_Text(text, LockedPtr, LockedStride, TextureOffset.I,
+			TextureOffset.J, source_x, chunk_width))
Evidence
The outer loop advances source_x by at most the available texture width, but each iteration
invokes a helper that recomputes full extents, allocates a text_width-wide bitmap, reruns
Uniscribe analysis, and outputs the complete string. Thus a run split into N chunks performs N
full-run rasterizations rather than one rasterization plus N slice copies.

Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[664-690]
Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[1494-1539]
Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[1542-1552]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The complex string is fully analyzed and rasterized once for every texture chunk, making long-run construction scale quadratically.
## Issue Context
`Build_Complex_Sentence` iterates over texture-sized slices, while `Blit_Complex_Text` recreates a full-width DIB and repeats `ScriptStringAnalyse`/`ScriptStringOut` on every call. Produce the full raster once, then copy each slice into its destination surface.
## Fix Focus Areas
- Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[664-690]
- Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[1494-1552]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can reply 'qodo' on any finding to push back, ask questions, or dig deeper

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp Outdated
Comment thread Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp Outdated
@greptile-apps

greptile-apps Bot commented Aug 28, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds Uniscribe-based shaping and bidirectional ordering for eligible single-line UI text while preserving the legacy path for wrapped, multiline, hot-key, monospaced, and editable strings.

  • Adds complex-text measurement, rasterization, texture chunking, and renderer controls.
  • Integrates the shaping option through both Generals variants and links the required Windows library.
  • The current height admission guard and minimum surface selection resolve the previously reported oversized-text loop, while measurement and rendering now use the same oversized-height fallback decision.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains from the previously reported oversized-text paths.

No blocking failure remains.

Important Files Changed

Filename Overview
Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp Adds the shaping, measurement, rasterization, and bounded texture-copy path; the current guards resolve both previously reported oversized-height failures.
Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.h Extends font and sentence renderer interfaces with complex-text operations and per-renderer enablement.
Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt Propagates the Windows Uniscribe link dependency through the shared WW3D2 interface.
Core/GameEngine/Include/GameClient/DisplayString.h Adds the abstract control used to opt individual display strings out of complex shaping.
Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplayString.cpp Connects full-string width measurement and complex-text enablement to the Generals display-string implementation.
GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplayString.cpp Mirrors the display-string integration for Zero Hour.
Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp Keeps editable Generals text-entry strings on the legacy renderer until shaped caret metrics are supported.
GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp Applies the same editable-text exclusion to Zero Hour.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    Text[UI text] --> Eligible{Eligible single-line complex text?}
    Eligible -- No --> Legacy[Legacy per-character renderer]
    Eligible -- Yes --> Measure[Measure with Uniscribe]
    Measure --> Fits{Height and wrap constraints fit?}
    Fits -- No --> Legacy
    Fits -- Yes --> Rasterize[Rasterize shaped run]
    Rasterize --> Chunk[Copy bounded chunks into sentence textures]
    Chunk --> Render[Render sentence]
Loading

Reviews (4): Last reviewed commit: "feat(text): Shape complex single-line UI..." | Re-trigger Greptile

Comment thread Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp
@tintinhamans

Copy link
Copy Markdown

@codex

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a852d41fbb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp
Comment thread Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp Outdated
@stephanmeesters

Copy link
Copy Markdown

I can't tell from the text and images what the problems were and how this fixes it.

The main-menu test confirms correct contextual shaping, bidirectional ordering, digit ordering, centering, and clipping.

Can you give before and afters of each of these individually?

@OmarAglan

Copy link
Copy Markdown
Author

I can't tell from the text and images what the problems were and how this fixes it.

The main-menu test confirms correct contextual shaping, bidirectional ordering, digit ordering, centering, and clipping.

Can you give before and afters of each of these individually?

will provide examples of it as soon as possible

@OmarAglan

OmarAglan commented Aug 28, 2026

Copy link
Copy Markdown
Author

I can't tell from the text and images what the problems were and how this fixes it.

The main-menu test confirms correct contextual shaping, bidirectional ordering, digit ordering, centering, and clipping.

Can you give before and afters of each of these individually?

will provide examples of it as soon as possible

the Arabic text as for now!

before

sshot001

after

sshot_20260828_215246_250

@OmarAglan
OmarAglan force-pushed the feature/arabic-ui-text-shaping branch from f88c715 to d25f054 Compare August 28, 2026 19:08
Comment thread Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp
@OmarAglan
OmarAglan force-pushed the feature/arabic-ui-text-shaping branch from d25f054 to eff4156 Compare August 28, 2026 19:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants