/** * PP-OCRv5 (ONNX Runtime) Config Plugin(plan.md「决策 4 Expo Config Plugin 方案」+「3.4 Layer 2」)。 * * 本插件在 expo prebuild 时: * 1. 复制 Kotlin 源码(OcrModule.kt + OcrPackage.kt)到 android/app/src/main/java/... * 2. 复制 ONNX 模型与字典到 android/app/src/main/assets * 3. 注册 OcrPackage 到 MainApplication(getPackages) * 4. 添加 onnxruntime-android 依赖(app/build.gradle) * * 引擎:ONNX Runtime(跨平台、微软官方、Windows 友好),替代 NCNN 路线。 * 注意:SDK 54 的 @expo/config-plugins 暴露的是 withMainApplication / withAppBuildGradle * (没有 withAndroidMainApplication / withAndroidGradle)。文件复制用 withDangerousMod。 * * 模型文件放 plugins/ppocr/assets/,版权/体积原因不入仓库,需自行下载(见 README.md)。 */ const { withMainApplication, withAppBuildGradle, withDangerousMod, } = require('@expo/config-plugins'); const fs = require('fs'); const path = require('path'); function getAppId(config) { return config.android?.package || 'com.example.driftledger'; } /** 递归复制目录,prebuild 阶段执行并替换包名。 */ function copyAndReplaceDir(src, dest, appId) { if (!fs.existsSync(src)) return; fs.mkdirSync(dest, { recursive: true }); for (const entry of fs.readdirSync(src)) { const s = path.join(src, entry); const d = path.join(dest, entry); if (fs.statSync(s).isDirectory()) { copyAndReplaceDir(s, d, appId); } else { if (entry.endsWith('.kt') || entry.endsWith('.java')) { let content = fs.readFileSync(s, 'utf8'); content = content.replace(/package\s+com\.beancount\.mobile/g, `package ${appId}`); content = content.replace(/import\s+com\.beancount\.mobile/g, `import ${appId}`); fs.writeFileSync(d, content, 'utf8'); } else { fs.copyFileSync(s, d); } } } } /** 递归复制目录(仅用于模型资源复制,不替换包名)。 */ function copyDir(src, dest) { if (!fs.existsSync(src)) return; fs.mkdirSync(dest, { recursive: true }); for (const entry of fs.readdirSync(src)) { const s = path.join(src, entry); const d = path.join(dest, entry); if (fs.statSync(s).isDirectory()) copyDir(s, d); else fs.copyFileSync(s, d); } } function withPpOcr(config) { const appId = getAppId(config); const PACKAGE = `${appId}.ppocr`; // 1+2. 复制 Kotlin 源码与 ONNX 模型/字典到原生工程 config = withDangerousMod(config, [ 'android', async (modConfig) => { const projectRoot = modConfig.modRequest.platformProjectRoot; const pkgPath = appId.replace(/\./g, '/'); const ktDest = path.join(projectRoot, 'app/src/main/java', pkgPath, 'ppocr'); // Kotlin 源码并自动重命名包名 copyAndReplaceDir( path.join(__dirname, 'android'), ktDest, appId, ); // ONNX 模型 + 字典(若已下载) const assetsSrc = path.join(__dirname, 'assets'); if (fs.existsSync(assetsSrc)) { copyDir( assetsSrc, path.join(projectRoot, 'app/src/main/assets'), ); } return modConfig; }, ]); // 3. 注册 OcrPackage 到 MainApplication config = withMainApplication(config, (modConfig) => { let content = modConfig.modResults.contents; // 3a. 注入 import(在 package 声明行后插入) if (!content.includes(`import ${PACKAGE}.OcrPackage`)) { content = content.replace( /^(package\s+[\w.]+;?\s*)$/m, `$1\nimport ${PACKAGE}.OcrPackage`, ); } // 3b. 在 getPackages() 的 .apply {} 块里注入 add(OcrPackage()) if (!content.includes('add(OcrPackage())')) { if (/PackageList\(this\)\.packages\.apply\s*\{/.test(content)) { // 新架构 Kotlin(SDK 54 默认):模板已有空 .apply {} 块,在块开头注入 content = content.replace( /(PackageList\(this\)\.packages\.apply\s*\{)/, `$1\n // PP-OCRv5 ONNX 原生模块(由 Config Plugin 注入)\n add(OcrPackage())`, ); } else if (/PackageList\(this\)\.packages\b/.test(content)) { // 新架构变体:.packages 后没有 .apply,包一层 content = content.replace( /PackageList\(this\)\.packages\b/, `PackageList(this).packages.apply { add(OcrPackage()) }`, ); } else if (/\breturn\s+packages\s*;/.test(content)) { // 老架构 Java:在 return packages; 前插入 content = content.replace( /(\breturn\s+packages\s*;)/, `packages.add(new OcrPackage());\n $1`, ); } } modConfig.modResults.contents = content; return modConfig; }); // 4. 配置 app/build.gradle 依赖(ONNX Runtime Android) config = withAppBuildGradle(config, (modConfig) => { let gradle = modConfig.modResults.contents; if (!gradle.includes('onnxruntime')) { // 在 dependencies { 开头处插入,避免嵌套花括号的正则匹配错误 gradle = gradle.replace( /(dependencies\s*\{)/, `$1\n // PP-OCRv5 ONNX Runtime(由 Config Plugin 注入)\n implementation 'com.microsoft.onnxruntime:onnxruntime-android:1.20.0'` ); } modConfig.modResults.contents = gradle; return modConfig; }); return config; } module.exports = withPpOcr;