diff --git a/lib/core/ng-package.json b/lib/core/ng-package.json
index 1a536e6a39..244f97c25d 100644
--- a/lib/core/ng-package.json
+++ b/lib/core/ng-package.json
@@ -2,11 +2,6 @@
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/libs/core",
"assets": [
- {
- "glob": "pdf.worker.mjs",
- "input": "./assets/pdfjs",
- "output": "assets/pdfjs"
- },
{
"glob": "assets/**/*",
"input": "./src/lib",
@@ -38,7 +33,6 @@
"allowedNonPeerDependencies": [
"cropperjs",
"angular-oauth2-oidc",
- "date-fns",
- "rxjs"
+ "date-fns"
]
}
diff --git a/lib/core/package.json b/lib/core/package.json
index 73ead1cc4d..423ee3c28b 100644
--- a/lib/core/package.json
+++ b/lib/core/package.json
@@ -19,8 +19,7 @@
"dependencies": {
"cropperjs": "^1.6.2",
"angular-oauth2-oidc": "19.0.0",
- "date-fns": "^2.30.0",
- "rxjs": "7.8.2"
+ "date-fns": "^2.30.0"
},
"peerDependencies": {
"@angular/animations": ">=16.0.0",
@@ -37,7 +36,13 @@
"@alfresco/js-api": ">=9.4.1",
"@alfresco/adf-extensions": ">=8.4.1",
"minimatch": ">=10.0.0",
- "pdfjs-dist": ">=3.3.122"
+ "pdfjs-dist": ">=3.3.122",
+ "rxjs": ">=7.8.0"
+ },
+ "peerDependenciesMeta": {
+ "pdfjs-dist": {
+ "optional": true
+ }
},
"keywords": [
"core",
diff --git a/lib/core/schematics/migrations/9_0_0/README.md b/lib/core/schematics/migrations/9_0_0/README.md
new file mode 100644
index 0000000000..04c323c82a
--- /dev/null
+++ b/lib/core/schematics/migrations/9_0_0/README.md
@@ -0,0 +1,91 @@
+# Migration Schematic: migrate-pdf-viewer-imports (v9.0.0)
+
+Automatically migrates consumer projects from the monolithic `@alfresco/adf-core` PDF viewer to the new `@alfresco/adf-core/viewer/pdf` secondary entrypoint.
+
+## What it does
+
+### 1. Rewrites imports
+
+Moves PDF-related symbols from `@alfresco/adf-core` to `@alfresco/adf-core/viewer/pdf`:
+
+**Before:**
+```typescript
+import { AppConfigService, PdfViewerComponent, RenderingQueueServices } from '@alfresco/adf-core';
+```
+
+**After:**
+```typescript
+import { AppConfigService } from '@alfresco/adf-core';
+import { PdfViewerComponent, RenderingQueueServices } from '@alfresco/adf-core/viewer/pdf';
+```
+
+Affected symbols:
+- `PdfViewerComponent`
+- `PdfPasswordDialogComponent`
+- `PdfThumbListComponent`
+- `PdfThumbComponent`
+- `PDFJS_MODULE`
+- `PDFJS_VIEWER_MODULE`
+- `RenderingQueueServices`
+
+### 2. Adds `providePdfViewer()` to app providers
+
+The PDF viewer now requires explicit registration via the `providePdfViewer()` provider function. The schematic automatically adds it to the consumer's application configuration.
+
+**Standalone app (`app.config.ts`):**
+```typescript
+import { providePdfViewer } from '@alfresco/adf-core/viewer/pdf';
+
+export const appConfig: ApplicationConfig = {
+ providers: [providePdfViewer(), /* ...existing providers */]
+};
+```
+
+**NgModule app (`app.module.ts`):**
+```typescript
+import { providePdfViewer } from '@alfresco/adf-core/viewer/pdf';
+
+@NgModule({
+ providers: [providePdfViewer()]
+})
+export class AppModule {}
+```
+
+## How it runs
+
+Triggered automatically via `ng update @alfresco/adf-core` when upgrading to v9.0.0+.
+
+Can also be run manually:
+```bash
+ng generate @alfresco/adf-core:migrate-pdf-viewer-imports
+```
+
+## File discovery
+
+The schematic scans all `.ts` files in the project (excluding `node_modules`, `.git`, `.angular`, `.nxcache`) and processes any file containing one of the PDF symbols listed above.
+
+For provider injection, it searches for the app config file in this order:
+1. `src/app/app.config.ts`
+2. `src/app/app.module.ts`
+3. `src/main.ts`
+4. Falls back to scanning for any file containing `bootstrapApplication` or `ApplicationConfig`
+
+## Edge cases handled
+
+| Scenario | Behavior |
+|----------|----------|
+| Existing `@alfresco/adf-core/viewer/pdf` import | Merges moved symbols into it (deduplicates) |
+| All symbols in the core import are PDF symbols | Removes the entire `@alfresco/adf-core` import |
+| `providePdfViewer` already present in the project | Skips provider injection |
+| No app config file found | Skips provider injection (consumer must add manually) |
+| File has no PDF symbols | Skipped entirely (no modifications) |
+| Empty providers array | Inserts `providePdfViewer()` as the first element |
+
+## Why this migration exists
+
+In v9.0.0, the PDF viewer was extracted into a secondary entrypoint (`@alfresco/adf-core/viewer/pdf`) so that:
+- `pdfjs-dist` becomes an optional peer dependency
+- Applications that don't use PDF viewing avoid bundling ~500KB of PDF processing code
+- The primary `@alfresco/adf-core` bundle stays lean
+
+Without this schematic, consumers upgrading to v9 would see PDF files rendered as "unknown format" because the `PDF_VIEWER_COMPONENT` injection token defaults to `null` when `providePdfViewer()` is not called.
diff --git a/lib/core/schematics/migrations/9_0_0/index.ts b/lib/core/schematics/migrations/9_0_0/index.ts
new file mode 100644
index 0000000000..cf6ce30ccb
--- /dev/null
+++ b/lib/core/schematics/migrations/9_0_0/index.ts
@@ -0,0 +1,274 @@
+/*!
+ * @license
+ * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as ts from 'typescript';
+import { Rule, Tree } from '@angular-devkit/schematics';
+
+const PDF_SYMBOLS = [
+ 'PdfViewerComponent',
+ 'PdfPasswordDialogComponent',
+ 'PdfThumbListComponent',
+ 'PdfThumbComponent',
+ 'PDFJS_MODULE',
+ 'PDFJS_VIEWER_MODULE',
+ 'RenderingQueueServices'
+];
+
+const OLD_SOURCE = '@alfresco/adf-core';
+const NEW_SOURCE = '@alfresco/adf-core/viewer/pdf';
+
+/**
+ * @returns Schematic rule for migrating PDF viewer imports to the secondary entrypoint
+ */
+export function migratePdfViewerImports(): Rule {
+ return (tree: Tree) => {
+ let providerAdded = false;
+
+ tree.visit((filePath: string) => {
+ if (
+ !filePath.includes('/.git/') &&
+ !filePath.includes('/node_modules/') &&
+ !filePath.includes('/.angular/') &&
+ !filePath.includes('/.nxcache/') &&
+ /\.ts$/.test(filePath)
+ ) {
+ const bufferContent = tree.read(filePath);
+ if (!bufferContent) {
+ return;
+ }
+
+ const fileContent = bufferContent.toString();
+
+ if (PDF_SYMBOLS.some((sym) => fileContent.includes(sym))) {
+ const sourceFile = ts.createSourceFile(filePath, fileContent, ts.ScriptTarget.Latest, true);
+ const updatedContent = movePdfImports(sourceFile, fileContent);
+
+ if (updatedContent !== fileContent) {
+ tree.overwrite(filePath, updatedContent);
+ }
+ }
+
+ if (!providerAdded && fileContent.includes('providePdfViewer')) {
+ providerAdded = true;
+ }
+ }
+ });
+
+ if (!providerAdded) {
+ addProvidePdfViewerToAppConfig(tree);
+ }
+
+ return tree;
+ };
+}
+
+/**
+ * @param sourceFile - the parsed TypeScript source file
+ * @param fileContent - the raw file content string
+ * @returns updated file content with PDF imports moved to the secondary entrypoint
+ */
+function movePdfImports(sourceFile: ts.SourceFile, fileContent: string): string {
+ const importDeclarations = sourceFile.statements.filter(ts.isImportDeclaration);
+ const coreImport = importDeclarations.find((decl) => {
+ const moduleSpecifier = decl.moduleSpecifier.getText().replace(/['"]/g, '');
+ return moduleSpecifier === OLD_SOURCE;
+ });
+
+ if (!coreImport?.importClause?.namedBindings || !ts.isNamedImports(coreImport.importClause.namedBindings)) {
+ return fileContent;
+ }
+
+ const namedImports = coreImport.importClause.namedBindings.elements;
+ const pdfImports = namedImports.filter((el) => PDF_SYMBOLS.includes(el.name.text));
+ const remainingImports = namedImports.filter((el) => !PDF_SYMBOLS.includes(el.name.text));
+
+ if (pdfImports.length === 0) {
+ return fileContent;
+ }
+
+ const pdfImportNames = pdfImports.map((el) => el.name.text);
+
+ const existingNewImport = importDeclarations.find((decl) => decl.moduleSpecifier.getText().replace(/['"]/g, '') === NEW_SOURCE);
+
+ const existingNewImportNames: string[] = [];
+ if (existingNewImport?.importClause?.namedBindings && ts.isNamedImports(existingNewImport.importClause.namedBindings)) {
+ existingNewImportNames.push(...existingNewImport.importClause.namedBindings.elements.map((el) => el.name.text));
+ }
+
+ const mergedNames = [...new Set([...existingNewImportNames, ...pdfImportNames])];
+ const mergedImportStatement = `import { ${mergedNames.join(', ')} } from '${NEW_SOURCE}';`;
+
+ const edits: { start: number; end: number; replacement: string }[] = [];
+
+ if (remainingImports.length === 0) {
+ edits.push({ start: coreImport.getFullStart(), end: coreImport.getEnd(), replacement: '' });
+ } else {
+ const remainingNames = remainingImports.map((el) => el.getText()).join(', ');
+ const updatedImport = `import { ${remainingNames} } from '${OLD_SOURCE}';`;
+ edits.push({ start: coreImport.getStart(), end: coreImport.getEnd(), replacement: updatedImport });
+ }
+
+ if (existingNewImport) {
+ edits.push({ start: existingNewImport.getStart(), end: existingNewImport.getEnd(), replacement: mergedImportStatement });
+ }
+
+ edits.sort((a, b) => b.start - a.start);
+ let updatedContent = fileContent;
+ for (const edit of edits) {
+ updatedContent = updatedContent.slice(0, edit.start) + edit.replacement + updatedContent.slice(edit.end);
+ }
+
+ if (!existingNewImport) {
+ const updatedSource = ts.createSourceFile('temp.ts', updatedContent, ts.ScriptTarget.Latest, true);
+ const firstNonImport = updatedSource.statements.find((stmt) => !ts.isImportDeclaration(stmt));
+ const insertPos = firstNonImport ? firstNonImport.getFullStart() : updatedContent.length;
+ updatedContent = updatedContent.slice(0, insertPos).trimEnd() + '\n' + mergedImportStatement + '\n' + updatedContent.slice(insertPos);
+ }
+
+ return updatedContent.replace(/\n{3,}/g, '\n\n');
+}
+
+/**
+ * @param tree - the schematic file tree
+ */
+function addProvidePdfViewerToAppConfig(tree: Tree): void {
+ const candidates = ['src/app/app.config.ts', 'src/app/app.module.ts', 'src/main.ts'];
+ let targetPath: string | null = null;
+
+ for (const candidate of candidates) {
+ if (tree.exists(`/${candidate}`)) {
+ targetPath = `/${candidate}`;
+ break;
+ }
+ }
+
+ if (!targetPath) {
+ tree.visit((filePath: string) => {
+ if (targetPath) {
+ return;
+ }
+ if (!filePath.includes('/node_modules/') && !filePath.includes('/.git/') && /\.ts$/.test(filePath)) {
+ const content = tree.read(filePath)?.toString() ?? '';
+ if (content.includes('bootstrapApplication') || content.includes('ApplicationConfig')) {
+ targetPath = filePath;
+ }
+ }
+ });
+ }
+
+ if (!targetPath) {
+ return;
+ }
+
+ const buffer = tree.read(targetPath);
+ if (!buffer) {
+ return;
+ }
+ const content = buffer.toString();
+
+ if (content.includes('providePdfViewer')) {
+ return;
+ }
+
+ const sourceFile = ts.createSourceFile(targetPath, content, ts.ScriptTarget.Latest, true);
+ const result = insertProvidePdfViewer(sourceFile, content);
+
+ if (result !== content) {
+ tree.overwrite(targetPath, result);
+ }
+}
+
+/**
+ * @param sourceFile - the parsed TypeScript source file
+ * @param content - the raw file content
+ * @returns updated content with providePdfViewer() added to providers
+ */
+function insertProvidePdfViewer(sourceFile: ts.SourceFile, content: string): string {
+ const providersArray = findProvidersArray(sourceFile);
+ if (!providersArray) {
+ return content;
+ }
+
+ const lastElement = providersArray.elements[providersArray.elements.length - 1];
+ let insertPos: number;
+ let prefix: string;
+
+ if (lastElement) {
+ insertPos = lastElement.getEnd();
+ prefix = ', providePdfViewer()';
+ } else {
+ insertPos = providersArray.getStart() + 1;
+ prefix = 'providePdfViewer()';
+ }
+
+ let updatedContent = content.slice(0, insertPos) + prefix + content.slice(insertPos);
+
+ const importStatement = `import { providePdfViewer } from '${NEW_SOURCE}';\n`;
+ const existingPdfImport = sourceFile.statements
+ .filter(ts.isImportDeclaration)
+ .find((decl) => decl.moduleSpecifier.getText().replace(/['"]/g, '') === NEW_SOURCE);
+
+ if (existingPdfImport) {
+ if (!content.includes('providePdfViewer')) {
+ const updatedSource = ts.createSourceFile('temp.ts', updatedContent, ts.ScriptTarget.Latest, true);
+ const existingDecl = updatedSource.statements
+ .filter(ts.isImportDeclaration)
+ .find((decl) => decl.moduleSpecifier.getText().replace(/['"]/g, '') === NEW_SOURCE);
+
+ if (existingDecl?.importClause?.namedBindings && ts.isNamedImports(existingDecl.importClause.namedBindings)) {
+ const names = existingDecl.importClause.namedBindings.elements.map((el) => el.name.text);
+ if (!names.includes('providePdfViewer')) {
+ names.push('providePdfViewer');
+ const newImport = `import { ${names.join(', ')} } from '${NEW_SOURCE}';`;
+ updatedContent = updatedContent.slice(0, existingDecl.getStart()) + newImport + updatedContent.slice(existingDecl.getEnd());
+ }
+ }
+ }
+ } else {
+ const firstImport = sourceFile.statements.find(ts.isImportDeclaration);
+ const importInsertPos = firstImport ? firstImport.getFullStart() : 0;
+ updatedContent = updatedContent.slice(0, importInsertPos) + importStatement + updatedContent.slice(importInsertPos);
+ }
+
+ return updatedContent;
+}
+
+/**
+ * @param sourceFile - the TypeScript source file to search
+ * @returns the providers ArrayLiteralExpression if found
+ */
+function findProvidersArray(sourceFile: ts.SourceFile): ts.ArrayLiteralExpression | null {
+ let result: ts.ArrayLiteralExpression | null = null;
+
+ const visit = (node: ts.Node): void => {
+ if (result) {
+ return;
+ }
+
+ if (ts.isPropertyAssignment(node) && node.name.getText() === 'providers' && ts.isArrayLiteralExpression(node.initializer)) {
+ result = node.initializer;
+ return;
+ }
+
+ ts.forEachChild(node, visit);
+ };
+
+ visit(sourceFile);
+ return result;
+}
+
+export default migratePdfViewerImports;
diff --git a/lib/core/schematics/migrations/collection.json b/lib/core/schematics/migrations/collection.json
index 34e94b44cf..72d2c85091 100644
--- a/lib/core/schematics/migrations/collection.json
+++ b/lib/core/schematics/migrations/collection.json
@@ -5,6 +5,11 @@
"description": "Update alfresco-api imports",
"version": "7.0.0",
"factory": "./7_0_0/index#updateAlfrescoApiImports"
+ },
+ "migrate-pdf-viewer-imports": {
+ "description": "Move PDF viewer imports to @alfresco/adf-core/viewer/pdf secondary entrypoint",
+ "version": "9.0.0",
+ "factory": "./9_0_0/index#migratePdfViewerImports"
}
},
"packageJsonUpdates": {
diff --git a/lib/core/src/lib/viewer/components/viewer-render/viewer-render.component.html b/lib/core/src/lib/viewer/components/viewer-render/viewer-render.component.html
index dd4d0f9729..be87ced881 100644
--- a/lib/core/src/lib/viewer/components/viewer-render/viewer-render.component.html
+++ b/lib/core/src/lib/viewer/components/viewer-render/viewer-render.component.html
@@ -32,17 +32,13 @@
}
@case ('pdf') {
-
+ @if (pdfViewerComponent) {
+
+ } @else {
+
+ }
}
@case ('image') {
diff --git a/lib/core/src/lib/viewer/components/viewer-render/viewer-render.component.spec.ts b/lib/core/src/lib/viewer/components/viewer-render/viewer-render.component.spec.ts
index 6c8a712342..221afd903d 100644
--- a/lib/core/src/lib/viewer/components/viewer-render/viewer-render.component.spec.ts
+++ b/lib/core/src/lib/viewer/components/viewer-render/viewer-render.component.spec.ts
@@ -18,13 +18,30 @@
import { AppExtensionService, ViewerExtensionRef } from '@alfresco/adf-extensions';
import { Location } from '@angular/common';
import { SpyLocation } from '@angular/common/testing';
-import { Component, DebugElement, TemplateRef, ViewChild } from '@angular/core';
+import { Component, DebugElement, EventEmitter, Input, Output, TemplateRef, ViewChild } from '@angular/core';
import { ComponentFixture, DeferBlockBehavior, TestBed } from '@angular/core/testing';
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
import { UnitTestingUtils } from '../../../testing';
-import { RenderingQueueServices } from '../../services/rendering-queue.services';
import { ViewerRenderComponent } from './viewer-render.component';
-import { ImgViewerComponent, MediaPlayerComponent, PdfViewerComponent, ViewerExtensionDirective } from '@alfresco/adf-core';
+import { PDF_VIEWER_COMPONENT } from '../../tokens/pdf-viewer.token';
+import { PdfViewerRef } from '../../tokens/pdf-viewer-ref';
+import { ImgViewerComponent, MediaPlayerComponent, ViewerExtensionDirective } from '@alfresco/adf-core';
+
+@Component({
+ selector: 'adf-pdf-viewer',
+ template: '
'
+})
+class MockPdfViewerComponent implements PdfViewerRef {
+ @Input() urlFile = '';
+ @Input() blobFile: Blob;
+ @Input() fileName = '';
+ @Input() allowThumbnails = false;
+ @Input() thumbnailsTemplate: TemplateRef = null;
+ @Input() cacheType = '';
+ @Output() pagesLoaded = new EventEmitter();
+ @Output() error = new EventEmitter();
+ @Output() close = new EventEmitter();
+}
@Component({
selector: 'adf-double-viewer',
@@ -69,7 +86,7 @@ describe('ViewerComponent', () => {
beforeEach(async () => {
TestBed.configureTestingModule({
imports: [MatDialogModule, ViewerRenderComponent, DoubleViewerComponent],
- providers: [RenderingQueueServices, { provide: Location, useClass: SpyLocation }, MatDialog],
+ providers: [{ provide: Location, useClass: SpyLocation }, MatDialog, { provide: PDF_VIEWER_COMPONENT, useValue: MockPdfViewerComponent }],
deferBlockBehavior: DeferBlockBehavior.Playthrough
});
fixture = TestBed.createComponent(ViewerRenderComponent);
@@ -532,7 +549,7 @@ describe('ViewerComponent', () => {
component.ngOnChanges();
fixture.detectChanges();
- const imgViewer = testingUtils.getByDirective(PdfViewerComponent);
+ const imgViewer = testingUtils.getByDirective(MockPdfViewerComponent);
imgViewer.triggerEventHandler('pagesLoaded', null);
fixture.detectChanges();
diff --git a/lib/core/src/lib/viewer/components/viewer-render/viewer-render.component.ts b/lib/core/src/lib/viewer/components/viewer-render/viewer-render.component.ts
index a83c6b0302..32001f2b0d 100644
--- a/lib/core/src/lib/viewer/components/viewer-render/viewer-render.component.ts
+++ b/lib/core/src/lib/viewer/components/viewer-render/viewer-render.component.ts
@@ -16,9 +16,10 @@
*/
import { AppExtensionService, ExtensionsModule, ViewerExtensionRef, PreviewExtensionComponent } from '@alfresco/adf-extensions';
-import { NgForOf, NgTemplateOutlet } from '@angular/common';
+import { NgComponentOutlet, NgForOf, NgTemplateOutlet } from '@angular/common';
import {
Component,
+ effect,
EventEmitter,
Injector,
Input,
@@ -26,18 +27,22 @@ import {
OnInit,
Output,
TemplateRef,
+ Type,
ViewChild,
ViewEncapsulation,
- inject
+ inject,
+ viewChild
} from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { TranslatePipe } from '@ngx-translate/core';
+import { Subscription } from 'rxjs';
import { Track } from '../../models/viewer.model';
import { ViewUtilService } from '../../services/view-util.service';
+import { PDF_VIEWER_COMPONENT } from '../../tokens/pdf-viewer.token';
+import { PdfViewerRef } from '../../tokens/pdf-viewer-ref';
import { ImgViewerComponent } from '../img-viewer/img-viewer.component';
import { MediaPlayerComponent } from '../media-player/media-player.component';
-import { PdfViewerComponent } from '../pdf-viewer/pdf-viewer.component';
import { TxtViewerComponent } from '../txt-viewer/txt-viewer.component';
import { UnknownFormatComponent } from '../unknown-format/unknown-format.component';
@@ -50,7 +55,7 @@ import { UnknownFormatComponent } from '../unknown-format/unknown-format.compone
imports: [
TranslatePipe,
MatProgressSpinnerModule,
- PdfViewerComponent,
+ NgComponentOutlet,
ImgViewerComponent,
MediaPlayerComponent,
TxtViewerComponent,
@@ -68,6 +73,30 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
dialog = inject(MatDialog);
readonly injector = inject(Injector);
+ readonly pdfViewerComponent: Type | null = inject(PDF_VIEWER_COMPONENT, { optional: true });
+
+ pdfViewerInputs: Record = {};
+
+ private readonly pdfOutlet = viewChild(NgComponentOutlet);
+
+ constructor() {
+ effect((onCleanup) => {
+ const outlet = this.pdfOutlet();
+ const instance = outlet?.componentInstance as PdfViewerRef | null;
+ if (!instance) {
+ return;
+ }
+
+ const subs: Subscription[] = [
+ instance.pagesLoaded.subscribe(() => this.markAsLoaded()),
+ instance.close.subscribe(() => this.onClose()),
+ instance.error.subscribe(() => this.onUnsupportedFile())
+ ];
+
+ onCleanup(() => subs.forEach((s) => s.unsubscribe()));
+ });
+ }
+
/**
* If you want to load an external file that does not come from ACS you
* can use this URL to specify where to load the file from.
@@ -197,6 +226,13 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
} else if (this.urlFile) {
this.setUpUrlFile();
}
+ if (this.viewerType === 'pdf' && !this.pdfViewerComponent) {
+ console.error(
+ '@alfresco/adf-core: PDF viewer is not configured. ' +
+ 'Add providePdfViewer() from @alfresco/adf-core/viewer/pdf to your application providers.'
+ );
+ }
+ this.updatePdfViewerInputs();
}
markAsLoaded() {
@@ -250,4 +286,15 @@ export class ViewerRenderComponent implements OnChanges, OnInit {
onClose() {
this.close.next(true);
}
+
+ private updatePdfViewerInputs(): void {
+ this.pdfViewerInputs = {
+ urlFile: this.urlFile,
+ blobFile: this.blobFile,
+ fileName: this.internalFileName,
+ allowThumbnails: this.allowThumbnails,
+ thumbnailsTemplate: this.thumbnailsTemplate,
+ cacheType: this.cacheTypeForContent
+ };
+ }
}
diff --git a/lib/core/src/lib/viewer/public-api.ts b/lib/core/src/lib/viewer/public-api.ts
index 9d60324639..3d17c4f7a9 100644
--- a/lib/core/src/lib/viewer/public-api.ts
+++ b/lib/core/src/lib/viewer/public-api.ts
@@ -16,13 +16,10 @@
*/
export * from './services/view-util.service';
+export * from './tokens/pdf-viewer.token';
+export * from './tokens/pdf-viewer-ref';
export * from './components/img-viewer/img-viewer.component';
export * from './components/media-player/media-player.component';
-export * from './components/pdf-viewer-password-dialog/pdf-viewer-password-dialog';
-export * from './components/pdf-viewer/pdf-viewer.component';
-export * from './components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component';
-export * from './components/pdf-viewer-thumb/pdf-viewer-thumb.component';
-export * from './components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component';
export * from './components/txt-viewer/txt-viewer.component';
export * from './components/unknown-format/unknown-format.component';
export * from './components/viewer-more-actions.component';
diff --git a/lib/core/src/lib/viewer/tokens/pdf-viewer-ref.ts b/lib/core/src/lib/viewer/tokens/pdf-viewer-ref.ts
new file mode 100644
index 0000000000..965ffbd1fb
--- /dev/null
+++ b/lib/core/src/lib/viewer/tokens/pdf-viewer-ref.ts
@@ -0,0 +1,30 @@
+/*!
+ * @license
+ * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { EventEmitter, TemplateRef } from '@angular/core';
+
+export interface PdfViewerRef {
+ urlFile: string;
+ blobFile: Blob;
+ fileName: string;
+ allowThumbnails: boolean;
+ thumbnailsTemplate: TemplateRef;
+ cacheType: string;
+ pagesLoaded: EventEmitter;
+ error: EventEmitter;
+ close: EventEmitter;
+}
diff --git a/lib/core/src/lib/viewer/tokens/pdf-viewer.token.ts b/lib/core/src/lib/viewer/tokens/pdf-viewer.token.ts
new file mode 100644
index 0000000000..adb5e24064
--- /dev/null
+++ b/lib/core/src/lib/viewer/tokens/pdf-viewer.token.ts
@@ -0,0 +1,24 @@
+/*!
+ * @license
+ * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { InjectionToken, Type } from '@angular/core';
+import { PdfViewerRef } from './pdf-viewer-ref';
+
+export const PDF_VIEWER_COMPONENT = new InjectionToken | null>('PDF_VIEWER_COMPONENT', {
+ providedIn: 'root',
+ factory: () => null
+});
diff --git a/lib/core/src/lib/viewer/viewer.module.ts b/lib/core/src/lib/viewer/viewer.module.ts
index 46b5f45d6f..33bf8aea7c 100644
--- a/lib/core/src/lib/viewer/viewer.module.ts
+++ b/lib/core/src/lib/viewer/viewer.module.ts
@@ -19,10 +19,6 @@ import { NgModule } from '@angular/core';
import { DownloadPromptDialogComponent } from './components/download-prompt-dialog/download-prompt-dialog.component';
import { ImgViewerComponent } from './components/img-viewer/img-viewer.component';
import { MediaPlayerComponent } from './components/media-player/media-player.component';
-import { PdfPasswordDialogComponent } from './components/pdf-viewer-password-dialog/pdf-viewer-password-dialog';
-import { PdfThumbComponent } from './components/pdf-viewer-thumb/pdf-viewer-thumb.component';
-import { PdfThumbListComponent } from './components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component';
-import { PdfViewerComponent } from './components/pdf-viewer/pdf-viewer.component';
import { TxtViewerComponent } from './components/txt-viewer/txt-viewer.component';
import { UnknownFormatComponent } from './components/unknown-format/unknown-format.component';
import { ViewerMoreActionsComponent } from './components/viewer-more-actions.component';
@@ -36,14 +32,10 @@ import { ViewerComponent } from './components/viewer.component';
import { ViewerExtensionDirective } from './directives/viewer-extension.directive';
export const VIEWER_DIRECTIVES = [
- PdfPasswordDialogComponent,
ViewerRenderComponent,
ImgViewerComponent,
TxtViewerComponent,
MediaPlayerComponent,
- PdfViewerComponent,
- PdfThumbComponent,
- PdfThumbListComponent,
ViewerExtensionDirective,
UnknownFormatComponent,
ViewerToolbarComponent,
diff --git a/lib/core/viewer/pdf/README.md b/lib/core/viewer/pdf/README.md
new file mode 100644
index 0000000000..ec2c0805af
--- /dev/null
+++ b/lib/core/viewer/pdf/README.md
@@ -0,0 +1,77 @@
+# @alfresco/adf-core/viewer/pdf
+
+Secondary entrypoint that provides the PDF viewer component, extracted from `@alfresco/adf-core` to make `pdfjs-dist` an optional dependency.
+
+## Setup
+
+1. Install `pdfjs-dist` (if not already present):
+
+```bash
+npm install pdfjs-dist
+```
+
+2. Add `providePdfViewer()` to your app providers:
+
+```typescript
+import { providePdfViewer } from '@alfresco/adf-core/viewer/pdf';
+
+// Standalone app (app.config.ts)
+export const appConfig: ApplicationConfig = {
+ providers: [
+ providePdfViewer(),
+ // ...other providers
+ ]
+};
+
+// NgModule-based app (app.module.ts)
+@NgModule({
+ providers: [providePdfViewer()]
+})
+export class AppModule {}
+```
+
+That's it. The viewer will render PDFs using the real `PdfViewerComponent`.
+
+## Migration from v8.x
+
+If upgrading from a version where `PdfViewerComponent` was part of `@alfresco/adf-core` directly, run:
+
+```bash
+ng update @alfresco/adf-core
+```
+
+The migration schematic will:
+- Rewrite imports of PDF symbols (`PdfViewerComponent`, `PdfPasswordDialogComponent`, `PdfThumbListComponent`, `PdfThumbComponent`, `PDFJS_MODULE`, `PDFJS_VIEWER_MODULE`, `RenderingQueueServices`) from `@alfresco/adf-core` to `@alfresco/adf-core/viewer/pdf`
+- Add `providePdfViewer()` to your app's providers array
+
+## Opting out of PDF support
+
+If your application does not need to render PDFs, you can skip installing `pdfjs-dist` and omit `providePdfViewer()`. The viewer will display an "unknown format" placeholder for PDF files.
+
+## Exports
+
+| Symbol | Description |
+|--------|-------------|
+| `providePdfViewer()` | Provider function that registers the PDF viewer |
+| `PdfViewerComponent` | The PDF viewer component |
+| `PdfPasswordDialogComponent` | Password dialog for protected PDFs |
+| `PdfThumbListComponent` | PDF thumbnail list |
+| `PdfThumbComponent` | Individual PDF thumbnail |
+| `PDFJS_MODULE` | Injection token for pdfjs-dist library |
+| `PDFJS_VIEWER_MODULE` | Injection token for pdfjs viewer module |
+| `RenderingQueueServices` | Service managing PDF page render queue |
+
+## Architecture
+
+```
+@alfresco/adf-core (primary entrypoint)
+ - Exports: PDF_VIEWER_COMPONENT token, PdfViewerRef interface
+ - ViewerRenderComponent injects the token via NgComponentOutlet
+ - Does NOT depend on pdfjs-dist
+
+@alfresco/adf-core/viewer/pdf (this entrypoint)
+ - Imports from: @alfresco/adf-core, pdfjs-dist
+ - Exports: PdfViewerComponent, providePdfViewer(), etc.
+```
+
+The dependency is one-way (secondary -> primary). The primary entrypoint never imports from this package, avoiding circular dependencies and keeping `pdfjs-dist` out of the main bundle.
diff --git a/lib/core/viewer/pdf/ng-package.json b/lib/core/viewer/pdf/ng-package.json
new file mode 100644
index 0000000000..16ec7433ad
--- /dev/null
+++ b/lib/core/viewer/pdf/ng-package.json
@@ -0,0 +1,8 @@
+{
+ "lib": {
+ "entryFile": "src/index.ts",
+ "styleIncludePaths": [
+ "../../src/lib"
+ ]
+ }
+}
diff --git a/lib/core/viewer/pdf/src/index.ts b/lib/core/viewer/pdf/src/index.ts
new file mode 100644
index 0000000000..dde81adbbc
--- /dev/null
+++ b/lib/core/viewer/pdf/src/index.ts
@@ -0,0 +1,23 @@
+/*!
+ * @license
+ * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './lib/components/pdf-viewer/pdf-viewer.component';
+export * from './lib/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component';
+export * from './lib/components/pdf-viewer-thumb/pdf-viewer-thumb.component';
+export * from './lib/components/pdf-viewer-password-dialog/pdf-viewer-password-dialog';
+export * from './lib/services/rendering-queue.services';
+export * from './lib/provide-pdf-viewer';
diff --git a/lib/core/viewer/pdf/src/lib/assets/fake-test-file.pdf b/lib/core/viewer/pdf/src/lib/assets/fake-test-file.pdf
new file mode 100644
index 0000000000..4843af26d6
Binary files /dev/null and b/lib/core/viewer/pdf/src/lib/assets/fake-test-file.pdf differ
diff --git a/lib/core/viewer/pdf/src/lib/assets/fake-test-password-file.pdf b/lib/core/viewer/pdf/src/lib/assets/fake-test-password-file.pdf
new file mode 100644
index 0000000000..d34fba5e0c
Binary files /dev/null and b/lib/core/viewer/pdf/src/lib/assets/fake-test-password-file.pdf differ
diff --git a/lib/core/viewer/pdf/src/lib/components/mock/pdfjs-lib.mock.ts b/lib/core/viewer/pdf/src/lib/components/mock/pdfjs-lib.mock.ts
new file mode 100644
index 0000000000..633899ece1
--- /dev/null
+++ b/lib/core/viewer/pdf/src/lib/components/mock/pdfjs-lib.mock.ts
@@ -0,0 +1,53 @@
+/*!
+ * @license
+ * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export const annotations = [
+ {
+ subtype: 'Text',
+ name: 'NoIcon',
+ id: 'R13',
+ titleObj: {
+ str: 'Annotation title'
+ },
+ contentsObj: {
+ str: 'Annotation contents'
+ },
+ modificationDate: "D:20260202104106Z00'00",
+ popupRef: 'R1'
+ }
+];
+export default {
+ GlobalWorkerOptions: {},
+ getDocument: jasmine.createSpy('getDocument').and.callFake(() => ({
+ loadingTask: () => ({
+ destroy: () => Promise.resolve()
+ }),
+ promise: new Promise((resolve) => {
+ resolve({
+ numPages: 6,
+ getPage: () =>
+ Promise.resolve({
+ getAnnotations: () => annotations
+ })
+ });
+ })
+ })),
+ PasswordResponses: {
+ NEED_PASSWORD: 1,
+ INCORRECT_PASSWORD: 2
+ }
+};
diff --git a/lib/core/src/lib/viewer/components/pdf-viewer-password-dialog/pdf-viewer-password-dialog.html b/lib/core/viewer/pdf/src/lib/components/pdf-viewer-password-dialog/pdf-viewer-password-dialog.html
similarity index 100%
rename from lib/core/src/lib/viewer/components/pdf-viewer-password-dialog/pdf-viewer-password-dialog.html
rename to lib/core/viewer/pdf/src/lib/components/pdf-viewer-password-dialog/pdf-viewer-password-dialog.html
diff --git a/lib/core/src/lib/viewer/components/pdf-viewer-password-dialog/pdf-viewer-password-dialog.scss b/lib/core/viewer/pdf/src/lib/components/pdf-viewer-password-dialog/pdf-viewer-password-dialog.scss
similarity index 100%
rename from lib/core/src/lib/viewer/components/pdf-viewer-password-dialog/pdf-viewer-password-dialog.scss
rename to lib/core/viewer/pdf/src/lib/components/pdf-viewer-password-dialog/pdf-viewer-password-dialog.scss
diff --git a/lib/core/src/lib/viewer/components/pdf-viewer-password-dialog/pdf-viewer-password-dialog.spec.ts b/lib/core/viewer/pdf/src/lib/components/pdf-viewer-password-dialog/pdf-viewer-password-dialog.spec.ts
similarity index 100%
rename from lib/core/src/lib/viewer/components/pdf-viewer-password-dialog/pdf-viewer-password-dialog.spec.ts
rename to lib/core/viewer/pdf/src/lib/components/pdf-viewer-password-dialog/pdf-viewer-password-dialog.spec.ts
diff --git a/lib/core/src/lib/viewer/components/pdf-viewer-password-dialog/pdf-viewer-password-dialog.ts b/lib/core/viewer/pdf/src/lib/components/pdf-viewer-password-dialog/pdf-viewer-password-dialog.ts
similarity index 97%
rename from lib/core/src/lib/viewer/components/pdf-viewer-password-dialog/pdf-viewer-password-dialog.ts
rename to lib/core/viewer/pdf/src/lib/components/pdf-viewer-password-dialog/pdf-viewer-password-dialog.ts
index bd5899bb73..50509956b5 100644
--- a/lib/core/src/lib/viewer/components/pdf-viewer-password-dialog/pdf-viewer-password-dialog.ts
+++ b/lib/core/viewer/pdf/src/lib/components/pdf-viewer-password-dialog/pdf-viewer-password-dialog.ts
@@ -23,7 +23,7 @@ import { ReactiveFormsModule, UntypedFormControl, Validators } from '@angular/fo
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { TranslatePipe } from '@ngx-translate/core';
-import { IconModule } from '../../../icon/icon.module';
+import { IconModule } from '@alfresco/adf-core';
declare const pdfjsLib: { PasswordResponses: { NEED_PASSWORD: number; INCORRECT_PASSWORD: number } };
diff --git a/lib/core/src/lib/viewer/components/pdf-viewer-thumb/pdf-viewer-thumb.component.html b/lib/core/viewer/pdf/src/lib/components/pdf-viewer-thumb/pdf-viewer-thumb.component.html
similarity index 100%
rename from lib/core/src/lib/viewer/components/pdf-viewer-thumb/pdf-viewer-thumb.component.html
rename to lib/core/viewer/pdf/src/lib/components/pdf-viewer-thumb/pdf-viewer-thumb.component.html
diff --git a/lib/core/src/lib/viewer/components/pdf-viewer-thumb/pdf-viewer-thumb.component.spec.ts b/lib/core/viewer/pdf/src/lib/components/pdf-viewer-thumb/pdf-viewer-thumb.component.spec.ts
similarity index 100%
rename from lib/core/src/lib/viewer/components/pdf-viewer-thumb/pdf-viewer-thumb.component.spec.ts
rename to lib/core/viewer/pdf/src/lib/components/pdf-viewer-thumb/pdf-viewer-thumb.component.spec.ts
diff --git a/lib/core/src/lib/viewer/components/pdf-viewer-thumb/pdf-viewer-thumb.component.ts b/lib/core/viewer/pdf/src/lib/components/pdf-viewer-thumb/pdf-viewer-thumb.component.ts
similarity index 100%
rename from lib/core/src/lib/viewer/components/pdf-viewer-thumb/pdf-viewer-thumb.component.ts
rename to lib/core/viewer/pdf/src/lib/components/pdf-viewer-thumb/pdf-viewer-thumb.component.ts
diff --git a/lib/core/src/lib/viewer/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component.html b/lib/core/viewer/pdf/src/lib/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component.html
similarity index 100%
rename from lib/core/src/lib/viewer/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component.html
rename to lib/core/viewer/pdf/src/lib/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component.html
diff --git a/lib/core/src/lib/viewer/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component.scss b/lib/core/viewer/pdf/src/lib/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component.scss
similarity index 100%
rename from lib/core/src/lib/viewer/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component.scss
rename to lib/core/viewer/pdf/src/lib/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component.scss
diff --git a/lib/core/src/lib/viewer/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component.spec.ts b/lib/core/viewer/pdf/src/lib/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component.spec.ts
similarity index 99%
rename from lib/core/src/lib/viewer/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component.spec.ts
rename to lib/core/viewer/pdf/src/lib/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component.spec.ts
index a38fa7aed6..1a3826d2fd 100644
--- a/lib/core/src/lib/viewer/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component.spec.ts
+++ b/lib/core/viewer/pdf/src/lib/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component.spec.ts
@@ -17,7 +17,7 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PdfThumbListComponent } from './pdf-viewer-thumbnails.component';
-import { UnitTestingUtils } from '../../../testing';
+import { UnitTestingUtils } from '@alfresco/adf-core';
import { DOWN_ARROW, ESCAPE, UP_ARROW } from '@angular/cdk/keycodes';
declare const pdfjsViewer: any;
diff --git a/lib/core/src/lib/viewer/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component.ts b/lib/core/viewer/pdf/src/lib/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component.ts
similarity index 100%
rename from lib/core/src/lib/viewer/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component.ts
rename to lib/core/viewer/pdf/src/lib/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component.ts
diff --git a/lib/core/src/lib/viewer/components/pdf-viewer/pdf-viewer-host.component.scss b/lib/core/viewer/pdf/src/lib/components/pdf-viewer/pdf-viewer-host.component.scss
similarity index 100%
rename from lib/core/src/lib/viewer/components/pdf-viewer/pdf-viewer-host.component.scss
rename to lib/core/viewer/pdf/src/lib/components/pdf-viewer/pdf-viewer-host.component.scss
diff --git a/lib/core/viewer/pdf/src/lib/components/pdf-viewer/pdf-viewer-integration.spec.ts b/lib/core/viewer/pdf/src/lib/components/pdf-viewer/pdf-viewer-integration.spec.ts
new file mode 100644
index 0000000000..ccf8939d46
--- /dev/null
+++ b/lib/core/viewer/pdf/src/lib/components/pdf-viewer/pdf-viewer-integration.spec.ts
@@ -0,0 +1,99 @@
+/*!
+ * @license
+ * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { PDF_VIEWER_COMPONENT, ViewerRenderComponent, UnitTestingUtils } from '@alfresco/adf-core';
+import { PdfViewerComponent, PDFJS_MODULE, PDFJS_VIEWER_MODULE } from './pdf-viewer.component';
+import { providePdfViewer } from '../../provide-pdf-viewer';
+import pdfjsLibraryMock from '../mock/pdfjs-lib.mock';
+
+describe('PdfViewer Integration with ViewerRenderComponent', () => {
+ let fixture: ComponentFixture;
+ let component: ViewerRenderComponent;
+ let testingUtils: UnitTestingUtils;
+
+ beforeEach(async () => {
+ TestBed.configureTestingModule({
+ imports: [ViewerRenderComponent],
+ providers: [
+ providePdfViewer(),
+ { provide: PDFJS_MODULE, useValue: pdfjsLibraryMock },
+ { provide: PDFJS_VIEWER_MODULE, useValue: class {} }
+ ]
+ });
+
+ fixture = TestBed.createComponent(ViewerRenderComponent);
+ component = fixture.componentInstance;
+ testingUtils = new UnitTestingUtils(fixture.debugElement);
+ });
+
+ afterEach(() => {
+ fixture.destroy();
+ });
+
+ it('should provide PdfViewerComponent via token', () => {
+ const token = TestBed.inject(PDF_VIEWER_COMPONENT);
+ expect(token).toBe(PdfViewerComponent);
+ });
+
+ it('should render real PdfViewerComponent for PDF files', async () => {
+ component.urlFile = 'fake-test-file.pdf';
+ component.ngOnChanges();
+ fixture.detectChanges();
+ await fixture.whenStable();
+ fixture.detectChanges();
+
+ expect(component.viewerType).toBe('pdf');
+ expect(testingUtils.getByCSS('adf-pdf-viewer')).not.toBeNull();
+ });
+
+ it('should pass inputs to real PdfViewerComponent', async () => {
+ component.urlFile = 'fake-test-file.pdf';
+ component.allowThumbnails = true;
+ component.ngOnChanges();
+ fixture.detectChanges();
+ await fixture.whenStable();
+ fixture.detectChanges();
+
+ const pdfViewer = testingUtils.getByCSS('adf-pdf-viewer');
+ expect(pdfViewer).not.toBeNull();
+ expect(pdfViewer.componentInstance.urlFile).toBe('fake-test-file.pdf');
+ expect(pdfViewer.componentInstance.allowThumbnails).toBe(true);
+ });
+
+ it('should show unknown format when token is not provided', async () => {
+ TestBed.resetTestingModule();
+ TestBed.configureTestingModule({
+ imports: [ViewerRenderComponent],
+ providers: [{ provide: PDF_VIEWER_COMPONENT, useValue: null }]
+ });
+
+ fixture = TestBed.createComponent(ViewerRenderComponent);
+ component = fixture.componentInstance;
+ testingUtils = new UnitTestingUtils(fixture.debugElement);
+
+ component.urlFile = 'fake-test-file.pdf';
+ component.ngOnChanges();
+ fixture.detectChanges();
+ await fixture.whenStable();
+ fixture.detectChanges();
+
+ expect(component.viewerType).toBe('pdf');
+ expect(testingUtils.getByCSS('adf-pdf-viewer')).toBeNull();
+ expect(testingUtils.getByCSS('adf-viewer-unknown-format')).not.toBeNull();
+ });
+});
diff --git a/lib/core/viewer/pdf/src/lib/components/pdf-viewer/pdf-viewer-real-pdf.spec.ts b/lib/core/viewer/pdf/src/lib/components/pdf-viewer/pdf-viewer-real-pdf.spec.ts
new file mode 100644
index 0000000000..35d02168ad
--- /dev/null
+++ b/lib/core/viewer/pdf/src/lib/components/pdf-viewer/pdf-viewer-real-pdf.spec.ts
@@ -0,0 +1,126 @@
+/*!
+ * @license
+ * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component, ViewChild } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { MatDialog } from '@angular/material/dialog';
+import { provideCoreAuthTesting } from '@alfresco/adf-core';
+import { PdfViewerComponent } from './pdf-viewer.component';
+import { RenderingQueueServices } from '../../services/rendering-queue.services';
+import * as pdfjsLib from 'pdfjs-dist/build/pdf.min.mjs';
+
+const MINIMAL_PDF_BASE64 =
+ 'JVBERi0xLjcKCjEgMCBvYmogICUgZW50cnkgcG9pbnQKPDwKICAvVHlwZSAvQ2F0YWxvZwog' +
+ 'IC9QYWdlcyAyIDAgUgo+PgplbmRvYmoKCjIgMCBvYmoKPDwKICAvVHlwZSAvUGFnZXMKICAv' +
+ 'TWVkaWFCb3ggWyAwIDAgMjAwIDIwMCBdCiAgL0NvdW50IDEKICAvS2lkcyBbIDMgMCBSIF0K' +
+ 'Pj4KZW5kb2JqCgozIDAgb2JqCjw8CiAgL1R5cGUgL1BhZ2UKICAvUGFyZW50IDIgMCBSCiAg' +
+ 'L1Jlc291cmNlcyA8PAogICAgL0ZvbnQgPDwKICAgICAgL0YxIDQgMCBSIAogICAgPj4KICA+' +
+ 'PgogIC9Db250ZW50cyA1IDAgUgo+PgplbmRvYmoKCjQgMCBvYmoKPDwKICAvVHlwZSAvRm9u' +
+ 'dAogIC9TdWJ0eXBlIC9UeXBlMQogIC9CYXNlRm9udCAvVGltZXMtUm9tYW4KPj4KZW5kb2Jq' +
+ 'Cgo1IDAgb2JqICAlIHBhZ2UgY29udGVudAo8PAogIC9MZW5ndGggNDQKPj4Kc3RyZWFtCkJU' +
+ 'CjcwIDUwIFRECi9GMSAxMiBUZgooSGVsbG8sIHdvcmxkISkgVGoKRVQKZW5kc3RyZWFtCmVu' +
+ 'ZG9iagoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDEwIDAwMDAwIG4g' +
+ 'CjAwMDAwMDAwNzkgMDAwMDAgbiAKMDAwMDAwMDE3MyAwMDAwMCBuIAowMDAwMDAwMzAxIDAw' +
+ 'MDAwIG4gCjAwMDAwMDAzODAgMDAwMDAgbiAKdHJhaWxlcgo8PAogIC9TaXplIDYKICAvUm9v' +
+ 'dCAxIDAgUgo+PgpzdGFydHhyZWYKNDkyCiUlRU9G';
+
+/** @returns a Blob containing a minimal valid 1-page PDF */
+function createRealPdfBlob(): Blob {
+ const binaryString = atob(MINIMAL_PDF_BASE64);
+ const bytes = new Uint8Array(binaryString.length);
+ for (let i = 0; i < binaryString.length; i++) {
+ bytes[i] = binaryString.charCodeAt(i);
+ }
+ return new Blob([bytes], { type: 'application/pdf' });
+}
+
+@Component({
+ imports: [PdfViewerComponent],
+ template: ``
+})
+class RealPdfTestHostComponent {
+ @ViewChild(PdfViewerComponent, { static: true })
+ pdfViewerComponent: PdfViewerComponent;
+
+ blobFile = createRealPdfBlob();
+}
+
+/**
+ * @param predicate - condition to wait for
+ * @param timeout - max wait time in ms
+ * @returns a promise that resolves when the predicate returns true
+ */
+function waitFor(predicate: () => boolean, timeout = 5000): Promise {
+ const start = Date.now();
+ return new Promise((resolve, reject) => {
+ const check = () => {
+ if (predicate()) {
+ resolve();
+ } else if (Date.now() - start > timeout) {
+ reject(new Error('waitFor timed out'));
+ } else {
+ setTimeout(check, 50);
+ }
+ };
+ check();
+ });
+}
+
+describe('PdfViewerComponent with real pdfjs-dist', () => {
+ let fixture: ComponentFixture;
+ let component: PdfViewerComponent;
+
+ beforeAll(async () => {
+ const workerModule = await import('pdfjs-dist/build/pdf.worker.min.mjs');
+ (globalThis as any).pdfjsWorker = { WorkerMessageHandler: workerModule.WorkerMessageHandler };
+ });
+
+ afterAll(() => {
+ delete (globalThis as any).pdfjsWorker;
+ });
+
+ beforeEach(async () => {
+ pdfjsLib.GlobalWorkerOptions.workerSrc = '';
+
+ TestBed.configureTestingModule({
+ imports: [RealPdfTestHostComponent],
+ providers: [provideCoreAuthTesting(), { provide: MatDialog, useValue: { open: () => {} } }, RenderingQueueServices]
+ });
+
+ fixture = TestBed.createComponent(RealPdfTestHostComponent);
+ component = fixture.componentInstance.pdfViewerComponent;
+
+ spyOn(component as any, 'setupPdfJsWorker').and.resolveTo();
+ });
+
+ afterEach(() => {
+ fixture.destroy();
+ });
+
+ it('should load a real PDF blob and detect page count', async () => {
+ fixture.detectChanges();
+ await waitFor(() => component.totalPages > 0);
+ expect(component.totalPages).toBe(1);
+ });
+
+ it('should emit pagesLoaded when a real PDF is rendered', async () => {
+ const pagesLoadedSpy = spyOn(component.pagesLoaded, 'emit');
+ fixture.detectChanges();
+ await waitFor(() => pagesLoadedSpy.calls.count() > 0);
+ expect(pagesLoadedSpy).toHaveBeenCalled();
+ });
+});
diff --git a/lib/core/src/lib/viewer/components/pdf-viewer/pdf-viewer.component.html b/lib/core/viewer/pdf/src/lib/components/pdf-viewer/pdf-viewer.component.html
similarity index 100%
rename from lib/core/src/lib/viewer/components/pdf-viewer/pdf-viewer.component.html
rename to lib/core/viewer/pdf/src/lib/components/pdf-viewer/pdf-viewer.component.html
diff --git a/lib/core/src/lib/viewer/components/pdf-viewer/pdf-viewer.component.scss b/lib/core/viewer/pdf/src/lib/components/pdf-viewer/pdf-viewer.component.scss
similarity index 98%
rename from lib/core/src/lib/viewer/components/pdf-viewer/pdf-viewer.component.scss
rename to lib/core/viewer/pdf/src/lib/components/pdf-viewer/pdf-viewer.component.scss
index 767650a0d5..d959e43e20 100644
--- a/lib/core/src/lib/viewer/components/pdf-viewer/pdf-viewer.component.scss
+++ b/lib/core/viewer/pdf/src/lib/components/pdf-viewer/pdf-viewer.component.scss
@@ -1,4 +1,4 @@
-@use '../../../styles/mat-selectors' as ms;
+@use 'styles/mat-selectors' as ms;
.adf-pdf-viewer {
width: 100%;
diff --git a/lib/core/src/lib/viewer/components/pdf-viewer/pdf-viewer.component.spec.ts b/lib/core/viewer/pdf/src/lib/components/pdf-viewer/pdf-viewer.component.spec.ts
similarity index 99%
rename from lib/core/src/lib/viewer/components/pdf-viewer/pdf-viewer.component.spec.ts
rename to lib/core/viewer/pdf/src/lib/components/pdf-viewer/pdf-viewer.component.spec.ts
index 74084eb5ac..3ddc7ce33a 100644
--- a/lib/core/src/lib/viewer/components/pdf-viewer/pdf-viewer.component.spec.ts
+++ b/lib/core/viewer/pdf/src/lib/components/pdf-viewer/pdf-viewer.component.spec.ts
@@ -21,9 +21,7 @@ import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core
import { MatDialog } from '@angular/material/dialog';
import { By } from '@angular/platform-browser';
import { of } from 'rxjs';
-import { AppConfigService } from '../../../app-config';
-import { EventMock } from '../../../mock';
-import { UnitTestingUtils, provideCoreAuthTesting } from '../../../testing';
+import { AppConfigService, EventMock, UnitTestingUtils, provideCoreAuthTesting } from '@alfresco/adf-core';
import { RenderingQueueServices } from '../../services/rendering-queue.services';
import { PdfThumbListComponent } from '../pdf-viewer-thumbnails/pdf-viewer-thumbnails.component';
import { PDFJS_MODULE, PDFJS_VIEWER_MODULE, PdfViewerComponent } from './pdf-viewer.component';
@@ -461,7 +459,7 @@ describe('Test PdfViewer - User interaction', () => {
component.urlFile = './fake-test-file.pdf';
fixture.detectChanges();
- component.ngOnChanges({
+ component.ngOnChanges({
urlFile: new SimpleChange(null, './fake-test-file.pdf', true)
});
diff --git a/lib/core/src/lib/viewer/components/pdf-viewer/pdf-viewer.component.ts b/lib/core/viewer/pdf/src/lib/components/pdf-viewer/pdf-viewer.component.ts
similarity index 98%
rename from lib/core/src/lib/viewer/components/pdf-viewer/pdf-viewer.component.ts
rename to lib/core/viewer/pdf/src/lib/components/pdf-viewer/pdf-viewer.component.ts
index df4ac9a15b..bc38bb7507 100644
--- a/lib/core/src/lib/viewer/components/pdf-viewer/pdf-viewer.component.ts
+++ b/lib/core/viewer/pdf/src/lib/components/pdf-viewer/pdf-viewer.component.ts
@@ -40,8 +40,7 @@ import { MatProgressBarModule } from '@angular/material/progress-bar';
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
import { from, Subject, switchMap } from 'rxjs';
import { catchError } from 'rxjs/operators';
-import { AppConfigService } from '../../../app-config';
-import { ToolbarComponent, ToolbarDividerComponent } from '../../../toolbar';
+import { AppConfigService, IconModule, ToolbarComponent, ToolbarDividerComponent } from '@alfresco/adf-core';
import { RenderingQueueServices } from '../../services/rendering-queue.services';
import { PdfPasswordDialogComponent } from '../pdf-viewer-password-dialog/pdf-viewer-password-dialog';
import { PdfThumbListComponent } from '../pdf-viewer-thumbnails/pdf-viewer-thumbnails.component';
@@ -49,7 +48,6 @@ import * as pdfjsLib from 'pdfjs-dist/build/pdf.min.mjs';
import { PDFDateString } from 'pdfjs-dist/build/pdf.min.mjs';
import { EventBus, PDFViewer } from 'pdfjs-dist/web/pdf_viewer.mjs';
import { OnProgressParameters, PDFDocumentLoadingTask, PDFDocumentProxy, PDFPageProxy } from 'pdfjs-dist/types/src/display/api';
-import { IconModule } from '../../../icon/icon.module';
export type PdfScaleMode = 'init' | 'page-actual' | 'page-width' | 'page-height' | 'page-fit' | 'auto';
@@ -630,7 +628,7 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
* @param event.source.container.id - the container id
*/
onPageChange(event: PageChangingEvent) {
- if (event.source && event.source.container.id === `${this.randomPdfId}-viewer-pdf-viewer`) {
+ if (event.source?.container.id === `${this.randomPdfId}-viewer-pdf-viewer`) {
this.page = event.pageNumber;
this.displayPage = event.pageNumber;
}
diff --git a/lib/core/viewer/pdf/src/lib/provide-pdf-viewer.ts b/lib/core/viewer/pdf/src/lib/provide-pdf-viewer.ts
new file mode 100644
index 0000000000..68740dfa32
--- /dev/null
+++ b/lib/core/viewer/pdf/src/lib/provide-pdf-viewer.ts
@@ -0,0 +1,25 @@
+/*!
+ * @license
+ * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Provider } from '@angular/core';
+import { PDF_VIEWER_COMPONENT } from '@alfresco/adf-core';
+import { PdfViewerComponent } from './components/pdf-viewer/pdf-viewer.component';
+
+/** @returns Provider that registers the PdfViewerComponent for the viewer */
+export function providePdfViewer(): Provider {
+ return { provide: PDF_VIEWER_COMPONENT, useValue: PdfViewerComponent };
+}
diff --git a/lib/core/src/lib/viewer/services/rendering-queue.services.ts b/lib/core/viewer/pdf/src/lib/services/rendering-queue.services.ts
similarity index 100%
rename from lib/core/src/lib/viewer/services/rendering-queue.services.ts
rename to lib/core/viewer/pdf/src/lib/services/rendering-queue.services.ts
diff --git a/lib/insights/ng-package.json b/lib/insights/ng-package.json
index a5805e7386..bf17446cd1 100644
--- a/lib/insights/ng-package.json
+++ b/lib/insights/ng-package.json
@@ -21,9 +21,6 @@
]
},
"allowedNonPeerDependencies": [
- "@alfresco/adf-core",
- "@alfresco/adf-content-services",
- "@ngx-translate/core",
"chart.js",
"ng2-charts",
"raphael"
diff --git a/lib/insights/package.json b/lib/insights/package.json
index c6cbfde300..54a5509d18 100644
--- a/lib/insights/package.json
+++ b/lib/insights/package.json
@@ -11,9 +11,6 @@
"url": "https://github.com/Alfresco/alfresco-ng2-components/issues"
},
"dependencies": {
- "@alfresco/adf-core": ">=8.4.1",
- "@alfresco/adf-content-services": ">=8.4.1",
- "@ngx-translate/core": ">=17.0.0",
"chart.js": "^4.3.0",
"ng2-charts": "^4.1.1",
"raphael": ">=2.3.0"
@@ -23,7 +20,10 @@
"@angular/compiler": ">=14.1.3",
"@angular/core": ">=14.1.3",
"@angular/forms": ">=14.1.3",
- "@angular/material": ">=14.1.2"
+ "@angular/material": ">=14.1.2",
+ "@alfresco/adf-core": ">=8.4.1",
+ "@alfresco/adf-content-services": ">=8.4.1",
+ "@ngx-translate/core": ">=17.0.0"
},
"keywords": [
"analytics",
diff --git a/lib/process-services-cloud/ng-package.json b/lib/process-services-cloud/ng-package.json
index 6b88db1399..cbec8864b3 100644
--- a/lib/process-services-cloud/ng-package.json
+++ b/lib/process-services-cloud/ng-package.json
@@ -26,8 +26,5 @@
"../core/src/lib"
]
},
- "allowedNonPeerDependencies": [
- "@apollo/client",
- "apollo-angular"
- ]
+ "allowedNonPeerDependencies": []
}
diff --git a/tsconfig.json b/tsconfig.json
index 4ea511f3bd..97d311250c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -30,6 +30,7 @@
"@alfresco/adf-core/breadcrumbs": ["lib/core/breadcrumbs/src/index.ts"],
"@alfresco/adf-core/feature-flags": ["lib/core/feature-flags/public-api.ts"],
"@alfresco/adf-core/shell": ["lib/core/shell/src/index.ts"],
+ "@alfresco/adf-core/viewer/pdf": ["lib/core/viewer/pdf/src/index.ts"],
"@alfresco/adf-extensions": ["lib/extensions/src/public-api.ts"],
"@alfresco/adf-insights": ["lib/insights/src/public-api.ts"],
"@alfresco/adf-process-services": ["lib/process-services/src/public-api.ts"],