|
@@ -0,0 +1,435 @@
|
|
|
+/**
|
|
|
+ * Copyright © 2014-2024 PDF Technologies, Inc. All Rights Reserved.
|
|
|
+ *
|
|
|
+ * THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW
|
|
|
+ * AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE ComPDFKit LICENSE AGREEMENT.
|
|
|
+ * UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES.
|
|
|
+ * This notice may not be removed from this file.
|
|
|
+ */
|
|
|
+
|
|
|
+import React, { useState, useRef } from 'react';
|
|
|
+import { Image, Modal, Platform, ScrollView, StyleSheet, Switch, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
|
|
+import { CPDFReaderView, ComPDFKit, CPDFToolbarAction, CPDFWidgetType, CPDFTextWidget, CPDFRadiobuttonWidget, CPDFSignatureWidget, CPDFWidget } from '@compdfkit_pdf_sdk/react_native';
|
|
|
+import { useNavigation, useRoute, RouteProp } from '@react-navigation/native';
|
|
|
+import { HeaderBackButton } from '@react-navigation/elements';
|
|
|
+import { MenuProvider, Menu, MenuTrigger, MenuOptions, MenuOption } from 'react-native-popup-menu';
|
|
|
+import { SafeAreaView } from 'react-native-safe-area-context';
|
|
|
+import DocumentPicker from 'react-native-document-picker';
|
|
|
+import { launchImageLibrary } from 'react-native-image-picker';
|
|
|
+
|
|
|
+type RootStackParamList = {
|
|
|
+ CPDFWidgetExample: { document?: string };
|
|
|
+};
|
|
|
+
|
|
|
+type CPDFWidgetsExampleScreenRouteProp = RouteProp<
|
|
|
+ RootStackParamList,
|
|
|
+ 'CPDFWidgetExample'
|
|
|
+>;
|
|
|
+
|
|
|
+const CPDFWidgetsExampleScreen = () => {
|
|
|
+
|
|
|
+ const pdfReaderRef = useRef<CPDFReaderView>(null);
|
|
|
+
|
|
|
+ const navigation = useNavigation();
|
|
|
+
|
|
|
+ const route = useRoute<CPDFWidgetsExampleScreenRouteProp>();
|
|
|
+
|
|
|
+ const [widgetsModalVisible, setWidgetsModalVisible] = useState(false);
|
|
|
+
|
|
|
+ const [textEditModalVisible, setTextEditModalVisible] = useState(false);
|
|
|
+
|
|
|
+ const [widgetData, setWidgetData] = useState<CPDFWidget[]>([]);
|
|
|
+
|
|
|
+ const [text, setText] = useState('');
|
|
|
+
|
|
|
+ const [currentEditingWidgetIndex, setCurrentEditingWidgetIndex] = useState<number | null>(null);
|
|
|
+
|
|
|
+ const [samplePDF] = useState(
|
|
|
+ route.params?.document || (Platform.OS === 'android'
|
|
|
+ ? 'file:///android_asset/annot_test.pdf'
|
|
|
+ : 'annot_test.pdf')
|
|
|
+ );
|
|
|
+
|
|
|
+ const menuOptions = [
|
|
|
+ 'openDocument',
|
|
|
+ 'Import Widgets',
|
|
|
+ 'Export Widgets',
|
|
|
+ 'Get Widgets',
|
|
|
+ ];
|
|
|
+
|
|
|
+ const handleMenuItemPress = async (action: string) => {
|
|
|
+ switch (action) {
|
|
|
+ case 'openDocument':
|
|
|
+ const document = await ComPDFKit.pickFile();
|
|
|
+ if (document) {
|
|
|
+ await pdfReaderRef.current?._pdfDocument.open(document);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'Import Widgets':
|
|
|
+ const pickerResult = DocumentPicker.pick({
|
|
|
+ type: [DocumentPicker.types.allFiles],
|
|
|
+ copyTo: 'cachesDirectory'
|
|
|
+ });
|
|
|
+ pickerResult.then(async (res) => {
|
|
|
+ const file = res[0];
|
|
|
+
|
|
|
+ console.log('fileUri:', file?.uri);
|
|
|
+ console.log('fileCopyUri:', file?.fileCopyUri);
|
|
|
+ console.log('fileType:', file?.type);
|
|
|
+ const path = file!!.fileCopyUri!!
|
|
|
+ if (!path?.endsWith('xml') && !path?.endsWith('xfdf')) {
|
|
|
+ console.log('ComPDFKitRN please select xfdf format file');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ console.log('ComPDFKitRN importWidget, filePath:', path);
|
|
|
+ const importWidgetResult = await pdfReaderRef.current?._pdfDocument.importWidgets(path);
|
|
|
+ console.log('ComPDFKitRN importWidget:', importWidgetResult);
|
|
|
+ })
|
|
|
+
|
|
|
+ break;
|
|
|
+ case 'Export Widgets':
|
|
|
+ const exportWidgetsPath = await pdfReaderRef.current?._pdfDocument.exportWidgets();
|
|
|
+ console.log('ComPDFKitRN exportWidgets:', exportWidgetsPath)
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'Get Annotations':
|
|
|
+ const pageCount = await pdfReaderRef!.current!._pdfDocument.getPageCount();
|
|
|
+ for (let i = 0; i < pageCount; i++) {
|
|
|
+ const page = pdfReaderRef?.current?._pdfDocument.pageAtIndex(i);
|
|
|
+ const annotations = await page?.getAnnotations();
|
|
|
+ console.log(`ComPDFKitRN-annotations pageIndex ${i}:`);
|
|
|
+ console.log(JSON.stringify(annotations, null, 2));
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'Get Widgets':
|
|
|
+ const pageCount1 = await pdfReaderRef!.current!._pdfDocument.getPageCount();
|
|
|
+ let allWidgets: CPDFWidget[] = [];
|
|
|
+ for (let i = 0; i < pageCount1; i++) {
|
|
|
+ const page = pdfReaderRef?.current?._pdfDocument.pageAtIndex(i);
|
|
|
+ const widgets = await page?.getWidgets();
|
|
|
+ if (widgets) {
|
|
|
+ allWidgets = allWidgets.concat(widgets);
|
|
|
+ }
|
|
|
+ console.log(JSON.stringify(widgets, null, 2));
|
|
|
+ }
|
|
|
+ setWidgetData(allWidgets);
|
|
|
+ setWidgetsModalVisible(true);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleBack = () => {
|
|
|
+ navigation.goBack();
|
|
|
+ };
|
|
|
+
|
|
|
+ const renderToolbar = () => {
|
|
|
+ return (
|
|
|
+ <View style={styles.toolbar}>
|
|
|
+ <HeaderBackButton onPress={handleBack} />
|
|
|
+ <Text style={styles.toolbarTitle}>Widgets Example</Text>
|
|
|
+ <Menu>
|
|
|
+ <MenuTrigger>
|
|
|
+ <Image source={require('../assets/more.png')} style={{ width: 24, height: 24, marginEnd: 8 }} />
|
|
|
+ </MenuTrigger>
|
|
|
+
|
|
|
+ <MenuOptions>
|
|
|
+ {menuOptions.map((option, index) => (
|
|
|
+ <MenuOption key={index} onSelect={() => handleMenuItemPress(option)}>
|
|
|
+ <Text style={styles.menuOption}>{option}</Text>
|
|
|
+ </MenuOption>
|
|
|
+ ))}
|
|
|
+ </MenuOptions>
|
|
|
+ </Menu>
|
|
|
+ </View>
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ const widgetItem = (index: number, widget: CPDFWidget) => {
|
|
|
+ return (
|
|
|
+ <TouchableOpacity key={index} onPress={async () => {
|
|
|
+ await pdfReaderRef?.current?.setDisplayPageIndex(widget.page);
|
|
|
+ setWidgetsModalVisible(false);
|
|
|
+ }}>
|
|
|
+ <View style={{ width: '100%' }}>
|
|
|
+ <View style={{ flexDirection: 'row' }}>
|
|
|
+ <Text style={styles.widgetItem}>Title: </Text>
|
|
|
+ <Text style={styles.widgetBody}>{widget.title}</Text>
|
|
|
+ </View>
|
|
|
+ <View style={{ flexDirection: 'row' }}>
|
|
|
+ <Text style={styles.widgetItem}>Type: </Text>
|
|
|
+ <Text style={styles.widgetBody}>{widget.type.toUpperCase()}</Text>
|
|
|
+ </View>
|
|
|
+ <View style={{ flexDirection: 'row' }}>
|
|
|
+ <Text style={styles.widgetItem}>PageIndex: </Text>
|
|
|
+ <Text style={styles.widgetBody}>{widget.page}</Text>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {widget.type === CPDFWidgetType.TEXT_FIELD && (
|
|
|
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
|
|
|
+ <Text style={styles.widgetBody}>{(widget as CPDFTextWidget).text}</Text>
|
|
|
+ <TouchableOpacity style={{ paddingHorizontal: 16, paddingVertical:4}} onPress={async () => {
|
|
|
+ setCurrentEditingWidgetIndex(index);
|
|
|
+ setTextEditModalVisible(true);
|
|
|
+ }}>
|
|
|
+ <Text style={styles.closeButtonText}>Edit</Text>
|
|
|
+ </TouchableOpacity>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+ {(widget.type === CPDFWidgetType.RADIO_BUTTON || widget.type == CPDFWidgetType.CHECKBOX) && (
|
|
|
+ <View style={{ flex: 1, flexDirection: 'row', justifyContent: 'space-between' }}>
|
|
|
+ <Text style={styles.widgetItem}>isChecked:</Text>
|
|
|
+ <Switch
|
|
|
+ thumbColor={(widget as CPDFRadiobuttonWidget).isChecked ? '#1460F3' : 'white'}
|
|
|
+ trackColor={{ false: '#E0E0E0', true: '#1460F34D' }}
|
|
|
+ value={(widget as CPDFRadiobuttonWidget).isChecked} onValueChange={async () => {
|
|
|
+ const updatedWidgetData = [...widgetData];
|
|
|
+
|
|
|
+ if ((widget as CPDFRadiobuttonWidget).type === CPDFWidgetType.RADIO_BUTTON || (widget as CPDFRadiobuttonWidget).type === CPDFWidgetType.CHECKBOX) {
|
|
|
+ const updatedWidget = widget as CPDFRadiobuttonWidget;
|
|
|
+ const newChecked = !updatedWidget.isChecked;
|
|
|
+ // change RadioButtonWidget or CPDFCheckboxWidget checked status;
|
|
|
+ await updatedWidget.setChecked(newChecked);
|
|
|
+ // update appearance
|
|
|
+ await updatedWidget.updateAp();
|
|
|
+
|
|
|
+ updatedWidgetData[index] = { ...widget, isChecked: newChecked };
|
|
|
+ setWidgetData(updatedWidgetData);
|
|
|
+ }
|
|
|
+ }} />
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+ {widget.type === CPDFWidgetType.SIGNATURES_FIELDS && (
|
|
|
+ <View style={{ flexDirection: 'row', justifyContent: 'flex-end' }}>
|
|
|
+ <TouchableOpacity style={{ paddingVertical:4}} onPress={async () => {
|
|
|
+ const signatureWidget = widget as CPDFSignatureWidget;
|
|
|
+ launchImageLibrary({
|
|
|
+ mediaType:'photo'
|
|
|
+ }, async res => {
|
|
|
+ if( res.didCancel){
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ const path = res.assets?.[0]?.uri;
|
|
|
+ const signResult = await signatureWidget?.addImageSignature(path!);
|
|
|
+ if(signResult){
|
|
|
+ setWidgetsModalVisible(false);
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ })
|
|
|
+ }}>
|
|
|
+ <Text style={styles.closeButtonText}>Signature</Text>
|
|
|
+ </TouchableOpacity>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+ <View style={{ flex: 1, height: 1.5, backgroundColor: 'gray', opacity: 0.2, marginVertical: 5 }} />
|
|
|
+ </View>
|
|
|
+ </TouchableOpacity>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <MenuProvider>
|
|
|
+ <SafeAreaView style={{ flex: 1 }}>
|
|
|
+ <View style={{ flex: 1 }}>
|
|
|
+ {renderToolbar()}
|
|
|
+ <CPDFReaderView
|
|
|
+ ref={pdfReaderRef}
|
|
|
+ document={samplePDF}
|
|
|
+ configuration={ComPDFKit.getDefaultConfig({
|
|
|
+ toolbarConfig: {
|
|
|
+ iosLeftBarAvailableActions: [
|
|
|
+ CPDFToolbarAction.THUMBNAIL
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ })} />
|
|
|
+ </View>
|
|
|
+ <Modal visible={widgetsModalVisible} transparent={true} animationType="slide">
|
|
|
+ <View style={styles.modalContainer}>
|
|
|
+ <View style={styles.modalContent}>
|
|
|
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
|
|
|
+ <Text style={styles.modalTitle}>Forms List</Text>
|
|
|
+ <TouchableOpacity onPress={() => setWidgetsModalVisible(false)} style={styles.closeButton}>
|
|
|
+ <Text style={styles.closeButtonText}>Close</Text>
|
|
|
+ </TouchableOpacity>
|
|
|
+ </View>
|
|
|
+ <ScrollView>
|
|
|
+ {widgetData.map((widget, index) => (
|
|
|
+ widgetItem(index, widget)
|
|
|
+ ))}
|
|
|
+ </ScrollView>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </Modal>
|
|
|
+
|
|
|
+ <Modal visible={textEditModalVisible} transparent={true} animationType="fade">
|
|
|
+ <View style={styles.editTextModalContainer}>
|
|
|
+ <View style={styles.editTextModalContent}>
|
|
|
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
|
|
|
+ <Text style={styles.modalTitle}>Edit Text</Text>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ <TextInput
|
|
|
+ style={styles.inputField}
|
|
|
+ value={text}
|
|
|
+ onChangeText={(newText) => setText(newText)}
|
|
|
+ placeholder="Enter text here"
|
|
|
+ multiline={true}
|
|
|
+ numberOfLines={4}
|
|
|
+ />
|
|
|
+
|
|
|
+ <View style={styles.buttonContainer}>
|
|
|
+ <TouchableOpacity onPress={() => {
|
|
|
+ setTextEditModalVisible(false);
|
|
|
+ }} style={styles.button}>
|
|
|
+ <Text style={styles.buttonText}>Cancel</Text>
|
|
|
+ </TouchableOpacity>
|
|
|
+ <TouchableOpacity onPress={async () => {
|
|
|
+ if (currentEditingWidgetIndex !== null && currentEditingWidgetIndex !== undefined) {
|
|
|
+ const updatedWidgetData = [...widgetData];
|
|
|
+ const widget = updatedWidgetData[currentEditingWidgetIndex];
|
|
|
+
|
|
|
+ console.log(JSON.stringify(widget, null, 2));
|
|
|
+ if(widget === undefined) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (widget.type === CPDFWidgetType.TEXT_FIELD) {
|
|
|
+ const textWidget = widget as CPDFTextWidget;
|
|
|
+
|
|
|
+ try {
|
|
|
+ // change textFields text
|
|
|
+ await textWidget.setText(text);
|
|
|
+ await textWidget.updateAp();
|
|
|
+
|
|
|
+ if (updatedWidgetData[currentEditingWidgetIndex]) {
|
|
|
+ (updatedWidgetData[currentEditingWidgetIndex] as CPDFTextWidget).text = text;
|
|
|
+ }
|
|
|
+ setWidgetData(updatedWidgetData);
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Failed to update text widget:", error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ setTextEditModalVisible(false);
|
|
|
+ }
|
|
|
+ }} style={styles.button}>
|
|
|
+ <Text style={styles.buttonText}>Confirm</Text>
|
|
|
+
|
|
|
+ </TouchableOpacity>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </Modal>
|
|
|
+
|
|
|
+ </SafeAreaView>
|
|
|
+ </MenuProvider>
|
|
|
+ );
|
|
|
+
|
|
|
+
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+const styles = StyleSheet.create({
|
|
|
+ toolbar: {
|
|
|
+ height: 56,
|
|
|
+ flexDirection: 'row',
|
|
|
+ alignItems: 'center',
|
|
|
+ justifyContent: 'flex-start',
|
|
|
+ backgroundColor: '#FAFCFF',
|
|
|
+ paddingHorizontal: 4,
|
|
|
+ },
|
|
|
+ toolbarButton: {
|
|
|
+ padding: 8,
|
|
|
+ },
|
|
|
+ toolbarTitle: {
|
|
|
+ flex: 1,
|
|
|
+ color: 'black',
|
|
|
+ fontSize: 16,
|
|
|
+ fontWeight: 'bold',
|
|
|
+ marginStart: 8
|
|
|
+ },
|
|
|
+ menuOption: {
|
|
|
+ padding: 8,
|
|
|
+ fontSize: 14,
|
|
|
+ color: 'black',
|
|
|
+ },
|
|
|
+ modalContainer: {
|
|
|
+ flex: 1,
|
|
|
+ justifyContent: 'flex-end',
|
|
|
+ backgroundColor: 'rgba(3, 3, 3, 0.2)',
|
|
|
+ },
|
|
|
+ modalContent: {
|
|
|
+ width: '100%',
|
|
|
+ maxHeight: '60%',
|
|
|
+ backgroundColor: 'white',
|
|
|
+ padding: 16,
|
|
|
+ borderTopLeftRadius: 10,
|
|
|
+ borderTopRightRadius: 10,
|
|
|
+ },
|
|
|
+ modalTitle: {
|
|
|
+ fontSize: 18,
|
|
|
+ fontWeight: '700',
|
|
|
+ marginBottom: 10,
|
|
|
+ color: 'black'
|
|
|
+ },
|
|
|
+ widgetItem: {
|
|
|
+ fontSize: 14,
|
|
|
+ paddingVertical: 5,
|
|
|
+ fontWeight: '500',
|
|
|
+ color: 'black'
|
|
|
+ },
|
|
|
+ widgetBody: {
|
|
|
+ fontSize: 14,
|
|
|
+ paddingVertical: 5,
|
|
|
+
|
|
|
+ },
|
|
|
+ closeButton: {
|
|
|
+ paddingVertical: 4,
|
|
|
+ paddingHorizontal: 8,
|
|
|
+ marginEnd: 8,
|
|
|
+ },
|
|
|
+ closeButtonText: {
|
|
|
+ color: '#007BFF',
|
|
|
+ fontSize: 14,
|
|
|
+ },
|
|
|
+ editTextModalContainer: {
|
|
|
+ flex: 1,
|
|
|
+ justifyContent: 'center',
|
|
|
+ alignItems: 'center',
|
|
|
+ backgroundColor: 'rgba(3, 3, 3, 0.2)',
|
|
|
+ },
|
|
|
+ editTextModalContent: {
|
|
|
+ width: '80%',
|
|
|
+ backgroundColor: 'white',
|
|
|
+ padding: 16,
|
|
|
+ borderRadius: 10
|
|
|
+ },
|
|
|
+ inputField: {
|
|
|
+ height: 100,
|
|
|
+ borderColor: 'gray',
|
|
|
+ borderWidth: 1,
|
|
|
+ borderRadius: 5,
|
|
|
+ padding: 10,
|
|
|
+ marginBottom: 20,
|
|
|
+ textAlignVertical: 'top',
|
|
|
+ },
|
|
|
+ buttonContainer: {
|
|
|
+ flexDirection: 'row',
|
|
|
+ justifyContent: 'flex-end',
|
|
|
+ },
|
|
|
+ button: {
|
|
|
+ paddingVertical: 10,
|
|
|
+ paddingHorizontal: 20,
|
|
|
+ borderRadius: 5,
|
|
|
+ marginTop: 10,
|
|
|
+ },
|
|
|
+ buttonText: {
|
|
|
+ color: '#1460F3',
|
|
|
+ fontSize: 16,
|
|
|
+ },
|
|
|
+});
|
|
|
+
|
|
|
+export default CPDFWidgetsExampleScreen;
|
|
|
+
|
|
|
+
|
|
|
+
|