Skip to content

Add shared sample library and native macOS CoreText host#3

Merged
wieslawsoltes merged 20 commits into
mainfrom
feature/pretext-native-backends
Apr 20, 2026
Merged

Add shared sample library and native macOS CoreText host#3
wieslawsoltes merged 20 commits into
mainfrom
feature/pretext-native-backends

Conversation

@wieslawsoltes
Copy link
Copy Markdown
Owner

PR Summary: Shared Sample Extraction and Native macOS CoreText Host

Overview

This PR does two related things:

  1. extracts reusable sample data/model logic out of the existing Uno sample app into a shared sample library, and
  2. adds a new native net10.0-macos sample host that uses Pretext.CoreText with AppKit instead of the Uno/Skia host stack.

The result is that the repository now has one shared sample corpus and two host applications:

  • samples/PretextSamples: Uno Platform sample host
  • samples/PretextSamples.MacOS: native AppKit sample host on net10.0-macos
  • samples/PretextSamples.Shared: shared sample data, prepared-model logic, and sample assets

Why

Before this change, the sample logic lived inside the Uno sample project, which made it difficult to reuse the scenarios in another host without copying large amounts of code or tying the new host to Uno-specific types.

This PR addresses that by:

  • moving reusable sample models/data into a host-neutral assembly,
  • keeping the Uno host focused on Uno-specific rendering and controls,
  • introducing a native macOS host that exercises the CoreText backend directly, and
  • preserving the same sample catalog across both hosts.

What Changed

1. New shared sample project

Added:

  • samples/PretextSamples.Shared/PretextSamples.Shared.csproj
  • samples/PretextSamples.Shared/GlobalUsings.cs
  • samples/PretextSamples.Shared/Assets/shower_thoughts.json

Moved reusable sample logic from samples/PretextSamples/Samples into samples/PretextSamples.Shared/Samples:

  • MarkdownChatData.cs
  • MarkdownChatModel.cs
  • RichNoteModel.cs
  • JustificationComparisonData.cs
  • JustificationComparisonModel.cs
  • SampleTextData.cs
  • SharedText.cs

Added shared sample-specific data/catalog helpers:

  • SampleCatalog.cs
  • AccordionSampleData.cs
  • BubblesSampleData.cs
  • MasonrySampleData.cs

Key implementation points:

  • the moved models were made public where cross-assembly access was required,
  • the masonry sample asset is now embedded in the shared project instead of being loaded only through the Uno app package,
  • the shared project references Pretext, Pretext.Layout, and Markdig, which are the real dependencies of the reusable sample logic.

2. Existing Uno sample host updated to consume shared sample logic

Updated:

  • samples/PretextSamples/PretextSamples.csproj
  • samples/PretextSamples/Samples/OverviewSampleView.cs
  • samples/PretextSamples/Samples/AccordionSampleView.cs
  • samples/PretextSamples/Samples/BubblesSampleView.cs
  • samples/PretextSamples/Samples/MasonrySampleView.cs

Key changes:

  • PretextSamples now references PretextSamples.Shared,
  • overview content is sourced from the shared catalog,
  • accordion and bubbles now use shared sample data,
  • masonry now loads cards from the shared embedded asset instead of the Uno app package,
  • Uno-specific rendering code stays in PretextSamples; only reusable data/model logic moved out.

3. New native macOS sample host

Added full project:

  • samples/PretextSamples.MacOS/PretextSamples.MacOS.csproj

Core host files:

  • AppDelegate.cs
  • Main.cs
  • ViewController.cs
  • SampleShellView.cs
  • SamplePageView.cs
  • MacTheme.cs
  • GlobalUsings.cs

Project/packaging files:

  • Info.plist
  • Entitlements.plist
  • Main.storyboard
  • Assets.xcassets/...

The native host explicitly configures:

  • PretextLayout.SetTextMeasurerFactory(new CoreTextTextMeasurerFactory())

This ensures the macOS sample app exercises the CoreText backend directly instead of relying on SkiaSharp fallback behavior.

4. Native AppKit sample pages

Added AppKit-native page implementations for the same sample catalog:

  • Pages/OverviewPageView.cs
  • Pages/AccordionPageView.cs
  • Pages/BubblesPageView.cs
  • Pages/MasonryPageView.cs
  • Pages/RichNotePageView.cs
  • Pages/MarkdownChatPageView.cs
  • Pages/DynamicLayoutPageView.cs
  • Pages/EditorialEnginePageView.cs
  • Pages/JustificationComparisonPageView.cs
  • Pages/VariableAsciiPageView.cs

Notable implementation details:

  • the native pages are not Uno ports; they are AppKit-native renderers built around the same underlying sample data and Pretext APIs,
  • markdown chat is implemented as a native scrollable canvas with visibility-window rendering driven by MarkdownChatModel,
  • dynamic/editorial pages use Pretext.Layout helpers and manual geometry instead of host UI measurement,
  • the ASCII sample avoids SkiaSharp and renders its field natively.

5. Solution and documentation updates

Updated:

  • PretextSamples.slnx
  • README.md

Changes include:

  • adding PretextSamples.Shared and PretextSamples.MacOS to the solution,
  • documenting the split between shared sample logic and host apps,
  • adding native macOS sample run instructions,
  • updating project structure docs to reflect the new sample layout.

Commit Breakdown

This work was split into three commits:

  1. 65ea557 Extract shared sample models and assets
  2. 84b4a70 Add native macOS CoreText sample host
  3. bbc7563 Document shared and native sample hosts

Verification

Verified locally with:

dotnet build samples/PretextSamples.Shared/PretextSamples.Shared.csproj
dotnet build samples/PretextSamples/PretextSamples.csproj
dotnet build samples/PretextSamples.MacOS/PretextSamples.MacOS.csproj -p:ValidateXcodeVersion=false
dotnet build PretextSamples.slnx -p:ValidateXcodeVersion=false
dotnet test tests/Pretext.Uno.Tests/Pretext.Uno.Tests.csproj

Test result:

  • 88 passing tests in Pretext.Uno.Tests

Environment Note

The native macOS build required -p:ValidateXcodeVersion=false in this environment because:

  • installed .NET macOS workload expects Xcode 26.3
  • local machine currently has Xcode 26.2

This override was used only for local verification and was not added to the project itself.

Scope and Tradeoffs

Included

  • reusable sample logic extraction,
  • native macOS/AppKit sample host,
  • explicit CoreText backend usage,
  • matching sample catalog across Uno and macOS hosts,
  • solution and README updates.

Not included

  • changes to the core Pretext backend contracts or native backends themselves,
  • new automated test coverage for the macOS sample host,
  • a pixel-identical port of the Uno sample visuals.

The macOS implementation is intentionally native-first rather than a host-agnostic UI layer trying to preserve every Uno control pattern.

Risks / Review Focus

Reviewers should pay attention to:

  1. cross-assembly accessibility in PretextSamples.Shared
  2. whether any remaining reusable logic is still unnecessarily trapped in the Uno host
  3. CoreText/AppKit sample behavior on a machine with the recommended Xcode version
  4. native page performance in the heavier samples, especially:
    • markdown chat virtualization
    • editorial/orb redraw behavior
    • ASCII animation redraw cost
  5. whether the macOS sample visuals are sufficiently representative of the existing scenarios

Follow-up Ideas

Potential follow-up work:

  • add native-host-specific smoke testing if practical,
  • further reduce rendering logic duplication between Uno and macOS hosts where that can be done without coupling either host,
  • refine macOS visuals and interaction polish,
  • revisit font fallback/nullability cleanup in the AppKit helper layer,
  • add CI validation for the macOS sample host once the Xcode/toolchain baseline is settled.

@wieslawsoltes wieslawsoltes changed the title [codex] Add shared sample library and native macOS CoreText host Add shared sample library and native macOS CoreText host Apr 19, 2026
@wieslawsoltes wieslawsoltes marked this pull request as ready for review April 20, 2026 08:07
Copy link
Copy Markdown

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

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: 555911b149

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

{
public string Name => "FreeType";

public bool IsSupported => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Gate FreeType support on native library availability

IsSupported reports true for every Linux runtime, so discovery will always prefer this backend (priority 100) over SkiaSharp. On Linux environments missing libfreetype/libfontconfig (common in slim containers), Create then fails with native-load/font-resolution errors and PretextLayout.Prepare* crashes instead of falling back to another backend. Please only advertise support after probing required native dependencies (or treat create-time failures as unsupported during discovery).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

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

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: b9441effc3

ℹ️ 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 on lines +462 to +463
[DllImport(CoreFoundationLibrary)]
private static extern IntPtr CFStringCreateWithCharacters(IntPtr allocator, string chars, nint numChars);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Marshal CoreFoundation strings as UTF-16

CFStringCreateWithCharacters takes a pointer to UTF-16 UniChar data, but this P/Invoke uses string with no Unicode marshalling directive, so .NET uses ANSI/UTF-8 on macOS; CreateString then passes incorrectly encoded text while still supplying a UTF-16 character count. In macOS runs this can corrupt family/text CFStrings, causing CTFontCreateWithName and text measurement to fail or return wrong widths instead of producing stable CoreText output.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

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

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: 6acadfae4e

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

var supportedFactories = new List<IPretextTextMeasurerFactory>();
foreach (var factoryType in candidateTypes.Values)
{
if (Activator.CreateInstance(factoryType) is not IPretextTextMeasurerFactory factory)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve reflected backend factories under trimming

Backend discovery relies entirely on reflection (GetCustomAttributes + Activator.CreateInstance) to find and construct factories, but there are no trim annotations to root those factory types/constructors. In PublishTrimmed/AOT builds, the linker can strip factories that are only reached this way, so discovery returns no supported backend and Prepare* fails at runtime with the "No Pretext text measurer factory is configured" exception even though a backend package is referenced.

Useful? React with 👍 / 👎.

{
prefixLength += graphemes[i].Length;
prefixWidths[i] = MeasureText(text.AsSpan(0, prefixLength));
prefixWidths[i] = MeasureText(text.Substring(0, prefixLength));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid allocating substrings for every grapheme prefix

The prefix-width loop now calls text.Substring(0, prefixLength) on every grapheme step, which turns prefix measurement into O(n²) string allocations/copies for long breakable runs. This regresses prepare-time performance and GC behavior in real workloads (for example long chat/editorial paragraphs) compared to the previous span-based measurement path that did not allocate per prefix.

Useful? React with 👍 / 👎.

@wieslawsoltes wieslawsoltes merged commit c4a8b96 into main Apr 20, 2026
5 checks passed
@wieslawsoltes wieslawsoltes deleted the feature/pretext-native-backends branch April 20, 2026 08:53
Copy link
Copy Markdown

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

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: 73e86e1377

ℹ️ 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 on lines +56 to +60
{
return shapedWidth;
}

return MeasureFallback(text);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Shape fallback text runs with HarfBuzz on Linux

When primary-face shaping fails, this code falls back to MeasureFallback, which measures by summing per-codepoint advances/kerning instead of shaping runs. In Linux environments where the requested family lacks some glyphs (common for emoji and many non-Latin scripts), fallback fonts are used without HarfBuzz shaping, so ligatures/joins/ZWJ sequences are mismeasured and line breaking becomes incorrect. Please shape fallback runs with HarfBuzz (per resolved face/run) rather than relying on codepoint-by-codepoint advance accumulation.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant