|
| 1 | +import 'dart:convert'; |
| 2 | + |
| 3 | +import '../../interfaces/pdf_interface.dart'; |
| 4 | +import '../io/pdf_constants.dart'; |
| 5 | +import '../io/pdf_cross_table.dart'; |
| 6 | +import '../pages/pdf_page.dart'; |
| 7 | +import '../primitives/pdf_array.dart'; |
| 8 | +import '../primitives/pdf_boolean.dart'; |
| 9 | +import '../primitives/pdf_dictionary.dart'; |
| 10 | +import '../primitives/pdf_name.dart'; |
| 11 | +import '../primitives/pdf_number.dart'; |
| 12 | +import '../primitives/pdf_reference_holder.dart'; |
| 13 | +import '../primitives/pdf_stream.dart'; |
| 14 | +import '../primitives/pdf_string.dart'; |
| 15 | + |
| 16 | +/// Internal class |
| 17 | +class FdfDocument { |
| 18 | + /// Internal Consturctor |
| 19 | + FdfDocument(this.dictionary, this.page); |
| 20 | + |
| 21 | + /// Internal field |
| 22 | + late PdfDictionary dictionary; |
| 23 | + |
| 24 | + /// Internal field |
| 25 | + late PdfPage page; |
| 26 | + |
| 27 | + /// Internal field |
| 28 | + String _annotationID = ''; |
| 29 | + |
| 30 | + /// Internal method |
| 31 | + Map<String, dynamic> exportAnnotations( |
| 32 | + int currentID, List<String> annotID, int pageIndex, bool hasAppearance) { |
| 33 | + const String startObject = |
| 34 | + '${PdfOperators.whiteSpace}0${PdfOperators.whiteSpace}${PdfOperators.obj}${PdfOperators.newLine}'; |
| 35 | + const String endObject = |
| 36 | + PdfOperators.newLine + PdfOperators.endobj + PdfOperators.newLine; |
| 37 | + PdfDictionary dictionary = this.dictionary; |
| 38 | + _annotationID = currentID.toString(); |
| 39 | + final List<int> exportData = <int>[]; |
| 40 | + exportData.addAll(utf8.encode('$currentID$startObject<<')); |
| 41 | + final Map<int, IPdfPrimitive> subDictionaries = <int, IPdfPrimitive>{}; |
| 42 | + final List<int> streamReferences = <int>[]; |
| 43 | + annotID.add(_annotationID); |
| 44 | + dictionary.items![PdfName('Page')] = PdfNumber(pageIndex); |
| 45 | + Map<String, dynamic> exportDataDictionary = _getEntriesInDictionary( |
| 46 | + subDictionaries, |
| 47 | + streamReferences, |
| 48 | + currentID, |
| 49 | + dictionary, |
| 50 | + hasAppearance); |
| 51 | + exportData.addAll(exportDataDictionary['exportData'] as List<int>); |
| 52 | + currentID = exportDataDictionary['currentID'] as int; |
| 53 | + dictionary.remove('Page'); |
| 54 | + exportData.addAll(utf8.encode('>>$endObject')); |
| 55 | + while (subDictionaries.isNotEmpty) { |
| 56 | + final List<int> keys = subDictionaries.keys.toList(); |
| 57 | + for (final int key in keys) { |
| 58 | + if (subDictionaries[key] is PdfDictionary) { |
| 59 | + dictionary = subDictionaries[key]! as PdfDictionary; |
| 60 | + if (dictionary.containsKey(PdfDictionaryProperties.type)) { |
| 61 | + final IPdfPrimitive? name = |
| 62 | + dictionary[PdfDictionaryProperties.type]; |
| 63 | + if (name != null && |
| 64 | + name is PdfName && |
| 65 | + name.name == PdfDictionaryProperties.annot) { |
| 66 | + annotID.add(key.toString()); |
| 67 | + dictionary.items![PdfName('Page')] = PdfNumber(pageIndex); |
| 68 | + } |
| 69 | + } |
| 70 | + exportData.addAll(utf8.encode('$key$startObject<<')); |
| 71 | + exportDataDictionary = _getEntriesInDictionary(subDictionaries, |
| 72 | + streamReferences, currentID, dictionary, hasAppearance); |
| 73 | + exportData.addAll(exportDataDictionary['exportData'] as List<int>); |
| 74 | + currentID = exportDataDictionary['currentID'] as int; |
| 75 | + if (dictionary.containsKey('Page')) { |
| 76 | + dictionary.remove('Page'); |
| 77 | + } |
| 78 | + exportData.addAll(utf8.encode('>>')); |
| 79 | + if (streamReferences.contains(key)) { |
| 80 | + exportData.addAll(_appendStream(subDictionaries[key]!)); |
| 81 | + } |
| 82 | + exportData.addAll(utf8.encode(endObject)); |
| 83 | + } else if (subDictionaries[key] is PdfName) { |
| 84 | + final PdfName name = subDictionaries[key]! as PdfName; |
| 85 | + exportData.addAll(utf8.encode('$key$startObject$name$endObject')); |
| 86 | + } else if (subDictionaries[key] is PdfArray) { |
| 87 | + final PdfArray array = subDictionaries[key]! as PdfArray; |
| 88 | + exportData.addAll(utf8.encode('$key$startObject')); |
| 89 | + final Map<String, dynamic> result = _appendArrayElements(array, |
| 90 | + currentID, hasAppearance, subDictionaries, streamReferences); |
| 91 | + exportData.addAll(result['exportData'] as List<int>); |
| 92 | + currentID = result['currentID'] as int; |
| 93 | + exportData.addAll(utf8.encode(endObject)); |
| 94 | + } else if (subDictionaries[key] is PdfBoolean) { |
| 95 | + final PdfBoolean boolean = subDictionaries[key]! as PdfBoolean; |
| 96 | + exportData.addAll(utf8.encode( |
| 97 | + '$key$startObject${boolean.value! ? 'true' : 'false'}$endObject')); |
| 98 | + } else if (subDictionaries[key] is PdfString) { |
| 99 | + final PdfString data = subDictionaries[key]! as PdfString; |
| 100 | + if (data.value != null) { |
| 101 | + exportData.addAll(utf8.encode( |
| 102 | + '$key$startObject(${_getFormattedStringFDF(data.value!)})$endObject')); |
| 103 | + } |
| 104 | + } |
| 105 | + subDictionaries.remove(key); |
| 106 | + } |
| 107 | + } |
| 108 | + currentID++; |
| 109 | + return <String, dynamic>{'exportData': exportData, 'currentID': currentID}; |
| 110 | + } |
| 111 | + |
| 112 | + /// Internal method |
| 113 | + List<int> _appendStream(IPdfPrimitive stream) { |
| 114 | + final List<int> streamData = <int>[]; |
| 115 | + if (stream is PdfStream && |
| 116 | + stream.dataStream != null && |
| 117 | + stream.dataStream!.isNotEmpty) { |
| 118 | + streamData.addAll(utf8.encode('stream${PdfOperators.newLine}')); |
| 119 | + streamData.addAll(stream.dataStream!); |
| 120 | + streamData.addAll(utf8.encode('${PdfOperators.newLine}endstream')); |
| 121 | + } |
| 122 | + return streamData; |
| 123 | + } |
| 124 | + |
| 125 | + Map<String, dynamic> _getEntriesInDictionary( |
| 126 | + Map<int, IPdfPrimitive> dictionaries, |
| 127 | + List<int> streamReferences, |
| 128 | + int currentID, |
| 129 | + PdfDictionary dictionary, |
| 130 | + bool hasAppearance) { |
| 131 | + final List<int> annotationData = <int>[]; |
| 132 | + bool isStream = false; |
| 133 | + final List<PdfName?> keys = dictionary.items!.keys.toList(); |
| 134 | + for (final PdfName? key in keys) { |
| 135 | + if (!hasAppearance && key!.name == PdfDictionaryProperties.ap) { |
| 136 | + continue; |
| 137 | + } |
| 138 | + if (key!.name != PdfDictionaryProperties.p) { |
| 139 | + annotationData.addAll(utf8.encode(key.toString())); |
| 140 | + } |
| 141 | + if (key.name == 'Sound' || |
| 142 | + key.name == PdfDictionaryProperties.f || |
| 143 | + hasAppearance) { |
| 144 | + isStream = true; |
| 145 | + } |
| 146 | + final IPdfPrimitive? primitive = dictionary[key]; |
| 147 | + if (primitive is PdfString) { |
| 148 | + if (primitive.value != null) { |
| 149 | + annotationData.addAll( |
| 150 | + utf8.encode('(${_getFormattedStringFDF(primitive.value!)})')); |
| 151 | + } |
| 152 | + } else if (primitive is PdfName) { |
| 153 | + annotationData.addAll(utf8.encode(primitive.toString())); |
| 154 | + } else if (primitive is PdfArray) { |
| 155 | + final Map<String, dynamic> result = _appendArrayElements( |
| 156 | + primitive, currentID, isStream, dictionaries, streamReferences); |
| 157 | + annotationData.addAll(result['exportData'] as List<int>); |
| 158 | + currentID = result['currentID'] as int; |
| 159 | + } else if (primitive is PdfNumber) { |
| 160 | + annotationData.addAll(utf8.encode(' ${primitive.value!}')); |
| 161 | + } else if (primitive is PdfBoolean) { |
| 162 | + annotationData.addAll(utf8.encode(' ${primitive.value!}')); |
| 163 | + } else if (primitive is PdfDictionary) { |
| 164 | + annotationData.addAll(utf8.encode('<<')); |
| 165 | + final Map<String, dynamic> data = _getEntriesInDictionary(dictionaries, |
| 166 | + streamReferences, currentID, primitive, hasAppearance); |
| 167 | + annotationData.addAll(data['exportData'] as List<int>); |
| 168 | + currentID = data['currentID'] as int; |
| 169 | + annotationData.addAll(utf8.encode('>>')); |
| 170 | + } else if (primitive is PdfReferenceHolder) { |
| 171 | + if (PdfPageHelper.getHelper(page).document != null) { |
| 172 | + final int pageNumber = |
| 173 | + PdfPageHelper.getHelper(page).document!.pages.indexOf(page); |
| 174 | + if (key.name == PdfDictionaryProperties.parent) { |
| 175 | + annotationData.addAll(utf8.encode(' $_annotationID 0 R')); |
| 176 | + annotationData.addAll(utf8.encode('/Page $pageNumber')); |
| 177 | + } else if (key.name == PdfDictionaryProperties.irt) { |
| 178 | + if (primitive.object != null && primitive.object is PdfDictionary) { |
| 179 | + final IPdfPrimitive? inReplyTo = primitive.object; |
| 180 | + if (inReplyTo != null && |
| 181 | + inReplyTo is PdfDictionary && |
| 182 | + inReplyTo.containsKey('NM')) { |
| 183 | + final IPdfPrimitive? name = |
| 184 | + PdfCrossTable.dereference(inReplyTo['NM']); |
| 185 | + if (name != null && name is PdfString) { |
| 186 | + if (name.value != null) { |
| 187 | + annotationData.addAll(utf8 |
| 188 | + .encode('(${_getFormattedStringFDF(name.value!)})')); |
| 189 | + } |
| 190 | + } |
| 191 | + } |
| 192 | + } |
| 193 | + } else if (key.name != PdfDictionaryProperties.p) { |
| 194 | + currentID++; |
| 195 | + annotationData.addAll(utf8.encode(' $currentID 0 R')); |
| 196 | + if (isStream) { |
| 197 | + streamReferences.add(currentID); |
| 198 | + } |
| 199 | + if (primitive.object != null) { |
| 200 | + dictionaries[currentID] = primitive.object!; |
| 201 | + } |
| 202 | + } |
| 203 | + } |
| 204 | + } |
| 205 | + isStream = false; |
| 206 | + } |
| 207 | + return <String, dynamic>{ |
| 208 | + 'exportData': annotationData, |
| 209 | + 'currentID': currentID |
| 210 | + }; |
| 211 | + } |
| 212 | + |
| 213 | + String _getFormattedStringFDF(String value) { |
| 214 | + String result = ''; |
| 215 | + for (int i = 0; i < value.length; i++) { |
| 216 | + final int c = value.codeUnitAt(i); |
| 217 | + if (c == 40 || c == 41) { |
| 218 | + result += r'\'; |
| 219 | + } |
| 220 | + if (c == 13 || c == 10) { |
| 221 | + if (c == 13) { |
| 222 | + result += r'\r'; |
| 223 | + } |
| 224 | + if (c == 10) { |
| 225 | + result += r'\n'; |
| 226 | + } |
| 227 | + continue; |
| 228 | + } |
| 229 | + result += String.fromCharCode(c); |
| 230 | + } |
| 231 | + return result; |
| 232 | + } |
| 233 | + |
| 234 | + Map<String, dynamic> _appendArrayElements( |
| 235 | + PdfArray array, |
| 236 | + int currentID, |
| 237 | + bool isStream, |
| 238 | + Map<int, IPdfPrimitive> dictionaries, |
| 239 | + List<int> streamReferences) { |
| 240 | + final List<int> arrayData = <int>[]; |
| 241 | + arrayData.addAll(utf8.encode('[')); |
| 242 | + if (array.elements.isNotEmpty) { |
| 243 | + final int count = array.elements.length; |
| 244 | + for (int i = 0; i < count; i++) { |
| 245 | + final IPdfPrimitive? element = array.elements[i]; |
| 246 | + if (i != 0 && |
| 247 | + element != null && |
| 248 | + (element is PdfNumber || |
| 249 | + element is PdfReferenceHolder || |
| 250 | + element is PdfBoolean)) { |
| 251 | + arrayData.addAll(utf8.encode(' ')); |
| 252 | + } |
| 253 | + final Map<String, dynamic> result = _appendElement( |
| 254 | + element!, currentID, isStream, dictionaries, streamReferences); |
| 255 | + arrayData.addAll(result['exportData'] as List<int>); |
| 256 | + currentID = result['currentID'] as int; |
| 257 | + } |
| 258 | + } |
| 259 | + arrayData.addAll(utf8.encode(']')); |
| 260 | + return <String, dynamic>{'exportData': arrayData, 'currentID': currentID}; |
| 261 | + } |
| 262 | + |
| 263 | + Map<String, dynamic> _appendElement( |
| 264 | + IPdfPrimitive element, |
| 265 | + int currentID, |
| 266 | + bool isStream, |
| 267 | + Map<int, IPdfPrimitive> dictionaries, |
| 268 | + List<int> streamReferences) { |
| 269 | + final List<int> exportData = <int>[]; |
| 270 | + if (element is PdfNumber) { |
| 271 | + exportData.addAll(utf8.encode(element.value!.toString())); |
| 272 | + } else if (element is PdfName) { |
| 273 | + exportData.addAll(utf8.encode(element.toString())); |
| 274 | + } else if (element is PdfString) { |
| 275 | + if (element.value != null) { |
| 276 | + exportData |
| 277 | + .addAll(utf8.encode('(${_getFormattedStringFDF(element.value!)})')); |
| 278 | + } |
| 279 | + } else if (element is PdfBoolean) { |
| 280 | + exportData.addAll(utf8.encode(element.value!.toString())); |
| 281 | + } else if (element is PdfReferenceHolder) { |
| 282 | + currentID++; |
| 283 | + if (isStream) { |
| 284 | + streamReferences.add(currentID); |
| 285 | + } |
| 286 | + if (element.object != null) { |
| 287 | + dictionaries[currentID] = element.object!; |
| 288 | + } |
| 289 | + exportData.addAll(utf8.encode('$currentID 0 R')); |
| 290 | + } else if (element is PdfArray) { |
| 291 | + final Map<String, dynamic> result = _appendArrayElements( |
| 292 | + element, currentID, isStream, dictionaries, streamReferences); |
| 293 | + currentID = result['currentID'] as int; |
| 294 | + exportData.addAll(result['exportData'] as List<int>); |
| 295 | + } else if (element is PdfDictionary) { |
| 296 | + exportData.addAll(utf8.encode('<<')); |
| 297 | + final Map<String, dynamic> data = _getEntriesInDictionary( |
| 298 | + dictionaries, streamReferences, currentID, element, isStream); |
| 299 | + exportData.addAll(data['exportData'] as List<int>); |
| 300 | + currentID = data['currentID'] as int; |
| 301 | + exportData.addAll(utf8.encode('>>')); |
| 302 | + } |
| 303 | + return <String, dynamic>{'exportData': exportData, 'currentID': currentID}; |
| 304 | + } |
| 305 | +} |
0 commit comments