expo-pretext 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/LICENSE +21 -0
- package/README.md +250 -0
- package/android/src/main/java/expo/modules/pretext/ExpoPretextModule.kt +354 -0
- package/expo-module.config.json +9 -0
- package/ios/ExpoPretext.podspec +20 -0
- package/ios/ExpoPretext.swift +444 -0
- package/package.json +59 -0
- package/src/ExpoPretext.ts +70 -0
- package/src/__tests__/cache.test.ts +71 -0
- package/src/__tests__/font-utils.test.ts +69 -0
- package/src/__tests__/layout.test.ts +300 -0
- package/src/__tests__/obstacle-layout.test.ts +127 -0
- package/src/__tests__/setup-mocks.ts +14 -0
- package/src/analysis.ts +1208 -0
- package/src/bidi.ts +175 -0
- package/src/build.ts +503 -0
- package/src/cache.ts +59 -0
- package/src/engine-profile.ts +38 -0
- package/src/font-utils.ts +50 -0
- package/src/generated/bidi-data.ts +998 -0
- package/src/hooks/useFlashListHeights.ts +88 -0
- package/src/hooks/usePreparedText.ts +16 -0
- package/src/hooks/useTextHeight.ts +45 -0
- package/src/index.ts +56 -0
- package/src/layout.ts +353 -0
- package/src/line-break.ts +1113 -0
- package/src/obstacle-layout.ts +193 -0
- package/src/prepare.ts +246 -0
- package/src/rich-inline.ts +647 -0
- package/src/streaming.ts +61 -0
- package/src/types.ts +104 -0
package/src/cache.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// src/cache.ts
|
|
2
|
+
// JS-side width cache to skip native calls when all segments are cached.
|
|
3
|
+
|
|
4
|
+
const widthCache = new Map<string, Map<string, number>>()
|
|
5
|
+
|
|
6
|
+
export function getCachedWidth(
|
|
7
|
+
fontKey: string,
|
|
8
|
+
segment: string
|
|
9
|
+
): number | undefined {
|
|
10
|
+
return widthCache.get(fontKey)?.get(segment)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function setCachedWidth(
|
|
14
|
+
fontKey: string,
|
|
15
|
+
segment: string,
|
|
16
|
+
width: number
|
|
17
|
+
): void {
|
|
18
|
+
let fontCache = widthCache.get(fontKey)
|
|
19
|
+
if (!fontCache) {
|
|
20
|
+
fontCache = new Map()
|
|
21
|
+
widthCache.set(fontKey, fontCache)
|
|
22
|
+
}
|
|
23
|
+
fontCache.set(segment, width)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function cacheNativeResult(
|
|
27
|
+
fontKey: string,
|
|
28
|
+
segments: string[],
|
|
29
|
+
widths: number[]
|
|
30
|
+
): void {
|
|
31
|
+
let fontCache = widthCache.get(fontKey)
|
|
32
|
+
if (!fontCache) {
|
|
33
|
+
fontCache = new Map()
|
|
34
|
+
widthCache.set(fontKey, fontCache)
|
|
35
|
+
}
|
|
36
|
+
for (let i = 0; i < segments.length; i++) {
|
|
37
|
+
fontCache.set(segments[i]!, widths[i]!)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function tryResolveAllFromCache(
|
|
42
|
+
fontKey: string,
|
|
43
|
+
segments: string[]
|
|
44
|
+
): number[] | null {
|
|
45
|
+
const fontCache = widthCache.get(fontKey)
|
|
46
|
+
if (!fontCache) return null
|
|
47
|
+
|
|
48
|
+
const widths: number[] = new Array(segments.length)
|
|
49
|
+
for (let i = 0; i < segments.length; i++) {
|
|
50
|
+
const w = fontCache.get(segments[i]!)
|
|
51
|
+
if (w === undefined) return null
|
|
52
|
+
widths[i] = w
|
|
53
|
+
}
|
|
54
|
+
return widths
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function clearJSCache(): void {
|
|
58
|
+
widthCache.clear()
|
|
59
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// src/engine-profile.ts
|
|
2
|
+
import { Platform } from 'react-native'
|
|
3
|
+
|
|
4
|
+
export type EngineProfile = {
|
|
5
|
+
lineFitEpsilon: number
|
|
6
|
+
carryCJKAfterClosingQuote: boolean
|
|
7
|
+
preferPrefixWidthsForBreakableRuns: boolean
|
|
8
|
+
preferEarlySoftHyphenBreak: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let cachedProfile: EngineProfile | null = null
|
|
12
|
+
|
|
13
|
+
export function getEngineProfile(): EngineProfile {
|
|
14
|
+
if (cachedProfile !== null) return cachedProfile
|
|
15
|
+
|
|
16
|
+
cachedProfile = Platform.select({
|
|
17
|
+
ios: {
|
|
18
|
+
lineFitEpsilon: 0.01, // restored to default for diagnostic
|
|
19
|
+
carryCJKAfterClosingQuote: true,
|
|
20
|
+
preferPrefixWidthsForBreakableRuns: false,
|
|
21
|
+
preferEarlySoftHyphenBreak: false,
|
|
22
|
+
},
|
|
23
|
+
android: {
|
|
24
|
+
lineFitEpsilon: 0.02,
|
|
25
|
+
carryCJKAfterClosingQuote: false,
|
|
26
|
+
preferPrefixWidthsForBreakableRuns: false,
|
|
27
|
+
preferEarlySoftHyphenBreak: false,
|
|
28
|
+
},
|
|
29
|
+
default: {
|
|
30
|
+
lineFitEpsilon: 0.01,
|
|
31
|
+
carryCJKAfterClosingQuote: false,
|
|
32
|
+
preferPrefixWidthsForBreakableRuns: false,
|
|
33
|
+
preferEarlySoftHyphenBreak: false,
|
|
34
|
+
},
|
|
35
|
+
})!
|
|
36
|
+
|
|
37
|
+
return cachedProfile
|
|
38
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/font-utils.ts
|
|
2
|
+
import type { TextStyle, FontDescriptor } from './types'
|
|
3
|
+
|
|
4
|
+
export function textStyleToFontDescriptor(style: TextStyle): FontDescriptor {
|
|
5
|
+
return {
|
|
6
|
+
fontFamily: style.fontFamily,
|
|
7
|
+
fontSize: style.fontSize,
|
|
8
|
+
fontWeight: style.fontWeight,
|
|
9
|
+
fontStyle: style.fontStyle,
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getFontKey(style: TextStyle): string {
|
|
14
|
+
const weight = style.fontWeight ?? '400'
|
|
15
|
+
const fStyle = style.fontStyle ?? 'normal'
|
|
16
|
+
return `${style.fontFamily}_${style.fontSize}_${weight}_${fStyle}`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getLineHeight(style: TextStyle): number {
|
|
20
|
+
return style.lineHeight ?? style.fontSize * 1.2
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const SYSTEM_FONTS = [
|
|
24
|
+
'System', 'system', 'sans-serif', 'serif', 'monospace',
|
|
25
|
+
// iOS built-in fonts
|
|
26
|
+
'Helvetica', 'Helvetica Neue', 'Arial', 'Courier', 'Courier New',
|
|
27
|
+
'Georgia', 'Times New Roman', 'Trebuchet MS', 'Verdana',
|
|
28
|
+
'American Typewriter', 'Avenir', 'Avenir Next', 'Baskerville',
|
|
29
|
+
'Didot', 'Futura', 'Gill Sans', 'Menlo', 'Optima', 'Palatino',
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
export function isFontLoaded(fontFamily: string): boolean {
|
|
33
|
+
// System fonts are always available
|
|
34
|
+
if (SYSTEM_FONTS.includes(fontFamily)) return true
|
|
35
|
+
try {
|
|
36
|
+
const Font = require('expo-font')
|
|
37
|
+
return Font.isLoaded(fontFamily)
|
|
38
|
+
} catch {
|
|
39
|
+
return true
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function warnIfFontNotLoaded(style: TextStyle): void {
|
|
44
|
+
if (__DEV__ && !isFontLoaded(style.fontFamily)) {
|
|
45
|
+
console.warn(
|
|
46
|
+
`[expo-pretext] Font "${style.fontFamily}" not loaded. ` +
|
|
47
|
+
`Heights will be inaccurate. Use Font.loadAsync() first.`
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
}
|