@penabt/pixi-expo 0.6.0 → 0.6.1

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/README.md CHANGED
@@ -292,7 +292,7 @@ module.exports = config;
292
292
 
293
293
  ## Design Resolution
294
294
 
295
- Cocos2d-style fixed coordinate system for your game. Define a virtual resolution and the library automatically scales the stage to fit any device screen.
295
+ Fixed coordinate system for your game. Define a virtual resolution and the library automatically scales the stage to fit any device screen.
296
296
 
297
297
  ```tsx
298
298
  <PixiView
package/dist/index.d.mts CHANGED
@@ -543,13 +543,6 @@ declare const loadExpoFont: LoaderParser<string>;
543
543
  * ```
544
544
  */
545
545
  declare function registerBitmapFont(xmlModuleId: number, pageModuleIds: number[]): string;
546
- /**
547
- * Expo Bitmap Font Loader
548
- *
549
- * A PixiJS LoadParser that handles local bitmap fonts registered via
550
- * registerBitmapFont(). Runs at High priority to intercept before
551
- * PixiJS's built-in loadBitmapFont (which can't handle module IDs).
552
- */
553
546
  declare const loadExpoBitmapFont: LoaderParser;
554
547
 
555
548
  /**
@@ -627,7 +620,7 @@ declare function createExpoManifest(manifest: ExpoAssetsManifest): AssetsManifes
627
620
  /**
628
621
  * Design resolution scaling utilities.
629
622
  *
630
- * Provides Cocos2d-style design resolution support: define a fixed logical
623
+ * Provides design resolution support: define a fixed logical
631
624
  * size (e.g. 768×1024) and the library automatically scales the PixiJS stage
632
625
  * to fit the device screen.
633
626
  */
package/dist/index.d.ts CHANGED
@@ -543,13 +543,6 @@ declare const loadExpoFont: LoaderParser<string>;
543
543
  * ```
544
544
  */
545
545
  declare function registerBitmapFont(xmlModuleId: number, pageModuleIds: number[]): string;
546
- /**
547
- * Expo Bitmap Font Loader
548
- *
549
- * A PixiJS LoadParser that handles local bitmap fonts registered via
550
- * registerBitmapFont(). Runs at High priority to intercept before
551
- * PixiJS's built-in loadBitmapFont (which can't handle module IDs).
552
- */
553
546
  declare const loadExpoBitmapFont: LoaderParser;
554
547
 
555
548
  /**
@@ -627,7 +620,7 @@ declare function createExpoManifest(manifest: ExpoAssetsManifest): AssetsManifes
627
620
  /**
628
621
  * Design resolution scaling utilities.
629
622
  *
630
- * Provides Cocos2d-style design resolution support: define a fixed logical
623
+ * Provides design resolution support: define a fixed logical
631
624
  * size (e.g. 768×1024) and the library automatically scales the PixiJS stage
632
625
  * to fit the device screen.
633
626
  */
package/dist/index.js CHANGED
@@ -1463,6 +1463,7 @@ var loadExpoFont = {
1463
1463
  // src/adapter/loadExpoBitmapFont.ts
1464
1464
  var import_expo_asset3 = require("expo-asset");
1465
1465
  var import_expo_file_system = require("expo-file-system");
1466
+ var import_react_native2 = require("react-native");
1466
1467
  var import_pixi3 = require("pixi.js");
1467
1468
  var BMFONT_PREFIX = "__expo_bmfont_";
1468
1469
  var bitmapFontRegistry = /* @__PURE__ */ new Map();
@@ -1472,6 +1473,57 @@ function registerBitmapFont(xmlModuleId, pageModuleIds) {
1472
1473
  bitmapFontRegistry.set(key, { xmlModuleId, pageModuleIds });
1473
1474
  return key;
1474
1475
  }
1476
+ async function readTextFromUri(uri) {
1477
+ console.log(`[bmfont] readTextFromUri: ${uri}`);
1478
+ if (uri.startsWith("file:///android_asset/") || uri.startsWith("http://") || uri.startsWith("https://")) {
1479
+ console.log(`[bmfont] Using fetch for: ${uri}`);
1480
+ const response = await fetch(uri);
1481
+ if (!response.ok) throw new Error(`HTTP ${response.status} for ${uri}`);
1482
+ const text = await response.text();
1483
+ console.log(`[bmfont] Fetch OK, length: ${text.length}`);
1484
+ return text;
1485
+ }
1486
+ if (uri.startsWith("file://") || uri.startsWith("/")) {
1487
+ const fileUri = uri.startsWith("/") ? `file://${uri}` : uri;
1488
+ console.log(`[bmfont] Using File API for: ${fileUri}`);
1489
+ const file = new import_expo_file_system.File(fileUri);
1490
+ const content = await file.text();
1491
+ console.log(`[bmfont] File read OK, length: ${content.length}`);
1492
+ return content;
1493
+ }
1494
+ throw new Error(`Unsupported URI scheme: ${uri.substring(0, 30)}`);
1495
+ }
1496
+ function getMetroBaseUrl() {
1497
+ try {
1498
+ const scriptURL = import_react_native2.NativeModules.SourceCode?.getConstants?.()?.scriptURL ?? import_react_native2.NativeModules.SourceCode?.scriptURL;
1499
+ if (scriptURL) {
1500
+ const match = scriptURL.match(/^(https?:\/\/[^/]+)/);
1501
+ if (match) return match[1];
1502
+ }
1503
+ } catch {
1504
+ }
1505
+ return null;
1506
+ }
1507
+ function getAndroidAssetCandidates(asset) {
1508
+ const candidates = [];
1509
+ const name = asset.name;
1510
+ const type = asset.type;
1511
+ const hash = asset.hash;
1512
+ const metroBase = getMetroBaseUrl();
1513
+ if (metroBase && name && type) {
1514
+ candidates.push(`${metroBase}/assets/assets/fonts/${name}.${type}?platform=android`);
1515
+ candidates.push(`${metroBase}/assets/fonts/${name}.${type}?platform=android`);
1516
+ }
1517
+ if (name && type) {
1518
+ candidates.push(`file:///android_asset/assets/fonts/${name}.${type}`);
1519
+ candidates.push(`file:///android_asset/fonts/${name}.${type}`);
1520
+ candidates.push(`file:///android_asset/${name}.${type}`);
1521
+ }
1522
+ if (hash && type) {
1523
+ candidates.push(`file:///android_asset/${hash}.${type}`);
1524
+ }
1525
+ return candidates;
1526
+ }
1475
1527
  var loadExpoBitmapFont = {
1476
1528
  extension: {
1477
1529
  type: import_pixi3.ExtensionType.LoadParser,
@@ -1492,20 +1544,61 @@ var loadExpoBitmapFont = {
1492
1544
  throw new Error(`[loadExpoBitmapFont] No registered bitmap font for key: ${url}`);
1493
1545
  }
1494
1546
  const expoAsset = import_expo_asset3.Asset.fromModule(entry.xmlModuleId);
1495
- await expoAsset.downloadAsync();
1496
- const localUri = expoAsset.localUri || expoAsset.uri;
1497
- if (!localUri) {
1498
- throw new Error(`[loadExpoBitmapFont] Failed to get local URI for XML asset`);
1547
+ const errors = [];
1548
+ console.log(`[bmfont] === Loading ${url} ===`);
1549
+ console.log(`[bmfont] moduleId: ${entry.xmlModuleId}`);
1550
+ console.log(`[bmfont] name: ${expoAsset.name}`);
1551
+ console.log(`[bmfont] type: ${expoAsset.type}`);
1552
+ console.log(`[bmfont] hash: ${expoAsset.hash}`);
1553
+ console.log(`[bmfont] localUri: ${expoAsset.localUri}`);
1554
+ console.log(`[bmfont] uri: ${expoAsset.uri}`);
1555
+ console.log(`[bmfont] downloaded: ${expoAsset.downloaded}`);
1556
+ console.log(`[bmfont] Platform: ${import_react_native2.Platform.OS}`);
1557
+ if (expoAsset.localUri) {
1558
+ console.log(`[bmfont] Strategy 1: localUri`);
1559
+ try {
1560
+ return await readTextFromUri(expoAsset.localUri);
1561
+ } catch (e) {
1562
+ console.log(`[bmfont] Strategy 1 FAILED: ${e.message}`);
1563
+ errors.push(`localUri: ${e.message}`);
1564
+ }
1499
1565
  }
1500
- if (__DEV__) {
1501
- console.log(`[loadExpoBitmapFont] Loading XML from: ${localUri}`);
1566
+ if (import_react_native2.Platform.OS === "android") {
1567
+ const candidates = getAndroidAssetCandidates(expoAsset);
1568
+ console.log(`[bmfont] Strategy 2: trying ${candidates.length} android_asset candidates`);
1569
+ console.log(`[bmfont] httpServerLocation: ${expoAsset.httpServerLocation}`);
1570
+ for (const candidate of candidates) {
1571
+ try {
1572
+ console.log(`[bmfont] Trying: ${candidate}`);
1573
+ const content = await readTextFromUri(candidate);
1574
+ console.log(`[bmfont] SUCCESS with: ${candidate}`);
1575
+ return content;
1576
+ } catch (e) {
1577
+ console.log(`[bmfont] Failed: ${candidate} \u2192 ${e.message}`);
1578
+ }
1579
+ }
1580
+ errors.push(`android_asset: all ${candidates.length} candidates failed`);
1502
1581
  }
1503
- const file = new import_expo_file_system.File(localUri);
1504
- const content = await file.text();
1505
- if (__DEV__) {
1506
- console.log(`[loadExpoBitmapFont] XML content length: ${content.length}`);
1582
+ console.log(`[bmfont] Strategy 3: downloadAsync`);
1583
+ try {
1584
+ await expoAsset.downloadAsync();
1585
+ console.log(`[bmfont] downloadAsync OK, localUri: ${expoAsset.localUri}`);
1586
+ const uri = expoAsset.localUri || expoAsset.uri;
1587
+ if (uri) return await readTextFromUri(uri);
1588
+ } catch (e) {
1589
+ console.log(`[bmfont] Strategy 3 FAILED: ${e.message}`);
1590
+ errors.push(`downloadAsync: ${e.message}`);
1507
1591
  }
1508
- return content;
1592
+ if (expoAsset.uri) {
1593
+ console.log(`[bmfont] Strategy 4: uri \u2192 ${expoAsset.uri}`);
1594
+ try {
1595
+ return await readTextFromUri(expoAsset.uri);
1596
+ } catch (e) {
1597
+ console.log(`[bmfont] Strategy 4 FAILED: ${e.message}`);
1598
+ errors.push(`uri(${expoAsset.uri.substring(0, 50)}): ${e.message}`);
1599
+ }
1600
+ }
1601
+ throw new Error(`[loadExpoBitmapFont] All strategies failed for ${url}: ${errors.join("; ")}`);
1509
1602
  },
1510
1603
  async parse(asset, data, loader) {
1511
1604
  if (__DEV__) console.log(`[loadExpoBitmapFont] parse() called, src=${data.src}`);
@@ -1611,16 +1704,16 @@ function createExpoManifest(manifest) {
1611
1704
 
1612
1705
  // src/components/PixiView.tsx
1613
1706
  var import_react = require("react");
1614
- var import_react_native3 = require("react-native");
1707
+ var import_react_native4 = require("react-native");
1615
1708
  var import_expo_gl = require("expo-gl");
1616
1709
  var import_pixi4 = require("pixi.js");
1617
1710
 
1618
1711
  // src/utils/touchEventBridge.ts
1619
- var import_react_native2 = require("react-native");
1712
+ var import_react_native3 = require("react-native");
1620
1713
  var touchPositions = /* @__PURE__ */ new Map();
1621
1714
  function createPointerEvent(touch, eventType, options, isPrimary, nativeEvent) {
1622
1715
  const { canvas, offsetX = 0, offsetY = 0 } = options;
1623
- const ratio = import_react_native2.PixelRatio.get();
1716
+ const ratio = import_react_native3.PixelRatio.get();
1624
1717
  const x = (touch.locationX ?? touch.pageX - offsetX) * ratio;
1625
1718
  const y = (touch.locationY ?? touch.pageY - offsetY) * ratio;
1626
1719
  const prevPos = touchPositions.get(touch.identifier);
@@ -1838,7 +1931,7 @@ var PixiView = (0, import_react.forwardRef)((props, ref) => {
1838
1931
  const { width, height } = event.nativeEvent.layout;
1839
1932
  layoutRef.current = { width, height };
1840
1933
  if (appRef.current) {
1841
- const res = resolution || import_react_native3.PixelRatio.get();
1934
+ const res = resolution || import_react_native4.PixelRatio.get();
1842
1935
  appRef.current.renderer.resize(width * res, height * res);
1843
1936
  applyDesignScale(appRef.current, width, height);
1844
1937
  }
@@ -1947,7 +2040,7 @@ var PixiView = (0, import_react.forwardRef)((props, ref) => {
1947
2040
  const handleContextCreate = (0, import_react.useCallback)(
1948
2041
  async (gl) => {
1949
2042
  glRef.current = gl;
1950
- const pixelRatio = import_react_native3.PixelRatio.get();
2043
+ const pixelRatio = import_react_native4.PixelRatio.get();
1951
2044
  const { width: layoutWidth, height: layoutHeight } = layoutRef.current;
1952
2045
  const logicalWidth = layoutWidth || gl.drawingBufferWidth / pixelRatio;
1953
2046
  const logicalHeight = layoutHeight || gl.drawingBufferHeight / pixelRatio;
@@ -2052,7 +2145,7 @@ var PixiView = (0, import_react.forwardRef)((props, ref) => {
2052
2145
  onResponderTerminationRequest: () => true
2053
2146
  } : {};
2054
2147
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2055
- import_react_native3.View,
2148
+ import_react_native4.View,
2056
2149
  {
2057
2150
  ref: containerRef,
2058
2151
  style: [styles.container, style],
@@ -2063,7 +2156,7 @@ var PixiView = (0, import_react.forwardRef)((props, ref) => {
2063
2156
  );
2064
2157
  });
2065
2158
  PixiView.displayName = "PixiView";
2066
- var styles = import_react_native3.StyleSheet.create({
2159
+ var styles = import_react_native4.StyleSheet.create({
2067
2160
  container: {
2068
2161
  flex: 1,
2069
2162
  overflow: "hidden"
package/dist/index.mjs CHANGED
@@ -1371,6 +1371,7 @@ var loadExpoFont = {
1371
1371
  // src/adapter/loadExpoBitmapFont.ts
1372
1372
  import { Asset as Asset3 } from "expo-asset";
1373
1373
  import { File } from "expo-file-system";
1374
+ import { Platform, NativeModules } from "react-native";
1374
1375
  import {
1375
1376
  ExtensionType as ExtensionType3,
1376
1377
  LoaderParserPriority as LoaderParserPriority2,
@@ -1386,6 +1387,57 @@ function registerBitmapFont(xmlModuleId, pageModuleIds) {
1386
1387
  bitmapFontRegistry.set(key, { xmlModuleId, pageModuleIds });
1387
1388
  return key;
1388
1389
  }
1390
+ async function readTextFromUri(uri) {
1391
+ console.log(`[bmfont] readTextFromUri: ${uri}`);
1392
+ if (uri.startsWith("file:///android_asset/") || uri.startsWith("http://") || uri.startsWith("https://")) {
1393
+ console.log(`[bmfont] Using fetch for: ${uri}`);
1394
+ const response = await fetch(uri);
1395
+ if (!response.ok) throw new Error(`HTTP ${response.status} for ${uri}`);
1396
+ const text = await response.text();
1397
+ console.log(`[bmfont] Fetch OK, length: ${text.length}`);
1398
+ return text;
1399
+ }
1400
+ if (uri.startsWith("file://") || uri.startsWith("/")) {
1401
+ const fileUri = uri.startsWith("/") ? `file://${uri}` : uri;
1402
+ console.log(`[bmfont] Using File API for: ${fileUri}`);
1403
+ const file = new File(fileUri);
1404
+ const content = await file.text();
1405
+ console.log(`[bmfont] File read OK, length: ${content.length}`);
1406
+ return content;
1407
+ }
1408
+ throw new Error(`Unsupported URI scheme: ${uri.substring(0, 30)}`);
1409
+ }
1410
+ function getMetroBaseUrl() {
1411
+ try {
1412
+ const scriptURL = NativeModules.SourceCode?.getConstants?.()?.scriptURL ?? NativeModules.SourceCode?.scriptURL;
1413
+ if (scriptURL) {
1414
+ const match = scriptURL.match(/^(https?:\/\/[^/]+)/);
1415
+ if (match) return match[1];
1416
+ }
1417
+ } catch {
1418
+ }
1419
+ return null;
1420
+ }
1421
+ function getAndroidAssetCandidates(asset) {
1422
+ const candidates = [];
1423
+ const name = asset.name;
1424
+ const type = asset.type;
1425
+ const hash = asset.hash;
1426
+ const metroBase = getMetroBaseUrl();
1427
+ if (metroBase && name && type) {
1428
+ candidates.push(`${metroBase}/assets/assets/fonts/${name}.${type}?platform=android`);
1429
+ candidates.push(`${metroBase}/assets/fonts/${name}.${type}?platform=android`);
1430
+ }
1431
+ if (name && type) {
1432
+ candidates.push(`file:///android_asset/assets/fonts/${name}.${type}`);
1433
+ candidates.push(`file:///android_asset/fonts/${name}.${type}`);
1434
+ candidates.push(`file:///android_asset/${name}.${type}`);
1435
+ }
1436
+ if (hash && type) {
1437
+ candidates.push(`file:///android_asset/${hash}.${type}`);
1438
+ }
1439
+ return candidates;
1440
+ }
1389
1441
  var loadExpoBitmapFont = {
1390
1442
  extension: {
1391
1443
  type: ExtensionType3.LoadParser,
@@ -1406,20 +1458,61 @@ var loadExpoBitmapFont = {
1406
1458
  throw new Error(`[loadExpoBitmapFont] No registered bitmap font for key: ${url}`);
1407
1459
  }
1408
1460
  const expoAsset = Asset3.fromModule(entry.xmlModuleId);
1409
- await expoAsset.downloadAsync();
1410
- const localUri = expoAsset.localUri || expoAsset.uri;
1411
- if (!localUri) {
1412
- throw new Error(`[loadExpoBitmapFont] Failed to get local URI for XML asset`);
1461
+ const errors = [];
1462
+ console.log(`[bmfont] === Loading ${url} ===`);
1463
+ console.log(`[bmfont] moduleId: ${entry.xmlModuleId}`);
1464
+ console.log(`[bmfont] name: ${expoAsset.name}`);
1465
+ console.log(`[bmfont] type: ${expoAsset.type}`);
1466
+ console.log(`[bmfont] hash: ${expoAsset.hash}`);
1467
+ console.log(`[bmfont] localUri: ${expoAsset.localUri}`);
1468
+ console.log(`[bmfont] uri: ${expoAsset.uri}`);
1469
+ console.log(`[bmfont] downloaded: ${expoAsset.downloaded}`);
1470
+ console.log(`[bmfont] Platform: ${Platform.OS}`);
1471
+ if (expoAsset.localUri) {
1472
+ console.log(`[bmfont] Strategy 1: localUri`);
1473
+ try {
1474
+ return await readTextFromUri(expoAsset.localUri);
1475
+ } catch (e) {
1476
+ console.log(`[bmfont] Strategy 1 FAILED: ${e.message}`);
1477
+ errors.push(`localUri: ${e.message}`);
1478
+ }
1413
1479
  }
1414
- if (__DEV__) {
1415
- console.log(`[loadExpoBitmapFont] Loading XML from: ${localUri}`);
1480
+ if (Platform.OS === "android") {
1481
+ const candidates = getAndroidAssetCandidates(expoAsset);
1482
+ console.log(`[bmfont] Strategy 2: trying ${candidates.length} android_asset candidates`);
1483
+ console.log(`[bmfont] httpServerLocation: ${expoAsset.httpServerLocation}`);
1484
+ for (const candidate of candidates) {
1485
+ try {
1486
+ console.log(`[bmfont] Trying: ${candidate}`);
1487
+ const content = await readTextFromUri(candidate);
1488
+ console.log(`[bmfont] SUCCESS with: ${candidate}`);
1489
+ return content;
1490
+ } catch (e) {
1491
+ console.log(`[bmfont] Failed: ${candidate} \u2192 ${e.message}`);
1492
+ }
1493
+ }
1494
+ errors.push(`android_asset: all ${candidates.length} candidates failed`);
1416
1495
  }
1417
- const file = new File(localUri);
1418
- const content = await file.text();
1419
- if (__DEV__) {
1420
- console.log(`[loadExpoBitmapFont] XML content length: ${content.length}`);
1496
+ console.log(`[bmfont] Strategy 3: downloadAsync`);
1497
+ try {
1498
+ await expoAsset.downloadAsync();
1499
+ console.log(`[bmfont] downloadAsync OK, localUri: ${expoAsset.localUri}`);
1500
+ const uri = expoAsset.localUri || expoAsset.uri;
1501
+ if (uri) return await readTextFromUri(uri);
1502
+ } catch (e) {
1503
+ console.log(`[bmfont] Strategy 3 FAILED: ${e.message}`);
1504
+ errors.push(`downloadAsync: ${e.message}`);
1421
1505
  }
1422
- return content;
1506
+ if (expoAsset.uri) {
1507
+ console.log(`[bmfont] Strategy 4: uri \u2192 ${expoAsset.uri}`);
1508
+ try {
1509
+ return await readTextFromUri(expoAsset.uri);
1510
+ } catch (e) {
1511
+ console.log(`[bmfont] Strategy 4 FAILED: ${e.message}`);
1512
+ errors.push(`uri(${expoAsset.uri.substring(0, 50)}): ${e.message}`);
1513
+ }
1514
+ }
1515
+ throw new Error(`[loadExpoBitmapFont] All strategies failed for ${url}: ${errors.join("; ")}`);
1423
1516
  },
1424
1517
  async parse(asset, data, loader) {
1425
1518
  if (__DEV__) console.log(`[loadExpoBitmapFont] parse() called, src=${data.src}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@penabt/pixi-expo",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "PixiJS v8 adapter for React Native Expo. Enables hardware-accelerated 2D graphics using expo-gl WebGL context.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -16,6 +16,7 @@
16
16
 
17
17
  import { Asset } from 'expo-asset';
18
18
  import { File } from 'expo-file-system';
19
+ import { Platform, NativeModules } from 'react-native';
19
20
  import {
20
21
  ExtensionType,
21
22
  LoaderParserPriority,
@@ -90,6 +91,82 @@ export function registerBitmapFont(xmlModuleId: number, pageModuleIds: number[])
90
91
  * registerBitmapFont(). Runs at High priority to intercept before
91
92
  * PixiJS's built-in loadBitmapFont (which can't handle module IDs).
92
93
  */
94
+ /** Read text from a URI using the most appropriate method */
95
+ async function readTextFromUri(uri: string): Promise<string> {
96
+ console.log(`[bmfont] readTextFromUri: ${uri}`);
97
+
98
+ // android_asset or http(s) → use fetch (RN's fetch supports file:///android_asset/)
99
+ if (uri.startsWith('file:///android_asset/') || uri.startsWith('http://') || uri.startsWith('https://')) {
100
+ console.log(`[bmfont] Using fetch for: ${uri}`);
101
+ const response = await fetch(uri);
102
+ if (!response.ok) throw new Error(`HTTP ${response.status} for ${uri}`);
103
+ const text = await response.text();
104
+ console.log(`[bmfont] Fetch OK, length: ${text.length}`);
105
+ return text;
106
+ }
107
+
108
+ // file:// or absolute path → use expo-file-system File API
109
+ if (uri.startsWith('file://') || uri.startsWith('/')) {
110
+ const fileUri = uri.startsWith('/') ? `file://${uri}` : uri;
111
+ console.log(`[bmfont] Using File API for: ${fileUri}`);
112
+ const file = new File(fileUri);
113
+ const content = await file.text();
114
+ console.log(`[bmfont] File read OK, length: ${content.length}`);
115
+ return content;
116
+ }
117
+
118
+ throw new Error(`Unsupported URI scheme: ${uri.substring(0, 30)}`);
119
+ }
120
+
121
+ /**
122
+ * Get Metro dev server base URL from the JS bundle source URL.
123
+ * Returns null if not available (e.g., production build).
124
+ */
125
+ function getMetroBaseUrl(): string | null {
126
+ try {
127
+ const scriptURL: string | undefined =
128
+ NativeModules.SourceCode?.getConstants?.()?.scriptURL ??
129
+ (NativeModules.SourceCode as any)?.scriptURL;
130
+ if (scriptURL) {
131
+ // scriptURL is like "http://192.168.1.105:8081/index.bundle?platform=android&..."
132
+ const match = scriptURL.match(/^(https?:\/\/[^/]+)/);
133
+ if (match) return match[1];
134
+ }
135
+ } catch {}
136
+ return null;
137
+ }
138
+
139
+ /**
140
+ * Generate candidate URIs for reading a font asset on Android.
141
+ * Includes both Metro HTTP URLs (dev) and APK asset paths (prod).
142
+ */
143
+ function getAndroidAssetCandidates(asset: Asset): string[] {
144
+ const candidates: string[] = [];
145
+ const name = asset.name;
146
+ const type = asset.type;
147
+ const hash = asset.hash;
148
+
149
+ // In dev builds, Metro serves assets over HTTP
150
+ const metroBase = getMetroBaseUrl();
151
+ if (metroBase && name && type) {
152
+ // Metro asset URL patterns
153
+ candidates.push(`${metroBase}/assets/assets/fonts/${name}.${type}?platform=android`);
154
+ candidates.push(`${metroBase}/assets/fonts/${name}.${type}?platform=android`);
155
+ }
156
+
157
+ // file:///android_asset/ patterns for production builds
158
+ if (name && type) {
159
+ candidates.push(`file:///android_asset/assets/fonts/${name}.${type}`);
160
+ candidates.push(`file:///android_asset/fonts/${name}.${type}`);
161
+ candidates.push(`file:///android_asset/${name}.${type}`);
162
+ }
163
+ if (hash && type) {
164
+ candidates.push(`file:///android_asset/${hash}.${type}`);
165
+ }
166
+
167
+ return candidates;
168
+ }
169
+
93
170
  export const loadExpoBitmapFont = {
94
171
  extension: {
95
172
  type: ExtensionType.LoadParser,
@@ -114,29 +191,63 @@ export const loadExpoBitmapFont = {
114
191
  throw new Error(`[loadExpoBitmapFont] No registered bitmap font for key: ${url}`);
115
192
  }
116
193
 
117
- // Resolve XML module ID to local URI via expo-asset
118
194
  const expoAsset = Asset.fromModule(entry.xmlModuleId);
119
- await expoAsset.downloadAsync();
120
-
121
- const localUri = expoAsset.localUri || expoAsset.uri;
122
- if (!localUri) {
123
- throw new Error(`[loadExpoBitmapFont] Failed to get local URI for XML asset`);
195
+ const errors: string[] = [];
196
+
197
+ console.log(`[bmfont] === Loading ${url} ===`);
198
+ console.log(`[bmfont] moduleId: ${entry.xmlModuleId}`);
199
+ console.log(`[bmfont] name: ${expoAsset.name}`);
200
+ console.log(`[bmfont] type: ${expoAsset.type}`);
201
+ console.log(`[bmfont] hash: ${expoAsset.hash}`);
202
+ console.log(`[bmfont] localUri: ${expoAsset.localUri}`);
203
+ console.log(`[bmfont] uri: ${expoAsset.uri}`);
204
+ console.log(`[bmfont] downloaded: ${expoAsset.downloaded}`);
205
+ console.log(`[bmfont] Platform: ${Platform.OS}`);
206
+
207
+ // Strategy 1: localUri already available (iOS native builds)
208
+ if (expoAsset.localUri) {
209
+ console.log(`[bmfont] Strategy 1: localUri`);
210
+ try {
211
+ return await readTextFromUri(expoAsset.localUri);
212
+ } catch (e: any) { console.log(`[bmfont] Strategy 1 FAILED: ${e.message}`); errors.push(`localUri: ${e.message}`); }
124
213
  }
125
214
 
126
- if (__DEV__) {
127
- console.log(`[loadExpoBitmapFont] Loading XML from: ${localUri}`);
215
+ // Strategy 2: Android bundled asset — try multiple path patterns
216
+ if (Platform.OS === 'android') {
217
+ const candidates = getAndroidAssetCandidates(expoAsset);
218
+ console.log(`[bmfont] Strategy 2: trying ${candidates.length} android_asset candidates`);
219
+ console.log(`[bmfont] httpServerLocation: ${(expoAsset as any).httpServerLocation}`);
220
+ for (const candidate of candidates) {
221
+ try {
222
+ console.log(`[bmfont] Trying: ${candidate}`);
223
+ const content = await readTextFromUri(candidate);
224
+ console.log(`[bmfont] SUCCESS with: ${candidate}`);
225
+ return content;
226
+ } catch (e: any) {
227
+ console.log(`[bmfont] Failed: ${candidate} → ${e.message}`);
228
+ }
229
+ }
230
+ errors.push(`android_asset: all ${candidates.length} candidates failed`);
128
231
  }
129
232
 
130
- // Read file content via expo-file-system's File API
131
- // (React Native's fetch/XHR don't reliably support file:// URIs)
132
- const file = new File(localUri);
133
- const content = await file.text();
134
-
135
- if (__DEV__) {
136
- console.log(`[loadExpoBitmapFont] XML content length: ${content.length}`);
233
+ // Strategy 3: downloadAsync localUri (works in Expo Go / dev client)
234
+ console.log(`[bmfont] Strategy 3: downloadAsync`);
235
+ try {
236
+ await expoAsset.downloadAsync();
237
+ console.log(`[bmfont] downloadAsync OK, localUri: ${expoAsset.localUri}`);
238
+ const uri = expoAsset.localUri || expoAsset.uri;
239
+ if (uri) return await readTextFromUri(uri);
240
+ } catch (e: any) { console.log(`[bmfont] Strategy 3 FAILED: ${e.message}`); errors.push(`downloadAsync: ${e.message}`); }
241
+
242
+ // Strategy 4: expo-asset uri (may be HTTP in dev, or asset:// scheme)
243
+ if (expoAsset.uri) {
244
+ console.log(`[bmfont] Strategy 4: uri → ${expoAsset.uri}`);
245
+ try {
246
+ return await readTextFromUri(expoAsset.uri);
247
+ } catch (e: any) { console.log(`[bmfont] Strategy 4 FAILED: ${e.message}`); errors.push(`uri(${expoAsset.uri.substring(0, 50)}): ${e.message}`); }
137
248
  }
138
249
 
139
- return content;
250
+ throw new Error(`[loadExpoBitmapFont] All strategies failed for ${url}: ${errors.join('; ')}`);
140
251
  },
141
252
 
142
253
  async parse(asset: string, data: ResolvedAsset, loader: Loader): Promise<any> {
package/src/index.ts CHANGED
@@ -180,7 +180,7 @@ export type { PixiViewProps, PixiViewHandle } from './components/PixiView';
180
180
 
181
181
  // =============================================================================
182
182
  // EXPORTS: DESIGN RESOLUTION
183
- // Cocos2d-style design resolution utilities for fixed coordinate systems.
183
+ // Design resolution utilities for fixed coordinate systems.
184
184
  // =============================================================================
185
185
 
186
186
  export { calculateDesignScale, calculateDesignSafeArea } from './utils/designResolution';
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Design resolution scaling utilities.
3
3
  *
4
- * Provides Cocos2d-style design resolution support: define a fixed logical
4
+ * Provides design resolution support: define a fixed logical
5
5
  * size (e.g. 768×1024) and the library automatically scales the PixiJS stage
6
6
  * to fit the device screen.
7
7
  */