npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-nitro-pretext

v1.1.0

Published

Layout-only Pretext engine for React Native text geometry before render

Readme

react-native-nitro-pretext

Text height before render for React Native.

react-native-nitro-pretext is a layout-only native text engine. It lets a screen ask for paragraph height, line count, line ranges, diagnostics, and rich inline box frames before mounting the visible UI. It does not render text for you, and it does not use hidden <Text onLayout> measurement views.

DEMO

Pretext demo

Why It Exists

Many React Native layouts need text geometry before they can place visible content:

  • masonry cards
  • chat bubbles that choose a tight width
  • bottom sheets and split views
  • dashboard annotations beside charts
  • editorial layouts with shape constraints
  • localized copy where emoji, fallback fonts, CJK, RTL, or complex scripts can change height

With plain RN <Text>, the common path is:

  1. render a hidden measurement tree
  2. wait for onLayout or onTextLayout
  3. calculate the real layout
  4. render the visible UI

Pretext moves step 1 and 2 into native text engines so height is available before the visible surface mounts. That removes the hidden measurement component, the onLayout callback fan-in, the second render pass, and the layout shift that usually follows when measured height is applied after mount.

Core API

import {
  Pretext,
  prepare,
  layout,
  usePretextLayout,
} from "react-native-nitro-pretext";

Manual lifecycle:

const prepared = prepare("Text that affects layout", {
  fontSize: 16,
  includeFontPadding: true,
});

const metrics = layout(prepared, 280);
const height = metrics.height;

prepared.release();

React lifecycle:

const result = usePretextLayout({
  text: "Text that affects layout",
  width: 280,
  style: {
    fontSize: 16,
  },
  output: "metrics",
});

Namespace style is also supported:

const prepared = Pretext.prepare(text, style);
const metrics = Pretext.layout(prepared, width);
const height = metrics.height;

Use the object form when layout needs positioning rules:

const lines = layout(prepared, {
  width: 280,
  left: 12,
  output: "lines",
  shapeSlices: [
    { top: 0, height: 24, left: 0, width: 120 },
    { top: 0, height: 24, left: 180, width: 100 },
  ],
});

Multiple shapeSlices may share the same vertical band. In output: "lines" mode, Pretext fills those same-row slots from left to right before advancing to the next visual row, which supports text around two sides of an obstacle. Use width: 0 for a constrained band that has no valid text slot; Pretext advances past that row without consuming text instead of falling back to the full paragraph width. If the next unbreakable token is wider than a constrained slot, Pretext skips that slot and advances until the token can fit or the shape constraint ends.

Rich inline boxes are prepared with inline segment paragraphs and caller-owned box metrics, then read with output: "rich". See the API Reference for the segment shape.

What It Does Not Do

  • No public renderer component.
  • No native drawing surface.
  • No hidden RN measurement view.
  • No fontSize * lineCount height heuristic.
  • No browser canvas pixel-parity target.

Your visible UI stays normal React Native View and Text. Pretext only returns the layout data you need before render.

Native Engines

Height is native text-engine output. It is affected by font metrics, lineHeight, fallback fonts, emoji, locale, Android includeFontPadding, text direction, and the platform line breaking strategy.

| Platform | Layout path | Status | | ------------------------ | ---------------------------- | -------------------------------------------------------------- | | Android API 24+ | RN-compatible StaticLayout | Canonical for normal-wrap requests without shape slices. | | iOS | TextKit normal-wrap layout | Current benchmark gate path for normal text. | | RN <Text onTextLayout> | strict parity oracle | Dedicated Maestro contract for raw line count, text, geometry. | | Visible RN <Text> | final renderer | Match styles carefully because Pretext does not draw pixels. |

Android includeFontPadding defaults to true to match RN <Text> defaults. If you turn it off in Pretext but leave RN <Text> at its default, height can drift. When lineHeight is omitted, Pretext uses platform font metrics instead of a fontSize heuristic. Diagnostics may report android_static_layout_compat on Android normal-wrap paths, android_legacy_fallback on Android fallback paths, ios_text_kit on iOS normal-wrap benchmark paths, ios_core_text on alternate iOS native line layout paths, and ios_manual_token_fallback on degraded iOS fallback paths.

The native implementation is split by responsibility across preparation, tokenization, line layout, diagnostics, constants, and native model files on both Android and iOS.

Compatibility

| Dependency | Package range | Example target | | ---------------------------- | ------------- | ---------------------------------------------------- | | React | * | Example app uses React 19.2.3. | | React Native | >=0.81.0 | Example app uses React Native 0.85.0. | | react-native-nitro-modules | * | Required runtime peer dependency. | | Android | API 24+ | Normal-wrap requests use RN-compatible StaticLayout. | | iOS | RN default | Example app currently targets iOS 15.1. |

Pretext keeps its React and react-native-nitro-modules peer ranges open as * and documents/enforces its own React Native peer floor as >=0.81.0. The bundled example app is on React 19.2.3 and React Native 0.85.0.

Performance Snapshot

Benchmarks are platform-specific. The current normal-wrap gate expects iOS TextKit and Android RN-compatible StaticLayout metadata, so their numbers should be reported separately and never averaged together.

The most important comparison is the path an app would otherwise build with RN only:

| Path | What happens before visible UI is stable | | ------------------------------- | ----------------------------------------------------------------------------------- | | Hidden RN <Text> + onLayout | Mount hidden measurement tree, wait for callbacks, apply height, render visible UI. | | prepare() + layout() | Measure in the native text engine first, then render visible UI with known height. |

Latest Maestro timing snapshot:

| Platform | Target | RN <Text> median | Pretext visible surface median | Delta vs RN | Pretext hot layout median | Prepare once | | -------------- | -------------------------------------- | -----------------: | -----------------------------: | ----------: | ------------------------: | -----------: | | iOS | iPhone 16 simulator, iOS 18.5, Release | 188.11 ms | 197.40 ms | +9.29 ms | 0.02 ms | 44.72 ms | | Android API 36 | Pixel_9_Pro AVD, API 36, release APK | 23.80 ms | 22.43 ms | -1.37 ms | 0.03 ms | 50.46 ms |

Negative delta means the Pretext visible surface was faster in that run; positive delta means it was slower. The visible-surface number includes the final RN <Text> render. The hot-layout number is the layout-only relayout cost after paragraph state has already been prepared.

Hot relayout compute improvement:

| Platform | Hot layout compute vs RN <Text> median | Relative compute speedup | | -------------- | ---------------------------------------: | -----------------------: | | iOS | 99.99% | 9405.5x | | Android API 36 | 99.9% | 793.3x |

The measured-layout case study is the render optimization claim: Pretext removes the hidden measurement <Text> surface, so the screen does not need a measurement render followed by a corrected visible render. The Maestro timing suite is a different contract: it includes the final visible RN <Text> surface. On the Android API 36 run above, the hot layout path was 0.03 ms, and the full visible-surface median was faster than RN by 1.37 ms; that visible surface number is still reported as context, not as the layout-only gate.

Strict parity contract:

| Platform | Contract source | Cases | Line count | Line text | Geometry | Status | | -------------- | ------------------------ | ----: | ---------: | --------: | -------: | ------ | | iOS | 260 Maestro parity cases | 260 | 0/260 | 0/260 | 0/260 | Passed | | Android API 36 | 260 Maestro parity cases | 260 | 0/260 | 0/260 | 0/260 | Passed |

The parity contract is no longer derived from repeated timing samples. It is a dedicated Maestro flow that executes 260 cases once per platform and requires line-count, exact raw line-text, and line-geometry parity to be 0/260. The suite contains 259 raw RN <Text onTextLayout> cases plus one structural shapeSlices case that verifies same-row multi-slot output, blocked rows, and gap containment. The line-text comparison does not trim, normalize, or collapse newline, trailing whitespace, tab, or NBSP characters; display output may JSON-escape raw values, but comparison uses the unmodified RN payload.

The latest iOS Release simulator app and Android API 36 release APK runs completed the 260-case gate on April 26, 2026.

Current benchmark details and gate thresholds are in the Benchmark Report.

Install

npm install react-native-nitro-pretext react-native-nitro-modules

react-native-nitro-modules is required because Pretext is exposed as a Nitro Module.

React and React Native are peer dependencies supplied by your app. Pretext declares React Native >=0.81.0 as its peer minimum. The bundled example app is currently on React 19.2.3 and React Native 0.85.0.

Example App

The example app is split into learning examples and benchmark routes:

  • examples/use-case: API-matched Pretext examples for prepare, layout outputs, usePretextLayout, and the Pretext namespace.
  • examples/non-use-case: matching RN-only workarounds that show the hidden <Text>, onLayout, onTextLayout, callback fan-in, and render-pass state you would otherwise manage yourself.
  • benchmark/measured-layout: case study for hidden RN measurement versus Pretext.layout() before render.
  • benchmark/parity: RN <Text> and shapeSlices parity contract using 260 Maestro cases.
  • benchmark/base-text and benchmark/pretext-layout: benchmark screens for compatibility, timing, and parity diagnostics.

Run it locally:

yarn example:ios
yarn example:android

If local Watchman is broken, the example Metro config already falls back to the Node filesystem watcher.

Documentation

Development

yarn nitrogen
yarn typecheck
yarn lint
yarn fmt:check
yarn test
yarn verify:api-examples
yarn verify:native-source-size
yarn build
yarn verify:package-exports
yarn verify:package-contents

CI runs the static, unit, package, and native build checks above. Maestro device flows are manual only because they depend on installed apps, simulators/devices, Metro, and API/example routes that may intentionally change.

Manual Maestro runs:

yarn examples:ios
yarn examples:android
MAESTRO_IOS_DEVICE_ID=<simulator-udid> yarn benchmark:ios
MAESTRO_ANDROID_DEVICE_ID=<adb-serial-api-29-or-newer> yarn benchmark:android
MAESTRO_IOS_DEVICE_ID=<simulator-udid> yarn benchmark:parity:ios
MAESTRO_ANDROID_DEVICE_ID=<adb-serial-api-29-or-newer> yarn benchmark:parity:android

Benchmark scripts write the latest summary and quality-gate report under example/.maestro-artifacts/<platform>-<flow>/latest-summary.txt and example/.maestro-artifacts/<platform>-<flow>/latest-gate.txt. BENCHMARK_SKIP_GATE=1 writes a skipped gate report for artifact capture only; do not report that as a benchmark result. .maestro-artifacts is ignored by git, so any latest-gate.txt there is generated output, not a tracked result. Parity runs also write:

  • example/.maestro-artifacts/ios-parity/latest-parity-summary.txt
  • example/.maestro-artifacts/ios-parity/latest-parity-mismatches.json
  • example/.maestro-artifacts/ios-parity/latest-parity-contracts.json
  • example/.maestro-artifacts/android-parity/latest-parity-summary.txt
  • example/.maestro-artifacts/android-parity/latest-parity-mismatches.json
  • example/.maestro-artifacts/android-parity/latest-parity-contracts.json

Example native builds:

yarn workspace react-native-nitro-pretext-example build:android
yarn workspace react-native-nitro-pretext-example build:ios

License

MIT