Skip to content

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

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

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

Conversation

@OmarAglan

@OmarAglan OmarAglan commented Aug 28, 2026

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

sshot001

after

sshot_20260828_215246_250

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

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

Copy link
Copy Markdown

PR Summary by Qodo

Shape complex single-line UI text with Uniscribe

✨ Enhancement 🐞 Bug fix 🕐 40+ Minutes

Grey Divider

AI Description

• Shape complex single-line UI text with contextual glyphs and bidirectional ordering.
• Measure and rasterize eligible runs through dynamically loaded Windows Uniscribe.
• Preserve legacy rendering for Latin, multiline, hotkey, monospaced, and editable text.
Diagram

graph TD
  A["Display String"] --> B["Sentence Renderer"] --> C{"Complex eligible?"}
  C -- Yes --> D["Windows Uniscribe"] --> E["GDI Raster"] --> F["Sentence Textures"]
  C -- No --> G["Legacy Glyph Path"] --> F
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Adopt DirectWrite rendering
  • ➕ Provides a modern shaping and text-layout stack
  • ➕ Could support multiline layout and caret metrics in one backend
  • ➖ Requires a broader renderer redesign beyond existing GDI HFONT integration
  • ➖ Raises migration, compatibility, and review risk substantially
2. Link usp10 statically
  • ➕ Removes function-pointer wrappers and lazy-load branching
  • ➕ Makes missing APIs fail at build or process load time
  • ➖ Introduces a hard Windows import dependency
  • ➖ Loses graceful fallback to legacy rendering when Uniscribe is unavailable

Recommendation: Keep the runtime-loaded Uniscribe approach for this scoped change. It reuses the existing GDI font pipeline, preserves legacy behavior when shaping is unavailable or unsupported, and avoids a disproportionate DirectWrite migration; editable and multiline shaping can be added once caret and layout metrics are designed.

Files changed (12) +600 / -5

Enhancement (7) +406 / -5
DisplayString.hExpose per-string complex text control +1/-0

Expose per-string complex text control

• Adds an abstract switch allowing display-string implementations and UI controls to enable or disable complex shaping.

Core/GameEngine/Include/GameClient/DisplayString.h

render2dsentence.cppShape and rasterize eligible complex text runs +355/-3

Shape and rasterize eligible complex text runs

• Detects complex single-line text, measures and renders it as one Uniscribe run, and copies bounded raster chunks into existing sentence textures. It preserves the legacy path for excluded modes, unsupported dimensions, non-Windows builds, or shaping failures.

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

render2dsentence.hDeclare complex shaping renderer APIs +18/-2

Declare complex shaping renderer APIs

• Adds font-level complexity, measurement, and rasterization methods plus renderer eligibility, sizing, texture-building, and enablement state.

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

W3DDisplayString.hExpose complex shaping in Generals display strings +1/-0

Expose complex shaping in Generals display strings

• Adds the W3D display-string override for controlling complex text shaping.

Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplayString.h

W3DDisplayString.cppUse shaped widths and propagate shaping state +15/-0

Use shaped widths and propagate shaping state

• Returns whole-run Uniscribe width when applicable and applies shaping enablement to normal and hotkey renderers. State changes invalidate cached text geometry.

Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplayString.cpp

W3DDisplayString.hExpose complex shaping in Zero Hour display strings +1/-0

Expose complex shaping in Zero Hour display strings

• Adds the W3D display-string override for controlling complex text shaping.

GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplayString.h

W3DDisplayString.cppUse shaped widths and propagate shaping state +15/-0

Use shaped widths and propagate shaping state

• Returns whole-run Uniscribe width when applicable and applies shaping enablement to normal and hotkey renderers. State changes invalidate cached text geometry.

GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplayString.cpp

Bug fix (2) +10 / -0
GameWindowManager.cppKeep Generals text entries on legacy rendering +5/-0

Keep Generals text entries on legacy rendering

• Disables complex shaping for editable, selected, and composition display strings because caret and partial-character metrics remain per-character.

Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp

GameWindowManager.cppKeep Zero Hour text entries on legacy rendering +5/-0

Keep Zero Hour text entries on legacy rendering

• Disables complex shaping for editable, selected, and composition display strings until shaped caret positioning is supported.

GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp

Other (3) +184 / -0
CMakeLists.txtBuild the Uniscribe loader on Windows +2/-0

Build the Uniscribe loader on Windows

• Registers the new runtime loader sources in the Windows-only WWLib source list.

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

Usp10Loader.cppLoad Uniscribe APIs safely at runtime +111/-0

Load Uniscribe APIs safely at runtime

• Implements one-time, lock-protected loading of the system usp10.dll and resolves the shaping APIs used by the renderer. Wrappers return failures when the library or required exports are unavailable.

Core/Libraries/Source/WWVegas/WWLib/Usp10Loader.cpp

Usp10Loader.hDefine the runtime Uniscribe interface +71/-0

Define the runtime Uniscribe interface

• Declares the required Uniscribe flags, opaque analysis types, API wrappers, and resolved function-pointer storage without adding a static import dependency.

Core/Libraries/Source/WWVegas/WWLib/Usp10Loader.h

@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 (1) 📘 Rule violations (0) 📎 Requirement gaps (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. Unsupported runs rasterize twice 🐞 Bug ➹ Performance ⭐ New
Description
Build_Complex_Sentence creates the complete GDI bitmap and A4R4G4B4 raster before checking whether
its width exceeds WrapWidth. Every single-line complex string requiring wrapping therefore pays
for a full-run shape, bitmap allocation, pixel conversion, and discard before Build_Sentence
renders it again through the legacy wrapped path, with particularly high cost for long text.
Code

Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[R687-688]

+	if (!Font->Rasterize_Complex_Text(text, &raster, &text_width, &text_height) ||
+		!Is_Complex_Text_Size_Supported(text_width, text_height))
Evidence
The complex predicate does not exclude wrapped renderers, while the support check rejects runs whose
measured width reaches WrapWidth. Build_Complex_Sentence invokes the allocating rasterizer
before that support check; after it returns false, Build_Sentence immediately continues into the
existing centered/non-centered renderer, proving the discarded first rendering is repeated work.

Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[621-632]
Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[642-645]
Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[680-692]
Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[1292-1302]
Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[1632-1645]

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 text is fully rasterized before checking whether its dimensions qualify for the complex single-line path. If its width reaches the configured wrapping width, that raster is discarded and the legacy path renders the text again.

## Issue Context
Use the lightweight Uniscribe extent query and `Is_Complex_Text_Size_Supported` before allocating/rasterizing the full run. Retain a post-rasterization dimension check for defensive consistency if needed.

## Fix Focus Areas
- Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[680-692]
- Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[642-645]

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


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

Context sources

Grey Divider

Tip of the day
💡 Did you know, you can type 'qodo, fix this' on a finding and the fix lands right on your PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Previous reviews

Review updated until commit 74c1e27 ⚖️ Balanced

Results up to commit N/A


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


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

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 Windows Uniscribe shaping and bidirectional ordering for supported single-line complex UI text while retaining the legacy path for editable, wrapped, multiline, hot-key, and monospaced strings.

  • Adds dynamic loading and wrappers for the required usp10.dll APIs.
  • Measures and rasterizes complex text as a complete run, then divides the raster across existing sentence textures.
  • Keeps shaped measurement and rendering behind the same size-support predicate.
  • Disables shaping for editable text until shaped caret metrics are supported.
  • Integrates the behavior into both Generals variants.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp Adds eligibility checks, shaped measurement and rasterization, bounded texture allocation, and a consistent legacy fallback; the two previously reported boundary failures are resolved.
Core/Libraries/Source/WWVegas/WWLib/Usp10Loader.cpp Dynamically loads the required Uniscribe functions from the Windows system directory and fails safely when loading is unavailable.
Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplayString.cpp Uses shaped full-string widths when available and invalidates cached text when complex rendering is toggled.
GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplayString.cpp Mirrors the Generals display-string integration for Zero Hour.
Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt Adds the Windows-only Uniscribe loader sources to the shared WWLib target.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    Text[Display string] --> Eligible{Supported complex single-line text?}
    Eligible -- No --> Legacy[Legacy per-character renderer]
    Eligible -- Yes --> Measure[Measure with Uniscribe]
    Measure --> Supported{Dimensions supported?}
    Supported -- No --> Legacy
    Supported -- Yes --> Rasterize[Rasterize shaped run]
    Rasterize --> Chunk[Copy raster chunks into sentence textures]
    Chunk --> Draw[Draw sentence]
    Legacy --> Draw
Loading

Reviews (5): 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 Outdated
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 Outdated
@OmarAglan
OmarAglan force-pushed the feature/arabic-ui-text-shaping branch from d25f054 to eff4156 Compare August 28, 2026 19:33
@OmarAglan
OmarAglan marked this pull request as draft August 30, 2026 20:13
@OmarAglan

Copy link
Copy Markdown
Author

draft to fix the vc6 issue

uint16 *raster = nullptr;
int text_width = 0;
int text_height = 0;
if (!Font->Rasterize_Complex_Text(text, &raster, &text_width, &text_height) ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Get_Complex_Text_Extents and Rasterize_Complex_Text each run ScriptStringAnalyse. If they disagree, rendering falls back to the old path even though layout may have already used the shaped size. Can we get the size and raster from the same analysis?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

it now fixed!
Build_Sentence() no longer measures the shaped text with Get_Complex_Text_Extents() and then performs a second analysis for rasterization and compares the two results.

It now checks only whether the string is eligible for complex rendering. Build_Complex_Sentence() rasterizes the text once and uses the width and height returned by that same analysis for texture admission and chunking. This removes the disagreement fallback described in the review.


if ( font )
{
if ( charPos == -1 )

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This sends every full string through Get_Text_Extents, not just complex text. It also changes multiline width from the sum of all lines to the widest line. Is that intended?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

it now fixed!

For a complete string, getWidth() now asks specifically whether complex-text extents are available. If the string is not eligible for shaping—including plain Latin, multiline text, partial charPos measurements, or strings with complex rendering disabled—it falls through to the original per-character width loop unchanged.

Therefore ordinary Latin strings retain the legacy path, and multiline width retains the previous behavior of summing the widths of its lines.

@bobtista

Copy link
Copy Markdown

We can handle VC6 in a small prerequisite PR by runtime-loading usp10.dll, using DbgHelpLoader as an example. Then this PR can drop the usp10 link and guard the Uniscribe code with _WIN32.

@OmarAglan
OmarAglan force-pushed the feature/arabic-ui-text-shaping branch from eff4156 to 74c1e27 Compare August 31, 2026 20:08
@OmarAglan
OmarAglan marked this pull request as ready for review August 31, 2026 20:10
Comment on lines +687 to +688
if (!Font->Rasterize_Complex_Text(text, &raster, &text_width, &text_height) ||
!Is_Complex_Text_Size_Supported(text_width, text_height))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Unsupported runs rasterize twice 🐞 Bug ➹ Performance

Build_Complex_Sentence creates the complete GDI bitmap and A4R4G4B4 raster before checking whether
its width exceeds WrapWidth. Every single-line complex string requiring wrapping therefore pays
for a full-run shape, bitmap allocation, pixel conversion, and discard before Build_Sentence
renders it again through the legacy wrapped path, with particularly high cost for long text.
Agent Prompt
## Issue description
Complex text is fully rasterized before checking whether its dimensions qualify for the complex single-line path. If its width reaches the configured wrapping width, that raster is discarded and the legacy path renders the text again.

## Issue Context
Use the lightweight Uniscribe extent query and `Is_Complex_Text_Size_Supported` before allocating/rasterizing the full run. Retain a post-rasterization dimension check for defensive consistency if needed.

## Fix Focus Areas
- Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[680-692]
- Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp[642-645]

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

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

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 74c1e27

@OmarAglan

OmarAglan commented Aug 31, 2026

Copy link
Copy Markdown
Author

We can handle VC6 in a small prerequisite PR by runtime-loading usp10.dll, using DbgHelpLoader as an example. Then this PR can drop the usp10 link and guard the Uniscribe code with _WIN32.

yes im working on it!

@xezon

xezon commented Sep 1, 2026

Copy link
Copy Markdown

In what shape is the arabic text you tested with? As far as I am aware the old translations had the words reversed to accomodate the game implementation. Can arabic text now be supplied normally?

@OmarAglan

Copy link
Copy Markdown
Author

In what shape is the arabic text you tested with? As far as I am aware the old translations had the words reversed to accomodate the game implementation. Can arabic text now be supplied normally?

i used noraml text arabic, i didnt reverse the text, this fixes the hack that is to reverse the arabic text!
still i need to extand upon this on adding maybe support for arabic text in chat and multi line, this can be addressed in follow up pr.

@OmarAglan

Copy link
Copy Markdown
Author

needs rebase and conflict fix, working on it.

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.

5 participants