Replace dependencies with system apis (#8)
* pdf dep rem * remove stale package * replace with native share * rename share module * revert: restore react-native-share (native replacement had worse perf, negligible size gain) * cosmetic
This commit is contained in:
@@ -17,9 +17,9 @@ class MainApplication : Application(), ReactApplication {
|
|||||||
override val reactNativeHost: ReactNativeHost =
|
override val reactNativeHost: ReactNativeHost =
|
||||||
object : DefaultReactNativeHost(this) {
|
object : DefaultReactNativeHost(this) {
|
||||||
override fun getPackages(): List<ReactPackage> {
|
override fun getPackages(): List<ReactPackage> {
|
||||||
// Packages that cannot be autolinked yet can be added manually here, for example:
|
val packages = PackageList(this).packages.toMutableList()
|
||||||
// packages.add(new MyReactNativePackage());
|
packages.add(PdfGeneratorPackage())
|
||||||
return PackageList(this).packages
|
return packages
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getJSMainModuleName(): String = "index"
|
override fun getJSMainModuleName(): String = "index"
|
||||||
|
|||||||
55
android/app/src/main/java/com/lensapp/PdfGeneratorModule.kt
Normal file
55
android/app/src/main/java/com/lensapp/PdfGeneratorModule.kt
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package com.lensapp
|
||||||
|
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.RectF
|
||||||
|
import android.graphics.pdf.PdfDocument
|
||||||
|
import com.facebook.react.bridge.Promise
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext
|
||||||
|
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
||||||
|
import com.facebook.react.bridge.ReactMethod
|
||||||
|
import com.facebook.react.bridge.ReadableArray
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
|
||||||
|
class PdfGeneratorModule(private val reactContext: ReactApplicationContext) :
|
||||||
|
ReactContextBaseJavaModule(reactContext) {
|
||||||
|
|
||||||
|
override fun getName() = "PdfGenerator"
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
fun generate(imagePaths: ReadableArray, fileName: String, promise: Promise) {
|
||||||
|
try {
|
||||||
|
val outDir = File(reactContext.filesDir, "pdfs").also { it.mkdirs() }
|
||||||
|
val outFile = File(outDir, "$fileName.pdf")
|
||||||
|
val doc = PdfDocument()
|
||||||
|
|
||||||
|
for (i in 0 until imagePaths.size()) {
|
||||||
|
val path = imagePaths.getString(i)!!.removePrefix("file://")
|
||||||
|
val bitmap = BitmapFactory.decodeFile(path)
|
||||||
|
?: throw Exception("Failed to decode image: $path")
|
||||||
|
|
||||||
|
// A4 at 72 DPI: 595 x 842 points
|
||||||
|
val pageWidth = 595
|
||||||
|
val pageHeight = 842
|
||||||
|
val pageInfo = PdfDocument.PageInfo.Builder(pageWidth, pageHeight, i + 1).create()
|
||||||
|
val page = doc.startPage(pageInfo)
|
||||||
|
|
||||||
|
val scale = minOf(pageWidth.toFloat() / bitmap.width, pageHeight.toFloat() / bitmap.height)
|
||||||
|
val scaledW = bitmap.width * scale
|
||||||
|
val scaledH = bitmap.height * scale
|
||||||
|
val left = (pageWidth - scaledW) / 2f
|
||||||
|
val top = (pageHeight - scaledH) / 2f
|
||||||
|
|
||||||
|
page.canvas.drawBitmap(bitmap, null, RectF(left, top, left + scaledW, top + scaledH), null)
|
||||||
|
bitmap.recycle()
|
||||||
|
doc.finishPage(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOutputStream(outFile).use { doc.writeTo(it) }
|
||||||
|
doc.close()
|
||||||
|
promise.resolve("file://${outFile.absolutePath}")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
promise.reject("PDF_ERROR", e.message, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
android/app/src/main/java/com/lensapp/PdfGeneratorPackage.kt
Normal file
14
android/app/src/main/java/com/lensapp/PdfGeneratorPackage.kt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package com.lensapp
|
||||||
|
|
||||||
|
import com.facebook.react.ReactPackage
|
||||||
|
import com.facebook.react.bridge.NativeModule
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext
|
||||||
|
import com.facebook.react.uimanager.ViewManager
|
||||||
|
|
||||||
|
class PdfGeneratorPackage : ReactPackage {
|
||||||
|
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
|
||||||
|
listOf(PdfGeneratorModule(reactContext))
|
||||||
|
|
||||||
|
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> =
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
4
ios/LensApp/PdfGenerator.h
Normal file
4
ios/LensApp/PdfGenerator.h
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#import <React/RCTBridgeModule.h>
|
||||||
|
|
||||||
|
@interface PdfGenerator : NSObject <RCTBridgeModule>
|
||||||
|
@end
|
||||||
58
ios/LensApp/PdfGenerator.m
Normal file
58
ios/LensApp/PdfGenerator.m
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#import "PdfGenerator.h"
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
@implementation PdfGenerator
|
||||||
|
|
||||||
|
RCT_EXPORT_MODULE();
|
||||||
|
|
||||||
|
RCT_EXPORT_METHOD(generate:(NSArray *)imagePaths
|
||||||
|
fileName:(NSString *)fileName
|
||||||
|
resolver:(RCTPromiseResolveBlock)resolve
|
||||||
|
rejecter:(RCTPromiseRejectBlock)reject)
|
||||||
|
{
|
||||||
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||||
|
NSArray *docPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
||||||
|
NSString *pdfsDir = [docPaths[0] stringByAppendingPathComponent:@"pdfs"];
|
||||||
|
[[NSFileManager defaultManager] createDirectoryAtPath:pdfsDir
|
||||||
|
withIntermediateDirectories:YES
|
||||||
|
attributes:nil
|
||||||
|
error:nil];
|
||||||
|
|
||||||
|
NSString *outPath = [[pdfsDir stringByAppendingPathComponent:fileName]
|
||||||
|
stringByAppendingPathExtension:@"pdf"];
|
||||||
|
|
||||||
|
// A4 at 72 DPI: 595 x 842 points
|
||||||
|
CGRect pageRect = CGRectMake(0, 0, 595, 842);
|
||||||
|
UIGraphicsPDFRendererFormat *format = [UIGraphicsPDFRendererFormat defaultFormat];
|
||||||
|
UIGraphicsPDFRenderer *renderer = [[UIGraphicsPDFRenderer alloc] initWithBounds:pageRect format:format];
|
||||||
|
|
||||||
|
NSError *error;
|
||||||
|
BOOL success = [renderer writePDFToURL:[NSURL fileURLWithPath:outPath]
|
||||||
|
withActions:^(UIGraphicsPDFRendererContext *ctx) {
|
||||||
|
for (NSString *rawPath in imagePaths) {
|
||||||
|
NSString *path = [rawPath hasPrefix:@"file://"] ? [rawPath substringFromIndex:7] : rawPath;
|
||||||
|
UIImage *image = [UIImage imageWithContentsOfFile:path];
|
||||||
|
if (!image) continue;
|
||||||
|
|
||||||
|
[ctx beginPage];
|
||||||
|
|
||||||
|
CGFloat scale = MIN(pageRect.size.width / image.size.width,
|
||||||
|
pageRect.size.height / image.size.height);
|
||||||
|
CGFloat w = image.size.width * scale;
|
||||||
|
CGFloat h = image.size.height * scale;
|
||||||
|
CGFloat x = (pageRect.size.width - w) / 2;
|
||||||
|
CGFloat y = (pageRect.size.height - h) / 2;
|
||||||
|
|
||||||
|
[image drawInRect:CGRectMake(x, y, w, h)];
|
||||||
|
}
|
||||||
|
} error:&error];
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
resolve([@"file://" stringByAppendingString:outPath]);
|
||||||
|
} else {
|
||||||
|
reject(@"PDF_ERROR", error.localizedDescription, error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -13,7 +13,6 @@
|
|||||||
"react-native": "0.73.0",
|
"react-native": "0.73.0",
|
||||||
"react-native-document-scanner-plugin": "^2.0.4",
|
"react-native-document-scanner-plugin": "^2.0.4",
|
||||||
"react-native-fs": "^2.20.0",
|
"react-native-fs": "^2.20.0",
|
||||||
"react-native-html-to-pdf": "^1.3.0",
|
|
||||||
"react-native-share": "^12.2.6"
|
"react-native-share": "^12.2.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -12418,19 +12417,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-native-html-to-pdf": {
|
|
||||||
"version": "1.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-native-html-to-pdf/-/react-native-html-to-pdf-1.3.0.tgz",
|
|
||||||
"integrity": "sha512-UCbQO1k5yeajaJrEF7Wqq8ojdgpxsTbICQvtorXmuO1LkU/a6B3/tZyLes4zXCiq/5nTMTNA/k9m5bYStg5S0Q==",
|
|
||||||
"license": "MIT",
|
|
||||||
"workspaces": [
|
|
||||||
"example"
|
|
||||||
],
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "*",
|
|
||||||
"react-native": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-native-share": {
|
"node_modules/react-native-share": {
|
||||||
"version": "12.2.6",
|
"version": "12.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-12.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-12.2.6.tgz",
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
"react-native": "0.73.0",
|
"react-native": "0.73.0",
|
||||||
"react-native-document-scanner-plugin": "^2.0.4",
|
"react-native-document-scanner-plugin": "^2.0.4",
|
||||||
"react-native-fs": "^2.20.0",
|
"react-native-fs": "^2.20.0",
|
||||||
"react-native-html-to-pdf": "^1.3.0",
|
|
||||||
"react-native-share": "^12.2.6"
|
"react-native-share": "^12.2.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
diff --git a/node_modules/react-native-html-to-pdf/android/src/main/java/com/htmltopdf/HtmlToPdfPackage.kt b/node_modules/react-native-html-to-pdf/android/src/main/java/com/htmltopdf/HtmlToPdfPackage.kt
|
|
||||||
index 5fe5a8d..d2586e0 100644
|
|
||||||
--- a/node_modules/react-native-html-to-pdf/android/src/main/java/com/htmltopdf/HtmlToPdfPackage.kt
|
|
||||||
+++ b/node_modules/react-native-html-to-pdf/android/src/main/java/com/htmltopdf/HtmlToPdfPackage.kt
|
|
||||||
@@ -1,33 +1,16 @@
|
|
||||||
package com.htmltopdf
|
|
||||||
|
|
||||||
-import com.facebook.react.BaseReactPackage
|
|
||||||
+import com.facebook.react.ReactPackage
|
|
||||||
import com.facebook.react.bridge.NativeModule
|
|
||||||
import com.facebook.react.bridge.ReactApplicationContext
|
|
||||||
-import com.facebook.react.module.model.ReactModuleInfo
|
|
||||||
-import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
||||||
-import java.util.HashMap
|
|
||||||
+import com.facebook.react.uimanager.ViewManager
|
|
||||||
|
|
||||||
-class HtmlToPdfPackage : BaseReactPackage() {
|
|
||||||
- override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
||||||
- return if (name == HtmlToPdfModule.NAME) {
|
|
||||||
- HtmlToPdfModule(reactContext)
|
|
||||||
- } else {
|
|
||||||
- null
|
|
||||||
- }
|
|
||||||
+class HtmlToPdfPackage : ReactPackage {
|
|
||||||
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
||||||
+ return listOf(HtmlToPdfModule(reactContext))
|
|
||||||
}
|
|
||||||
|
|
||||||
- override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
|
||||||
- return ReactModuleInfoProvider {
|
|
||||||
- val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
|
|
||||||
- moduleInfos[HtmlToPdfModule.NAME] = ReactModuleInfo(
|
|
||||||
- HtmlToPdfModule.NAME,
|
|
||||||
- HtmlToPdfModule.NAME,
|
|
||||||
- false, // canOverrideExistingModule
|
|
||||||
- false, // needsEagerInit
|
|
||||||
- false, // isCxxModule
|
|
||||||
- true // isTurboModule
|
|
||||||
- )
|
|
||||||
- moduleInfos
|
|
||||||
- }
|
|
||||||
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
||||||
+ return emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +1,17 @@
|
|||||||
// Mock native modules before importing the module under test
|
jest.mock('react-native', () => ({
|
||||||
jest.mock('react-native-fs', () => ({
|
NativeModules: {
|
||||||
DocumentDirectoryPath: '/mock/documents',
|
PdfGenerator: {
|
||||||
readFile: jest.fn().mockResolvedValue('base64data'),
|
generate: jest.fn(),
|
||||||
mkdir: jest.fn().mockResolvedValue(undefined),
|
},
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('react-native-html-to-pdf', () => ({
|
import {NativeModules} from 'react-native';
|
||||||
generatePDF: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
import RNFS from 'react-native-fs';
|
|
||||||
import {generatePDF as mockGeneratePDF} from 'react-native-html-to-pdf';
|
|
||||||
import {generatePdf} from '../utils/generatePdf';
|
import {generatePdf} from '../utils/generatePdf';
|
||||||
import {ScannedPage} from '../types';
|
import {ScannedPage} from '../types';
|
||||||
|
|
||||||
|
const mockGenerate = NativeModules.PdfGenerator.generate as jest.Mock;
|
||||||
|
|
||||||
const mockPages: ScannedPage[] = [
|
const mockPages: ScannedPage[] = [
|
||||||
{id: 'p1', uri: 'file:///mock/scans/scan1.jpg', width: 1240, height: 1754},
|
{id: 'p1', uri: 'file:///mock/scans/scan1.jpg', width: 1240, height: 1754},
|
||||||
{id: 'p2', uri: '/mock/scans/scan2.jpg', width: 1240, height: 1754},
|
{id: 'p2', uri: '/mock/scans/scan2.jpg', width: 1240, height: 1754},
|
||||||
@@ -24,73 +22,35 @@ beforeEach(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('generatePdf', () => {
|
describe('generatePdf', () => {
|
||||||
it('strips file:// before reading and passes base64 to HTML', async () => {
|
it('passes all image URIs to the native module', async () => {
|
||||||
(mockGeneratePDF as jest.Mock).mockResolvedValue({
|
mockGenerate.mockResolvedValue('file:///mock/pdfs/test.pdf');
|
||||||
filePath: '/mock/documents/pdfs/test.pdf',
|
|
||||||
});
|
|
||||||
|
|
||||||
await generatePdf(mockPages, 'test doc');
|
await generatePdf(mockPages, 'test');
|
||||||
|
|
||||||
// Should have been called for each page
|
expect(mockGenerate).toHaveBeenCalledWith(
|
||||||
expect(RNFS.readFile).toHaveBeenCalledTimes(2);
|
['file:///mock/scans/scan1.jpg', '/mock/scans/scan2.jpg'],
|
||||||
// file:// prefix stripped for first page
|
'test',
|
||||||
expect(RNFS.readFile).toHaveBeenCalledWith('/mock/scans/scan1.jpg', 'base64');
|
|
||||||
// path without prefix used as-is for second page
|
|
||||||
expect(RNFS.readFile).toHaveBeenCalledWith('/mock/scans/scan2.jpg', 'base64');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('passes a relative directory name, not an absolute path', async () => {
|
|
||||||
(mockGeneratePDF as jest.Mock).mockResolvedValue({
|
|
||||||
filePath: '/mock/documents/pdfs/test.pdf',
|
|
||||||
});
|
|
||||||
|
|
||||||
await generatePdf(mockPages, 'My Doc');
|
|
||||||
|
|
||||||
const call = (mockGeneratePDF as jest.Mock).mock.calls[0][0];
|
|
||||||
expect(call.directory).toBe('pdfs');
|
|
||||||
expect(call.directory).not.toContain('/');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('sanitises the file name', async () => {
|
|
||||||
(mockGeneratePDF as jest.Mock).mockResolvedValue({
|
|
||||||
filePath: '/mock/documents/pdfs/test.pdf',
|
|
||||||
});
|
|
||||||
|
|
||||||
await generatePdf(mockPages, 'Scan 2024/06/15 — page 1');
|
|
||||||
|
|
||||||
const call = (mockGeneratePDF as jest.Mock).mock.calls[0][0];
|
|
||||||
expect(call.fileName).not.toMatch(/[^a-z0-9_\-]/i);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns a file:// URI', async () => {
|
|
||||||
(mockGeneratePDF as jest.Mock).mockResolvedValue({
|
|
||||||
filePath: '/mock/documents/pdfs/test.pdf',
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await generatePdf(mockPages, 'test');
|
|
||||||
expect(result).toBe('file:///mock/documents/pdfs/test.pdf');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not double-prefix an already file:// path', async () => {
|
|
||||||
(mockGeneratePDF as jest.Mock).mockResolvedValue({
|
|
||||||
filePath: 'file:///mock/documents/pdfs/test.pdf',
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await generatePdf(mockPages, 'test');
|
|
||||||
expect(result).toBe('file:///mock/documents/pdfs/test.pdf');
|
|
||||||
expect(result).not.toContain('file://file://');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws when the library returns no filePath', async () => {
|
|
||||||
(mockGeneratePDF as jest.Mock).mockResolvedValue({filePath: null});
|
|
||||||
|
|
||||||
await expect(generatePdf(mockPages, 'test')).rejects.toThrow(
|
|
||||||
'PDF generation failed',
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws when the library rejects', async () => {
|
it('sanitises the file name before passing to native module', async () => {
|
||||||
(mockGeneratePDF as jest.Mock).mockRejectedValue(new Error('native crash'));
|
mockGenerate.mockResolvedValue('file:///mock/pdfs/test.pdf');
|
||||||
|
|
||||||
|
await generatePdf(mockPages, 'Scan 2024/06/15 — page 1');
|
||||||
|
|
||||||
|
const fileName = mockGenerate.mock.calls[0][1];
|
||||||
|
expect(fileName).not.toMatch(/[^a-z0-9_\-]/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the path resolved by the native module', async () => {
|
||||||
|
mockGenerate.mockResolvedValue('file:///mock/pdfs/test.pdf');
|
||||||
|
|
||||||
|
const result = await generatePdf(mockPages, 'test');
|
||||||
|
expect(result).toBe('file:///mock/pdfs/test.pdf');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws when the native module rejects', async () => {
|
||||||
|
mockGenerate.mockRejectedValue(new Error('native crash'));
|
||||||
|
|
||||||
await expect(generatePdf(mockPages, 'test')).rejects.toThrow('native crash');
|
await expect(generatePdf(mockPages, 'test')).rejects.toThrow('native crash');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export default function ViewerScreen({document, onBack, onDelete, onRename}: Pro
|
|||||||
|
|
||||||
const sharePdf = async (pageIndices?: number[]) => {
|
const sharePdf = async (pageIndices?: number[]) => {
|
||||||
setGenerating(true);
|
setGenerating(true);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 0));
|
||||||
try {
|
try {
|
||||||
const pages = pageIndices
|
const pages = pageIndices
|
||||||
? pageIndices.map(i => document.pages[i]).filter(Boolean)
|
? pageIndices.map(i => document.pages[i]).filter(Boolean)
|
||||||
|
|||||||
@@ -1,58 +1,10 @@
|
|||||||
import {generatePDF as rnGeneratePDF} from 'react-native-html-to-pdf';
|
import {NativeModules} from 'react-native';
|
||||||
import RNFS from 'react-native-fs';
|
|
||||||
import {ScannedPage} from '../types';
|
import {ScannedPage} from '../types';
|
||||||
|
|
||||||
// On Android, `directory` is a relative subdirectory name appended to
|
const {PdfGenerator} = NativeModules;
|
||||||
// getExternalFilesDir()/filesDir — NOT an absolute path.
|
|
||||||
const PDF_SUBDIR = 'pdfs';
|
|
||||||
|
|
||||||
async function pageToBase64(uri: string): Promise<string> {
|
|
||||||
const path = uri.startsWith('file://') ? uri.slice(7) : uri;
|
|
||||||
return RNFS.readFile(path, 'base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
function toFileUri(p: string): string {
|
|
||||||
return p.startsWith('file://') ? p : `file://${p}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function generatePdf(
|
|
||||||
pages: ScannedPage[],
|
|
||||||
name: string,
|
|
||||||
): Promise<string> {
|
|
||||||
const imageHtml = await Promise.all(
|
|
||||||
pages.map(async p => {
|
|
||||||
const b64 = await pageToBase64(p.uri);
|
|
||||||
return `<div class="page"><img src="data:image/jpeg;base64,${b64}" /></div>`;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const html = `<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<style>
|
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
||||||
body { background: #fff; }
|
|
||||||
.page { width: 100%; page-break-after: always; }
|
|
||||||
.page:last-child { page-break-after: avoid; }
|
|
||||||
img { width: 100%; height: auto; display: block; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>${imageHtml.join('\n')}</body>
|
|
||||||
</html>`;
|
|
||||||
|
|
||||||
|
export async function generatePdf(pages: ScannedPage[], name: string): Promise<string> {
|
||||||
|
const imagePaths = pages.map(p => p.uri);
|
||||||
const fileName = name.replace(/[^a-z0-9_\-]/gi, '_');
|
const fileName = name.replace(/[^a-z0-9_\-]/gi, '_');
|
||||||
|
return PdfGenerator.generate(imagePaths, fileName);
|
||||||
const result = await rnGeneratePDF({
|
|
||||||
html,
|
|
||||||
fileName,
|
|
||||||
directory: PDF_SUBDIR,
|
|
||||||
base64: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result.filePath) {
|
|
||||||
throw new Error('PDF generation failed: library returned no file path');
|
|
||||||
}
|
|
||||||
|
|
||||||
return toFileUri(result.filePath);
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user