[Editor] Add the possibility to update an existing annotation with some new properties when saving or printing
This commit is contained in:
		
							parent
							
								
									f8a84a6f03
								
							
						
					
					
						commit
						1a047f843c
					
				| @ -264,6 +264,9 @@ class AnnotationFactory { | ||||
|     const promises = []; | ||||
| 
 | ||||
|     for (const annotation of annotations) { | ||||
|       if (annotation.deleted) { | ||||
|         continue; | ||||
|       } | ||||
|       switch (annotation.annotationType) { | ||||
|         case AnnotationEditorType.FREETEXT: | ||||
|           if (!baseFontRef) { | ||||
| @ -308,6 +311,9 @@ class AnnotationFactory { | ||||
|     const { isOffscreenCanvasSupported } = evaluator.options; | ||||
|     const promises = []; | ||||
|     for (const annotation of annotations) { | ||||
|       if (annotation.deleted) { | ||||
|         continue; | ||||
|       } | ||||
|       switch (annotation.annotationType) { | ||||
|         case AnnotationEditorType.FREETEXT: | ||||
|           promises.push( | ||||
| @ -466,6 +472,7 @@ class Annotation { | ||||
|     const MK = dict.get("MK"); | ||||
|     this.setBorderAndBackgroundColors(MK); | ||||
|     this.setRotation(MK); | ||||
|     this.ref = params.ref instanceof Ref ? params.ref : null; | ||||
| 
 | ||||
|     this._streams = []; | ||||
|     if (this.appearance) { | ||||
| @ -1467,7 +1474,7 @@ class MarkupAnnotation extends Annotation { | ||||
|   } | ||||
| 
 | ||||
|   static async createNewAnnotation(xref, annotation, dependencies, params) { | ||||
|     const annotationRef = xref.getNewTemporaryRef(); | ||||
|     const annotationRef = annotation.ref || xref.getNewTemporaryRef(); | ||||
|     const ap = await this.createNewAppearanceStream(annotation, xref, params); | ||||
|     const buffer = []; | ||||
|     let annotationDict; | ||||
| @ -1497,11 +1504,17 @@ class MarkupAnnotation extends Annotation { | ||||
|     const ap = await this.createNewAppearanceStream(annotation, xref, params); | ||||
|     const annotationDict = this.createNewDict(annotation, xref, { ap }); | ||||
| 
 | ||||
|     return new this.prototype.constructor({ | ||||
|     const newAnnotation = new this.prototype.constructor({ | ||||
|       dict: annotationDict, | ||||
|       xref, | ||||
|       isOffscreenCanvasSupported: params.isOffscreenCanvasSupported, | ||||
|     }); | ||||
| 
 | ||||
|     if (annotation.ref) { | ||||
|       newAnnotation.ref = newAnnotation.refToReplace = annotation.ref; | ||||
|     } | ||||
| 
 | ||||
|     return newAnnotation; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @ -1511,7 +1524,6 @@ class WidgetAnnotation extends Annotation { | ||||
| 
 | ||||
|     const { dict, xref } = params; | ||||
|     const data = this.data; | ||||
|     this.ref = params.ref; | ||||
|     this._needAppearances = params.needAppearances; | ||||
| 
 | ||||
|     data.annotationType = AnnotationType.WIDGET; | ||||
|  | ||||
| @ -41,7 +41,7 @@ import { | ||||
|   XRefEntryException, | ||||
|   XRefParseException, | ||||
| } from "./core_utils.js"; | ||||
| import { Dict, isName, Name, Ref } from "./primitives.js"; | ||||
| import { Dict, isName, isRefsEqual, Name, Ref, RefSet } from "./primitives.js"; | ||||
| import { getXfaFontDict, getXfaFontName } from "./xfa_fonts.js"; | ||||
| import { BaseStream } from "./base_stream.js"; | ||||
| import { calculateMD5 } from "./crypto.js"; | ||||
| @ -258,6 +258,24 @@ class Page { | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   #replaceIdByRef(annotations, deletedAnnotations) { | ||||
|     for (const annotation of annotations) { | ||||
|       if (annotation.id) { | ||||
|         const ref = Ref.fromString(annotation.id); | ||||
|         if (!ref) { | ||||
|           warn(`A non-linked annotation cannot be modified: ${annotation.id}`); | ||||
|           continue; | ||||
|         } | ||||
|         if (annotation.deleted) { | ||||
|           deletedAnnotations.put(ref); | ||||
|           continue; | ||||
|         } | ||||
|         annotation.ref = ref; | ||||
|         delete annotation.id; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   async saveNewAnnotations(handler, task, annotations) { | ||||
|     if (this.xfaFactory) { | ||||
|       throw new Error("XFA: Cannot save new annotations."); | ||||
| @ -276,8 +294,13 @@ class Page { | ||||
|       options: this.evaluatorOptions, | ||||
|     }); | ||||
| 
 | ||||
|     const deletedAnnotations = new RefSet(); | ||||
|     this.#replaceIdByRef(annotations, deletedAnnotations); | ||||
| 
 | ||||
|     const pageDict = this.pageDict; | ||||
|     const annotationsArray = this.annotations.slice(); | ||||
|     const annotationsArray = this.annotations.filter( | ||||
|       a => !(a instanceof Ref && deletedAnnotations.has(a)) | ||||
|     ); | ||||
|     const newData = await AnnotationFactory.saveNewAnnotations( | ||||
|       partialEvaluator, | ||||
|       task, | ||||
| @ -401,11 +424,14 @@ class Page { | ||||
|     const newAnnotationsByPage = !this.xfaFactory | ||||
|       ? getNewAnnotationsMap(annotationStorage) | ||||
|       : null; | ||||
|     let deletedAnnotations = null; | ||||
| 
 | ||||
|     let newAnnotationsPromise = Promise.resolve(null); | ||||
|     if (newAnnotationsByPage) { | ||||
|       const newAnnotations = newAnnotationsByPage.get(this.pageIndex); | ||||
|       if (newAnnotations) { | ||||
|         deletedAnnotations = new RefSet(); | ||||
|         this.#replaceIdByRef(newAnnotations, deletedAnnotations); | ||||
|         newAnnotationsPromise = AnnotationFactory.printNewAnnotations( | ||||
|           partialEvaluator, | ||||
|           task, | ||||
| @ -446,6 +472,25 @@ class Page { | ||||
|       newAnnotationsPromise, | ||||
|     ]).then(function ([pageOpList, annotations, newAnnotations]) { | ||||
|       if (newAnnotations) { | ||||
|         // Some annotations can already exist (if it has the refToReplace
 | ||||
|         // property). In this case, we replace the old annotation by the new
 | ||||
|         // one.
 | ||||
|         annotations = annotations.filter( | ||||
|           a => !(a.ref && deletedAnnotations.has(a.ref)) | ||||
|         ); | ||||
|         for (let i = 0, ii = newAnnotations.length; i < ii; i++) { | ||||
|           const newAnnotation = newAnnotations[i]; | ||||
|           if (newAnnotation.refToReplace) { | ||||
|             const j = annotations.findIndex( | ||||
|               a => a.ref && isRefsEqual(a.ref, newAnnotation.refToReplace) | ||||
|             ); | ||||
|             if (j >= 0) { | ||||
|               annotations.splice(j, 1, newAnnotation); | ||||
|               newAnnotations.splice(i--, 1); | ||||
|               ii--; | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|         annotations = annotations.concat(newAnnotations); | ||||
|       } | ||||
|       if ( | ||||
|  | ||||
| @ -279,6 +279,23 @@ class Ref { | ||||
|     return `${this.num}R${this.gen}`; | ||||
|   } | ||||
| 
 | ||||
|   static fromString(str) { | ||||
|     const ref = RefCache[str]; | ||||
|     if (ref) { | ||||
|       return ref; | ||||
|     } | ||||
|     const m = /^(\d+)R(\d*)$/.exec(str); | ||||
|     if (!m || m[1] === "0") { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     // eslint-disable-next-line no-restricted-syntax
 | ||||
|     return (RefCache[str] = new Ref( | ||||
|       parseInt(m[1]), | ||||
|       !m[2] ? 0 : parseInt(m[2]) | ||||
|     )); | ||||
|   } | ||||
| 
 | ||||
|   static get(num, gen) { | ||||
|     const key = gen === 0 ? `${num}R` : `${num}R${gen}`; | ||||
|     // eslint-disable-next-line no-restricted-syntax
 | ||||
|  | ||||
							
								
								
									
										1
									
								
								test/pdfs/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								test/pdfs/.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -599,3 +599,4 @@ | ||||
| !bug1529502.pdf | ||||
| !issue16500.pdf | ||||
| !issue16538.pdf | ||||
| !freetexts.pdf | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								test/pdfs/freetexts.pdf
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/pdfs/freetexts.pdf
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							| @ -7720,5 +7720,91 @@ | ||||
|    "md5": "35b691c3a343f4531bd287b001b67a77", | ||||
|    "rounds": 1, | ||||
|    "type": "eq" | ||||
|   }, | ||||
|    { | ||||
|       "id": "freetexts-editor-print", | ||||
|       "file": "pdfs/freetexts.pdf", | ||||
|       "md5": "da1310a25ab796c1201810070d5032a3", | ||||
|       "rounds": 1, | ||||
|       "lastPage": 1, | ||||
|       "type": "eq", | ||||
|       "print": true, | ||||
|       "annotationStorage": { | ||||
|          "pdfjs_internal_editor_0": { | ||||
|             "annotationType": 3, | ||||
|             "color": [0, 0, 255], | ||||
|             "fontSize": 21, | ||||
|             "value": "The content must have been changed", | ||||
|             "pageIndex": 0, | ||||
|             "rect": [ 92.58600000000003, 415.2426115258789, 449.1110015258789, 447.59261], | ||||
|             "rotation": 0, | ||||
|             "id": "36R" | ||||
|          } | ||||
|       } | ||||
|    }, | ||||
|    { | ||||
|       "id": "freetexts-editor-save", | ||||
|       "file": "pdfs/freetexts.pdf", | ||||
|       "md5": "da1310a25ab796c1201810070d5032a3", | ||||
|       "rounds": 1, | ||||
|       "lastPage": 1, | ||||
|       "type": "eq", | ||||
|       "save": true, | ||||
|       "print": true, | ||||
|       "annotationStorage": { | ||||
|          "pdfjs_internal_editor_0": { | ||||
|             "annotationType": 3, | ||||
|             "color": [255, 0, 0], | ||||
|             "fontSize": 21, | ||||
|             "value": "The content must have been changed", | ||||
|             "pageIndex": 0, | ||||
|             "rect": [ 92.58600000000003, 415.2426115258789, 449.1110015258789, 447.59261], | ||||
|             "rotation": 0, | ||||
|             "id": "36R" | ||||
|          } | ||||
|       } | ||||
|    }, | ||||
|    { | ||||
|       "id": "freetexts-delete-editor-print", | ||||
|       "file": "pdfs/freetexts.pdf", | ||||
|       "md5": "da1310a25ab796c1201810070d5032a3", | ||||
|       "rounds": 1, | ||||
|       "lastPage": 1, | ||||
|       "type": "eq", | ||||
|       "print": true, | ||||
|       "annotationStorage": { | ||||
|          "pdfjs_internal_editor_0": { | ||||
|             "pageIndex": 0, | ||||
|             "deleted": true, | ||||
|             "id": "36R" | ||||
|          }, | ||||
|          "pdfjs_internal_editor_1": { | ||||
|             "pageIndex": 0, | ||||
|             "deleted": true, | ||||
|             "id": "53R" | ||||
|          } | ||||
|       } | ||||
|    }, | ||||
|    { | ||||
|       "id": "freetexts-delete-editor-save", | ||||
|       "file": "pdfs/freetexts.pdf", | ||||
|       "md5": "da1310a25ab796c1201810070d5032a3", | ||||
|       "rounds": 1, | ||||
|       "lastPage": 1, | ||||
|       "type": "eq", | ||||
|       "save": true, | ||||
|       "print": true, | ||||
|       "annotationStorage": { | ||||
|          "pdfjs_internal_editor_0": { | ||||
|             "pageIndex": 0, | ||||
|             "deleted": true, | ||||
|             "id": "36R" | ||||
|          }, | ||||
|          "pdfjs_internal_editor_1": { | ||||
|             "pageIndex": 0, | ||||
|             "deleted": true, | ||||
|             "id": "53R" | ||||
|          } | ||||
|       } | ||||
|    } | ||||
| ] | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user