From b7f26c47c1f355e2424f4834750d84836a7b9128 Mon Sep 17 00:00:00 2001 From: Martynas Bagdonas Date: Mon, 28 Feb 2022 20:10:20 +0200 Subject: [PATCH] Split annotation if position data exceeds the limit --- chrome/content/zotero/xpcom/annotations.js | 106 +++++++++++++++++++++ test/tests/annotationsTest.js | 65 +++++++++++++ 2 files changed, 171 insertions(+) diff --git a/chrome/content/zotero/xpcom/annotations.js b/chrome/content/zotero/xpcom/annotations.js index d69b26bd86..07654ffcf6 100644 --- a/chrome/content/zotero/xpcom/annotations.js +++ b/chrome/content/zotero/xpcom/annotations.js @@ -26,6 +26,7 @@ "use strict"; Zotero.Annotations = new function () { + Zotero.defineProperty(this, 'ANNOTATION_POSITION_MAX_SIZE', { value: 65000 }); // Keep in sync with items.js::loadAnnotations() Zotero.defineProperty(this, 'ANNOTATION_TYPE_HIGHLIGHT', { value: 1 }); Zotero.defineProperty(this, 'ANNOTATION_TYPE_NOTE', { value: 2 }); @@ -234,4 +235,109 @@ Zotero.Annotations = new function () { return item; }; + + /** + * Split annotation if position exceed the limit + * + * @param {Object} annotation + * @returns {Array} annotations + */ + this.splitAnnotationJSON = function (annotation) { + let splitAnnotations = []; + let tmpAnnotation = null; + let totalLength = 0; + if (annotation.position.rects) { + for (let i = 0; i < annotation.position.rects.length; i++) { + let rect = annotation.position.rects[i]; + if (!tmpAnnotation) { + tmpAnnotation = JSON.parse(JSON.stringify(annotation)); + tmpAnnotation.key = Zotero.DataObjectUtilities.generateKey(); + tmpAnnotation.position.rects = []; + totalLength = JSON.stringify(tmpAnnotation.position).length; + } + // [], + let length = rect.join(',').length + 3; + if (totalLength + length <= this.ANNOTATION_POSITION_MAX_SIZE) { + tmpAnnotation.position.rects.push(rect); + totalLength += length; + } + else if (!tmpAnnotation.position.rects.length) { + throw new Error(`Cannot fit single 'rect' into 'position'`); + } + else { + splitAnnotations.push(tmpAnnotation); + tmpAnnotation = null; + i--; + } + } + if (tmpAnnotation) { + splitAnnotations.push(tmpAnnotation); + } + } + else if (annotation.position.paths) { + for (let i = 0; i < annotation.position.paths.length; i++) { + let path = annotation.position.paths[i]; + for (let j = 0; j < path.length; j += 2) { + if (!tmpAnnotation) { + tmpAnnotation = JSON.parse(JSON.stringify(annotation)); + tmpAnnotation.key = Zotero.DataObjectUtilities.generateKey(); + tmpAnnotation.position.paths = [[]]; + totalLength = JSON.stringify(tmpAnnotation.position).length; + } + let point = [path[j], path[j + 1]]; + // 1,2, + let length = point.join(',').length + 1; + if (totalLength + length <= this.ANNOTATION_POSITION_MAX_SIZE) { + tmpAnnotation.position.paths[tmpAnnotation.position.paths.length - 1].push(...point); + totalLength += length; + } + else if (tmpAnnotation.position.paths.length === 1 + && !tmpAnnotation.position.paths[tmpAnnotation.position.paths.length - 1].length) { + throw new Error(`Cannot fit single point into 'position'`); + } + else { + splitAnnotations.push(tmpAnnotation); + tmpAnnotation = null; + j -= 2; + } + } + // If not the last path + if (i !== annotation.position.paths.length - 1) { + // [], + totalLength += 3; + tmpAnnotation.position.paths.push([]); + } + } + if (tmpAnnotation) { + splitAnnotations.push(tmpAnnotation); + } + } + return splitAnnotations; + }; + + /** + * Split annotations + * + * @param {Zotero.Item[]} items + * @returns {Promise} + */ + this.splitAnnotations = async function (items) { + if (!Array.isArray(items)) { + items = [items]; + } + if (!items.every(item => item.isAnnotation())) { + throw new Error('All items must be annotations'); + } + for (let item of items) { + if (item.annotationPosition.length <= this.ANNOTATION_POSITION_MAX_SIZE) { + continue; + } + let annotation = await this.toJSON(item); + let splitAnnotations = this.splitAnnotationJSON(annotation); + for (let splitAnnotation of splitAnnotations) { + await this.saveFromJSON(item.parentItem, splitAnnotation); + } + await item.eraseTx(); + } + }; }; diff --git a/test/tests/annotationsTest.js b/test/tests/annotationsTest.js index dc555da1d6..fa97d7f8ba 100644 --- a/test/tests/annotationsTest.js +++ b/test/tests/annotationsTest.js @@ -338,4 +338,69 @@ describe("Zotero.Annotations", function() { assert.isNull(annotation.annotationPageLabel); }); }); + + describe("#splitAnnotations()", function () { + it("should split a highlight annotation", async function () { + await Zotero.Items.erase(attachment.getAnnotations().map(x => x.id)); + let annotation = await createAnnotation('highlight', attachment); + let position = { + pageIndex: 1, + rects: [] + }; + for (let i = 0; i < 10000; i++) { + position.rects.push([100, 200, 100, 200]); + } + annotation.annotationPosition = JSON.stringify(position); + annotation.annotationText = 'test'; + await annotation.saveTx(); + + await Zotero.Annotations.splitAnnotations([annotation]); + + let splitAnnotations = attachment.getAnnotations(); + assert.equal(splitAnnotations.length, 3); + assert.equal(splitAnnotations[0].annotationPosition.length, 64987); + assert.equal(splitAnnotations[1].annotationPosition.length, 64987); + assert.equal(splitAnnotations[2].annotationPosition.length, 50101); + assert.equal(splitAnnotations[0].annotationText, 'test'); + assert.equal(splitAnnotations[1].annotationText, 'test'); + assert.equal(splitAnnotations[2].annotationText, 'test'); + + assert.equal(Zotero.Items.get(annotation.id), false); + await Zotero.Items.erase(splitAnnotations.map(x => x.id)); + }); + + it("should split an ink annotation", async function () { + await Zotero.Items.erase(attachment.getAnnotations().map(x => x.id)); + let annotation = await createAnnotation('ink', attachment); + let position = { + pageIndex: 1, + width: 2, + paths: [] + }; + for (let i = 0; i < 100; i++) { + let path = []; + for (let j = 0; j < 200; j++) { + path.push(100, 200); + } + position.paths.push(path); + } + annotation.annotationPosition = JSON.stringify(position); + annotation.annotationComment = 'test'; + await annotation.saveTx(); + + await Zotero.Annotations.splitAnnotations([annotation]); + + let splitAnnotations = attachment.getAnnotations(); + assert.equal(splitAnnotations.length, 3); + assert.equal(splitAnnotations[0].annotationPosition.length, 64957); + assert.equal(splitAnnotations[1].annotationPosition.length, 64951); + assert.equal(splitAnnotations[2].annotationPosition.length, 30401); + assert.equal(splitAnnotations[0].annotationComment, 'test'); + assert.equal(splitAnnotations[1].annotationComment, 'test'); + assert.equal(splitAnnotations[2].annotationComment, 'test'); + + assert.equal(Zotero.Items.get(annotation.id), false); + await Zotero.Items.erase(splitAnnotations.map(x => x.id)); + }); + }); }) \ No newline at end of file