]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Enhancement: better TIFF display support (#8087)
authorshamoon <4887959+shamoon@users.noreply.github.com>
Sun, 1 Dec 2024 19:46:19 +0000 (11:46 -0800)
committerGitHub <noreply@github.com>
Sun, 1 Dec 2024 19:46:19 +0000 (11:46 -0800)
src-ui/package-lock.json
src-ui/package.json
src-ui/src/app/components/document-detail/document-detail.component.html
src-ui/src/app/components/document-detail/document-detail.component.scss
src-ui/src/app/components/document-detail/document-detail.component.spec.ts
src-ui/src/app/components/document-detail/document-detail.component.ts

index 101100f946d5aaeee532d2d2d8aa57c15c12a0ff..15451b598a6affda750c7d3ae1619df3c23f68d6 100644 (file)
@@ -33,6 +33,7 @@
         "ngx-ui-tour-ng-bootstrap": "^15.0.0",
         "rxjs": "^7.8.1",
         "tslib": "^2.8.1",
+        "utif": "^3.1.0",
         "uuid": "^11.0.2",
         "zone.js": "^0.14.8"
       },
         "node": "^16.14.0 || >=18.0.0"
       }
     },
+    "node_modules/pako": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+      "license": "(MIT AND Zlib)"
+    },
     "node_modules/parent-module": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
         "requires-port": "^1.0.0"
       }
     },
+    "node_modules/utif": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/utif/-/utif-3.1.0.tgz",
+      "integrity": "sha512-WEo4D/xOvFW53K5f5QTaTbbiORcm2/pCL9P6qmJnup+17eYfKaEhDeX9PeQkuyEoIxlbGklDuGl8xwuXYMrrXQ==",
+      "license": "MIT",
+      "dependencies": {
+        "pako": "^1.0.5"
+      }
+    },
     "node_modules/util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
index f607655ce4a96882970952bbc6767c0fd590659a..e2b2b656906e3de2dc0297d5a3c3385aee15aec2 100644 (file)
@@ -35,6 +35,7 @@
     "ngx-ui-tour-ng-bootstrap": "^15.0.0",
     "rxjs": "^7.8.1",
     "tslib": "^2.8.1",
+    "utif": "^3.1.0",
     "uuid": "^11.0.2",
     "zone.js": "^0.14.8"
   },
index 6a39b13bd21b5818b9170c9035266be65b250fb4..486277c21d9dfa5bc8a33793d74b644410603fd7 100644 (file)
           <img [src]="previewUrl | safeUrl" width="100%" height="100%" alt="{{title}}" />
         </div>
       }
+      @case (ContentRenderType.TIFF) {
+        @if (!tiffError) {
+          <div class="preview-sticky">
+            <img [src]="tiffURL" width="100%" height="100%" alt="{{title}}" />
+          </div>
+        } @else {
+          <div class="preview-sticky bg-light p-3 overflow-auto whitespace-preserve" width="100%">{{tiffError}}</div>
+        }
+      }
       @case (ContentRenderType.Other) {
         <object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
       }
index f61e20e83dfa4ee010d8926287c2238d8fd063da..e3d17476b14869d4004c67831d47128ffb78a6fe 100644 (file)
@@ -61,6 +61,7 @@ textarea.rtl {
   width: 100%;
   height: 100%;
   object-fit: contain;
+  object-position: top;
 }
 
 .thumb-preview {
index 41a576f01f4c5942ca09fd325ee024a7e769ce5c..46b72cb4e26d7ecf7978111ba8cdb554f3f500fd 100644 (file)
@@ -1270,4 +1270,46 @@ describe('DocumentDetailComponent', () => {
     expect(component.createDisabled(DataType.StoragePath)).toBeFalsy()
     expect(component.createDisabled(DataType.Tag)).toBeFalsy()
   })
+
+  it('should call tryRenderTiff when no archive and file is tiff', () => {
+    initNormally()
+    const tiffRenderSpy = jest.spyOn(
+      DocumentDetailComponent.prototype as any,
+      'tryRenderTiff'
+    )
+    const doc = Object.assign({}, component.document)
+    doc.archived_file_name = null
+    doc.mime_type = 'image/tiff'
+    jest
+      .spyOn(documentService, 'getMetadata')
+      .mockReturnValue(
+        of({ has_archive_version: false, original_mime_type: 'image/tiff' })
+      )
+    component.updateComponent(doc)
+    fixture.detectChanges()
+    expect(component.archiveContentRenderType).toEqual(
+      component.ContentRenderType.TIFF
+    )
+    expect(tiffRenderSpy).toHaveBeenCalled()
+  })
+
+  it('should try to render tiff and show error if failed', () => {
+    initNormally()
+    // just the text request
+    httpTestingController.expectOne(component.previewUrl)
+
+    // invalid tiff
+    component['tryRenderTiff']()
+    httpTestingController
+      .expectOne(component.previewUrl)
+      .flush(new ArrayBuffer(100)) // arraybuffer
+    expect(component.tiffError).not.toBeUndefined()
+
+    // http error
+    component['tryRenderTiff']()
+    httpTestingController
+      .expectOne(component.previewUrl)
+      .error(new ErrorEvent('failed'))
+    expect(component.tiffError).not.toBeUndefined()
+  })
 })
index 2842509fc692a73a05d34ce8aad351b8667f9545..f1afd95c05b17faba06d459d3e8f8f8c7ad24b39 100644 (file)
@@ -72,6 +72,7 @@ import { DeletePagesConfirmDialogComponent } from '../common/confirm-dialog/dele
 import { HotKeyService } from 'src/app/services/hot-key.service'
 import { PDFDocumentProxy } from 'ng2-pdf-viewer'
 import { DataType } from 'src/app/data/datatype'
+import * as UTIF from 'utif'
 
 enum DocumentDetailNavIDs {
   Details = 1,
@@ -89,6 +90,7 @@ enum ContentRenderType {
   Text = 'text',
   Other = 'other',
   Unknown = 'unknown',
+  TIFF = 'tiff',
 }
 
 enum ZoomSetting {
@@ -136,6 +138,8 @@ export class DocumentDetailComponent
   downloadUrl: string
   downloadOriginalUrl: string
   previewLoaded: boolean = false
+  tiffURL: string
+  tiffError: string
 
   correspondents: Correspondent[]
   documentTypes: DocumentType[]
@@ -244,6 +248,8 @@ export class DocumentDetailComponent
       ['text/plain', 'application/csv', 'text/csv'].includes(mimeType)
     ) {
       return ContentRenderType.Text
+    } else if (mimeType.indexOf('tiff') >= 0) {
+      return ContentRenderType.TIFF
     } else if (mimeType?.indexOf('image/') === 0) {
       return ContentRenderType.Image
     }
@@ -542,6 +548,9 @@ export class DocumentDetailComponent
     this.document = doc
     this.requiresPassword = false
     this.updateFormForCustomFields()
+    if (this.archiveContentRenderType === ContentRenderType.TIFF) {
+      this.tryRenderTiff()
+    }
     this.documentsService
       .getMetadata(doc.id)
       .pipe(
@@ -1278,4 +1287,45 @@ export class DocumentDetailComponent
           })
       })
   }
+
+  private tryRenderTiff() {
+    this.http.get(this.previewUrl, { responseType: 'arraybuffer' }).subscribe({
+      next: (res) => {
+        /* istanbul ignore next */
+        try {
+          // See UTIF.js > _imgLoaded
+          const tiffIfds: any[] = UTIF.decode(res)
+          var vsns = tiffIfds,
+            ma = 0,
+            page = vsns[0]
+          if (tiffIfds[0].subIFD) vsns = vsns.concat(tiffIfds[0].subIFD)
+          for (var i = 0; i < vsns.length; i++) {
+            var img = vsns[i]
+            if (img['t258'] == null || img['t258'].length < 3) continue
+            var ar = img['t256'] * img['t257']
+            if (ar > ma) {
+              ma = ar
+              page = img
+            }
+          }
+          UTIF.decodeImage(res, page, tiffIfds)
+          const rgba = UTIF.toRGBA8(page)
+          const { width: w, height: h } = page
+          var cnv = document.createElement('canvas')
+          cnv.width = w
+          cnv.height = h
+          var ctx = cnv.getContext('2d'),
+            imgd = ctx.createImageData(w, h)
+          for (var i = 0; i < rgba.length; i++) imgd.data[i] = rgba[i]
+          ctx.putImageData(imgd, 0, 0)
+          this.tiffURL = cnv.toDataURL()
+        } catch (err) {
+          this.tiffError = $localize`An error occurred loading tiff: ${err.toString()}`
+        }
+      },
+      error: (err) => {
+        this.tiffError = $localize`An error occurred loading tiff: ${err.toString()}`
+      },
+    })
+  }
 }